Skip to content

Commit 06d94b2

Browse files
Marcell TothP0lip
andauthored
feat: preserve original ref info - onto master (#12)
* feat: preserve original ref info * fix: update MirroredRegularNode * chore: incorporate PR feedback * feat: preserve originalFragment for combined nodes, too * feat: preserve originalFragment for combined nodes, too * feat: preserve originalFragment for mirrored nodes * style: spacing * style: lint Co-authored-by: Jakub Rożek <[email protected]>
1 parent 3b2083b commit 06d94b2

File tree

5 files changed

+46
-21
lines changed

5 files changed

+46
-21
lines changed

src/__tests__/tree.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as fs from 'fs';
33
import type { JSONSchema4 } from 'json-schema';
44
import * as path from 'path';
55

6+
import { isRegularNode } from '../guards';
7+
import type { RegularNode } from '../nodes';
68
import { SchemaTree } from '../tree';
79
import { printTree } from './utils/printTree';
810

@@ -102,6 +104,27 @@ describe('SchemaTree', () => {
102104
`);
103105
});
104106

107+
it('preserves the original $ref info', () => {
108+
const schema: JSONSchema4 = {
109+
type: 'object',
110+
properties: {
111+
foo: {
112+
$ref: '#/properties/bar',
113+
},
114+
bar: {
115+
type: 'boolean',
116+
},
117+
},
118+
};
119+
120+
const tree = new SchemaTree(schema);
121+
tree.populate();
122+
123+
const topLevelObject = tree.root.children[0] as RegularNode;
124+
const fooObj = topLevelObject.children!.find(child => child.path[child.path.length - 1] === 'foo')!;
125+
expect(isRegularNode(fooObj) && fooObj.originalFragment.$ref).toBe('#/properties/bar');
126+
});
127+
105128
it('given an array with $reffed items, should resolve', () => {
106129
const schema: JSONSchema4 = {
107130
type: 'object',

src/mergers/mergeOneOrAnyOf.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ export function mergeOneOrAnyOf(
2828
const prunedSchema = { ...fragment };
2929
delete prunedSchema[combiner];
3030

31-
const resolvedItem =
32-
typeof item.$ref === 'string' && walkingOptions.resolveRef !== null
33-
? walkingOptions.resolveRef(null, item.$ref)
34-
: item;
35-
3631
if (Object.keys(prunedSchema).length === 0) {
37-
merged.push(resolvedItem);
32+
merged.push(item);
3833
} else {
34+
const resolvedItem =
35+
typeof item.$ref === 'string' && walkingOptions.resolveRef !== null
36+
? walkingOptions.resolveRef(null, item.$ref)
37+
: item;
3938
const mergedSchema = {
4039
allOf: [prunedSchema, resolvedItem],
4140
};

src/nodes/RegularNode.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ export class RegularNode extends BaseNode {
2929

3030
public readonly annotations: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
3131
public readonly validations: Readonly<Dictionary<unknown>>;
32+
public readonly originalFragment: SchemaFragment;
3233

33-
constructor(public readonly fragment: SchemaFragment) {
34+
constructor(public readonly fragment: SchemaFragment, context?: { originalFragment?: SchemaFragment }) {
3435
super(fragment);
3536

3637
this.$id = unwrapStringOrNull('id' in fragment ? fragment.id : fragment.$id);
@@ -46,6 +47,7 @@ export class RegularNode extends BaseNode {
4647

4748
this.annotations = getAnnotations(fragment);
4849
this.validations = getValidations(fragment, this.types);
50+
this.originalFragment = context?.originalFragment ?? fragment;
4951

5052
this.children = void 0;
5153
}

src/nodes/mirrored/MirroredRegularNode.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Dictionary } from '@stoplight/types';
22

33
import { isRegularNode } from '../../guards';
4+
import type { SchemaFragment } from '../../types';
45
import { isNonNullable } from '../../utils';
56
import { BaseNode } from '../BaseNode';
67
import type { ReferenceNode } from '../ReferenceNode';
@@ -22,14 +23,16 @@ export class MirroredRegularNode extends BaseNode implements RegularNode {
2223

2324
public readonly annotations!: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
2425
public readonly validations!: Readonly<Dictionary<unknown>>;
26+
public readonly originalFragment!: SchemaFragment;
2527

2628
public readonly simple!: boolean;
2729
public readonly unknown!: boolean;
2830

2931
private readonly cache: WeakMap<RegularNode | ReferenceNode, MirroredRegularNode | MirroredReferenceNode>;
3032

31-
constructor(public readonly mirroredNode: RegularNode) {
33+
constructor(public readonly mirroredNode: RegularNode, context?: { originalFragment?: SchemaFragment }) {
3234
super(mirroredNode.fragment);
35+
this.originalFragment = context?.originalFragment ?? mirroredNode.originalFragment;
3336

3437
this.cache = new WeakMap();
3538

src/walker/walker.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,14 @@ export class Walker extends EventEmitter<WalkerEmitter> {
241241
this.schemaNode = schemaNode;
242242
}
243243

244-
protected retrieveFromFragment(fragment: ProcessedFragment): [MirroredSchemaNode, ProcessedFragment] | void {
244+
protected retrieveFromFragment(
245+
fragment: ProcessedFragment,
246+
originalFragment: SchemaFragment,
247+
): [MirroredSchemaNode, ProcessedFragment] | void {
245248
const processedSchemaNode = this.processedFragments.get(fragment);
246249
if (processedSchemaNode !== void 0) {
247250
if (isRegularNode(processedSchemaNode)) {
248-
return [new MirroredRegularNode(processedSchemaNode), fragment];
251+
return [new MirroredRegularNode(processedSchemaNode, { originalFragment }), fragment];
249252
}
250253

251254
if (isReferenceNode(processedSchemaNode)) {
@@ -258,10 +261,10 @@ export class Walker extends EventEmitter<WalkerEmitter> {
258261
}
259262

260263
protected processFragment(): [SchemaNode, ProcessedFragment] {
261-
const { walkingOptions, path } = this;
264+
const { walkingOptions, path, fragment: originalFragment } = this;
262265
let { fragment } = this;
263266

264-
let retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment) : null;
267+
let retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(fragment, originalFragment) : null;
265268

266269
if (retrieved) {
267270
return retrieved;
@@ -301,28 +304,23 @@ export class Walker extends EventEmitter<WalkerEmitter> {
301304
try {
302305
const merged = mergeOneOrAnyOf(fragment, path, walkingOptions);
303306
if (merged.length === 1) {
304-
return [new RegularNode(merged[0]), initialFragment];
307+
return [new RegularNode(merged[0], { originalFragment }), initialFragment];
305308
} else {
306309
const combiner = SchemaCombinerName.OneOf in fragment ? SchemaCombinerName.OneOf : SchemaCombinerName.AnyOf;
307-
return [
308-
new RegularNode({
309-
[combiner]: merged,
310-
}),
311-
initialFragment,
312-
];
310+
return [new RegularNode({ [combiner]: merged }, { originalFragment }), initialFragment];
313311
}
314312
} catch (ex) {
315313
super.emit('error', createMagicError(new MergingError(ex?.message ?? 'Unknown merging error')));
316314
// no the end of the world - we will render raw unprocessed fragment
317315
}
318316
}
319317

320-
retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(initialFragment) : null;
318+
retrieved = isNonNullable(fragment) ? this.retrieveFromFragment(initialFragment, originalFragment) : null;
321319

322320
if (retrieved) {
323321
return retrieved;
324322
}
325323

326-
return [new RegularNode(fragment), initialFragment];
324+
return [new RegularNode(fragment, { originalFragment }), initialFragment];
327325
}
328326
}

0 commit comments

Comments
 (0)