diff --git a/.README/rules/no-undefined-types.md b/.README/rules/no-undefined-types.md index dccc95430..7998671e6 100644 --- a/.README/rules/no-undefined-types.md +++ b/.README/rules/no-undefined-types.md @@ -45,12 +45,38 @@ array's items will be considered as defined for the purposes of that tag. #### Options -An option object may have the following key: +An option object may have the following keys, helping indicate types or +file sources of types: - `definedTypes` - This array can be populated to indicate other types which are automatically considered as defined (in addition to globals, etc.). Defaults to an empty array. +- `entryFiles` - Array of entry files objects indicating JavaScript or HTML + files whose `import` or `require` statements should be resolved recursively + and be analyzed for `@typedef`'s, globals, etc. (see `typeSources`) to treat + as "defined" for the purposes of this rule. Each object should have a + `file` array and with an optional `node` boolean property to indicate whether + to use the Node Resolution Algorithm (e.g., for Node.js) and/or a `cjs` + boolean property (if following `require`) properties. Set one of the `file` + items to `
`, ``, ``, or `` + to use the file referenced in the correpsonding property in `package.json`. + +- `jsdocConfig` - Object with: + - `file` string pointing to a path for a + [jsdoc config file](https://jsdoc.app/about-configuring-jsdoc.html) + which will be parsed for [input files](https://jsdoc.app/about-configuring-jsdoc.html#specifying-input-files), + including `include`, `exclude`, `includePattern`, and `excludePattern` + properties within the file as well as `opts.recurse`. See `entryFiles` + on how the (JavaScript) files will be treated (with + `sourceType: 'module'` in the jsdoc config file causing "cjs" to be + set to `false`). + +- `typeSources` - Array with `globals`, `exports`, and/or `locals` indicating + the source types that will be treated as valid types when found in the + current file or any entry files (`locals` will only apply to the + current file). Defaults to `['typedefs', 'globals', 'exports', 'locals']`. + ||| |---|---| |Context|everywhere| diff --git a/README.md b/README.md index 50e456336..a89d027f8 100644 --- a/README.md +++ b/README.md @@ -7823,12 +7823,38 @@ array's items will be considered as defined for the purposes of that tag. #### Options -An option object may have the following key: +An option object may have the following keys, helping indicate types or +file sources of types: - `definedTypes` - This array can be populated to indicate other types which are automatically considered as defined (in addition to globals, etc.). Defaults to an empty array. +- `entryFiles` - Array of entry files objects indicating JavaScript or HTML + files whose `import` or `require` statements should be resolved recursively + and be analyzed for `@typedef`'s, globals, etc. (see `typeSources`) to treat + as "defined" for the purposes of this rule. Each object should have a + `file` array and with an optional `node` boolean property to indicate whether + to use the Node Resolution Algorithm (e.g., for Node.js) and/or a `cjs` + boolean property (if following `require`) properties. Set one of the `file` + items to `
`, ``, ``, or `` + to use the file referenced in the correpsonding property in `package.json`. + +- `jsdocConfig` - Object with: + - `file` string pointing to a path for a + [jsdoc config file](https://jsdoc.app/about-configuring-jsdoc.html) + which will be parsed for [input files](https://jsdoc.app/about-configuring-jsdoc.html#specifying-input-files), + including `include`, `exclude`, `includePattern`, and `excludePattern` + properties within the file as well as `opts.recurse`. See `entryFiles` + on how the (JavaScript) files will be treated (with + `sourceType: 'module'` in the jsdoc config file causing "cjs" to be + set to `false`). + +- `typeSources` - Array with `globals`, `exports`, and/or `locals` indicating + the source types that will be treated as valid types when found in the + current file or any entry files (`locals` will only apply to the + current file). Defaults to `['typedefs', 'globals', 'exports', 'locals']`. + ||| |---|---| |Context|everywhere| @@ -8367,6 +8393,31 @@ class Test { return this; } } + +import {myTypesA} from '../internal/file.js'; // ERROR +import {myTypesB} from '../internal/file.js'; // NO ERROR + +/** +* @typedef newType +* @property {myTypesA.someType} someProp - Some prop. +*/ + +/** +* @param {newType} arg - Arg. +*/ +function myFunctionA(arg) { + return arg; +} + +/** +* @param {myTypesB.someType} arg - Arg. +*/ +function myFunctionB(arg) { + return arg; +} + +export {myFunctionA, myFunctionB}; +// "jsdoc/no-undefined-types": ["error"|"warn", {"entryFiles":[]}] ```` diff --git a/package.json b/package.json index 3bfaf4fed..e511e55c0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@es-joy/jsdoccomment": "^0.4.3", "comment-parser": "1.1.5", "debug": "^4.3.1", + "es-file-traverse": "^0.10.0", "esquery": "^1.4.0", "jsdoctypeparser": "^9.0.0", "lodash": "^4.17.21", diff --git a/src/rules/noUndefinedTypes.js b/src/rules/noUndefinedTypes.js index 62f1c8d3d..0064f4e65 100644 --- a/src/rules/noUndefinedTypes.js +++ b/src/rules/noUndefinedTypes.js @@ -1,6 +1,9 @@ import { getJSDocComment, } from '@es-joy/jsdoccomment'; +import { + traverse as esFileTraverse, +} from 'es-file-traverse'; import { parse as parseType, traverse, } from 'jsdoctypeparser'; @@ -23,9 +26,9 @@ const stripPseudoTypes = (str) => { return str && str.replace(/(?:\.|<>|\.<>|\[\])$/u, ''); }; -export default iterateJsdoc(({ +export default iterateJsdoc(async ({ context, - node, + node: jsdocNode, report, settings, sourceCode, @@ -34,7 +37,57 @@ export default iterateJsdoc(({ const {scopeManager} = sourceCode; const {globalScope} = scopeManager; - const {definedTypes = []} = context.options[0] || {}; + const { + definedTypes = [], + entryFiles = [ + // {file, cjs, node} + ], + jsdocConfig: {file: jsdocConfigFile}, + typeSources = ['typedefs', 'globals', 'exports', 'locals'], + } = context.options[0] || {}; + + // eslint-disable-next-line no-console + console.log('entryFiles', entryFiles, jsdocConfigFile, typeSources); + + // No async rules yet per ESLint Discord + // chat response from nzakas + await Promise.all(entryFiles.map(({file, node, cjs}) => { + if ([ + '
', '', '', '', + ].includes(file)) { + // Todo: Replace `file` with `package.json`-pointed value + } + + return esFileTraverse({ + /** + * @callback PromiseReturner + * @returns {Promise} + */ + /** + * @typedef {PlainObject} ESFileTraverseInfo + * @property {string} fullPath + * @property {string} text + * @property {AST} ast + * @property {"esm"|"cjs"|"amd"} [type] + * @property {PromiseReturner[]} [promMethods] + * @property {Set} [resolvedSet] + */ + /** + * @param {"enter"|"exit"} state + * @param {ESFileTraverseInfo} info + * @returns {void} + */ + callback (state, info) { + // Todo: Handle + // eslint-disable-next-line no-console + console.log('state', state, info); + }, + + cjs, + file, + node, + }); + })); let definedPreferredTypes = []; const {preferredTypes, structuredTags, mode} = settings; @@ -77,7 +130,7 @@ export default iterateJsdoc(({ .value(); const ancestorNodes = []; - let currentScope = scopeManager.acquire(node); + let currentScope = scopeManager.acquire(jsdocNode); while (currentScope && currentScope.block.type !== 'Program') { ancestorNodes.push(currentScope.block); @@ -182,6 +235,44 @@ export default iterateJsdoc(({ }, type: 'array', }, + entryFiles: { + items: { + properties: { + cjs: { + type: 'boolean', + }, + file: { + items: { + type: 'string', + }, + type: 'array', + }, + node: { + type: 'boolean', + }, + }, + require: ['file'], + type: 'object', + }, + type: 'array', + }, + jsdocConfig: { + properties: { + file: { + type: 'string', + }, + }, + type: 'object', + }, + typeSources: { + items: { + enum: [ + 'globals', 'exports', 'locals', + ], + type: 'string', + }, + type: 'array', + }, }, type: 'object', }, diff --git a/test/rules/assertions/noUndefinedTypes.js b/test/rules/assertions/noUndefinedTypes.js index 47649d5fc..017f76376 100644 --- a/test/rules/assertions/noUndefinedTypes.js +++ b/test/rules/assertions/noUndefinedTypes.js @@ -993,5 +993,43 @@ export default { `, parser: require.resolve('@typescript-eslint/parser'), }, + { + code: ` + import {myTypesA} from '../internal/file.js'; // ERROR + import {myTypesB} from '../internal/file.js'; // NO ERROR + + /** + * @typedef newType + * @property {myTypesA.someType} someProp - Some prop. + */ + + /** + * @param {newType} arg - Arg. + */ + function myFunctionA(arg) { + return arg; + } + + /** + * @param {myTypesB.someType} arg - Arg. + */ + function myFunctionB(arg) { + return arg; + } + + export {myFunctionA, myFunctionB}; + `, + options: [ + { + entryFiles: [], + }, + ], + parserOptions: { + sourceType: 'module', + }, + rules: { + 'no-unused-vars': ['error'], + }, + }, ], };