Skip to content

Commit af07b48

Browse files
committed
feat(nodes): implement Symbol.hasInstance
1 parent 5a2ad45 commit af07b48

File tree

8 files changed

+75
-12
lines changed

8 files changed

+75
-12
lines changed

src/__tests__/utils/printTree.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { pathToPointer } from '@stoplight/json';
22
import type { Dictionary } from '@stoplight/types';
33
import * as treeify from 'treeify';
44

5-
import { isMirroredNode, isRegularNode } from '../../guards';
6-
import { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from '../../nodes';
5+
import { isMirroredNode, isReferenceNode, isRegularNode } from '../../guards';
6+
import type { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from '../../nodes';
77
import type { SchemaTreeOptions } from '../../tree';
88
import { SchemaTree } from '../../tree';
99
import type { SchemaFragment } from '../../types';
@@ -54,9 +54,9 @@ function printMirrorNode(this: WeakSet<SchemaFragment>, node: MirroredSchemaNode
5454
function printNode(this: WeakSet<SchemaFragment>, node: SchemaNode) {
5555
return isMirroredNode(node)
5656
? printMirrorNode.call(this, node)
57-
: node instanceof RegularNode
57+
: isRegularNode(node)
5858
? printRegularNode.call(this, node)
59-
: node instanceof ReferenceNode
59+
: isReferenceNode(node)
6060
? printReferenceNode.call(this, node)
6161
: {
6262
kind: 'unknown node',

src/guards/nodes.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
1-
import type { MirroredSchemaNode, ReferenceNode, RegularNode, SchemaNode } from '../nodes';
1+
import {
2+
MirroredReferenceNode,
3+
MirroredRegularNode,
4+
MirroredSchemaNode,
5+
ReferenceNode,
6+
RegularNode,
7+
RootNode,
8+
SchemaNode,
9+
} from '../nodes';
10+
11+
export function isSchemaNode(node: unknown): node is SchemaNode {
12+
const name = Object.getPrototypeOf(node).constructor.name;
13+
return (
14+
name === RootNode.name ||
15+
name === RegularNode.name ||
16+
name === MirroredRegularNode.name ||
17+
name === ReferenceNode.name ||
18+
name === MirroredReferenceNode.name
19+
);
20+
}
21+
22+
export function isRootNode(node: SchemaNode): node is RootNode {
23+
return Object.getPrototypeOf(node).constructor.name === 'RootNode';
24+
}
225

326
export function isRegularNode(node: SchemaNode): node is RegularNode {
427
return 'types' in node && 'primaryType' in node && 'combiners' in node;

src/nodes/ReferenceNode.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isLocalRef } from '@stoplight/json';
22

33
import { unwrapStringOrNull } from '../accessors/unwrap';
4+
import { isReferenceNode, isSchemaNode } from '../guards';
45
import type { SchemaFragment } from '../types';
56
import { BaseNode } from './BaseNode';
67

@@ -16,4 +17,8 @@ export class ReferenceNode extends BaseNode {
1617
public get external() {
1718
return this.value !== null && !isLocalRef(this.value);
1819
}
20+
21+
static [Symbol.hasInstance](instance: unknown) {
22+
return isSchemaNode(instance) && isReferenceNode(instance);
23+
}
1924
}

src/nodes/RegularNode.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getTypes } from '../accessors/getTypes';
99
import { getValidations } from '../accessors/getValidations';
1010
import { isDeprecated } from '../accessors/isDeprecated';
1111
import { unwrapArrayOrNull, unwrapStringOrNull } from '../accessors/unwrap';
12+
import { isRegularNode, isSchemaNode } from '../guards';
1213
import type { SchemaFragment } from '../types';
1314
import { BaseNode } from './BaseNode';
1415
import type { ReferenceNode } from './ReferenceNode';
@@ -66,4 +67,8 @@ export class RegularNode extends BaseNode {
6667
Object.keys(this.annotations).length + Object.keys(this.validations).length === 0
6768
);
6869
}
70+
71+
static [Symbol.hasInstance](instance: unknown) {
72+
return isSchemaNode(instance) && isRegularNode(instance);
73+
}
6974
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { MirroredReferenceNode } from '../mirrored';
2+
import { ReferenceNode } from '../ReferenceNode';
3+
import { RegularNode } from '../RegularNode';
4+
5+
describe('ReferenceNode node', () => {
6+
it('should have a working Symbol.hasInstance trait', () => {
7+
const referenceNode = new ReferenceNode({ $ref: '' }, null);
8+
const mirroredNode = new MirroredReferenceNode(referenceNode);
9+
10+
expect(mirroredNode instanceof ReferenceNode).toBe(true);
11+
expect(referenceNode instanceof ReferenceNode).toBe(true);
12+
expect(mirroredNode instanceof RegularNode).toBe(false);
13+
expect(referenceNode instanceof RegularNode).toBe(false);
14+
});
15+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { MirroredRegularNode } from '../mirrored';
2+
import { ReferenceNode } from '../ReferenceNode';
3+
import { RegularNode } from '../RegularNode';
4+
5+
describe('RegularNode node', () => {
6+
it('should have a working Symbol.hasInstance trait', () => {
7+
const regularNode = new RegularNode({ type: 'string' });
8+
const mirroredNode = new MirroredRegularNode(regularNode);
9+
10+
expect(mirroredNode instanceof RegularNode).toBe(true);
11+
expect(regularNode instanceof RegularNode).toBe(true);
12+
expect(mirroredNode instanceof ReferenceNode).toBe(false);
13+
expect(regularNode instanceof ReferenceNode).toBe(false);
14+
});
15+
});

src/nodes/mirrored/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export * from './MirroredReferenceNode';
2-
export * from './MirroredRegularNode';
1+
export { MirroredReferenceNode } from './MirroredReferenceNode';
2+
export { MirroredRegularNode } from './MirroredRegularNode';

src/walker/walker.ts

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

55
import { MergingError } from '../errors';
6-
import { isReferenceNode, isRegularNode } from '../guards';
6+
import { 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';
10-
import { RootNode } from '../nodes/RootNode';
10+
import type { RootNode } from '../nodes/RootNode';
1111
import { SchemaCombinerName, SchemaNode, SchemaNodeKind } from '../nodes/types';
1212
import type { SchemaFragment } from '../types';
1313
import { isObjectLiteral } from '../utils/guards';
@@ -104,12 +104,12 @@ export class Walker extends EventEmitter<WalkerEmitter> {
104104
continue;
105105
}
106106

107-
if (!(schemaNode instanceof RootNode)) {
107+
if (!isRootNode(schemaNode)) {
108108
schemaNode.parent = initialSchemaNode;
109109
schemaNode.subpath = this.path.slice(initialSchemaNode.path.length);
110110
}
111111

112-
if ('children' in initialSchemaNode && !(schemaNode instanceof RootNode)) {
112+
if ('children' in initialSchemaNode && !isRootNode(schemaNode)) {
113113
if (initialSchemaNode.children === null) {
114114
(initialSchemaNode as RegularNode).children = [schemaNode];
115115
} else {
@@ -119,7 +119,7 @@ export class Walker extends EventEmitter<WalkerEmitter> {
119119

120120
super.emit('includeNode', schemaNode);
121121

122-
if (schemaNode instanceof RegularNode) {
122+
if (isRegularNode(schemaNode)) {
123123
this.schemaNode = schemaNode;
124124

125125
if (this.hooks.stepIn?.(schemaNode) !== false) {

0 commit comments

Comments
 (0)