Skip to content

Commit

Permalink
Fix up JSDoc visitor and try harder to find import types
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Nov 28, 2023
1 parent f6c1d93 commit bd96a2b
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 11 deletions.
5 changes: 5 additions & 0 deletions packages/knip/fixtures/jsdoc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ const ignorePatterns = [];
/** @type {import('some-types').Module} */
const obj = {};

/**
* @returns {Promise<import('type-fest').PackageJson>}
*/
const getPackageManifest = async () => ({});

/** @type {string | null} */
const str = 'str';

Expand Down
4 changes: 0 additions & 4 deletions packages/knip/src/typescript/ast-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ interface ValidImportTypeNode extends ts.ImportTypeNode {
argument: ts.LiteralTypeNode & { literal: ts.StringLiteral };
}

export function isValidImportTypeNode(node: ts.Node): node is ValidImportTypeNode {
return ts.isImportTypeNode(node);
}

export function isGetOrSetAccessorDeclaration(node: ts.Node): node is ts.AccessorDeclaration {
return node.kind === ts.SyntaxKind.SetAccessor || node.kind === ts.SyntaxKind.GetAccessor;
}
Expand Down
47 changes: 41 additions & 6 deletions packages/knip/src/typescript/visitors/imports/jsDocType.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,51 @@
import ts from 'typescript';
import { isValidImportTypeNode } from '../../ast-helpers.js';
import { importVisitor as visit } from '../index.js';

const extractImportSpecifiers = (node: ts.JSDocTag) => {
const importSpecifiers: string[] = [];

function visit(node: ts.Node) {
if (ts.isJSDocTypeExpression(node)) {
const typeNode = node.type;
if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) {
typeNode.typeArguments.forEach(arg => {
if (ts.isImportTypeNode(arg)) {
const importClause = arg.argument;
if (ts.isLiteralTypeNode(importClause) && ts.isStringLiteral(importClause.literal)) {
importSpecifiers.push(importClause.literal.text);
}
}
});
}
}
if (ts.isJSDocTypeTag(node)) {
const typeNode = node.typeExpression?.type;
if (ts.isImportTypeNode(typeNode)) {
const importClause = typeNode.argument;
if (ts.isLiteralTypeNode(importClause) && ts.isStringLiteral(importClause.literal)) {
importSpecifiers.push(importClause.literal.text);
}
}
}
ts.forEachChild(node, visit);
}

visit(node);

return importSpecifiers;
};

export default visit(
() => true,
node => {
if ('jsDoc' in node) {
const type = ts.getJSDocType(node);
if (type && isValidImportTypeNode(type)) {
// TODO Odd to assume this is an `import()` call?
return { specifier: type.argument.literal.text };
if ('jsDoc' in node && node.jsDoc) {
const jsDoc = node.jsDoc as ts.JSDoc[];
if (jsDoc.length > 0 && jsDoc[0].parent.parent === node.parent) {
return jsDoc
.flatMap(jsDoc => (jsDoc.tags ?? []).flatMap(extractImportSpecifiers))
.map(specifier => ({ specifier }));
}
}
return [];
}
);
4 changes: 3 additions & 1 deletion packages/knip/test/jsdoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ test('Find imports from jsdoc @type tags', async () => {
});

assert(issues.unlisted['index.ts']['some-types']);
assert(issues.unlisted['index.ts']['type-fest']);
assert(issues.unlisted['index.ts']['more-types']);
assert(issues.unlisted['index.ts']['@jest/types']);

assert.deepEqual(counters, {
...baseCounters,
unlisted: 3,
unlisted: 4,
processed: 1,
total: 1,
});
Expand Down

0 comments on commit bd96a2b

Please sign in to comment.