Skip to content

Commit

Permalink
🐣 adds an option to limit the cruise depth
Browse files Browse the repository at this point in the history
  • Loading branch information
sverweij committed Oct 25, 2017
1 parent 4df10c7 commit 2b17885
Show file tree
Hide file tree
Showing 22 changed files with 166 additions and 5 deletions.
2 changes: 2 additions & 0 deletions bin/dependency-cruise
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ program
.option("-f, --output-to <file>", `file to write output to; - for stdout
(default: -)`)
.option("-x, --exclude <regex>", "a regular expression for excluding modules")
.option("-d, --max-depth <n>", `the maximum depth to cruise; 0 <= n <= 99
(default: 0, which means 'infinite depth')`)
.option("-M, --system <items>", `list of module systems (default: amd,cjs,es6)`)
.option("-T, --output-type <type>", `output type - html|dot|err|json
(default: err)`)
Expand Down
2 changes: 2 additions & 0 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ These are all the options:
should adhere to the [ruleset schema](../src/validate/jsonschema.json)
exclude : regular expression describing which dependencies the function
should not cruise
maxDepth : the maximum depth to cruise; 0 <= n <= 99
(default: 0, which means 'infinite depth')
system : an array of module systems to use for following dependencies;
defaults to ["es6", "cjs", "amd"]
outputType : one of "json", "html", "dot", "csv" or "err". When left
Expand Down
26 changes: 25 additions & 1 deletion doc/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Running with no parameters gets you help:
-f, --output-to <file> file to write output to; - for stdout
(default: -)
-x, --exclude <regex> a regular expression for excluding modules
-d, --max-depth <n> the maximum depth to cruise; 0 <= n <= 99
(default: 0, which means 'infinite depth')
-M, --system <items> list of module systems (default: amd,cjs,es6)
-T, --output-type <type> output type - html|dot|err|json
(default:err)
Expand Down Expand Up @@ -77,13 +79,35 @@ scanned:
dependency-cruise -x "node_modules" -T html -f deps-without-node_modules.html src
```

Beacuse it's regular expressions, you can do more interesting stuff here as well. To exclude
Because it's regular expressions, you can do more interesting stuff here as well. To exclude
all modules with a file path starting with coverage, test or node_modules, you could do this:

```sh
dependency-cruise -x "^(coverage|test|node_modules)" -T html -f deps-without-stuffs.html src
```

## `--max-depth`
Only cruise the specified depth, counting from the specified root-module(s). This
command is mostly useful in combination with visualisation output like _dot_ to
keep the generated output to a manageable size.

Although totally up to you I advise you to not use this with the `err` reporter;
you'll probably miss validating a dependency or two.

This will cruise the dependencies of each file directly in the src folder, up
to a depth of 3:
```sh
dependency-cruise --max-depth 3 -T dot src | dot -T svg > depth_three.svg
```

This will cruise the dependencies of the dependency-cruiser command up till 3
deep:
```sh
dependency-cruise -x node_modules --max-depth 3 -T dot src | dot -T png > dependency-cruiser-max-depth-3.png
```
Result:
![dependency-cruiser cruised with max depth 3](real-world-samples/dependency-cruiser-max-depth-3.png)

## `--validate`
Validates against a list of rules in a rules file. This defaults to a file
called `.dependency-cruiser.json`, but you can specify your own rules file.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion src/cli/normalizeOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ module.exports = (pOptions) => {
exclude: "",
outputTo: "-",
outputType: "err",
system: DEFAULT_MODULE_SYSTEMS
system: DEFAULT_MODULE_SYSTEMS,
maxDepthSpecified: false
});

pOptions.moduleSystems = normalizeModuleSystems(pOptions.system);
Expand All @@ -48,5 +49,12 @@ module.exports = (pOptions) => {

pOptions.validate = pOptions.hasOwnProperty("validate");

if (pOptions.hasOwnProperty("maxDepth")) {
pOptions.maxDepth = parseInt(pOptions.maxDepth, 10);
pOptions.maxDepthSpecified = pOptions.maxDepth > 0;
} else {
pOptions.maxDepth = 0;
}

return pOptions;
};
10 changes: 10 additions & 0 deletions src/cli/validateParameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const safeRegex = require('safe-regex');

const MODULE_SYSTEM_LIST_RE = /^((cjs|amd|es6)(,|$))+$/gi;
const OUTPUT_TYPES_RE = /^(html|dot|csv|err|json)$/g;
const VALID_DEPTH_RE = /^[0-9]{1,2}$/g;

function validateFileExistence(pDirOrFile) {
try {
Expand Down Expand Up @@ -49,12 +50,21 @@ function validateValidation(pOptions) {
}
}

function validateMaxDepth(pDepth) {
if (Boolean(pDepth) && !(pDepth.match(VALID_DEPTH_RE))) {
throw Error(
`'${pDepth}' is not a valid depth - use an integer between 0 and 99`
);
}
}

module.exports = (pFileDirArray, pOptions) => {
pFileDirArray.forEach(validateFileExistence);
if (Boolean(pOptions)) {
validateSystems(pOptions.system);
validateExcludePattern(pOptions.exclude);
validateOutputType(pOptions.outputType);
validateMaxDepth(pOptions.maxDepth);
validateValidation(pOptions);
}
};
10 changes: 7 additions & 3 deletions src/extract/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,22 @@ const gather = require('./gatherInitialSources');
const summarize = require('./summarize');
const addValidations = require('./addValidations');

function extractRecursive (pFileName, pOptions, pVisited) {
function extractRecursive (pFileName, pOptions, pVisited, pDepth) {
pOptions = pOptions || {};
pDepth = pDepth || 0;
pVisited.add(pFileName);

const lDependencies = extract(pFileName, pOptions);
const lDependencies = (!pOptions.maxDepthSpecified || pDepth < pOptions.maxDepth)
? extract(pFileName, pOptions)
: [];

return lDependencies
.filter(pDep => pDep.followable)
.reduce(
(pAll, pDep) => {
if (!pVisited.has(pDep.resolved)){
return pAll.concat(
extractRecursive(pDep.resolved, pOptions, pVisited)
extractRecursive(pDep.resolved, pOptions, pVisited, pDepth + 1)
);
}
return pAll;
Expand Down Expand Up @@ -84,6 +87,7 @@ function makeOptionsPresentable(pOptions) {
"rulesFile",
"outputTo",
"exclude",
"maxDepth",
"system",
"outputType",
"prefix"
Expand Down
4 changes: 4 additions & 0 deletions src/extract/jsonschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@
"type": "string",
"description": "The regular expression used for excluding modules from being cruised"
},
"maxDepth": {
"type": "number",
"description": "The maximum cruise depth specified. 0 means no maximum specified"
},
"system": {
"type": "array",
"items": {"$ref": "#/definitions/ModuleSystemType" }
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/cjs.dir.filtered.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
"optionsUsed": {
"outputTo": "test/cli/output/cjs.dir.filtered.json",
"exclude": "node_modules",
"maxDepth": 0,
"system": [
"amd",
"cjs",
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/cjs.dir.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@
"optionsUsed": {
"outputTo": "test/cli/output/cjs.dir.json",
"exclude": "",
"maxDepth": 0,
"system": [
"amd",
"cjs",
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/cjs.dir.stdout.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@
"optionsUsed": {
"outputTo": "-",
"exclude": "",
"maxDepth": 0,
"system": [
"amd",
"cjs",
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/cjs.file.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
"optionsUsed": {
"outputTo": "test/cli/output/cjs.file.json",
"exclude": "",
"maxDepth": 0,
"system": [
"amd",
"cjs",
Expand Down
1 change: 1 addition & 0 deletions test/cli/fixtures/multiple-in-one-go.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"optionsUsed": {
"outputTo": "test/cli/output/multiple-in-one-go.json",
"exclude": "",
"maxDepth": 0,
"system": [
"amd",
"cjs",
Expand Down
42 changes: 42 additions & 0 deletions test/cli/normalizeOptions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ describe("normalizeOptions", () => {
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: ["amd", "cjs", "es6"],
moduleSystems: ["amd", "cjs", "es6"],
validate: false
Expand All @@ -26,6 +28,8 @@ describe("normalizeOptions", () => {
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: "cjs,es6",
moduleSystems: ["cjs", "es6"],
validate: false
Expand All @@ -41,6 +45,8 @@ describe("normalizeOptions", () => {
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: {},
moduleSystems: ["amd", "cjs", "es6"],
validate: false
Expand All @@ -56,6 +62,8 @@ describe("normalizeOptions", () => {
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: ["amd", "cjs", "es6"],
moduleSystems: ["amd", "cjs", "es6"],
rulesFile: ".dependency-cruiser.json",
Expand All @@ -71,12 +79,46 @@ describe("normalizeOptions", () => {
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: ["amd", "cjs", "es6"],
moduleSystems: ["amd", "cjs", "es6"],
rulesFile: "./fixtures/rules.empty.json",
validate: true
}
);
});
it("--max-depth '0' keeps maxDepthSpecified on false", () => {
expect(
normalizeOptions({maxDepth: "0"})
).to.deep.equal(
{
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 0,
maxDepthSpecified: false,
system: ["amd", "cjs", "es6"],
moduleSystems: ["amd", "cjs", "es6"],
validate: false
}
);
});
it("--max-depth '7' switches maxDepthSpecified to true. And parseInt's to 7", () => {
expect(
normalizeOptions({maxDepth: "7"})
).to.deep.equal(
{
exclude: "",
outputTo: "-",
outputType: "err",
maxDepth: 7,
maxDepthSpecified: true,
system: ["amd", "cjs", "es6"],
moduleSystems: ["amd", "cjs", "es6"],
validate: false
}
);
});

});
41 changes: 41 additions & 0 deletions test/cli/validateParameters.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,47 @@ describe("validateParameters", () => {
} catch (e) {
expect("not to be here").to.equal(`still here, though: ${e}`);
}
});

it("throws when a non-integer is passed as maxDepth", () => {
try {
validateParameters(["test/cli/fixtures"], {"maxDepth": "not an integer"});
expect("not to be here").to.equal("still here, though");
} catch (e) {
expect(e.toString()).to.deep.equal(
"Error: 'not an integer' is not a valid depth - use an integer between 0 and 99"
);
}
});

it("throws when > 99 is passed as maxDepth", () => {
try {
validateParameters(["test/cli/fixtures"], {"maxDepth": "101"});
expect("not to be here").to.equal("still here, though");
} catch (e) {
expect(e.toString()).to.deep.equal(
"Error: '101' is not a valid depth - use an integer between 0 and 99"
);
}
});

it("throws when < 0 is passed as maxDepth", () => {
try {
validateParameters(["test/cli/fixtures"], {"maxDepth": "-1"});
expect("not to be here").to.equal("still here, though");
} catch (e) {
expect(e.toString()).to.deep.equal(
"Error: '-1' is not a valid depth - use an integer between 0 and 99"
);
}
});

it("passes when a valid depth is passed as maxDepth", () => {
try {
validateParameters(["test/cli/fixtures"], {"maxDepth": "42"});
expect("to be here without throws happening").to.equal("to be here without throws happening");
} catch (e) {
expect("not to be here").to.equal(`still here, though: ${e}`);
}
});
});
7 changes: 7 additions & 0 deletions test/extract/fixtures/maxDepth/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ping from "./oneDeep";
import pingTwo from "./oneAndTwoDeep";
import pingSub from "./sub/oneDeepInSub";
import "os";

export const version = "1.0.0";
export const cpus = os.cpus().length;
1 change: 1 addition & 0 deletions test/extract/fixtures/maxDepth/oneAndTwoDeep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ping = "ping one and two";
4 changes: 4 additions & 0 deletions test/extract/fixtures/maxDepth/oneDeep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as twoDeep from "./twoDeep";
import * as oneAndTwoDeep from "./oneAndTwoDeep";

export const ping = "ping";
3 changes: 3 additions & 0 deletions test/extract/fixtures/maxDepth/sub/oneDeepInSub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {caramba} from "./twoDeepInSub";

export const ping = "oneDeepInSub";
1 change: 1 addition & 0 deletions test/extract/fixtures/maxDepth/sub/threeDeepInSub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const os = require('os');
1 change: 1 addition & 0 deletions test/extract/fixtures/maxDepth/sub/twoDeepInSub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const threeDeep = require('./threeDeepInSub');
2 changes: 2 additions & 0 deletions test/extract/fixtures/maxDepth/twoDeep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const threeDeep = require('./sub/threeDeepInSub');
export default const twoDeep = "two deep";

0 comments on commit 2b17885

Please sign in to comment.