Skip to content

Commit 92561dd

Browse files
committed
feat(nodes): RegularNode#children should be undefined initially
1 parent af07b48 commit 92561dd

File tree

7 files changed

+57
-460
lines changed

7 files changed

+57
-460
lines changed

src/__tests__/__snapshots__/tree.spec.ts.snap

Lines changed: 1 addition & 432 deletions
Large diffs are not rendered by default.

src/__tests__/tree.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ describe('SchemaTree', () => {
250250
│ └─ children
251251
│ └─ 0
252252
│ └─ #/properties/foo/items/properties/user/items/properties/user/items
253-
│ └─ mirrors: #/properties/foo/items
253+
│ └─ mirrors: #/properties/foo/items/properties/user/items
254254
├─ 1
255255
│ └─ #/properties/bar
256256
│ ├─ types

src/__tests__/utils/printTree.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from
77
import type { SchemaTreeOptions } from '../../tree';
88
import { SchemaTree } from '../../tree';
99
import type { SchemaFragment } from '../../types';
10+
import { isNonNullable } from '../../utils';
1011

1112
export function printTree(schema: SchemaFragment, opts?: Partial<SchemaTreeOptions>) {
1213
const tree = new SchemaTree(schema, opts);
@@ -28,7 +29,7 @@ function printRegularNode(this: WeakSet<SchemaFragment>, node: RegularNode): Dic
2829
...(node.primaryType !== null ? { primaryType: node.primaryType } : null),
2930
...(node.combiners !== null ? { combiners: node.combiners } : null),
3031
...(node.enum !== null ? { enum: node.enum } : null),
31-
...(node.children !== null ? { children: node.children.map(prepareTree, this) } : null),
32+
...(isNonNullable(node.children) ? { children: node.children.map(prepareTree, this) } : null),
3233
};
3334
}
3435

src/nodes/RegularNode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class RegularNode extends BaseNode {
2626
public readonly title: string | null;
2727
public readonly deprecated: boolean;
2828

29-
public children: (RegularNode | ReferenceNode | MirroredSchemaNode)[] | null;
29+
public children: (RegularNode | ReferenceNode | MirroredSchemaNode)[] | null | undefined;
3030

3131
public readonly meta: Readonly<Partial<Dictionary<unknown, SchemaMeta>>>;
3232
public readonly annotations: Readonly<Partial<Dictionary<unknown, SchemaAnnotations>>>;
@@ -49,7 +49,7 @@ export class RegularNode extends BaseNode {
4949
this.annotations = getAnnotations(fragment);
5050
this.validations = getValidations(fragment, this.types);
5151

52-
this.children = null;
52+
this.children = void 0;
5353
}
5454

5555
public get simple() {

src/nodes/mirrored/MirroredRegularNode.ts

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

3-
import { isMirroredNode, isRegularNode } from '../../guards';
3+
import { isRegularNode } from '../../guards';
4+
import { isNonNullable } from '../../utils';
45
import { BaseNode } from '../BaseNode';
6+
import type { ReferenceNode } from '../ReferenceNode';
57
import type { RegularNode } from '../RegularNode';
68
import type { SchemaAnnotations, SchemaCombinerName, SchemaMeta, SchemaNodeKind } from '../types';
79
import { MirroredReferenceNode } from './MirroredReferenceNode';
@@ -24,9 +26,13 @@ export class MirroredRegularNode extends BaseNode implements RegularNode {
2426
public readonly simple!: boolean;
2527
public readonly unknown!: boolean;
2628

29+
private readonly cache: WeakMap<RegularNode | ReferenceNode, MirroredRegularNode | MirroredReferenceNode>;
30+
2731
constructor(public readonly mirroredNode: RegularNode) {
2832
super(mirroredNode.fragment);
2933

34+
this.cache = new WeakMap();
35+
3036
this._this = new Proxy(this, {
3137
get(target, key) {
3238
if (key in target) {
@@ -49,38 +55,40 @@ export class MirroredRegularNode extends BaseNode implements RegularNode {
4955
}
5056

5157
private readonly _this: MirroredRegularNode;
52-
private _children?: (MirroredRegularNode | MirroredReferenceNode)[] | null;
5358

54-
public get children(): (MirroredRegularNode | MirroredReferenceNode)[] | null {
55-
if (this._children !== void 0) {
56-
return this._children;
57-
}
59+
private _children?: (MirroredRegularNode | MirroredReferenceNode)[];
60+
61+
public get children(): (MirroredRegularNode | MirroredReferenceNode)[] | null | undefined {
62+
const referencedChildren = this.mirroredNode.children;
5863

59-
if (!('children' in this.mirroredNode)) {
60-
this._children = null;
61-
return null;
64+
if (!isNonNullable(referencedChildren)) {
65+
return referencedChildren;
6266
}
6367

64-
const referencedChildren = this.mirroredNode.children;
65-
if (referencedChildren === null) {
66-
this._children = null;
67-
return null;
68+
if (this._children === void 0) {
69+
this._children = [];
70+
} else {
71+
this._children.length = 0;
6872
}
6973

70-
const children: (MirroredRegularNode | MirroredReferenceNode)[] = [];
74+
const children: (MirroredRegularNode | MirroredReferenceNode)[] = this._children;
7175
for (const child of referencedChildren) {
7276
// this is to avoid pointing at nested mirroring
73-
const actualChild = isMirroredNode(child) ? child.mirroredNode : child;
74-
const mirroredChild = isRegularNode(actualChild)
75-
? new MirroredRegularNode(actualChild)
76-
: new MirroredReferenceNode(actualChild);
77+
const cached = this.cache.get(child);
78+
79+
if (cached !== void 0) {
80+
children.push(cached);
81+
continue;
82+
}
83+
84+
const mirroredChild = isRegularNode(child) ? new MirroredRegularNode(child) : new MirroredReferenceNode(child);
7785

7886
mirroredChild.parent = this._this;
79-
mirroredChild.subpath = actualChild.subpath;
87+
mirroredChild.subpath = child.subpath;
88+
this.cache.set(child, mirroredChild);
8089
children.push(mirroredChild);
8190
}
8291

83-
this._children = children;
8492
return children;
8593
}
8694
}

src/utils/guards.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ export function isObjectLiteral(maybeObj: unknown): maybeObj is Dictionary<unkno
1919
const proto = Object.getPrototypeOf(maybeObj);
2020
return proto === null || proto === Object.prototype;
2121
}
22+
23+
export function isNonNullable<T = unknown>(maybeNullable: T): maybeNullable is NonNullable<T> {
24+
return maybeNullable !== void 0 && maybeNullable !== null;
25+
}

src/walker/walker.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Dictionary } from '@stoplight/types';
33
import createMagicError from 'magic-error';
44

55
import { MergingError } from '../errors';
6-
import { isReferenceNode, isRegularNode, isRootNode } from '../guards';
6+
import { isMirroredNode, isReferenceNode, isRegularNode, isRootNode } from '../guards';
77
import { mergeAllOf } from '../mergers/mergeAllOf';
88
import { mergeOneOrAnyOf } from '../mergers/mergeOneOrAnyOf';
99
import { MirroredReferenceNode, MirroredRegularNode, ReferenceNode, RegularNode } from '../nodes';
@@ -78,13 +78,28 @@ export class Walker extends EventEmitter<WalkerEmitter> {
7878
}
7979

8080
public walk(): void {
81-
const { depth: initialDepth, schemaNode: initialSchemaNode, fragment } = this;
81+
const { depth: initialDepth, fragment } = this;
82+
let { schemaNode: initialSchemaNode } = this;
8283

8384
if (initialDepth === -1 && Object.keys(fragment).length === 0) {
8485
// empty schema, nothing to do
8586
return;
8687
}
8788

89+
while (isMirroredNode(initialSchemaNode)) {
90+
if (!isRegularNode(initialSchemaNode.mirroredNode)) {
91+
return;
92+
}
93+
94+
if (initialSchemaNode.mirroredNode.children === void 0) {
95+
this.restoreWalkerAtNode(initialSchemaNode.mirroredNode);
96+
initialSchemaNode = this.schemaNode;
97+
this.depth = initialDepth;
98+
} else {
99+
return;
100+
}
101+
}
102+
88103
const state = this.dumpInternalWalkerState();
89104

90105
super.emit('enterFragment', fragment);
@@ -110,10 +125,10 @@ export class Walker extends EventEmitter<WalkerEmitter> {
110125
}
111126

112127
if ('children' in initialSchemaNode && !isRootNode(schemaNode)) {
113-
if (initialSchemaNode.children === null) {
128+
if (initialSchemaNode.children === void 0) {
114129
(initialSchemaNode as RegularNode).children = [schemaNode];
115130
} else {
116-
initialSchemaNode.children.push(schemaNode);
131+
initialSchemaNode.children!.push(schemaNode);
117132
}
118133
}
119134

0 commit comments

Comments
 (0)