Skip to content

Commit 2461839

Browse files
committed
chore: refactor a bit
1 parent 837b6fa commit 2461839

File tree

2 files changed

+158
-28
lines changed

2 files changed

+158
-28
lines changed

src/index.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -71,38 +71,18 @@ const last = (i: JSONSchema[], skip = 1): JSONSchema => {
7171
return i[i.length - skip];
7272
};
7373

74-
/**
75-
* Traverse all subschema of a schema, calling the mutator function with each.
76-
* The mutator is called on leaf nodes first.
77-
*
78-
* @param schema the schema to traverse
79-
* @param mutation the function to pass each node in the subschema tree.
80-
* @param traverseOptions a set of options for traversal.
81-
* @param depth For internal use. Tracks the current recursive depth in the tree. This is used to implement
82-
* some of the options.
83-
*
84-
*/
85-
export default function traverse(
74+
75+
const _traverse = (
8676
schema: JSONSchema,
8777
mutation: MutationFunction,
88-
traverseOptions = defaultOptions,
78+
opts: TraverseOptions,
8979
depth = 0,
9080
recursiveStack: JSONSchema[] = [],
9181
mutableStack: JSONSchema[] = [],
9282
pathStack: string[] = [],
9383
prePostMap: Array<[JSONSchema, JSONSchema]> = [],
9484
cycleSet: JSONSchema[] = [],
95-
): JSONSchema {
96-
const opts = { ...defaultOptions, ...traverseOptions }; // would be nice to make an 'entry' func when we get around to optimizations
97-
98-
// booleans are a bit messed. Since all other schemas are objects (non-primitive type
99-
// which gets a new address in mem) for each new JS refer to one of 2 memory addrs, and
100-
// thus adding it to the recursive stack will prevent it from being explored if the
101-
// boolean is seen in a further nested schema.
102-
if (depth === 0) {
103-
pathStack = [""];
104-
}
105-
85+
) => {
10686
if (typeof schema === "boolean" || schema instanceof Boolean) {
10787
if (opts.skipFirstMutation === true && depth === 0) {
10888
return schema;
@@ -161,10 +141,10 @@ export default function traverse(
161141
}
162142

163143
// else
164-
return traverse(
144+
return _traverse(
165145
s,
166146
mutation,
167-
traverseOptions,
147+
opts,
168148
depth + 1,
169149
recursiveStack,
170150
mutableStack,
@@ -216,10 +196,10 @@ export default function traverse(
216196
mutableSchema.items = cycledMutableSchema;
217197
}
218198
} else {
219-
mutableSchema.items = traverse(
199+
mutableSchema.items = _traverse(
220200
schema.items,
221201
mutation,
222-
traverseOptions,
202+
opts,
223203
depth + 1,
224204
recursiveStack,
225205
mutableStack,
@@ -282,4 +262,40 @@ export default function traverse(
282262
last(mutableStack)
283263
);
284264
}
265+
};
266+
267+
268+
/**
269+
* Traverse all subschema of a schema, calling the mutator function with each.
270+
* The mutator is called on leaf nodes first.
271+
*
272+
* @param schema the schema to traverse
273+
* @param mutation the function to pass each node in the subschema tree.
274+
* @param traverseOptions a set of options for traversal.
275+
* @param depth For internal use. Tracks the current recursive depth in the tree. This is used to implement
276+
* some of the options.
277+
*
278+
*/
279+
export default function traverse(
280+
schema: JSONSchema,
281+
mutation: MutationFunction,
282+
traverseOptions = defaultOptions,
283+
): JSONSchema {
284+
const opts = { ...defaultOptions, ...traverseOptions }; // would be nice to make an 'entry' func when we get around to optimizations
285+
286+
return _traverse(
287+
schema,
288+
mutation,
289+
opts,
290+
0,
291+
[],
292+
[],
293+
// booleans are a bit messed. Since all other schemas are objects (non-primitive type
294+
// which gets a new address in mem) for each new JS refer to one of 2 memory addrs, and
295+
// thus adding it to the recursive stack will prevent it from being explored if the
296+
// boolean is seen in a further nested schema.
297+
[""],
298+
[],
299+
[],
300+
);
285301
}

src/mutability.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Required imports
2+
import traverse, { TraverseOptions } from './';
3+
import { JSONSchema } from '@json-schema-tools/meta-schema';
4+
5+
// Example schemas for testing
6+
const exampleSchema: JSONSchema = {
7+
type: 'object',
8+
properties: {
9+
name: { type: 'string' },
10+
age: { type: 'number' },
11+
},
12+
};
13+
14+
// Test cases for different behaviors
15+
describe('traverse mutability behavior', () => {
16+
it('should modify the original schema when mutable is true and mutation modifies properties directly', () => {
17+
const mutableSchema = { ...exampleSchema };
18+
const mutationFn = (schema: any) => {
19+
if (typeof schema === 'object' && schema.type === 'string') {
20+
schema.type = 'number'; // Directly modify the schema
21+
}
22+
return schema;
23+
};
24+
25+
traverse(mutableSchema, mutationFn, { mutable: true } as TraverseOptions);
26+
27+
expect(mutableSchema.properties?.name.type).toBe('number'); // Original schema is modified
28+
});
29+
30+
it('should not modify the original schema when mutable is false and mutation modifies properties directly', () => {
31+
const mutableSchema = { ...exampleSchema };
32+
const mutationFn = (schema: any) => {
33+
if (typeof schema === 'object' && schema.type === 'string') {
34+
schema.type = 'number'; // Directly modify the schema
35+
}
36+
return schema;
37+
};
38+
39+
const result: any = traverse(mutableSchema, mutationFn, { mutable: false } as TraverseOptions);
40+
41+
expect(mutableSchema.properties?.name.type).toBe('string'); // Original schema remains unchanged
42+
expect(result.properties.name.type).toBe('number'); // Modified copy
43+
});
44+
45+
it('should replace the schema with a new object when mutation returns a new schema (mutable true)', () => {
46+
const mutableSchema = { ...exampleSchema };
47+
const mutationFn = (schema: any) => {
48+
if (typeof schema === 'object' && schema.type === 'string') {
49+
return { ...schema, type: 'boolean' }; // Return a new object
50+
}
51+
return schema;
52+
};
53+
54+
traverse(mutableSchema, mutationFn, { mutable: true } as TraverseOptions);
55+
56+
expect(mutableSchema.properties?.name.type).toBe('boolean'); // Original schema is replaced
57+
});
58+
59+
it('should replace the schema with a new object when mutation returns a new schema (mutable false)', () => {
60+
const mutableSchema = { ...exampleSchema };
61+
const mutationFn = (schema: any) => {
62+
if (typeof schema === 'object' && schema.type === 'string') {
63+
return { ...schema, type: 'boolean' }; // Return a new object
64+
}
65+
return schema;
66+
};
67+
68+
const result: any = traverse(mutableSchema, mutationFn, { mutable: false } as TraverseOptions);
69+
70+
expect(mutableSchema.properties?.name.type).toBe('string'); // Original schema remains unchanged
71+
expect(result.properties.name.type).toBe('boolean'); // Modified copy
72+
});
73+
74+
it('should skip the first mutation when skipFirstMutation is true', () => {
75+
const mutableSchema = { ...exampleSchema };
76+
const mutationFn = (schema: any) => {
77+
if (typeof schema === 'object' && schema.type) {
78+
schema.type = 'null'; // Directly modify the schema
79+
}
80+
return schema;
81+
};
82+
83+
const result: any = traverse(mutableSchema, mutationFn, { skipFirstMutation: true } as TraverseOptions);
84+
85+
expect(result.type).toBeUndefined(); // Root is not mutated
86+
expect(result.properties.name.type).toBe('null'); // Subschemas are mutated
87+
});
88+
89+
it('should traverse in breadth-first manner when bfs is true', () => {
90+
const bfsSchema: JSONSchema = {
91+
type: 'object',
92+
properties: {
93+
nested: {
94+
type: 'object',
95+
properties: {
96+
deep: { type: 'string' },
97+
},
98+
},
99+
},
100+
};
101+
102+
const order: string[] = [];
103+
const mutationFn = (schema: any) => {
104+
if (typeof schema === 'object' && schema.type) {
105+
order.push(schema.type);
106+
}
107+
return schema;
108+
};
109+
110+
traverse(bfsSchema, mutationFn, { bfs: true } as TraverseOptions);
111+
112+
expect(order).toEqual(['object', 'object', 'string']); // Breadth-first order
113+
});
114+
});

0 commit comments

Comments
 (0)