Skip to content

Commit 53e0683

Browse files
rework substitute implementation
1 parent fd08599 commit 53e0683

File tree

10 files changed

+405
-615
lines changed

10 files changed

+405
-615
lines changed

src/ContextNode.ts

Lines changed: 0 additions & 122 deletions
This file was deleted.

src/RecordedArguments.ts

Lines changed: 93 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,93 @@
1-
import { Argument, AllArguments } from './Arguments'
2-
3-
type ArgumentsClass = 'primitives-only' | 'with-single-argument' | 'all-arguments'
4-
export class RecordedArguments {
5-
private _argumentsClass: ArgumentsClass
6-
private _value?: any[]
7-
private readonly _hasNoArguments: boolean = false
8-
9-
private constructor(rawArguments?: any[]) {
10-
if (typeof rawArguments === 'undefined') {
11-
this._hasNoArguments = true
12-
return this
13-
}
14-
15-
this._argumentsClass = this.classifyArguments(rawArguments)
16-
this._value = rawArguments
17-
}
18-
19-
static from(rawArguments: any[]): RecordedArguments {
20-
return new this(rawArguments)
21-
}
22-
23-
static noArguments(): RecordedArguments {
24-
return new this()
25-
}
26-
27-
static sort<T extends { arguments: RecordedArguments }>(objectWithArguments: T[]): T[] {
28-
return objectWithArguments.sort((a, b) => {
29-
const aClass = a.arguments.argumentsClass
30-
const bClass = b.arguments.argumentsClass
31-
32-
if (aClass === bClass) return 0
33-
if (aClass === 'primitives-only') return -1
34-
if (bClass === 'primitives-only') return 1
35-
36-
if (aClass === 'with-single-argument') return -1
37-
if (bClass === 'with-single-argument') return 1
38-
39-
if (aClass === 'all-arguments') return -1
40-
return 1
41-
})
42-
}
43-
44-
get argumentsClass(): ArgumentsClass {
45-
return this._argumentsClass
46-
}
47-
48-
get value(): any[] | undefined {
49-
return this._value
50-
}
51-
52-
get hasNoArguments(): boolean {
53-
return this._hasNoArguments
54-
}
55-
56-
public match(other: RecordedArguments) {
57-
if (this.hasNoArguments && other.hasNoArguments) return true
58-
if (this.argumentsClass === 'all-arguments' || other.argumentsClass === 'all-arguments') return true
59-
if (this.value.length !== this.value.length) return false
60-
61-
return this.value.every((argument, index) => this.areArgumentsEqual(argument, other.value[index]))
62-
}
63-
64-
private classifyArguments(rawArguments: any[]): ArgumentsClass {
65-
const allPrimitives = rawArguments.every(arg => !(arg instanceof Argument || arg instanceof AllArguments))
66-
if (allPrimitives) return 'primitives-only'
67-
68-
const hasSingleArgument = rawArguments.some(arg => arg instanceof Argument)
69-
if (hasSingleArgument) return 'with-single-argument'
70-
71-
return 'all-arguments'
72-
}
73-
74-
private areArgumentsEqual(a: any, b: any): boolean {
75-
if (a instanceof Argument && b instanceof Argument) return false
76-
if (a instanceof Argument) return a.matches(b)
77-
if (b instanceof Argument) return b.matches(a)
78-
return this.areArgumentsDeepEqual(a, b)
79-
}
80-
81-
private areArgumentsDeepEqual(a: any, b: any, previousSeenObjects?: WeakSet<object>): boolean {
82-
const seenObjects = previousSeenObjects ?? new WeakSet()
83-
84-
const seenA = seenObjects.has(a)
85-
const seenB = seenObjects.has(b)
86-
const aIsNonNullObject = typeof a === 'object' && a !== null
87-
const bIsNonNullObject = typeof b === 'object' && b !== null
88-
89-
if (!seenA && aIsNonNullObject) seenObjects.add(a)
90-
if (!seenB && bIsNonNullObject) seenObjects.add(b)
91-
92-
const aEqualsB = a === b
93-
if ((seenA && seenB) || aEqualsB) return aEqualsB
94-
95-
if (aIsNonNullObject && bIsNonNullObject) {
96-
if (a.constructor !== b.constructor) return false
97-
const objectAKeys = Object.keys(a)
98-
if (objectAKeys.length !== Object.keys(b).length) return false
99-
return objectAKeys.every(key => this.areArgumentsDeepEqual(a[key], b[key], seenObjects))
100-
}
101-
102-
return aEqualsB
103-
}
104-
}
1+
import { inspect, InspectOptions, isDeepStrictEqual } from 'util'
2+
import { Argument, AllArguments } from './Arguments'
3+
4+
type ArgumentsClass = 'plain' | 'with-predicate' | 'wildcard'
5+
export class RecordedArguments {
6+
private _argumentsClass: ArgumentsClass
7+
private _value?: any[]
8+
private readonly _hasNoArguments: boolean = false
9+
10+
private constructor(rawArguments: any[] | void) {
11+
if (typeof rawArguments === 'undefined') {
12+
this._hasNoArguments = true
13+
return this
14+
}
15+
16+
this._argumentsClass = this.classifyArguments(rawArguments)
17+
this._value = rawArguments
18+
}
19+
20+
static from(rawArguments: any[]): RecordedArguments {
21+
return new this(rawArguments)
22+
}
23+
24+
static none(): RecordedArguments {
25+
return new this()
26+
}
27+
28+
static sort<T extends { recordedArguments: RecordedArguments }>(objectWithArguments: T[]): T[] {
29+
return objectWithArguments.sort((a, b) => {
30+
const aClass = a.recordedArguments.argumentsClass
31+
const bClass = b.recordedArguments.argumentsClass
32+
33+
if (aClass === bClass) return 0
34+
if (aClass === 'plain') return -1
35+
if (bClass === 'plain') return 1
36+
37+
if (aClass === 'with-predicate') return -1
38+
if (bClass === 'with-predicate') return 1
39+
40+
if (aClass === 'wildcard') return -1
41+
return 1
42+
})
43+
}
44+
45+
get argumentsClass(): ArgumentsClass {
46+
return this._argumentsClass
47+
}
48+
49+
get value(): any[] | undefined {
50+
return this._value
51+
}
52+
53+
get hasNoArguments(): boolean {
54+
return this._hasNoArguments
55+
}
56+
57+
public match(other: RecordedArguments) {
58+
if (this.argumentsClass === 'wildcard' || other.argumentsClass === 'wildcard') return true
59+
if (this.hasNoArguments || other.hasNoArguments) return this.hasNoArguments && other.hasNoArguments
60+
if (this.value.length !== other.value.length) return false
61+
62+
return this.value.every((argument, index) => this.areArgumentsEqual(argument, other.value[index]))
63+
}
64+
65+
private classifyArguments(rawArguments: any[]): ArgumentsClass {
66+
const allPlain = rawArguments.every(arg => !(arg instanceof Argument || arg instanceof AllArguments))
67+
if (allPlain) return 'plain'
68+
69+
const hasSingleArgument = rawArguments.some(arg => arg instanceof Argument)
70+
if (hasSingleArgument) return 'with-predicate'
71+
72+
return 'wildcard'
73+
}
74+
75+
private areArgumentsEqual(a: any, b: any): boolean {
76+
if (a instanceof Argument && b instanceof Argument) return false
77+
if (a instanceof Argument) return a.matches(b)
78+
if (b instanceof Argument) return b.matches(a)
79+
return isDeepStrictEqual(a, b)
80+
}
81+
82+
[inspect.custom](_: number, options: InspectOptions) {
83+
return this.printableForm(options)
84+
}
85+
86+
private printableForm(options: InspectOptions): string {
87+
const inspectedValues = this.value?.map(v => inspect(v, options))
88+
if (!Array.isArray(inspectedValues)) return ''
89+
return inspectedValues.length !== 1
90+
? `(${inspectedValues.join(', ')})`
91+
: inspectedValues[0]
92+
}
93+
}

0 commit comments

Comments
 (0)