Skip to content

Commit c511aea

Browse files
author
Arthur Ozga
committed
Add Support for multiple signatures
1 parent f0c7713 commit c511aea

File tree

7 files changed

+149
-29
lines changed

7 files changed

+149
-29
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ namespace ts {
8888
getReturnTypeOfSignature,
8989
getNonNullableType,
9090
getSymbolsInScope,
91+
createSymbol,
9192
getSymbolAtLocation,
9293
getShorthandAssignmentValueSymbol,
9394
getExportSpecifierLocalTargetSymbol,

src/compiler/types.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -640,9 +640,9 @@ namespace ts {
640640

641641
export interface ParameterDeclaration extends Declaration {
642642
kind: SyntaxKind.Parameter;
643-
dotDotDotToken?: DotDotDotToken; // Present on rest parameter
643+
dotDotDotToken?: DotDotDotToken; // Present on rest parameter
644644
name: BindingName; // Declared parameter name
645-
questionToken?: QuestionToken; // Present on optional parameter
645+
questionToken?: QuestionToken; // Present on optional parameter
646646
type?: TypeNode; // Optional type annotation
647647
initializer?: Expression; // Optional initializer
648648
}
@@ -658,14 +658,14 @@ namespace ts {
658658
export interface PropertySignature extends TypeElement {
659659
kind: SyntaxKind.PropertySignature | SyntaxKind.JSDocRecordMember;
660660
name: PropertyName; // Declared property name
661-
questionToken?: QuestionToken; // Present on optional property
661+
questionToken?: QuestionToken; // Present on optional property
662662
type?: TypeNode; // Optional type annotation
663663
initializer?: Expression; // Optional initializer
664664
}
665665

666666
export interface PropertyDeclaration extends ClassElement {
667667
kind: SyntaxKind.PropertyDeclaration;
668-
questionToken?: QuestionToken; // Present for use with reporting a grammar error
668+
questionToken?: QuestionToken; // Present for use with reporting a grammar error
669669
name: PropertyName;
670670
type?: TypeNode;
671671
initializer?: Expression; // Optional initializer
@@ -2354,6 +2354,7 @@ namespace ts {
23542354
signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string;
23552355
typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string;
23562356
symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string;
2357+
createSymbol(flags: SymbolFlags, name: string): Symbol;
23572358
getSymbolDisplayBuilder(): SymbolDisplayBuilder;
23582359
getFullyQualifiedName(symbol: Symbol): string;
23592360
getAugmentedPropertiesOfType(type: Type): Symbol[];

src/services/codefixes/helpers.ts

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,42 @@ namespace ts.codefix {
3838

3939
case SyntaxKind.MethodSignature:
4040
case SyntaxKind.MethodDeclaration:
41+
// The signature for the implementation appears as an entry in `signatures` iff
42+
// there is only one signature.
43+
// If there are overloads and an implementation signature, it appears as an
44+
// extra declaration that isn't a signature for `type`.
45+
// If there is more than one overload but no implementation signature
46+
// (eg: an abstract method or interface declaration), there is a 1-1
47+
// correspondence of declarations and signatures.
4148
const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
4249
if (!(signatures && signatures.length > 0)) {
4350
return "";
4451
}
45-
// TODO: (arozga) Deal with multiple signatures.
46-
const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
52+
if (declarations.length === 1) {
53+
Debug.assert(signatures.length === 1);
54+
const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
55+
return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
56+
}
57+
58+
let result = "";
59+
for (let i = 0; i < signatures.length; i++) {
60+
const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
61+
result += `${visibility}${name}${sigString};${newlineChar}`;
62+
}
63+
64+
// If there is a declaration with a body, it is the last declaration,
65+
// and it isn't caught by `getSignaturesOfType`.
66+
let bodySig: Signature | undefined = undefined;
67+
if (declarations.length > signatures.length) {
68+
bodySig = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration);
69+
}
70+
else {
71+
bodySig = createBodyDeclarationSignatureWithAnyTypes(declarations as SignatureDeclaration[], signatures, enclosingDeclaration, checker);
72+
}
73+
const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call);
74+
result += `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
4775

48-
return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`;
76+
return result;
4977
case SyntaxKind.ComputedPropertyName:
5078
if (hasDynamicName(node)) {
5179
return "";
@@ -59,8 +87,73 @@ namespace ts.codefix {
5987
}
6088
}
6189

90+
function createBodyDeclarationSignatureWithAnyTypes(signatureDecls: SignatureDeclaration[], signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Signature {
91+
Debug.assert(signatureDecls.length === signatures.length);
92+
93+
const newSignatureDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration;
94+
newSignatureDeclaration.parent = enclosingDeclaration;
95+
newSignatureDeclaration.name = signatureDecls[0].name;
96+
97+
let maxArgs = -1, maxArgsIndex = 0;
98+
let minArgumentCount = signatures[0].minArgumentCount;
99+
let hasRestParameter = false;
100+
for (let i = 0; i < signatures.length; i++) {
101+
const sig = signatures[i];
102+
minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount);
103+
if (sig.parameters.length > maxArgs) {
104+
maxArgs = sig.parameters.length;
105+
maxArgsIndex = i;
106+
}
107+
hasRestParameter = hasRestParameter || sig.hasRestParameter;
108+
}
109+
110+
const anyTypeNode: TypeNode = createNode(SyntaxKind.AnyKeyword) as TypeNode;
111+
const optionalToken = createToken(SyntaxKind.QuestionToken);
112+
113+
newSignatureDeclaration.parameters = createNodeArray<ParameterDeclaration>();
114+
for (let i = 0; i < maxArgs - 1; i++) {
115+
const newParameter = createParameterDeclaration(i, minArgumentCount, newSignatureDeclaration);
116+
newSignatureDeclaration.parameters.push(newParameter);
117+
}
118+
119+
const lastParameter = createParameterDeclaration(maxArgs - 1, minArgumentCount, newSignatureDeclaration);
120+
if (hasRestParameter) {
121+
lastParameter.dotDotDotToken = createToken(SyntaxKind.DotDotDotToken);
122+
123+
let allMaxArgsAreRest = true;
124+
for (const sig of signatures) {
125+
allMaxArgsAreRest = allMaxArgsAreRest && sig.parameters[maxArgs - 1] && sig.hasRestParameter;
126+
}
127+
if (!allMaxArgsAreRest) {
128+
const newParameter = createParameterDeclaration(maxArgs - 1, minArgumentCount, newSignatureDeclaration);
129+
newSignatureDeclaration.parameters.push(newParameter);
130+
}
131+
}
132+
133+
newSignatureDeclaration.parameters.push(lastParameter);
134+
135+
newSignatureDeclaration.type = anyTypeNode;
136+
newSignatureDeclaration.type.parent = newSignatureDeclaration;
137+
138+
return checker.getSignatureFromDeclaration(newSignatureDeclaration);
139+
140+
function createParameterDeclaration(index: number, minArgCount: number, enclosingSignature: SignatureDeclaration): ParameterDeclaration {
141+
const newParameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration;
142+
newParameter.symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, "arg" + index);
143+
newParameter.symbol.valueDeclaration = newParameter;
144+
newParameter.symbol.declarations = [newParameter];
145+
newParameter.type = anyTypeNode;
146+
newParameter.parent = enclosingSignature;
147+
if (index >= minArgCount) {
148+
newParameter.questionToken = optionalToken;
149+
}
150+
151+
return newParameter;
152+
}
153+
}
154+
62155
function getMethodBodyStub(newLineChar: string) {
63-
return `{${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`;
156+
return ` {${newLineChar}throw new Error('Method not implemented.');${newLineChar}}${newLineChar}`;
64157
}
65158

66159
function getVisibilityPrefix(flags: ModifierFlags): string {
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
/// <reference path='fourslash.ts' />
22

33
//// abstract class A {
4-
//// abstract f();
4+
//// abstract f(a: number, b: string): boolean;
5+
//// abstract f(a: string, b: number): Function;
6+
//// abstract f(a: string): Function;
57
//// }
68
////
7-
//// class C extends A {[|
8-
//// |]}
9+
//// class C extends A {[| |]}
910

10-
verify.rangeAfterCodeFix(`f(){
11-
throw new Error('Method not implemented.');
12-
}
13-
`);
11+
verify.rangeAfterCodeFix(`
12+
f(a: number, b: string): boolean;
13+
f(a: string, b: number): Function;
14+
f(a: string): Function;
15+
f(arg0: any, arg1? any) {
16+
throw new Error("Method not implemented");
17+
}
18+
`);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// method(a: number, b: string): boolean;
5+
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
6+
////
7+
//// class C implements A {[| |]}
8+
9+
verify.rangeAfterCodeFix(`
10+
method(a: number, b: string): boolean;
11+
method(a: string | number, b?: string | number): boolean | Function {
12+
throw new Error('Method not implemented.');
13+
}
14+
`);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// method(a: any, b: string): boolean;
5+
//// method(a: string, b: number): Function;
6+
//// method(a: string): Function;
7+
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
8+
////
9+
//// class C implements A {[| |]}
10+
11+
verify.rangeAfterCodeFix(`
12+
method(a: any, b: string): boolean;
13+
method(a: string, b: number): Function;
14+
method(a: string): Function;
15+
method(a: string | number, b?: string | number): boolean | Function {
16+
throw new Error('Method not implemented.');
17+
}
18+
`);
Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
/// <reference path='fourslash.ts' />
22

3-
//// namespace N1 {
4-
//// export interface I1 {
5-
//// f1():string;
6-
//// }
7-
//// }
8-
//// interface I1 {
9-
//// f1();
10-
//// }
11-
////
12-
//// class C1 implements N1.I1 {[|
13-
//// |]}
14-
153
//// interface I {
164
//// method(a: number, b: string): boolean;
175
//// method(a: string, b: number): Function;
@@ -24,7 +12,7 @@ verify.rangeAfterCodeFix(`
2412
method(a: number, b: string): boolean;
2513
method(a: string, b: number): Function;
2614
method(a: string): Function;
27-
method(a: number | string, b?: string | number): boolean | Function {
28-
throw new Error("Method not implemented");
15+
method(arg0: any, arg1?: any) {
16+
throw new Error('Method not implemented.');
2917
}
3018
`);

0 commit comments

Comments
 (0)