Skip to content

Commit

Permalink
fix(no-undefined-types): treat variables imported by @import tags…
Browse files Browse the repository at this point in the history
… as defined; fixes #1244
  • Loading branch information
brettz9 committed Jun 23, 2024
1 parent 0bea154 commit 34ba2e0
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 5 deletions.
67 changes: 67 additions & 0 deletions docs/rules/no-undefined-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,36 @@ const a = new Todo();
*/
// Settings: {"jsdoc":{"structuredTags":{"namepathOrURLReferencer":{"name":"namepath-or-url-referencing"}}}}
// Message: The type 'SomeType' is undefined.

/**
* @import BadImportIgnoredByThisRule
*/
/**
* @import LinterDef, { Sth as Something, Another as Another2 } from "eslint"
*/
/**
* @import { Linter } from "eslint"
*/
/**
* @import LinterDefault from "eslint"
*/
/**
* @import {Linter as Lintee} from "eslint"
*/
/**
* @import * as Linters from "eslint"
*/

/**
* @type {BadImportIgnoredByThisRule}
*/
/**
* @type {Sth}
*/
/**
* @type {Another}
*/
// Message: The type 'BadImportIgnoredByThisRule' is undefined.
````


Expand Down Expand Up @@ -777,8 +807,45 @@ function quux(foo) {

quux(0);

/**
* @import BadImportIgnoredByThisRule
*/
/**
* @import LinterDef, { Sth as Something, Another as Another2 } from "eslint"
*/
/**
* @import { Linter } from "eslint"
*/
/**
* @import LinterDefault from "eslint"
*/
/**
* @import {Linter as Lintee} from "eslint"
*/
/**
* @import * as Linters from "eslint"
*/

/**
* @type {LinterDef}
*/
/**
* @type {Something}
*/
/**
* @type {Another2}
*/
/**
* @type {Linter}
*/
/**
* @type {LinterDefault}
*/
/**
* @type {Lintee}
*/
/**
* @type {Linters}
*/
````

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"debug": "^4.3.4",
"escape-string-regexp": "^4.0.0",
"esquery": "^1.5.0",
"parse-imports": "^2.0.0",
"semver": "^7.6.2",
"spdx-expression-parse": "^4.0.0"
},
Expand Down
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 54 additions & 5 deletions src/rules/noUndefinedTypes.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import iterateJsdoc, {
parseComment,
} from '../iterateJsdoc.js';
import { parseImportsSync } from 'parse-imports';
import {
getJSDocComment,
parse as parseType,
traverse,
tryParse as tryParseType,
} from '@es-joy/jsdoccomment';
import iterateJsdoc, {
parseComment,
} from '../iterateJsdoc.js';

const extraTypes = [
'null', 'undefined', 'void', 'string', 'boolean', 'object',
Expand Down Expand Up @@ -109,13 +110,15 @@ export default iterateJsdoc(({
.filter(Boolean));
}

const typedefDeclarations = sourceCode.getAllComments()
const comments = sourceCode.getAllComments()
.filter((comment) => {
return (/^\*\s/u).test(comment.value);
})
.map((commentNode) => {
return parseComment(commentNode, '');
})
});

const typedefDeclarations = comments
.flatMap((doc) => {
return doc.tags.filter(({
tag,
Expand All @@ -127,6 +130,51 @@ export default iterateJsdoc(({
return tag.name;
});


const importTags = /** @type {string[]} */ (comments.flatMap((doc) => {
return doc.tags.filter(({
tag,
}) => {
return tag === 'import';
});
}).flatMap((tag) => {
const {
type, name, description
} = tag;
const typePart = type ? `{${type}} `: '';
const imprt = 'import ' + (description
? `${typePart}${name} ${description}`
: `${typePart}${name}`);

let imports;
try {
// Should technically await non-sync, but ESLint doesn't support sync rules;
// thankfully, the Wasm load time is safely fast
imports = parseImportsSync(imprt);
} catch (err) {
return null;
}

return [...imports].flatMap(({importClause}) => {
/* c8 ignore next */
const {default: dflt, named, namespace} = importClause || {};
const types = [];
if (dflt) {
types.push(dflt);
}
if (namespace) {
types.push(namespace);
}
if (named) {
for (const {binding} of named) {
types.push(binding);
}
}

return types;
});
}).filter(Boolean));

const ancestorNodes = [];

let currentNode = node;
Expand Down Expand Up @@ -193,6 +241,7 @@ export default iterateJsdoc(({
)
.concat(extraTypes)
.concat(typedefDeclarations)
.concat(importTags)
.concat(definedTypes)
.concat(/** @type {string[]} */ (definedPreferredTypes))
.concat(
Expand Down
83 changes: 83 additions & 0 deletions test/rules/assertions/noUndefinedTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,52 @@ export default {
},
},
},
{
code: `
/**
* @import BadImportIgnoredByThisRule
*/
/**
* @import LinterDef, { Sth as Something, Another as Another2 } from "eslint"
*/
/**
* @import { Linter } from "eslint"
*/
/**
* @import LinterDefault from "eslint"
*/
/**
* @import {Linter as Lintee} from "eslint"
*/
/**
* @import * as Linters from "eslint"
*/
/**
* @type {BadImportIgnoredByThisRule}
*/
/**
* @type {Sth}
*/
/**
* @type {Another}
*/
`,
errors: [
{
line: 22,
message: 'The type \'BadImportIgnoredByThisRule\' is undefined.',
},
{
line: 25,
message: 'The type \'Sth\' is undefined.',
},
{
line: 28,
message: 'The type \'Another\' is undefined.',
},
],
},
],
valid: [
{
Expand Down Expand Up @@ -1438,9 +1484,46 @@ export default {
},
{
code: `
/**
* @import BadImportIgnoredByThisRule
*/
/**
* @import LinterDef, { Sth as Something, Another as Another2 } from "eslint"
*/
/**
* @import { Linter } from "eslint"
*/
/**
* @import LinterDefault from "eslint"
*/
/**
* @import {Linter as Lintee} from "eslint"
*/
/**
* @import * as Linters from "eslint"
*/
/**
* @type {LinterDef}
*/
/**
* @type {Something}
*/
/**
* @type {Another2}
*/
/**
* @type {Linter}
*/
/**
* @type {LinterDefault}
*/
/**
* @type {Lintee}
*/
/**
* @type {Linters}
*/
`,
},
],
Expand Down

0 comments on commit 34ba2e0

Please sign in to comment.