Add Support for multiple signatures

This commit is contained in:
Arthur Ozga
2016-12-06 19:46:08 -08:00
parent f0c771303d
commit c511aea581
7 changed files with 149 additions and 29 deletions

View File

@@ -88,6 +88,7 @@ namespace ts {
getReturnTypeOfSignature,
getNonNullableType,
getSymbolsInScope,
createSymbol,
getSymbolAtLocation,
getShorthandAssignmentValueSymbol,
getExportSpecifierLocalTargetSymbol,

View File

@@ -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[];

View File

@@ -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<ParameterDeclaration>();
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 {

View File

@@ -1,13 +1,18 @@
/// <reference path='fourslash.ts' />
//// 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.');
}
`);
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");
}
`);

View File

@@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
//// 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.');
}
`);

View File

@@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
//// 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.');
}
`);

View File

@@ -1,17 +1,5 @@
/// <reference path='fourslash.ts' />
//// 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.');
}
`);