diff --git a/README.md b/README.md index 257481950..38b4a54a0 100644 --- a/README.md +++ b/README.md @@ -2092,6 +2092,33 @@ function quux ({a, b}) { } +/** + * @param foo + * @param foo.a + * @param foo.b + */ +function quux ({"a": A, b}) { + +} + +/** + * @param foo + * @param foo."a" + * @param foo.b + */ +function quux ({a: A, b}) { + +} + +/** + * @param foo + * @param foo."a-b" + * @param foo.b + */ +function quux ({"a-b": A, b}) { + +} + /** * @param foo * @param foo.bar diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js index 2aa365591..61b5dac67 100644 --- a/src/jsdocUtils.js +++ b/src/jsdocUtils.js @@ -133,6 +133,14 @@ const getFunctionParameterNames = (functionNode : Object) : Array => { if (param.key.type === 'Identifier') { return param.key.name; } + + // The key of an object could also be a string or number + /* istanbul ignore else */ + if (param.key.type === 'Literal') { + return param.key.raw || + // istanbul ignore next -- `raw` may not be present in all parsers + param.key.value; + } } if (param.type === 'ArrayPattern' || param.left?.type === 'ArrayPattern') { diff --git a/src/rules/checkParamNames.js b/src/rules/checkParamNames.js index d0aa9aa5c..0189aa5cc 100644 --- a/src/rules/checkParamNames.js +++ b/src/rules/checkParamNames.js @@ -1,5 +1,25 @@ import iterateJsdoc from '../iterateJsdoc'; +/** + * Since path segments may be unquoted (if matching a reserved word, + * identifier or numeric literal) or single or double quoted, in either + * the `@param` or in source, we need to strip the quotes to give a fair + * comparison. + * + * @param {string} str + * @returns {string} + */ +const dropPathSegmentQuotes = (str) => { + return str.replace(/\.(['"])(.*)\1/gu, '.$2'); +}; + +const comparePaths = (name) => { + return (otherPathName) => { + return otherPathName === name || + dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name); + }; +}; + const validateParameterNames = ( targetTagName : string, allowExtraTrailingParamDocs: boolean, @@ -76,8 +96,9 @@ const validateParameterNames = ( }); const missingProperties = []; + expectedNames.forEach((name, idx) => { - if (!actualNames.includes(name)) { + if (!actualNames.some(comparePaths(name))) { if (!checkRestProperty && rests[idx]) { return; } @@ -89,7 +110,7 @@ const validateParameterNames = ( if (!hasPropertyRest || checkRestProperty) { actualNames.forEach((name, idx) => { const match = name.startsWith(tag.name.trim() + '.'); - if (match && !expectedNames.includes(name) && name !== tag.name) { + if (match && !expectedNames.some(comparePaths(name)) && name !== tag.name) { extraProperties.push([name, paramTags[idx][1]]); } }); diff --git a/test/rules/assertions/checkParamNames.js b/test/rules/assertions/checkParamNames.js index 9a599df32..f70caaa96 100644 --- a/test/rules/assertions/checkParamNames.js +++ b/test/rules/assertions/checkParamNames.js @@ -73,7 +73,8 @@ export default { errors: [ { line: 3, - message: '@param path declaration ("Foo.Bar") appears before any real parameter.', + message: + '@param path declaration ("Foo.Bar") appears before any real parameter.', }, ], }, @@ -90,7 +91,8 @@ export default { errors: [ { line: 4, - message: '@param path declaration ("Foo.Bar") root node name ("Foo") does not match previous real parameter name ("foo").', + message: + '@param path declaration ("Foo.Bar") root node name ("Foo") does not match previous real parameter name ("foo").', }, ], }, @@ -108,7 +110,8 @@ export default { errors: [ { line: 4, - message: '@param path declaration ("employees[].name") appears before any real parameter.', + message: + '@param path declaration ("employees[].name") appears before any real parameter.', }, ], }, @@ -175,7 +178,8 @@ export default { errors: [ { line: 4, - message: '@param "bar" does not match an existing function parameter.', + message: + '@param "bar" does not match an existing function parameter.', }, ], }, @@ -661,7 +665,8 @@ export default { errors: [ { line: 4, - message: 'Expected @param names to be "error, cde". Got "error, code".', + message: + 'Expected @param names to be "error, cde". Got "error, code".', }, ], }, @@ -925,6 +930,42 @@ export default { } `, }, + { + code: ` + /** + * @param foo + * @param foo.a + * @param foo.b + */ + function quux ({"a": A, b}) { + + } + `, + }, + { + code: ` + /** + * @param foo + * @param foo."a" + * @param foo.b + */ + function quux ({a: A, b}) { + + } + `, + }, + { + code: ` + /** + * @param foo + * @param foo."a-b" + * @param foo.b + */ + function quux ({"a-b": A, b}) { + + } + `, + }, { code: ` /**