diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f1a1783c45..0834322ff48 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -88,6 +88,7 @@ namespace ts { getReturnTypeOfSignature, getNonNullableType, getSymbolsInScope, + createSymbol, getSymbolAtLocation, getShorthandAssignmentValueSymbol, getExportSpecifierLocalTargetSymbol, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 65ac6460f5c..4adf4f7ac95 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -640,9 +640,9 @@ namespace ts { export interface ParameterDeclaration extends Declaration { kind: SyntaxKind.Parameter; - dotDotDotToken?: DotDotDotToken; // Present on rest parameter + dotDotDotToken?: DotDotDotToken; // Present on rest parameter name: BindingName; // Declared parameter name - questionToken?: QuestionToken; // Present on optional parameter + questionToken?: QuestionToken; // Present on optional parameter type?: TypeNode; // Optional type annotation initializer?: Expression; // Optional initializer } @@ -658,14 +658,14 @@ namespace ts { export interface PropertySignature extends TypeElement { kind: SyntaxKind.PropertySignature | SyntaxKind.JSDocRecordMember; name: PropertyName; // Declared property name - questionToken?: QuestionToken; // Present on optional property + questionToken?: QuestionToken; // Present on optional property type?: TypeNode; // Optional type annotation initializer?: Expression; // Optional initializer } export interface PropertyDeclaration extends ClassElement { kind: SyntaxKind.PropertyDeclaration; - questionToken?: QuestionToken; // Present for use with reporting a grammar error + questionToken?: QuestionToken; // Present for use with reporting a grammar error name: PropertyName; type?: TypeNode; initializer?: Expression; // Optional initializer @@ -2354,6 +2354,7 @@ namespace ts { signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + createSymbol(flags: SymbolFlags, name: string): Symbol; getSymbolDisplayBuilder(): SymbolDisplayBuilder; getFullyQualifiedName(symbol: Symbol): string; getAugmentedPropertiesOfType(type: Type): Symbol[]; diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index f4d50b57dbf..7e4cf36f1f0 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -38,14 +38,42 @@ namespace ts.codefix { case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: + // The signature for the implementation appears as an entry in `signatures` iff + // there is only one signature. + // If there are overloads and an implementation signature, it appears as an + // extra declaration that isn't a signature for `type`. + // If there is more than one overload but no implementation signature + // (eg: an abstract method or interface declaration), there is a 1-1 + // correspondence of declarations and signatures. const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); if (!(signatures && signatures.length > 0)) { return ""; } - // TODO: (arozga) Deal with multiple signatures. - const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + if (declarations.length === 1) { + Debug.assert(signatures.length === 1); + const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`; + } - return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`; + let result = ""; + for (let i = 0; i < signatures.length; i++) { + const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + result += `${visibility}${name}${sigString};${newlineChar}`; + } + + // 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); + } + else { + bodySig = createBodyDeclarationSignatureWithAnyTypes(declarations as SignatureDeclaration[], signatures, enclosingDeclaration, checker); + } + const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + result += `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`; + + return result; case SyntaxKind.ComputedPropertyName: if (hasDynamicName(node)) { return ""; @@ -59,8 +87,73 @@ namespace ts.codefix { } } + function createBodyDeclarationSignatureWithAnyTypes(signatureDecls: SignatureDeclaration[], signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Signature { + Debug.assert(signatureDecls.length === signatures.length); + + const newSignatureDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; + newSignatureDeclaration.parent = enclosingDeclaration; + newSignatureDeclaration.name = signatureDecls[0].name; + + let maxArgs = -1, maxArgsIndex = 0; + let minArgumentCount = signatures[0].minArgumentCount; + let hasRestParameter = false; + for (let i = 0; i < signatures.length; i++) { + const sig = signatures[i]; + minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); + if (sig.parameters.length > maxArgs) { + maxArgs = sig.parameters.length; + maxArgsIndex = i; + } + hasRestParameter = hasRestParameter || sig.hasRestParameter; + } + + const anyTypeNode: TypeNode = createNode(SyntaxKind.AnyKeyword) as TypeNode; + const optionalToken = createToken(SyntaxKind.QuestionToken); + + newSignatureDeclaration.parameters = createNodeArray(); + for (let i = 0; i < maxArgs - 1; i++) { + const newParameter = createParameterDeclaration(i, minArgumentCount, newSignatureDeclaration); + newSignatureDeclaration.parameters.push(newParameter); + } + + const lastParameter = createParameterDeclaration(maxArgs - 1, minArgumentCount, newSignatureDeclaration); + if (hasRestParameter) { + lastParameter.dotDotDotToken = createToken(SyntaxKind.DotDotDotToken); + + let allMaxArgsAreRest = true; + for (const sig of signatures) { + allMaxArgsAreRest = allMaxArgsAreRest && sig.parameters[maxArgs - 1] && sig.hasRestParameter; + } + if (!allMaxArgsAreRest) { + const newParameter = createParameterDeclaration(maxArgs - 1, minArgumentCount, newSignatureDeclaration); + newSignatureDeclaration.parameters.push(newParameter); + } + } + + newSignatureDeclaration.parameters.push(lastParameter); + + newSignatureDeclaration.type = anyTypeNode; + newSignatureDeclaration.type.parent = newSignatureDeclaration; + + return checker.getSignatureFromDeclaration(newSignatureDeclaration); + + function createParameterDeclaration(index: number, minArgCount: number, enclosingSignature: SignatureDeclaration): ParameterDeclaration { + const newParameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration; + newParameter.symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, "arg" + index); + newParameter.symbol.valueDeclaration = newParameter; + newParameter.symbol.declarations = [newParameter]; + newParameter.type = anyTypeNode; + newParameter.parent = enclosingSignature; + if (index >= minArgCount) { + newParameter.questionToken = optionalToken; + } + + return newParameter; + } + } + function getMethodBodyStub(newLineChar: string) { - return `{${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`; + return ` {${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`; } function getVisibilityPrefix(flags: ModifierFlags): string { diff --git a/tests/cases/fourslash/codeFixClassExtendsAbstractMethod.ts b/tests/cases/fourslash/codeFixClassExtendsAbstractMethod.ts index cf1322ff8b3..bf9eba11c8e 100644 --- a/tests/cases/fourslash/codeFixClassExtendsAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassExtendsAbstractMethod.ts @@ -1,13 +1,18 @@ /// //// abstract class A { -//// abstract f(); +//// abstract f(a: number, b: string): boolean; +//// abstract f(a: string, b: number): Function; +//// abstract f(a: string): Function; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} -verify.rangeAfterCodeFix(`f(){ - throw new Error('Method not implemented.'); -} -`); \ No newline at end of file +verify.rangeAfterCodeFix(` + f(a: number, b: string): boolean; + f(a: string, b: number): Function; + f(a: string): Function; + f(arg0: any, arg1? any) { + throw new Error("Method not implemented"); + } +`); diff --git a/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures1.ts b/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures1.ts new file mode 100644 index 00000000000..82ecde6c418 --- /dev/null +++ b/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures1.ts @@ -0,0 +1,14 @@ +/// + +//// class A { +//// method(a: number, b: string): boolean; +//// method(a: string | number, b?: string | number): boolean | Function { return true; } +//// +//// class C implements A {[| |]} + +verify.rangeAfterCodeFix(` + method(a: number, b: string): boolean; + method(a: string | number, b?: string | number): boolean | Function { + throw new Error('Method not implemented.'); + } +`); diff --git a/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures2.ts b/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures2.ts new file mode 100644 index 00000000000..f187e0ca0f1 --- /dev/null +++ b/tests/cases/fourslash/codeFixUnImplementedClassMultipleSignatures2.ts @@ -0,0 +1,18 @@ +/// + +//// class A { +//// method(a: any, b: string): boolean; +//// method(a: string, b: number): Function; +//// method(a: string): Function; +//// method(a: string | number, b?: string | number): boolean | Function { return true; } +//// +//// class C implements A {[| |]} + +verify.rangeAfterCodeFix(` + method(a: any, b: string): boolean; + method(a: string, b: number): Function; + method(a: string): Function; + method(a: string | number, b?: string | number): boolean | Function { + throw new Error('Method not implemented.'); + } +`); diff --git a/tests/cases/fourslash/codeFixUnImplementedInterfaceMultipleSignatures.ts b/tests/cases/fourslash/codeFixUnImplementedInterfaceMultipleSignatures.ts index a7142931eed..ce777567e1e 100644 --- a/tests/cases/fourslash/codeFixUnImplementedInterfaceMultipleSignatures.ts +++ b/tests/cases/fourslash/codeFixUnImplementedInterfaceMultipleSignatures.ts @@ -1,17 +1,5 @@ /// -//// namespace N1 { -//// export interface I1 { -//// f1():string; -//// } -//// } -//// interface I1 { -//// f1(); -//// } -//// -//// class C1 implements N1.I1 {[| -//// |]} - //// interface I { //// method(a: number, b: string): boolean; //// method(a: string, b: number): Function; @@ -24,7 +12,7 @@ verify.rangeAfterCodeFix(` method(a: number, b: string): boolean; method(a: string, b: number): Function; method(a: string): Function; - method(a: number | string, b?: string | number): boolean | Function { - throw new Error("Method not implemented"); + method(arg0: any, arg1?: any) { + throw new Error('Method not implemented.'); } `);