Skip to content

Commit

Permalink
feat: also detect imports nested deeper into jsdoc tags
Browse files Browse the repository at this point in the history
  • Loading branch information
sverweij committed Nov 30, 2024
1 parent b4664b6 commit 6c98818
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 15 deletions.
67 changes: 56 additions & 11 deletions src/extract/tsc/extract-typescript-deps.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable security/detect-object-injection */
/* eslint-disable unicorn/prevent-abbreviations */
/* eslint-disable max-lines */
/* eslint-disable no-inline-comments */
Expand Down Expand Up @@ -275,24 +276,68 @@ function extractJSDocImportTags(pJSDocTags) {
}));
}

function isJSDocImport(pTypeNode) {
// import('./hello.mjs') within jsdoc
return (
typescript.SyntaxKind[pTypeNode?.kind] === "LastTypeNode" &&
typescript.SyntaxKind[pTypeNode.argument?.kind] === "LiteralType" &&
typescript.SyntaxKind[pTypeNode.argument?.literal?.kind] ===
"StringLiteral" &&
pTypeNode.argument.literal.text
);
}

function keyIsBoring(pKey) {
return [
"parent",
"pos",
"end",
"flags",
"emitNode",
"modifierFlagsCache",
"transformFlags",
"id",
"flowNode",
"symbol",
"original",
].includes(pKey);
}

/**
* Walks the given object, that can have both arrays and objects as values, and returns a new object with the same structure, but with all the values replaced by the result of the given function.
* @param {Object} obj The object to walk.
*/
export function walkJSDoc(pObject, pCollection = new Set()) {
if (isJSDocImport(pObject)) {
pCollection.add(pObject.argument.literal.text);
} else if (Array.isArray(pObject)) {
pObject.forEach((pValue) => walkJSDoc(pValue, pCollection));
} else if (typeof pObject === "object") {
for (const lKey in pObject) {
if (!keyIsBoring(lKey) && pObject[lKey]) {
walkJSDoc(pObject[lKey], pCollection);
}
}
}
}

export function getJSDocImports(pObject) {
const lCollection = new Set();
walkJSDoc(pObject, lCollection);
return Array.from(lCollection);
}

function extractJSDocBracketImports(pJSDocTags) {
// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
return pJSDocTags
.filter(
(pTag) =>
pTag.tagName.escapedText !== "import" &&
typescript.SyntaxKind[pTag.typeExpression?.kind] === "FirstJSDocNode" &&
typescript.SyntaxKind[pTag.typeExpression.type?.kind] ===
"LastTypeNode" &&
typescript.SyntaxKind[pTag.typeExpression.type.argument?.kind] ===
"LiteralType" &&
typescript.SyntaxKind[
pTag.typeExpression.type.argument?.literal?.kind
] === "StringLiteral" &&
pTag.typeExpression.type.argument.literal.text,
typescript.SyntaxKind[pTag.typeExpression?.kind] === "FirstJSDocNode",
)
.map((pTag) => ({
module: pTag.typeExpression.type.argument.literal.text,
.flatMap((pTag) => getJSDocImports(pTag))
.map((pImportName) => ({
module: pImportName,
moduleSystem: "es6",
exoticallyRequired: false,
dependencyTypes: ["type-only", "import", "jsdoc", "jsdoc-bracket-import"],
Expand Down
46 changes: 42 additions & 4 deletions test/extract/tsc/jsdoc-bracket-imports.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe("[U] ast-extractors/extract-typescript - jsdoc 'bracket' imports", () =
});

/* eslint mocha/no-skipped-tests: "off" */
xit("extracts @type whole module even when wrapped in type shenanigans (Partial)", () => {
it("extracts @type whole module even when wrapped in type shenanigans (Partial)", () => {
deepEqual(
extractTypescript(
"/** @type {Partial<import('./hello.mjs')>} */",
Expand All @@ -138,7 +138,44 @@ describe("[U] ast-extractors/extract-typescript - jsdoc 'bracket' imports", () =
],
);
});
xit("extracts @type whole module even when wrapped in type shenanigans (Map)", () => {

it("extracts @type whole module even when wrapped in type shenanigans (union)", () => {
deepEqual(
extractTypescript(
"/** @type {import('./hello.mjs')|import('./goodbye.mjs')} */",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
{
module: "./goodbye.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});

it("extracts @type whole module even when wrapped in type shenanigans (Map)", () => {
deepEqual(
extractTypescript(
"/** @type {Map<string,import('./hello.mjs')>} */",
Expand All @@ -161,10 +198,11 @@ describe("[U] ast-extractors/extract-typescript - jsdoc 'bracket' imports", () =
],
);
});
xit("extracts @type whole module even when wrapped in type shenanigans (Map & Parital)", () => {

it("extracts @type whole module even when wrapped in type shenanigans (Map & Partial)", () => {
deepEqual(
extractTypescript(
"/** @type {string, Partial<import('./hello.mjs')>} */",
"/** @type {Map<string, Partial<import('./hello.mjs')>>} */",
[],
true,
),
Expand Down

0 comments on commit 6c98818

Please sign in to comment.