diff --git a/package-lock.json b/package-lock.json index 18535fb..8d45eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -881,9 +881,9 @@ "dev": true }, "@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", + "version": "15.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.3.tgz", + "integrity": "sha512-SNt65CPCXvGNDZ3bvk1TQ0Qxoe3y1RKH88+wZ2Uf05dduBCqqFQ76ADP9pbT+Cpvj60SkRppMCh2Zo8tDixqjQ==", "dev": true }, "@types/normalize-package-data": { @@ -920,71 +920,142 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz", - "integrity": "sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", + "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.26.1", - "@typescript-eslint/scope-manager": "4.26.1", + "@typescript-eslint/experimental-utils": "4.27.0", + "@typescript-eslint/scope-manager": "4.27.0", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.21", "regexpp": "^3.1.0", "semver": "^7.3.5", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", + "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0" + } + }, + "@typescript-eslint/types": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", + "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", + "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.27.0", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/experimental-utils": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz", - "integrity": "sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", + "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.26.1", - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/typescript-estree": "4.26.1", + "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/typescript-estree": "4.27.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", + "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0" + } + }, + "@typescript-eslint/types": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", + "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", + "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", + "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.27.0", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/parser": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.26.1.tgz", - "integrity": "sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", + "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.26.1", - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/typescript-estree": "4.26.1", + "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/typescript-estree": "4.27.0", "debug": "^4.3.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", - "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", + "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1" + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0" } }, "@typescript-eslint/types": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", - "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", + "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", - "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", + "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -993,12 +1064,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", - "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", + "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/types": "4.27.0", "eslint-visitor-keys": "^2.0.0" } }, diff --git a/package.json b/package.json index 8d2c9a4..d4dc687 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,13 @@ "devDependencies": { "@json-schema-tools/meta-schema": "^1.6.10", "@types/jest": "^26.0.23", - "@types/node": "^15.12.2", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@types/node": "^15.12.3", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", "eslint": "^7.28.0", "jest": "^26.6.3", "ts-jest": "^26.5.6", - "typedoc": "^0.20.36", + "typedoc": "^0.20.37", "typescript": "4.2.4" }, "dependencies": {} diff --git a/src/index.test.ts b/src/index.test.ts index dcd0b69..7943e7b 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -415,7 +415,7 @@ describe("traverse", () => { title: "6", type: "object", allOf: [ - { title: "7", type: "object", properties: { baz: { title: "8" } } }, + { title: "7", type: "object", properties: { baz: { title: "5" } } }, ], }, }, @@ -573,6 +573,27 @@ describe("traverse", () => { expect(mockMutation).toHaveBeenCalledWith(testSchema, true, ""); expect(mockMutation).not.toHaveBeenCalledWith(testSchema, false, ""); }); + + it("true when the cycle is inside oneOf", () => { + const testSchema = { + title: "a", + oneOf: [{ + title: "b", + type: "object", + properties: { + a: {} + } + }] + }; + testSchema.oneOf[0].properties.a = testSchema; + + const mockMutation = jest.fn((mockS) => mockS); + + traverse(testSchema, mockMutation, { mutable: false }); + + expect(mockMutation).toHaveBeenCalledWith(testSchema, true, ""); + expect(mockMutation).not.toHaveBeenCalledWith(testSchema, false, ""); + }); }); describe("bfs", () => { diff --git a/src/index.ts b/src/index.ts index 7919c34..9772710 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,10 @@ import { JSONSchema, JSONSchemaObject, PatternProperties } from "@json-schema-to * Signature of the mutation method passed to traverse. * * @param schema The schema or subschema node being traversed - * @param isRootOfCycle false if the schema passed is not the root of a detected cycle. Useful for special handling of cycled schemas. + * @param isCycle false if the schema passed is not the root of a detected cycle. Useful for special handling of cycled schemas. * @param path json path string separated by periods */ -export type MutationFunction = (schema: JSONSchema, isRootOfCycle: boolean, path: string, ) => JSONSchema; +export type MutationFunction = (schema: JSONSchema, isCycle: boolean, path: string,) => JSONSchema; /** * The options you can use when traversing. @@ -74,8 +74,8 @@ export default function traverse( recursiveStack: JSONSchema[] = [], pathStack: string[] = [], prePostMap: Array<[JSONSchema, JSONSchema]> = [], + cycleSet: JSONSchema[] = [], ): JSONSchema { - let isRootOfCycle = false; const opts = { ...defaultOptions, ...traverseOptions }; // would be nice to make an 'entry' func when we get around to optimizations // booleans are a bit messed. Since all other schemas are objects (non-primitive type @@ -112,7 +112,7 @@ export default function traverse( const rec = (s: JSONSchema, path: string[]): JSONSchema => { const foundCycle = isCycle(s, recursiveStack); if (foundCycle) { - if (foundCycle === schema) { isRootOfCycle = true; } + cycleSet.push(foundCycle); // if the cycle is a ref to the root schema && skipFirstMutation is try we need to call mutate. // If we don't, it will never happen. @@ -127,6 +127,7 @@ export default function traverse( return cycledMutableSchema; } + // else return traverse( s, mutation, @@ -135,21 +136,22 @@ export default function traverse( recursiveStack, path, prePostMap, + cycleSet, ); }; if (schema.anyOf) { - mutableSchema.anyOf = schema.anyOf.map((x,i) => { + mutableSchema.anyOf = schema.anyOf.map((x, i) => { const result = rec(x, [...pathStack, "anyOf", i.toString()]); return result; }); } else if (schema.allOf) { - mutableSchema.allOf = schema.allOf.map((x,i) => { + mutableSchema.allOf = schema.allOf.map((x, i) => { const result = rec(x, [...pathStack, "allOf", i.toString()]); return result; }); } else if (schema.oneOf) { - mutableSchema.oneOf = schema.oneOf.map((x,i) => { + mutableSchema.oneOf = schema.oneOf.map((x, i) => { const result = rec(x, [...pathStack, "oneOf", i.toString()]); return result; }); @@ -158,14 +160,14 @@ export default function traverse( if (schema.items) { if (schema.items instanceof Array) { - mutableSchema.items = schema.items.map((x,i) => { + mutableSchema.items = schema.items.map((x, i) => { const result = rec(x, [...pathStack, "items", i.toString()]); return result; }); } else { const foundCycle = isCycle(schema.items, recursiveStack); if (foundCycle) { - if (foundCycle === schema) { isRootOfCycle = true; } + cycleSet.push(foundCycle); if (opts.skipFirstMutation === true && foundCycle === recursiveStack[0]) { mutableSchema.items = mutation(schema.items, true, jsonPathStringify(pathStack)); @@ -186,6 +188,7 @@ export default function traverse( recursiveStack, pathStack, prePostMap, + cycleSet, ); } } @@ -229,6 +232,7 @@ export default function traverse( if (opts.bfs === true) { return mutableSchema; } else { - return mutation(mutableSchema, isRootOfCycle, jsonPathStringify(pathStack)); + const isCycle = cycleSet.indexOf(schema) !== -1 + return mutation(mutableSchema, isCycle, jsonPathStringify(pathStack)); } }