risei 2.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -28
- package/index.js +18 -66
- package/package.json +7 -6
- package/system/CallTypes.js +10 -0
- package/system/Choices.js +92 -0
- package/system/ClassTestGroup.js +8 -8
- package/system/LocalCaller.js +47 -7
- package/system/MethodSpoofer.js +2 -2
- package/system/MethodTestGroup.js +8 -8
- package/system/Moment.js +29 -29
- package/system/NameAnalyzer.js +26 -26
- package/system/ObjectAnalyzer.js +36 -0
- package/system/PropertySpoofer.js +1 -1
- package/system/SpoofDef.js +73 -63
- package/system/TerminalReporter.js +102 -25
- package/system/TestDef.js +75 -17
- package/system/TestFinder.js +31 -29
- package/system/TestFrame.js +21 -12
- package/system/TestGroup.js +25 -25
- package/system/TestResult.js +63 -13
- package/system/TestRunner.js +44 -16
- package/system/TestStages.js +85 -26
- package/system/TestSummary.js +38 -37
- package/system/TotalComparer.js +54 -17
- package/system/TotalDisplayer.js +29 -6
- package/system/TypeAnalyzer.js +117 -26
- package/system/TypeIdentifier.js +10 -6
- package/system/Types.js +1 -0
- package/system/ATestCaller.js +0 -61
- package/system/ATestFinder.js +0 -38
- package/system/ATestReporter.js +0 -26
- package/system/ChosenTestFinder.js +0 -31
- package/system/Risei.js +0 -118
package/system/TestStages.js
CHANGED
|
@@ -6,26 +6,29 @@ import PropertySpoofer from "./PropertySpoofer.js";
|
|
|
6
6
|
import TypeAnalyzer from "./TypeAnalyzer.js";
|
|
7
7
|
import NameAnalyzer from "./NameAnalyzer.js";
|
|
8
8
|
import TotalComparer from "./TotalComparer.js";
|
|
9
|
+
import CallTypes from "./CallTypes.js";
|
|
9
10
|
|
|
10
11
|
export default class TestStages {
|
|
11
|
-
/*
|
|
12
|
-
|
|
13
|
-
/* These methods hold operations carried out in the test frame,
|
|
14
|
-
or before / after all test frames, in the test runner.
|
|
12
|
+
/* These methods hold reentrant operations carried
|
|
13
|
+
out in the test frame in the test runner.
|
|
15
14
|
Not necessarily all test steps are found here. */
|
|
16
15
|
|
|
16
|
+
// region Components
|
|
17
|
+
|
|
17
18
|
static #methodSpoofer = new MethodSpoofer();
|
|
18
19
|
static #propertySpoofer = new PropertySpoofer();
|
|
19
20
|
static #comparer = new TotalComparer();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
// endregion Components
|
|
23
|
+
|
|
24
|
+
async anyPreTargetingGroundwork(test) /* passed */ {
|
|
22
25
|
// Spoofing methods; properties spoofed later.
|
|
23
26
|
TestStages.#methodSpoofer.spoof(test);
|
|
24
27
|
|
|
25
28
|
// Arbitrary user-defined actions.
|
|
26
29
|
// Only spoofed methods available.
|
|
27
30
|
if (test.doesHaveDoEarly) {
|
|
28
|
-
test.do.early(test);
|
|
31
|
+
await test.do.early(test);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -35,7 +38,7 @@ export default class TestStages {
|
|
|
35
38
|
static or instance properties. */
|
|
36
39
|
|
|
37
40
|
if (test.isConstructorTest) {
|
|
38
|
-
test.target =
|
|
41
|
+
test.target = test.on.prototype;
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -48,36 +51,49 @@ export default class TestStages {
|
|
|
48
51
|
|
|
49
52
|
supplyLocalTarget(test) /* passed */ {
|
|
50
53
|
/* Methods are called on instance / type / prototype directly.
|
|
51
|
-
Properties are called on instance / type in a nonce.
|
|
54
|
+
Properties are called on instance / type in a nonce.
|
|
55
|
+
Methods or properties are poly-called in a nonce. */
|
|
52
56
|
|
|
53
57
|
let callable = NameAnalyzer.plainNameOf(test.method);
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
switch (test.callType) {
|
|
60
|
+
case CallTypes.methodMono:
|
|
61
|
+
return test.target;
|
|
62
|
+
case CallTypes.propMono:
|
|
63
|
+
return { nonce: () => { return test.target[callable]; } };
|
|
64
|
+
case CallTypes.conMono:
|
|
65
|
+
return { nonce: (...args) => { return new test.target[callable](...args); } };
|
|
66
|
+
case CallTypes.methodPoly:
|
|
67
|
+
return { nonce: this.supplyPolyMethodNonce(test, callable) };
|
|
68
|
+
case CallTypes.propPoly:
|
|
69
|
+
return { nonce: this.supplyPolyPropNonce(test, callable) };
|
|
70
|
+
case CallTypes.conPoly:
|
|
71
|
+
return { nonce: this.supplyPolyConstructorNonce(test, callable) };
|
|
72
|
+
}
|
|
58
73
|
|
|
59
74
|
return localTarget;
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
supplyCallableName(test) /* passed */ {
|
|
63
|
-
/*
|
|
64
|
-
or a nonce method for
|
|
78
|
+
/* Name is of the method named in test,
|
|
79
|
+
or of a nonce method for prop, con,
|
|
80
|
+
or poly-call test (of any target). */
|
|
65
81
|
|
|
66
|
-
let name = test.isMethodTest
|
|
82
|
+
let name = test.isMethodTest && test.isMonoCallTest
|
|
67
83
|
? NameAnalyzer.plainNameOf(test.method)
|
|
68
84
|
: TestDef.nonceLocalCallableName;
|
|
69
85
|
|
|
70
86
|
return name;
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
anyPostTargetingGroundwork(test) /* passed */ {
|
|
89
|
+
async anyPostTargetingGroundwork(test) /* passed */ {
|
|
74
90
|
// Spoofing properties; methods spoofed earlier.
|
|
75
91
|
TestStages.#propertySpoofer.spoof(test);
|
|
76
92
|
|
|
77
93
|
// Arbitrary user-defined actions.
|
|
78
94
|
// All spoofs normally available.
|
|
79
95
|
if (test.doesHaveDoLate) {
|
|
80
|
-
test.do.late(test);
|
|
96
|
+
await test.do.late(test);
|
|
81
97
|
}
|
|
82
98
|
}
|
|
83
99
|
|
|
@@ -99,7 +115,7 @@ export default class TestStages {
|
|
|
99
115
|
return TestStages.#comparer.compare(expected, actual);
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
anyGroundworkReversal(test) /* passed */ {
|
|
118
|
+
async anyGroundworkReversal(test) /* passed */ {
|
|
103
119
|
// Unspoofing methods and properties.
|
|
104
120
|
TestStages.#methodSpoofer.unspoof(test);
|
|
105
121
|
TestStages.#propertySpoofer.unspoof(test);
|
|
@@ -107,12 +123,57 @@ export default class TestStages {
|
|
|
107
123
|
// Arbitrary user-defined operations.
|
|
108
124
|
// No spoofs are available here.
|
|
109
125
|
if (test.doesHaveUndo) {
|
|
110
|
-
test.undo(test);
|
|
126
|
+
await test.undo(test);
|
|
111
127
|
}
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
// region Dependencies of test stages
|
|
115
131
|
|
|
132
|
+
supplyPolyMethodNonce(test, callable) /* verified */ {
|
|
133
|
+
let nonce = (...inputs) => {
|
|
134
|
+
let actuals = [];
|
|
135
|
+
|
|
136
|
+
for (let args of inputs) {
|
|
137
|
+
let local = test.target[callable](...args);
|
|
138
|
+
actuals.push(local);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return actuals;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return nonce;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
supplyPolyPropNonce(test, callable) /* verified */ {
|
|
148
|
+
let nonce = (...inputs) => {
|
|
149
|
+
let actuals = [];
|
|
150
|
+
|
|
151
|
+
for (let args of inputs) {
|
|
152
|
+
let local = test.target[callable];
|
|
153
|
+
actuals.push(local);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return actuals;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return nonce;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
supplyPolyConstructorNonce(test, callable) /* verified */ {
|
|
163
|
+
let nonce = (...inputs) => {
|
|
164
|
+
let actuals = [];
|
|
165
|
+
|
|
166
|
+
for (let args of inputs) {
|
|
167
|
+
let local = new test.target[callable](...args);
|
|
168
|
+
actuals.push(local);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return actuals;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return nonce;
|
|
175
|
+
}
|
|
176
|
+
|
|
116
177
|
supplyNonReturnActual(test) /* passed */ {
|
|
117
178
|
// When .from is a string, the actual
|
|
118
179
|
// is the named target or type member.
|
|
@@ -125,28 +186,26 @@ export default class TestStages {
|
|
|
125
186
|
? test.target
|
|
126
187
|
: test.type;
|
|
127
188
|
|
|
189
|
+
// Constructors are different.
|
|
190
|
+
host = test.isConstructorTest
|
|
191
|
+
? test.actual
|
|
192
|
+
: host;
|
|
193
|
+
|
|
128
194
|
// Actually supplying value.
|
|
129
195
|
return host[name];
|
|
130
196
|
}
|
|
131
197
|
|
|
132
|
-
/* &cruft, possibly change all .from syntax
|
|
133
|
-
to ? (actual, test), or ? (test) */
|
|
134
198
|
// When there is a .from that's a function,
|
|
135
199
|
// the actual is the result of calling it,
|
|
136
200
|
// given everything that might be needed.
|
|
137
201
|
if (test.from instanceof Function) {
|
|
138
202
|
// Actually supplying value.
|
|
139
|
-
return test.from(test
|
|
203
|
+
return test.from(test, test.actual);
|
|
140
204
|
}
|
|
141
205
|
|
|
142
206
|
// Only property names and functions make sense to support.
|
|
143
207
|
throw new Error("The test.from value was not a usable type. It must be either a property name or a function to work.");
|
|
144
208
|
}
|
|
145
209
|
|
|
146
|
-
#compare(expected, actual) /* verified */ {
|
|
147
|
-
return this.#comparer.compare(expected, actual);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
210
|
// endregion Dependencies of test stages
|
|
151
|
-
|
|
152
211
|
}
|
package/system/TestSummary.js
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
/**/
|
|
2
|
-
|
|
3
|
-
export default class TestSummary {
|
|
4
|
-
#summary;
|
|
5
|
-
#allDidPass;
|
|
6
|
-
#anyWereRun;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this
|
|
34
|
-
this
|
|
35
|
-
this
|
|
36
|
-
|
|
37
|
-
}
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
export default class TestSummary {
|
|
4
|
+
#summary;
|
|
5
|
+
#allDidPass;
|
|
6
|
+
#anyWereRun;
|
|
7
|
+
|
|
8
|
+
#anyThrows;
|
|
9
|
+
#anyTroubles;
|
|
10
|
+
|
|
11
|
+
get summary() {
|
|
12
|
+
return this.#summary;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get allDidPass() {
|
|
16
|
+
return this.#allDidPass;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get anyWereRun() {
|
|
20
|
+
return this.#anyWereRun;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get anyThrows() {
|
|
24
|
+
return this.#anyThrows;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get anyTroubles() {
|
|
28
|
+
return this.#anyTroubles;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor(summary, allDidPass, anyWereRun, anyThrows, anyTroubles) {
|
|
32
|
+
this.#summary = summary;
|
|
33
|
+
this.#allDidPass = allDidPass;
|
|
34
|
+
this.#anyWereRun = anyWereRun;
|
|
35
|
+
this.#anyThrows = anyThrows;
|
|
36
|
+
this.#anyTroubles = anyTroubles;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/system/TotalComparer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**/
|
|
2
2
|
|
|
3
3
|
import ATestSource from "./ATestSource.js";
|
|
4
|
+
import ObjectAnalyzer from "./ObjectAnalyzer.js";
|
|
4
5
|
|
|
5
6
|
export default class TotalComparer {
|
|
6
7
|
constructor() {
|
|
@@ -65,26 +66,31 @@ export default class TotalComparer {
|
|
|
65
66
|
if (!(actual instanceof Object)) {
|
|
66
67
|
return false;
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
|
|
70
|
+
let expectedNames = ObjectAnalyzer.allProperties(expected);
|
|
71
|
+
let actualNames = ObjectAnalyzer.allProperties(actual);
|
|
72
|
+
|
|
73
|
+
// Objects with different numbers of properties can't be the same.
|
|
74
|
+
if (expectedNames.length !== actualNames.length) {
|
|
75
|
+
return false;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!(propertyName in actual)) {
|
|
78
|
+
// O(n) algorithm covers all other cases, including recursion.
|
|
79
|
+
for (let at = 0; at < expectedNames.length; at++) {
|
|
80
|
+
// Objects with differing property names can't be the same.
|
|
81
|
+
if (expectedNames[at] !== actualNames[at]) {
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
|
|
85
|
+
// Same property name in both objects.
|
|
86
|
+
let name = expectedNames[at];
|
|
87
|
+
|
|
88
|
+
// Individual props, or their throws if getters that fail.
|
|
89
|
+
let paired = this.#retrieveExpectedAndActualProps(name, expected, actual);
|
|
90
|
+
|
|
85
91
|
// Cross-recursion.
|
|
86
|
-
let areEqual = this.#recurse(expected
|
|
87
|
-
|
|
92
|
+
let areEqual = this.#recurse(paired.expected, paired.actual);
|
|
93
|
+
|
|
88
94
|
// Exit only at first false.
|
|
89
95
|
switch (areEqual) {
|
|
90
96
|
case true: continue;
|
|
@@ -95,7 +101,32 @@ export default class TotalComparer {
|
|
|
95
101
|
// No mismatched or missing found.
|
|
96
102
|
return true;
|
|
97
103
|
}
|
|
98
|
-
|
|
104
|
+
|
|
105
|
+
/* Dependency of #recurseOverObjects(). */
|
|
106
|
+
#retrieveExpectedAndActualProps(name, expected, actual) {
|
|
107
|
+
/* Expected and actual prop values are retrieved in individual
|
|
108
|
+
try-catches because getters may fail, and this way,
|
|
109
|
+
any fails can be compared as indicators of overall state. */
|
|
110
|
+
|
|
111
|
+
let paired = { };
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
paired.expected = expected[name];
|
|
115
|
+
}
|
|
116
|
+
catch (thrown) {
|
|
117
|
+
paired.expected = thrown;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
paired.actual = actual[name];
|
|
122
|
+
}
|
|
123
|
+
catch (thrown) {
|
|
124
|
+
paired.actual = thrown;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return paired;
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
#recurseOverMaps(expected, actual) /* verified */ {
|
|
100
131
|
// Both must be Maps.
|
|
101
132
|
if (!(actual instanceof Map)) {
|
|
@@ -229,7 +260,7 @@ export default class TotalComparer {
|
|
|
229
260
|
}
|
|
230
261
|
|
|
231
262
|
#compareFunctions(expected, actual) /* verified */ {
|
|
232
|
-
//
|
|
263
|
+
// 'expected' is already known to be a function / method.
|
|
233
264
|
if (!(actual instanceof Function)) {
|
|
234
265
|
return false;
|
|
235
266
|
}
|
|
@@ -253,6 +284,7 @@ export default class TotalComparer {
|
|
|
253
284
|
}
|
|
254
285
|
|
|
255
286
|
#compareDates(expected, actual) /* verified */ {
|
|
287
|
+
// 'expected' is already known to be a Date.
|
|
256
288
|
if (!(actual instanceof Date)) {
|
|
257
289
|
return false;
|
|
258
290
|
}
|
|
@@ -261,6 +293,11 @@ export default class TotalComparer {
|
|
|
261
293
|
}
|
|
262
294
|
|
|
263
295
|
#compareErrors(expected, actual) /* verified */ {
|
|
296
|
+
// 'expected' is already known to be an Error.
|
|
297
|
+
if (!(actual instanceof Error)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
264
301
|
/* Compares Errors and subclasses accurately apart from .stack,
|
|
265
302
|
which is ignored because it rarely makes sense to compare it. */
|
|
266
303
|
|
package/system/TotalDisplayer.js
CHANGED
|
@@ -3,23 +3,27 @@
|
|
|
3
3
|
import TypeIdentifier from "./TypeIdentifier.js";
|
|
4
4
|
import Types from "./Types.js";
|
|
5
5
|
import ATestSource from "./ATestSource.js";
|
|
6
|
+
import ObjectAnalyzer from "./ObjectAnalyzer.js";
|
|
6
7
|
|
|
7
8
|
export default class TotalDisplayer {
|
|
8
|
-
|
|
9
|
+
static defaultToString = "[object Object]";
|
|
10
|
+
|
|
11
|
+
display(value) /* passed */ {
|
|
9
12
|
let typeId = TypeIdentifier.identify(value);
|
|
10
13
|
|
|
11
14
|
switch (typeId) {
|
|
15
|
+
case Types.isUndefined: return this.displayUndefined(value);
|
|
12
16
|
case Types.isValue: return this.displayValue(value);
|
|
13
17
|
case Types.isString: return this.displayString(value);
|
|
14
18
|
case Types.isArray: return this.displayArray(value);
|
|
15
19
|
case Types.isMap: return this.displayMap(value);
|
|
16
20
|
case Types.isSet: return this.displaySet(value);
|
|
21
|
+
case Types.isFile: return this.displayFile(value);
|
|
17
22
|
case Types.isError: return this.displayError(value);
|
|
18
23
|
case Types.isClass: return this.displayClass(value);
|
|
19
24
|
case Types.isFunction: return this.displayFunction(value);
|
|
20
25
|
case Types.isObject: return this.displayObject(value);
|
|
21
26
|
case Types.isDate: return this.displayValue(value);
|
|
22
|
-
case Types.isUndefined: return this.displayUndefined(value);
|
|
23
27
|
case Types.isUndefSymbol: return this.displayUndefined(value);
|
|
24
28
|
case Types.isNull: return this.displayNull(value);
|
|
25
29
|
}
|
|
@@ -82,6 +86,10 @@ export default class TotalDisplayer {
|
|
|
82
86
|
return output;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
displayFile(value) /* passed */ {
|
|
90
|
+
return `File{name:"${ value.name }",size:${ value.size }}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
displayError(value) /* passed */ {
|
|
86
94
|
// Subtype can't be found with typeof.
|
|
87
95
|
let type = value.name;
|
|
@@ -125,13 +133,28 @@ export default class TotalDisplayer {
|
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
displayObject(value) /* passed */ {
|
|
136
|
+
// If a custom toString() exists, use its output.
|
|
137
|
+
let asString = value.toString();
|
|
138
|
+
|
|
139
|
+
if (asString !== TotalDisplayer.defaultToString) {
|
|
140
|
+
return asString;
|
|
141
|
+
}
|
|
142
|
+
|
|
128
143
|
// Each property of the object is handled individually.
|
|
129
144
|
let items = [ ];
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
let props = ObjectAnalyzer.allProperties(value);
|
|
147
|
+
|
|
148
|
+
for (let prop of props) {
|
|
149
|
+
// Cross-recursion. Try-catch because getters can fail.
|
|
150
|
+
try {
|
|
151
|
+
let item = this.display(value[prop]);
|
|
152
|
+
items.push(`${ prop }:${ item }`);
|
|
153
|
+
}
|
|
154
|
+
catch (e)
|
|
155
|
+
{
|
|
156
|
+
items.push(`${ prop }:(threw)`);
|
|
157
|
+
}
|
|
135
158
|
}
|
|
136
159
|
|
|
137
160
|
// Properties are all listed together.
|
package/system/TypeAnalyzer.js
CHANGED
|
@@ -45,40 +45,131 @@ export default class TypeAnalyzer {
|
|
|
45
45
|
/* Returns true if instance or static member is a method.
|
|
46
46
|
Returns false if member is any kind of property. */
|
|
47
47
|
static isMethodMember(type, name) /* passed */ {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
return !TypeAnalyzer.isPropertyMember(type, name);
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/* Returns true if instance or static member is a property
|
|
52
|
+
(value or accessor). Returns false if member is a method. */
|
|
53
|
+
static isPropertyMember(type, name) /* passed */ {
|
|
54
|
+
/* Type and supertypes are tested directly for static props,
|
|
55
|
+
and their definition texts are read for instance props. */
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
let isInstanceMethod = anyInstanceType === Types.isFunction;
|
|
57
|
-
let isStaticMethod = anyStaticType === Types.isFunction;
|
|
57
|
+
let isProperty = this.#typeChainIncludesAsProperty(type, name);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
return isProperty;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// region Dependencies of isPropertyMember()
|
|
63
|
+
|
|
64
|
+
static #typeChainIncludesAsProperty(type, name) /* verified */ {
|
|
65
|
+
let prototype = type.prototype;
|
|
66
|
+
|
|
67
|
+
// Rootward traversal of the prototype chain.
|
|
68
|
+
while (prototype !== null) {
|
|
69
|
+
// Type is needed, rather than prototype.
|
|
70
|
+
type = prototype.constructor;
|
|
71
|
+
|
|
72
|
+
// First line finds any static props; second, any instance props.
|
|
73
|
+
let isProperty
|
|
74
|
+
= TypeAnalyzer.#typeDefinitionIncludesAsProperty(type, name)
|
|
75
|
+
|| TypeAnalyzer.#typeTextContainsNameAsProperty(type, name);
|
|
76
|
+
|
|
77
|
+
// Was found as static or instance.
|
|
78
|
+
if (isProperty) {
|
|
79
|
+
return true;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
// Parent class, or null at root.
|
|
83
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
// Never found.
|
|
87
|
+
return false;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
static #typeDefinitionIncludesAsProperty(type, name) /* verified */ {
|
|
91
|
+
let named = Object.getOwnPropertyDescriptor(type, name);
|
|
92
|
+
|
|
93
|
+
// If not found, try parent.
|
|
94
|
+
if (named === undefined) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Members with getters are properties.
|
|
99
|
+
if (named.get !== undefined) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Members with function values are methods.
|
|
104
|
+
if (named.value instanceof Function) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Members with any other values are properties.
|
|
109
|
+
if (named.value !== undefined) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static #typeTextContainsNameAsProperty(type, name) /* verified */ {
|
|
115
|
+
let text = type.toString();
|
|
116
|
+
|
|
117
|
+
// Instance properties are always defined as 'name =',
|
|
118
|
+
// 'name;', or 'get name()' at the start of a line.
|
|
119
|
+
let valueRegex = new RegExp(`^\\s*${ name }\\s*[=;]`, "m");
|
|
120
|
+
let accessorRegex = new RegExp(`^\\s*get ${ name }\\(\\)`, "m");
|
|
121
|
+
|
|
122
|
+
// Conditional execution of the second test() call.
|
|
123
|
+
let doesContain = valueRegex.test(text) || accessorRegex.test(text);
|
|
124
|
+
return doesContain;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// endregion Dependencies of isPropertyMember()
|
|
128
|
+
|
|
129
|
+
static allGetters(type) /* passed */ {
|
|
130
|
+
return TypeAnalyzer.#allTypeChainGetters(type);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// region Dependencies of allGetters()
|
|
134
|
+
|
|
135
|
+
static #allTypeChainGetters(type) /* verified */ {
|
|
136
|
+
let output = [];
|
|
137
|
+
|
|
138
|
+
let prototype = type.prototype;
|
|
139
|
+
|
|
140
|
+
// Rootward traversal of the prototype chain.
|
|
141
|
+
while (prototype !== null) {
|
|
142
|
+
// Type is needed, rather than prototype.
|
|
143
|
+
type = prototype.constructor;
|
|
144
|
+
|
|
145
|
+
// Any getters at this level.
|
|
146
|
+
let local = TypeAnalyzer.#gettersInTypeText(type);
|
|
147
|
+
output.push(...local);
|
|
148
|
+
|
|
149
|
+
// Parent class, or null at root.
|
|
150
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static #gettersInTypeText(type) /* verified */ {
|
|
157
|
+
let text = type.toString();
|
|
158
|
+
|
|
159
|
+
// At start of line's text, get + name + ().
|
|
160
|
+
let getterRegex = /^\s*get\s+(\w+)\(\)/gm;
|
|
161
|
+
|
|
162
|
+
// All getters and their names
|
|
163
|
+
// as an array of arrays.
|
|
164
|
+
let raw = text.matchAll(getterRegex);
|
|
165
|
+
raw = [ ...raw ];
|
|
166
|
+
|
|
167
|
+
// First capturing group is name.
|
|
168
|
+
let names = raw.map(x => x[1]);
|
|
169
|
+
|
|
170
|
+
return names;
|
|
83
171
|
}
|
|
172
|
+
|
|
173
|
+
// endregion Dependencies of allGetters()
|
|
174
|
+
|
|
84
175
|
}
|
package/system/TypeIdentifier.js
CHANGED
|
@@ -14,27 +14,31 @@ export default class TypeIdentifier {
|
|
|
14
14
|
if (Array.isArray(value)) {
|
|
15
15
|
return Types.isArray;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
if (value instanceof Map) {
|
|
19
19
|
return Types.isMap;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
if (value instanceof Set) {
|
|
23
23
|
return Types.isSet;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
if (value instanceof File) {
|
|
27
|
+
return Types.isFile;
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
if (value instanceof Date) {
|
|
27
31
|
return Types.isDate;
|
|
28
32
|
}
|
|
29
|
-
|
|
33
|
+
|
|
30
34
|
if (value instanceof Error) {
|
|
31
35
|
return Types.isError;
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
if (value === null) {
|
|
35
39
|
return Types.isNull;
|
|
36
40
|
}
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
return Types.isObject;
|
|
39
43
|
}
|
|
40
44
|
|