@maroonedog/luq 2.1.0 → 2.2.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 +269 -52
- package/dist/builder/builder-surface.types.d.ts +1 -0
- package/dist/builder/create-builder.js +9 -0
- package/dist/builder/create-builder.mjs +9 -0
- package/dist/builder/field-builder.types.d.ts +13 -0
- package/dist/chain/bundle-paths.types.d.ts +26 -0
- package/dist/chain/bundle-paths.types.js +2 -0
- package/dist/chain/bundle-paths.types.mjs +1 -0
- package/dist/chain/chain-method.types.d.ts +8 -3
- package/dist/chain/resolve-args.types.d.ts +4 -2
- package/dist/compile/compile-array-node.d.ts +0 -7
- package/dist/compile/compile-array-node.js +4 -0
- package/dist/compile/compile-array-node.mjs +4 -0
- package/dist/compile/compile-field.js +10 -1
- package/dist/compile/compile-field.mjs +10 -1
- package/dist/compile/split-rules-by-kind.js +31 -7
- package/dist/compile/split-rules-by-kind.mjs +31 -7
- package/dist/compile/validation-plan.types.d.ts +23 -0
- package/dist/json-schema/flatten-schema.js +1 -1
- package/dist/json-schema/flatten-schema.mjs +1 -1
- package/dist/plugin-kit/marker.types.d.ts +18 -0
- package/dist/plugins/index.generated.d.ts +1 -0
- package/dist/plugins/index.generated.js +4 -2
- package/dist/plugins/index.generated.mjs +1 -0
- package/dist/plugins/manifest.generated.js +1 -0
- package/dist/plugins/manifest.generated.mjs +1 -0
- package/dist/plugins/stitch/stitch.d.ts +28 -8
- package/dist/plugins/stitch-with/index.d.ts +2 -0
- package/dist/plugins/stitch-with/index.js +5 -0
- package/dist/plugins/stitch-with/index.mjs +1 -0
- package/dist/plugins/stitch-with/stitch-with.d.ts +12 -0
- package/dist/plugins/stitch-with/stitch-with.js +94 -0
- package/dist/plugins/stitch-with/stitch-with.mjs +91 -0
- package/dist/plugins/stitchWith.d.ts +1 -0
- package/dist/plugins/stitchWith.js +2 -0
- package/dist/plugins/stitchWith.mjs +1 -0
- package/dist/plugins/string-min/string-min.js +21 -3
- package/dist/plugins/string-min/string-min.mjs +22 -4
- package/dist/presets/index.d.ts +1 -0
- package/dist/presets/index.js +9 -0
- package/dist/presets/index.mjs +1 -0
- package/dist/presets/presets.d.ts +165 -0
- package/dist/presets/presets.js +80 -0
- package/dist/presets/presets.mjs +77 -0
- package/dist/runtime/create-field-validator.js +24 -8
- package/dist/runtime/create-field-validator.mjs +24 -8
- package/dist/runtime/create-validator.js +38 -8
- package/dist/runtime/create-validator.mjs +38 -8
- package/dist/runtime/field-rule-context.d.ts +32 -0
- package/dist/runtime/field-rule-context.js +48 -0
- package/dist/runtime/field-rule-context.mjs +44 -0
- package/dist/runtime/index-stack.d.ts +29 -4
- package/dist/runtime/index-stack.js +76 -15
- package/dist/runtime/index-stack.mjs +76 -15
- package/dist/runtime/run-array-node.js +22 -10
- package/dist/runtime/run-array-node.mjs +22 -10
- package/dist/runtime/run-field.js +47 -17
- package/dist/runtime/run-field.mjs +47 -17
- package/dist/runtime/run-plan.js +5 -1
- package/dist/runtime/run-plan.mjs +5 -1
- package/dist/types/index.d.ts +12 -0
- package/package.json +35 -19
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FieldRuleContext = void 0;
|
|
4
|
+
class FieldRuleContext {
|
|
5
|
+
constructor(root, indices,
|
|
6
|
+
/** Built at compile time, relative to the subject this field reads from. */
|
|
7
|
+
ownPath, item, external) {
|
|
8
|
+
this.root = root;
|
|
9
|
+
this.indices = indices;
|
|
10
|
+
this.ownPath = ownPath;
|
|
11
|
+
this.item = item;
|
|
12
|
+
this.external = external;
|
|
13
|
+
/**
|
|
14
|
+
* The path once something has asked for it. `null` rather than `""` because
|
|
15
|
+
* the empty string is a real path — the root.
|
|
16
|
+
*
|
|
17
|
+
* It is remembered because the index stack underneath is MUTABLE and is
|
|
18
|
+
* popped when the element ends. A rule that kept its context and read `path`
|
|
19
|
+
* afterwards would otherwise see wherever the runner had moved on to.
|
|
20
|
+
* Reading it during the rule's own call — which is what every rule does —
|
|
21
|
+
* pins it.
|
|
22
|
+
*/
|
|
23
|
+
this.rendered = null;
|
|
24
|
+
}
|
|
25
|
+
get path() {
|
|
26
|
+
const already = this.rendered;
|
|
27
|
+
if (already !== null)
|
|
28
|
+
return already;
|
|
29
|
+
const built = this.indices.renderFieldPath(this.ownPath);
|
|
30
|
+
this.rendered = built;
|
|
31
|
+
return built;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Serialising a context is a thing people do when they log a failure, and a
|
|
35
|
+
* prototype getter would silently drop `path` from the output. This pins the
|
|
36
|
+
* JSON shape to the four DECLARED members and, as a side effect, stops the
|
|
37
|
+
* internals above from reaching a log line.
|
|
38
|
+
*/
|
|
39
|
+
toJSON() {
|
|
40
|
+
return {
|
|
41
|
+
root: this.root,
|
|
42
|
+
path: this.path,
|
|
43
|
+
item: this.item,
|
|
44
|
+
external: this.external,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.FieldRuleContext = FieldRuleContext;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class FieldRuleContext {
|
|
2
|
+
constructor(root, indices,
|
|
3
|
+
/** Built at compile time, relative to the subject this field reads from. */
|
|
4
|
+
ownPath, item, external) {
|
|
5
|
+
this.root = root;
|
|
6
|
+
this.indices = indices;
|
|
7
|
+
this.ownPath = ownPath;
|
|
8
|
+
this.item = item;
|
|
9
|
+
this.external = external;
|
|
10
|
+
/**
|
|
11
|
+
* The path once something has asked for it. `null` rather than `""` because
|
|
12
|
+
* the empty string is a real path — the root.
|
|
13
|
+
*
|
|
14
|
+
* It is remembered because the index stack underneath is MUTABLE and is
|
|
15
|
+
* popped when the element ends. A rule that kept its context and read `path`
|
|
16
|
+
* afterwards would otherwise see wherever the runner had moved on to.
|
|
17
|
+
* Reading it during the rule's own call — which is what every rule does —
|
|
18
|
+
* pins it.
|
|
19
|
+
*/
|
|
20
|
+
this.rendered = null;
|
|
21
|
+
}
|
|
22
|
+
get path() {
|
|
23
|
+
const already = this.rendered;
|
|
24
|
+
if (already !== null)
|
|
25
|
+
return already;
|
|
26
|
+
const built = this.indices.renderFieldPath(this.ownPath);
|
|
27
|
+
this.rendered = built;
|
|
28
|
+
return built;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Serialising a context is a thing people do when they log a failure, and a
|
|
32
|
+
* prototype getter would silently drop `path` from the output. This pins the
|
|
33
|
+
* JSON shape to the four DECLARED members and, as a side effect, stops the
|
|
34
|
+
* internals above from reaching a log line.
|
|
35
|
+
*/
|
|
36
|
+
toJSON() {
|
|
37
|
+
return {
|
|
38
|
+
root: this.root,
|
|
39
|
+
path: this.path,
|
|
40
|
+
item: this.item,
|
|
41
|
+
external: this.external,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import type { PathSegment } from "../path/path-segment.types";
|
|
2
1
|
export declare class IndexStack {
|
|
3
|
-
private readonly
|
|
2
|
+
private readonly nodePaths;
|
|
4
3
|
private readonly openIndices;
|
|
4
|
+
/**
|
|
5
|
+
* The rendered prefix, or `null` when it has not been asked for since the
|
|
6
|
+
* last push or pop. `null` and not `""` because the empty string is a real
|
|
7
|
+
* answer — the root.
|
|
8
|
+
*
|
|
9
|
+
* Invalidating rather than recomputing on push is the point: an element that
|
|
10
|
+
* raises no issue never renders its own prefix at all.
|
|
11
|
+
*/
|
|
12
|
+
private currentPrefix;
|
|
5
13
|
/** How many array levels are currently open. */
|
|
6
14
|
get depth(): number;
|
|
7
15
|
/** The open indices, outermost first: `[0, 2]` inside `grid[0][2]`. */
|
|
8
16
|
get indices(): readonly number[];
|
|
17
|
+
/**
|
|
18
|
+
* The prefix for the currently open frames — `""` when none are open,
|
|
19
|
+
* `items[0]` inside one, `grid[0][2]` inside two.
|
|
20
|
+
*
|
|
21
|
+
* Indexed, never `nodePaths[nodePaths.length - 1]`. That expression at
|
|
22
|
+
* depth 0 is `nodePaths[-1]`, which is not an element read at all: -1 is
|
|
23
|
+
* outside the array, so V8 falls back to a named-property lookup and walks
|
|
24
|
+
* the prototype chain. The `prefix` getter used to be written that way and
|
|
25
|
+
* it took 12% of the array shape's self time.
|
|
26
|
+
*/
|
|
27
|
+
private readTop;
|
|
9
28
|
/** `""` at the root, `items[0]` inside the first element of `items`. */
|
|
10
29
|
get prefix(): string;
|
|
11
30
|
/**
|
|
@@ -21,8 +40,14 @@ export declare class IndexStack {
|
|
|
21
40
|
push(nodePath: string, index: number): void;
|
|
22
41
|
/** An unbalanced pop means a runner lost track of its own nesting. */
|
|
23
42
|
pop(): void;
|
|
24
|
-
/**
|
|
25
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The issue path of a field read from the subject at the current level.
|
|
45
|
+
*
|
|
46
|
+
* `renderedPath` was built once, at compile time. This used to walk the
|
|
47
|
+
* template on every call, which for an array meant rebuilding the same
|
|
48
|
+
* string once per element.
|
|
49
|
+
*/
|
|
50
|
+
renderFieldPath(renderedPath: string): string;
|
|
26
51
|
}
|
|
27
52
|
/** `""` is the root path, so it never contributes a separator dot. */
|
|
28
53
|
export declare function joinIssuePath(prefix: string, own: string): string;
|
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L5 src/runtime/index-stack.ts
|
|
4
|
+
// Where the runtime currently is, expressed as a concrete issue path.
|
|
5
|
+
//
|
|
6
|
+
// An issue path must read `items[0].name`, never the declaration pattern
|
|
7
|
+
// `items[*].name`. The legacy tree carried indices two different ways — the
|
|
8
|
+
// nested-array processor threaded them through its own recursion while
|
|
9
|
+
// validator-factory fell back to `error.path || elementPath` — which is how
|
|
10
|
+
// the pattern string leaked into user-visible errors. There is one stack here
|
|
11
|
+
// and every path a runner reports comes out of it.
|
|
12
|
+
//
|
|
13
|
+
// A CompiledField's template is RELATIVE to the subject it reads from, so an
|
|
14
|
+
// element field of `items[*]` has the template `name` and could never render
|
|
15
|
+
// `items[0].name` on its own. The array runner pushes the enclosing node and
|
|
16
|
+
// the index; the stack renders the prefix.
|
|
17
|
+
//
|
|
18
|
+
// The prefix is rendered ON DEMAND and remembered until the next push or pop.
|
|
19
|
+
// Entering used to build `items[0]` eagerly, which is one string per element
|
|
20
|
+
// that nothing reads unless a rule fails — and now nothing reads it even then
|
|
21
|
+
// until FieldRuleContext is asked for its path. Entering therefore costs two
|
|
22
|
+
// pushes and one assignment, and the first field that asks pays the render.
|
|
23
|
+
//
|
|
24
|
+
// It is deliberately MUTABLE and pushed/popped around an element loop: one
|
|
25
|
+
// stack lives for one validate() call, and 10k elements must not allocate 10k
|
|
26
|
+
// index arrays. It is also why FieldRuleContext remembers the path it built —
|
|
27
|
+
// this stack has moved on by the time a retained context could read it.
|
|
28
|
+
// ===========================================================================
|
|
2
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
30
|
exports.IndexStack = void 0;
|
|
4
31
|
exports.joinIssuePath = joinIssuePath;
|
|
5
|
-
const format_issue_path_1 = require("../path/format-issue-path");
|
|
6
|
-
/**
|
|
7
|
-
* A compiled template never contains a wildcard (createValueReader rejects
|
|
8
|
-
* one), so rendering it needs no index. Passing none is what makes a stray
|
|
9
|
-
* wildcard template throw here instead of silently rendering a second copy of
|
|
10
|
-
* an index this stack has already written into the prefix.
|
|
11
|
-
*/
|
|
12
|
-
const NO_INDICES = Object.freeze([]);
|
|
13
32
|
class IndexStack {
|
|
14
33
|
constructor() {
|
|
15
|
-
this.
|
|
34
|
+
this.nodePaths = [];
|
|
16
35
|
this.openIndices = [];
|
|
36
|
+
/**
|
|
37
|
+
* The rendered prefix, or `null` when it has not been asked for since the
|
|
38
|
+
* last push or pop. `null` and not `""` because the empty string is a real
|
|
39
|
+
* answer — the root.
|
|
40
|
+
*
|
|
41
|
+
* Invalidating rather than recomputing on push is the point: an element that
|
|
42
|
+
* raises no issue never renders its own prefix at all.
|
|
43
|
+
*/
|
|
44
|
+
this.currentPrefix = null;
|
|
17
45
|
}
|
|
18
46
|
/** How many array levels are currently open. */
|
|
19
47
|
get depth() {
|
|
@@ -23,9 +51,34 @@ class IndexStack {
|
|
|
23
51
|
get indices() {
|
|
24
52
|
return this.openIndices;
|
|
25
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The prefix for the currently open frames — `""` when none are open,
|
|
56
|
+
* `items[0]` inside one, `grid[0][2]` inside two.
|
|
57
|
+
*
|
|
58
|
+
* Indexed, never `nodePaths[nodePaths.length - 1]`. That expression at
|
|
59
|
+
* depth 0 is `nodePaths[-1]`, which is not an element read at all: -1 is
|
|
60
|
+
* outside the array, so V8 falls back to a named-property lookup and walks
|
|
61
|
+
* the prototype chain. The `prefix` getter used to be written that way and
|
|
62
|
+
* it took 12% of the array shape's self time.
|
|
63
|
+
*/
|
|
64
|
+
readTop() {
|
|
65
|
+
const already = this.currentPrefix;
|
|
66
|
+
if (already !== null)
|
|
67
|
+
return already;
|
|
68
|
+
let built = "";
|
|
69
|
+
for (let i = 0; i < this.nodePaths.length; i += 1) {
|
|
70
|
+
const nodePath = this.nodePaths[i];
|
|
71
|
+
const index = this.openIndices[i];
|
|
72
|
+
if (nodePath === undefined || index === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
built = `${joinIssuePath(built, nodePath)}[${index}]`;
|
|
75
|
+
}
|
|
76
|
+
this.currentPrefix = built;
|
|
77
|
+
return built;
|
|
78
|
+
}
|
|
26
79
|
/** `""` at the root, `items[0]` inside the first element of `items`. */
|
|
27
80
|
get prefix() {
|
|
28
|
-
return this.
|
|
81
|
+
return this.readTop();
|
|
29
82
|
}
|
|
30
83
|
/**
|
|
31
84
|
* Enters element `index` of an array node. `nodePath` is the node's own path
|
|
@@ -41,20 +94,28 @@ class IndexStack {
|
|
|
41
94
|
if (!Number.isInteger(index) || index < 0) {
|
|
42
95
|
throw new RangeError(`an array index must be a non-negative integer, received ${String(index)}`);
|
|
43
96
|
}
|
|
44
|
-
this.
|
|
97
|
+
this.nodePaths.push(nodePath);
|
|
45
98
|
this.openIndices.push(index);
|
|
99
|
+
this.currentPrefix = null;
|
|
46
100
|
}
|
|
47
101
|
/** An unbalanced pop means a runner lost track of its own nesting. */
|
|
48
102
|
pop() {
|
|
49
103
|
if (this.openIndices.length === 0) {
|
|
50
104
|
throw new RangeError("popped an array index that was never pushed");
|
|
51
105
|
}
|
|
52
|
-
this.
|
|
106
|
+
this.nodePaths.pop();
|
|
53
107
|
this.openIndices.pop();
|
|
108
|
+
this.currentPrefix = null;
|
|
54
109
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
110
|
+
/**
|
|
111
|
+
* The issue path of a field read from the subject at the current level.
|
|
112
|
+
*
|
|
113
|
+
* `renderedPath` was built once, at compile time. This used to walk the
|
|
114
|
+
* template on every call, which for an array meant rebuilding the same
|
|
115
|
+
* string once per element.
|
|
116
|
+
*/
|
|
117
|
+
renderFieldPath(renderedPath) {
|
|
118
|
+
return joinIssuePath(this.readTop(), renderedPath);
|
|
58
119
|
}
|
|
59
120
|
}
|
|
60
121
|
exports.IndexStack = IndexStack;
|
|
@@ -1,15 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L5 src/runtime/index-stack.ts
|
|
3
|
+
// Where the runtime currently is, expressed as a concrete issue path.
|
|
4
|
+
//
|
|
5
|
+
// An issue path must read `items[0].name`, never the declaration pattern
|
|
6
|
+
// `items[*].name`. The legacy tree carried indices two different ways — the
|
|
7
|
+
// nested-array processor threaded them through its own recursion while
|
|
8
|
+
// validator-factory fell back to `error.path || elementPath` — which is how
|
|
9
|
+
// the pattern string leaked into user-visible errors. There is one stack here
|
|
10
|
+
// and every path a runner reports comes out of it.
|
|
11
|
+
//
|
|
12
|
+
// A CompiledField's template is RELATIVE to the subject it reads from, so an
|
|
13
|
+
// element field of `items[*]` has the template `name` and could never render
|
|
14
|
+
// `items[0].name` on its own. The array runner pushes the enclosing node and
|
|
15
|
+
// the index; the stack renders the prefix.
|
|
16
|
+
//
|
|
17
|
+
// The prefix is rendered ON DEMAND and remembered until the next push or pop.
|
|
18
|
+
// Entering used to build `items[0]` eagerly, which is one string per element
|
|
19
|
+
// that nothing reads unless a rule fails — and now nothing reads it even then
|
|
20
|
+
// until FieldRuleContext is asked for its path. Entering therefore costs two
|
|
21
|
+
// pushes and one assignment, and the first field that asks pays the render.
|
|
22
|
+
//
|
|
23
|
+
// It is deliberately MUTABLE and pushed/popped around an element loop: one
|
|
24
|
+
// stack lives for one validate() call, and 10k elements must not allocate 10k
|
|
25
|
+
// index arrays. It is also why FieldRuleContext remembers the path it built —
|
|
26
|
+
// this stack has moved on by the time a retained context could read it.
|
|
27
|
+
// ===========================================================================
|
|
9
28
|
export class IndexStack {
|
|
10
29
|
constructor() {
|
|
11
|
-
this.
|
|
30
|
+
this.nodePaths = [];
|
|
12
31
|
this.openIndices = [];
|
|
32
|
+
/**
|
|
33
|
+
* The rendered prefix, or `null` when it has not been asked for since the
|
|
34
|
+
* last push or pop. `null` and not `""` because the empty string is a real
|
|
35
|
+
* answer — the root.
|
|
36
|
+
*
|
|
37
|
+
* Invalidating rather than recomputing on push is the point: an element that
|
|
38
|
+
* raises no issue never renders its own prefix at all.
|
|
39
|
+
*/
|
|
40
|
+
this.currentPrefix = null;
|
|
13
41
|
}
|
|
14
42
|
/** How many array levels are currently open. */
|
|
15
43
|
get depth() {
|
|
@@ -19,9 +47,34 @@ export class IndexStack {
|
|
|
19
47
|
get indices() {
|
|
20
48
|
return this.openIndices;
|
|
21
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The prefix for the currently open frames — `""` when none are open,
|
|
52
|
+
* `items[0]` inside one, `grid[0][2]` inside two.
|
|
53
|
+
*
|
|
54
|
+
* Indexed, never `nodePaths[nodePaths.length - 1]`. That expression at
|
|
55
|
+
* depth 0 is `nodePaths[-1]`, which is not an element read at all: -1 is
|
|
56
|
+
* outside the array, so V8 falls back to a named-property lookup and walks
|
|
57
|
+
* the prototype chain. The `prefix` getter used to be written that way and
|
|
58
|
+
* it took 12% of the array shape's self time.
|
|
59
|
+
*/
|
|
60
|
+
readTop() {
|
|
61
|
+
const already = this.currentPrefix;
|
|
62
|
+
if (already !== null)
|
|
63
|
+
return already;
|
|
64
|
+
let built = "";
|
|
65
|
+
for (let i = 0; i < this.nodePaths.length; i += 1) {
|
|
66
|
+
const nodePath = this.nodePaths[i];
|
|
67
|
+
const index = this.openIndices[i];
|
|
68
|
+
if (nodePath === undefined || index === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
built = `${joinIssuePath(built, nodePath)}[${index}]`;
|
|
71
|
+
}
|
|
72
|
+
this.currentPrefix = built;
|
|
73
|
+
return built;
|
|
74
|
+
}
|
|
22
75
|
/** `""` at the root, `items[0]` inside the first element of `items`. */
|
|
23
76
|
get prefix() {
|
|
24
|
-
return this.
|
|
77
|
+
return this.readTop();
|
|
25
78
|
}
|
|
26
79
|
/**
|
|
27
80
|
* Enters element `index` of an array node. `nodePath` is the node's own path
|
|
@@ -37,20 +90,28 @@ export class IndexStack {
|
|
|
37
90
|
if (!Number.isInteger(index) || index < 0) {
|
|
38
91
|
throw new RangeError(`an array index must be a non-negative integer, received ${String(index)}`);
|
|
39
92
|
}
|
|
40
|
-
this.
|
|
93
|
+
this.nodePaths.push(nodePath);
|
|
41
94
|
this.openIndices.push(index);
|
|
95
|
+
this.currentPrefix = null;
|
|
42
96
|
}
|
|
43
97
|
/** An unbalanced pop means a runner lost track of its own nesting. */
|
|
44
98
|
pop() {
|
|
45
99
|
if (this.openIndices.length === 0) {
|
|
46
100
|
throw new RangeError("popped an array index that was never pushed");
|
|
47
101
|
}
|
|
48
|
-
this.
|
|
102
|
+
this.nodePaths.pop();
|
|
49
103
|
this.openIndices.pop();
|
|
104
|
+
this.currentPrefix = null;
|
|
50
105
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The issue path of a field read from the subject at the current level.
|
|
108
|
+
*
|
|
109
|
+
* `renderedPath` was built once, at compile time. This used to walk the
|
|
110
|
+
* template on every call, which for an array meant rebuilding the same
|
|
111
|
+
* string once per element.
|
|
112
|
+
*/
|
|
113
|
+
renderFieldPath(renderedPath) {
|
|
114
|
+
return joinIssuePath(this.readTop(), renderedPath);
|
|
54
115
|
}
|
|
55
116
|
}
|
|
56
117
|
/** `""` is the root path, so it never contributes a separator dot. */
|
|
@@ -19,11 +19,8 @@ exports.runArrayNodes = runArrayNodes;
|
|
|
19
19
|
// `grid[0][2].name` and never the declaration pattern.
|
|
20
20
|
// ===========================================================================
|
|
21
21
|
const types_1 = require("../types");
|
|
22
|
-
const format_issue_path_1 = require("../path/format-issue-path");
|
|
23
22
|
const run_field_1 = require("./run-field");
|
|
24
23
|
const output_writer_1 = require("./output-writer");
|
|
25
|
-
/** A node template never contains a wildcard: the grouping cut it off. */
|
|
26
|
-
const NO_INDICES = Object.freeze([]);
|
|
27
24
|
/**
|
|
28
25
|
* `targets` mirrors `nodes` by position and is EMPTY on every run that produces
|
|
29
26
|
* no output. Zipping by index needs no lookup and no identity check, because
|
|
@@ -57,16 +54,27 @@ function runArrayNode(node, subject, context, target) {
|
|
|
57
54
|
return target.write(subject, rebuilt);
|
|
58
55
|
}
|
|
59
56
|
function runElements(node, array, context, target) {
|
|
60
|
-
const nodePath =
|
|
57
|
+
const nodePath = node.renderedPath;
|
|
61
58
|
const elementSink = context.sink.forArrayElements();
|
|
62
59
|
const nested = target === undefined ? output_writer_1.NO_WRITE_TARGETS : target.nested;
|
|
60
|
+
// 要素コンテキストはノードごとに一つ。7つの成員のうち要素ごとに変わるのは
|
|
61
|
+
// `item` だけで、残る6つは validate() 一回のあいだ、あるいはこのノードの
|
|
62
|
+
// あいだ動かない。毎要素で組み直していたのは、動かないものを動くものと
|
|
63
|
+
// 同じ場所に置いていたからにすぎない。**プラグインには渡らない** —
|
|
64
|
+
// プラグインが見るのは runField が組む RuleContext で、そちらは `item` を
|
|
65
|
+
// 都度読み直すので、この使い回しは外から観測できない。
|
|
66
|
+
const elementContext = {
|
|
67
|
+
root: context.root,
|
|
68
|
+
sink: elementSink,
|
|
69
|
+
indices: context.indices,
|
|
70
|
+
shouldApplyTransforms: context.shouldApplyTransforms,
|
|
71
|
+
runRecursion: context.runRecursion,
|
|
72
|
+
item: undefined,
|
|
73
|
+
external: context.external,
|
|
74
|
+
};
|
|
63
75
|
let rebuilt = array;
|
|
64
76
|
for (let index = 0; index < array.length; index += 1) {
|
|
65
|
-
|
|
66
|
-
...context,
|
|
67
|
-
sink: elementSink,
|
|
68
|
-
item: describeItem(array, index),
|
|
69
|
-
};
|
|
77
|
+
elementContext.item = describeItem(array, index);
|
|
70
78
|
context.indices.push(nodePath, index);
|
|
71
79
|
const element = runNested(node, runElementFields(node, array[index], elementContext), elementContext, nested);
|
|
72
80
|
context.indices.pop();
|
|
@@ -84,7 +92,11 @@ function runNested(node, element, elementContext, nested) {
|
|
|
84
92
|
}
|
|
85
93
|
function runElementFields(node, element, elementContext) {
|
|
86
94
|
let current = element;
|
|
87
|
-
|
|
95
|
+
const fields = node.elementFields;
|
|
96
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
97
|
+
const field = fields[i];
|
|
98
|
+
if (field === undefined)
|
|
99
|
+
continue;
|
|
88
100
|
const outcome = (0, run_field_1.runField)(field, current, elementContext);
|
|
89
101
|
current = (0, output_writer_1.writeFieldValue)(field, current, outcome, elementContext.shouldApplyTransforms);
|
|
90
102
|
if (elementContext.sink.shouldStopPlan())
|
|
@@ -16,11 +16,8 @@
|
|
|
16
16
|
// `grid[0][2].name` and never the declaration pattern.
|
|
17
17
|
// ===========================================================================
|
|
18
18
|
import { isArray } from "../types/index.mjs";
|
|
19
|
-
import { formatIssuePath } from "../path/format-issue-path.mjs";
|
|
20
19
|
import { runField } from "./run-field.mjs";
|
|
21
20
|
import { NO_WRITE_TARGETS, replaceElement, writeFieldValue, } from "./output-writer.mjs";
|
|
22
|
-
/** A node template never contains a wildcard: the grouping cut it off. */
|
|
23
|
-
const NO_INDICES = Object.freeze([]);
|
|
24
21
|
/**
|
|
25
22
|
* `targets` mirrors `nodes` by position and is EMPTY on every run that produces
|
|
26
23
|
* no output. Zipping by index needs no lookup and no identity check, because
|
|
@@ -54,16 +51,27 @@ function runArrayNode(node, subject, context, target) {
|
|
|
54
51
|
return target.write(subject, rebuilt);
|
|
55
52
|
}
|
|
56
53
|
function runElements(node, array, context, target) {
|
|
57
|
-
const nodePath =
|
|
54
|
+
const nodePath = node.renderedPath;
|
|
58
55
|
const elementSink = context.sink.forArrayElements();
|
|
59
56
|
const nested = target === undefined ? NO_WRITE_TARGETS : target.nested;
|
|
57
|
+
// 要素コンテキストはノードごとに一つ。7つの成員のうち要素ごとに変わるのは
|
|
58
|
+
// `item` だけで、残る6つは validate() 一回のあいだ、あるいはこのノードの
|
|
59
|
+
// あいだ動かない。毎要素で組み直していたのは、動かないものを動くものと
|
|
60
|
+
// 同じ場所に置いていたからにすぎない。**プラグインには渡らない** —
|
|
61
|
+
// プラグインが見るのは runField が組む RuleContext で、そちらは `item` を
|
|
62
|
+
// 都度読み直すので、この使い回しは外から観測できない。
|
|
63
|
+
const elementContext = {
|
|
64
|
+
root: context.root,
|
|
65
|
+
sink: elementSink,
|
|
66
|
+
indices: context.indices,
|
|
67
|
+
shouldApplyTransforms: context.shouldApplyTransforms,
|
|
68
|
+
runRecursion: context.runRecursion,
|
|
69
|
+
item: undefined,
|
|
70
|
+
external: context.external,
|
|
71
|
+
};
|
|
60
72
|
let rebuilt = array;
|
|
61
73
|
for (let index = 0; index < array.length; index += 1) {
|
|
62
|
-
|
|
63
|
-
...context,
|
|
64
|
-
sink: elementSink,
|
|
65
|
-
item: describeItem(array, index),
|
|
66
|
-
};
|
|
74
|
+
elementContext.item = describeItem(array, index);
|
|
67
75
|
context.indices.push(nodePath, index);
|
|
68
76
|
const element = runNested(node, runElementFields(node, array[index], elementContext), elementContext, nested);
|
|
69
77
|
context.indices.pop();
|
|
@@ -81,7 +89,11 @@ function runNested(node, element, elementContext, nested) {
|
|
|
81
89
|
}
|
|
82
90
|
function runElementFields(node, element, elementContext) {
|
|
83
91
|
let current = element;
|
|
84
|
-
|
|
92
|
+
const fields = node.elementFields;
|
|
93
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
94
|
+
const field = fields[i];
|
|
95
|
+
if (field === undefined)
|
|
96
|
+
continue;
|
|
85
97
|
const outcome = runField(field, current, elementContext);
|
|
86
98
|
current = writeFieldValue(field, current, outcome, elementContext.shouldApplyTransforms);
|
|
87
99
|
if (elementContext.sink.shouldStopPlan())
|
|
@@ -3,19 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FIELD_VALUE_UNCHANGED = void 0;
|
|
4
4
|
exports.runField = runField;
|
|
5
5
|
const create_issue_1 = require("./create-issue");
|
|
6
|
+
const field_rule_context_1 = require("./field-rule-context");
|
|
6
7
|
const decide_presence_1 = require("./decide-presence");
|
|
7
8
|
/** Shared, frozen, never re-assigned: the common answer allocates nothing. */
|
|
8
9
|
exports.FIELD_VALUE_UNCHANGED = Object.freeze({
|
|
9
10
|
hasWriteBack: false,
|
|
10
11
|
});
|
|
11
12
|
function runField(field, subject, context) {
|
|
12
|
-
const
|
|
13
|
-
const ruleContext = {
|
|
14
|
-
root: context.root,
|
|
15
|
-
path,
|
|
16
|
-
item: context.item,
|
|
17
|
-
external: context.external,
|
|
18
|
-
};
|
|
13
|
+
const ruleContext = new field_rule_context_1.FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
|
|
19
14
|
const read = field.read(subject);
|
|
20
15
|
const value = applyDefault(field, read, context.root);
|
|
21
16
|
if (!(0, decide_presence_1.decidePresence)(field, value, ruleContext, context.sink)) {
|
|
@@ -47,24 +42,55 @@ function applyDefault(field, value, root) {
|
|
|
47
42
|
}
|
|
48
43
|
/** A closed gate ends the field successfully: no check, no transform. */
|
|
49
44
|
function openGates(field, value, ruleContext) {
|
|
50
|
-
for
|
|
45
|
+
// 添字ループである。for-of ではない。ここが回るのはコンパイル済みの
|
|
46
|
+
// 凍結配列で、凍結配列は V8 では PACKED_FROZEN_ELEMENTS になり、配列
|
|
47
|
+
// イテレータの高速化パスから外れる — イテレータと IteratorResult が
|
|
48
|
+
// 消去されず、要素×フィールドの回数だけ確保される。配列シェイプでは
|
|
49
|
+
// それだけで全ゴミの 45〜54% を占めていた (独立に5通りの改変で -45%
|
|
50
|
+
// 〜 -54%)。凍結は落とさない: コンパイル層の不変条件であり、凍結を
|
|
51
|
+
// 外しても添字ループより速くはならない。
|
|
52
|
+
const gates = field.gates;
|
|
53
|
+
for (let i = 0; i < gates.length; i += 1) {
|
|
54
|
+
const gate = gates[i];
|
|
55
|
+
if (gate === undefined)
|
|
56
|
+
continue;
|
|
51
57
|
if (!gate.shouldRun(value, ruleContext))
|
|
52
58
|
return false;
|
|
53
59
|
}
|
|
54
60
|
return true;
|
|
55
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 失敗したときだけ通る側。ループ本体から出してある。
|
|
64
|
+
*
|
|
65
|
+
* runChecks はバイトコードで 302 バイトあり、TurboFan の呼び出し先
|
|
66
|
+
* インライン予算 (既定で累計 920 バイト) の最大の落選候補として
|
|
67
|
+
* --trace-turbo-inlining に名指しされていた。その 302 バイトの大半が、
|
|
68
|
+
* 受理された値では一度も走らない issue の組み立てである。ここへ出すと
|
|
69
|
+
* 残るループ本体が縮み、受理パスで 6.4% 速くなった。
|
|
70
|
+
*
|
|
71
|
+
* 中断の判定はここに含めない。issue を足したあとに shouldStopField を
|
|
72
|
+
* 見るという順序が abortEarlyOnEachField の意味そのものなので、呼び出し側に
|
|
73
|
+
* 並べて置いておく。
|
|
74
|
+
*/
|
|
75
|
+
function reportCheckFailure(check, detail, value, ruleContext, context) {
|
|
76
|
+
context.sink.add((0, create_issue_1.createIssue)({
|
|
77
|
+
path: ruleContext.path,
|
|
78
|
+
code: check.code,
|
|
79
|
+
severity: check.severity,
|
|
80
|
+
value,
|
|
81
|
+
render: (ctx) => check.describe(detail, ctx),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
56
84
|
function runChecks(field, value, ruleContext, context, mark) {
|
|
57
|
-
|
|
85
|
+
const checks = field.checks;
|
|
86
|
+
for (let i = 0; i < checks.length; i += 1) {
|
|
87
|
+
const check = checks[i];
|
|
88
|
+
if (check === undefined)
|
|
89
|
+
continue;
|
|
58
90
|
const outcome = check.run(value, ruleContext);
|
|
59
91
|
if (outcome.ok)
|
|
60
92
|
continue;
|
|
61
|
-
|
|
62
|
-
path: ruleContext.path,
|
|
63
|
-
code: check.code,
|
|
64
|
-
severity: check.severity,
|
|
65
|
-
value,
|
|
66
|
-
render: (ctx) => check.describe(outcome.detail, ctx),
|
|
67
|
-
}));
|
|
93
|
+
reportCheckFailure(check, outcome.detail, value, ruleContext, context);
|
|
68
94
|
if (context.sink.shouldStopField(mark))
|
|
69
95
|
return;
|
|
70
96
|
}
|
|
@@ -75,7 +101,11 @@ function runTransforms(field, value, ruleContext, context, mark) {
|
|
|
75
101
|
return value;
|
|
76
102
|
}
|
|
77
103
|
let current = value;
|
|
78
|
-
|
|
104
|
+
const transforms = field.transforms;
|
|
105
|
+
for (let i = 0; i < transforms.length; i += 1) {
|
|
106
|
+
const transform = transforms[i];
|
|
107
|
+
if (transform === undefined)
|
|
108
|
+
continue;
|
|
79
109
|
current = transform.apply(current, ruleContext);
|
|
80
110
|
}
|
|
81
111
|
return current;
|