From e5da5bb4afa6d777e84b92bb21cd38551a290b1e Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 31 Oct 2022 08:06:16 -0700 Subject: [PATCH] feat(`check-types`): add `skipRootChecking` option to `preferredTypes` setting; fixes #863 --- .README/README.md | 2 ++ README.md | 18 ++++++++++++ src/rules/checkTypes.js | 34 ++++++++++++++-------- test/rules/assertions/checkTypes.js | 44 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/.README/README.md b/.README/README.md index 17990f2ac..e35d5cc50 100644 --- a/.README/README.md +++ b/.README/README.md @@ -391,6 +391,8 @@ The format of the configuration is as follows: - a string type to be preferred in its place (and which `fix` mode can replace) - `false` (for forbidding the type) + - an optional key `skipRootChecking` (for `check-types`) to allow for this + type in the context of a root (i.e., a parent object of some child type) Note that the preferred types indicated as targets in `settings.jsdoc.preferredTypes` map will be assumed to be defined by diff --git a/README.md b/README.md index 3532a0126..8d5e3bbc5 100644 --- a/README.md +++ b/README.md @@ -480,6 +480,8 @@ The format of the configuration is as follows: - a string type to be preferred in its place (and which `fix` mode can replace) - `false` (for forbidding the type) + - an optional key `skipRootChecking` (for `check-types`) to allow for this + type in the context of a root (i.e., a parent object of some child type) Note that the preferred types indicated as targets in `settings.jsdoc.preferredTypes` map will be assumed to be defined by @@ -5737,6 +5739,15 @@ function abc(param) { return 'abc'; } // Message: Invalid JSDoc @param "param" type "Object"; prefer: "object". + +/** + * @param {object} root + * @param {number} root.a + * @param {object} b + */ +function a () {} +// Settings: {"jsdoc":{"preferredTypes":{"object":{"skipRootChecking":true}}}} +// Message: Invalid JSDoc @param "b" type "object". ```` The following patterns are not considered problems: @@ -6055,6 +6066,13 @@ function foo(spec) { } foo() + +/** + * @param {object} root + * @param {number} root.a + */ +function a () {} +// Settings: {"jsdoc":{"preferredTypes":{"object":{"message":"Won't see this message","skipRootChecking":true}}}} ```` diff --git a/src/rules/checkTypes.js b/src/rules/checkTypes.js index 64e1f1a33..fcc4dd13c 100644 --- a/src/rules/checkTypes.js +++ b/src/rules/checkTypes.js @@ -237,13 +237,15 @@ export default iterateJsdoc(({ * @param {string} type * @param {string} value * @param {string} tagName + * @param {string} nameInTag + * @param {number} idx * @param {string} property * @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} node * @param {import('jsdoc-type-pratt-parser/dist/src/index.d.ts').NonTerminalResult} parentNode * @param {string[]} invalidTypes * @returns {void} */ - const getInvalidTypes = (type, value, tagName, property, node, parentNode, invalidTypes) => { + const getInvalidTypes = (type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes) => { let typeNodeName = type === 'JsdocTypeAny' ? '*' : value; const [ @@ -267,13 +269,17 @@ export default iterateJsdoc(({ invalidTypes.push([ typeNodeName, preferred, ]); - } else if (typeof preferredSetting === 'object') { - preferred = preferredSetting?.replacement; - invalidTypes.push([ - typeNodeName, - preferred, - preferredSetting?.message, - ]); + } else if (preferredSetting && typeof preferredSetting === 'object') { + const nextItem = preferredSetting.skipRootChecking && jsdocTagsWithPossibleType[idx + 1]; + + if (!nextItem || !nextItem.name.startsWith(`${nameInTag}.`)) { + preferred = preferredSetting.replacement; + invalidTypes.push([ + typeNodeName, + preferred, + preferredSetting.message, + ]); + } } else { utils.reportSettings( 'Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.', @@ -306,7 +312,10 @@ export default iterateJsdoc(({ } }; - for (const jsdocTag of jsdocTagsWithPossibleType) { + for (const [ + idx, + jsdocTag, + ] of jsdocTagsWithPossibleType.entries()) { const invalidTypes = []; let typeAst; @@ -316,7 +325,10 @@ export default iterateJsdoc(({ continue; } - const tagName = jsdocTag.tag; + const { + tag: tagName, + name: nameInTag, + } = jsdocTag; traverse(typeAst, (node, parentNode, property) => { const { @@ -329,7 +341,7 @@ export default iterateJsdoc(({ return; } - getInvalidTypes(type, value, tagName, property, node, parentNode, invalidTypes); + getInvalidTypes(type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes); }); if (invalidTypes.length) { diff --git a/test/rules/assertions/checkTypes.js b/test/rules/assertions/checkTypes.js index 05d393464..f99c7fefc 100644 --- a/test/rules/assertions/checkTypes.js +++ b/test/rules/assertions/checkTypes.js @@ -2396,6 +2396,31 @@ export default { } `, }, + { + code: ` + /** + * @param {object} root + * @param {number} root.a + * @param {object} b + */ + function a () {} + `, + errors: [ + { + line: 5, + message: 'Invalid JSDoc @param "b" type "object".', + }, + ], + settings: { + jsdoc: { + preferredTypes: { + object: { + skipRootChecking: true, + }, + }, + }, + }, + }, ], valid: [ { @@ -3057,5 +3082,24 @@ export default { foo() `, }, + { + code: ` + /** + * @param {object} root + * @param {number} root.a + */ + function a () {} + `, + settings: { + jsdoc: { + preferredTypes: { + object: { + message: 'Won\'t see this message', + skipRootChecking: true, + }, + }, + }, + }, + }, ], };