Skip to content

Commit

Permalink
feat(extract): adds recognition of jsdoc 'bracket' imports
Browse files Browse the repository at this point in the history
  • Loading branch information
sverweij committed Nov 24, 2024
1 parent 6948da5 commit c01204f
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .dependency-cruiser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
const defaultStrictRules = fileURLToPath(
new URL("configs/recommended-strict.cjs", import.meta.url),
);
/** @type {import('./').IConfiguration} */
/** @type {import('./types/configuration.mjs').IConfiguration} */
export default {
extends: defaultStrictRules,
forbidden: [
Expand Down
2 changes: 1 addition & 1 deletion configs/.dependency-cruiser-show-metrics-config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import baseConfig from "../.dependency-cruiser.mjs";
/** @type {import('../').IConfiguration} */
/** @type {import('../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
forbidden: [
Expand Down
2 changes: 1 addition & 1 deletion configs/.dependency-cruiser-unlimited.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import baseConfig from "../.dependency-cruiser.mjs";
/** @type {import('../').IConfiguration} */
/** @type {import('../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {
Expand Down
39 changes: 36 additions & 3 deletions src/extract/tsc/extract-typescript-deps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,43 @@ function extractJSDocImportTags(pJSDocTags) {
}));
}

function extractJSDocBracketImports(pJSDocTags) {
return pJSDocTags
.filter(
(pTag) =>
pTag.tagName.escapedText !== "import" &&
/* c8 ignore start */
typescript.SyntaxKind[pTag.typeExpression?.kind ?? -1] ===
"FirstJSDocNode" &&
typescript.SyntaxKind[pTag.typeExpression.type?.kind ?? -1] ===
"LastTypeNode" &&
typescript.SyntaxKind[pTag.typeExpression.type.argument?.kind ?? -1] ===
"LiteralType" &&
typescript.SyntaxKind[
pTag.typeExpression.type.argument?.literal?.kind ?? -1
] === "StringLiteral" &&
/* c8 ignore stop*/
pTag.typeExpression.type.argument.literal.text,
)
.map((pTag) => ({
module: pTag.typeExpression.type.argument.literal.text,
moduleSystem: "es6",
exoticallyRequired: false,
dependencyTypes: ["type-only", "import", "jsdoc", "jsdoc-bracket-import"],
}));
}

