Merge pull request #14920 from Microsoft/jsInferRestArgs

Infer a rest parameter for javascript function that uses 'arguments'
This commit is contained in:
Ron Buckton 2017-03-31 12:52:11 -07:00 committed by GitHub
commit 22b4e4d65d
3 changed files with 75 additions and 8 deletions

View file

@ -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 = <ParameterDeclaration>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(<BindingPattern>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 (<Identifier>node).text === "arguments" && isPartOfExpression(node);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return (<Declaration>node).name.kind === SyntaxKind.ComputedPropertyName
&& traverse((<Declaration>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(<FunctionLikeDeclaration>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);

View file

@ -2870,6 +2870,7 @@ namespace ts {
/* @internal */
export interface TransientSymbol extends Symbol, SymbolLinks {
checkFlags: CheckFlags;
isRestParameter?: boolean;
}
export type SymbolTable = Map<Symbol>;
@ -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

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
// @checkJs: true
// @allowJs: true
// @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[]");
verify.numberOfErrorsInCurrentFile(0);