JSDoc:positional matching of destructured params (#23307)

* JSDoc:positional matching of destructured params

1. When looking up tags for a parameter whose name is a binding pattern, use
the index of the parameter to get the type.
2. When reporting errors for `@param` tags with no matching parameter
name, do not report the error for tags whose index in the `@param` tag list
matches the index of a parameter whose name is a binding pattern.

* Change to an assert

* Improve comment text
This commit is contained in:
Nathan Shively-Sanders 2018-04-10 12:48:35 -07:00 committed by GitHub
parent 4b706fc479
commit 22919d57fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 405 additions and 4 deletions

View File

@ -21871,6 +21871,10 @@ namespace ts {
// and give a better error message when the host function mentions `arguments`
// but the tag doesn't have an array type
if (decl) {
const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node);
if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) {
return;
}
if (!containsArgumentsReference(decl)) {
error(node.name,
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,

View File

@ -4632,11 +4632,21 @@ namespace ts {
* parameters by name and binding patterns do not have a name.
*/
export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray<JSDocParameterTag> {
if (param.name && isIdentifier(param.name)) {
const name = param.name.escapedText;
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name);
if (param.name) {
if (isIdentifier(param.name)) {
const name = param.name.escapedText;
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name);
}
else {
const i = param.parent.parameters.indexOf(param);
Debug.assert(i > -1, "Parameters should always be in their parents' parameter list");
const paramTags = getJSDocTags(param.parent).filter(isJSDocParameterTag);
if (i < paramTags.length) {
return [paramTags[i]];
}
}
}
// a binding pattern doesn't have a name, so it's not possible to match it a JSDoc parameter, which is identified by name
// return empty array for: out-of-order binding patterns and JSDoc function syntax, which has un-named parameters
return emptyArray;
}

View File

@ -0,0 +1,82 @@
tests/cases/conformance/jsdoc/0.js(56,20): error TS8024: JSDoc '@param' tag has name 'obj', but there is no parameter with that name.
tests/cases/conformance/jsdoc/0.js(61,19): error TS2459: Type 'string' has no property 'a' and no string index signature.
tests/cases/conformance/jsdoc/0.js(61,22): error TS2459: Type 'string' has no property 'b' and no string index signature.
tests/cases/conformance/jsdoc/0.js(63,20): error TS8024: JSDoc '@param' tag has name 'y', but there is no parameter with that name.
==== tests/cases/conformance/jsdoc/0.js (4 errors) ====
// Object literal syntax
/**
* @param {{a: string, b: string}} obj
* @param {string} x
*/
function good1({a, b}, x) {}
/**
* @param {{a: string, b: string}} obj
* @param {{c: number, d: number}} OBJECTION
*/
function good2({a, b}, {c, d}) {}
/**
* @param {number} x
* @param {{a: string, b: string}} obj
* @param {string} y
*/
function good3(x, {a, b}, y) {}
/**
* @param {{a: string, b: string}} obj
*/
function good4({a, b}) {}
// nested object syntax
/**
* @param {Object} obj
* @param {string} obj.a - this is like the saddest way to specify a type
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {string} x
*/
function good5({a, b}, x) {}
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {Object} OBJECTION - documentation here too
* @param {string} OBJECTION.c
* @param {string} OBJECTION.d - meh
*/
function good6({a, b}, {c, d}) {}
/**
* @param {number} x
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
* @param {string} y
*/
function good7(x, {a, b}, y) {}
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
*/
function good8({a, b}) {}
/**
* @param {object} obj - this type gets ignored
~~~
!!! error TS8024: JSDoc '@param' tag has name 'obj', but there is no parameter with that name.
* @param {string} obj.a
* @param {string} obj.b - and x's type gets used for both parameters
* @param {string} x
*/
function bad1(x, {a, b}) {}
~
!!! error TS2459: Type 'string' has no property 'a' and no string index signature.
~
!!! error TS2459: Type 'string' has no property 'b' and no string index signature.
/**
* @param {string} y - here, y's type gets ignored but obj's is fine
~
!!! error TS8024: JSDoc '@param' tag has name 'y', but there is no parameter with that name.
* @param {{a: string, b: string}} obj
*/
function bad2(x, {a, b}) {}

View File

@ -0,0 +1,117 @@
=== tests/cases/conformance/jsdoc/0.js ===
// Object literal syntax
/**
* @param {{a: string, b: string}} obj
* @param {string} x
*/
function good1({a, b}, x) {}
>good1 : Symbol(good1, Decl(0.js, 0, 0))
>a : Symbol(a, Decl(0.js, 5, 16))
>b : Symbol(b, Decl(0.js, 5, 18))
>x : Symbol(x, Decl(0.js, 5, 22))
/**
* @param {{a: string, b: string}} obj
* @param {{c: number, d: number}} OBJECTION
*/
function good2({a, b}, {c, d}) {}
>good2 : Symbol(good2, Decl(0.js, 5, 28))
>a : Symbol(a, Decl(0.js, 10, 16))
>b : Symbol(b, Decl(0.js, 10, 18))
>c : Symbol(c, Decl(0.js, 10, 24))
>d : Symbol(d, Decl(0.js, 10, 26))
/**
* @param {number} x
* @param {{a: string, b: string}} obj
* @param {string} y
*/
function good3(x, {a, b}, y) {}
>good3 : Symbol(good3, Decl(0.js, 10, 33))
>x : Symbol(x, Decl(0.js, 16, 15))
>a : Symbol(a, Decl(0.js, 16, 19))
>b : Symbol(b, Decl(0.js, 16, 21))
>y : Symbol(y, Decl(0.js, 16, 25))
/**
* @param {{a: string, b: string}} obj
*/
function good4({a, b}) {}
>good4 : Symbol(good4, Decl(0.js, 16, 31))
>a : Symbol(a, Decl(0.js, 20, 16))
>b : Symbol(b, Decl(0.js, 20, 18))
// nested object syntax
/**
* @param {Object} obj
* @param {string} obj.a - this is like the saddest way to specify a type
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {string} x
*/
function good5({a, b}, x) {}
>good5 : Symbol(good5, Decl(0.js, 20, 25))
>a : Symbol(a, Decl(0.js, 29, 16))
>b : Symbol(b, Decl(0.js, 29, 18))
>x : Symbol(x, Decl(0.js, 29, 22))
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {Object} OBJECTION - documentation here too
* @param {string} OBJECTION.c
* @param {string} OBJECTION.d - meh
*/
function good6({a, b}, {c, d}) {}
>good6 : Symbol(good6, Decl(0.js, 29, 28))
>a : Symbol(a, Decl(0.js, 38, 16))
>b : Symbol(b, Decl(0.js, 38, 18))
>c : Symbol(c, Decl(0.js, 38, 24))
>d : Symbol(d, Decl(0.js, 38, 26))
/**
* @param {number} x
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
* @param {string} y
*/
function good7(x, {a, b}, y) {}
>good7 : Symbol(good7, Decl(0.js, 38, 33))
>x : Symbol(x, Decl(0.js, 46, 15))
>a : Symbol(a, Decl(0.js, 46, 19))
>b : Symbol(b, Decl(0.js, 46, 21))
>y : Symbol(y, Decl(0.js, 46, 25))
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
*/
function good8({a, b}) {}
>good8 : Symbol(good8, Decl(0.js, 46, 31))
>a : Symbol(a, Decl(0.js, 52, 16))
>b : Symbol(b, Decl(0.js, 52, 18))
/**
* @param {object} obj - this type gets ignored
* @param {string} obj.a
* @param {string} obj.b - and x's type gets used for both parameters
* @param {string} x
*/
function bad1(x, {a, b}) {}
>bad1 : Symbol(bad1, Decl(0.js, 52, 25))
>x : Symbol(x, Decl(0.js, 60, 14))
>a : Symbol(a, Decl(0.js, 60, 18))
>b : Symbol(b, Decl(0.js, 60, 20))
/**
* @param {string} y - here, y's type gets ignored but obj's is fine
* @param {{a: string, b: string}} obj
*/
function bad2(x, {a, b}) {}
>bad2 : Symbol(bad2, Decl(0.js, 60, 27))
>x : Symbol(x, Decl(0.js, 65, 14))
>a : Symbol(a, Decl(0.js, 65, 18))
>b : Symbol(b, Decl(0.js, 65, 20))

View File

@ -0,0 +1,117 @@
=== tests/cases/conformance/jsdoc/0.js ===
// Object literal syntax
/**
* @param {{a: string, b: string}} obj
* @param {string} x
*/
function good1({a, b}, x) {}
>good1 : ({ a, b }: { a: string; b: string; }, x: string) => void
>a : string
>b : string
>x : string
/**
* @param {{a: string, b: string}} obj
* @param {{c: number, d: number}} OBJECTION
*/
function good2({a, b}, {c, d}) {}
>good2 : ({ a, b }: { a: string; b: string; }, { c, d }: { c: number; d: number; }) => void
>a : string
>b : string
>c : number
>d : number
/**
* @param {number} x
* @param {{a: string, b: string}} obj
* @param {string} y
*/
function good3(x, {a, b}, y) {}
>good3 : (x: number, { a, b }: { a: string; b: string; }, y: string) => void
>x : number
>a : string
>b : string
>y : string
/**
* @param {{a: string, b: string}} obj
*/
function good4({a, b}) {}
>good4 : ({ a, b }: { a: string; b: string; }) => void
>a : string
>b : string
// nested object syntax
/**
* @param {Object} obj
* @param {string} obj.a - this is like the saddest way to specify a type
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {string} x
*/
function good5({a, b}, x) {}
>good5 : ({ a, b }: { a: string; b: string; }, x: string) => void
>a : string
>b : string
>x : string
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {Object} OBJECTION - documentation here too
* @param {string} OBJECTION.c
* @param {string} OBJECTION.d - meh
*/
function good6({a, b}, {c, d}) {}
>good6 : ({ a, b }: { a: string; b: string; }, { c, d }: { c: string; d: string; }) => void
>a : string
>b : string
>c : string
>d : string
/**
* @param {number} x
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
* @param {string} y
*/
function good7(x, {a, b}, y) {}
>good7 : (x: number, { a, b }: { a: string; b: string; }, y: string) => void
>x : number
>a : string
>b : string
>y : string
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
*/
function good8({a, b}) {}
>good8 : ({ a, b }: { a: string; b: string; }) => void
>a : string
>b : string
/**
* @param {object} obj - this type gets ignored
* @param {string} obj.a
* @param {string} obj.b - and x's type gets used for both parameters
* @param {string} x
*/
function bad1(x, {a, b}) {}
>bad1 : (x: string, { a, b }: string) => void
>x : string
>a : any
>b : any
/**
* @param {string} y - here, y's type gets ignored but obj's is fine
* @param {{a: string, b: string}} obj
*/
function bad2(x, {a, b}) {}
>bad2 : (x: any, { a, b }: { a: string; b: string; }) => void
>x : any
>a : string
>b : string

View File

@ -0,0 +1,71 @@
// @allowJS: true
// @checkJS: true
// @noEmit: true
// @Filename: 0.js
// Object literal syntax
/**
* @param {{a: string, b: string}} obj
* @param {string} x
*/
function good1({a, b}, x) {}
/**
* @param {{a: string, b: string}} obj
* @param {{c: number, d: number}} OBJECTION
*/
function good2({a, b}, {c, d}) {}
/**
* @param {number} x
* @param {{a: string, b: string}} obj
* @param {string} y
*/
function good3(x, {a, b}, y) {}
/**
* @param {{a: string, b: string}} obj
*/
function good4({a, b}) {}
// nested object syntax
/**
* @param {Object} obj
* @param {string} obj.a - this is like the saddest way to specify a type
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {string} x
*/
function good5({a, b}, x) {}
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b - but it sure does allow a lot of documentation
* @param {Object} OBJECTION - documentation here too
* @param {string} OBJECTION.c
* @param {string} OBJECTION.d - meh
*/
function good6({a, b}, {c, d}) {}
/**
* @param {number} x
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
* @param {string} y
*/
function good7(x, {a, b}, y) {}
/**
* @param {Object} obj
* @param {string} obj.a
* @param {string} obj.b
*/
function good8({a, b}) {}
/**
* @param {object} obj - this type gets ignored
* @param {string} obj.a
* @param {string} obj.b - and x's type gets used for both parameters
* @param {string} x
*/
function bad1(x, {a, b}) {}
/**
* @param {string} y - here, y's type gets ignored but obj's is fine
* @param {{a: string, b: string}} obj
*/
function bad2(x, {a, b}) {}