From 4a0415a89a805e949faede748b37bd4bb3b3b3e5 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 22 Sep 2025 09:30:47 +0800 Subject: [PATCH 1/2] feat: add `flat/recommended-mixed` config; fixes #1101 --- src/index-cjs.js | 26 ++++++++++++++++++++++++-- src/index.js | 26 ++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/index-cjs.js b/src/index-cjs.js index 5355e9e9f..0c21b8b85 100644 --- a/src/index-cjs.js +++ b/src/index-cjs.js @@ -74,8 +74,15 @@ import validTypes from './rules/validTypes.js'; * @typedef {"" | "-typescript" | "-typescript-flavor"} ConfigVariants * @typedef {"" | "-error"} ErrorLevelVariants * @type {import('eslint').ESLint.Plugin & { - * configs: Record<`flat/${ConfigGroups}${ConfigVariants}${ErrorLevelVariants}`, - * import('eslint').Linter.Config> & Record<"examples"|"default-expressions"|"examples-and-default-expressions", import('eslint').Linter.Config[]> + * configs: Record< + * `flat/${ConfigGroups}${ConfigVariants}${ErrorLevelVariants}`, + * import('eslint').Linter.Config + * > & + * Record< + * "examples"|"default-expressions"|"examples-and-default-expressions", + * import('eslint').Linter.Config[] + * > & + * Record<"flat/recommended-mixed", import('eslint').Linter.Config[]> * }} */ const index = {}; @@ -666,4 +673,19 @@ index.configs['examples-and-default-expressions'] = /** @type {import('eslint'). }), ]); +index.configs['flat/recommended-mixed'] = [ + { + ...index.configs['flat/recommended-typescript-flavor'], + files: [ + '**/*.{js,jsx,cjs,mjs}', + ], + }, + { + ...index.configs['flat/recommended-typescript'], + files: [ + '**/*.{ts,tsx,cts,mts}', + ], + }, +]; + export default index; diff --git a/src/index.js b/src/index.js index 777dd84b9..b2548819a 100644 --- a/src/index.js +++ b/src/index.js @@ -80,8 +80,15 @@ import validTypes from './rules/validTypes.js'; * @typedef {"" | "-typescript" | "-typescript-flavor"} ConfigVariants * @typedef {"" | "-error"} ErrorLevelVariants * @type {import('eslint').ESLint.Plugin & { - * configs: Record<`flat/${ConfigGroups}${ConfigVariants}${ErrorLevelVariants}`, - * import('eslint').Linter.Config> & Record<"examples"|"default-expressions"|"examples-and-default-expressions", import('eslint').Linter.Config[]> + * configs: Record< + * `flat/${ConfigGroups}${ConfigVariants}${ErrorLevelVariants}`, + * import('eslint').Linter.Config + * > & + * Record< + * "examples"|"default-expressions"|"examples-and-default-expressions", + * import('eslint').Linter.Config[] + * > & + * Record<"flat/recommended-mixed", import('eslint').Linter.Config[]> * }} */ const index = {}; @@ -672,6 +679,21 @@ index.configs['examples-and-default-expressions'] = /** @type {import('eslint'). }), ]); +index.configs['flat/recommended-mixed'] = [ + { + ...index.configs['flat/recommended-typescript-flavor'], + files: [ + '**/*.{js,jsx,cjs,mjs}', + ], + }, + { + ...index.configs['flat/recommended-typescript'], + files: [ + '**/*.{ts,tsx,cts,mts}', + ], + }, +]; + export default index; /* eslint-disable jsdoc/valid-types -- Bug */ From fced9213075876aa4e3d9b0aced8649903de587e Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Fri, 26 Sep 2025 09:31:42 +0800 Subject: [PATCH 2/2] fix: ensure permissive settings are converted for jsdoc-type-pratt-parser Also: - chore: update jsdoccomment and devDeps. --- docs/rules/prefer-import-tag.md | 26 ++++++ package.json | 6 +- pnpm-lock.yaml | 32 +++---- src/index-cjs.js | 2 - src/index.js | 2 - src/iterateJsdoc.js | 10 ++- src/rules/preferImportTag.js | 19 ++++- test/rules/assertions/noRestrictedSyntax.js | 55 ++++++++++++ test/rules/assertions/preferImportTag.js | 93 +++++++++++++++++++++ 9 files changed, 217 insertions(+), 28 deletions(-) diff --git a/docs/rules/prefer-import-tag.md b/docs/rules/prefer-import-tag.md index 0caf44589..40fb8800b 100644 --- a/docs/rules/prefer-import-tag.md +++ b/docs/rules/prefer-import-tag.md @@ -266,6 +266,32 @@ The following patterns are considered problems: */ // "jsdoc/prefer-import-tag": ["error"|"warn", {"exemptTypedefs":true}] // Message: Inline `import()` found; prefer `@import` + +/** + * @type {import('eslint').anchors[keyof DataMap.anchors]} + */ +// Message: Inline `import()` found; prefer `@import` + +/** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ +/** + * @type {import('eslint').Abc.Rule} + */ +// "jsdoc/prefer-import-tag": ["error"|"warn", {"exemptTypedefs":true}] +// Message: Inline `import()` found; prefer `@import` + +/** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ +/** + * @type {import('eslint').Rule[keyof import('eslint').Rule]} + */ +// "jsdoc/prefer-import-tag": ["error"|"warn", {"exemptTypedefs":true}] +// Message: Inline `import()` found; using `@typedef` + +/** @typedef {import('eslint').Rule} Rule */ +/** + * @type {import('eslint').Rule[keyof import('eslint').Rule]} + */ +// "jsdoc/prefer-import-tag": ["error"|"warn", {"exemptTypedefs":true}] +// Message: Inline `import()` found; using `@typedef` ```` diff --git a/package.json b/package.json index 1d697160a..1abf0b5de 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "url": "http://gajus.com" }, "dependencies": { - "@es-joy/jsdoccomment": "~0.60.0", + "@es-joy/jsdoccomment": "~0.61.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.4.3", @@ -58,10 +58,10 @@ "glob": "^11.0.3", "globals": "^16.4.0", "husky": "^9.1.7", - "jsdoc-type-pratt-parser": "^5.4.0", + "jsdoc-type-pratt-parser": "^5.8.0", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", - "lint-staged": "^16.2.0", + "lint-staged": "^16.2.1", "mocha": "^11.7.2", "open-editor": "^5.1.0", "replace": "^1.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9c385c4b..85b591f8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: .: dependencies: '@es-joy/jsdoccomment': - specifier: ~0.60.0 - version: 0.60.0 + specifier: ~0.61.0 + version: 0.61.0 are-docs-informative: specifier: ^0.0.2 version: 0.0.2 @@ -163,8 +163,8 @@ importers: specifier: ^9.1.7 version: 9.1.7 jsdoc-type-pratt-parser: - specifier: ^5.4.0 - version: 5.4.0 + specifier: ^5.8.0 + version: 5.8.0 json-schema: specifier: ^0.4.0 version: 0.4.0 @@ -172,8 +172,8 @@ importers: specifier: ^15.0.4 version: 15.0.4 lint-staged: - specifier: ^16.2.0 - version: 16.2.0 + specifier: ^16.2.1 + version: 16.2.1 mocha: specifier: ^11.7.2 version: 11.7.2 @@ -802,8 +802,8 @@ packages: resolution: {integrity: sha512-c6EW+aA1w2rjqOMjbL93nZlwxp6c1Ln06vTYs5FjRRhmJXK8V/OrSXdT+pUr4aRYgjCgu8/OkiZr0tzeVrRSbw==} engines: {node: '>=20.11.0'} - '@es-joy/jsdoccomment@0.60.0': - resolution: {integrity: sha512-nZIXk63VbpIooJVXRWEhLIbVScE8rtbcPWr+zQ0ZQsnflvomq31DvB5hR0T1IoikvrNaF4pNoDOi5se5tmIZIg==} + '@es-joy/jsdoccomment@0.61.0': + resolution: {integrity: sha512-8DBk2LXau86fQBj7f9zx3MKqoAYgomxeoLgvHFa+OMhGYz6L9n/jbqa52wLHnbU6JzcSXu2OGuuTFOOMD4NpRg==} engines: {node: '>=20.11.0'} '@eslint-community/eslint-utils@4.8.0': @@ -3392,8 +3392,8 @@ packages: resolution: {integrity: sha512-DYYlVP1fe4QBMh2xTIs20/YeTz2GYVbWAEZweHSZD+qQ/Cx2d5RShuhhsdk64eTjNq0FeVnteP/qVOgaywSRbg==} engines: {node: '>=12.0.0'} - jsdoc-type-pratt-parser@5.4.0: - resolution: {integrity: sha512-F9GQ+F1ZU6qvSrZV8fNFpjDNf614YzR2eF6S0+XbDjAcUI28FSoXnYZFjQmb1kFx3rrJb5PnxUH3/Yti6fcM+g==} + jsdoc-type-pratt-parser@5.8.0: + resolution: {integrity: sha512-YLmlPdkn1G34K/8NgSFL3D1D/HqQ9WgQOW816Q+6uMLvAO5QohdmG4qkuiseqnRXVAAN9RYtbCKyMSfwcU8wRw==} engines: {node: '>=12.0.0'} jsdom@6.5.1: @@ -3480,8 +3480,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@16.2.0: - resolution: {integrity: sha512-spdYSOCQ2MdZ9CM1/bu/kDmaYGsrpNOeu1InFFV8uhv14x6YIubGxbCpSmGILFoxkiheNQPDXSg5Sbb5ZuVnug==} + lint-staged@16.2.1: + resolution: {integrity: sha512-KMeYmH9wKvHsXdUp+z6w7HN3fHKHXwT1pSTQTYxB9kI6ekK1rlL3kLZEoXZCppRPXFK9PFW/wfQctV7XUqMrPQ==} engines: {node: '>=20.17'} hasBin: true @@ -5941,13 +5941,13 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 5.1.1 - '@es-joy/jsdoccomment@0.60.0': + '@es-joy/jsdoccomment@0.61.0': dependencies: '@types/estree': 1.0.8 '@typescript-eslint/types': 8.44.1 comment-parser: 1.4.1 esquery: 1.6.0 - jsdoc-type-pratt-parser: 5.4.0 + jsdoc-type-pratt-parser: 5.8.0 '@eslint-community/eslint-utils@4.8.0(eslint@9.36.0(jiti@2.5.1))': dependencies: @@ -8990,7 +8990,7 @@ snapshots: jsdoc-type-pratt-parser@5.1.1: {} - jsdoc-type-pratt-parser@5.4.0: {} + jsdoc-type-pratt-parser@5.8.0: {} jsdom@6.5.1: dependencies: @@ -9096,7 +9096,7 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@16.2.0: + lint-staged@16.2.1: dependencies: commander: 14.0.1 listr2: 9.0.4 diff --git a/src/index-cjs.js b/src/index-cjs.js index 0c21b8b85..d4c0df31a 100644 --- a/src/index-cjs.js +++ b/src/index-cjs.js @@ -68,7 +68,6 @@ import textEscaping from './rules/textEscaping.js'; import typeFormatting from './rules/typeFormatting.js'; import validTypes from './rules/validTypes.js'; -/* eslint-disable jsdoc/valid-types -- Bug */ /** * @typedef {"recommended" | "stylistic" | "contents" | "logical" | "requirements"} ConfigGroups * @typedef {"" | "-typescript" | "-typescript-flavor"} ConfigVariants @@ -86,7 +85,6 @@ import validTypes from './rules/validTypes.js'; * }} */ const index = {}; -/* eslint-enable jsdoc/valid-types -- Bug */ index.configs = {}; index.rules = { 'check-access': checkAccess, diff --git a/src/index.js b/src/index.js index b2548819a..d4dd39b8b 100644 --- a/src/index.js +++ b/src/index.js @@ -74,7 +74,6 @@ import textEscaping from './rules/textEscaping.js'; import typeFormatting from './rules/typeFormatting.js'; import validTypes from './rules/validTypes.js'; -/* eslint-disable jsdoc/valid-types -- Bug */ /** * @typedef {"recommended" | "stylistic" | "contents" | "logical" | "requirements"} ConfigGroups * @typedef {"" | "-typescript" | "-typescript-flavor"} ConfigVariants @@ -92,7 +91,6 @@ import validTypes from './rules/validTypes.js'; * }} */ const index = {}; -/* eslint-enable jsdoc/valid-types -- Bug */ index.configs = {}; index.rules = { 'check-access': checkAccess, diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index b4c116ad7..00e8dc748 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -2284,7 +2284,10 @@ const iterateAllJsdocs = (iterator, ruleConfig, contexts, additiveCommentContext } if (contexts) { - handler = commentHandler(settings); + handler = commentHandler({ + ...settings, + mode: settings.mode === 'permissive' ? 'typescript' : settings.mode, + }); } const state = {}; @@ -2537,7 +2540,10 @@ export default function iterateJsdoc (iterator, ruleConfig) { contextObject = jsdocUtils.getContextObject( contexts, checkJsdoc, - commentHandler(settings), + commentHandler({ + ...settings, + mode: settings.mode === 'permissive' ? 'typescript' : settings.mode, + }), ); } else { for (const prop of [ diff --git a/src/rules/preferImportTag.js b/src/rules/preferImportTag.js index 2f21b1a63..9e4e4ccdb 100644 --- a/src/rules/preferImportTag.js +++ b/src/rules/preferImportTag.js @@ -117,10 +117,18 @@ export default iterateJsdoc(({ break; } - pathSegments.unshift(currentNode.right.value); + pathSegments.unshift( + currentNode.right.type === 'JsdocTypeIndexedAccessIndex' ? + stringify(currentNode.right.right) : + currentNode.right.value, + ); nodes.unshift(currentNode); propertyOrBrackets.unshift(currentNode.pathType); - quotes.unshift(currentNode.right.meta.quote); + quotes.unshift( + currentNode.right.type === 'JsdocTypeIndexedAccessIndex' ? + undefined : + currentNode.right.meta.quote, + ); } /** @@ -218,7 +226,12 @@ export default iterateJsdoc(({ break; } - if (typedefNode.right.value !== pathSegment) { + if ( + (typedefNode.right.type === 'JsdocTypeIndexedAccessIndex' && + stringify(typedefNode.right.right) !== pathSegment) || + (typedefNode.right.type !== 'JsdocTypeIndexedAccessIndex' && + typedefNode.right.value !== pathSegment) + ) { if (namepathMatch === true) { // It stopped matching, so stop break; diff --git a/test/rules/assertions/noRestrictedSyntax.js b/test/rules/assertions/noRestrictedSyntax.js index a8a0e6573..8ec41ce73 100644 --- a/test/rules/assertions/noRestrictedSyntax.js +++ b/test/rules/assertions/noRestrictedSyntax.js @@ -35,6 +35,35 @@ export default /** @type {import('../index.js').TestCases} */ ({ */ function quux () { + } + `, + errors: [ + { + line: 2, + message: 'Syntax is restricted: FunctionDeclaration', + }, + ], + ignoreReadme: true, + options: [ + { + contexts: [ + 'FunctionDeclaration', + ], + }, + ], + settings: { + jsdoc: { + mode: 'permissive', + }, + }, + }, + { + code: ` + /** + * + */ + function quux () { + } `, errors: [ @@ -780,6 +809,32 @@ export default /** @type {import('../index.js').TestCases} */ ({ }, ], }, + { + code: ` + /** + * @param ab + * @param cd + */ + function a () {} + `, + ignoreReadme: true, + options: [ + { + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }, + ], + settings: { + jsdoc: { + mode: 'permissive', + }, + }, + }, { code: ` /** diff --git a/test/rules/assertions/preferImportTag.js b/test/rules/assertions/preferImportTag.js index 8268daea1..56acb5ccc 100644 --- a/test/rules/assertions/preferImportTag.js +++ b/test/rules/assertions/preferImportTag.js @@ -726,6 +726,99 @@ export default { */ `, }, + { + code: ` + /** + * @type {import('eslint').anchors[keyof DataMap.anchors]} + */ + `, + errors: [ + { + line: 3, + message: 'Inline `import()` found; prefer `@import`', + }, + ], + output: `/** @import * as eslint from 'eslint'; */ + /** + * @type {eslint.anchors[keyof DataMap.anchors]} + */ + `, + }, + { + code: ` + /** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ + /** + * @type {import('eslint').Abc.Rule} + */ + `, + errors: [ + { + line: 4, + message: 'Inline `import()` found; prefer `@import`', + }, + ], + options: [ + { + exemptTypedefs: true, + }, + ], + output: `/** @import * as eslint from 'eslint'; */ + /** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ + /** + * @type {eslint.Abc.Rule} + */ + `, + }, + { + code: ` + /** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ + /** + * @type {import('eslint').Rule[keyof import('eslint').Rule]} + */ + `, + errors: [ + { + line: 4, + message: 'Inline `import()` found; using `@typedef`', + }, + ], + options: [ + { + exemptTypedefs: true, + }, + ], + output: ` + /** @typedef {import('eslint').Rule[keyof import('eslint').Rule]} Rule */ + /** + * @type {Rule} + */ + `, + }, + { + code: ` + /** @typedef {import('eslint').Rule} Rule */ + /** + * @type {import('eslint').Rule[keyof import('eslint').Rule]} + */ + `, + errors: [ + { + line: 4, + message: 'Inline `import()` found; using `@typedef`', + }, + ], + options: [ + { + exemptTypedefs: true, + }, + ], + output: ` + /** @typedef {import('eslint').Rule} Rule */ + /** + * @type {Rule[keyof import('eslint').Rule]} + */ + `, + }, ], valid: [ {