# Dependency cruiser rules - reference ## Introduction - This is a small reference guide to the elements you can use to write rules for dependency-cruiser. If you want a step-by-step introduction check the [rules _tutorial_](./rules-tutorial.md). - There is a [json schema](../src/schema/configuration.schema.json) that describes the output format. Dependency-cruiser checks rule sets against it. - Some examples: - dependency-cruiser's [own configuration](../.dependency-cruiser.json) - the configuration [State Machine cat](https://state-machine-cat.js.org) uses [for validation](https://github.com/sverweij/state-machine-cat/blob/develop/config/dependency-cruiser.js) and the one it uses [for generating a visual graph](https://github.com/sverweij/state-machine-cat/blob/develop/config/dependency-cruiser-graph.js). - [mscgen.js](https://mscgen.js.org)'s [.dependency-cruiser.js](https://github.com/mscgenjs/mscgenjs-core/blob/develop/.dependency-cruiser.js) - Tip: run `depcruise --init` to create a .dependency-cruiser.js with some rules that make sense in most projects. ## Contents 1. [The structure of a dependency-cruiser configuration](#the-structure-of-a-dependency-cruiser-configuration) - [`forbidden`](#forbidden) - [`allowed`](#allowed) - [`allowedSeverity`](#allowedSeverity) - [`required`](#required) - [`extends`](#extends) - [`options`](#options) 2. [The structure of an individual rule](#the-structure-of-an-individual-rule) 3. [Conditions](#conditions) - [`path`](#path) - [`pathNot`](#pathnot) - [path specials](#path-specials) - [`orphan`](#orphans) - [`reachable`](#reachable---detecting-dead-wood-and-transient-dependencies) - [`couldNotResolve`](#couldnotresolve) - [rules on dependents - `numberOfDependentsLessThan`](#rules-on-dependents---numberOfDependentsLessThan) - [rules on dependents - `numberOfDependentsMoreThan`](#rules-on-dependents---numberOfDependentsMoreThan) - [`circular`](#circular) - [`ancestor`](#ancestor) - [`license` and `licenseNot`](#license-and-licensenot) - [`dependencyTypes` and `dependencyTypesNot`](#dependencytypes-and-dependencytypesnot) - [`dynamic`](#dynamic) - [`moreThanOneDependencyType`](#more-than-one-dependencytype-per-dependency-morethanonedependencytype) - [`exoticallyRequired`, `exoticRequire` and `exoticRequireNot`](#exoticallyrequired-exoticrequire-and-exoticrequirenot) - [`preCompilationOnly`](#precompilationonly) - [`moreUnstable`](#moreunstable) 4. [Configurations in JavaScript](#configurations-in-javascript) ## The structure of a dependency cruiser configuration The typical dependency-cruiser config is either json file or a JavaScript file that exports a simple JavaScript object (see [Configurations in JavaScript below](#configurations-in-javascript)). The most important sections in the config are `forbidden`, `allowed`, `required` and `options`, so a skeleton config could look something like this: ```json { "forbidden": [], "allowed": [], "required": [], "options": {} } ``` The following paragraphs explain these as well as other sections. ### `forbidden` A list of rules that describe dependencies that are not allowed. dependency-cruiser will emit a separate error (warning/ informational) message for each violated rule. ### `allowed` A list of rules that describe dependencies that are _allowed_. dependency-cruiser will emit a 'not-in-allowed' message for each dependency that does not satisfy at least one of them. The severity of the message is _warn_ by default, but you can override it with `allowedSeverity` ### `allowedSeverity` The severity to use in reports when a dependency is not in the `allowed` list of rules. It takes the same values as other `severity` fields and also defaults to `warn`. ### `required` A list of rules that describe what dependencies modules _must_ have, like 'every controller _must depend on_ the base controller'. 'Required' rules have slightly different semantics from the `forbidden` and `allowed` types. There's a mandatory `module` attribute that specifies which modules the rule applies to and the `to` describes what dependencies that module should exactly have. To check for _direct_ dependencies (controllers must directly depend on the base controller): ```javascript { required: [ { name: "controllers-inherit-from-base", comment: "Each controller should inherit from the framework's base controller", module: { // every module that matches the pattern specified in path & pathNot ... path: "-controller[.]js$", pathNot: "framework/base-controller[.]js$", }, to: { // ... must depend at least once on the framework's base controller path: "^src/framework/base-controller[.]js$", }, }, ]; } ``` To check for _indirect_ ('transitive') dependencies (controllers must depend on the base controller, either directly or via other modules) add the `reachable` attribute to `to` part of the rule, like so: ```javascript { required: [ { name: "controllers-transitively-inherit-from-base", comment: "Each controller should inherit from the framework's base controller", module: { // every module that matches the pattern specified in path & pathNot ... path: "-controller[.]js$", pathNot: "framework/base-controller[.]js$", }, to: { // ... must depend at least once on the framework's base controller path: "^src/framework/base-controller[.]js$", // ... either directly or indirectly reachable: true, }, }, ]; } ``` > `reachable: false` is the same as leaving the reachable attribute out; so it > does 'nothing'. ### `extends` This takes one or more file path to other dependency-cruiser-configs. When dependency-cruiser reads your config, it takes the contents of the `extends` and merges them with the contents of your config. #### File resolution dependency-cruiser resolves the `extends` relative to the file name with the same algorithm node uses, which means a.o. - names starting with `./` are local - you can use external node_modules to reference rule sets (e.g. `dependency-cruiser/configs/recommended`) - there's no need to specify the extension for JavaScript files, but for json it's mandatory. #### How rules are merged - `allowed` rules Dependency-cruiser concatenates `allowed` rules from the extends, and de-duplicates them. - `forbidden` and `required` rules For `forbidden` and `required` rules it uses the same approach, except when the rules have a name, in which case the rule with the same name in the current file gets merged into the one from extends, where attributes from the current file win. This allows you to override only one attribute, e.g. the severity - `allowedSeverity` If there's an `allowedSeverity` in the current file, it wins. If neither file has an `allowedSeverity` dependency-cruiser uses _warn_ as a default - `options` `options` get the Object.assign treatment - where the option in the current file wins. - If there's more than one path in extends, they get merged into the current file one by one, running through the array left to right. #### Examples To use a local base config: ```json { "extends": "./configs/dependency-cruiser-base.json" } ``` To use a base config from an npm package: ```json { "extends": "@ourcompany/dependency-cruiser-configs/frontend-rules-base.json" } ``` ```js module.exports = { extends: "dependency-cruiser/configs/recommended", forbidden: [ { // because we still use a deprecated core module, still let // the no-deprecated-core rule from recommended fire, // but at least temporarily don't let it break our build // by setting the severity to "warn" here name: "no-deprecated-core", severity: "warn", // no need to specify the from and to, because they're already // defined in 'recommended' }, ], }; ``` ### `options` Options that influence what is cruised, and how it is cruised. See the [options reference](./options-reference.md) for an exhaustive list. ## The structure of an individual rule A rule consists at least of a `from` and a `to` attribute that contain one or more conditions that trigger the rule, so a minimal rule will look like this: ```json { "from": {}, "to": {} } ``` A rule within the 'allowed' section can also have a `comment` attribute which you can use to describe the rule. Rules within the 'forbidden' section can have a `name` and a `severity`. ```json { "name": "kebab-cased-name", "comment": "(optional) description of the rule", "severity": "warn", "from": {}, "to": {} } ``` ### `from` and `to` Conditions an end of a dependency should match to be caught by this rule. Leave it empty if you want any module to be matched. The [conditions](#conditions) section below describes them all. ### `comment` You can use this field to document why the rule is there. It's not used in any rule logic. ### `name` > (only available in the `forbidden` section ) A short name for the rule - will appear in reporters to enable customers to quickly identify a violated rule. Try to keep them short, eslint style. E.g. 'not-to-core' for a rule forbidding dependencies on core modules, or 'not-to-unresolvable' for one that prevents dependencies on modules that probably don't exist. If you do not provide a name, dependency-cruiser will default it to `unnamed`. ### `severity` > (only available in the `forbidden` section ) How severe a violation of a rule is. The 'error' severity will make some reporters (at least the `err` one) return a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated: use that. The other values you can use are `info`, `warn` and `ignore`. If you leave it out dependency-cruiser will assume it to be `warn`. With the severity set to `ignore` dependency-cruiser will not check the rule at all. This can be useful if you want to temporarily disable a rule or disable a rule you inherited from a rule set you extended. > If you want to set the severity for the 'allowed' rules you can > use [`allowedSeverity`](#allowedSeverity) ### `scope` - enable rules to apply on folders instead of modules What to apply the rule to - either `module` (the default) or `folder`. For many rules this makes no difference. For these rules we advise to not include the `scope` attribute at all. There are two notable exceptions where it _does_ make a difference: - rules regarding Instability metrics A folder can contain a highly instable module (e.g. an index.js barrel that depends on everything and their mother within the module), but be stable itself e.g. because there's nothing in the folder that depends on modules outside it. - rules regarding circular dependencies For example, when `main/index.js` depends on `utl/helper.js` and `utl/helper.js` depends on `main/utl.js` there is no circular dependency on module level, but there _is_ one between the `main` and `utl` folders > [!NOTE] > at this time only the `moreUnstable`, `circular` and `path`/ `pathNot` > attributes (including 'group matching') work, so it is > possible to check the "stable dependencies principle" on folder level. Other > attributes (including, but not limited to _via_) > still have to be implemented (after release 11.7.0) ## Conditions ### `path` A regular expression an end of a dependency should match to be caught by this rule. In `from`, this is the path from the current working directory (typically your project root) to the file containing a dependency. In `to`, this is the path from the current working directory to the file the dependency resolves to. When path is in a `to` part of a rule it accepts the regular expression 'group matching' special variables `$0`, `$1`, `$2`, ... as well. See 'group matching' below for an explanation & example. ### `pathNot` A regular expression an end of a dependency should NOT match to be caught by this rule. When pathNot is in a `to` part of a rule it accepts the regular expression 'group matching' special variables `$0`, `$1`, `$2`, ... just like the path attribute. See 'group matching' below for an explanation & example. ### path specials #### regular expressions - not globs I chose _regular expressions_ for matching paths over the more traditional _glob_ because they're more expressive - which makes it easier to specify rules. Some common patterns | glob | regular expression | this expresses: | | -------------- | ------------------ | ----------------------------------------------------------------------------------------------------- | | `*.js` | `[^/]+\.js$` | files in the current folder with the extension _.js_ | | `src/**/*` | `^src` | all files in the _src_ folder | | _not possible_ | `^src/([^/]+)/.+` | everything in the src tree - remember the matched folder name directly under src for later reference. | #### forward slashes To make sure rules you specify run on all platforms, dependency-cruiser internally represents paths with forward slashes as path separators (`src/alez/houpe`). #### 'group matching' Sometimes you'll want to use a part of the path the 'from' part of your rule matched and use it in the 'to' part. E.g. when you want to prevent stuff in the same folder to be matched. To achieve this you'll need to do two things: - In the `from` of your rule: Make sure the part of the `path` you want to be matched is between brackets. Like so: `"^src/([^/]+)/.+"` - In the `to` part of your rule: You can reference the part matched between brackets by using `$1` in `path` and `pathNot` rules. Like so: `"pathNot": "^src/$1/.+".` - It is possible to use more than one group per rule as well. E.g. this expression `"^src/([^/]+)/[^\.]\.(.+)$"` has two groups; one for the folder directly under src, and one for the extension. The first is available in the `to` part of your rule with `$1`, the second with `$2`. - The special variable `$0` contains the _whole_ matched string. I haven't seen a practical use for it in the context of dependency-cruiser, but I'll be glad to be surprised. #### 'group matching' - an example: matching peer folders Say you have the following folder structure ``` src └── business-components    ├── search    ├── upsell    ├── check-out    ├── view-trip    └── check-in ``` Business components should be completely independent of each other. So typically you'd specify a rule like this to prevent accidents in the "forbidden" section: ```json { "name": "no-inter-ubc", "comment": "Don't allow relations between code in business components", "severity": "error", "from": { "path": "^src/business-components/([^/]+)/.+" }, "to": { "path": "^src/business-components/([^/]+)/.+" } } ``` This will correctly flag relations from one folder to another, but also relations _within_ folders. It's possible to get around that by specifying it for each folder explicitly, leaving the current 'from' folder from the to list e.g. _from: search, to: upsell|check-out|view-trip|check-in,_ _from: upsell, to: search|check-out|view-trip|check-in,_ _..._ That'll be heavy maintenance though; especially when your business components breed like a litter of rabbits. In stead, you can use group matching: ```json { "name": "no-inter-ubc", "comment": "Don't allow relations between business components", "severity": "error", "from": { "path": "^src/business-components/([^/]+)/.+" }, "to": { "path": "^src/business-components/([^/]+)/.+", "pathNot": "^src/business-components/$1/.+" } } ``` ... which makes sure dependency-cruiser does not match stuff in the from folder currently being matched. #### Using an array of regular expressions When your regular expressions grow bigger, you might want to express them as an array of regular expressions instead of one regular expression to improve legibility.
example ... ```javascript { from: { path: "(^src/report/)" }, to: { pathNot: "$1|^node_modules|^(path|package.json)$|^src/utl" } } ``` ```javascript { from: { path: "(^src/report/)" }, to: { pathNot: [ "$1", "^node_modules", "^(path|package.json)$", "^src/utl" ] } } ``` ![focus](assets/filtering/focus.svg)
### orphans A Boolean indicating whether or not to match modules that have no incoming and no outgoing dependencies. Orphans might need special attention because they're unused leftovers from a refactoring. Or the start of some feature that never got finished but which was merged anyway. Leaving the `orphan` attribute out means you don't care about orphans in your code. Detecting orphans will have an impact on performance. You will probably only notice it when you have a larger code base (thousands of modules in your dependency graph), but it is something to keep in mind. To detect orphan guys you can add e.g. this snippet to your .dependency-cruiser.js's `forbidden` section: ```json { "name": "no-orphans", "severity": "warn", "from": { "orphan": true }, "to": {} } ``` #### Usage notes - :bulb: dependency-cruiser will typically not find orphans when you give it only one module to start with. Any module it finds, it finds by following its dependencies, so each module will have at least one dependency incoming or outgoing. Specify one or more folder, several files or a glob. E.g. ``` depcruise -v -- src lib test ``` will find orphans if they exist, whereas ```sh depcruise -v -- src/index.ts ``` probably won't (unless index.ts is an orphan itself). - :bulb: by definition orphan modules have no dependencies. So when `orphan` is part of a rule, the `to` part won't make sense. This is why dependency-cruiser will ignore the `to` part of these rules. - :bulb: For similar reasons `orphan` is not allowed in the `to` part of rules. ### `reachable` - detecting dead wood and transient dependencies `reachable` is a Boolean indicating whether or not modules matching the `to` part of the rule are _reachable_ (either directly or via other modules) from modules matching the `from` part of the rule. This can be useful for two use cases: - [detect dead wood](#detect-dead-wood-with-reachable) - [prevent modules from being reached via via](#prevent-modules-from-being-reached-via-via-with-reachable) #### detect dead wood with `reachable` For instance, in this dependency-graph several modules are not reachable from the root `index.js`. If `index.js` is the only (legal) entry to this package, those unreachable modules are likely candidates for removal: dependencies unreachable from src/index.js - reachable rule off Here's a rule snippet that will detect these for you: ```javascript { "forbidden": [ { "name": "no-unreachable-from-root", "severity": "error", "from": { "path": "src/index\\.js$" }, "to": { "path": "src", /* spec files shouldn't be reachable from regular code anyway, so you might typically want to exclude these from reachability rules. The same goes for typescript definition files: */ "pathNot": "\\.spec\\.(js|ts)$|\\.d\\.ts$" /* for each file matching path and pathNot, check if it's reachable from the modules matching the criteria mentioned in "from" */ "reachable": false } } ] } ``` With this rule enabled, the unreachable rules jump out immediately. Both in the output of the `err` reporter ```sh error no-unreachable-from-root: src/other-stuff/index.js error no-unreachable-from-root: src/other-stuff/untouched-one.js error no-unreachable-from-root: src/other-stuff/untouched-two.js error no-unreachable-from-root: src/relevant/to-untouched.js ✖ 4 dependency violations (4 errors, 0 warnings). 8 modules cruised. ``` ... and in the output of the `dot` one: dependencies unreachable from src/index.js - reachable rule on #### Prevent modules from being reached via via with `reachable` You can use the same `reachable` attribute to find transient dependencies (fancy way to say _via via_). Let's say you have a bunch of JavaScript files that define static schemas. It's OK if they import stuff, but they should _never_ touch database implementation code (which happens to live in `src/lib/database`). With a rule like this in the `forbidden` section you can make sure that never happens: ```json { "name": "implementation-not-reachable-from-info-ts", "comment": "Don't allow importing database implementation files for schema declaration files", "severity": "error", "from": { "path": "\\.schema\\.ts$" }, "to": { "path": "^src/libs/database/", "reachable": true } } ``` #### Usage notes - :bulb: You can set up multiple rules with a `reachable` attribute in the `to` section. If you do so, make sure you give a `name` to each rule. It's not only the only way dependency-cruiser can keep reachable rules apart - it will be for you as well :-). - :bulb: Different from other rules, rules with a `reachable` attribute can only have - `path` and `pathNot` in the `from` part of the rule - `path` and `pathNot` alongside the `reachable` in the `to` part of the rule (these limitations might get lifted somewhere in the future) ### rules on dependents - `numberOfDependentsLessThan` Matches when the number of dependents of a module is less than the provided number. Useful to detect whether modules are 'shared' enough to your liking, or whether they're actually used in the first place. E.g. to flag modules in the `shared` folder that are only used from the `features` folder once (or not a all), you can use a rule like this in the `forbidden` section ```javascript { name: "no-unshared-in-shared", from: { path: "^features/" }, module: { path: "^shared/", numberOfDependentsLessThan: 2 } } ``` #### Usage notes - :bulb: Currently rules on dependents only work within the `forbidden` context. - :bulb: In the `from` part `path` and `pathNot` attributes work, but none other. - :bulb: Similarly the `to` part of the rule can (next to the `numberOfDependentsLessThan` attribute) also only use `path` and `pathNot`. - :bulb: You can't use group matching with this rule.
why? Unlike regular dependency rules this rule will not match one module at a time, but a whole bunch of them. With one match (i.e. `src/coolstuff/ice.ts` and a a regular expression with a group (`/^src/(^[/]+)/`) the `$1` variable can only mean one thing (`coolstuff`). With more than one result (`src/coolstuff/ice.ts, src/hotstuff/pepper.ts`) there's also more than one thing `$1` means - making use in e.g. `to.path` ambiguous.
### rules on dependents - `numberOfDependentsMoreThan` Matches when the number of dependents of a module is more than the provided number. Useful to - detect whether modules are 'shared' enough to your liking, or whether they're actually used in the first place, but you want or need to enforce that in an allowed rule - put a maximum number on the number of dependents a module can have (not sure it's like super useful or anything, it's just a side-effect of the previous use) E.g. to flag modules in the `shared` folder that are only used from the `features` folder once (or not a all), you can use a rule like this in the `allowed` section ```javascript { name: "no-unshared-in-shared", from: { path: "^features/" }, module: { path: "^shared/", numberOfDependentsMoreThan: 1 } } ``` The same usage notes and caveats apply as do for the numberOfDependentsLessThan attribute. ### `couldNotResolve` Whether or not to match modules dependency-cruiser could not resolve (and probably aren't on disk). For this one too: leave out if you don't care either way. To get an error for each unresolvable dependency, put this in your "forbidden" section: ```json { "name": "not-to-unresolvable", "severity": "error", "from": {}, "to": { "couldNotResolve": true } } ``` ### `circular` A Boolean indicating whether or not to match module dependencies that end up where you started (a.k.a. circular dependencies). Leaving this out => you don't care either way. For example, adding this rule to the "forbidden" section in your .dependency-cruiser.js will issue a warning for each dependency that ends up at itself. ```json { "name": "no-circular", "severity": "warn", "from": { "pathNot": "^(node_modules)" }, "to": { "circular": true } } ``` #### `via` and `viaOnly`- restricting what cycles to match There are two attributes to put restrictions on through which modules cycles are allowed to pass: - `via`: matches against _some_ of the modules in the cycle - `viaOnly`: matches against _all_ of the modules in the cycle Both take the `path`, `pathNot` and `dependencyTypes`, `dependencyTypesNot` attributes that have a meaning similar to the ones in the `from` and `to` parts of a rule, with dependencyTypes expressing the type of relation a module has with its predecessor in the cycle. With the cycle `a/aa.js` -> `a/ab.js` -> `b/bb.js` -> `a/aa.js` the restrictions to this: | restriction | what it does | example input | match? | because... | | -------------- | -------------------------------------------------------- | --------------- | ------- | ----------------------------- | | `via` | **some** of the modules in the cycle match the condition | `path: "^a/.+"` | `true` | `a/aa.js` and `a/ab.js` match | | `viaOnly` | **all** of the modules in the cycle match the condition | `path: "^a/.+"` | `false` | `b/bb.js` doesn't match | | _`viaNot`_ | _deprecated - use `viaOnly.pathNot` in stead_ | `path: "^a/.+"` | `false` | `a/aa.js` and `a/ab.js` match | | _`viaSomeNot`_ | _deprecated - use `via.pathNot` in stead_ | `path: "^a/.+"` | `true` | `b/bb.js` doesn't match | ##### Example: allow cycles that have a type-only dependency in them If you're only interested in cycles at run time, you might allow dependencies that are only used at compile time to be part of a cycle. `type-only` ones are a good example of that. E.g. at run time this cycle, where `r.ts` imports some things from `s.ts` as 'type-only': ```mermaid flowchart LR p.ts --> q.ts --> r.ts --> |type-only| s.ts --> p.ts ``` ... would look like this at run time (so: not a cycle): ```mermaid flowchart LR p.ts --> q.ts --> r.ts s.ts --> p.ts ``` To express this in a rule, you can use the `viaOnly` attribute: ```javascript // log an error for all circular dependencies which consist of only non-`type-only` // dependencies { name: 'no-circular-at-runtime', severity: 'error', from: { }, to: { circular: true, viaOnly: { dependencyTypesNot: ['type-only'] } } }, ``` ##### Example: exclude cycles from erroring when they go to a known 'knot' Some codebases include a lot of circular dependencies, sometimes with a few 'knots' (typically barrel files) that partake in most of them. Fixing these cycles might take a spell, so you might want to (temporarily :-) ) exclude them from breaking the build. At the same time you might want to prevent any new violation going unnoticed because of this. The recommended solution for this is dependency-cruiser's [`ignore-known`](cli.md#--ignore-known-ignore-known-violations) mechanism. However, you can also use the `via` attribute. In this example `app/javascript/tables/index.ts` and `app/javascript/tables/index.ts` are the known 'knots': ```javascript // log an error for all circular dependencies except when they are via one of the // known 'knots' { name: 'no-circular', severity: 'error', from: { }, to: { circular: true, via: { pathNot: [ '^app/javascript/tables/index.ts', '^app/javascript/ui/index.tsx', ] } } }, // for circular dependencies that pass through one of the knots, still generate // a warning { name: 'no-circular (exception for known barrels)', severity: 'warn', from: { }, to: { circular: true, via: { path: [ '^app/javascript/tables/index.ts', '^app/javascript/ui/index.tsx', ] } } } ``` ##### Example: prevent cycles from going outside a folder This example (adapted from a [question on GitHub](https://github.com/sverweij/dependency-cruiser/issues/585) by [@PetFeld-ed](https://github.com/PetFeld-ed)) not only uses the `via`, but also displays [group matching](#group-matching). ```javascript // in the `forbidden` section of a dependency-cruiser config: { name: 'no-circular-dependency-of-modules', comment: 'If a module in business component A depends on one in component B, then ' + 'the module in component B should not depend on that component in module A ' + 'This is also forbidden if the dependency is transitive. ', "I.a.w.: if there's a cycle, it should stay within the same component " + severity: 'error', from: { path: '^src/business-components/([^/]+)/.+' }, to: { circular: true, via: { pathNot: '^src/business-components/$1/.+' }, // previously written with the now deprecated viaSomeNot attribute: // viaSomeNot: '^src/business-components/$1/.+' }, } ``` ### `ancestor` Whether or not to match modules that are in a folder above the folder of the module. This can be useful to prevent modules in a 'lower' folder to depend on modules in a 'higher' folder. ```javascript // in the `forbidden` section of a dependency-cruiser config: { name: "no-ancestor", comment: "don't allow importing from folders above the current one", severity: "error", from: {}, to: { "ancestor": true } } ``` For example, applied to the following dependency graph: ![ancestor](assets/sample-ancestor.svg) ### `license` and `licenseNot` You can flag dependent modules that have licenses that are e.g. not compatible with your own license or with the policies within your company with `license` and `licenseNot`. Both take a regular expression that matches against the license string that goes with the dependency. E.g. to forbid GPL and APL licenses (which require you to publish your source code - which will not always be what you want): ```json { "name": "no-gpl-apl-licenses", "severity": "error", "from": {}, "to": { "license": "GPL|APL" } } ``` This raise an error when you use a dependency that has a string with GPL or APL in the "license" attribute of its package.json (e.g. [SPDX](https://spdx.org) compatible expressions like `GPL-3.0`, `APL-1.0` and `MIT OR GPL-3.0` but also on non SPDX compatible) To only allow licenses from an approved list (e.g. a greenlist provided by your legal department): ```json { "name": "only-licenses-approved-by-legal", "severity": "warn", "from": {}, "to": { "licenseNot": ["MIT", " ISC"] } } ``` > Just with _path_ and _pathNot_ you can pass an array of regular expressions > as well if you think that's more legible. > [!NOTE] > dependency-cruiser can help out a bit here, but you remain responsible > for managing your own legal stuff. To re-iterate what is in the > [LICENSE](../LICENSE) to dependency-cruiser: > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > SOFTWARE. ### `dependencyTypes` and `dependencyTypesNot` You might have spent some time wondering why something works on your machine, but not on other's. Only to discover you _did_ install a dependency, but _did not_ save it to package.json. Or you already had it in your devDependencies and started using it in a production source. To save you from embarrassing moments like this, you can make rules with the `dependencyTypes` restriction. E.g. to prevent you accidentally depend on a `devDependency` from anything in `src` add this to your .dependency-cruiser.js's "forbidden" section: ```json { "name": "not-to-dev-dep", "severity": "error", "comment": "because an npm i --production will otherwise deliver an unreliably running package", "from": { "path": "^src" }, "to": { "dependencyTypes": ["npm-dev"] } } ``` Or to detect stuff you npm i'd without putting it in your package.json: ```json { "name": "no-non-package-json", "severity": "error", "comment": "because an npm i --production will otherwise deliver an unreliably running package", "from": { "pathNot": "^(node_modules)" }, "to": { "dependencyTypes": ["unknown", "undetermined", "npm-no-pkg", "npm-unknown"] } } ``` Likewise you can use the inverse `dependencyTypesNot` restriction. E.g. to ensure type-only imports (e.g. `import type { IYadda } from "./types"`) are used from `.d.ts` modules and/ or modules called `types.ts`: ```json { "name": "only-type-only", "comment": "use explicit 'type' imports to import from type declaration modules", "severity": "error", "from": {}, "to": { "path": ["types\\.ts$", "\\.d\\.ts$"], "dependencyTypesNot": ["type-only"] } } ``` If you don't specify dependencyTypes (or dependencyTypesNot) in a rule, dependency-cruiser will ignore them in the evaluation of that rule. #### OK - `unknown`, `npm-unknown`, `undetermined` - I'm officially weirded out - what's that about? This is a list of the main dependency types dependency-cruiser currently detects. | dependency type | meaning | example | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | local | a module in your own ('local') package | "./klont" | | localmodule | a module in your own ('local') package, but which was in the `resolve.modules` attribute of the webpack config you passed | "shared/stuff.ts" | | npm | it's a module in package.json's `dependencies` | "lodash" | | npm-dev | it's a module in package.json's `devDependencies` | "chai" | | npm-optional | it's a module in package.json's `optionalDependencies` | "livescript" | | npm-peer | it's a module in package.json's `peerDependencies` - note: deprecated in npm 3, but later on revived. | "thing-i-am-a-plugin-for" | | npm-bundled | it's a module that occurs in package.json's `bundle(d)Dependencies` array | "iwillgetbundled" | | npm-no-pkg | it's an npm module - but it's nowhere in your package.json | "forgetmenot" | | npm-unknown | it's an npm module - but there is no (parseable/ valid) package.json in your package | | | deprecated | it's an npm module, but the version you're using or the module itself is officially deprecated | "some-deprecated-package" | | core | it's a (nodejs) core module. B.t.w. you can [influence](./options-reference.md#builtinmodules-influencing-what-to-consider-built-in--core-modules) this | "fs", "node:test" | | unknown | it's unknown what kind of dependency type this is - probably because the module could not be resolved in the first place | "loodash" | | undetermined | the dependency fell through all detection holes. | "veloci!./raptor" | These are types that will typically occur alongside the ones above, telling a little bit more on how the dependency was declared. One or more of these can occur at the same time. E.g. if you import a 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` & `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` | | process-get-builtin-module | with (globalThis.)process.getBuiltinModule. As this is only possible with core modules it will typically appear next to the dependencyType 'core' | `const test = globalThis.process.getBuiltinModule("node:test")` | | 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` A Boolean that tells you whether the dependency is a dynamic one (i.e. it uses the async ES import statement a la `import('othermodule').then(pMod => pMod.doStuff())`). You can use this e.g. to restrict the usage of dynamic dependencies: ```json { "forbidden": [ { "name": "no-non-dynamic-dependencies", "severity": "error", "from": {}, "to": { "dynamic": true } } ] } ``` ... or to enforce the use of dynamic dependencies for certain dependencies ```json { "forbidden": [ { "name": "only-dyn-deps-to-otherside", "comment": "only dynamically depend on 'otherside' modules", "severity": "error", "from": {}, "to": { "path": "@theotherside/", "dynamic": false } } ] } ``` ### More than one dependencyType per dependency? `moreThanOneDependencyType` With the flexible character of package.json it's totally possible to specify a package more than once - e.g. both in the `peerDependencies` and in the `dependencies`. Sometimes this is intentional (e.g. to make sure a plugin type package works with both npm 2 and 3), but it can be a typo as well. Anyway, it's useful to be conscious about it. You can check for it with a `moreThanOneDependencyType` attribute - which matches these when set to true: ```json { "name": "no-duplicate-dep-types", "severity": "warn", "from": {}, "to": { "moreThanOneDependencyType": true } } ``` For this rule the `aliased`, `aliased-subpath-import`, `aliased-webpack`, `aliased-tsconfig` and `type-only` dependency types do not count towards the number of dependency types a dependency has, as these _by definition_ are always in addition to one of the other dependency types. When left out it doesn't matter how many dependency types a dependency has. (If you're more of an 'allowed' user: it matches the 0 and 1 cases when set to false). ### `exoticallyRequired`, `exoticRequire` and `exoticRequireNot` For exotic requires/ require wrappers you might want to have different rules a.c.t. normal requires. E.g. - When you use a require wrapper to include a dependency that might not be there and handle it elegantly, it's not an error if the module-to-be-there doesn't actually exist - or is e.g. in your optionalDependencies. - You might want to only allow the use of certain dependencies through an exotic require: ```json { "name": "not-to-optional-deps", "severity": "error", "from": {}, "to": { "dependencyTypes": ["npm-optional"], "exoticallyRequired": false } } ``` - ... or ban exotic requires altogether: ```json { "name": "ban-all-exotic-requires", "severity": "error", "from": {}, "to": { "exoticallyRequired": true } } ``` - Or allow only one specific: ```json { "name": "only-window-require-exotic", "severity": "error", "comment": "The only 'exotic' require allowed is window.require", "from": {}, "to": { "exoticRequireNot": "^window\\.require$", "exoticallyRequired": true } } ``` ### `preCompilationOnly` If you want to set restrictions on dependencies that only exist before compilation from TypeScript to JavaScript, you can use the (boolean) `preCompilation` only attribute. E.g. to make sure to only import stuff from the react-native stuff that doesn't make it beyond the pre-compilation step: ```json { "forbidden": [ { "name": "only-types-from-react-native", "description": "make sure to only import stuff that's portable over platforms", "severity": "error", "from": { "path": "^src/platform-independent-stuff" }, "to": { "path": "^src/lib/react-native-stuff", "preCompilationOnly": false } } ], "options": { "tsPreCompilationDeps": "specify" } } ``` > [!NOTE] > This attribute only works for TypeScript sources, and only when > [`tsPreCompilationDeps`](#tsprecompilationdeps) option is set to `"specify"`. ### `moreUnstable` When set to true moreUnstable matches for any dependency that has a higher Instability than the module that depends on it. When set to false it matches when the opposite is true; the dependency has an equal or lower Instability. This attribute is useful when you want to check against Robert C. Martin's stable dependencies principle: "depend in the direction of stability". Martin defines $Instability$ as a function of the number of dependents and dependencies a component has: $$Instability = {\\#dependencies \over \\#dependents + \\#dependencies}$$ E.g. a module with no dependencies and one or more dependents has a Instability of 0%; as stable as it can get. Conversely a module with no dependents, but with one or more dependencies is 100% Instable. Instability has a bit of an unusual connotation here - it's not 'bad' to be a 100% Instable module - it's only the nature of the module. A CLI or GUI component typically only depends on other modules and is 100% Instable. This is not a bad thing. Another way to look at Instability is how hard it is to change a module without consequences to other modules. Changing a 0% Instable module will typically have consequences for all its dependents. Making changes to a 100% Instable module will have consequences for itself only. To enforce/ find all modules that violate the stable dependencies principle (SDP) you can include a rule like this in the _forbidden_ section ```javascript module.exports = { forbidden: [ { name: "SDP", comment: "This module violates the 'stable dependencies' principle; it depends " + "on a module that is likely to be more prone to changes than it is " + "itself. Consider refactoring.", from: {}, to: { moreUnstable: true, }, }, ], }; ``` A note on using metrics: > ..., a metric is not a god; it is merely a measurement against an arbitrary > standard. It is certainly possible that the standard chosen in this chapter > is appropriate only for certain applications and not for others. > > Robert C. Martin - Agile Principles, Patterns and Practices in C# (2006) ## Configurations in JavaScript From version 4.7.0 you can pass a JavaScript module to `--validate`. It'll work as long as it exports a valid configuration object and node can understand it. This allows you to do all sorts of nifty stuff, like composing rule sets or using function predicates in rules. For example: ```javascript const subNotAllowed = require("rules/sub-not-allowed.json"); const noInterComponents = require("rules/sub-no-inter-components.json"); module.exports = { forbidden: [subNotAllowed, noInterComponents], options: { tsConfig: { fileName: "./tsconfig.json", }, }, }; ``` ### dot > If you're looking for configuring the `dot`, `archi` or `ddot` reporters, that > section has moved to the [options reference](./options-reference.md#reporteroptions).