From 5d996636961cd44c13e2b89d84180909bd4706e9 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 28 Dec 2019 11:54:34 +0800 Subject: [PATCH] feat(`check-types`): add option `exemptTagContexts` to exempt type-checking (certain types or any types) on specific tags; fixes #255 --- .README/rules/check-types.md | 12 +++ README.md | 49 +++++++++ src/rules/checkTypes.js | 39 +++++++- test/rules/assertions/checkTypes.js | 150 ++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 3 deletions(-) diff --git a/.README/rules/check-types.md b/.README/rules/check-types.md index 6884aab9..65257e7b 100644 --- a/.README/rules/check-types.md +++ b/.README/rules/check-types.md @@ -27,6 +27,18 @@ RegExp - with the key `noDefaults` to insist that only the supplied option type map is to be used, and that the default preferences (such as "string" over "String") will not be enforced. The option's default is `false`. + - with the key `exemptTagContexts` which will avoid reporting when a + bad type is found on a specified tag. Set to an array of objects with + a key `tag` set to the tag to exempt, and a `types` key which can + either be `true` to indicate that any types on that tag will be allowed, + or to an array of strings which will only allow specific bad types. + If an array of strings is given, these must match the type exactly, + e.g., if you only allow `"object"`, it will not allow + `"object"`. Note that this is different from the + behavior of `settings.jsdoc.preferredTypes`. This option is useful + for normally restricting generic types like `object` with + `preferredTypes`, but allowing `typedef` to indicate that its base + type is `object`. - with the key `unifyParentAndChildTypeChecks` which will treat `settings.jsdoc.preferredTypes` keys such as `SomeType` as matching not only child types such as an unadorned `SomeType` but also diff --git a/README.md b/README.md index c8178e5c..f4dda54d 100644 --- a/README.md +++ b/README.md @@ -2525,6 +2525,18 @@ RegExp - with the key `noDefaults` to insist that only the supplied option type map is to be used, and that the default preferences (such as "string" over "String") will not be enforced. The option's default is `false`. + - with the key `exemptTagContexts` which will avoid reporting when a + bad type is found on a specified tag. Set to an array of objects with + a key `tag` set to the tag to exempt, and a `types` key which can + either be `true` to indicate that any types on that tag will be allowed, + or to an array of strings which will only allow specific bad types. + If an array of strings is given, these must match the type exactly, + e.g., if you only allow `"object"`, it will not allow + `"object"`. Note that this is different from the + behavior of `settings.jsdoc.preferredTypes`. This option is useful + for normally restricting generic types like `object` with + `preferredTypes`, but allowing `typedef` to indicate that its base + type is `object`. - with the key `unifyParentAndChildTypeChecks` which will treat `settings.jsdoc.preferredTypes` keys such as `SomeType` as matching not only child types such as an unadorned `SomeType` but also @@ -3115,6 +3127,32 @@ function quux () {} function quux () {} // Settings: {"jsdoc":{"mode":"closure"}} // Message: Invalid JSDoc @export type "array"; prefer: "Array". + +/** + * @typedef {object} foo + * @property {object} bar + */ +// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":true}]}] +// Message: Invalid JSDoc @property "bar" type "object"; prefer: "Object". + +/** @typedef {object} foo */ +// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":["array"]}]}] +// Message: Invalid JSDoc @typedef "foo" type "object"; prefer: "Object". + +/** + * @typedef {object} foo + * @property {object} bar + */ +// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":["object"]}]}] +// Message: Invalid JSDoc @property "bar" type "object"; prefer: "Object". + +/** @typedef {object} foo */ +// Settings: {"jsdoc":{"preferredTypes":{"object<>":"Object<>"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":["object"]}]}] +// Message: Invalid JSDoc @typedef "foo" type "object"; prefer: "Object<>". ```` The following patterns are not considered problems: @@ -3337,6 +3375,17 @@ function quux () {} // Settings: {"jsdoc":{"mode":"closure"}} /** @type {new() => EntityBase} */ + +/** @typedef {object} foo */ +// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":true}]}] + +/** @typedef {object} foo */ +// Settings: {"jsdoc":{"preferredTypes":{"object":"Object"}}} + +/** @typedef {object} foo */ +// Settings: {"jsdoc":{"preferredTypes":{"object<>":"Object<>"}}} +// Options: [{"exemptTagContexts":[{"tag":"typedef","types":["object"]}]}] ```` diff --git a/src/rules/checkTypes.js b/src/rules/checkTypes.js index 5f79a6ce..9c635c46 100644 --- a/src/rules/checkTypes.js +++ b/src/rules/checkTypes.js @@ -60,9 +60,11 @@ export default iterateJsdoc(({ }); const {preferredTypes} = settings; - const optionObj = context.options[0]; - const noDefaults = _.get(optionObj, 'noDefaults'); - const unifyParentAndChildTypeChecks = _.get(optionObj, 'unifyParentAndChildTypeChecks'); + const { + noDefaults, + unifyParentAndChildTypeChecks, + exemptTagContexts = [], + } = context.options[0] || {}; const getPreferredTypeInfo = (type, nodeName, parentName, parentNode) => { let hasMatchingPreferredType; @@ -197,6 +199,12 @@ export default iterateJsdoc(({ }; const tagValue = jsdocTag.name ? ` "${jsdocTag.name}"` : ''; + if (exemptTagContexts.some(({tag, types}) => { + return tag === tagName && + (types === true || types.includes(jsdocTag.type)); + })) { + return; + } report( message || @@ -221,6 +229,31 @@ export default iterateJsdoc(({ { additionalProperties: false, properties: { + exemptTagContexts: { + items: { + additionalProperties: false, + properties: { + tag: { + type: 'string', + }, + types: { + oneOf: [ + { + type: 'boolean', + }, + { + items: { + type: 'string', + }, + type: 'array', + }, + ], + }, + }, + type: 'object', + }, + type: 'array', + }, noDefaults: { type: 'boolean', }, diff --git a/test/rules/assertions/checkTypes.js b/test/rules/assertions/checkTypes.js index 3e99d606..c4c9a7fd 100644 --- a/test/rules/assertions/checkTypes.js +++ b/test/rules/assertions/checkTypes.js @@ -1737,6 +1737,110 @@ export default { }, }, }, + { + code: ` + /** + * @typedef {object} foo + * @property {object} bar + */ + `, + errors: [ + { + line: 4, + message: 'Invalid JSDoc @property "bar" type "object"; prefer: "Object".', + }, + ], + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: true, + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + }, + }, + }, + { + code: '/** @typedef {object} foo */', + errors: [ + { + line: 1, + message: 'Invalid JSDoc @typedef "foo" type "object"; prefer: "Object".', + }, + ], + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: ['array'], + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + }, + }, + }, + { + code: ` + /** + * @typedef {object} foo + * @property {object} bar + */ + `, + errors: [ + { + line: 4, + message: 'Invalid JSDoc @property "bar" type "object"; prefer: "Object".', + }, + ], + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: ['object'], + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + }, + }, + }, + { + code: '/** @typedef {object} foo */', + errors: [{ + line: 1, + message: 'Invalid JSDoc @typedef "foo" type "object"; prefer: "Object<>".', + }], + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: ['object'], + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + 'object<>': 'Object<>', + }, + }, + }, + }, ], valid: [ { @@ -2154,5 +2258,51 @@ export default { /** @type {new() => EntityBase} */ `, }, + { + code: '/** @typedef {object} foo */', + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: true, + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + }, + }, + }, + { + code: '/** @typedef {object} foo */', + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + }, + }, + }, + { + code: '/** @typedef {object} foo */', + options: [ + { + exemptTagContexts: [{ + tag: 'typedef', + types: ['object'], + }], + }, + ], + settings: { + jsdoc: { + preferredTypes: { + 'object<>': 'Object<>', + }, + }, + }, + }, ], };