Skip to content

Commit 677d860

Browse files
authored
No error on unmatchable @param tags (#22510)
* No errr on unmatchable `@param` tags Such as when the initializer is not a function, or when the function mentions `arguments` in its body. * Do not require dummy param for JS uses of arguments 1. JS functions that use `arguments` do not require a dummy parameter in order to get a type for the synthetic `args` parameter if there is an `@param` with a `...` type. 2.JS functions that use `arguments` and have an `@param` must have a type that is a `...` type. * Check for array type instead of syntactic `...` * Address PR comments * Update baselines
1 parent 24fdb52 commit 677d860

10 files changed

+243
-17
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6753,17 +6753,20 @@ namespace ts {
67536753
return links.resolvedSignature;
67546754
}
67556755

6756+
/**
6757+
* A JS function gets a synthetic rest parameter if it references `arguments` AND:
6758+
* 1. It has no parameters but at least one `@param` with a type that starts with `...`
6759+
* OR
6760+
* 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...`
6761+
*/
67566762
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration, parameters: Symbol[]): boolean {
6757-
// JS functions get a free rest parameter if:
6758-
// a) The last parameter has `...` preceding its type
6759-
// b) It references `arguments` somewhere
6763+
if (!containsArgumentsReference(declaration)) {
6764+
return false;
6765+
}
67606766
const lastParam = lastOrUndefined(declaration.parameters);
6761-
const lastParamTags = lastParam && getJSDocParameterTags(lastParam);
6767+
const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag);
67626768
const lastParamVariadicType = firstDefined(lastParamTags, p =>
67636769
p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined);
6764-
if (!lastParamVariadicType && !containsArgumentsReference(declaration)) {
6765-
return false;
6766-
}
67676770

67686771
const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String);
67696772
syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : anyArrayType;
@@ -21440,9 +21443,24 @@ namespace ts {
2144021443
function checkJSDocParameterTag(node: JSDocParameterTag) {
2144121444
checkSourceElement(node.typeExpression);
2144221445
if (!getParameterSymbolFromJSDoc(node)) {
21443-
error(node.name,
21444-
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
21445-
idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name));
21446+
const decl = getHostSignatureFromJSDoc(node);
21447+
// don't issue an error for invalid hosts -- just functions --
21448+
// and give a better error message when the host function mentions `arguments`
21449+
// but the tag doesn't have an array type
21450+
if (decl) {
21451+
if (!containsArgumentsReference(decl)) {
21452+
error(node.name,
21453+
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
21454+
idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name));
21455+
}
21456+
else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node &&
21457+
node.typeExpression && node.typeExpression.type &&
21458+
!isArrayType(getTypeFromTypeNode(node.typeExpression.type))) {
21459+
error(node.name,
21460+
Diagnostics.The_last_param_tag_of_a_function_that_uses_arguments_must_have_an_array_type,
21461+
idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name));
21462+
}
21463+
}
2144621464
}
2144721465
}
2144821466