function extractJSDocImports(pJSDocNodes) {
return pJSDocNodes
.filter((pJSDocLine) => pJSDocLine.tags)
.flatMap((pJSDocLine) => extractJSDocImportTags(pJSDocLine.tags));
const lJSDocNodesWithTags = pJSDocNodes.filter(
(pJSDocLine) => pJSDocLine.tags,
);
const lJSDocImportTags = lJSDocNodesWithTags.flatMap((pJSDocLine) =>
extractJSDocImportTags(pJSDocLine.tags),
);
const lJSDocBracketImports = lJSDocNodesWithTags.flatMap((pJSDocLine) =>
extractJSDocBracketImports(pJSDocLine.tags),
);
return lJSDocImportTags.concat(lJSDocBracketImports);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions test/cache/cache.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe("[I] cache/cache - writeCache", () => {
});

it("writes the passed cruise options to the cache folder (which is created when it doesn't exist yet) - content based cached strategy", async () => {
/** @type {import("../..").ICruiseResult} */
/** @type {import("../../types/cruise-result.mjs").ICruiseResult} */
const lDummyCacheContents = {
modules: [],
summary: { optionsUsed: { baseDir: "test/cache/__mocks__/cache" } },
Expand All @@ -102,7 +102,7 @@ describe("[I] cache/cache - canServeFromCache", () => {
OUTPUTS_FOLDER,
"serve-from-cache-compatible",
);
/** @type import("../..").ICruiseResult */
/** @type import("../../types/cruise-result.mjs").ICruiseResult */
const lMinimalCruiseResult = {
modules: [],
summary: {
Expand All @@ -117,7 +117,7 @@ describe("[I] cache/cache - canServeFromCache", () => {
revisionData: { cacheFormatVersion: 16.2, SHA1: "dummy-sha", changes: [] },
};

/** @type import("../..").ICruiseResult */
/** @type import("../../types/cruise-result.mjs").ICruiseResult */
const lNoVersionCruiseResult = {
modules: [],
summary: {
Expand All @@ -132,7 +132,7 @@ describe("[I] cache/cache - canServeFromCache", () => {
revisionData: { SHA1: "dummy-sha", changes: [] },
};

/** @type import("../..").ICruiseResult */
/** @type import("../../types/cruise-result.mjs").ICruiseResult */
const lOldVersionCruiseResult = {
modules: [],
summary: {
Expand Down
10 changes: 5 additions & 5 deletions test/cache/content-strategy.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("[U] cache/content-strategy - prepareRevisionDataForSaving", () => {
violations: [],
},
};
/** @type {import("../..").IRevisionData} */
/** @type {import("../../types/cruise-result.mjs").IRevisionData} */
const lEmptyRevisionData = {
SHA1: "shwoop",
changes: [],
Expand Down Expand Up @@ -284,7 +284,7 @@ describe("[U] cache/content-strategy - prepareRevisionDataForSaving", () => {
});

it("adds checksums to modules in the cruise result", () => {
/** @type {import("../..").ICruiseResult} */
/** @type {import("../../types/cruise-result.mjs").ICruiseResult} */
const lEmptyCruiseResult = {
modules: [
{ source: "foo.js" },
Expand All @@ -305,7 +305,7 @@ describe("[U] cache/content-strategy - prepareRevisionDataForSaving", () => {
violations: [],
},
};
/** @type {import("../..").IRevisionData} */
/** @type {import("../../types/cruise-result.mjs").IRevisionData} */
const lEmptyRevisionData = {
SHA1: "shwoop",
changes: [],
Expand Down Expand Up @@ -343,7 +343,7 @@ describe("[U] cache/content-strategy - prepareRevisionDataForSaving", () => {
});

it("removes changes from the revision data that aren't different anymore from the cruise result", () => {
/** @type {import("../..").ICruiseResult} */
/** @type {import("../../types/cruise-result.mjs").ICruiseResult} */
const lCruiseResult = {
modules: [
{ source: "foo.js" },
Expand All @@ -364,7 +364,7 @@ describe("[U] cache/content-strategy - prepareRevisionDataForSaving", () => {
violations: [],
},
};
/** @type {import("../..").IRevisionData} */
/** @type {import("../../types/cruise-result.mjs").IRevisionData} */
const lRevisionData = {
SHA1: "shwoop",
changes: [
Expand Down
201 changes: 201 additions & 0 deletions test/extract/tsc/jsdoc-bracket-imports.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { deepEqual } from "node:assert/strict";
import extractTypescript from "./extract-typescript.utl.mjs";

describe("[U] ast-extractors/extract-typescript - jsdoc 'bracket' imports", () => {
it("extracts @type whole module", () => {
deepEqual(
extractTypescript(
"/** @type {import('./hello.mjs')} */ export default {};",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});

it("extracts @type one type from a module", () => {
deepEqual(
extractTypescript("/** @type {import('./hello.mjs').thing} */", [], true),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});

it("extracts @typedef whole module", () => {
deepEqual(
extractTypescript(
"/** @typedef {import('./hello.mjs')} Hello */ ",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});
// * @returns {import('./goodby.mjs).wave} A goodbye
it("extracts @param & @returns for a function definitions", () => {
deepEqual(
extractTypescript(
`/**
* This function says hello and goodbye
*
* @param {import('./hello.mjs')} pHello a hello
* @returns {import('./goodbye.mjs').waveyWavey} A goodbye
*/
function findGoodbyeForGreeting(pHello) {
return Goodbyes[pHello];
}`,
[],
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",
],
},
],
);
});

/* eslint mocha/no-skipped-tests: "off" */
xit("extracts @type whole module even when wrapped in type shenanigans (Partial)", () => {
deepEqual(
extractTypescript(
"/** @type {Partial<import('./hello.mjs')>} */",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});
xit("extracts @type whole module even when wrapped in type shenanigans (Map)", () => {
deepEqual(
extractTypescript(
"/** @type {Map<string,import('./hello.mjs')>} */",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});
xit("extracts @type whole module even when wrapped in type shenanigans (Map & Parital)", () => {
deepEqual(
extractTypescript(
"/** @type {string, Partial<import('./hello.mjs')>} */",
[],
true,
),
[
{
module: "./hello.mjs",
moduleSystem: "es6",
dynamic: false,
exoticallyRequired: false,
dependencyTypes: [
"type-only",
"import",
"jsdoc",
"jsdoc-bracket-import",
],
},
],
);
});

it("leaves @type things alone that are not imports (but that look a bit like them)", () => {
deepEqual(
extractTypescript(
"/** @type {notAnImport('./hello.mjs').thing} */",
[],
true,
),
[],
);
});
it("leaves @type things alone that is empty", () => {
deepEqual(extractTypescript("/** @type } */", [], true), []);
});
});
7 changes: 0 additions & 7 deletions test/extract/tsc/jsdoc-import-tags.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,4 @@ describe("[U] ast-extractors/extract-typescript - jsdoc @imports", () => {
[],
);
});

it("does not extract imports with dynamic looking imports (@type {import('./ting.mjs')})", () => {
deepEqual(
extractTypescript("/** @type {import('./thing.mjs').thing} */", [], true),
[],
);
});
});
2 changes: 1 addition & 1 deletion tools/schema/.dependency-cruiser.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import baseConfig from "../../.dependency-cruiser.mjs";

/** @type {import('../../').IConfiguration} */
/** @type {import('../../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {
Expand Down
2 changes: 1 addition & 1 deletion types/.dependency-cruiser.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import baseConfig from "../.dependency-cruiser.mjs";

/** @type {import('../').IConfiguration} */
/** @type {import('./configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {
Expand Down

0 comments on commit c01204f

Please sign in to comment.