From 03511c0b092a33e3e1fad2f61406760f2142c769 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 16:36:03 -0700 Subject: [PATCH] snythesize methods in missing member fix --- src/compiler/factory.ts | 5 +- src/services/codefixes/helpers.ts | 123 ++++++++++++++++++------------ 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 19e8ef25da0..f59eea8461b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -45,7 +45,10 @@ namespace ts { * Creates a shallow, memberwise clone of a node with no source map location. */ /* @internal */ - export function getSynthesizedClone(node: T): T { + export function getSynthesizedClone(node: T | undefined): T { + if(node === undefined) { + return undefined; + } // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 0af13bf41e4..27195ac0be0 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -28,7 +28,7 @@ namespace ts.codefix { let newNodes: Node[] = []; for (const symbol of missingMembers) { - const newNode = getNewNodeForMemberSymbol(symbol, classDeclaration, checker); + const newNode = createNewNodeForMemberSymbol(symbol, classDeclaration, checker); if (newNode) { if (Array.isArray(newNode)) { newNodes = newNodes.concat(newNode); @@ -44,7 +44,7 @@ namespace ts.codefix { /** * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`. */ - function getNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { + function createNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; @@ -52,8 +52,7 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; const name = declaration.name ? declaration.name.getText() : undefined; - const visibility = getVisibilityPrefixWithSpace(getModifierFlags(declaration)); - + const modifiers = [createVisibilityModifier(getModifierFlags(declaration))]; const type = checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration); switch (declaration.kind) { @@ -86,43 +85,55 @@ namespace ts.codefix { } if (declarations.length === 1) { Debug.assert(signatures.length === 1); - // TODO: extract signature declaration from a signature. - // const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + // TODO: suppress any return type // TODO: get parameters working. // TODO: add support for type parameters. - return createStubbedMethod([visibility], name, /*typeParameters*/undefined, []); + const signature = signatures[0]; + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + return createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType); } let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { // const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); // TODO: make signatures instead of methods - signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); + const signature = signatures[i]; + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + signatureDeclarations.push(createMethod( + /*decorators*/ undefined + , modifiers + , /*asteriskToken*/ undefined + , name + , /*typeParameters*/undefined + , newParameterNodes + , returnType + , /*body*/undefined)); } - // If there is a declaration with a body, it is the last declaration, - // and it isn't caught by `getSignaturesOfType`. - let bodySig: Signature | undefined = undefined; if (declarations.length > signatures.length) { - bodySig = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); + let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + signatureDeclarations.push(createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType)); } else { Debug.assert(declarations.length === signatures.length); - bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker); + const methodImplementingSignatures = createMethodImplementingSignatures(signatures, enclosingDeclaration, name, modifiers); + signatureDeclarations.push(methodImplementingSignatures); } - // const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); - signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); - return signatureDeclarations; default: return undefined; } } - function createBodySignatureWithAnyTypes(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Signature { - const newSignatureDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; - newSignatureDeclaration.parent = enclosingDeclaration; - newSignatureDeclaration.name = signatures[0].getDeclaration().name; + // TODO: infer types of arguments? + function createMethodImplementingSignatures(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, name: string, modifiers: Modifier[] | undefined): MethodDeclaration { + const newMethodDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; + newMethodDeclaration.parent = enclosingDeclaration; + newMethodDeclaration.name = signatures[0].getDeclaration().name; let maxNonRestArgs = -1; let maxArgsIndex = 0; @@ -133,42 +144,47 @@ namespace ts.codefix { minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); hasRestParameter = hasRestParameter || sig.hasRestParameter; const nonRestLength = sig.parameters.length - (sig.hasRestParameter ? 1 : 0); - if (nonRestLength > maxNonRestArgs) { + if (nonRestLength >= maxNonRestArgs) { maxNonRestArgs = nonRestLength; maxArgsIndex = i; } } const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName()); - const optionalToken = createToken(SyntaxKind.QuestionToken); - - newSignatureDeclaration.parameters = createNodeArray(); + const parameters = createNodeArray(); for (let i = 0; i < maxNonRestArgs; i++) { - const newParameter = createParameterDeclarationWithoutType(i, minArgumentCount, newSignatureDeclaration); - newSignatureDeclaration.parameters.push(newParameter); + const newParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , maxArgsParameterSymbolNames[i] + , /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined + , /*type*/ undefined + , /*initializer*/ undefined); + parameters.push(newParameter); } if (hasRestParameter) { - const restParameter = createParameterDeclarationWithoutType(maxNonRestArgs, minArgumentCount, newSignatureDeclaration); - restParameter.dotDotDotToken = createToken(SyntaxKind.DotDotDotToken); - newSignatureDeclaration.parameters.push(restParameter); + const restParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , createToken(SyntaxKind.DotDotDotToken) + , maxArgsParameterSymbolNames[maxNonRestArgs] || "rest" + , /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined + , /*type*/ undefined + , /*initializer*/ undefined); + parameters.push(restParameter); } - return checker.getSignatureFromDeclaration(newSignatureDeclaration); - - function createParameterDeclarationWithoutType(index: number, minArgCount: number, enclosingSignatureDeclaration: SignatureDeclaration): ParameterDeclaration { - const newParameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration; - - newParameter.symbol = new SymbolConstructor(SymbolFlags.FunctionScopedVariable, maxArgsParameterSymbolNames[index] || "rest"); - newParameter.symbol.valueDeclaration = newParameter; - newParameter.symbol.declarations = [newParameter]; - newParameter.parent = enclosingSignatureDeclaration; - if (index >= minArgCount) { - newParameter.questionToken = optionalToken; - } - - return newParameter; - } + return createMethod( + /*decorators*/ undefined + , modifiers + , /*asteriskToken*/ undefined + , name + , /*typeParameters*/undefined + , parameters + , /*type*/ undefined + , /*body*/undefined); } export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType?: TypeNode) { @@ -187,13 +203,13 @@ namespace ts.codefix { return createBlock( [createThrow( createNew( - createIdentifier('Error') + createIdentifier('Error') , /*typeArguments*/undefined , [createLiteral('Method not implemented.')]))] , /*multiline*/true); } - function getVisibilityPrefixWithSpace(flags: ModifierFlags) { + function createVisibilityModifier(flags: ModifierFlags) { if (flags & ModifierFlags.Public) { return createToken(SyntaxKind.PublicKeyword); } @@ -203,5 +219,18 @@ namespace ts.codefix { return undefined; } - const SymbolConstructor = objectAllocator.getSymbolConstructor(); + function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker) { + const parameterDeclaration = parameterSymbol.getDeclarations()[0] as ParameterDeclaration; + const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration); + const parameterTypeNode = checker.createTypeNode(parameterType); + // TODO: deep cloning of decorators/any node. + const parameterNode = createParameter( + parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedClone) + , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone) + , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) + , parameterDeclaration.name + , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) + , parameterTypeNode); + return parameterNode; + } } \ No newline at end of file