diff --git a/.dependency-cruiser.mjs b/.dependency-cruiser.mjs
index f7ebe0f90..9244b7d34 100644
--- a/.dependency-cruiser.mjs
+++ b/.dependency-cruiser.mjs
@@ -1,9 +1,10 @@
+/* eslint-disable max-lines */
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: [
diff --git a/configs/.dependency-cruiser-show-metrics-config.mjs b/configs/.dependency-cruiser-show-metrics-config.mjs
index 090b0dafe..1bdcbf6c9 100644
--- a/configs/.dependency-cruiser-show-metrics-config.mjs
+++ b/configs/.dependency-cruiser-show-metrics-config.mjs
@@ -1,5 +1,5 @@
import baseConfig from "../.dependency-cruiser.mjs";
-/** @type {import('../').IConfiguration} */
+/** @type {import('../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
forbidden: [
diff --git a/configs/.dependency-cruiser-unlimited.mjs b/configs/.dependency-cruiser-unlimited.mjs
index d532f879b..ab0106687 100644
--- a/configs/.dependency-cruiser-unlimited.mjs
+++ b/configs/.dependency-cruiser-unlimited.mjs
@@ -1,5 +1,5 @@
import baseConfig from "../.dependency-cruiser.mjs";
-/** @type {import('../').IConfiguration} */
+/** @type {import('../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {
diff --git a/doc/faq.md b/doc/faq.md
index 2f8e04d1e..9c5782a97 100644
--- a/doc/faq.md
+++ b/doc/faq.md
@@ -576,10 +576,24 @@ _teamcity_ reporters (he _dot_ and _ddot_ reporters already did before).
**A**: From version 5.4.0 or higher you can add an _exoticRequireStrings_ key in
your configuration with the wrapper(s) and/ or re-definitions of require:
-```json
-"exoticRequireStrings": ["window.require", "need", "tryRequire"]
+```javascript
+exoticRequireStrings: ["window.require", "need", "tryRequire"];
```
+### Q: I'm using jsdoc/ tsdoc comments to declare dependencies - how do I make sure dependency-cruiser picks those up?
+
+**A** From version 16.7.0 you can add this to your configuration:
+
+```javascript
+//...
+detectJSDocImports: true; // implies `parser: tsc`
+// ...
+```
+
+As only the `tsc` TypeScript parser supports this, it will need `typescript`
+to be installed (dependency-cruiser will automatically use it). For more
+information see [detectJSDocImports in the options reference](./options-reference#detectjsdocimports-detect-dependencies-in-jsdoc-comments)
+
### Q: Can I get code completion for .dependency-cruiser.js?
**A**: Yes.
diff --git a/doc/options-reference.md b/doc/options-reference.md
index 5cb4a2864..b61582771 100644
--- a/doc/options-reference.md
+++ b/doc/options-reference.md
@@ -791,12 +791,10 @@ you can provide the parameters like so:
If you have dependencies in JSDoc comments that you want to take into account
you can set this option to `true`. This will make dependency-cruiser look at
-TypeScript 5.5+ [`@import` tags](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag).
+TypeScript 5.5+ [`@import` tags](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag) as well as to bracket style imports (e.g. `/** @type {import('./thing').SomeType} */`)
+that have been part of TypeScript jsdoc/ tsdoc specification for a long time.
-In the near future it will also look to bracket style imports (e.g. `/** @type {import('./thing').SomeType} */`)
-in all JSDoc tags they can occur in (e.g. `@param`, `@returns`, `@type`, `@typedef` etc).
-
-As currently on the TypeScript compiler (`tsc`) can detect these imports, switching
+As currently only the TypeScript compiler (`tsc`) can detect these imports, switching
on this option implies dependency-cruiser will set `options.parser` to `tsc` so
it uses the TypeScript compiler to parse not only TypeScript but also JavaScript.
diff --git a/doc/rules-reference.md b/doc/rules-reference.md
index cfeb4775d..8263cb843 100644
--- a/doc/rules-reference.md
+++ b/doc/rules-reference.md
@@ -984,34 +984,34 @@ the dependency was declared. One or more of these can occur at the same time. E.
dependency which resolves to a base url in a tsconfig.json you'll see `import`, `aliased` as well as
`aliased-tsconfig` and `aliased-tsconfig-base-url`.
-| dependency type | meaning the module was imported ... | example |
-| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
-| aliased | via an alias of some sort (e.g. tsconfig paths, subpath imports, npm workspace or webpack aliases) | "~/hello.ts" |
-| aliased-subpath-import | via a [subpath import](https://nodejs.org/api/packages.html#subpath-imports) | "#thing/hello.mjs" |
-| aliased-tsconfig | via a typescript compilerOptions.paths or compilerOptions.baseUrl setting in tsconfig. | "@thing/hello" |
-| aliased-tsconfig-base-url | via a typescript [compilerOptions.baseUrl setting in tsconfig](https://www.typescriptlang.org/tsconfig#baseUrl) | "libs/utensils/src/hello.js" |
-| aliased-tsconfig-paths | via a typescript [compilerOptions.paths setting in tsconfig](https://www.typescriptlang.org/tsconfig#paths) | "@thing/hello" |
-| aliased-webpack | via a [webpack resolve alias](https://webpack.js.org/configuration/resolve/#resolvealias) | "Utilities" |
-| aliased-workspace | via a [workspace](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#workspaces) | "local-workspace-package" |
-| amd-define | with an AMD `define` wrapper (popularized by requirejs) | `define(["./thing"], function(thing){ /* do stuff */ })` |
-| amd-exotic-require | with a require statement within an AMD module (but with the first parameter baring an insensible non-standard name) | `define(function(want, exports, module){ var one = want('./thing')})` |
-| amd-require | with a require statement within an AMD module | `define(function(require, exports, module){ var one = require('./thing')})` |
-| dynamic-import | with a dynamic import statement | `const { thing } = await import("./things")` |
-| exotic-require | with a statement that isn't 'require' see [exoticallyRequired](#exoticallyrequired-exoticrequire-and-exoticrequirenot) | `const { thing } = want("./thing")` |
-| export | implicitly via a module export | `export { thing } from "./things"` |
-| import | with a 'regular' ES import | `import { thing } from "./things` |
-| import-equals | with an 'import equals' statement | `import fs = require("fs")` |
-| jsdoc | in jsdoc. See `jsdoc-bracket-import` and `jsdoc-import-tag`. Needs [detectJSDocImports](options-reference.md#detectjsdocimports-detect-dependencies-in-jsdoc-comments) set to `true` | `/** @type {import('./things').thing} */`, `/** @import { thing } from "things" */` |
-| jsdoc-bracket-import | in a jsdoc comment with a 'bracket' style import. Always `type-only`, also has the `jsdoc` dependency type. FUTURE FEATURE | `/** @type {import('./things').thing} */` |
-| jsdoc-import-tag | in a jsdoc comment with an @import tag. Always `type-only`, also has the `jsdoc` dependency type. | `/** @import { thing } from "things" */` |
-| pre-compilation-only | but the dependency will disappear at runtime. See [preCompilationOnly](#preCompilationOnly) | `import { thing } from "./things"` // and continue to not use `thing` |
-| require | with a commonjs 'require' statement | `const memoize = require("lodash/memoize")` |
-| triple-slash-amd-dependency | with a triple slash directive, specifically declaring an AMD dependency | `/// ` |
-| triple-slash-directive | with a triple slash directive (oldskool TypeScript) | |
-| triple-slash-file-reference | with a triple slash directive, specifically importing another module | `/// ` |
-| triple-slash-type-reference | with a triple slash directive, specifically importing types | `/// ` |
-| type-import | as part of a type declaration | `const lAThing: import('./things').IThing = {}` |
-| type-only | as 'type only' - only available for TypeScript sources, only for tsPreCompilationDeps !== false. | `import type { IThing } from "./things"` |
+| dependency type | meaning the module was imported ... | example |
+| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| aliased | via an alias of some sort (e.g. tsconfig paths, subpath imports, npm workspace or webpack aliases) | "~/hello.ts" |
+| aliased-subpath-import | via a [subpath import](https://nodejs.org/api/packages.html#subpath-imports) | "#thing/hello.mjs" |
+| aliased-tsconfig | via a typescript compilerOptions.paths or compilerOptions.baseUrl setting in tsconfig. | "@thing/hello" |
+| aliased-tsconfig-base-url | via a typescript [compilerOptions.baseUrl setting in tsconfig](https://www.typescriptlang.org/tsconfig#baseUrl) | "libs/utensils/src/hello.js" |
+| aliased-tsconfig-paths | via a typescript [compilerOptions.paths setting in tsconfig](https://www.typescriptlang.org/tsconfig#paths) | "@thing/hello" |
+| aliased-webpack | via a [webpack resolve alias](https://webpack.js.org/configuration/resolve/#resolvealias) | "Utilities" |
+| aliased-workspace | via a [workspace](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#workspaces) | "local-workspace-package" |
+| amd-define | with an AMD `define` wrapper (popularized by requirejs) | `define(["./thing"], function(thing){ /* do stuff */ })` |
+| amd-exotic-require | with a require statement within an AMD module (but with the first parameter baring an insensible non-standard name) | `define(function(want, exports, module){ var one = want('./thing')})` |
+| amd-require | with a require statement within an AMD module | `define(function(require, exports, module){ var one = require('./thing')})` |
+| dynamic-import | with a dynamic import statement | `const { thing } = await import("./things")` |
+| exotic-require | with a statement that isn't 'require' see [exoticallyRequired](#exoticallyrequired-exoticrequire-and-exoticrequirenot) | `const { thing } = want("./thing")` |
+| export | implicitly via a module export | `export { thing } from "./things"` |
+| import | with a 'regular' ES import | `import { thing } from "./things` |
+| import-equals | with an 'import equals' statement | `import fs = require("fs")` |
+| jsdoc | in jsdoc. See `jsdoc-bracket-import` & `jsdoc-import-tag`. Needs [detectJSDocImports](options-reference.md#detectjsdocimports-detect-dependencies-in-jsdoc-comments) set to `true` | `/** @type {import('./things').thing} */`, `/** @import { thing } from "things" */` |
+| jsdoc-bracket-import | in jsdoc with a 'bracket' style import. Always `type-only`, also has the `jsdoc` dependency type. | `/** @type {import('./things').thing} */` |
+| jsdoc-import-tag | in jsdoc with an @import tag. Always `type-only`, also has the `jsdoc` dependency type. | `/** @import { thing } from "things" */` |
+| pre-compilation-only | but the dependency will disappear at runtime. See [preCompilationOnly](#preCompilationOnly) | `import { thing } from "./things"` // and continue to not use `thing` |
+| require | with a commonjs 'require' statement | `const memoize = require("lodash/memoize")` |
+| triple-slash-amd-dependency | with a triple slash directive, specifically declaring an AMD dependency | `/// ` |
+| triple-slash-directive | with a triple slash directive (oldskool TypeScript) | |
+| triple-slash-file-reference | with a triple slash directive, specifically importing another module | `/// ` |
+| triple-slash-type-reference | with a triple slash directive, specifically importing types | `/// ` |
+| type-import | as part of a type declaration | `const lAThing: import('./things').IThing = {}` |
+| type-only | as 'type only' - only available for TypeScript sources, only for tsPreCompilationDeps !== false. | `import type { IThing } from "./things"` |
### `dynamic`
diff --git a/package.json b/package.json
index 714a6679c..9d4d6dee9 100644
--- a/package.json
+++ b/package.json
@@ -303,4 +303,4 @@
"vue-template-compiler": ">=2.0.0 <3.0.0",
"@vue/compiler-sfc": ">=3.0.0 <4.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/src/extract/tsc/extract-typescript-deps.mjs b/src/extract/tsc/extract-typescript-deps.mjs
index 3a142258c..e9b19868a 100644
--- a/src/extract/tsc/extract-typescript-deps.mjs
+++ b/src/extract/tsc/extract-typescript-deps.mjs
@@ -1,3 +1,4 @@
+/* eslint-disable security/detect-object-injection */
/* eslint-disable unicorn/prevent-abbreviations */
/* eslint-disable max-lines */
/* eslint-disable no-inline-comments */
@@ -263,7 +264,7 @@ function extractJSDocImportTags(pJSDocTags) {
.filter(
(pTag) =>
pTag.tagName.escapedText === "import" &&
- pTag.moduleSpecifier?.kind &&
+ pTag.moduleSpecifier &&
typescript.SyntaxKind[pTag.moduleSpecifier.kind] === "StringLiteral" &&
pTag.moduleSpecifier.text,
)
@@ -275,10 +276,82 @@ 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 keyInJSDocIsIgnorable(pKey) {
+ return [
+ "parent",
+ "pos",
+ "end",
+ "flags",
+ "emitNode",
+ "modifierFlagsCache",
+ "transformFlags",
+ "id",
+ "flowNode",
+ "symbol",
+ "original",
+ ].includes(pKey);
+}
+
+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 (!keyInJSDocIsIgnorable(lKey) && pObject[lKey]) {
+ walkJSDoc(pObject[lKey], pCollection);
+ }
+ }
+ }
+}
+
+export function getJSDocImports(pTagNode) {
+ const lCollection = new Set();
+ walkJSDoc(pTagNode, 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",
+ )
+ .flatMap((pTag) => getJSDocImports(pTag))
+ .map((pImportName) => ({
+ module: pImportName,
+ 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));
+ // https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag
+ 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);
}
/**
@@ -338,14 +411,8 @@ function walk(pResult, pExoticRequireStrings, pDetectJSDocImports) {
});
}
- // /** @import thing from './module' */
- // /** @import {thing} from './module' */
- // /** @import * as thing from './module' */
- // see https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag
- //
- // TODO: all the kinds of tags that can have import statements as type declarations
- // (e.g. @type, @param, @returns, @typedef, @property, @prop, @arg, ...)
- // https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
+ // /** @import thing from './module' */ etc
+ // /** @type {import('module').thing}*/ etc
if (pDetectJSDocImports && pASTNode.jsDoc) {
const lJSDocImports = extractJSDocImports(pASTNode.jsDoc);
diff --git a/test/cache/cache.spec.mjs b/test/cache/cache.spec.mjs
index 6b30196e8..31fbba01f 100644
--- a/test/cache/cache.spec.mjs
+++ b/test/cache/cache.spec.mjs
@@ -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" } },
@@ -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: {
@@ -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: {
@@ -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: {
diff --git a/test/cache/content-strategy.spec.mjs b/test/cache/content-strategy.spec.mjs
index eb79aaa30..3840a367f 100644
--- a/test/cache/content-strategy.spec.mjs
+++ b/test/cache/content-strategy.spec.mjs
@@ -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: [],
@@ -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" },
@@ -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: [],
@@ -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" },
@@ -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: [
diff --git a/test/extract/tsc/jsdoc-bracket-imports.spec.mjs b/test/extract/tsc/jsdoc-bracket-imports.spec.mjs
new file mode 100644
index 000000000..e880d3431
--- /dev/null
+++ b/test/extract/tsc/jsdoc-bracket-imports.spec.mjs
@@ -0,0 +1,261 @@
+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",
+ ],
+ },
+ ],
+ );
+ });
+
+ it("extracts @type whole module even when wrapped in type shenanigans (Partial)", () => {
+ deepEqual(
+ extractTypescript(
+ "/** @type {Partial} */",
+ [],
+ true,
+ ),
+ [
+ {
+ module: "./hello.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 (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} */",
+ [],
+ true,
+ ),
+ [
+ {
+ module: "./hello.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 & Partial)", () => {
+ deepEqual(
+ extractTypescript(
+ "/** @type {Map>} */",
+ [],
+ true,
+ ),
+ [
+ {
+ module: "./hello.mjs",
+ moduleSystem: "es6",
+ dynamic: false,
+ exoticallyRequired: false,
+ dependencyTypes: [
+ "type-only",
+ "import",
+ "jsdoc",
+ "jsdoc-bracket-import",
+ ],
+ },
+ ],
+ );
+ });
+ it("extracts @return wrapped in type shenanigans)", () => {
+ deepEqual(
+ extractTypescript(
+ "/** @return {Promise} */",
+ [],
+ true,
+ ),
+ [
+ {
+ module: "./types.js",
+ 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), []);
+ });
+});
diff --git a/test/extract/tsc/jsdoc-import-tags.spec.mjs b/test/extract/tsc/jsdoc-import-tags.spec.mjs
index eb159f053..dbc84525e 100644
--- a/test/extract/tsc/jsdoc-import-tags.spec.mjs
+++ b/test/extract/tsc/jsdoc-import-tags.spec.mjs
@@ -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),
- [],
- );
- });
});
diff --git a/tools/schema/.dependency-cruiser.mjs b/tools/schema/.dependency-cruiser.mjs
index 41d0dd9da..d488883ca 100644
--- a/tools/schema/.dependency-cruiser.mjs
+++ b/tools/schema/.dependency-cruiser.mjs
@@ -1,6 +1,6 @@
import baseConfig from "../../.dependency-cruiser.mjs";
-/** @type {import('../../').IConfiguration} */
+/** @type {import('../../types/configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {
diff --git a/types/.dependency-cruiser.mjs b/types/.dependency-cruiser.mjs
index 1b4ddc734..12f13b06a 100644
--- a/types/.dependency-cruiser.mjs
+++ b/types/.dependency-cruiser.mjs
@@ -1,6 +1,6 @@
import baseConfig from "../.dependency-cruiser.mjs";
-/** @type {import('../').IConfiguration} */
+/** @type {import('./configuration.mjs').IConfiguration} */
export default {
...baseConfig,
options: {