@@ -24470,18 +24488,19 @@ namespace ts {
2447024488
const paramTag = parent.parent;
2447124489
if (isJSDocTypeExpression(parent) && isJSDocParameterTag(paramTag)) {
2447224490
// Else we will add a diagnostic, see `checkJSDocVariadicType`.
24473-
const param = getParameterSymbolFromJSDoc(paramTag);
24474-
if (param) {
24475-
const host = getHostSignatureFromJSDoc(paramTag);
24491+
const host = getHostSignatureFromJSDoc(paramTag);
24492+
if (host) {
2447624493
/*
24477-
Only return an array type if the corresponding parameter is marked as a rest parameter.
24494+
Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters.
2447824495
So in the following situation we will not create an array type:
2447924496
/** @param {...number} a * /
2448024497
function f(a) {}
2448124498
Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type.
2448224499
*/
24483-
const lastParamDeclaration = host && last(host.parameters);
24484-
if (lastParamDeclaration.symbol === param && isRestParameter(lastParamDeclaration)) {
24500+
const lastParamDeclaration = lastOrUndefined(host.parameters);
24501+
const symbol = getParameterSymbolFromJSDoc(paramTag);
24502+
if (!lastParamDeclaration ||
24503+
symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) {
2448524504
return createArrayType(type);
2448624505
}
2448724506
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,6 +3720,10 @@
37203720
"category": "Error",
37213721
"code": 8028
37223722
},
3723+
"The last @param tag of a function that uses 'arguments' must have an array type.": {
3724+
"category": "Error",
3725+
"code": 8029
3726+
},
37233727
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
37243728
"category": "Error",
37253729
"code": 9002

tests/baselines/reference/jsdocPrefixPostfixParsing.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* @param {...number?[]!} k - (number[] | null)[]
1717
*/
1818
function f(x, y, z, a, b, c, d, e, f, g, h, i, j, k) {
19-
>f : (x: number[], y: number[], z: number[], a: (number | null)[], b: number[] | null, c: number[] | null, d: number | null | undefined, e: number | null | undefined, f: number | null | undefined, g: number | null | undefined, h: number | null | undefined, i: number[] | undefined, j: number[] | null | undefined, ...args: (number | null)[][]) => void
19+
>f : (x: number[], y: number[], z: number[], a: (number | null)[], b: number[] | null, c: number[] | null, d: number | null | undefined, e: number | null | undefined, f: number | null | undefined, g: number | null | undefined, h: number | null | undefined, i: number[] | undefined, j: number[] | null | undefined, k: (number | null)[] | undefined) => void
2020
>x : number[]
2121
>y : number[]
2222
>z : number[]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== tests/cases/conformance/jsdoc/decls.d.ts ===
2+
declare function factory(type: string): {};
3+
>factory : Symbol(factory, Decl(decls.d.ts, 0, 0))
4+
>type : Symbol(type, Decl(decls.d.ts, 0, 25))
5+
6+
=== tests/cases/conformance/jsdoc/a.js ===
7+
// from util
8+
/** @param {function} ctor - A big long explanation follows */
9+
exports.inherits = factory('inherits')
10+
>exports.inherits : Symbol(inherits, Decl(a.js, 0, 0))
11+
>exports : Symbol(inherits, Decl(a.js, 0, 0))
12+
>inherits : Symbol(inherits, Decl(a.js, 0, 0))
13+
>factory : Symbol(factory, Decl(decls.d.ts, 0, 0))
14+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/jsdoc/decls.d.ts ===
2+
declare function factory(type: string): {};
3+
>factory : (type: string) => {}
4+
>type : string
5+
6+
=== tests/cases/conformance/jsdoc/a.js ===
7+
// from util
8+
/** @param {function} ctor - A big long explanation follows */
9+
exports.inherits = factory('inherits')
10+
>exports.inherits = factory('inherits') : {}
11+
>exports.inherits : {}
12+
>exports : typeof "tests/cases/conformance/jsdoc/a"
13+
>inherits : {}
14+
>factory('inherits') : {}
15+
>factory : (type: string) => {}
16+
>'inherits' : "inherits"
17+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
tests/cases/conformance/jsdoc/a.js(2,20): error TS8029: The last @param tag of a function that uses 'arguments' must have an array type.
2+
tests/cases/conformance/jsdoc/a.js(19,9): error TS2345: Argument of type '1' is not assignable to parameter of type 'string'.
3+
4+
5+
==== tests/cases/conformance/jsdoc/decls.d.ts (0 errors) ====
6+
declare function factory(type: string): {};
7+
==== tests/cases/conformance/jsdoc/a.js (2 errors) ====
8+
/**
9+
* @param {string} first
10+
~~~~~
11+
!!! error TS8029: The last @param tag of a function that uses 'arguments' must have an array type.
12+
*/
13+
function concat(/* first, second, ... */) {
14+
var s = ''
15+
for (var i = 0, l = arguments.length; i < l; i++) {
16+
s += arguments[i]
17+
}
18+
return s
19+
}
20+
21+
/**
22+
* @param {...string} strings
23+
*/
24+
function correct() {
25+
arguments
26+
}
27+
28+
correct(1,2,3) // oh no
29+
~
30+
!!! error TS2345: Argument of type '1' is not assignable to parameter of type 'string'.
31+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/conformance/jsdoc/decls.d.ts ===
2+
declare function factory(type: string): {};
3+
>factory : Symbol(factory, Decl(decls.d.ts, 0, 0))
4+
>type : Symbol(type, Decl(decls.d.ts, 0, 25))
5+
6+
=== tests/cases/conformance/jsdoc/a.js ===
7+
/**
8+
* @param {string} first
9+
*/
10+
function concat(/* first, second, ... */) {
11+
>concat : Symbol(concat, Decl(a.js, 0, 0))
12+
13+
var s = ''
14+
>s : Symbol(s, Decl(a.js, 4, 5))
15+
16+
for (var i = 0, l = arguments.length; i < l; i++) {
17+
>i : Symbol(i, Decl(a.js, 5, 10))
18+
>l : Symbol(l, Decl(a.js, 5, 17))
19+
>arguments.length : Symbol(IArguments.length, Decl(lib.d.ts, --, --))
20+
>arguments : Symbol(arguments)
21+
>length : Symbol(IArguments.length, Decl(lib.d.ts, --, --))
22+
>i : Symbol(i, Decl(a.js, 5, 10))
23+
>l : Symbol(l, Decl(a.js, 5, 17))
24+
>i : Symbol(i, Decl(a.js, 5, 10))
25+
26+
s += arguments[i]
27+
>s : Symbol(s, Decl(a.js, 4, 5))
28+
>arguments : Symbol(arguments)
29+
>i : Symbol(i, Decl(a.js, 5, 10))
30+
}
31+
return s
32+
>s : Symbol(s, Decl(a.js, 4, 5))
33+
}
34+
35+
/**
36+
* @param {...string} strings
37+
*/
38+
function correct() {
39+
>correct : Symbol(correct, Decl(a.js, 9, 1))
40+
41+
arguments
42+
>arguments : Symbol(arguments)
43+
}
44+
45+
correct(1,2,3) // oh no
46+
>correct : Symbol(correct, Decl(a.js, 9, 1))
47+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/conformance/jsdoc/decls.d.ts ===
2+
declare function factory(type: string): {};
3+
>factory : (type: string) => {}
4+
>type : string
5+
6+
=== tests/cases/conformance/jsdoc/a.js ===
7+
/**
8+
* @param {string} first
9+
*/
10+
function concat(/* first, second, ... */) {
11+
>concat : (...args: any[]) => string
12+
13+
var s = ''
14+
>s : string
15+
>'' : ""
16+
17+
for (var i = 0, l = arguments.length; i < l; i++) {
18+
>i : number
19+
>0 : 0
20+
>l : number
21+
>arguments.length : number
22+
>arguments : IArguments
23+
>length : number
24+
>i < l : boolean
25+
>i : number
26+
>l : number
27+
>i++ : number
28+
>i : number
29+
30+
s += arguments[i]
31+
>s += arguments[i] : string
32+
>s : string
33+
>arguments[i] : any
34+
>arguments : IArguments
35+
>i : number
36+
}
37+
return s
38+
>s : string
39+
}
40+
41+
/**
42+
* @param {...string} strings
43+
*/
44+
function correct() {
45+
>correct : (...args: string[]) => void
46+
47+
arguments
48+
>arguments : IArguments
49+
}
50+
51+
correct(1,2,3) // oh no
52+
>correct(1,2,3) : void
53+
>correct : (...args: string[]) => void
54+
>1 : 1
55+
>2 : 2
56+
>3 : 3
57+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: decls.d.ts
5+
declare function factory(type: string): {};
6+
// @Filename: a.js
7+
8+
// from util
9+
/** @param {function} ctor - A big long explanation follows */
10+
exports.inherits = factory('inherits')
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @strict: true
5+
// @Filename: decls.d.ts
6+
declare function factory(type: string): {};
7+
// @Filename: a.js
8+
9+
/**
10+
* @param {string} first
11+
*/
12+
function concat(/* first, second, ... */) {
13+
var s = ''
14+
for (var i = 0, l = arguments.length; i < l; i++) {
15+
s += arguments[i]
16+
}
17+
return s
18+
}
19+
20+
/**
21+
* @param {...string} strings
22+
*/
23+
function correct() {
24+
arguments
25+
}
26+
27+
correct(1,2,3) // oh no

0 commit comments

Comments
 (0)