risei 1.3.0 → 1.3.1
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 +18 -13
- package/package.json +2 -2
- package/system/SpoofClassMethodsFixture.js +4 -4
- package/system/TestDefinition.js +1 -2
- package/system/TestFrame.js +1 -0
- package/system/TotalCopier.js +106 -103
- package/system/TypeAnalyzer.js +103 -103
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ You can save a lot of time by putting repeated test properties into an object on
|
|
|
125
125
|
|
|
126
126
|
- This _collapsing forward_ works for just about every test property, but is reset when you change the class in `.on`, when you give the property a new value, or you erase it with an empty array `[ ]`.
|
|
127
127
|
|
|
128
|
-
-
|
|
128
|
+
- When the tested code mutates the contents of `.with` or `.in`, provide these args as the output of a function or method, because at present, the mutated values are collapsed forward.
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
|
|
@@ -254,9 +254,8 @@ There's plenty more to learn about Risei and how it makes testing easy...
|
|
|
254
254
|
- Changing the tested class in `.on` wipes out all prior test properties, so the new class has a fresh start.
|
|
255
255
|
- Changing the tested method in `.of` wipes out only test properties related to methods, to preserve class values you usually want.
|
|
256
256
|
|
|
257
|
-
- Individual tests remain
|
|
258
|
-
-
|
|
259
|
-
- Class instances, spoofs (covered later), and so on are all created anew for each test.
|
|
257
|
+
- Individual tests remain isolated, except for when args are mutable.
|
|
258
|
+
- To use the original, un-mutated args for successive tests, just return them from a function or method to set `.with` or `.in`.
|
|
260
259
|
|
|
261
260
|
|
|
262
261
|
|
|
@@ -551,27 +550,33 @@ Most problems with using Risei are minor mistakes in syntax, or omissions in tes
|
|
|
551
550
|
| `"Test loading failed for... SyntaxError: Unexpected token ':'"` | Missing comma in your tests in the named file |
|
|
552
551
|
| `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
|
|
553
552
|
| Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
|
|
554
|
-
| Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]
|
|
555
|
-
|
|
553
|
+
| Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]` |
|
|
554
|
+
| Unexpected actual values, or unexpected passes / fails in test runs | Pre-listed test properties have produced extra tests; changing properties as part of new tests prevents this |
|
|
555
|
+
| Unexpected actual values, or unexpected passes / fails in test runs | `.with` or `.in` values were mutated by a test and passed to subsequent tests; providing contents of these properties with a function prevents this |
|
|
556
556
|
|
|
557
557
|
|
|
558
558
|
|
|
559
559
|
## Known issues and workarounds
|
|
560
560
|
|
|
561
|
-
|
|
561
|
+
Two issues are known to exist:
|
|
562
562
|
|
|
563
|
-
-
|
|
564
|
-
-
|
|
565
|
-
|
|
566
|
-
- In this case, Rs is actually working properly, running prior tests with the new properties swapped in.
|
|
567
|
-
- To avoid this problem, include changed properties in full tests to run, or restate `.of` along with the changed properties.
|
|
563
|
+
- If the contents of `.with` or `.in` are mutated by the code being tested, the mutated values are carried from test to test.
|
|
564
|
+
- To use the original, un-mutated args for successive tests, just return them from a function or method, and call it to set `.with` or `.in`.
|
|
565
|
+
- Ways to prevent this are being looked at.
|
|
568
566
|
|
|
569
|
-
|
|
567
|
+
|
|
568
|
+
- Changing one or more properties in objects by themselves in the middle of tests (perhaps to emphasize them) can create extra tests.
|
|
569
|
+
- The extra tests usually fail, because they have an unintended mix of old and new properties.
|
|
570
|
+
- In this case, Rs is actually working properly, running prior tests with the new properties swapped in.
|
|
571
|
+
- To avoid this problem, include changed properties in full tests to run, or restate `.of` along with the changed properties.
|
|
572
|
+
- This situation is rarely an issue, but ways to optionally prevent it are being considered.
|
|
570
573
|
|
|
571
574
|
|
|
572
575
|
|
|
573
576
|
## What's new in Risei
|
|
574
577
|
|
|
578
|
+
- Release **1.3.1** (February, 2024) reverses some changes in 1.3.0 that were intended to fully isolate tests against mutable args, but which caused incomplete or inaccurate object contents in some cases. Later work may restore full isolation in a different way at some point.
|
|
579
|
+
|
|
575
580
|
- Release **1.3.0** (February, 2024) moves the display of test-loading error messages to a new gold bar that appears at the bottom of the summary when there are any loading errors. A sorting error that occurs when no test files exist yet has also been fixed.
|
|
576
581
|
|
|
577
582
|
- Release **1.2.0** (January, 2024) changes sorting of test files. Previously they were displayed in the order found. Now the order remains the same, except that the last-edited file is moved to the bottom, making it easy to see the latest test results.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "risei",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Risei is the framework that allows you to write unit tests as collections of values in JavaScript objects, so it's easy and fast, and tests don't serve as a drag on redesigns.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unit test",
|
|
@@ -48,6 +48,6 @@
|
|
|
48
48
|
"fs": "^0.0.1-security",
|
|
49
49
|
"mocha": "^10.0.0",
|
|
50
50
|
"morgan": "~1.9.1",
|
|
51
|
-
"risei": "1.
|
|
51
|
+
"risei": "1.3.1"
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -53,10 +53,10 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Converting to scope object type.
|
|
56
|
-
let
|
|
56
|
+
let spoofDefinitions = SpoofDefinition.fromNonceTuples(spoofNonces);
|
|
57
57
|
|
|
58
58
|
// Storing spoofing definitions.
|
|
59
|
-
this.#retainSpoofsByClass(type,
|
|
59
|
+
this.#retainSpoofsByClass(type, spoofDefinitions);
|
|
60
60
|
|
|
61
61
|
// Storing originals for restoring later.
|
|
62
62
|
this.#reserveOriginalsByClass();
|
|
@@ -67,9 +67,9 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
|
|
|
67
67
|
|
|
68
68
|
// region Dependencies of spoof()
|
|
69
69
|
|
|
70
|
-
#retainSpoofsByClass(type,
|
|
70
|
+
#retainSpoofsByClass(type, spoofDefinitions) {
|
|
71
71
|
// Map of Maps: by type, then by method.
|
|
72
|
-
for (let tuple of
|
|
72
|
+
for (let tuple of spoofDefinitions) {
|
|
73
73
|
// If no .target, use `type`.
|
|
74
74
|
if (!tuple.target) {
|
|
75
75
|
tuple.target = type;
|
package/system/TestDefinition.js
CHANGED
package/system/TestFrame.js
CHANGED
package/system/TotalCopier.js
CHANGED
|
@@ -1,103 +1,106 @@
|
|
|
1
|
-
/**/
|
|
2
|
-
|
|
3
|
-
import { TypeIdentifier } from "./TypeIdentifier.js";
|
|
4
|
-
import { Types } from "./Types.js";
|
|
5
|
-
|
|
6
|
-
export class TotalCopier {
|
|
7
|
-
#identifier = new TypeIdentifier();
|
|
8
|
-
|
|
9
|
-
copy(original) /* passed */ {
|
|
10
|
-
return this.recursiveCopy(original);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
recursiveCopy(original) /* verified */ {
|
|
14
|
-
/* &cruft, factor, possibly abstracting similar code,
|
|
15
|
-
and/or return copy only once, or similar */
|
|
16
|
-
|
|
17
|
-
/* Algorithm: First copied at current level by value if a value, by reference if
|
|
18
|
-
not a value; then recursively replaced at next level, and so on. */
|
|
19
|
-
|
|
20
|
-
let copy;
|
|
21
|
-
|
|
22
|
-
let type = this.#identifier.identify(original);
|
|
23
|
-
|
|
24
|
-
if (type === Types.isArray) {
|
|
25
|
-
copy = [ ];
|
|
26
|
-
|
|
27
|
-
// Traversal construction with recursion.
|
|
28
|
-
for (let item of original) {
|
|
29
|
-
let next = this.recursiveCopy(item);
|
|
30
|
-
copy.push(next);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return copy;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (type === Types.isMap) {
|
|
37
|
-
copy = new Map();
|
|
38
|
-
|
|
39
|
-
// Traversal construction with recursion.
|
|
40
|
-
for (let entry of original.entries()) {
|
|
41
|
-
let next = this.recursiveCopy(entry);
|
|
42
|
-
copy.set(next[0], next[1]);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return copy;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (type === Types.isSet) {
|
|
49
|
-
copy = new Set();
|
|
50
|
-
|
|
51
|
-
// Traversal construction with recursion.
|
|
52
|
-
for (let value of original.values()) {
|
|
53
|
-
let next = this.recursiveCopy(value);
|
|
54
|
-
copy.add(next);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return copy;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (type === Types.isDate) {
|
|
61
|
-
copy = new Date(original);
|
|
62
|
-
return copy;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Actually copying isn't needed (or desirable) here,
|
|
66
|
-
// since should always point to same thing regardless.
|
|
67
|
-
if (type === Types.isClass) {
|
|
68
|
-
copy = original;
|
|
69
|
-
return copy;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// The workaround here makes a true independent copy of the
|
|
73
|
-
// original function, regardless of its definition style.
|
|
74
|
-
if (type === Types.isFunction) {
|
|
75
|
-
let passer = [ original ];
|
|
76
|
-
passer = [ ...passer ];
|
|
77
|
-
copy = passer[0];
|
|
78
|
-
|
|
79
|
-
return copy;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (original instanceof Object) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { TypeIdentifier } from "./TypeIdentifier.js";
|
|
4
|
+
import { Types } from "./Types.js";
|
|
5
|
+
|
|
6
|
+
export class TotalCopier {
|
|
7
|
+
#identifier = new TypeIdentifier();
|
|
8
|
+
|
|
9
|
+
copy(original) /* passed */ {
|
|
10
|
+
return this.recursiveCopy(original);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
recursiveCopy(original) /* verified */ {
|
|
14
|
+
/* &cruft, factor, possibly abstracting similar code,
|
|
15
|
+
and/or return copy only once, or similar */
|
|
16
|
+
|
|
17
|
+
/* Algorithm: First copied at current level by value if a value, by reference if
|
|
18
|
+
not a value; then recursively replaced at next level, and so on. */
|
|
19
|
+
|
|
20
|
+
let copy;
|
|
21
|
+
|
|
22
|
+
let type = this.#identifier.identify(original);
|
|
23
|
+
|
|
24
|
+
if (type === Types.isArray) {
|
|
25
|
+
copy = [ ];
|
|
26
|
+
|
|
27
|
+
// Traversal construction with recursion.
|
|
28
|
+
for (let item of original) {
|
|
29
|
+
let next = this.recursiveCopy(item);
|
|
30
|
+
copy.push(next);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return copy;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (type === Types.isMap) {
|
|
37
|
+
copy = new Map();
|
|
38
|
+
|
|
39
|
+
// Traversal construction with recursion.
|
|
40
|
+
for (let entry of original.entries()) {
|
|
41
|
+
let next = this.recursiveCopy(entry);
|
|
42
|
+
copy.set(next[0], next[1]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return copy;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (type === Types.isSet) {
|
|
49
|
+
copy = new Set();
|
|
50
|
+
|
|
51
|
+
// Traversal construction with recursion.
|
|
52
|
+
for (let value of original.values()) {
|
|
53
|
+
let next = this.recursiveCopy(value);
|
|
54
|
+
copy.add(next);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return copy;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (type === Types.isDate) {
|
|
61
|
+
copy = new Date(original);
|
|
62
|
+
return copy;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Actually copying isn't needed (or desirable) here,
|
|
66
|
+
// since should always point to same thing regardless.
|
|
67
|
+
if (type === Types.isClass) {
|
|
68
|
+
copy = original;
|
|
69
|
+
return copy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The workaround here makes a true independent copy of the
|
|
73
|
+
// original function, regardless of its definition style.
|
|
74
|
+
if (type === Types.isFunction) {
|
|
75
|
+
let passer = [ original ];
|
|
76
|
+
passer = [ ...passer ];
|
|
77
|
+
copy = passer[0];
|
|
78
|
+
|
|
79
|
+
return copy;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (original instanceof Object) {
|
|
83
|
+
// If original has a prototype, it's probably a class instance,
|
|
84
|
+
// so it's constructed, so it includes all methods and accessors.
|
|
85
|
+
let prototype = Object.getPrototypeOf(original);
|
|
86
|
+
copy = prototype !== null ? new prototype.constructor() : { };
|
|
87
|
+
|
|
88
|
+
let keys = Object.keys(original);
|
|
89
|
+
|
|
90
|
+
// Settable properties are added, with recursion.
|
|
91
|
+
for (let key of Object.keys(original)) {
|
|
92
|
+
let next = this.recursiveCopy(original[key]);
|
|
93
|
+
copy[key] = next;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return copy;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* &cruft, other object types here */
|
|
100
|
+
|
|
101
|
+
// All object types exhausted, so this is a value,
|
|
102
|
+
// which can be used directly for a copy.
|
|
103
|
+
copy = original;
|
|
104
|
+
return copy;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/system/TypeAnalyzer.js
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
/**/
|
|
2
|
-
|
|
3
|
-
/* Used to analyze classes / members of classes being tested. */
|
|
4
|
-
|
|
5
|
-
export class TypeAnalyzer {
|
|
6
|
-
// region Definitions
|
|
7
|
-
|
|
8
|
-
static constructorName = "constructor";
|
|
9
|
-
static getName = "get";
|
|
10
|
-
static setName = "set";
|
|
11
|
-
|
|
12
|
-
// endregion Definitions
|
|
13
|
-
|
|
14
|
-
// region Fields
|
|
15
|
-
|
|
16
|
-
#type;
|
|
17
|
-
|
|
18
|
-
// endregion Fields
|
|
19
|
-
|
|
20
|
-
constructor(type) {
|
|
21
|
-
this.#type = type;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/* Returns true if instance or static member is a property (value or accessor). Returns false if member is a method. */
|
|
25
|
-
memberIsProperty(name) /* passed */ {
|
|
26
|
-
// Constructor is never a property and is irregular,
|
|
27
|
-
// so other type-analysis code can't handle it.
|
|
28
|
-
if (name === TypeAnalyzer.constructorName) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Static methods and properties (value or accessor) are on type.
|
|
33
|
-
if (name in this.#type) {
|
|
34
|
-
return this.#staticMemberIsProperty(this.#type, name);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Methods and accessor properties are on prototype.
|
|
38
|
-
if (name in this.#type.prototype) {
|
|
39
|
-
return this.#instanceMemberIsProperty(this.#type.prototype, name);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Instance value properties are not on type or prototype.
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// region Dependencies of memberIsProperty()
|
|
47
|
-
|
|
48
|
-
/* Algorithm specialized with function check for static members. */
|
|
49
|
-
#staticMemberIsProperty(type, name) {
|
|
50
|
-
let descriptor = Object.getOwnPropertyDescriptor(type, name);
|
|
51
|
-
|
|
52
|
-
// If on type and has accessors, it's a static accessor property.
|
|
53
|
-
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// If on type and has non-Function .value, it's a static value property.
|
|
58
|
-
if (this.#doesNotHaveFunctionValue(descriptor)) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// If on type but not a static property, it's a static method.
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* Algorithm specialized for instance members, no function check. */
|
|
67
|
-
#instanceMemberIsProperty(prototype, name) {
|
|
68
|
-
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
69
|
-
|
|
70
|
-
// If on prototype and has accessors, it's an accessor property.
|
|
71
|
-
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// If on prototype but not an accessor property, it's a method.
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// region Internally reused dependencies of other memberIsProperty() dependencies
|
|
80
|
-
|
|
81
|
-
#doesHaveAccessorProps(descriptor) /* verified */ {
|
|
82
|
-
// Accessor properties have one or both of these in descriptor.
|
|
83
|
-
if (TypeAnalyzer.getName in descriptor || TypeAnalyzer.setName in descriptor) {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#doesNotHaveFunctionValue(descriptor) /* verified */ {
|
|
91
|
-
// Simple inverter of other method for readability.
|
|
92
|
-
return !this.#doesHaveFunctionValue(descriptor);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#doesHaveFunctionValue(descriptor) /* verified */ {
|
|
96
|
-
// If no member, .value of undefined.
|
|
97
|
-
return descriptor.value instanceof Function;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// endregion Internally reused dependencies of other memberIsProperty() dependencies
|
|
101
|
-
|
|
102
|
-
// endregion Dependencies of memberIsProperty()
|
|
103
|
-
}
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
/* Used to analyze classes / members of classes being tested. */
|
|
4
|
+
|
|
5
|
+
export class TypeAnalyzer {
|
|
6
|
+
// region Definitions
|
|
7
|
+
|
|
8
|
+
static constructorName = "constructor";
|
|
9
|
+
static getName = "get";
|
|
10
|
+
static setName = "set";
|
|
11
|
+
|
|
12
|
+
// endregion Definitions
|
|
13
|
+
|
|
14
|
+
// region Fields
|
|
15
|
+
|
|
16
|
+
#type;
|
|
17
|
+
|
|
18
|
+
// endregion Fields
|
|
19
|
+
|
|
20
|
+
constructor(type) {
|
|
21
|
+
this.#type = type;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Returns true if instance or static member is a property (value or accessor). Returns false if member is a method. */
|
|
25
|
+
memberIsProperty(name) /* passed */ {
|
|
26
|
+
// Constructor is never a property and is irregular,
|
|
27
|
+
// so other type-analysis code can't handle it.
|
|
28
|
+
if (name === TypeAnalyzer.constructorName) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Static methods and properties (value or accessor) are on type.
|
|
33
|
+
if (name in this.#type) {
|
|
34
|
+
return this.#staticMemberIsProperty(this.#type, name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Methods and accessor properties are on prototype.
|
|
38
|
+
if (name in this.#type.prototype) {
|
|
39
|
+
return this.#instanceMemberIsProperty(this.#type.prototype, name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Instance value properties are not on type or prototype.
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// region Dependencies of memberIsProperty()
|
|
47
|
+
|
|
48
|
+
/* Algorithm specialized with function check for static members. */
|
|
49
|
+
#staticMemberIsProperty(type, name) {
|
|
50
|
+
let descriptor = Object.getOwnPropertyDescriptor(type, name);
|
|
51
|
+
|
|
52
|
+
// If on type and has accessors, it's a static accessor property.
|
|
53
|
+
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If on type and has non-Function .value, it's a static value property.
|
|
58
|
+
if (this.#doesNotHaveFunctionValue(descriptor)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If on type but not a static property, it's a static method.
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Algorithm specialized for instance members, no function check. */
|
|
67
|
+
#instanceMemberIsProperty(prototype, name) {
|
|
68
|
+
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
69
|
+
|
|
70
|
+
// If on prototype and has accessors, it's an accessor property.
|
|
71
|
+
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If on prototype but not an accessor property, it's a method.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// region Internally reused dependencies of other memberIsProperty() dependencies
|
|
80
|
+
|
|
81
|
+
#doesHaveAccessorProps(descriptor) /* verified */ {
|
|
82
|
+
// Accessor properties have one or both of these in descriptor.
|
|
83
|
+
if (TypeAnalyzer.getName in descriptor || TypeAnalyzer.setName in descriptor) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#doesNotHaveFunctionValue(descriptor) /* verified */ {
|
|
91
|
+
// Simple inverter of other method for readability.
|
|
92
|
+
return !this.#doesHaveFunctionValue(descriptor);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#doesHaveFunctionValue(descriptor) /* verified */ {
|
|
96
|
+
// If no member, .value of undefined.
|
|
97
|
+
return descriptor.value instanceof Function;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// endregion Internally reused dependencies of other memberIsProperty() dependencies
|
|
101
|
+
|
|
102
|
+
// endregion Dependencies of memberIsProperty()
|
|
103
|
+
}
|