diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2657843192e..cb855969ea8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2832,6 +2832,17 @@ namespace ts { function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext): ParameterDeclaration { const parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (isTransientSymbol(parameterSymbol) && parameterSymbol.isRestParameter) { + // special-case synthetic rest parameters in JS files + return createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameterSymbol.isRestParameter ? createToken(SyntaxKind.DotDotDotToken) : undefined, + "args", + /*questionToken*/ undefined, + typeToTypeNodeHelper(anyArrayType, context), + /*initializer*/ undefined); + } const modifiers = parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone); const dotDotDotToken = isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined; const name = parameterDeclaration.name ? @@ -6391,8 +6402,17 @@ namespace ts { const typePredicate = declaration.type && declaration.type.kind === SyntaxKind.TypePredicate ? createTypePredicateFromTypePredicateNode(declaration.type as TypePredicateNode) : undefined; + // JS functions get a free rest parameter if they reference `arguments` + let hasRestLikeParameter = hasRestParameter(declaration); + if (!hasRestLikeParameter && isInJavaScriptFile(declaration) && !hasJSDocParameterTags(declaration) && containsArgumentsReference(declaration)) { + hasRestLikeParameter = true; + const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args"); + syntheticArgsSymbol.type = anyArrayType; + syntheticArgsSymbol.isRestParameter = true; + parameters.push(syntheticArgsSymbol); + } - links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, hasRestParameter(declaration), hasLiteralTypes); + links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, hasRestLikeParameter, hasLiteralTypes); } return links.resolvedSignature; } @@ -6427,14 +6447,14 @@ namespace ts { } } - function containsArgumentsReference(declaration: FunctionLikeDeclaration): boolean { + function containsArgumentsReference(declaration: SignatureDeclaration): boolean { const links = getNodeLinks(declaration); if (links.containsArgumentsReference === undefined) { if (links.flags & NodeCheckFlags.CaptureArguments) { links.containsArgumentsReference = true; } else { - links.containsArgumentsReference = traverse(declaration.body); + links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body); } } return links.containsArgumentsReference; @@ -15501,21 +15521,6 @@ 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/services/jsDoc.ts b/src/services/jsDoc.ts index baf9ae44b5f..5d03ad03b0c 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -143,8 +143,9 @@ namespace ts.JsDoc { const name = param.name.text; if (jsdoc.tags.some(t => t !== tag && isJSDocParameterTag(t) && t.name.text === name) - || nameThusFar !== undefined && !startsWith(name, nameThusFar)) + || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { return undefined; + } return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: "0" }; }); diff --git a/tests/baselines/reference/argumentsObjectCreatesRestForJs.symbols b/tests/baselines/reference/argumentsObjectCreatesRestForJs.symbols new file mode 100644 index 00000000000..6a95dbc86d8 --- /dev/null +++ b/tests/baselines/reference/argumentsObjectCreatesRestForJs.symbols @@ -0,0 +1,44 @@ +=== tests/cases/compiler/main.js === +function allRest() { arguments; } +>allRest : Symbol(allRest, Decl(main.js, 0, 0)) +>arguments : Symbol(arguments) + +allRest(); +>allRest : Symbol(allRest, Decl(main.js, 0, 0)) + +allRest(1, 2, 3); +>allRest : Symbol(allRest, Decl(main.js, 0, 0)) + +function someRest(x, y) { arguments; } +>someRest : Symbol(someRest, Decl(main.js, 2, 17)) +>x : Symbol(x, Decl(main.js, 3, 18)) +>y : Symbol(y, Decl(main.js, 3, 20)) +>arguments : Symbol(arguments) + +someRest(); // x and y are still optional because they are in a JS file +>someRest : Symbol(someRest, Decl(main.js, 2, 17)) + +someRest(1, 2, 3); +>someRest : Symbol(someRest, Decl(main.js, 2, 17)) + +/** + * @param {number} x - a thing + */ +function jsdocced(x) { arguments; } +>jsdocced : Symbol(jsdocced, Decl(main.js, 5, 18)) +>x : Symbol(x, Decl(main.js, 10, 18)) +>arguments : Symbol(arguments) + +jsdocced(1); +>jsdocced : Symbol(jsdocced, Decl(main.js, 5, 18)) + +function dontDoubleRest(x, ...y) { arguments; } +>dontDoubleRest : Symbol(dontDoubleRest, Decl(main.js, 11, 12)) +>x : Symbol(x, Decl(main.js, 13, 24)) +>y : Symbol(y, Decl(main.js, 13, 26)) +>arguments : Symbol(arguments) + +dontDoubleRest(1, 2, 3); +>dontDoubleRest : Symbol(dontDoubleRest, Decl(main.js, 11, 12)) + + diff --git a/tests/baselines/reference/argumentsObjectCreatesRestForJs.types b/tests/baselines/reference/argumentsObjectCreatesRestForJs.types new file mode 100644 index 00000000000..10596fb10c7 --- /dev/null +++ b/tests/baselines/reference/argumentsObjectCreatesRestForJs.types @@ -0,0 +1,60 @@ +=== tests/cases/compiler/main.js === +function allRest() { arguments; } +>allRest : (...args: any[]) => void +>arguments : IArguments + +allRest(); +>allRest() : void +>allRest : (...args: any[]) => void + +allRest(1, 2, 3); +>allRest(1, 2, 3) : void +>allRest : (...args: any[]) => void +>1 : 1 +>2 : 2 +>3 : 3 + +function someRest(x, y) { arguments; } +>someRest : (x: any, y: any, ...args: any[]) => void +>x : any +>y : any +>arguments : IArguments + +someRest(); // x and y are still optional because they are in a JS file +>someRest() : void +>someRest : (x: any, y: any, ...args: any[]) => void + +someRest(1, 2, 3); +>someRest(1, 2, 3) : void +>someRest : (x: any, y: any, ...args: any[]) => void +>1 : 1 +>2 : 2 +>3 : 3 + +/** + * @param {number} x - a thing + */ +function jsdocced(x) { arguments; } +>jsdocced : (x: number) => void +>x : number +>arguments : IArguments + +jsdocced(1); +>jsdocced(1) : void +>jsdocced : (x: number) => void +>1 : 1 + +function dontDoubleRest(x, ...y) { arguments; } +>dontDoubleRest : (x: any, ...y: any[]) => void +>x : any +>y : any[] +>arguments : IArguments + +dontDoubleRest(1, 2, 3); +>dontDoubleRest(1, 2, 3) : void +>dontDoubleRest : (x: any, ...y: any[]) => void +>1 : 1 +>2 : 2 +>3 : 3 + + diff --git a/tests/baselines/reference/jsSelfReferencingArgumentsFunction.symbols b/tests/baselines/reference/jsSelfReferencingArgumentsFunction.symbols new file mode 100644 index 00000000000..e4fe336575c --- /dev/null +++ b/tests/baselines/reference/jsSelfReferencingArgumentsFunction.symbols @@ -0,0 +1,12 @@ +=== tests/cases/compiler/foo.js === +// Test #16139 +function Foo() { +>Foo : Symbol(Foo, Decl(foo.js, 0, 0)) + + arguments; +>arguments : Symbol(arguments) + + return new Foo(); +>Foo : Symbol(Foo, Decl(foo.js, 0, 0)) +} + diff --git a/tests/baselines/reference/jsSelfReferencingArgumentsFunction.types b/tests/baselines/reference/jsSelfReferencingArgumentsFunction.types new file mode 100644 index 00000000000..cab51cbafc1 --- /dev/null +++ b/tests/baselines/reference/jsSelfReferencingArgumentsFunction.types @@ -0,0 +1,13 @@ +=== tests/cases/compiler/foo.js === +// Test #16139 +function Foo() { +>Foo : (...args: any[]) => any + + arguments; +>arguments : IArguments + + return new Foo(); +>new Foo() : any +>Foo : (...args: any[]) => any +} + diff --git a/tests/cases/compiler/argumentsObjectCreatesRestForJs.ts b/tests/cases/compiler/argumentsObjectCreatesRestForJs.ts new file mode 100644 index 00000000000..4c3ca335d1d --- /dev/null +++ b/tests/cases/compiler/argumentsObjectCreatesRestForJs.ts @@ -0,0 +1,20 @@ +// @checkJs: true +// @allowJs: true +// @Filename: main.js +// @noemit: true +function allRest() { arguments; } +allRest(); +allRest(1, 2, 3); +function someRest(x, y) { arguments; } +someRest(); // x and y are still optional because they are in a JS file +someRest(1, 2, 3); + +/** + * @param {number} x - a thing + */ +function jsdocced(x) { arguments; } +jsdocced(1); + +function dontDoubleRest(x, ...y) { arguments; } +dontDoubleRest(1, 2, 3); + diff --git a/tests/cases/compiler/jsSelfReferencingArgumentsFunction.ts b/tests/cases/compiler/jsSelfReferencingArgumentsFunction.ts new file mode 100644 index 00000000000..386b88c9cd1 --- /dev/null +++ b/tests/cases/compiler/jsSelfReferencingArgumentsFunction.ts @@ -0,0 +1,8 @@ +// @Filename: foo.js +// @noEmit: true +// @allowJs: true +// Test #16139 +function Foo() { + arguments; + return new Foo(); +} diff --git a/tests/cases/fourslash/signatureHelpCallExpressionJs.ts b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts index e425196bd07..17045a74717 100644 --- a/tests/cases/fourslash/signatureHelpCallExpressionJs.ts +++ b/tests/cases/fourslash/signatureHelpCallExpressionJs.ts @@ -4,14 +4,25 @@ // @allowJs: true // @Filename: main.js -////function fnTest() { arguments; } -////fnTest(/*1*/); -////fnTest(1, 2, 3); +////function allOptional() { arguments; } +////allOptional(/*1*/); +////allOptional(1, 2, 3); +////function someOptional(x, y) { arguments; } +////someOptional(/*2*/); +////someOptional(1, 2, 3); +////someOptional(); // no error here; x and y are optional in JS goTo.marker('1'); verify.signatureHelpCountIs(1); verify.currentSignatureParameterCountIs(1); -verify.currentSignatureHelpIs('fnTest(...args: any[]): void'); +verify.currentSignatureHelpIs('allOptional(...args: any[]): void'); verify.currentParameterHelpArgumentNameIs('args'); verify.currentParameterSpanIs("...args: any[]"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file + +goTo.marker('2'); +verify.signatureHelpCountIs(1); +verify.currentSignatureParameterCountIs(3); +verify.currentSignatureHelpIs('someOptional(x: any, y: any, ...args: any[]): void'); +verify.currentParameterHelpArgumentNameIs('x'); +verify.currentParameterSpanIs("x: any"); +verify.numberOfErrorsInCurrentFile(0);