From d8c3e6eca0f036df1a863e7d41686f9421aa4598 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 8 Apr 2019 19:28:49 +0800 Subject: [PATCH] - Change: `check-types` will now include the preferred type in its report message - Change: `no-undefined-types` now automatically allows `NaN` and `Infinity` types - Enhancement: `check-types` and `no-undefined-types`: utilize `settings.jsdoc.preferredTypes` map - Enhancement: `check-types`: Add option object with `noDefaults` - Enhancement: `no-undefined-types`: Add option object with `preferredTypesAreDefined` and `definedTypes` - Docs: `no-undefined-types`: Mention role of globals --- .README/README.md | 25 ++ .README/rules/check-types.md | 13 +- .README/rules/no-undefined-types.md | 23 +- README.md | 258 +++++++++++++- src/iterateJsdoc.js | 4 +- src/rules/checkTypes.js | 77 +++- src/rules/noUndefinedTypes.js | 38 +- test/rules/assertions/checkTypes.js | 410 +++++++++++++++++++++- test/rules/assertions/noUndefinedTypes.js | 123 +++++++ 9 files changed, 934 insertions(+), 37 deletions(-) diff --git a/.README/README.md b/.README/README.md index 373cf5594..775d38f4e 100644 --- a/.README/README.md +++ b/.README/README.md @@ -198,6 +198,31 @@ The format of the configuration is as follows: } ``` +### Settings to Configure `check-types` and `no-undefined-types` + +- `settings.jsdoc.preferredTypes` An option map to indicate preferred + or forbidden types (if default types are indicated here, these will + have precedence over the default recommendations for `check-types`). + The keys of this map are the types to be replaced (or forbidden) and + can include the "ANY" type, `*`. + The values can be: + - `false` to forbid the type + - a string to indicate the type that should be preferred in its place + (and which `fix` mode can replace) + - an object with the key `message` to provide a specific error message + when encountering the discouraged type and, if a type is to be preferred + in its place, the key `replacement` to indicate the type that should be + used in its place (and which `fix` mode can replace) or `false` if + forbidding the type. The message string will have the following + substrings with special meaning replaced with their corresponding + value (`{{tagName}}`, `{{tagValue}}`, `{{badType}}`, and + `{{preferredType}}`, noting that the latter is of no use when one is + merely forbidding a type). + +If `no-undefined-types` has the option key `preferredTypesDefined` set to +`true`, the preferred types indicated in the `settings.jsdoc.preferredTypes` +map will be assumed to be defined. + ### Settings to Configure `valid-types` * `settings.jsdoc.allowEmptyNamepaths` - Set to `false` to disallow diff --git a/.README/rules/check-types.md b/.README/rules/check-types.md index 264031c25..fb198ece0 100644 --- a/.README/rules/check-types.md +++ b/.README/rules/check-types.md @@ -2,7 +2,7 @@ Reports invalid types. -Ensures that case of native types is the same as in this list: +By default, ensures that case of native types is the same as in this list: ``` undefined @@ -16,6 +16,17 @@ Date RegExp ``` +#### Options + +`check-types` allows one option: + +- An option object 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. + +See also the documentation on `settings.jsdoc.preferredTypes` which impacts +the behavior of `check-types`. + #### Why not capital case everything? Why are `boolean`, `number` and `string` exempt from starting with a capital letter? Let's take `string` as an example. In Javascript, everything is an object. The string Object has prototypes for string functions such as `.toUpperCase()`. diff --git a/.README/rules/no-undefined-types.md b/.README/rules/no-undefined-types.md index 01071de73..c8e0bda0d 100644 --- a/.README/rules/no-undefined-types.md +++ b/.README/rules/no-undefined-types.md @@ -6,11 +6,30 @@ unimported types. When enabling this rule, types in jsdoc comments will resolve as used variables, i.e. will not be marked as unused by `no-unused-vars`. -The following tags will be checked for name(paths) definitions to also serve -as a potential "type" for checking the tag types in the table below: +In addition to considering globals found in code (or in ESLint-indicated +`globals`) as defined, the following tags will also be checked for +name(path) definitions to also serve as a potential "type" for checking +the tag types in the table below: `callback`, `class` (or `constructor`), `constant` (or `const`), `event`, `external` (or `host`), `function` (or `func` or `method`), `interface`, `member` (or `var`), `mixin`, `name`, `namespace`, `typedef`. +The following types are always considered defined. + +- `null`, `undefined`, `string`, `boolean` +- `number`, `NaN`, `Infinity` +- `any`, `*` +- `Array`, `Object`, `RegExp`, `Date`, `Function` + +#### Options + +An option object may have the following keys: + +- `preferredTypesDefined` - If this option is set to `true` and preferred + types are indicated within `settings.jsdoc.preferredTypes`, any such + types will be assumed to be defined as well. +- `definedTypes` - This array can be populated to indicate other types which + are automatically considered as defined (in addition to globals, etc.) + ||| |---|---| |Context|`ArrowFunctionExpression`, `FunctionDeclaration`, `FunctionExpression`| diff --git a/README.md b/README.md index f98d83ebb..2021a19e6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ JSDoc linting rules for ESLint. * [Alias Preference](#eslint-plugin-jsdoc-settings-alias-preference) * [Additional Tag Names](#eslint-plugin-jsdoc-settings-additional-tag-names) * [Allow `@override` Without Accompanying `@param` Tags](#eslint-plugin-jsdoc-settings-allow-override-without-accompanying-param-tags) + * [Settings to Configure `check-types` and `no-undefined-types`](#eslint-plugin-jsdoc-settings-settings-to-configure-check-types-and-no-undefined-types) * [Settings to Configure `valid-types`](#eslint-plugin-jsdoc-settings-settings-to-configure-valid-types) * [Settings to Configure `require-returns`](#eslint-plugin-jsdoc-settings-settings-to-configure-require-returns) * [Settings to Configure `require-example`](#eslint-plugin-jsdoc-settings-settings-to-configure-require-example) @@ -249,6 +250,32 @@ The format of the configuration is as follows: } ``` + +### Settings to Configure check-types and no-undefined-types + +- `settings.jsdoc.preferredTypes` An option map to indicate preferred + or forbidden types (if default types are indicated here, these will + have precedence over the default recommendations for `check-types`). + The keys of this map are the types to be replaced (or forbidden) and + can include the "ANY" type, `*`. + The values can be: + - `false` to forbid the type + - a string to indicate the type that should be preferred in its place + (and which `fix` mode can replace) + - an object with the key `message` to provide a specific error message + when encountering the discouraged type and, if a type is to be preferred + in its place, the key `replacement` to indicate the type that should be + used in its place (and which `fix` mode can replace) or `false` if + forbidding the type. The message string will have the following + substrings with special meaning replaced with their corresponding + value (`{{tagName}}`, `{{tagValue}}`, `{{badType}}`, and + `{{preferredType}}`, noting that the latter is of no use when one is + merely forbidding a type). + +If `no-undefined-types` has the option key `preferredTypesDefined` set to +`true`, the preferred types indicated in the `settings.jsdoc.preferredTypes` +map will be assumed to be defined. + ### Settings to Configure valid-types @@ -1213,7 +1240,7 @@ function quux (foo) {} Reports invalid types. -Ensures that case of native types is the same as in this list: +By default, ensures that case of native types is the same as in this list: ``` undefined @@ -1227,6 +1254,18 @@ Date RegExp ``` + +#### Options + +`check-types` allows one option: + +- An option object 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. + +See also the documentation on `settings.jsdoc.preferredTypes` which impacts +the behavior of `check-types`. + #### Why not capital case everything? @@ -1274,7 +1313,7 @@ The following patterns are considered problems: function quux (foo) { } -// Message: Invalid JSDoc @param "foo" type "Number". +// Message: Invalid JSDoc @param "foo" type "Number"; prefer: "number". /** * @arg {Number} foo @@ -1282,7 +1321,7 @@ function quux (foo) { function quux (foo) { } -// Message: Invalid JSDoc @arg "foo" type "Number". +// Message: Invalid JSDoc @arg "foo" type "Number"; prefer: "number". /** * @returns {Number} foo @@ -1291,7 +1330,7 @@ function quux (foo) { function quux () { } -// Message: Invalid JSDoc @returns type "Number". +// Message: Invalid JSDoc @returns type "Number"; prefer: "number". /** * @param {(Number|string|Boolean)=} foo @@ -1299,7 +1338,7 @@ function quux () { function quux (foo, bar, baz) { } -// Message: Invalid JSDoc @param "foo" type "Number". +// Message: Invalid JSDoc @param "foo" type "Number"; prefer: "number". /** * @param {Array} foo @@ -1307,7 +1346,117 @@ function quux (foo, bar, baz) { function quux (foo, bar, baz) { } -// Message: Invalid JSDoc @param "foo" type "Number". +// Message: Invalid JSDoc @param "foo" type "Number"; prefer: "number". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":"Abc","string":"Str"}}} +// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":{"replacement":"Abc"},"string":"Str"}}} +// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":{"message":"Messed up JSDoc @{{tagName}}{{tagValue}} type \"{{badType}}\"; prefer: \"{{preferredType}}\".","replacement":"Abc"},"string":"Str"}}} +// Message: Messed up JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + * @param {cde} bar + * @param {Object} baz + */ +function qux(foo, bar, baz) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":{"message":"Messed up JSDoc @{{tagName}}{{tagValue}} type \"{{badType}}\"; prefer: \"{{preferredType}}\".","replacement":"Abc"},"cde":{"message":"More messed up JSDoc @{{tagName}}{{tagValue}} type \"{{badType}}\"; prefer: \"{{preferredType}}\".","replacement":"Cde"},"Object":"object"}}} +// Message: Messed up JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":{"message":"Messed up JSDoc @{{tagName}}{{tagValue}} type \"{{badType}}\".","replacement":false},"string":"Str"}}} +// Message: Messed up JSDoc @param "foo" type "abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":{"message":"Messed up JSDoc @{{tagName}}{{tagValue}} type \"{{badType}}\"."},"string":"Str"}}} +// Message: Messed up JSDoc @param "foo" type "abc". + +/** + * @param {abc} foo + * @param {Number} bar + */ +function qux(foo, bar) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":"Abc","string":"Str"}}} +// Options: [{"noDefaults":true}] +// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + * @param {Number} bar + */ +function qux(foo, bar) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":"Abc","string":"Str"}}} +// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":false,"string":"Str"}}} +// Message: Invalid JSDoc @param "foo" type "abc". + +/** + * @param {abc} foo + */ +function qux(foo) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":false}}} +// Message: Invalid JSDoc @param "foo" type "abc". + +/** + * @param {*} baz + */ +function qux(baz) { +} +// Settings: {"jsdoc":{"preferredTypes":{"*":false,"abc":"Abc","string":"Str"}}} +// Message: Invalid JSDoc @param "baz" type "*". + +/** + * @param {*} baz + */ +function qux(baz) { +} +// Settings: {"jsdoc":{"preferredTypes":{"*":"aaa","abc":"Abc","string":"Str"}}} +// Message: Invalid JSDoc @param "baz" type "*"; prefer: "aaa". + +/** + * @param {abc} foo + * @param {Number} bar + */ +function qux(foo, bar) { +} +// Settings: {"jsdoc":{"preferredTypes":{"abc":"Abc","string":"Str"}}} +// Message: Invalid JSDoc @param "foo" type "abc"; prefer: "Abc". ```` The following patterns are not considered problems: @@ -1361,6 +1510,23 @@ function qux(foo) { */ function qux(foo) { } + +/** + * @returns {Number} foo + * @throws {Number} foo + */ +function quux () { + +} +// Options: [{"noDefaults":true}] + +/** + * @param {object} foo + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"preferredTypes":{"Object":"object"}}} ```` @@ -1891,11 +2057,31 @@ unimported types. When enabling this rule, types in jsdoc comments will resolve as used variables, i.e. will not be marked as unused by `no-unused-vars`. -The following tags will be checked for name(paths) definitions to also serve -as a potential "type" for checking the tag types in the table below: +In addition to considering globals found in code (or in ESLint-indicated +`globals`) as defined, the following tags will also be checked for +name(path) definitions to also serve as a potential "type" for checking +the tag types in the table below: `callback`, `class` (or `constructor`), `constant` (or `const`), `event`, `external` (or `host`), `function` (or `func` or `method`), `interface`, `member` (or `var`), `mixin`, `name`, `namespace`, `typedef`. +The following types are always considered defined. + +- `null`, `undefined`, `string`, `boolean` +- `number`, `NaN`, `Infinity` +- `any`, `*` +- `Array`, `Object`, `RegExp`, `Date`, `Function` + + +#### Options + +An option object may have the following keys: + +- `preferredTypesDefined` - If this option is set to `true` and preferred + types are indicated within `settings.jsdoc.preferredTypes`, any such + types will be assumed to be defined as well. +- `definedTypes` - This array can be populated to indicate other types which + are automatically considered as defined (in addition to globals, etc.) + ||| |---|---| |Context|`ArrowFunctionExpression`, `FunctionDeclaration`, `FunctionExpression`| @@ -1913,6 +2099,40 @@ function quux(foo) { } // Message: The type 'strnig' is undefined. + +/** +* @param {MyType} foo - Bar. +* @param {HisType} bar - Foo. +*/ +function quux(foo, bar) { + +} +// Options: [{"definedTypes":["MyType"]}] +// Message: The type 'HisType' is undefined. + +/** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ +function quux(foo, bar, baz) { + +} +// Settings: {"jsdoc":{"preferredTypes":{"hertype":{"replacement":"HerType"}}}} +// Options: [{"definedTypes":["MyType"],"preferredTypesDefined":true}] +// Message: The type 'HisType' is undefined. + +/** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ +function quux(foo, bar, baz) { + +} +// Settings: {"jsdoc":{"preferredTypes":{"hertype":{"replacement":false},"histype":"HisType"}}} +// Options: [{"definedTypes":["MyType"],"preferredTypesDefined":true}] +// Message: The type 'HerType' is undefined. ```` The following patterns are not considered problems: @@ -2023,6 +2243,26 @@ function testFunction(callback) { function foo () { } + +/** +* @param {MyType} foo - Bar. +* @param {HisType} bar - Foo. +*/ +function quux(foo, bar) { + +} +// Options: [{"definedTypes":["MyType","HisType"]}] + +/** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ +function quux(foo, bar, baz) { + +} +// Settings: {"jsdoc":{"preferredTypes":{"hertype":{"replacement":"HerType"},"histype":"HisType"}}} +// Options: [{"definedTypes":["MyType"],"preferredTypesDefined":true}] ```` @@ -2300,7 +2540,7 @@ Requires that all functions have a description. * All functions must have a `@description` tag. * Every description tag must have a non-empty description that explains the purpose of the method. - + #### Options - `contexts` - Set to a string or array of strings representing the AST context diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 81b34dcd7..97e799130 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -293,7 +293,7 @@ export default (iterator, opts = {}) => { const jsdoc = parseComment(jsdocNode, indent); - const report = (message, fixer = null, jsdocLoc = null) => { + const report = (message, fixer = null, jsdocLoc = null, data = null) => { let loc; if (jsdocLoc) { @@ -312,12 +312,14 @@ export default (iterator, opts = {}) => { } if (fixer === null) { context.report({ + data, loc, message, node: jsdocNode }); } else { context.report({ + data, fix: fixer, loc, message, diff --git a/src/rules/checkTypes.js b/src/rules/checkTypes.js index 4f8403169..1bfc67ace 100644 --- a/src/rules/checkTypes.js +++ b/src/rules/checkTypes.js @@ -1,3 +1,4 @@ +import _ from 'lodash'; import {parse, traverse, publish} from 'jsdoctypeparser'; import iterateJsdoc from '../iterateJsdoc'; @@ -18,12 +19,17 @@ export default iterateJsdoc(({ jsdocNode, sourceCode, report, - utils + utils, + context }) => { const jsdocTags = utils.filterTags((tag) => { return utils.isTagWithType(tag.tag); }); + const preferredTypes = _.get(context, 'settings.jsdoc.preferredTypes'); + const optionObj = context.options[0]; + const noDefaults = _.get(optionObj, 'noDefaults'); + jsdocTags.forEach((jsdocTag) => { const invalidTypes = []; let typeAst; @@ -35,12 +41,44 @@ export default iterateJsdoc(({ } traverse(typeAst, (node) => { - if (node.type === 'NAME') { - for (const strictNativeType of strictNativeTypes) { - if (strictNativeType.toLowerCase() === node.name.toLowerCase() && strictNativeType !== node.name) { - invalidTypes.push(node.name); - node.name = strictNativeType; + if (['NAME', 'ANY'].includes(node.type)) { + const nodeName = node.type === 'ANY' ? '*' : node.name; + let preferred; + if (preferredTypes && _.get(preferredTypes, nodeName) !== undefined) { + const preferredSetting = preferredTypes[nodeName]; + + if (!preferredSetting) { + invalidTypes.push([nodeName]); + } else if (typeof preferredSetting === 'string') { + preferred = preferredSetting; + invalidTypes.push([nodeName, preferred]); + } else if (typeof preferredSetting === 'object') { + preferred = _.get(preferredSetting, 'replacement'); + invalidTypes.push([ + nodeName, + preferred, + _.get(preferredSetting, 'message') + ]); + } + } else if (!noDefaults && node.type === 'NAME') { + for (const strictNativeType of strictNativeTypes) { + if (strictNativeType.toLowerCase() === nodeName.toLowerCase() && + strictNativeType !== nodeName && + + // Don't report if user has own map for a strict native type + (!preferredTypes || _.get(preferredTypes, strictNativeType) === undefined) + ) { + preferred = strictNativeType; + invalidTypes.push([nodeName, preferred]); + break; + } + } + } + if (preferred) { + if (node.type === 'ANY') { + node.type = 'NAME'; } + node.name = preferred; } } }); @@ -48,14 +86,33 @@ export default iterateJsdoc(({ if (invalidTypes) { const fixedType = publish(typeAst); - invalidTypes.forEach((invalidType) => { + const tagName = jsdocTag.tag; + invalidTypes.forEach(([badType, preferredType = '', message]) => { const fix = (fixer) => { - return fixer.replaceText(jsdocNode, sourceCode.getText(jsdocNode).replace('{' + jsdocTag.type + '}', '{' + fixedType + '}')); + return fixer.replaceText( + jsdocNode, + sourceCode.getText(jsdocNode).replace( + '{' + jsdocTag.type + '}', '{' + fixedType + '}' + ) + ); }; - const name = jsdocTag.name ? ' "' + jsdocTag.name + '"' : ''; + const tagValue = jsdocTag.name ? ' "' + jsdocTag.name + '"' : ''; - report('Invalid JSDoc @' + jsdocTag.tag + name + ' type "' + invalidType + '".', fix, jsdocTag); + report( + message || + 'Invalid JSDoc @' + tagName + tagValue + ' type "' + badType + + (preferredType ? '"; prefer: "' + preferredType : '') + + '".', + preferredType ? fix : null, + jsdocTag, + message ? { + badType, + preferredType, + tagName, + tagValue + } : null + ); }); } }); diff --git a/src/rules/noUndefinedTypes.js b/src/rules/noUndefinedTypes.js index 74a21a495..c88c1906b 100644 --- a/src/rules/noUndefinedTypes.js +++ b/src/rules/noUndefinedTypes.js @@ -3,7 +3,9 @@ import {parse as parseType, traverse} from 'jsdoctypeparser'; import iterateJsdoc, {parseComment} from '../iterateJsdoc'; const extraTypes = [ - 'null', 'undefined', 'string', 'number', 'boolean', 'any', '*', + 'null', 'undefined', 'string', 'boolean', + 'number', 'NaN', 'Infinity', + 'any', '*', 'Array', 'Object', 'RegExp', 'Date', 'Function' ]; @@ -13,8 +15,30 @@ export default iterateJsdoc(({ sourceCode, utils }) => { - const scopeManager = sourceCode.scopeManager; - const globalScope = scopeManager.globalScope; + const {scopeManager} = sourceCode; + const {globalScope} = scopeManager; + + const {preferredTypesDefined, definedTypes = []} = context.options[0] || {}; + + let definedPreferredTypes = []; + if (preferredTypesDefined) { + const preferredTypes = _.get(context, 'settings.jsdoc.preferredTypes'); + if (preferredTypes) { + // Replace `_.values` with `Object.values` when we may start requiring Node 7+ + definedPreferredTypes = _.values(preferredTypes).map((preferredType) => { + if (typeof preferredType === 'string') { + return preferredType; + } + if (!preferredType || typeof preferredType !== 'object') { + return undefined; + } + + return preferredType.replacement; + }).filter((preferredType) => { + return preferredType; + }); + } + } const typedefDeclarations = _(context.getAllComments()) .filter((comment) => { @@ -31,7 +55,7 @@ export default iterateJsdoc(({ }) .value(); - const definedTypes = globalScope.variables.map((variable) => { + const allDefinedTypes = globalScope.variables.map((variable) => { return variable.name; }) @@ -51,7 +75,9 @@ export default iterateJsdoc(({ }) : [] ) .concat(extraTypes) - .concat(typedefDeclarations); + .concat(typedefDeclarations) + .concat(definedTypes) + .concat(definedPreferredTypes); const jsdocTags = utils.filterTags((tag) => { return utils.isTagWithType(tag.tag); @@ -69,7 +95,7 @@ export default iterateJsdoc(({ traverse(parsedType, ({type, name}) => { if (type === 'NAME') { - if (!definedTypes.includes(name)) { + if (!allDefinedTypes.includes(name)) { report('The type \'' + name + '\' is undefined.', null, tag); } else if (!_.includes(extraTypes, name)) { context.markVariableAsUsed(name); diff --git a/test/rules/assertions/checkTypes.js b/test/rules/assertions/checkTypes.js index 9ab2c7cc6..0b8e13e01 100644 --- a/test/rules/assertions/checkTypes.js +++ b/test/rules/assertions/checkTypes.js @@ -12,7 +12,7 @@ export default { errors: [ { line: 3, - message: 'Invalid JSDoc @param "foo" type "Number".' + message: 'Invalid JSDoc @param "foo" type "Number"; prefer: "number".' } ], output: ` @@ -36,7 +36,7 @@ export default { errors: [ { line: 3, - message: 'Invalid JSDoc @arg "foo" type "Number".' + message: 'Invalid JSDoc @arg "foo" type "Number"; prefer: "number".' } ], output: ` @@ -61,11 +61,11 @@ export default { errors: [ { line: 3, - message: 'Invalid JSDoc @returns type "Number".' + message: 'Invalid JSDoc @returns type "Number"; prefer: "number".' }, { line: 4, - message: 'Invalid JSDoc @throws type "Number".' + message: 'Invalid JSDoc @throws type "Number"; prefer: "number".' } ] }, @@ -81,11 +81,11 @@ export default { errors: [ { line: 3, - message: 'Invalid JSDoc @param "foo" type "Number".' + message: 'Invalid JSDoc @param "foo" type "Number"; prefer: "number".' }, { line: 3, - message: 'Invalid JSDoc @param "foo" type "Boolean".' + message: 'Invalid JSDoc @param "foo" type "Boolean"; prefer: "boolean".' } ], output: ` @@ -109,11 +109,11 @@ export default { errors: [ { line: 3, - message: 'Invalid JSDoc @param "foo" type "Number".' + message: 'Invalid JSDoc @param "foo" type "Number"; prefer: "number".' }, { line: 3, - message: 'Invalid JSDoc @param "foo" type "String".' + message: 'Invalid JSDoc @param "foo" type "String"; prefer: "string".' } ], output: ` @@ -124,6 +124,369 @@ export default { } ` + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: 'Abc', + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: { + replacement: 'Abc' + }, + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Messed up JSDoc @param "foo" type "abc"; prefer: "Abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: { + message: 'Messed up JSDoc @{{tagName}}{{tagValue}} type "{{badType}}"; prefer: "{{preferredType}}".', + replacement: 'Abc' + }, + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + * @param {cde} bar + * @param {Object} baz + */ + function qux(foo, bar, baz) { + } + `, + errors: [ + { + line: 3, + message: 'Messed up JSDoc @param "foo" type "abc"; prefer: "Abc".' + }, + { + line: 4, + message: 'More messed up JSDoc @param "bar" type "cde"; prefer: "Cde".' + }, + { + line: 5, + message: 'Invalid JSDoc @param "baz" type "Object"; prefer: "object".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: { + message: 'Messed up JSDoc @{{tagName}}{{tagValue}} type "{{badType}}"; prefer: "{{preferredType}}".', + replacement: 'Abc' + }, + cde: { + message: 'More messed up JSDoc @{{tagName}}{{tagValue}} type "{{badType}}"; prefer: "{{preferredType}}".', + replacement: 'Cde' + }, + Object: 'object' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Messed up JSDoc @param "foo" type "abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: { + message: 'Messed up JSDoc @{{tagName}}{{tagValue}} type "{{badType}}".', + replacement: false + }, + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Messed up JSDoc @param "foo" type "abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: { + message: 'Messed up JSDoc @{{tagName}}{{tagValue}} type "{{badType}}".' + }, + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + * @param {Number} bar + */ + function qux(foo, bar) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".' + } + ], + options: [{ + noDefaults: true + }], + settings: { + jsdoc: { + preferredTypes: { + abc: 'Abc', + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + * @param {Number} bar + */ + function qux(foo, bar) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".' + }, + { + line: 4, + message: 'Invalid JSDoc @param "bar" type "Number"; prefer: "number".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: 'Abc', + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: false, + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + */ + function qux(foo) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc".' + } + ], + settings: { + jsdoc: { + preferredTypes: { + abc: false + } + } + } + }, + { + code: ` + /** + * @param {*} baz + */ + function qux(baz) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "baz" type "*".' + } + ], + output: ` + /** + * @param {*} baz + */ + function qux(baz) { + } + `, + settings: { + jsdoc: { + preferredTypes: { + '*': false, + abc: 'Abc', + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {*} baz + */ + function qux(baz) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "baz" type "*"; prefer: "aaa".' + } + ], + output: ` + /** + * @param {aaa} baz + */ + function qux(baz) { + } + `, + settings: { + jsdoc: { + preferredTypes: { + '*': 'aaa', + abc: 'Abc', + string: 'Str' + } + } + } + }, + { + code: ` + /** + * @param {abc} foo + * @param {Number} bar + */ + function qux(foo, bar) { + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "abc"; prefer: "Abc".' + }, + { + line: 4, + message: 'Invalid JSDoc @param "bar" type "Number"; prefer: "number".' + } + ], + output: ` + /** + * @param {Abc} foo + * @param {Number} bar + */ + function qux(foo, bar) { + } + `, + settings: { + jsdoc: { + preferredTypes: { + abc: 'Abc', + string: 'Str' + } + } + } } ], valid: [ @@ -196,6 +559,37 @@ export default { function qux(foo) { } ` + }, + { + code: ` + /** + * @returns {Number} foo + * @throws {Number} foo + */ + function quux () { + + } + `, + options: [{ + noDefaults: true + }] + }, + { + code: ` + /** + * @param {object} foo + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + preferredTypes: { + Object: 'object' + } + } + } } ] }; diff --git a/test/rules/assertions/noUndefinedTypes.js b/test/rules/assertions/noUndefinedTypes.js index 42e6acda8..63fc48810 100644 --- a/test/rules/assertions/noUndefinedTypes.js +++ b/test/rules/assertions/noUndefinedTypes.js @@ -18,6 +18,89 @@ export default { rules: { 'no-undef': 'error' } + }, + { + code: ` + /** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + */ + function quux(foo, bar) { + + } + `, + errors: [ + { + line: 4, + message: 'The type \'HisType\' is undefined.' + } + ], + options: [{ + definedTypes: ['MyType'] + }] + }, + { + code: ` + /** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ + function quux(foo, bar, baz) { + + } + `, + errors: [ + { + line: 4, + message: 'The type \'HisType\' is undefined.' + } + ], + options: [{ + definedTypes: ['MyType'], + preferredTypesDefined: true + }], + settings: { + jsdoc: { + preferredTypes: { + hertype: { + replacement: 'HerType' + } + } + } + } + }, + { + code: ` + /** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ + function quux(foo, bar, baz) { + + } + `, + errors: [ + { + line: 5, + message: 'The type \'HerType\' is undefined.' + } + ], + options: [{ + definedTypes: ['MyType'], + preferredTypesDefined: true + }], + settings: { + jsdoc: { + preferredTypes: { + hertype: { + replacement: false + }, + histype: 'HisType' + } + } + } } ], valid: [ @@ -177,6 +260,46 @@ export default { } ` + }, + { + code: ` + /** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + */ + function quux(foo, bar) { + + } + `, + options: [{ + definedTypes: ['MyType', 'HisType'] + }] + }, + { + code: ` + /** + * @param {MyType} foo - Bar. + * @param {HisType} bar - Foo. + * @param {HerType} baz - Foo. + */ + function quux(foo, bar, baz) { + + } + `, + options: [{ + definedTypes: ['MyType'], + preferredTypesDefined: true + }], + settings: { + jsdoc: { + preferredTypes: { + hertype: { + replacement: 'HerType' + }, + histype: 'HisType' + } + } + } } ] };