Repository: sverweij/dependency-cruiser
Pull request #890 (2023-12-18 10:25:29)

feat(config-utl): makes possibly old format known violations forward compatible
## Description, Motivation and Context

- makes possibly old format known violations forward compatible directly after reading

... to prevent users from bumping into inexplicable errors. See the comment in 
extract-known-violations.mjs for details.


## How Has This Been Tested?

- [x] green ci
- [x] additional non-regression tests

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [ ] Refactor (non-breaking change which fixes an issue without changing functionality)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist

- [x] :book:

  - My change doesn't require a documentation update, or ...
  - it _does_ and I have updated it

- [x] :balance_scale:
  - The contribution will be subject to [The MIT license](https://github.com/sverweij/dependency-cruiser/blob/main/LICENSE), and I'm OK with that.
  - The contribution is my own original work.
  - I am ok with the stuff in [**CONTRIBUTING.md**](https://github.com/sverweij/dependency-cruiser/blob/main/.github/CONTRIBUTING.md).


codeclimate[bot]: Code Climate has analyzed commit d8d8782c and detected **0 issues** on this pull request.

View more on [Code Climate](https://codeclimate.com/github/sverweij/dependency-cruiser/pull/890).


Patch:
diff --git a/src/config-utl/extract-known-violations.mjs b/src/config-utl/extract-known-violations.mjs
index ac58ee61..b741b973 100644
--- a/src/config-utl/extract-known-violations.mjs
+++ b/src/config-utl/extract-known-violations.mjs
@@ -2,15 +2,55 @@ import { readFile } from "node:fs/promises";
 import json5 from "json5";
 import makeAbsolute from "./make-absolute.mjs";
 
+/**
+ * @typedef {import("../../types/violations.d.mjs").IViolation} IViolation
+ * @typedef {import("../../types/shared-types.d.mts").DependencyType} DependencyType
+ *
+ * @typedef {IViolation & {cycle: Array<{name: string, dependencyTypes: Array<DependencyType>}>} | {cycle: Array<DependencyType>}} IMaybeOldFormatViolation
+ */
+
+/**
+ * cycles went from Array<string> to Array<{name: string, dependencyTypes: Array<DependencyType>}>
+ * in version 16.0.0. Known violations would typically be still in the old
+ * format. To prevent customers from bumping into error messages and having
+ * to update or regenerate their known violations, we'll make the old format
+ * forward compatible with this.
+ *
+ * @param {IMaybeOldFormatViolation} pKnownViolation
+ * @returns {IViolation}
+ */
+function makeForwardCompatible(pKnownViolation) {
+  if (Boolean(pKnownViolation.cycle)) {
+    return {
+      ...pKnownViolation,
+      cycle: pKnownViolation.cycle.map((pModule) => {
+        if (Boolean(pModule.name)) {
+          return pModule;
+        }
+        return {
+          name: pModule,
+          dependencyTypes: [],
+        };
+      }),
+    };
+  }
+  return pKnownViolation;
+}
+
 export default async function extractKnownViolations(pKnownViolationsFileName) {
   try {
-    return json5.parse(
-      await readFile(makeAbsolute(pKnownViolationsFileName), "utf8")
+    const lFileContents = await readFile(
+      makeAbsolute(pKnownViolationsFileName),
+      "utf8",
     );
+    const lKnownViolations = json5.parse(lFileContents);
+    const lForwardCompatible = lKnownViolations.map(makeForwardCompatible);
+
+    return lForwardCompatible;
   } catch (pError) {
     if (pError instanceof SyntaxError) {
       throw new SyntaxError(
-        `'${pKnownViolationsFileName}' should be valid json\n          ${pError}`
+        `'${pKnownViolationsFileName}' should be valid json\n          ${pError}`,
       );
     }
     throw pError;


Test patch:
diff --git a/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-new-format.json b/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-new-format.json
new file mode 100644
index 00000000..130033b4
--- /dev/null
+++ b/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-new-format.json
@@ -0,0 +1,47 @@
+[
+  {
+    "type": "cycle",
+    "from": "tmp-errors/cycle-1.js",
+    "to": "tmp-errors/cycle-2.js",
+    "rule": {
+      "severity": "error",
+      "name": "no-circular"
+    },
+    "cycle": [
+      {
+        "name": "tmp-errors/cycle-2.js",
+        "dependencyTypes": ["local", "require"]
+      },
+      {
+        "name": "tmp-errors/cycle-3.js",
+        "dependencyTypes": ["local", "require"]
+      },
+      {
+        "name": "tmp-errors/cycle-4.js",
+        "dependencyTypes": ["local", "require"]
+      },
+      {
+        "name": "tmp-errors/cycle-1.js",
+        "dependencyTypes": ["local", "require"]
+      }
+    ]
+  },
+  {
+    "type": "module",
+    "from": "tmp-errors/mod.mts",
+    "to": "tmp-errors/mod.mts",
+    "rule": {
+      "severity": "error",
+      "name": "no-orphans"
+    }
+  },
+  {
+    "type": "dependency",
+    "from": "tmp-errors/call-mod.mts",
+    "to": "./mod.mjs",
+    "rule": {
+      "severity": "error",
+      "name": "not-to-unresolvable"
+    }
+  }
+]
diff --git a/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-old-format.json b/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-old-format.json
new file mode 100644
index 00000000..a3d38a72
--- /dev/null
+++ b/test/config-utl/__mocks__/known-violations/known-violations-with-cycles-in-old-format.json
@@ -0,0 +1,35 @@
+[
+  {
+    "type": "cycle",
+    "from": "tmp-errors/cycle-1.js",
+    "to": "tmp-errors/cycle-2.js",
+    "rule": {
+      "severity": "error",
+      "name": "no-circular"
+    },
+    "cycle": [
+      "tmp-errors/cycle-2.js",
+      "tmp-errors/cycle-3.js",
+      "tmp-errors/cycle-4.js",
+      "tmp-errors/cycle-1.js"
+    ]
+  },
+  {
+    "type": "module",
+    "from": "tmp-errors/mod.mts",
+    "to": "tmp-errors/mod.mts",
+    "rule": {
+      "severity": "error",
+      "name": "no-orphans"
+    }
+  },
+  {
+    "type": "dependency",
+    "from": "tmp-errors/call-mod.mts",
+    "to": "./mod.mjs",
+    "rule": {
+      "severity": "error",
+      "name": "not-to-unresolvable"
+    }
+  }
+]
diff --git a/test/config-utl/extract-known-violations.spec.mjs b/test/config-utl/extract-known-violations.spec.mjs
index 748e5e39..53f6f11a 100644
--- a/test/config-utl/extract-known-violations.spec.mjs
+++ b/test/config-utl/extract-known-violations.spec.mjs
@@ -55,4 +55,116 @@ describe("[I] config-utl/extractKnownViolations", () => {
       },
     ]);
   });
+
+  it("Makes a forward compatible version of the known violations", async () => {
+    process.chdir("./test/config-utl/__mocks__/known-violations");
+    deepEqual(
+      await extractKnownViolations(
+        "known-violations-with-cycles-in-old-format.json",
+      ),
+      [
+        {
+          type: "cycle",
+          from: "tmp-errors/cycle-1.js",
+          to: "tmp-errors/cycle-2.js",
+          rule: {
+            severity: "error",
+            name: "no-circular",
+          },
+          cycle: [
+            {
+              name: "tmp-errors/cycle-2.js",
+              dependencyTypes: [],
+            },
+            {
+              name: "tmp-errors/cycle-3.js",
+              dependencyTypes: [],
+            },
+            {
+              name: "tmp-errors/cycle-4.js",
+              dependencyTypes: [],
+            },
+            {
+              name: "tmp-errors/cycle-1.js",
+              dependencyTypes: [],
+            },
+          ],
+        },
+        {
+          type: "module",
+          from: "tmp-errors/mod.mts",
+          to: "tmp-errors/mod.mts",
+          rule: {
+            severity: "error",
+            name: "no-orphans",
+          },
+        },
+        {
+          type: "dependency",
+          from: "tmp-errors/call-mod.mts",
+          to: "./mod.mjs",
+          rule: {
+            severity: "error",
+            name: "not-to-unresolvable",
+          },
+        },
+      ],
+    );
+  });
+
+  it("Leaves new format cycles alone", async () => {
+    process.chdir("./test/config-utl/__mocks__/known-violations");
+    deepEqual(
+      await extractKnownViolations(
+        "known-violations-with-cycles-in-new-format.json",
+      ),
+      [
+        {
+          type: "cycle",
+          from: "tmp-errors/cycle-1.js",
+          to: "tmp-errors/cycle-2.js",
+          rule: {
+            severity: "error",
+            name: "no-circular",
+          },
+          cycle: [
+            {
+              name: "tmp-errors/cycle-2.js",
+              dependencyTypes: ["local", "require"],
+            },
+            {
+              name: "tmp-errors/cycle-3.js",
+              dependencyTypes: ["local", "require"],
+            },
+            {
+              name: "tmp-errors/cycle-4.js",
+              dependencyTypes: ["local", "require"],
+            },
+            {
+              name: "tmp-errors/cycle-1.js",
+              dependencyTypes: ["local", "require"],
+            },
+          ],
+        },
+        {
+          type: "module",
+          from: "tmp-errors/mod.mts",
+          to: "tmp-errors/mod.mts",
+          rule: {
+            severity: "error",
+            name: "no-orphans",
+          },
+        },
+        {
+          type: "dependency",
+          from: "tmp-errors/call-mod.mts",
+          to: "./mod.mjs",
+          rule: {
+            severity: "error",
+            name: "not-to-unresolvable",
+          },
+        },
+      ],
+    );
+  });
 });