Skip to content

Commit 22b4e4d

Browse files
authored
Merge pull request #14920 from Microsoft/jsInferRestArgs
Infer a rest parameter for javascript function that uses 'arguments'
2 parents 0459987 + e06b3ec commit 22b4e4d

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

src/compiler/checker.ts

+55-7
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ namespace ts {
493493
return symbol;
494494
}
495495

496+
function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol {
497+
return (symbol.flags & SymbolFlags.Transient) !== 0;
498+
}
499+
496500
function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags {
497501
let result: SymbolFlags = 0;
498502
if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes;
@@ -3385,23 +3389,23 @@ namespace ts {
33853389

33863390
function buildParameterDisplay(p: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) {
33873391
const parameterNode = <ParameterDeclaration>p.valueDeclaration;
3388-
if (isRestParameter(parameterNode)) {
3392+
if (parameterNode ? isRestParameter(parameterNode) : isTransientSymbol(p) && p.isRestParameter) {
33893393
writePunctuation(writer, SyntaxKind.DotDotDotToken);
33903394
}
3391-
if (isBindingPattern(parameterNode.name)) {
3395+
if (parameterNode && isBindingPattern(parameterNode.name)) {
33923396
buildBindingPatternDisplay(<BindingPattern>parameterNode.name, writer, enclosingDeclaration, flags, symbolStack);
33933397
}
33943398
else {
33953399
appendSymbolNameOnly(p, writer);
33963400
}
3397-
if (isOptionalParameter(parameterNode)) {
3401+
if (parameterNode && isOptionalParameter(parameterNode)) {
33983402
writePunctuation(writer, SyntaxKind.QuestionToken);
33993403
}
34003404
writePunctuation(writer, SyntaxKind.ColonToken);
34013405
writeSpace(writer);
34023406

34033407
let type = getTypeOfSymbol(p);
3404-
if (isRequiredInitializedParameter(parameterNode)) {
3408+
if (parameterNode && isRequiredInitializedParameter(parameterNode)) {
34053409
type = includeFalsyTypes(type, TypeFlags.Undefined);
34063410
}
34073411
buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack);
@@ -6170,6 +6174,37 @@ namespace ts {
61706174
}
61716175
}
61726176

6177+
function containsArgumentsReference(declaration: FunctionLikeDeclaration): boolean {
6178+
const links = getNodeLinks(declaration);
6179+
if (links.containsArgumentsReference === undefined) {
6180+
if (links.flags & NodeCheckFlags.CaptureArguments) {
6181+
links.containsArgumentsReference = true;
6182+
}
6183+
else {
6184+
links.containsArgumentsReference = traverse(declaration.body);
6185+
}
6186+
}
6187+
return links.containsArgumentsReference;
6188+
6189+
function traverse(node: Node): boolean {
6190+
if (!node) return false;
6191+
switch (node.kind) {
6192+
case SyntaxKind.Identifier:
6193+
return (<Identifier>node).text === "arguments" && isPartOfExpression(node);
6194+
6195+
case SyntaxKind.PropertyDeclaration:
6196+
case SyntaxKind.MethodDeclaration:
6197+
case SyntaxKind.GetAccessor:
6198+
case SyntaxKind.SetAccessor:
6199+
return (<Declaration>node).name.kind === SyntaxKind.ComputedPropertyName
6200+
&& traverse((<Declaration>node).name);
6201+
6202+
default:
6203+
return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && forEachChild(node, traverse);
6204+
}
6205+
}
6206+
}
6207+
61736208
function getSignaturesOfSymbol(symbol: Symbol): Signature[] {
61746209
if (!symbol) return emptyArray;
61756210
const result: Signature[] = [];
@@ -11613,9 +11648,7 @@ namespace ts {
1161311648
}
1161411649
}
1161511650

11616-
if (node.flags & NodeFlags.AwaitContext) {
11617-
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
11618-
}
11651+
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
1161911652
return getTypeOfSymbol(symbol);
1162011653
}
1162111654

@@ -14854,6 +14887,21 @@ namespace ts {
1485414887
}
1485514888
}
1485614889

14890+
if (signatures.length === 1) {
14891+
const declaration = signatures[0].declaration;
14892+
if (declaration && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration)) {
14893+
if (containsArgumentsReference(<FunctionLikeDeclaration>declaration)) {
14894+
const signatureWithRest = cloneSignature(signatures[0]);
14895+
const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args");
14896+
syntheticArgsSymbol.type = anyArrayType;
14897+
syntheticArgsSymbol.isRestParameter = true;
14898+
signatureWithRest.parameters = concatenate(signatureWithRest.parameters, [syntheticArgsSymbol]);
14899+
signatureWithRest.hasRestParameter = true;
14900+
signatures = [signatureWithRest];
14901+
}
14902+
}
14903+
}
14904+
1485714905
const candidates = candidatesOutArray || [];
1485814906
// reorderCandidates fills up the candidates array directly
1485914907
reorderCandidates(signatures, candidates);

src/compiler/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,7 @@ namespace ts {
28702870
/* @internal */
28712871
export interface TransientSymbol extends Symbol, SymbolLinks {
28722872
checkFlags: CheckFlags;
2873+
isRestParameter?: boolean;
28732874
}
28742875

28752876
export type SymbolTable = Map<Symbol>;
@@ -2899,7 +2900,7 @@ namespace ts {
28992900
ContextChecked = 0x00000400, // Contextual types have been assigned
29002901
AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'.
29012902
AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'.
2902-
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions)
2903+
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body
29032904
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
29042905
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
29052906
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
@@ -2923,6 +2924,7 @@ namespace ts {
29232924
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
29242925
enumMemberValue?: number; // Constant value of enum member
29252926
isVisible?: boolean; // Is this node visible
2927+
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference
29262928
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
29272929
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
29282930
resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @checkJs: true
4+
// @allowJs: true
5+
6+
// @Filename: main.js
7+
////function fnTest() { arguments; }
8+
////fnTest(/*1*/);
9+
////fnTest(1, 2, 3);
10+
11+
goTo.marker('1');
12+
verify.signatureHelpCountIs(1);
13+
verify.currentSignatureParameterCountIs(1);
14+
verify.currentSignatureHelpIs('fnTest(...args: any[]): void');
15+
verify.currentParameterHelpArgumentNameIs('args');
16+
verify.currentParameterSpanIs("...args: any[]");
17+
verify.numberOfErrorsInCurrentFile(0);

0 commit comments

Comments
 (0)