From ea2aa0c0791d2dda31d4dc2132488dc7c18957e4 Mon Sep 17 00:00:00 2001 From: rbuckton Date: Wed, 29 Mar 2017 18:13:19 -0700 Subject: [PATCH 1/3] Infer a rest parameter for javascript function that uses 'arguments' --- src/compiler/checker.ts | 62 ++++++++++++++++--- src/compiler/types.ts | 4 +- .../signatureHelpCallExpressionJs.ts | 15 +++++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/signatureHelpCallExpressionJs.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45c5fe0afa..5010a8528a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -493,6 +493,10 @@ namespace ts { return symbol; } + function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { + return (symbol.flags & SymbolFlags.Transient) !== 0; + } + function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { let result: SymbolFlags = 0; if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; @@ -3385,23 +3389,23 @@ namespace ts { function buildParameterDisplay(p: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { const parameterNode = p.valueDeclaration; - if (isRestParameter(parameterNode)) { + if (parameterNode ? isRestParameter(parameterNode) : isTransientSymbol(p) && p.isRestParameter) { writePunctuation(writer, SyntaxKind.DotDotDotToken); } - if (isBindingPattern(parameterNode.name)) { + if (parameterNode && isBindingPattern(parameterNode.name)) { buildBindingPatternDisplay(parameterNode.name, writer, enclosingDeclaration, flags, symbolStack); } else { appendSymbolNameOnly(p, writer); } - if (isOptionalParameter(parameterNode)) { + if (parameterNode && isOptionalParameter(parameterNode)) { writePunctuation(writer, SyntaxKind.QuestionToken); } writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); let type = getTypeOfSymbol(p); - if (isRequiredInitializedParameter(parameterNode)) { + if (parameterNode && isRequiredInitializedParameter(parameterNode)) { type = includeFalsyTypes(type, TypeFlags.Undefined); } buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); @@ -6170,6 +6174,37 @@ namespace ts { } } + function containsArgumentsReference(declaration: FunctionLikeDeclaration): boolean { + const links = getNodeLinks(declaration); + if (links.containsArgumentsReference === undefined) { + if (links.flags & NodeCheckFlags.CaptureArguments) { + links.containsArgumentsReference = true; + } + else { + links.containsArgumentsReference = traverse(declaration.body); + } + } + return links.containsArgumentsReference; + + function traverse(node: Node): boolean { + if (!node) return false; + switch (node.kind) { + case SyntaxKind.Identifier: + return (node).text === "arguments" && isPartOfExpression(node); + + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return (node).name.kind === SyntaxKind.ComputedPropertyName + && traverse((node).name); + + default: + return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && forEachChild(node, traverse); + } + } + } + function getSignaturesOfSymbol(symbol: Symbol): Signature[] { if (!symbol) return emptyArray; const result: Signature[] = []; @@ -11613,9 +11648,7 @@ namespace ts { } } - if (node.flags & NodeFlags.AwaitContext) { - getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; - } + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; return getTypeOfSymbol(symbol); } @@ -14854,6 +14887,21 @@ namespace ts { } } + if (signatures.length === 1) { + const declaration = signatures[0].declaration; + if (declaration && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration)) { + if (containsArgumentsReference(declaration)) { + const signatureWithRest = cloneSignature(signatures[0]); + const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args"); + syntheticArgsSymbol.type = anyArrayType; + syntheticArgsSymbol.isRestParameter = true; + signatureWithRest.parameters = concatenate(signatureWithRest.parameters, [syntheticArgsSymbol]); + signatureWithRest.hasRestParameter = true; + signatures = [signatureWithRest]; + } + } + } + const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly reorderCandidates(signatures, candidates); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 10bfcfdb40..dad59dcd63 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2870,6 +2870,7 @@ namespace ts { /* @internal */ export interface TransientSymbol extends Symbol, SymbolLinks { checkFlags: CheckFlags; + isRestParameter?: boolean; } export type SymbolTable = Map; @@ -2899,7 +2900,7 @@ namespace ts { ContextChecked = 0x00000400, // Contextual types have been assigned AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'. AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'. - CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions) + CaptureArguments = 0x00002000, // Lexical 'arguments' used in body EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them. LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration. LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure @@ -2923,6 +2924,7 @@ namespace ts { maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate enumMemberValue?: number; // Constant value of enum member isVisible?: boolean; // Is this node visible + containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element diff --git a/tests/cases/fourslash/signatureHelpCallExpressionJs.ts b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts new file mode 100644 index 0000000000..8c1afa5544 --- /dev/null +++ b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts @@ -0,0 +1,15 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: main.js +////function fnTest() { arguments; } +////fnTest(/*1*/); + +goTo.marker('1'); +verify.signatureHelpCountIs(1); +verify.currentSignatureParameterCountIs(1); +verify.currentSignatureHelpIs('fnTest(...args: any[]): void'); +verify.currentParameterHelpArgumentNameIs('args'); +verify.currentParameterSpanIs("...args: any[]"); \ No newline at end of file From 3eb6270c9b68e10c5e6603103b4824776b332239 Mon Sep 17 00:00:00 2001 From: rbuckton Date: Thu, 30 Mar 2017 12:59:09 -0700 Subject: [PATCH 2/3] Trim whitespace --- src/compiler/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dad59dcd63..dcad61a317 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2870,7 +2870,7 @@ namespace ts { /* @internal */ export interface TransientSymbol extends Symbol, SymbolLinks { checkFlags: CheckFlags; - isRestParameter?: boolean; + isRestParameter?: boolean; } export type SymbolTable = Map; From e06b3ecbcaa7dca3bb3a70e226a07338de6c9c69 Mon Sep 17 00:00:00 2001 From: rbuckton Date: Thu, 30 Mar 2017 13:02:39 -0700 Subject: [PATCH 3/3] Added additional test verification --- tests/cases/fourslash/signatureHelpCallExpressionJs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/cases/fourslash/signatureHelpCallExpressionJs.ts b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts index 8c1afa5544..e425196bd0 100644 --- a/tests/cases/fourslash/signatureHelpCallExpressionJs.ts +++ b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts @@ -6,10 +6,12 @@ // @Filename: main.js ////function fnTest() { arguments; } ////fnTest(/*1*/); +////fnTest(1, 2, 3); goTo.marker('1'); verify.signatureHelpCountIs(1); verify.currentSignatureParameterCountIs(1); verify.currentSignatureHelpIs('fnTest(...args: any[]): void'); verify.currentParameterHelpArgumentNameIs('args'); -verify.currentParameterSpanIs("...args: any[]"); \ No newline at end of file +verify.currentParameterSpanIs("...args: any[]"); +verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file