Skip to content

Commit ff84dae

Browse files
committed
INCOMPLETE
1. Add tests (for desired features added to docs) -> implement 2. (apparent problem in eslint itself with our use of Program:exit): 3. Could also use this approach for checking consistency between function calls and jsdoc argument signature types; compile to WebAssembly test(`no-undefined-types`): issues gajus#507
1 parent a54079c commit ff84dae

File tree

5 files changed

+213
-6
lines changed

5 files changed

+213
-6
lines changed

.README/rules/no-undefined-types.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,38 @@ array's items will be considered as defined for the purposes of that tag.
4545

4646
#### Options
4747

48-
An option object may have the following key:
48+
An option object may have the following keys, helping indicate types or
49+
file sources of types:
4950

5051
- `definedTypes` - This array can be populated to indicate other types which
5152
are automatically considered as defined (in addition to globals, etc.).
5253
Defaults to an empty array.
5354

55+
- `entryFiles` - Array of entry files objects indicating JavaScript or HTML
56+
files whose `import` or `require` statements should be resolved recursively
57+
and be analyzed for `@typedef`'s, globals, etc. (see `typeSources`) to treat
58+
as "defined" for the purposes of this rule. Each object should have a
59+
`file` array and with an optional `node` boolean property to indicate whether
60+
to use the Node Resolution Algorithm (e.g., for Node.js) and/or a `cjs`
61+
boolean property (if following `require`) properties. Set one of the `file`
62+
items to `<main>`, `<exports>`, `<exports.imports>`, or `<exports.require>`
63+
to use the file referenced in the correpsonding property in `package.json`.
64+
65+
- `jsdocConfig` - Object with:
66+
- `file` string pointing to a path for a
67+
[jsdoc config file](https://jsdoc.app/about-configuring-jsdoc.html)
68+
which will be parsed for [input files](https://jsdoc.app/about-configuring-jsdoc.html#specifying-input-files),
69+
including `include`, `exclude`, `includePattern`, and `excludePattern`
70+
properties within the file as well as `opts.recurse`. See `entryFiles`
71+
on how the (JavaScript) files will be treated (with
72+
`sourceType: 'module'` in the jsdoc config file causing "cjs" to be
73+
set to `false`).
74+
75+
- `typeSources` - Array with `globals`, `exports`, and/or `locals` indicating
76+
the source types that will be treated as valid types when found in the
77+
current file or any entry files (`locals` will only apply to the
78+
current file). Defaults to `['typedefs', 'globals', 'exports', 'locals']`.
79+
5480
|||
5581
|---|---|
5682
|Context|everywhere|

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8545,12 +8545,38 @@ array's items will be considered as defined for the purposes of that tag.
85458545
<a name="eslint-plugin-jsdoc-rules-no-undefined-types-options-20"></a>
85468546
#### Options
85478547

8548-
An option object may have the following key:
8548+
An option object may have the following keys, helping indicate types or
8549+
file sources of types:
85498550

85508551
- `definedTypes` - This array can be populated to indicate other types which
85518552
are automatically considered as defined (in addition to globals, etc.).
85528553
Defaults to an empty array.
85538554

8555+
- `entryFiles` - Array of entry files objects indicating JavaScript or HTML
8556+
files whose `import` or `require` statements should be resolved recursively
8557+
and be analyzed for `@typedef`'s, globals, etc. (see `typeSources`) to treat
8558+
as "defined" for the purposes of this rule. Each object should have a
8559+
`file` array and with an optional `node` boolean property to indicate whether
8560+
to use the Node Resolution Algorithm (e.g., for Node.js) and/or a `cjs`
8561+
boolean property (if following `require`) properties. Set one of the `file`
8562+
items to `<main>`, `<exports>`, `<exports.imports>`, or `<exports.require>`
8563+
to use the file referenced in the correpsonding property in `package.json`.
8564+
8565+
- `jsdocConfig` - Object with:
8566+
- `file` string pointing to a path for a
8567+
[jsdoc config file](https://jsdoc.app/about-configuring-jsdoc.html)
8568+
which will be parsed for [input files](https://jsdoc.app/about-configuring-jsdoc.html#specifying-input-files),
8569+
including `include`, `exclude`, `includePattern`, and `excludePattern`
8570+
properties within the file as well as `opts.recurse`. See `entryFiles`
8571+
on how the (JavaScript) files will be treated (with
8572+
`sourceType: 'module'` in the jsdoc config file causing "cjs" to be
8573+
set to `false`).
8574+
8575+
- `typeSources` - Array with `globals`, `exports`, and/or `locals` indicating
8576+
the source types that will be treated as valid types when found in the
8577+
current file or any entry files (`locals` will only apply to the
8578+
current file). Defaults to `['typedefs', 'globals', 'exports', 'locals']`.
8579+
85548580
|||
85558581
|---|---|
85568582
|Context|everywhere|
@@ -9089,6 +9115,31 @@ class Test {
90899115
return this;
90909116
}
90919117
}
9118+
9119+
import {myTypesA} from '../internal/file.js'; // ERROR
9120+
import {myTypesB} from '../internal/file.js'; // NO ERROR
9121+
9122+
/**
9123+
* @typedef newType
9124+
* @property {myTypesA.someType} someProp - Some prop.
9125+
*/
9126+
9127+
/**
9128+
* @param {newType} arg - Arg.
9129+
*/
9130+
function myFunctionA(arg) {
9131+
return arg;
9132+
}
9133+
9134+
/**
9135+
* @param {myTypesB.someType} arg - Arg.
9136+
*/
9137+
function myFunctionB(arg) {
9138+
return arg;
9139+
}
9140+
9141+
export {myFunctionA, myFunctionB};
9142+
// "jsdoc/no-undefined-types": ["error"|"warn", {"entryFiles":[]}]
90929143
````
90939144

90949145

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"@es-joy/jsdoccomment": "^0.6.0",
99
"comment-parser": "1.1.5",
1010
"debug": "^4.3.1",
11+
"es-file-traverse": "^0.10.0",
1112
"esquery": "^1.4.0",
1213
"jsdoctypeparser": "^9.0.0",
1314
"lodash": "^4.17.21",

src/rules/noUndefinedTypes.js

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {
22
getJSDocComment,
33
} from '@es-joy/jsdoccomment';
4+
import {
5+
traverse as esFileTraverse,
6+
} from 'es-file-traverse';
47
import {
58
parse as parseType, traverse,
69
} from 'jsdoctypeparser';
@@ -23,9 +26,9 @@ const stripPseudoTypes = (str) => {
2326
return str && str.replace(/(?:\.|<>|\.<>|\[\])$/u, '');
2427
};
2528

26-
export default iterateJsdoc(({
29+
export default iterateJsdoc(async ({
2730
context,
28-
node,
31+
node: jsdocNode,
2932
report,
3033
settings,
3134
sourceCode,
@@ -34,7 +37,57 @@ export default iterateJsdoc(({
3437
const {scopeManager} = sourceCode;
3538
const {globalScope} = scopeManager;
3639

37-
const {definedTypes = []} = context.options[0] || {};
40+
const {
41+
definedTypes = [],
42+
entryFiles = [
43+
// {file, cjs, node}
44+
],
45+
jsdocConfig: {file: jsdocConfigFile},
46+
typeSources = ['typedefs', 'globals', 'exports', 'locals'],
47+
} = context.options[0] || {};
48+
49+
// eslint-disable-next-line no-console
50+
console.log('entryFiles', entryFiles, jsdocConfigFile, typeSources);
51+
52+
// No async rules yet per ESLint Discord
53+
// chat response from nzakas
54+
await Promise.all(entryFiles.map(({file, node, cjs}) => {
55+
if ([
56+
'<main>', '<exports>', '<exports.imports>', '<exports.require>',
57+
].includes(file)) {
58+
// Todo: Replace `file` with `package.json`-pointed value
59+
}
60+
61+
return esFileTraverse({
62+
/**
63+
* @callback PromiseReturner
64+
* @returns {Promise<void>}
65+
*/
66+
/**
67+
* @typedef {PlainObject} ESFileTraverseInfo
68+
* @property {string} fullPath
69+
* @property {string} text
70+
* @property {AST} ast
71+
* @property {"esm"|"cjs"|"amd"} [type]
72+
* @property {PromiseReturner[]} [promMethods]
73+
* @property {Set<string>} [resolvedSet]
74+
*/
75+
/**
76+
* @param {"enter"|"exit"} state
77+
* @param {ESFileTraverseInfo} info
78+
* @returns {void}
79+
*/
80+
callback (state, info) {
81+
// Todo: Handle
82+
// eslint-disable-next-line no-console
83+
console.log('state', state, info);
84+
},
85+
86+
cjs,
87+
file,
88+
node,
89+
});
90+
}));
3891

3992
let definedPreferredTypes = [];
4093
const {preferredTypes, structuredTags, mode} = settings;
@@ -77,7 +130,7 @@ export default iterateJsdoc(({
77130
.value();
78131

79132
const ancestorNodes = [];
80-
let currentScope = scopeManager.acquire(node);
133+
let currentScope = scopeManager.acquire(jsdocNode);
81134

82135
while (currentScope && currentScope.block.type !== 'Program') {
83136
ancestorNodes.push(currentScope.block);
@@ -182,6 +235,44 @@ export default iterateJsdoc(({
182235
},
183236
type: 'array',
184237
},
238+
entryFiles: {
239+
items: {
240+
properties: {
241+
cjs: {
242+
type: 'boolean',
243+
},
244+
file: {
245+
items: {
246+
type: 'string',
247+
},
248+
type: 'array',
249+
},
250+
node: {
251+
type: 'boolean',
252+
},
253+
},
254+
require: ['file'],
255+
type: 'object',
256+
},
257+
type: 'array',
258+
},
259+
jsdocConfig: {
260+
properties: {
261+
file: {
262+
type: 'string',
263+
},
264+
},
265+
type: 'object',
266+
},
267+
typeSources: {
268+
items: {
269+
enum: [
270+
'globals', 'exports', 'locals',
271+
],
272+
type: 'string',
273+
},
274+
type: 'array',
275+
},
185276
},
186277
type: 'object',
187278
},

test/rules/assertions/noUndefinedTypes.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,5 +997,43 @@ export default {
997997
`,
998998
parser: require.resolve('@typescript-eslint/parser'),
999999
},
1000+
{
1001+
code: `
1002+
import {myTypesA} from '../internal/file.js'; // ERROR
1003+
import {myTypesB} from '../internal/file.js'; // NO ERROR
1004+
1005+
/**
1006+
* @typedef newType
1007+
* @property {myTypesA.someType} someProp - Some prop.
1008+
*/
1009+
1010+
/**
1011+
* @param {newType} arg - Arg.
1012+
*/
1013+
function myFunctionA(arg) {
1014+
return arg;
1015+
}
1016+
1017+
/**
1018+
* @param {myTypesB.someType} arg - Arg.
1019+
*/
1020+
function myFunctionB(arg) {
1021+
return arg;
1022+
}
1023+
1024+
export {myFunctionA, myFunctionB};
1025+
`,
1026+
options: [
1027+
{
1028+
entryFiles: [],
1029+
},
1030+
],
1031+
parserOptions: {
1032+
sourceType: 'module',
1033+
},
1034+
rules: {
1035+
'no-unused-vars': ['error'],
1036+
},
1037+
},
10001038
],
10011039
};

0 commit comments

Comments
 (0)