Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extract): adds recognition of jsdoc @import type imports #965

Merged
merged 12 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dependency-cruiser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,8 @@ export default {
// },
/* Experimental: the parser to use
*/
parser: "tsc", // acorn, tsc
// parser: "tsc", // acorn, tsc
detectJSDocImports: true, // implies parser: "tsc"
experimentalStats: true,
metrics: true,
enhancedResolveOptions: {
Expand Down
68 changes: 35 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,36 +113,38 @@ jobs:
- run: npm run depcruise
- run: npx mocha --invert --fgrep "#do-not-run-on-windows"

check-berry-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
.yarn
.yarnrc.yml
.pnp.js
yarn.lock
key: ${{env.NODE_LATEST}}@${{env.PLATFORM}}-build-${{hashFiles('package.json')}}
restore-keys: |
${{env.NODE_LATEST}}@${{env.PLATFORM}}-build-
- uses: actions/setup-node@v4
with:
node-version: ${{env.NODE_LATEST}}
- name: install & build
run: |
rm -f .npmrc
yarn set version berry
YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
- name: forbidden dependency check
run: |
yarn --version
yarn depcruise
# testing doesn't work as the tests are esm and berry, with pnp enabled,
# doesn't support esm yet.
# - name: test coverage
# run: |
# node --version
# yarn --version
# yarn test:cover
# for #reasons the run step takes forever to complete on the ci - while
# running fine locally. Something to figure out another time.
# check-berry-integration:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/cache@v4
# with:
# path: |
# .yarn
# .yarnrc.yml
# .pnp.js
# yarn.lock
# key: ${{env.NODE_LATEST}}@${{env.PLATFORM}}-build-${{hashFiles('package.json')}}
# restore-keys: |
# ${{env.NODE_LATEST}}@${{env.PLATFORM}}-build-
# - uses: actions/setup-node@v4
# with:
# node-version: ${{env.NODE_LATEST}}
# - name: install & build
# run: |
# rm -f .npmrc
# yarn set version berry
# YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn
# - name: forbidden dependency check
# run: |
# yarn --version
# yarn depcruise
# # testing doesn't work as the tests are esm and berry, with pnp enabled,
# # doesn't support esm yet.
# # - name: test coverage
# # run: |
# # node --version
# # yarn --version
# # yarn test:cover
29 changes: 29 additions & 0 deletions doc/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [`tsConfig`: use a TypeScript configuration file ('project')](#tsconfig-use-a-typescript-configuration-file-project)
- [`babelConfig`: use a babel configuration file](#babelconfig-use-a-babel-configuration-file)
- [`webpackConfig`: use (the resolution options of) a webpack configuration](#webpackconfig-use-the-resolution-options-of-a-webpack-configuration)
- [`detectJSDocImports`: detect dependencies in JSDoc comments](#detectjsdocimports-detect-dependencies-in-jsdoc-comments)
- [Yarn Plug'n'Play support - `externalModuleResolutionStrategy`](#yarn-plugnplay-support---externalmoduleresolutionstrategy)
- [`prefix`: prefix links in reports](#prefix-prefix-links-in-reports)
- [`reporterOptions`](#reporteroptions)
Expand Down Expand Up @@ -784,6 +785,34 @@ you can provide the parameters like so:
- :bulb: For more information check out the the [webpack resolve](https://webpack.js.org/configuration/resolve/)
documentation.

### `detectJSDocImports`: detect dependencies in JSDoc comments

> :shell: there is no command line equivalent for this

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).

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
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.

```javascript
options: {
detectJSDocImports: true; // implies `parser: "tsc"`
}
```

#### Usage notes

- :bulb: Only TypeScript compilers 5.5 and up can detect `@import` tags.
- :bulb: If you want to take imports in JSDoc comments in consideration you
will need the `typescript` compiler in your (dev-)dependencies as it's currently
the only parser that supports these.

### Yarn Plug'n'Play support - `externalModuleResolutionStrategy`

> :shell: there is no command line equivalent for this
Expand Down
53 changes: 28 additions & 25 deletions doc/rules-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -984,31 +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-require | with a require statement within an AMD module | `define(function(require, exports, module){ var one = require('./thing')})` |
| 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')})` |
| type-only | as 'type only' - only available for TypeScript sources, only for tsPreCompilationDeps !== false. | `import type { IThing } from "./things"` |
| export | implicitly via a module export | `export { thing } from "./things"` |
| import | with a 'regular' ES import | `import { thing } from "./things` |
| dynamic-import | with a dynamic import statement | `const { thing } = await import("./things")` |
| import-equals | with an 'import equals' statement | `import fs = require("fs")` |
| type-import | as part of a type declaration | `const lAThing: import('./things').IThing = {}` |
| require | with a commonjs 'require' statement | `const memoize = require("lodash/memoize")` |
| exotic-require | with a statement that isn't 'require' see [exoticallyRequired](#exoticallyrequired-exoticrequire-and-exoticrequirenot) | `const { thing } = want("./thing")` |
| triple-slash-directive | with a triple slash directive (oldskool TypeScript) | |
| triple-slash-file-reference | with a triple slash directive, specifically importing another module | `/// <reference path="./ts-thing" />` |
| triple-slash-type-reference | with a triple slash directive, specifically importing types | `/// <reference types="./ts-thing-types" />` |
| triple-slash-amd-dependency | with a triple slash directive, specifically declaring an AMD dependency | `/// <amd-dependency path="./ts-thing-types" />` |
| pre-compilation-only | but the dependency will disappear at runtime. See [preCompilationOnly](#preCompilationOnly) | `import { thing } from "./things"` // and continue to not use `thing` |
| 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 | `/// <amd-dependency path="./ts-thing-types" />` |
| triple-slash-directive | with a triple slash directive (oldskool TypeScript) | |
| triple-slash-file-reference | with a triple slash directive, specifically importing another module | `/// <reference path="./ts-thing" />` |
| triple-slash-type-reference | with a triple slash directive, specifically importing types | `/// <reference types="./ts-thing-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`

Expand Down
7 changes: 3 additions & 4 deletions src/cache/cache.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import { scannableExtensions } from "#extract/transpile/meta.mjs";
import { bus } from "#utl/bus.mjs";

/**
* @typedef {import("../../types/dependency-cruiser.mjs").IRevisionData} IRevisionData
* @typedef {import("../../types/strict-options.mjs").IStrictCruiseOptions} IStrictCruiseOptions
* @typedef {import("../../types/dependency-cruiser.mjs").ICruiseResult} ICruiseResult
* @typedef {import("../../types/cache-options.mjs").cacheStrategyType} cacheStrategyType
* @import { IRevisionData, ICruiseResult } from "../../types/dependency-cruiser.mjs";
* @import { IStrictCruiseOptions } from "../../types/strict-options.mjs";
* @import { cacheStrategyType } from "../../types/cache-options.mjs";
*/

const CACHE_FILE_NAME = "cache.json";
Expand Down
Loading