@descryy/ir 0.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/LICENSE +21 -0
- package/dist/capabilities.d.ts +83 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +91 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/conformance/bind.d.ts +82 -0
- package/dist/conformance/bind.d.ts.map +1 -0
- package/dist/conformance/bind.js +157 -0
- package/dist/conformance/bind.js.map +1 -0
- package/dist/conformance/cli.d.ts +25 -0
- package/dist/conformance/cli.d.ts.map +1 -0
- package/dist/conformance/cli.js +137 -0
- package/dist/conformance/cli.js.map +1 -0
- package/dist/conformance/compare.d.ts +74 -0
- package/dist/conformance/compare.d.ts.map +1 -0
- package/dist/conformance/compare.js +140 -0
- package/dist/conformance/compare.js.map +1 -0
- package/dist/conformance/compose.d.ts +79 -0
- package/dist/conformance/compose.d.ts.map +1 -0
- package/dist/conformance/compose.js +167 -0
- package/dist/conformance/compose.js.map +1 -0
- package/dist/conformance/golden.d.ts +110 -0
- package/dist/conformance/golden.d.ts.map +1 -0
- package/dist/conformance/golden.js +252 -0
- package/dist/conformance/golden.js.map +1 -0
- package/dist/conformance/index.d.ts +26 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +18 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/manifest-check.d.ts +78 -0
- package/dist/conformance/manifest-check.d.ts.map +1 -0
- package/dist/conformance/manifest-check.js +149 -0
- package/dist/conformance/manifest-check.js.map +1 -0
- package/dist/conformance/manifest.d.ts +83 -0
- package/dist/conformance/manifest.d.ts.map +1 -0
- package/dist/conformance/manifest.js +158 -0
- package/dist/conformance/manifest.js.map +1 -0
- package/dist/conformance/report.d.ts +14 -0
- package/dist/conformance/report.d.ts.map +1 -0
- package/dist/conformance/report.js +87 -0
- package/dist/conformance/report.js.map +1 -0
- package/dist/conformance/run.d.ts +172 -0
- package/dist/conformance/run.d.ts.map +1 -0
- package/dist/conformance/run.js +626 -0
- package/dist/conformance/run.js.map +1 -0
- package/dist/contracts.d.ts +198 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +10 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contradiction.d.ts +112 -0
- package/dist/contradiction.d.ts.map +1 -0
- package/dist/contradiction.js +97 -0
- package/dist/contradiction.js.map +1 -0
- package/dist/finding.d.ts +567 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +491 -0
- package/dist/finding.js.map +1 -0
- package/dist/hypothesis.d.ts +130 -0
- package/dist/hypothesis.d.ts.map +1 -0
- package/dist/hypothesis.js +116 -0
- package/dist/hypothesis.js.map +1 -0
- package/dist/identity.d.ts +295 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +404 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/ir.d.ts +360 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +76 -0
- package/dist/ir.js.map +1 -0
- package/dist/model-tables.d.ts +111 -0
- package/dist/model-tables.d.ts.map +1 -0
- package/dist/model-tables.js +103 -0
- package/dist/model-tables.js.map +1 -0
- package/dist/normalise.d.ts +114 -0
- package/dist/normalise.d.ts.map +1 -0
- package/dist/normalise.js +603 -0
- package/dist/normalise.js.map +1 -0
- package/dist/reliability.d.ts +89 -0
- package/dist/reliability.d.ts.map +1 -0
- package/dist/reliability.js +181 -0
- package/dist/reliability.js.map +1 -0
- package/dist/tool-surface.d.ts +131 -0
- package/dist/tool-surface.d.ts.map +1 -0
- package/dist/tool-surface.js +133 -0
- package/dist/tool-surface.js.map +1 -0
- package/dist/vocabulary.d.ts +73 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +150 -0
- package/dist/vocabulary.js.map +1 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Descry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework capabilities — the named things a pattern may require beyond the
|
|
3
|
+
* language adapter itself (§11A.8).
|
|
4
|
+
*
|
|
5
|
+
* ## Why these are named rather than counted
|
|
6
|
+
*
|
|
7
|
+
* There was one capability, `"framework-extractor"`, and the harness matched it
|
|
8
|
+
* against `frameworkExtractors.length === 0`. One boolean standing in for *which
|
|
9
|
+
* framework knowledge does this adapter have*, and it cost coverage in both
|
|
10
|
+
* directions at once:
|
|
11
|
+
*
|
|
12
|
+
* - **It kept a pattern out of the corpus.** A directional-migration pattern
|
|
13
|
+
* cannot be skipped for adapters that do not read migrations, because any
|
|
14
|
+
* adapter binding *any* extractor is then expected to satisfy it.
|
|
15
|
+
* - **It hid an extractor already in the tree.** `adapter-java` shipped a Spring
|
|
16
|
+
* route reader and had to keep declaring `frameworkExtractors: []`, because
|
|
17
|
+
* declaring it would have turned patterns 06, 07 and 08 from skipped into
|
|
18
|
+
* failing for extractors nobody had written. So pattern 04 reported *"requires
|
|
19
|
+
* a framework-extractor; none is bound"* for a capability the adapter has.
|
|
20
|
+
*
|
|
21
|
+
* Three lanes hit the same field from three sides. Naming the capabilities lets
|
|
22
|
+
* an adapter say yes to one of them.
|
|
23
|
+
*
|
|
24
|
+
* ## The set is closed, and it is closed on BOTH sides
|
|
25
|
+
*
|
|
26
|
+
* A pattern names what it needs; an adapter names what it supplies; the harness
|
|
27
|
+
* compares two strings and knows nothing about any framework. That is the IR
|
|
28
|
+
* boundary doing its job — `"spring"` and `"rails"` never appear above the
|
|
29
|
+
* adapter, only `"route-extractor"`.
|
|
30
|
+
*
|
|
31
|
+
* An unrecognised value is a **loud failure on either side** — a parse error in
|
|
32
|
+
* the golden, an adapter failure in a run — never a skip. That asymmetry is the
|
|
33
|
+
* whole safety property: a typo that skipped would be a gate that silently
|
|
34
|
+
* stopped running, and a suite which has stopped checking looks exactly like a
|
|
35
|
+
* suite with nothing to report.
|
|
36
|
+
*
|
|
37
|
+
* Adding a value changes what "skipped" means in every report that mentions it,
|
|
38
|
+
* so it is added deliberately, with the pattern that needs it, or not at all.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Routes: `API_ROUTE` with a method and a path template, and the `SERVES_API`
|
|
42
|
+
* edge to the endpoint it serves. Pattern 04.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ROUTE_EXTRACTOR = "route-extractor";
|
|
45
|
+
/**
|
|
46
|
+
* Persisted entities: `MODEL`, its fields, and their nullability — evidence
|
|
47
|
+
* being an ORM mapping rather than a name. Patterns 06 and 07.
|
|
48
|
+
*
|
|
49
|
+
* 07 is here rather than under routes despite being described as *"a route
|
|
50
|
+
* handler reads a field of the model"*: its assertion is `FUNCTION --READS-->
|
|
51
|
+
* MODEL`, with no `API_ROUTE` role and no `SERVES_API` edge. The only framework
|
|
52
|
+
* claim in it is `MODEL`. Reading the assertions settles this; reading the
|
|
53
|
+
* description does not.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ORM_EXTRACTOR = "orm-extractor";
|
|
56
|
+
/**
|
|
57
|
+
* The calling side of an HTTP boundary: `USES_API` from a caller to an
|
|
58
|
+
* `API_ENDPOINT`. Pattern 08.
|
|
59
|
+
*
|
|
60
|
+
* Separate from `ROUTE_EXTRACTOR` because the two are genuinely independent —
|
|
61
|
+
* `adapter-go` reads routes and no client calls, and a frontend-only adapter
|
|
62
|
+
* would be the reverse. Nobody declares an endpoint; it is minted by whoever
|
|
63
|
+
* reads a route *or* a call, which is exactly why either side can exist alone.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CLIENT_CALL_EXTRACTOR = "client-call-extractor";
|
|
66
|
+
/**
|
|
67
|
+
* The strict client-base resolution contract (DEC-164): `attrs.clientBase` on
|
|
68
|
+
* every caller a `USES_API` edge might come from, and `unresolved` refuses the
|
|
69
|
+
* edge. Patterns 08a/08b/08c.
|
|
70
|
+
*
|
|
71
|
+
* Separate from `CLIENT_CALL_EXTRACTOR` on purpose — an adapter can read a call
|
|
72
|
+
* site (08's plain case) without ever reading what base it composes against.
|
|
73
|
+
* DEC-164 §2 is explicit that this is opt-in: an adapter declaring it accepts
|
|
74
|
+
* the stricter refusal rule, and one that does not keeps emitting `USES_API`
|
|
75
|
+
* unqualified, which is why the two capabilities are not the same string.
|
|
76
|
+
*/
|
|
77
|
+
export declare const CLIENT_BASE_EXTRACTOR = "client-base-extractor";
|
|
78
|
+
export declare const FRAMEWORK_CAPABILITIES: readonly ["route-extractor", "orm-extractor", "client-call-extractor", "client-base-extractor"];
|
|
79
|
+
export type FrameworkCapability = (typeof FRAMEWORK_CAPABILITIES)[number];
|
|
80
|
+
export declare function isFrameworkCapability(value: string): value is FrameworkCapability;
|
|
81
|
+
/** For error messages, so every rejection prints the same closed set. */
|
|
82
|
+
export declare function capabilityList(): string;
|
|
83
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;GAGG;AACH,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,eAAO,MAAM,sBAAsB,iGAKzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,mBAAmB,CAEjF;AAED,yEAAyE;AACzE,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework capabilities — the named things a pattern may require beyond the
|
|
3
|
+
* language adapter itself (§11A.8).
|
|
4
|
+
*
|
|
5
|
+
* ## Why these are named rather than counted
|
|
6
|
+
*
|
|
7
|
+
* There was one capability, `"framework-extractor"`, and the harness matched it
|
|
8
|
+
* against `frameworkExtractors.length === 0`. One boolean standing in for *which
|
|
9
|
+
* framework knowledge does this adapter have*, and it cost coverage in both
|
|
10
|
+
* directions at once:
|
|
11
|
+
*
|
|
12
|
+
* - **It kept a pattern out of the corpus.** A directional-migration pattern
|
|
13
|
+
* cannot be skipped for adapters that do not read migrations, because any
|
|
14
|
+
* adapter binding *any* extractor is then expected to satisfy it.
|
|
15
|
+
* - **It hid an extractor already in the tree.** `adapter-java` shipped a Spring
|
|
16
|
+
* route reader and had to keep declaring `frameworkExtractors: []`, because
|
|
17
|
+
* declaring it would have turned patterns 06, 07 and 08 from skipped into
|
|
18
|
+
* failing for extractors nobody had written. So pattern 04 reported *"requires
|
|
19
|
+
* a framework-extractor; none is bound"* for a capability the adapter has.
|
|
20
|
+
*
|
|
21
|
+
* Three lanes hit the same field from three sides. Naming the capabilities lets
|
|
22
|
+
* an adapter say yes to one of them.
|
|
23
|
+
*
|
|
24
|
+
* ## The set is closed, and it is closed on BOTH sides
|
|
25
|
+
*
|
|
26
|
+
* A pattern names what it needs; an adapter names what it supplies; the harness
|
|
27
|
+
* compares two strings and knows nothing about any framework. That is the IR
|
|
28
|
+
* boundary doing its job — `"spring"` and `"rails"` never appear above the
|
|
29
|
+
* adapter, only `"route-extractor"`.
|
|
30
|
+
*
|
|
31
|
+
* An unrecognised value is a **loud failure on either side** — a parse error in
|
|
32
|
+
* the golden, an adapter failure in a run — never a skip. That asymmetry is the
|
|
33
|
+
* whole safety property: a typo that skipped would be a gate that silently
|
|
34
|
+
* stopped running, and a suite which has stopped checking looks exactly like a
|
|
35
|
+
* suite with nothing to report.
|
|
36
|
+
*
|
|
37
|
+
* Adding a value changes what "skipped" means in every report that mentions it,
|
|
38
|
+
* so it is added deliberately, with the pattern that needs it, or not at all.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Routes: `API_ROUTE` with a method and a path template, and the `SERVES_API`
|
|
42
|
+
* edge to the endpoint it serves. Pattern 04.
|
|
43
|
+
*/
|
|
44
|
+
export const ROUTE_EXTRACTOR = "route-extractor";
|
|
45
|
+
/**
|
|
46
|
+
* Persisted entities: `MODEL`, its fields, and their nullability — evidence
|
|
47
|
+
* being an ORM mapping rather than a name. Patterns 06 and 07.
|
|
48
|
+
*
|
|
49
|
+
* 07 is here rather than under routes despite being described as *"a route
|
|
50
|
+
* handler reads a field of the model"*: its assertion is `FUNCTION --READS-->
|
|
51
|
+
* MODEL`, with no `API_ROUTE` role and no `SERVES_API` edge. The only framework
|
|
52
|
+
* claim in it is `MODEL`. Reading the assertions settles this; reading the
|
|
53
|
+
* description does not.
|
|
54
|
+
*/
|
|
55
|
+
export const ORM_EXTRACTOR = "orm-extractor";
|
|
56
|
+
/**
|
|
57
|
+
* The calling side of an HTTP boundary: `USES_API` from a caller to an
|
|
58
|
+
* `API_ENDPOINT`. Pattern 08.
|
|
59
|
+
*
|
|
60
|
+
* Separate from `ROUTE_EXTRACTOR` because the two are genuinely independent —
|
|
61
|
+
* `adapter-go` reads routes and no client calls, and a frontend-only adapter
|
|
62
|
+
* would be the reverse. Nobody declares an endpoint; it is minted by whoever
|
|
63
|
+
* reads a route *or* a call, which is exactly why either side can exist alone.
|
|
64
|
+
*/
|
|
65
|
+
export const CLIENT_CALL_EXTRACTOR = "client-call-extractor";
|
|
66
|
+
/**
|
|
67
|
+
* The strict client-base resolution contract (DEC-164): `attrs.clientBase` on
|
|
68
|
+
* every caller a `USES_API` edge might come from, and `unresolved` refuses the
|
|
69
|
+
* edge. Patterns 08a/08b/08c.
|
|
70
|
+
*
|
|
71
|
+
* Separate from `CLIENT_CALL_EXTRACTOR` on purpose — an adapter can read a call
|
|
72
|
+
* site (08's plain case) without ever reading what base it composes against.
|
|
73
|
+
* DEC-164 §2 is explicit that this is opt-in: an adapter declaring it accepts
|
|
74
|
+
* the stricter refusal rule, and one that does not keeps emitting `USES_API`
|
|
75
|
+
* unqualified, which is why the two capabilities are not the same string.
|
|
76
|
+
*/
|
|
77
|
+
export const CLIENT_BASE_EXTRACTOR = "client-base-extractor";
|
|
78
|
+
export const FRAMEWORK_CAPABILITIES = [
|
|
79
|
+
ROUTE_EXTRACTOR,
|
|
80
|
+
ORM_EXTRACTOR,
|
|
81
|
+
CLIENT_CALL_EXTRACTOR,
|
|
82
|
+
CLIENT_BASE_EXTRACTOR,
|
|
83
|
+
];
|
|
84
|
+
export function isFrameworkCapability(value) {
|
|
85
|
+
return FRAMEWORK_CAPABILITIES.includes(value);
|
|
86
|
+
}
|
|
87
|
+
/** For error messages, so every rejection prints the same closed set. */
|
|
88
|
+
export function capabilityList() {
|
|
89
|
+
return FRAMEWORK_CAPABILITIES.map((c) => `"${c}"`).join(", ");
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,eAAe;IACf,aAAa;IACb,qBAAqB;IACrB,qBAAqB;CACb,CAAC;AAIX,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAQ,sBAA4C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc;IAC5B,OAAO,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role binding — turning `"calc_method"` into the actual node an adapter emitted.
|
|
3
|
+
*
|
|
4
|
+
* ## Why binding is on `(file, name)` and not on node id
|
|
5
|
+
*
|
|
6
|
+
* A node id is `hash(repo, kind, qualifiedSymbolPath)` (DEC-011), and the
|
|
7
|
+
* qualified symbol path is built from a package root the adapter chooses. The
|
|
8
|
+
* harness cannot recompute it without knowing how that adapter names packages —
|
|
9
|
+
* which is adapter-private knowledge, and asking for it would make the harness
|
|
10
|
+
* language-aware. `file` and `name` are the two fields a human can point at in
|
|
11
|
+
* the source, and a manifest is written by a human pointing at source.
|
|
12
|
+
*
|
|
13
|
+
* ## Binding does not filter by expected type, on purpose
|
|
14
|
+
*
|
|
15
|
+
* A node bound on `(file, name)` and then found to have the wrong type reports
|
|
16
|
+
* *wrong type*. A node filtered out by type before binding reports *role
|
|
17
|
+
* unbound* — the same defect, described so vaguely the adapter author cannot act
|
|
18
|
+
* on it. Bind first, judge after.
|
|
19
|
+
*
|
|
20
|
+
* The one place the expected type is consulted is a **tie**, and only a tie: two
|
|
21
|
+
* nodes matching one `(file, name)`, which `OrderPage.tsx` produces the moment a
|
|
22
|
+
* frontend extractor exists, because the module and the component share a name.
|
|
23
|
+
* A single match is never filtered, so the good error above is never traded for
|
|
24
|
+
* a worse one.
|
|
25
|
+
*
|
|
26
|
+
* ## Ambiguity is a failure, never a guess
|
|
27
|
+
*
|
|
28
|
+
* The symbol fallback below (matching `dispatch` against
|
|
29
|
+
* `OrderService.dispatch`) exists because no rule pins how much qualification an
|
|
30
|
+
* adapter puts in `IRNode.name`. It is safe only because two candidates are a
|
|
31
|
+
* loud failure rather than a coin flip — precision over recall applies to the
|
|
32
|
+
* harness itself, not only to the adapters it grades.
|
|
33
|
+
*/
|
|
34
|
+
import type { IRNode } from "../ir.ts";
|
|
35
|
+
import type { NodeType } from "../vocabulary.ts";
|
|
36
|
+
import type { CorpusManifest } from "./manifest.ts";
|
|
37
|
+
export type BindFailureReason = "unbound" | "ambiguous";
|
|
38
|
+
export interface BindFailure {
|
|
39
|
+
readonly role: string;
|
|
40
|
+
readonly reason: BindFailureReason;
|
|
41
|
+
readonly detail: string;
|
|
42
|
+
}
|
|
43
|
+
export interface BindResult {
|
|
44
|
+
readonly bound: ReadonlyMap<string, IRNode>;
|
|
45
|
+
readonly failures: readonly BindFailure[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Compare two repo-relative paths.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately shallow: the Normaliser has already rejected absolute paths, so
|
|
51
|
+
* anything reaching here is repo-relative and the only remaining variation is a
|
|
52
|
+
* leading `./` or a platform separator. `file` is **not** normalised away —
|
|
53
|
+
* golden pattern 01's `sameFileAs` compares it between two nodes, so it has to
|
|
54
|
+
* survive intact.
|
|
55
|
+
*/
|
|
56
|
+
export declare function samePath(left: string | null, right: string | null): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Bind the given roles against a batch's nodes.
|
|
59
|
+
*
|
|
60
|
+
* Only the roles asked for are bound. A pattern that is skipped or not required
|
|
61
|
+
* at this resolution never asks, which is what stops "this adapter has no
|
|
62
|
+
* framework extractor" from being reported as eighteen unbound roles.
|
|
63
|
+
*/
|
|
64
|
+
export declare function bindRoles(nodes: readonly IRNode[], manifest: CorpusManifest, roles: readonly string[],
|
|
65
|
+
/**
|
|
66
|
+
* The type the golden expects for each role, used **only to break a tie**.
|
|
67
|
+
*
|
|
68
|
+
* A `.tsx` file called `OrderPage.tsx` declares a component called `OrderPage`
|
|
69
|
+
* and a module called `OrderPage`, and that is not a naming accident — it is
|
|
70
|
+
* how React projects are laid out. Before this, no manifest could name that
|
|
71
|
+
* role at all: `(symbol, file)` matched two nodes and the binder rightly
|
|
72
|
+
* refused to choose.
|
|
73
|
+
*
|
|
74
|
+
* It is a tie-break and deliberately not a filter. Filtering by expected type
|
|
75
|
+
* would turn "the adapter emitted this as a MODULE, and the golden wants a
|
|
76
|
+
* COMPONENT" into "no node named OrderPage" — the same failure reported as a
|
|
77
|
+
* worse sentence, and one that points at the manifest instead of at the
|
|
78
|
+
* adapter. A single match is still bound whatever its type, and still fails
|
|
79
|
+
* the golden's own type assertion afterwards.
|
|
80
|
+
*/
|
|
81
|
+
expectedTypes?: ReadonlyMap<string, NodeType>): BindResult;
|
|
82
|
+
//# sourceMappingURL=bind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/conformance/bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,eAAe,CAAC;AAEjE,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;CAC3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAI3E;AAiBD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,SAAS,MAAM,EAAE;AACxB;;;;;;;;;;;;;;;GAeG;AACH,aAAa,GAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAa,GACvD,UAAU,CAyEZ"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role binding — turning `"calc_method"` into the actual node an adapter emitted.
|
|
3
|
+
*
|
|
4
|
+
* ## Why binding is on `(file, name)` and not on node id
|
|
5
|
+
*
|
|
6
|
+
* A node id is `hash(repo, kind, qualifiedSymbolPath)` (DEC-011), and the
|
|
7
|
+
* qualified symbol path is built from a package root the adapter chooses. The
|
|
8
|
+
* harness cannot recompute it without knowing how that adapter names packages —
|
|
9
|
+
* which is adapter-private knowledge, and asking for it would make the harness
|
|
10
|
+
* language-aware. `file` and `name` are the two fields a human can point at in
|
|
11
|
+
* the source, and a manifest is written by a human pointing at source.
|
|
12
|
+
*
|
|
13
|
+
* ## Binding does not filter by expected type, on purpose
|
|
14
|
+
*
|
|
15
|
+
* A node bound on `(file, name)` and then found to have the wrong type reports
|
|
16
|
+
* *wrong type*. A node filtered out by type before binding reports *role
|
|
17
|
+
* unbound* — the same defect, described so vaguely the adapter author cannot act
|
|
18
|
+
* on it. Bind first, judge after.
|
|
19
|
+
*
|
|
20
|
+
* The one place the expected type is consulted is a **tie**, and only a tie: two
|
|
21
|
+
* nodes matching one `(file, name)`, which `OrderPage.tsx` produces the moment a
|
|
22
|
+
* frontend extractor exists, because the module and the component share a name.
|
|
23
|
+
* A single match is never filtered, so the good error above is never traded for
|
|
24
|
+
* a worse one.
|
|
25
|
+
*
|
|
26
|
+
* ## Ambiguity is a failure, never a guess
|
|
27
|
+
*
|
|
28
|
+
* The symbol fallback below (matching `dispatch` against
|
|
29
|
+
* `OrderService.dispatch`) exists because no rule pins how much qualification an
|
|
30
|
+
* adapter puts in `IRNode.name`. It is safe only because two candidates are a
|
|
31
|
+
* loud failure rather than a coin flip — precision over recall applies to the
|
|
32
|
+
* harness itself, not only to the adapters it grades.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Compare two repo-relative paths.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately shallow: the Normaliser has already rejected absolute paths, so
|
|
38
|
+
* anything reaching here is repo-relative and the only remaining variation is a
|
|
39
|
+
* leading `./` or a platform separator. `file` is **not** normalised away —
|
|
40
|
+
* golden pattern 01's `sameFileAs` compares it between two nodes, so it has to
|
|
41
|
+
* survive intact.
|
|
42
|
+
*/
|
|
43
|
+
export function samePath(left, right) {
|
|
44
|
+
if (left === null || right === null)
|
|
45
|
+
return left === right;
|
|
46
|
+
const clean = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
47
|
+
return clean(left) === clean(right);
|
|
48
|
+
}
|
|
49
|
+
/** The last segment of a dotted symbol: `OrderService.dispatch` -> `dispatch`. */
|
|
50
|
+
function lastSegment(symbol) {
|
|
51
|
+
const index = symbol.lastIndexOf(".");
|
|
52
|
+
return index === -1 ? symbol : symbol.slice(index + 1);
|
|
53
|
+
}
|
|
54
|
+
function bindOne(nodes, binding) {
|
|
55
|
+
const inFile = nodes.filter((node) => samePath(node.file, binding.file));
|
|
56
|
+
const exact = inFile.filter((node) => node.name === binding.symbol);
|
|
57
|
+
if (exact.length > 0)
|
|
58
|
+
return exact;
|
|
59
|
+
const tail = lastSegment(binding.symbol);
|
|
60
|
+
if (tail === binding.symbol)
|
|
61
|
+
return [];
|
|
62
|
+
return inFile.filter((node) => node.name === tail);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Bind the given roles against a batch's nodes.
|
|
66
|
+
*
|
|
67
|
+
* Only the roles asked for are bound. A pattern that is skipped or not required
|
|
68
|
+
* at this resolution never asks, which is what stops "this adapter has no
|
|
69
|
+
* framework extractor" from being reported as eighteen unbound roles.
|
|
70
|
+
*/
|
|
71
|
+
export function bindRoles(nodes, manifest, roles,
|
|
72
|
+
/**
|
|
73
|
+
* The type the golden expects for each role, used **only to break a tie**.
|
|
74
|
+
*
|
|
75
|
+
* A `.tsx` file called `OrderPage.tsx` declares a component called `OrderPage`
|
|
76
|
+
* and a module called `OrderPage`, and that is not a naming accident — it is
|
|
77
|
+
* how React projects are laid out. Before this, no manifest could name that
|
|
78
|
+
* role at all: `(symbol, file)` matched two nodes and the binder rightly
|
|
79
|
+
* refused to choose.
|
|
80
|
+
*
|
|
81
|
+
* It is a tie-break and deliberately not a filter. Filtering by expected type
|
|
82
|
+
* would turn "the adapter emitted this as a MODULE, and the golden wants a
|
|
83
|
+
* COMPONENT" into "no node named OrderPage" — the same failure reported as a
|
|
84
|
+
* worse sentence, and one that points at the manifest instead of at the
|
|
85
|
+
* adapter. A single match is still bound whatever its type, and still fails
|
|
86
|
+
* the golden's own type assertion afterwards.
|
|
87
|
+
*/
|
|
88
|
+
expectedTypes = new Map()) {
|
|
89
|
+
const bound = new Map();
|
|
90
|
+
const failures = [];
|
|
91
|
+
for (const role of roles) {
|
|
92
|
+
const binding = manifest.roles[role];
|
|
93
|
+
if (binding === undefined) {
|
|
94
|
+
// THE ONE EXCEPTION, RECORDED HERE BECAUSE THIS IS WHERE SOMEONE WOULD
|
|
95
|
+
// "FIX" IT. A `mustNotBind` role's absence IS its assertion — the corpus
|
|
96
|
+
// names an over-admission trap and the adapter must produce no node for
|
|
97
|
+
// it, so unbound is the pass condition. The inversion is applied in
|
|
98
|
+
// `run.ts`, which filters these roles out of `failures`, deliberately
|
|
99
|
+
// rather than by loosening the rule here. This rule is not wrong for
|
|
100
|
+
// them; it is not applied to them. See DEC-160.
|
|
101
|
+
failures.push({
|
|
102
|
+
role,
|
|
103
|
+
reason: "unbound",
|
|
104
|
+
detail: `the manifest binds no symbol for this role. Every role in the golden must be bound ` +
|
|
105
|
+
`(fixtures/README.md, "Adding a language" step 2); an unbound role is a loud failure ` +
|
|
106
|
+
`and never a silent skip, because a corpus that quietly stops covering a pattern still ` +
|
|
107
|
+
`reports green.`,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// The primary first, then each alternate in order. More than one producer
|
|
112
|
+
// may legitimately declare a role — a route is `GET /orders/{id}` in a
|
|
113
|
+
// published contract and `GET /orders/:id` in the service source — and the
|
|
114
|
+
// corpus should accept whichever producer is running rather than demanding
|
|
115
|
+
// the one it happened to be written against.
|
|
116
|
+
const attempts = [binding, ...(binding.alternates ?? [])];
|
|
117
|
+
let matches = [];
|
|
118
|
+
let matched = attempts[0];
|
|
119
|
+
for (const attempt of attempts) {
|
|
120
|
+
const found = bindOne(nodes, attempt);
|
|
121
|
+
if (found.length > 0) {
|
|
122
|
+
matches = found;
|
|
123
|
+
matched = attempt;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (matches.length > 1) {
|
|
128
|
+
const wanted = expectedTypes.get(role);
|
|
129
|
+
const ofType = wanted === undefined ? [] : matches.filter((node) => node.type === wanted);
|
|
130
|
+
if (ofType.length === 1)
|
|
131
|
+
matches = ofType;
|
|
132
|
+
}
|
|
133
|
+
if (matches.length === 1) {
|
|
134
|
+
bound.set(role, matches[0]);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (matches.length === 0) {
|
|
138
|
+
failures.push({
|
|
139
|
+
role,
|
|
140
|
+
reason: "unbound",
|
|
141
|
+
detail: `no node matched ` +
|
|
142
|
+
attempts.map((a) => `"${a.symbol}" in ${a.file ?? "(no file)"}`).join(" or ") +
|
|
143
|
+
`. The adapter either did not emit it or named it something the manifest does not expect.`,
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
failures.push({
|
|
148
|
+
role,
|
|
149
|
+
reason: "ambiguous",
|
|
150
|
+
detail: `${matches.length} nodes in ${matched.file ?? "(no file)"} match "${matched.symbol}" ` +
|
|
151
|
+
`(types: ${matches.map((m) => m.type).join(", ")}). The harness will not guess which one ` +
|
|
152
|
+
`the manifest meant; qualify the symbol in the manifest until exactly one matches.`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return { bound, failures };
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=bind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../../src/conformance/bind.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The conformance CLI.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately thin, and deliberately here rather than in the engine: an adapter
|
|
5
|
+
* lives in its own repo (DEC-001) and depends on `@descryy/ir` alone, so the
|
|
6
|
+
* command that gates its release cannot live in a package it does not have. When
|
|
7
|
+
* the engine's own CLI exists this becomes `descry conformance` and calls the
|
|
8
|
+
* same `main()`.
|
|
9
|
+
*
|
|
10
|
+
* The adapter is loaded by module specifier and named export. That is the only
|
|
11
|
+
* way the harness can stay ignorant of every adapter — it imports what it is
|
|
12
|
+
* told to import and asks it for a batch.
|
|
13
|
+
*/
|
|
14
|
+
interface Args {
|
|
15
|
+
readonly source: string;
|
|
16
|
+
readonly corpus: string;
|
|
17
|
+
readonly golden: string | undefined;
|
|
18
|
+
readonly patterns: readonly string[];
|
|
19
|
+
readonly sweep: boolean;
|
|
20
|
+
readonly json: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function parseArgs(argv: readonly string[]): Args;
|
|
23
|
+
export declare function main(argv: readonly string[], log?: (...data: any[]) => void): Promise<number>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/conformance/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2BH,UAAU,IAAI;IACZ,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAyCvD;AAoBD,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,2BAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BtF"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The conformance CLI.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately thin, and deliberately here rather than in the engine: an adapter
|
|
5
|
+
* lives in its own repo (DEC-001) and depends on `@descryy/ir` alone, so the
|
|
6
|
+
* command that gates its release cannot live in a package it does not have. When
|
|
7
|
+
* the engine's own CLI exists this becomes `descry conformance` and calls the
|
|
8
|
+
* same `main()`.
|
|
9
|
+
*
|
|
10
|
+
* The adapter is loaded by module specifier and named export. That is the only
|
|
11
|
+
* way the harness can stay ignorant of every adapter — it imports what it is
|
|
12
|
+
* told to import and asks it for a batch.
|
|
13
|
+
*/
|
|
14
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
15
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
16
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
17
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return path;
|
|
21
|
+
};
|
|
22
|
+
import { resolve } from "node:path";
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
24
|
+
import { loadGolden } from "./golden.js";
|
|
25
|
+
import { loadManifest } from "./manifest.js";
|
|
26
|
+
import { formatReport, formatSweepReport } from "./report.js";
|
|
27
|
+
import { runConformance, runConformanceSweep } from "./run.js";
|
|
28
|
+
const USAGE = `
|
|
29
|
+
descry conformance --source <module>#<export> --corpus <dir> [options]
|
|
30
|
+
|
|
31
|
+
--source Module specifier and named export of the source to grade. The
|
|
32
|
+
export is either an IRSource or a factory taking the resolution
|
|
33
|
+
level to cap at.
|
|
34
|
+
--corpus Directory holding manifest.json and the fixture sources.
|
|
35
|
+
--golden Path to patterns.json. Defaults to <corpus>/../../golden/patterns.json.
|
|
36
|
+
--pattern Run one pattern. Repeatable. Defaults to the whole corpus.
|
|
37
|
+
--sweep Run every level up to the corpus maximum and check monotonicity.
|
|
38
|
+
--json Emit the raw result instead of the report.
|
|
39
|
+
|
|
40
|
+
Exit code 0 only when every pattern passes and the boundary rejected nothing.
|
|
41
|
+
`.trim();
|
|
42
|
+
export function parseArgs(argv) {
|
|
43
|
+
let source = "";
|
|
44
|
+
let corpus = "";
|
|
45
|
+
let golden;
|
|
46
|
+
const patterns = [];
|
|
47
|
+
let sweep = false;
|
|
48
|
+
let json = false;
|
|
49
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
50
|
+
const flag = argv[i];
|
|
51
|
+
const value = argv[i + 1];
|
|
52
|
+
switch (flag) {
|
|
53
|
+
case "--source":
|
|
54
|
+
source = value ?? "";
|
|
55
|
+
i += 1;
|
|
56
|
+
break;
|
|
57
|
+
case "--corpus":
|
|
58
|
+
corpus = value ?? "";
|
|
59
|
+
i += 1;
|
|
60
|
+
break;
|
|
61
|
+
case "--golden":
|
|
62
|
+
golden = value;
|
|
63
|
+
i += 1;
|
|
64
|
+
break;
|
|
65
|
+
case "--pattern":
|
|
66
|
+
if (value !== undefined)
|
|
67
|
+
patterns.push(value);
|
|
68
|
+
i += 1;
|
|
69
|
+
break;
|
|
70
|
+
case "--sweep":
|
|
71
|
+
sweep = true;
|
|
72
|
+
break;
|
|
73
|
+
case "--json":
|
|
74
|
+
json = true;
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`unknown argument "${String(flag)}"\n\n${USAGE}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (source === "" || corpus === "")
|
|
81
|
+
throw new Error(`--source and --corpus are required\n\n${USAGE}`);
|
|
82
|
+
return { source, corpus, golden, patterns, sweep, json };
|
|
83
|
+
}
|
|
84
|
+
async function loadSource(specifier) {
|
|
85
|
+
const hash = specifier.lastIndexOf("#");
|
|
86
|
+
if (hash === -1)
|
|
87
|
+
throw new Error(`--source must be "<module>#<export>", got "${specifier}"`);
|
|
88
|
+
const modulePath = specifier.slice(0, hash);
|
|
89
|
+
const exportName = specifier.slice(hash + 1);
|
|
90
|
+
const url = modulePath.startsWith(".") || modulePath.startsWith("/")
|
|
91
|
+
? pathToFileURL(resolve(modulePath)).href
|
|
92
|
+
: modulePath;
|
|
93
|
+
const loaded = (await import(__rewriteRelativeImportExtension(url)));
|
|
94
|
+
const entry = loaded[exportName];
|
|
95
|
+
if (entry === undefined)
|
|
96
|
+
throw new Error(`"${modulePath}" has no export "${exportName}"`);
|
|
97
|
+
if (typeof entry === "function")
|
|
98
|
+
return entry;
|
|
99
|
+
return () => entry;
|
|
100
|
+
}
|
|
101
|
+
export async function main(argv, log = console.log) {
|
|
102
|
+
const args = parseArgs(argv);
|
|
103
|
+
const corpus = resolve(args.corpus);
|
|
104
|
+
const manifest = loadManifest(resolve(corpus, "manifest.json"));
|
|
105
|
+
const golden = loadGolden(args.golden ?? resolve(corpus, "..", "..", "golden", "patterns.json"));
|
|
106
|
+
const factory = await loadSource(args.source);
|
|
107
|
+
const shared = {
|
|
108
|
+
manifest,
|
|
109
|
+
golden,
|
|
110
|
+
root: corpus,
|
|
111
|
+
...(args.patterns.length === 0 ? {} : { patterns: args.patterns }),
|
|
112
|
+
};
|
|
113
|
+
if (args.sweep) {
|
|
114
|
+
const sweep = await runConformanceSweep({ ...shared, createSource: factory });
|
|
115
|
+
log(args.json ? JSON.stringify(sweep, null, 2) : formatSweepReport(sweep));
|
|
116
|
+
return sweep.passed ? 0 : 1;
|
|
117
|
+
}
|
|
118
|
+
const result = await runConformance({
|
|
119
|
+
...shared,
|
|
120
|
+
source: await factory(manifest.expectedMaxResolution),
|
|
121
|
+
});
|
|
122
|
+
log(args.json ? JSON.stringify(result, null, 2) : formatReport(result));
|
|
123
|
+
return result.passed ? 0 : 1;
|
|
124
|
+
}
|
|
125
|
+
/* node:coverage disable */
|
|
126
|
+
if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
127
|
+
main(process.argv.slice(2))
|
|
128
|
+
.then((code) => {
|
|
129
|
+
process.exitCode = code;
|
|
130
|
+
})
|
|
131
|
+
.catch((error) => {
|
|
132
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
133
|
+
process.exitCode = 2;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/* node:coverage enable */
|
|
137
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/conformance/cli.ts"],"names":[],"mappings":"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"}
|