From 0fa5ad9e065bd593aa51725e4bff86182e28c308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 19:08:48 +0200 Subject: [PATCH 01/12] Adding scaffolding for a new 'mark-used' rule --- .README/README.md | 2 ++ .README/rules/mark-used.md | 40 ++++++++++++++++++++++++++++++++++++++ src/index.js | 2 ++ src/rules/markUsed.js | 19 ++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 .README/rules/mark-used.md create mode 100644 src/rules/markUsed.js diff --git a/.README/README.md b/.README/README.md index 5a241d1e5..f0dceb26d 100644 --- a/.README/README.md +++ b/.README/README.md @@ -57,6 +57,7 @@ Finally, enable all of the rules that you would like to use. "jsdoc/empty-tags": 1, // Recommended "jsdoc/implements-on-classes": 1, // Recommended "jsdoc/informative-docs": 1, + "jsdoc/mark-used": 1, "jsdoc/match-description": 1, "jsdoc/multiline-blocks": 1, // Recommended "jsdoc/no-bad-blocks": 1, @@ -203,6 +204,7 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restart their attached name.| +|||[mark-used](./docs/rules/mark-used.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| diff --git a/.README/rules/mark-used.md b/.README/rules/mark-used.md new file mode 100644 index 000000000..0c10f7671 --- /dev/null +++ b/.README/rules/mark-used.md @@ -0,0 +1,40 @@ + + +# mark-used + +* [Fixer](#user-content-mark-used-fixer) +* [Failing examples](#user-content-mark-used-failing-examples) +* [Passing examples](#user-content-mark-used-passing-examples) + + +Marks all types referenced in `{@link}` tags as used. + + + +## Fixer + +N/A + + + +#### Options + +None. + + + +## Failing examples + +Not applicable, since this only marks identifiers and types as used and never actually fails. + + + +## Passing examples + +The following patterns are not considered problems: + +````js +const foo = "bar"; +/** Uses {@link foo} for something */ +```` + diff --git a/src/index.js b/src/index.js index 257f0245c..ef110adf2 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import checkValues from './rules/checkValues'; import emptyTags from './rules/emptyTags'; import implementsOnClasses from './rules/implementsOnClasses'; import informativeDocs from './rules/informativeDocs'; +import markUsed from './rules/markUsed'; import matchDescription from './rules/matchDescription'; import matchName from './rules/matchName'; import multilineBlocks from './rules/multilineBlocks'; @@ -68,6 +69,7 @@ const index = { 'empty-tags': emptyTags, 'implements-on-classes': implementsOnClasses, 'informative-docs': informativeDocs, + 'mark-used': markUsed, 'match-description': matchDescription, 'match-name': matchName, 'multiline-blocks': multilineBlocks, diff --git a/src/rules/markUsed.js b/src/rules/markUsed.js new file mode 100644 index 000000000..878a16d81 --- /dev/null +++ b/src/rules/markUsed.js @@ -0,0 +1,19 @@ +import iterateJsdoc from '../iterateJsdoc'; + +export default iterateJsdoc(() => {}, { + iterateAllJsdocs: true, + meta: { + docs: { + description: 'Marks all types referenced in {@link} tags as used', + url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-mark-used', + }, + fixable: 'code', + schema: [ + { + additionalProperties: false, + properties: {}, + }, + ], + type: 'suggestion', + }, +}); From e992e3ed4e10a6ec3efd7f2ce97e295a301d073c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 19:11:47 +0200 Subject: [PATCH 02/12] Adding the start of a failing test --- test/rules/assertions/markUsed.js | 16 ++++++++++++++++ test/rules/ruleNames.json | 1 + 2 files changed, 17 insertions(+) create mode 100644 test/rules/assertions/markUsed.js diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/markUsed.js new file mode 100644 index 000000000..0d116eef2 --- /dev/null +++ b/test/rules/assertions/markUsed.js @@ -0,0 +1,16 @@ +export default { + invalid: [], + valid: [ + { + code: ` + const foo = "bar"; + /** This thing uses {@link foo} for something */ + `, + /* + rules: { + 'no-unused-vars': 'error', + }, + */ + }, + ], +}; diff --git a/test/rules/ruleNames.json b/test/rules/ruleNames.json index 7cff81eab..447d9a397 100644 --- a/test/rules/ruleNames.json +++ b/test/rules/ruleNames.json @@ -13,6 +13,7 @@ "empty-tags", "implements-on-classes", "informative-docs", + "mark-used", "match-description", "match-name", "multiline-blocks", From a5a5fdba4583b7850e0c242b090fae7399bfab09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 19:31:52 +0200 Subject: [PATCH 03/12] Updated docs --- .README/rules/mark-used.md | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/.README/rules/mark-used.md b/.README/rules/mark-used.md index 0c10f7671..aca7e541b 100644 --- a/.README/rules/mark-used.md +++ b/.README/rules/mark-used.md @@ -1,40 +1,27 @@ - - -# mark-used - -* [Fixer](#user-content-mark-used-fixer) -* [Failing examples](#user-content-mark-used-failing-examples) -* [Passing examples](#user-content-mark-used-passing-examples) +# `mark-used` +{"gitdown": "contents", "rootId": "mark-used"} Marks all types referenced in `{@link}` tags as used. - - ## Fixer -N/A +Not applicable. - - #### Options -None. +||| +|---|---| +|Context|everywhere| +|Tags|N/A| +|Recommended|false| +|Settings|| +|Options|| - - ## Failing examples -Not applicable, since this only marks identifiers and types as used and never actually fails. + - - ## Passing examples -The following patterns are not considered problems: - -````js -const foo = "bar"; -/** Uses {@link foo} for something */ -```` - + From 0206aa1a7c09a471157c2af721b19ffef7fc6610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 20:40:19 +0200 Subject: [PATCH 04/12] Ran "create-readme" --- README.md | 2 ++ docs/rules/mark-used.md | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 docs/rules/mark-used.md diff --git a/README.md b/README.md index 19ed07970..1537d42a5 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Finally, enable all of the rules that you would like to use. "jsdoc/empty-tags": 1, // Recommended "jsdoc/implements-on-classes": 1, // Recommended "jsdoc/informative-docs": 1, + "jsdoc/mark-used": 1, "jsdoc/match-description": 1, "jsdoc/multiline-blocks": 1, // Recommended "jsdoc/no-bad-blocks": 1, @@ -224,6 +225,7 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restart their attached name.| +|||[mark-used](./docs/rules/mark-used.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| diff --git a/docs/rules/mark-used.md b/docs/rules/mark-used.md new file mode 100644 index 000000000..bb05e4a7a --- /dev/null +++ b/docs/rules/mark-used.md @@ -0,0 +1,66 @@ + + +# mark-used + +* [Fixer](#user-content-mark-used-fixer) +* [Failing examples](#user-content-mark-used-failing-examples) +* [Passing examples](#user-content-mark-used-passing-examples) + + +Marks all types referenced in `{@link}` tags as used. + + + +## Fixer + +Not applicable. + + + +#### Options + +||| +|---|---| +|Context|everywhere| +|Tags|N/A| +|Recommended|false| +|Settings|| +|Options|| + + + +## Failing examples + +The following patterns are considered problems: + +````js + +```` + + + + + +## Passing examples + +The following patterns are not considered problems: + +````js +class Foo {} +/** @param {Foo} */ +function foo() {} +foo(); + +class Foo {} +/** @returns {Foo} */ +function foo() {} +foo(); + +class Foo {} +class Bar {} +class Baz {} +class Qux {} +/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=)} */ +let foo = null; +```` + From aa17ecd4d5168e609cb16bd5a31aeda9286f1c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 20:42:53 +0200 Subject: [PATCH 05/12] Implemented rule for non-inline tags --- src/rules/markUsed.js | 42 +++++++++++++++++++++++++++++- test/rules/assertions/markUsed.js | 43 ++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/rules/markUsed.js b/src/rules/markUsed.js index 878a16d81..6cb661b27 100644 --- a/src/rules/markUsed.js +++ b/src/rules/markUsed.js @@ -1,6 +1,46 @@ import iterateJsdoc from '../iterateJsdoc'; -export default iterateJsdoc(() => {}, { +/** + * Extracts the type names from a type declaration string. + * + * @see https://jsdoc.app/tags-type.html + */ +const extractTypeNames = (type) => { + if (type.startsWith('(') && type.endsWith(')')) { + // Type union + return type.slice(1, type.length - 1).split('|').flatMap(extractTypeNames); + } else if (type.endsWith('[]')) { + // Arrays + return extractTypeNames(type.slice(0, Math.max(0, type.length - 2))); + } else if (type.startsWith('!') || type.startsWith('?')) { + // Nullable type or non-nullable type + return extractTypeNames(type.slice(1)); + } else if (type.startsWith('...')) { + // Variable number of that type + return extractTypeNames(type.slice(3)); + } else if (type.endsWith('=')) { + // Optional parameter + return extractTypeNames(type.slice(0, Math.max(0, type.length - 1))); + } else { + return [ + type, + ]; + } +}; + +export default iterateJsdoc(({ + jsdoc, + jsdocNode, + context, +}) => { + const sourceCode = context.getSourceCode(); + for (const tag of jsdoc.tags) { + const typeNames = extractTypeNames(tag.type); + for (const typeName of typeNames) { + sourceCode.markVariableAsUsed(typeName, jsdocNode); + } + } +}, { iterateAllJsdocs: true, meta: { docs: { diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/markUsed.js index 0d116eef2..1ac7e6cf6 100644 --- a/test/rules/assertions/markUsed.js +++ b/test/rules/assertions/markUsed.js @@ -1,16 +1,51 @@ export default { invalid: [], valid: [ + // { + // code: ` + // const foo = "bar"; + // /** This thing uses {@link foo} for something */ + // `, + // /* + // rules: { + // 'no-unused-vars': 'error', + // }, + // */ + // }, { code: ` - const foo = "bar"; - /** This thing uses {@link foo} for something */ + class Foo {} + /** @param {Foo} */ + function foo() {} + foo(); + `, + rules: { + 'no-unused-vars': 'error', + }, + }, + { + code: ` + class Foo {} + /** @returns {Foo} */ + function foo() {} + foo(); + `, + rules: { + 'no-unused-vars': 'error', + }, + }, + { + code: ` + class Foo {} + class Bar {} + class Baz {} + class Qux {} + /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=)} */ + let foo = null; `, - /* rules: { 'no-unused-vars': 'error', }, - */ }, ], }; From 41260d34e2f6599c0ab9d9e441728ec3691935ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 22:42:37 +0200 Subject: [PATCH 06/12] Moved "jsdoc-type-pratt-parser" up from a dev dependency --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46f9ad585..4f820669c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", "semver": "^7.5.0", "spdx-expression-parse": "^3.0.1" }, @@ -44,7 +45,6 @@ "gitdown": "^3.1.5", "glob": "^10.2.2", "husky": "^8.0.3", - "jsdoc-type-pratt-parser": "^4.0.0", "lint-staged": "^13.2.2", "lodash.defaultsdeep": "^4.6.1", "mocha": "^10.2.0", diff --git a/package.json b/package.json index 530b0b44c..a91e72550 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", "semver": "^7.5.0", "spdx-expression-parse": "^3.0.1" }, @@ -41,7 +42,6 @@ "gitdown": "^3.1.5", "glob": "^10.2.2", "husky": "^8.0.3", - "jsdoc-type-pratt-parser": "^4.0.0", "lint-staged": "^13.2.2", "lodash.defaultsdeep": "^4.6.1", "mocha": "^10.2.0", From 7802a7a1a51429e3ee07ba2be7709d2d14fad058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 23:02:02 +0200 Subject: [PATCH 07/12] Using "jsdoc-type-pratt-parser" --- docs/rules/mark-used.md | 7 ++- src/rules/markUsed.js | 83 +++++++++++++++++++++++-------- test/rules/assertions/markUsed.js | 17 ++++++- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/docs/rules/mark-used.md b/docs/rules/mark-used.md index bb05e4a7a..9d814f982 100644 --- a/docs/rules/mark-used.md +++ b/docs/rules/mark-used.md @@ -60,7 +60,12 @@ class Foo {} class Bar {} class Baz {} class Qux {} -/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=)} */ +/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))} */ let foo = null; + +class Foo {} +/** @type {typeof foo|import("some-package")|new(number, string): Foo|foo is Foo|{foo: Foo}} */ +let foo = null; +// Settings: {"jsdoc":{"mode":"typescript"}} ```` diff --git a/src/rules/markUsed.js b/src/rules/markUsed.js index 6cb661b27..754869626 100644 --- a/src/rules/markUsed.js +++ b/src/rules/markUsed.js @@ -1,30 +1,63 @@ +import { + parse, +} from 'jsdoc-type-pratt-parser'; import iterateJsdoc from '../iterateJsdoc'; /** - * Extracts the type names from a type declaration string. - * - * @see https://jsdoc.app/tags-type.html + * Extracts the type names from parsed type declaration. */ -const extractTypeNames = (type) => { - if (type.startsWith('(') && type.endsWith(')')) { - // Type union - return type.slice(1, type.length - 1).split('|').flatMap(extractTypeNames); - } else if (type.endsWith('[]')) { - // Arrays - return extractTypeNames(type.slice(0, Math.max(0, type.length - 2))); - } else if (type.startsWith('!') || type.startsWith('?')) { - // Nullable type or non-nullable type - return extractTypeNames(type.slice(1)); - } else if (type.startsWith('...')) { - // Variable number of that type - return extractTypeNames(type.slice(3)); - } else if (type.endsWith('=')) { - // Optional parameter - return extractTypeNames(type.slice(0, Math.max(0, type.length - 1))); - } else { +const extractTypeNames = (parsed) => { + if (typeof parsed !== 'object') { + /* istanbul ignore next */ + return []; + } else if (parsed.type === 'JsdocTypeName') { + return [ + parsed.value, + ]; + } else if (parsed.type === 'JsdocTypeGeneric') { + return [ + ...extractTypeNames(parsed.left), + ...parsed.elements.flatMap(extractTypeNames), + ]; + } else if (parsed.type === 'JsdocTypeFunction') { + return [ + ...parsed.parameters.flatMap(extractTypeNames), + ...extractTypeNames(parsed.returnType), + ]; + } else if (parsed.type === 'JsdocTypeNamePath') { + return extractTypeNames(parsed.left); + } else if (parsed.type === 'JsdocTypeImport') { + // We don't want this to fall through to the base-case + return []; + } else if (parsed.type === 'JsdocTypePredicate') { + // We purposefully don't consider the left (subject of the predicate) used + return extractTypeNames(parsed.right); + } else if (parsed.type === 'JsdocTypeObjectField') { return [ - type, + ...extractTypeNames(parsed.key), + ...extractTypeNames(parsed.right), ]; + } else if (parsed.type === 'JsdocTypeJsdocObjectField') { + return [ + ...extractTypeNames(parsed.left), + ...extractTypeNames(parsed.right), + ]; + } else if ( + parsed.type === 'JsdocTypeKeyValue' || + parsed.type === 'JsdocTypeIndexSignature' || + parsed.type === 'JsdocTypeMappedType') { + return extractTypeNames(parsed.right); + } else { + const result = []; + if (parsed.element) { + result.push(...extractTypeNames(parsed.element)); + } + + if (parsed.elements) { + result.push(...parsed.elements.flatMap(extractTypeNames)); + } + + return result; } }; @@ -32,10 +65,16 @@ export default iterateJsdoc(({ jsdoc, jsdocNode, context, + settings, }) => { + const { + mode, + } = settings; + const sourceCode = context.getSourceCode(); for (const tag of jsdoc.tags) { - const typeNames = extractTypeNames(tag.type); + const parsedType = parse(tag.type, mode); + const typeNames = extractTypeNames(parsedType); for (const typeName of typeNames) { sourceCode.markVariableAsUsed(typeName, jsdocNode); } diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/markUsed.js index 1ac7e6cf6..fd86e2ab6 100644 --- a/test/rules/assertions/markUsed.js +++ b/test/rules/assertions/markUsed.js @@ -40,12 +40,27 @@ export default { class Bar {} class Baz {} class Qux {} - /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=)} */ + /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))} */ let foo = null; `, rules: { 'no-unused-vars': 'error', }, }, + { + code: ` + class Foo {} + /** @type {typeof foo|import("some-package")|new(number, string): Foo|foo is Foo|{foo: Foo}} */ + let foo = null; + `, + rules: { + 'no-unused-vars': 'error', + }, + settings: { + jsdoc: { + mode: 'typescript', + }, + }, + }, ], }; From 45be8f49e80fe3cb942a5db379ac12e6ecb4e53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 23:22:00 +0200 Subject: [PATCH 08/12] Added explicit case for "JsdocTypeSpecialNamePath" --- docs/rules/mark-used.md | 2 +- src/rules/markUsed.js | 7 ++----- test/rules/assertions/markUsed.js | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/rules/mark-used.md b/docs/rules/mark-used.md index 9d814f982..2a9861fa6 100644 --- a/docs/rules/mark-used.md +++ b/docs/rules/mark-used.md @@ -60,7 +60,7 @@ class Foo {} class Bar {} class Baz {} class Qux {} -/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))} */ +/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))|external:something} */ let foo = null; class Foo {} diff --git a/src/rules/markUsed.js b/src/rules/markUsed.js index 754869626..af9541bb5 100644 --- a/src/rules/markUsed.js +++ b/src/rules/markUsed.js @@ -7,8 +7,8 @@ import iterateJsdoc from '../iterateJsdoc'; * Extracts the type names from parsed type declaration. */ const extractTypeNames = (parsed) => { - if (typeof parsed !== 'object') { - /* istanbul ignore next */ + if (typeof parsed !== 'object' || parsed.type === 'JsdocTypeImport' || parsed.type === 'JsdocTypeSpecialNamePath') { + // We don't want this to fall through to the base-case return []; } else if (parsed.type === 'JsdocTypeName') { return [ @@ -26,9 +26,6 @@ const extractTypeNames = (parsed) => { ]; } else if (parsed.type === 'JsdocTypeNamePath') { return extractTypeNames(parsed.left); - } else if (parsed.type === 'JsdocTypeImport') { - // We don't want this to fall through to the base-case - return []; } else if (parsed.type === 'JsdocTypePredicate') { // We purposefully don't consider the left (subject of the predicate) used return extractTypeNames(parsed.right); diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/markUsed.js index fd86e2ab6..b1e7f46d5 100644 --- a/test/rules/assertions/markUsed.js +++ b/test/rules/assertions/markUsed.js @@ -40,7 +40,7 @@ export default { class Bar {} class Baz {} class Qux {} - /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))} */ + /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))|external:something} */ let foo = null; `, rules: { From 8a9f2b6358ac4d7242ed2d9a305aac2d0460e635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 23:50:18 +0200 Subject: [PATCH 09/12] Switched to a switch-case --- src/rules/markUsed.js | 55 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/rules/markUsed.js b/src/rules/markUsed.js index af9541bb5..2c32896c7 100644 --- a/src/rules/markUsed.js +++ b/src/rules/markUsed.js @@ -6,55 +6,60 @@ import iterateJsdoc from '../iterateJsdoc'; /** * Extracts the type names from parsed type declaration. */ -const extractTypeNames = (parsed) => { - if (typeof parsed !== 'object' || parsed.type === 'JsdocTypeImport' || parsed.type === 'JsdocTypeSpecialNamePath') { - // We don't want this to fall through to the base-case +const extractTypeNames = (parsed) => { // eslint-disable-line complexity + if (typeof parsed !== 'object') { return []; - } else if (parsed.type === 'JsdocTypeName') { + } + + switch (parsed.type) { + case 'JsdocTypeName': return [ parsed.value, ]; - } else if (parsed.type === 'JsdocTypeGeneric') { + case 'JsdocTypeOptional': + case 'JsdocTypeNullable': + case 'JsdocTypeNotNullable': + case 'JsdocTypeTypeof': + case 'JsdocTypeKeyof': + case 'JsdocTypeParenthesis': + case 'JsdocTypeVariadic': + return extractTypeNames(parsed.element); + case 'JsdocTypeUnion': + case 'JsdocTypeObject': + case 'JsdocTypeTuple': + case 'JsdocTypeIntersection': + return parsed.elements.flatMap(extractTypeNames); + case 'JsdocTypeGeneric': return [ ...extractTypeNames(parsed.left), ...parsed.elements.flatMap(extractTypeNames), ]; - } else if (parsed.type === 'JsdocTypeFunction') { + case 'JsdocTypeFunction': return [ ...parsed.parameters.flatMap(extractTypeNames), ...extractTypeNames(parsed.returnType), ]; - } else if (parsed.type === 'JsdocTypeNamePath') { + case 'JsdocTypeNamePath': return extractTypeNames(parsed.left); - } else if (parsed.type === 'JsdocTypePredicate') { + case 'JsdocTypePredicate': // We purposefully don't consider the left (subject of the predicate) used return extractTypeNames(parsed.right); - } else if (parsed.type === 'JsdocTypeObjectField') { + case 'JsdocTypeObjectField': return [ ...extractTypeNames(parsed.key), ...extractTypeNames(parsed.right), ]; - } else if (parsed.type === 'JsdocTypeJsdocObjectField') { + case 'JsdocTypeJsdocObjectField': return [ ...extractTypeNames(parsed.left), ...extractTypeNames(parsed.right), ]; - } else if ( - parsed.type === 'JsdocTypeKeyValue' || - parsed.type === 'JsdocTypeIndexSignature' || - parsed.type === 'JsdocTypeMappedType') { + case 'JsdocTypeKeyValue': + case 'JsdocTypeIndexSignature': + case 'JsdocTypeMappedType': return extractTypeNames(parsed.right); - } else { - const result = []; - if (parsed.element) { - result.push(...extractTypeNames(parsed.element)); - } - - if (parsed.elements) { - result.push(...parsed.elements.flatMap(extractTypeNames)); - } - - return result; + default: + return []; } }; From 4db539e4f8a1d60b65c82868e0c32dc599f1097a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 2 May 2023 23:52:37 +0200 Subject: [PATCH 10/12] Using ignoreReadme on the bloated tests --- docs/rules/mark-used.md | 12 ------------ test/rules/assertions/markUsed.js | 2 ++ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/rules/mark-used.md b/docs/rules/mark-used.md index 2a9861fa6..81710e52d 100644 --- a/docs/rules/mark-used.md +++ b/docs/rules/mark-used.md @@ -55,17 +55,5 @@ class Foo {} /** @returns {Foo} */ function foo() {} foo(); - -class Foo {} -class Bar {} -class Baz {} -class Qux {} -/** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))|external:something} */ -let foo = null; - -class Foo {} -/** @type {typeof foo|import("some-package")|new(number, string): Foo|foo is Foo|{foo: Foo}} */ -let foo = null; -// Settings: {"jsdoc":{"mode":"typescript"}} ```` diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/markUsed.js index b1e7f46d5..23b0ceb4b 100644 --- a/test/rules/assertions/markUsed.js +++ b/test/rules/assertions/markUsed.js @@ -43,6 +43,7 @@ export default { /** @type {(!Foo|?Bar|...Baz|Qux[]|foo=|obj["level1"]|{Foo?: Foo}|function(this:Foo))|external:something} */ let foo = null; `, + ignoreReadme: true, rules: { 'no-unused-vars': 'error', }, @@ -53,6 +54,7 @@ export default { /** @type {typeof foo|import("some-package")|new(number, string): Foo|foo is Foo|{foo: Foo}} */ let foo = null; `, + ignoreReadme: true, rules: { 'no-unused-vars': 'error', }, From aaa9ce5afb0d052924746e489cf642f6f114811e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 3 May 2023 09:40:16 +0200 Subject: [PATCH 11/12] Renamed rule to "used-types" --- .README/README.md | 4 +- .README/rules/{mark-used.md => used-types.md} | 8 +-- README.md | 4 +- docs/rules/mark-used.md | 59 ------------------- docs/rules/used-types.md | 59 +++++++++++++++++++ src/index.js | 4 +- src/rules/{markUsed.js => usedTypes.js} | 2 +- .../assertions/{markUsed.js => usedTypes.js} | 0 test/rules/ruleNames.json | 2 +- 9 files changed, 71 insertions(+), 71 deletions(-) rename .README/rules/{mark-used.md => used-types.md} (62%) delete mode 100644 docs/rules/mark-used.md create mode 100644 docs/rules/used-types.md rename src/rules/{markUsed.js => usedTypes.js} (98%) rename test/rules/assertions/{markUsed.js => usedTypes.js} (100%) diff --git a/.README/README.md b/.README/README.md index f0dceb26d..8bd81fb40 100644 --- a/.README/README.md +++ b/.README/README.md @@ -57,7 +57,7 @@ Finally, enable all of the rules that you would like to use. "jsdoc/empty-tags": 1, // Recommended "jsdoc/implements-on-classes": 1, // Recommended "jsdoc/informative-docs": 1, - "jsdoc/mark-used": 1, + "jsdoc/used-types": 1, "jsdoc/match-description": 1, "jsdoc/multiline-blocks": 1, // Recommended "jsdoc/no-bad-blocks": 1, @@ -204,7 +204,6 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restart their attached name.| -|||[mark-used](./docs/rules/mark-used.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| @@ -242,4 +241,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups| |:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags| ||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions| +|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)| diff --git a/.README/rules/mark-used.md b/.README/rules/used-types.md similarity index 62% rename from .README/rules/mark-used.md rename to .README/rules/used-types.md index aca7e541b..40021ea87 100644 --- a/.README/rules/mark-used.md +++ b/.README/rules/used-types.md @@ -1,6 +1,6 @@ -# `mark-used` +# `used-types` -{"gitdown": "contents", "rootId": "mark-used"} +{"gitdown": "contents", "rootId": "used-types"} Marks all types referenced in `{@link}` tags as used. @@ -20,8 +20,8 @@ Not applicable. ## Failing examples - + ## Passing examples - + diff --git a/README.md b/README.md index 1537d42a5..22ffb6e36 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Finally, enable all of the rules that you would like to use. "jsdoc/empty-tags": 1, // Recommended "jsdoc/implements-on-classes": 1, // Recommended "jsdoc/informative-docs": 1, - "jsdoc/mark-used": 1, + "jsdoc/used-types": 1, "jsdoc/match-description": 1, "jsdoc/multiline-blocks": 1, // Recommended "jsdoc/no-bad-blocks": 1, @@ -225,7 +225,6 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |:heavy_check_mark:|:wrench:|[empty-tags](./docs/rules/empty-tags.md#readme)|Checks tags that are expected to be empty (e.g., `@abstract` or `@async`), reporting if they have content| |:heavy_check_mark:||[implements-on-classes](./docs/rules/implements-on-classes.md#readme)|Prohibits use of `@implements` on non-constructor functions (to enforce the tag only being used on classes/constructors)| |||[informative-docs](./docs/rules/informative-docs.md#readme)|Reports on JSDoc texts that serve only to restart their attached name.| -|||[mark-used](./docs/rules/mark-used.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |||[match-description](./docs/rules/match-description.md#readme)|Defines customizable regular expression rules for your tag descriptions| ||:wrench:|[match-name](./docs/rules/match-name.md#readme)|Reports the name portion of a JSDoc tag if matching or not matching a given regular expression| |:heavy_check_mark:|:wrench:|[multiline-blocks](./docs/rules/multiline-blocks.md#readme)|Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks| @@ -263,4 +262,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups| |:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags| ||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions| +|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced in `{@link}`d tags as used.| |:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)| diff --git a/docs/rules/mark-used.md b/docs/rules/mark-used.md deleted file mode 100644 index 81710e52d..000000000 --- a/docs/rules/mark-used.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# mark-used - -* [Fixer](#user-content-mark-used-fixer) -* [Failing examples](#user-content-mark-used-failing-examples) -* [Passing examples](#user-content-mark-used-passing-examples) - - -Marks all types referenced in `{@link}` tags as used. - - - -## Fixer - -Not applicable. - - - -#### Options - -||| -|---|---| -|Context|everywhere| -|Tags|N/A| -|Recommended|false| -|Settings|| -|Options|| - - - -## Failing examples - -The following patterns are considered problems: - -````js - -```` - - - - - -## Passing examples - -The following patterns are not considered problems: - -````js -class Foo {} -/** @param {Foo} */ -function foo() {} -foo(); - -class Foo {} -/** @returns {Foo} */ -function foo() {} -foo(); -```` - diff --git a/docs/rules/used-types.md b/docs/rules/used-types.md new file mode 100644 index 000000000..56d489b40 --- /dev/null +++ b/docs/rules/used-types.md @@ -0,0 +1,59 @@ + + +# used-types + +* [Fixer](#user-content-used-types-fixer) +* [Failing examples](#user-content-used-types-failing-examples) +* [Passing examples](#user-content-used-types-passing-examples) + + +Marks all types referenced in `{@link}` tags as used. + + + +## Fixer + +Not applicable. + + + +#### Options + +||| +|---|---| +|Context|everywhere| +|Tags|N/A| +|Recommended|false| +|Settings|| +|Options|| + + + +## Failing examples + +The following patterns are considered problems: + +````js + +```` + + + + + +## Passing examples + +The following patterns are not considered problems: + +````js +class Foo {} +/** @param {Foo} */ +function foo() {} +foo(); + +class Foo {} +/** @returns {Foo} */ +function foo() {} +foo(); +```` + diff --git a/src/index.js b/src/index.js index ef110adf2..8b5c54694 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,6 @@ import checkValues from './rules/checkValues'; import emptyTags from './rules/emptyTags'; import implementsOnClasses from './rules/implementsOnClasses'; import informativeDocs from './rules/informativeDocs'; -import markUsed from './rules/markUsed'; import matchDescription from './rules/matchDescription'; import matchName from './rules/matchName'; import multilineBlocks from './rules/multilineBlocks'; @@ -50,6 +49,7 @@ import requireYieldsCheck from './rules/requireYieldsCheck'; import sortTags from './rules/sortTags'; import tagLines from './rules/tagLines'; import textEscaping from './rules/textEscaping'; +import usedTypes from './rules/usedTypes'; import validTypes from './rules/validTypes'; const index = { @@ -69,7 +69,6 @@ const index = { 'empty-tags': emptyTags, 'implements-on-classes': implementsOnClasses, 'informative-docs': informativeDocs, - 'mark-used': markUsed, 'match-description': matchDescription, 'match-name': matchName, 'multiline-blocks': multilineBlocks, @@ -107,6 +106,7 @@ const index = { 'sort-tags': sortTags, 'tag-lines': tagLines, 'text-escaping': textEscaping, + 'used-types': usedTypes, 'valid-types': validTypes, }, }; diff --git a/src/rules/markUsed.js b/src/rules/usedTypes.js similarity index 98% rename from src/rules/markUsed.js rename to src/rules/usedTypes.js index 2c32896c7..fe0dbad2c 100644 --- a/src/rules/markUsed.js +++ b/src/rules/usedTypes.js @@ -86,7 +86,7 @@ export default iterateJsdoc(({ meta: { docs: { description: 'Marks all types referenced in {@link} tags as used', - url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-mark-used', + url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-used-types', }, fixable: 'code', schema: [ diff --git a/test/rules/assertions/markUsed.js b/test/rules/assertions/usedTypes.js similarity index 100% rename from test/rules/assertions/markUsed.js rename to test/rules/assertions/usedTypes.js diff --git a/test/rules/ruleNames.json b/test/rules/ruleNames.json index 447d9a397..6ce1a4aef 100644 --- a/test/rules/ruleNames.json +++ b/test/rules/ruleNames.json @@ -13,7 +13,7 @@ "empty-tags", "implements-on-classes", "informative-docs", - "mark-used", + "used-types", "match-description", "match-name", "multiline-blocks", From 8eda77446eafd60cc16d1a0c3ccde94125490454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 3 May 2023 09:41:58 +0200 Subject: [PATCH 12/12] Updated rule description --- .README/README.md | 2 +- .README/rules/used-types.md | 2 +- README.md | 2 +- docs/rules/used-types.md | 2 +- src/rules/usedTypes.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.README/README.md b/.README/README.md index 8bd81fb40..2c94aaf34 100644 --- a/.README/README.md +++ b/.README/README.md @@ -241,5 +241,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups| |:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags| ||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions| -|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced in `{@link}`d tags as used.| +|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced from JSDoc tags as used.| |:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)| diff --git a/.README/rules/used-types.md b/.README/rules/used-types.md index 40021ea87..2b148751b 100644 --- a/.README/rules/used-types.md +++ b/.README/rules/used-types.md @@ -2,7 +2,7 @@ {"gitdown": "contents", "rootId": "used-types"} -Marks all types referenced in `{@link}` tags as used. +Marks all types referenced from JSDoc tags as used. ## Fixer diff --git a/README.md b/README.md index 22ffb6e36..1ee5dd5ec 100644 --- a/README.md +++ b/README.md @@ -262,5 +262,5 @@ Problems reported by rules which have a wrench :wrench: below can be fixed autom |||[sort-tags](./docs/rules/sort-tags.md#readme)|Sorts tags by a specified sequence according to tag name, optionally adding line breaks between tag groups| |:heavy_check_mark:|:wrench:|[tag-lines](./docs/rules/tag-lines.md#readme)|Enforces lines (or no lines) between tags| ||:wrench:|[text-escaping](./docs/rules/text-escaping.md#readme)|This rule can auto-escape certain characters that are input within block and tag descriptions| -|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced in `{@link}`d tags as used.| +|||[used-types](./docs/rules/used-types.md#readme)|Marks all types referenced from JSDoc tags as used.| |:heavy_check_mark:||[valid-types](./docs/rules/valid-types.md#readme)|Requires all types/namepaths to be valid JSDoc, Closure compiler, or TypeScript types (configurable in settings)| diff --git a/docs/rules/used-types.md b/docs/rules/used-types.md index 56d489b40..e150e15a3 100644 --- a/docs/rules/used-types.md +++ b/docs/rules/used-types.md @@ -7,7 +7,7 @@ * [Passing examples](#user-content-used-types-passing-examples) -Marks all types referenced in `{@link}` tags as used. +Marks all types referenced from JSDoc tags as used. diff --git a/src/rules/usedTypes.js b/src/rules/usedTypes.js index fe0dbad2c..42d60c664 100644 --- a/src/rules/usedTypes.js +++ b/src/rules/usedTypes.js @@ -85,7 +85,7 @@ export default iterateJsdoc(({ iterateAllJsdocs: true, meta: { docs: { - description: 'Marks all types referenced in {@link} tags as used', + description: 'Marks all types referenced from JSDoc tags as used.', url: 'https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-used-types', }, fixable: 'code',