@shrkcrft/boundaries 0.1.0-alpha.30 → 0.1.0-alpha.31
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/dist/baseline/compute-baseline.d.ts +8 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -1
- package/dist/baseline/compute-baseline.js +8 -7
- package/dist/baseline/diff-baseline.d.ts +28 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -1
- package/dist/baseline/diff-baseline.js +29 -0
- package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
- package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-finding.js +21 -0
- package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
- package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-kind.js +14 -0
- package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
- package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
- package/dist/evaluate/evaluate-boundaries.js +481 -53
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-unit-finding.js +1 -0
- package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
- package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
- package/dist/evaluate/settle-boundary-rule.js +73 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
- package/dist/extract/code-zones.d.ts +95 -3
- package/dist/extract/code-zones.d.ts.map +1 -1
- package/dist/extract/code-zones.js +313 -4
- package/dist/extract/extract-tokens.d.ts +30 -0
- package/dist/extract/extract-tokens.d.ts.map +1 -1
- package/dist/extract/extract-tokens.js +233 -27
- package/dist/extract/i-labeled-source.d.ts +12 -0
- package/dist/extract/i-labeled-source.d.ts.map +1 -0
- package/dist/extract/i-labeled-source.js +1 -0
- package/dist/extract/import-edges.d.ts +1 -0
- package/dist/extract/import-edges.d.ts.map +1 -1
- package/dist/extract/import-edges.js +20 -7
- package/dist/extract/inspect-source.d.ts +32 -3
- package/dist/extract/inspect-source.d.ts.map +1 -1
- package/dist/extract/inspect-source.js +50 -7
- package/dist/extract/parse-imports.d.ts +34 -12
- package/dist/extract/parse-imports.d.ts.map +1 -1
- package/dist/extract/parse-imports.js +119 -20
- package/dist/extract/scan-literals.d.ts +12 -1
- package/dist/extract/scan-literals.d.ts.map +1 -1
- package/dist/extract/scan-literals.js +15 -1
- package/dist/extract/source-liveness-request.d.ts +24 -0
- package/dist/extract/source-liveness-request.d.ts.map +1 -0
- package/dist/extract/source-liveness-request.js +45 -0
- package/dist/generated/check-provenance.d.ts.map +1 -1
- package/dist/generated/check-provenance.js +3 -1
- package/dist/generated/read-regen-tree.d.ts +16 -0
- package/dist/generated/read-regen-tree.d.ts.map +1 -0
- package/dist/generated/read-regen-tree.js +61 -0
- package/dist/generated/scan-generated.d.ts +8 -0
- package/dist/generated/scan-generated.d.ts.map +1 -1
- package/dist/generated/scan-generated.js +25 -10
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -0
- package/dist/model/boundary-intended-empty-line.d.ts +10 -0
- package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
- package/dist/model/boundary-intended-empty-line.js +11 -0
- package/dist/model/boundary-markable-list.d.ts +15 -0
- package/dist/model/boundary-markable-list.d.ts.map +1 -0
- package/dist/model/boundary-markable-list.js +15 -0
- package/dist/model/boundary-rule-input-keys.d.ts +11 -0
- package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
- package/dist/model/boundary-rule-input-keys.js +29 -0
- package/dist/model/boundary-rule-input.d.ts +23 -0
- package/dist/model/boundary-rule-input.d.ts.map +1 -0
- package/dist/model/boundary-rule-input.js +1 -0
- package/dist/model/boundary-rule-key-problems.d.ts +11 -0
- package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-key-problems.js +36 -0
- package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
- package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-marker-problems.js +94 -0
- package/dist/model/boundary-rule-scope.d.ts +88 -0
- package/dist/model/boundary-rule-scope.d.ts.map +1 -0
- package/dist/model/boundary-rule-scope.js +175 -0
- package/dist/model/boundary-rule.d.ts +120 -5
- package/dist/model/boundary-rule.d.ts.map +1 -1
- package/dist/model/boundary-rule.js +148 -1
- package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
- package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
- package/dist/model/boundary-unit-problem-issue.js +16 -0
- package/dist/model/normalize-boundary-rule.d.ts +21 -0
- package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
- package/dist/model/normalize-boundary-rule.js +62 -0
- package/dist/policy/evaluate-policy.d.ts +80 -3
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +152 -19
- package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
- package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
- package/dist/policy/i-policy-rule-liveness.js +1 -0
- package/dist/policy/run-policy.d.ts +1 -1
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +171 -21
- package/dist/registry/load-boundary-rules.d.ts +29 -1
- package/dist/registry/load-boundary-rules.d.ts.map +1 -1
- package/dist/registry/load-boundary-rules.js +38 -8
- package/dist/scan/glob.d.ts +90 -0
- package/dist/scan/glob.d.ts.map +1 -1
- package/dist/scan/glob.js +205 -0
- package/dist/scan/i-glob-unit-measure.d.ts +22 -0
- package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
- package/dist/scan/i-glob-unit-measure.js +1 -0
- package/dist/scan/import-pattern.d.ts +109 -0
- package/dist/scan/import-pattern.d.ts.map +1 -0
- package/dist/scan/import-pattern.js +191 -0
- package/dist/scan/node-builtin-package-names.d.ts +13 -0
- package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
- package/dist/scan/node-builtin-package-names.js +25 -0
- package/dist/scan/scan-imports.d.ts +36 -3
- package/dist/scan/scan-imports.d.ts.map +1 -1
- package/dist/scan/scan-imports.js +114 -45
- package/dist/util/blank-run-hazard-finding.d.ts +34 -0
- package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
- package/dist/util/blank-run-hazard-finding.js +1 -0
- package/dist/util/blank-run-hazard.d.ts +29 -0
- package/dist/util/blank-run-hazard.d.ts.map +1 -0
- package/dist/util/blank-run-hazard.js +450 -0
- package/dist/util/dead-glob-units.d.ts +46 -0
- package/dist/util/dead-glob-units.d.ts.map +1 -0
- package/dist/util/dead-glob-units.js +116 -0
- package/dist/util/glob-list-liveness-input.d.ts +31 -0
- package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
- package/dist/util/glob-list-liveness-input.js +65 -0
- package/dist/util/i-dead-glob-unit.d.ts +24 -0
- package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
- package/dist/util/i-dead-glob-unit.js +1 -0
- package/dist/util/i-glob-list-units.d.ts +25 -0
- package/dist/util/i-glob-list-units.d.ts.map +1 -0
- package/dist/util/i-glob-list-units.js +1 -0
- package/dist/util/i-glob-liveness-list.d.ts +23 -0
- package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-list.js +1 -0
- package/dist/util/i-glob-liveness-request.d.ts +11 -0
- package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-request.js +1 -0
- package/dist/util/i-glob-negation.d.ts +13 -0
- package/dist/util/i-glob-negation.d.ts.map +1 -0
- package/dist/util/i-glob-negation.js +1 -0
- package/dist/util/matched-files.d.ts +22 -0
- package/dist/util/matched-files.d.ts.map +1 -0
- package/dist/util/matched-files.js +1 -0
- package/dist/util/negation-cause.d.ts +15 -0
- package/dist/util/negation-cause.d.ts.map +1 -0
- package/dist/util/negation-cause.js +21 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
- package/dist/util/plane-scan-exclude-dirs.js +22 -0
- package/dist/util/read-glob-list-liveness.d.ts +20 -0
- package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
- package/dist/util/read-glob-list-liveness.js +20 -0
- package/dist/util/read-scope-coverage.d.ts +90 -0
- package/dist/util/read-scope-coverage.d.ts.map +1 -0
- package/dist/util/read-scope-coverage.js +173 -0
- package/dist/util/read-scope.d.ts +14 -0
- package/dist/util/read-scope.d.ts.map +1 -0
- package/dist/util/read-scope.js +1 -0
- package/dist/util/read-selected-files.d.ts +16 -0
- package/dist/util/read-selected-files.d.ts.map +1 -0
- package/dist/util/read-selected-files.js +25 -0
- package/dist/util/settle-glob-lists.d.ts +12 -0
- package/dist/util/settle-glob-lists.d.ts.map +1 -0
- package/dist/util/settle-glob-lists.js +13 -0
- package/dist/util/unread-file-reason.d.ts +26 -0
- package/dist/util/unread-file-reason.d.ts.map +1 -0
- package/dist/util/unread-file-reason.js +26 -0
- package/dist/util/unread-file.d.ts +10 -0
- package/dist/util/unread-file.d.ts.map +1 -0
- package/dist/util/unread-file.js +1 -0
- package/dist/util/walk-files.d.ts +74 -10
- package/dist/util/walk-files.d.ts.map +1 -1
- package/dist/util/walk-files.js +137 -31
- package/dist/wiring/evaluate-wiring.d.ts +79 -5
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +267 -12
- package/dist/wiring/explain-wiring.d.ts +49 -5
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +118 -10
- package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
- package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/i-idiom-role-coverage.js +1 -0
- package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
- package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/i-registration-query-verdict.js +1 -0
- package/dist/wiring/i-registration-roles.d.ts +71 -0
- package/dist/wiring/i-registration-roles.d.ts.map +1 -0
- package/dist/wiring/i-registration-roles.js +1 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/measure-idiom-role-coverage.js +20 -0
- package/dist/wiring/measure-registration-roles.d.ts +25 -0
- package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
- package/dist/wiring/measure-registration-roles.js +124 -0
- package/dist/wiring/plan-wiring-fix.js +2 -2
- package/dist/wiring/registration-graph.d.ts +12 -0
- package/dist/wiring/registration-graph.d.ts.map +1 -1
- package/dist/wiring/registration-graph.js +16 -5
- package/dist/wiring/registration-query-verdict.d.ts +23 -0
- package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/registration-query-verdict.js +118 -0
- package/dist/wiring/registry-query.d.ts +8 -0
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +12 -5
- package/dist/wiring/scan-wiring-files.d.ts +4 -2
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +52 -15
- package/dist/wiring/sink-imports.d.ts +11 -0
- package/dist/wiring/sink-imports.d.ts.map +1 -1
- package/dist/wiring/sink-imports.js +11 -2
- package/dist/wiring/trace-literal.d.ts +6 -0
- package/dist/wiring/trace-literal.d.ts.map +1 -1
- package/dist/wiring/trace-literal.js +5 -2
- package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
- package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
- package/dist/wiring/wiring-labeled-sources.js +20 -0
- package/package.json +2 -2
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Which text an import statement may be read from.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* It is a lexer, not a compiler: it answers what the text says, which is the
|
|
12
|
-
* largest thing that stays honest across `.ts`, `.tsx`, `.mts` and plain JS
|
|
13
|
-
* with one code path.
|
|
4
|
+
* - `code` (the default): the statement's keyword must start in executable
|
|
5
|
+
* code and its specifier must be a real string literal; comments are
|
|
6
|
+
* blanked before matching, so an apostrophe inside a comment within a
|
|
7
|
+
* multi-line clause can no longer cut a real import short.
|
|
8
|
+
* - `all`: the raw file text — every byte, comments included. The explicit
|
|
9
|
+
* escape hatch (`check boundaries --include-comments`, `scan: 'all'`).
|
|
14
10
|
*/
|
|
11
|
+
export type ImportParseZone = 'code' | 'all';
|
|
12
|
+
/** Options for {@link parseImportStatements}. */
|
|
13
|
+
export interface IParseImportsOptions {
|
|
14
|
+
/** Default `code`. */
|
|
15
|
+
readonly zone?: ImportParseZone;
|
|
16
|
+
}
|
|
15
17
|
/** How a name entered scope through an import. */
|
|
16
18
|
export type ImportBindingKind = 'named' | 'default' | 'namespace';
|
|
17
19
|
/** One name bound by an import statement. */
|
|
@@ -53,6 +55,26 @@ export interface IParsedImport {
|
|
|
53
55
|
* dependency graph legitimately counts it as an edge. Returning it and letting
|
|
54
56
|
* each caller decide keeps that judgement at the call site instead of baking
|
|
55
57
|
* one feature's answer into the shared parser.
|
|
58
|
+
*
|
|
59
|
+
* In the default `code` zone a statement is kept only when its keyword starts
|
|
60
|
+
* in a code zone AND its specifier quote opens a real string literal — the
|
|
61
|
+
* same {@link zoneKeepsAt} authority the policy plane and the extraction DSL
|
|
62
|
+
* use. Offsets, lines and `raw` always refer to the ORIGINAL text (blanking
|
|
63
|
+
* preserves length), so callers that splice `content` stay correct.
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseImportStatements(content: string, options?: IParseImportsOptions): IParsedImport[];
|
|
66
|
+
/**
|
|
67
|
+
* {@link parseImportStatements}, plus how many characters the zone BLANKED
|
|
68
|
+
* before matching: the comment characters under `code`, `0` under `all`.
|
|
69
|
+
*
|
|
70
|
+
* The `import-edges` extractor zones itself (a pre-blanked buffer would erase
|
|
71
|
+
* every specifier), so this is the figure it reports as its `blankedChars` —
|
|
72
|
+
* the number `wiring explain` / `gates explain` print as
|
|
73
|
+
* `scan: code (N chars blanked)`. Without it the note claimed a zone removed
|
|
74
|
+
* nothing while it had dropped a commented-out edge.
|
|
56
75
|
*/
|
|
57
|
-
export declare function
|
|
76
|
+
export declare function parseImportStatementsWithMeta(content: string, options?: IParseImportsOptions): {
|
|
77
|
+
readonly statements: IParsedImport[];
|
|
78
|
+
readonly blankedChars: number;
|
|
79
|
+
};
|
|
58
80
|
//# sourceMappingURL=parse-imports.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-imports.d.ts","sourceRoot":"","sources":["../../src/extract/parse-imports.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parse-imports.d.ts","sourceRoot":"","sources":["../../src/extract/parse-imports.ts"],"names":[],"mappings":"AA8BA;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7C,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,sBAAsB;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;CACjC;AAED,kDAAkD;AAClD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhG,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAsID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,aAAa,EAAE,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC;IAAE,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CA4EzE"}
|
|
@@ -11,19 +11,43 @@
|
|
|
11
11
|
* It is a lexer, not a compiler: it answers what the text says, which is the
|
|
12
12
|
* largest thing that stays honest across `.ts`, `.tsx`, `.mts` and plain JS
|
|
13
13
|
* with one code path.
|
|
14
|
+
*
|
|
15
|
+
* Round 11 (6.1a) made it the ONLY import extractor: `scanImports` (every
|
|
16
|
+
* boundary check, drift, impact, review packet…) and the `import-edges` DSL
|
|
17
|
+
* extractor both read through here, so "what does this file import" has one
|
|
18
|
+
* answer. By default it reads CODE only — a commented-out import, an import in
|
|
19
|
+
* a doc-comment code fence, or `"import x from 'y'"` inside a string is not a
|
|
20
|
+
* dependency. Measured against the TypeScript oracle over this repo (1716
|
|
21
|
+
* files): 79 phantom edges and 1 real miss before, 0 / 0 after.
|
|
14
22
|
*/
|
|
23
|
+
import { blankZoneKinds, lexCodeZones, zoneContaining, zoneKeepsAt, } from "./code-zones.js";
|
|
15
24
|
/**
|
|
16
25
|
* Matches one import/re-export statement up to its specifier.
|
|
17
26
|
*
|
|
18
27
|
* The clause may span lines (a multi-line `{ … }` block is normal) but never
|
|
19
|
-
* contains a
|
|
20
|
-
* past the end of its own statement into the next one.
|
|
28
|
+
* contains a `;`, a quote or a BACKTICK, which is what keeps the lazy match
|
|
29
|
+
* from running past the end of its own statement into the next one. No real
|
|
30
|
+
* import clause holds a backtick; prose does — a doc comment's
|
|
31
|
+
* `` `shrk import <format> --populate` `` used to open a clause that ran on to
|
|
32
|
+
* the next real `from '…'`, so the raw reading (`--include-comments`,
|
|
33
|
+
* `scan: 'all'`) filed that import on the comment's line. The old scanImports
|
|
34
|
+
* regex excluded the backtick too; raw-mode lines match it again.
|
|
21
35
|
*
|
|
22
36
|
* `export … from '…'` is matched too: a re-export is an edge in the dependency
|
|
23
37
|
* graph exactly like an import, and a barrel that is invisible here would make
|
|
24
38
|
* every consumer behind it invisible as well.
|
|
39
|
+
*
|
|
40
|
+
* Linear by construction (round 11, 6.1(b)): the old form
|
|
41
|
+
* `\s+([^;'"]*?)\s*from` put three whitespace-consuming quantifiers side by
|
|
42
|
+
* side, which is fine on raw source and O(run²) once comments are blanked into
|
|
43
|
+
* long runs of spaces (6 / 24 / 94 / 371 ms as a JSDoc block doubled). Here a
|
|
44
|
+
* single `\s` separates the keyword from ONE lazy class, and `\bfrom` needs no
|
|
45
|
+
* whitespace quantifier in front of it, so each keyword costs one forward scan
|
|
46
|
+
* to its statement's end. Verified byte-identical to the old form on the raw
|
|
47
|
+
* text of every file in this repo. Do not reintroduce adjacent `\s*`/`\s+`
|
|
48
|
+
* around a lazy class — `r75-import-scan-zones.test.ts` times it.
|
|
25
49
|
*/
|
|
26
|
-
const IMPORT_STATEMENT = /\b(import|export)\s
|
|
50
|
+
const IMPORT_STATEMENT = /\b(import|export)\s([^;'"`]*?)\bfrom\s*['"]([^'"]+)['"]\s*;?/g;
|
|
27
51
|
/**
|
|
28
52
|
* The specifier-only forms, which bind no name: a side-effect import, a dynamic
|
|
29
53
|
* `import()`, and `require()`. Each is a real dependency edge — the boundary
|
|
@@ -90,14 +114,42 @@ function parseClause(clause) {
|
|
|
90
114
|
}
|
|
91
115
|
return bindings;
|
|
92
116
|
}
|
|
93
|
-
/**
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
for (let i =
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
/** Offsets of every `\n` in `content`, for O(log n) line lookups. */
|
|
118
|
+
function newlineOffsets(content) {
|
|
119
|
+
const out = [];
|
|
120
|
+
for (let i = content.indexOf('\n'); i !== -1; i = content.indexOf('\n', i + 1))
|
|
121
|
+
out.push(i);
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 1-based line of `index`: one plus the number of newlines BEFORE it. The
|
|
126
|
+
* index is the keyword's own offset — the old `scanImports` regex consumed the
|
|
127
|
+
* preceding `\n` with `(?:^|\s)`, so every import after line 1 was reported
|
|
128
|
+
* one line early (round 11, 6.1a#line-numbers).
|
|
129
|
+
*/
|
|
130
|
+
function lineAt(newlines, index) {
|
|
131
|
+
let lo = 0;
|
|
132
|
+
let hi = newlines.length;
|
|
133
|
+
while (lo < hi) {
|
|
134
|
+
const mid = (lo + hi) >> 1;
|
|
135
|
+
if (newlines[mid] < index)
|
|
136
|
+
lo = mid + 1;
|
|
137
|
+
else
|
|
138
|
+
hi = mid;
|
|
99
139
|
}
|
|
100
|
-
return
|
|
140
|
+
return lo + 1;
|
|
141
|
+
}
|
|
142
|
+
/** Zones blanked before matching in `code` mode: comments only — the specifier is a string. */
|
|
143
|
+
const COMMENT_ZONES = new Set(['comment']);
|
|
144
|
+
/**
|
|
145
|
+
* Offset (within the match) of the specifier's OPENING quote. Every pattern
|
|
146
|
+
* ends `<quote><specifier><quote>` followed only by whitespace, `;` or `)`,
|
|
147
|
+
* and no clause may contain a quote, so the last quote in the match closes the
|
|
148
|
+
* specifier.
|
|
149
|
+
*/
|
|
150
|
+
function specifierQuoteOffset(match, specifier) {
|
|
151
|
+
const closing = Math.max(match.lastIndexOf("'"), match.lastIndexOf('"'));
|
|
152
|
+
return closing - specifier.length - 1;
|
|
101
153
|
}
|
|
102
154
|
/**
|
|
103
155
|
* Every import / re-export statement in `content`, with the names it binds.
|
|
@@ -107,22 +159,66 @@ function lineAt(content, index) {
|
|
|
107
159
|
* dependency graph legitimately counts it as an edge. Returning it and letting
|
|
108
160
|
* each caller decide keeps that judgement at the call site instead of baking
|
|
109
161
|
* one feature's answer into the shared parser.
|
|
162
|
+
*
|
|
163
|
+
* In the default `code` zone a statement is kept only when its keyword starts
|
|
164
|
+
* in a code zone AND its specifier quote opens a real string literal — the
|
|
165
|
+
* same {@link zoneKeepsAt} authority the policy plane and the extraction DSL
|
|
166
|
+
* use. Offsets, lines and `raw` always refer to the ORIGINAL text (blanking
|
|
167
|
+
* preserves length), so callers that splice `content` stay correct.
|
|
168
|
+
*/
|
|
169
|
+
export function parseImportStatements(content, options = {}) {
|
|
170
|
+
return parseImportStatementsWithMeta(content, options).statements;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* {@link parseImportStatements}, plus how many characters the zone BLANKED
|
|
174
|
+
* before matching: the comment characters under `code`, `0` under `all`.
|
|
175
|
+
*
|
|
176
|
+
* The `import-edges` extractor zones itself (a pre-blanked buffer would erase
|
|
177
|
+
* every specifier), so this is the figure it reports as its `blankedChars` —
|
|
178
|
+
* the number `wiring explain` / `gates explain` print as
|
|
179
|
+
* `scan: code (N chars blanked)`. Without it the note claimed a zone removed
|
|
180
|
+
* nothing while it had dropped a commented-out edge.
|
|
110
181
|
*/
|
|
111
|
-
export function
|
|
182
|
+
export function parseImportStatementsWithMeta(content, options = {}) {
|
|
183
|
+
const zone = options.zone ?? 'code';
|
|
184
|
+
const zones = zone === 'code' ? lexCodeZones(content) : undefined;
|
|
185
|
+
const blanked = zones ? blankZoneKinds(content, zones, COMMENT_ZONES) : undefined;
|
|
186
|
+
const text = blanked ? blanked.content : content;
|
|
187
|
+
// Built on first use: a file whose statements are all zoned out (or that has
|
|
188
|
+
// none) never needs a line table.
|
|
189
|
+
let newlines;
|
|
190
|
+
const lineOfIndex = (index) => lineAt((newlines ??= newlineOffsets(content)), index);
|
|
191
|
+
const keep = (index, match, specifier) => {
|
|
192
|
+
if (!zones)
|
|
193
|
+
return true;
|
|
194
|
+
const quote = index + specifierQuoteOffset(match, specifier);
|
|
195
|
+
// The specifier's quote must OPEN a plain string literal. "Inside a string"
|
|
196
|
+
// is not enough: `export const t = \`import z from 'w'\`` starts with a
|
|
197
|
+
// code `export`, and its `'w'` sits inside the template literal.
|
|
198
|
+
const specZone = zoneContaining(zones, quote);
|
|
199
|
+
return (zoneKeepsAt('code', zones, index) &&
|
|
200
|
+
zoneKeepsAt('strings', zones, quote) &&
|
|
201
|
+
specZone !== undefined &&
|
|
202
|
+
specZone.start === quote &&
|
|
203
|
+
specZone.template !== true);
|
|
204
|
+
};
|
|
112
205
|
const out = [];
|
|
113
206
|
const claimed = [];
|
|
114
207
|
let m;
|
|
115
208
|
IMPORT_STATEMENT.lastIndex = 0;
|
|
116
|
-
while ((m = IMPORT_STATEMENT.exec(
|
|
209
|
+
while ((m = IMPORT_STATEMENT.exec(text)) !== null) {
|
|
210
|
+
const specifier = m[3];
|
|
211
|
+
if (!keep(m.index, m[0], specifier))
|
|
212
|
+
continue;
|
|
117
213
|
const clause = m[2];
|
|
118
214
|
const typeOnly = /^type\b/.test(clause.trim());
|
|
119
215
|
claimed.push({ start: m.index, end: m.index + m[0].length });
|
|
120
216
|
out.push({
|
|
121
|
-
specifier
|
|
217
|
+
specifier,
|
|
122
218
|
bindings: typeOnly ? [] : parseClause(clause),
|
|
123
|
-
line:
|
|
219
|
+
line: lineOfIndex(m.index),
|
|
124
220
|
index: m.index,
|
|
125
|
-
raw: m[0],
|
|
221
|
+
raw: content.slice(m.index, m.index + m[0].length),
|
|
126
222
|
typeOnly,
|
|
127
223
|
kind: m[1] === 'export' ? 'reexport' : 'import',
|
|
128
224
|
});
|
|
@@ -137,19 +233,22 @@ export function parseImportStatements(content) {
|
|
|
137
233
|
[REQUIRE_CALL, 'require'],
|
|
138
234
|
]) {
|
|
139
235
|
re.lastIndex = 0;
|
|
140
|
-
while ((m = re.exec(
|
|
236
|
+
while ((m = re.exec(text)) !== null) {
|
|
141
237
|
if (overlapsClaimed(m.index))
|
|
142
238
|
continue;
|
|
239
|
+
const specifier = m[1];
|
|
240
|
+
if (!keep(m.index, m[0], specifier))
|
|
241
|
+
continue;
|
|
143
242
|
out.push({
|
|
144
|
-
specifier
|
|
243
|
+
specifier,
|
|
145
244
|
bindings: [],
|
|
146
|
-
line:
|
|
245
|
+
line: lineOfIndex(m.index),
|
|
147
246
|
index: m.index,
|
|
148
|
-
raw: m[0],
|
|
247
|
+
raw: content.slice(m.index, m.index + m[0].length),
|
|
149
248
|
typeOnly: false,
|
|
150
249
|
kind,
|
|
151
250
|
});
|
|
152
251
|
}
|
|
153
252
|
}
|
|
154
|
-
return out.sort((a, b) => a.index - b.index);
|
|
253
|
+
return { statements: out.sort((a, b) => a.index - b.index), blankedChars: blanked?.blankedChars ?? 0 };
|
|
155
254
|
}
|
|
@@ -11,7 +11,18 @@
|
|
|
11
11
|
* comment syntax differs from the C/JS family (e.g. `#` comments) is scanned as
|
|
12
12
|
* plain code.
|
|
13
13
|
*/
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Index of the closing quote of the string starting at `start` (handles escapes).
|
|
16
|
+
*
|
|
17
|
+
* A `'…'` or `"…"` literal ends at an unescaped NEWLINE (JS semantics — only a
|
|
18
|
+
* template literal spans lines): the returned index is then the last character
|
|
19
|
+
* before the newline. Without this a quote the lexer could not recognise as
|
|
20
|
+
* code — the `'` inside a regex literal such as `/[&<>"']/g`, an apostrophe in
|
|
21
|
+
* prose — swallowed the rest of the FILE as one phantom string, hiding every
|
|
22
|
+
* real construct after it (round 11, 6.1a: a real `import()` 60 lines later
|
|
23
|
+
* vanished). Bounding the mis-lex to one line is strictly more accurate for
|
|
24
|
+
* every caller: zone lexing, the policy plane and array extraction alike.
|
|
25
|
+
*/
|
|
15
26
|
export declare function skipString(content: string, start: number): number;
|
|
16
27
|
/**
|
|
17
28
|
* If a comment starts at `start`, the index of its LAST character; otherwise
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-literals.d.ts","sourceRoot":"","sources":["../../src/extract/scan-literals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH
|
|
1
|
+
{"version":3,"file":"scan-literals.d.ts","sourceRoot":"","sources":["../../src/extract/scan-literals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYjE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA0CD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAgC7E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ7D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsB7D"}
|
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
* comment syntax differs from the C/JS family (e.g. `#` comments) is scanned as
|
|
12
12
|
* plain code.
|
|
13
13
|
*/
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Index of the closing quote of the string starting at `start` (handles escapes).
|
|
16
|
+
*
|
|
17
|
+
* A `'…'` or `"…"` literal ends at an unescaped NEWLINE (JS semantics — only a
|
|
18
|
+
* template literal spans lines): the returned index is then the last character
|
|
19
|
+
* before the newline. Without this a quote the lexer could not recognise as
|
|
20
|
+
* code — the `'` inside a regex literal such as `/[&<>"']/g`, an apostrophe in
|
|
21
|
+
* prose — swallowed the rest of the FILE as one phantom string, hiding every
|
|
22
|
+
* real construct after it (round 11, 6.1a: a real `import()` 60 lines later
|
|
23
|
+
* vanished). Bounding the mis-lex to one line is strictly more accurate for
|
|
24
|
+
* every caller: zone lexing, the policy plane and array extraction alike.
|
|
25
|
+
*/
|
|
15
26
|
export function skipString(content, start) {
|
|
16
27
|
const quote = content[start];
|
|
28
|
+
const singleLine = quote === "'" || quote === '"';
|
|
17
29
|
for (let i = start + 1; i < content.length; i += 1) {
|
|
18
30
|
if (content[i] === '\\') {
|
|
19
31
|
i += 1;
|
|
@@ -21,6 +33,8 @@ export function skipString(content, start) {
|
|
|
21
33
|
}
|
|
22
34
|
if (content[i] === quote)
|
|
23
35
|
return i;
|
|
36
|
+
if (singleLine && content[i] === '\n')
|
|
37
|
+
return i - 1;
|
|
24
38
|
}
|
|
25
39
|
return content.length - 1;
|
|
26
40
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IGlobListUnits } from '../util/i-glob-list-units.js';
|
|
2
|
+
import type { IGlobLivenessRequest } from '../util/i-glob-liveness-request.js';
|
|
3
|
+
import type { ILabeledSource } from './i-labeled-source.js';
|
|
4
|
+
/**
|
|
5
|
+
* THE glob lists of a rule's extraction sources as the gate-plane liveness
|
|
6
|
+
* settle reads them (round 13) — every source's `files` and, NEW this round,
|
|
7
|
+
* an `import-edges` source's `to.files` (judged for liveness, so a typo'd fence
|
|
8
|
+
* target is no longer a silent pass, and markable, so a fence to a planned
|
|
9
|
+
* subtree is sayable).
|
|
10
|
+
*
|
|
11
|
+
* Each list is qualified by its side (`declared.files`, `compute.source.to.files`
|
|
12
|
+
* — `qualifyListPath`), and each source's markers the same way
|
|
13
|
+
* (`qualifyUnitMarks`), so one settle spans a rule's every source without two
|
|
14
|
+
* sides' `files` colliding. In a multi-source rule a unit prints `<side>: <glob>`
|
|
15
|
+
* (today's dead-glob selector); `to.files` units print `[<side> ]to.files: <glob>`.
|
|
16
|
+
*
|
|
17
|
+
* `gates coverage` (`buildGateCoverage`), the wiring engine (`runWiring`) and
|
|
18
|
+
* the registration role measurement all build their request here, so the one
|
|
19
|
+
* rule's units read identically on every surface. `unitsOf` lets a caller
|
|
20
|
+
* judge a `files` list off a walk it already did; `to.files` matches resolved
|
|
21
|
+
* import targets, so it is judged off the one reader's own walk of its globs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sourceLivenessRequest(projectRoot: string, sources: readonly ILabeledSource[], excludeDirs?: readonly string[], subject?: string, unitsOf?: (globs: readonly string[]) => IGlobListUnits): IGlobLivenessRequest;
|
|
24
|
+
//# sourceMappingURL=source-liveness-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-liveness-request.d.ts","sourceRoot":"","sources":["../../src/extract/source-liveness-request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,WAAW,GAAE,SAAS,MAAM,EAAO,EACnC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,cAAc,GACrD,oBAAoB,CAuBtB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { qualifyListPath, qualifyUnitMarks } from '@shrkcrft/core';
|
|
2
|
+
import { readGlobListUnits } from "../util/dead-glob-units.js";
|
|
3
|
+
/**
|
|
4
|
+
* THE glob lists of a rule's extraction sources as the gate-plane liveness
|
|
5
|
+
* settle reads them (round 13) — every source's `files` and, NEW this round,
|
|
6
|
+
* an `import-edges` source's `to.files` (judged for liveness, so a typo'd fence
|
|
7
|
+
* target is no longer a silent pass, and markable, so a fence to a planned
|
|
8
|
+
* subtree is sayable).
|
|
9
|
+
*
|
|
10
|
+
* Each list is qualified by its side (`declared.files`, `compute.source.to.files`
|
|
11
|
+
* — `qualifyListPath`), and each source's markers the same way
|
|
12
|
+
* (`qualifyUnitMarks`), so one settle spans a rule's every source without two
|
|
13
|
+
* sides' `files` colliding. In a multi-source rule a unit prints `<side>: <glob>`
|
|
14
|
+
* (today's dead-glob selector); `to.files` units print `[<side> ]to.files: <glob>`.
|
|
15
|
+
*
|
|
16
|
+
* `gates coverage` (`buildGateCoverage`), the wiring engine (`runWiring`) and
|
|
17
|
+
* the registration role measurement all build their request here, so the one
|
|
18
|
+
* rule's units read identically on every surface. `unitsOf` lets a caller
|
|
19
|
+
* judge a `files` list off a walk it already did; `to.files` matches resolved
|
|
20
|
+
* import targets, so it is judged off the one reader's own walk of its globs.
|
|
21
|
+
*/
|
|
22
|
+
export function sourceLivenessRequest(projectRoot, sources, excludeDirs = [], subject, unitsOf) {
|
|
23
|
+
const multi = sources.length > 1;
|
|
24
|
+
const exclude = new Set(excludeDirs);
|
|
25
|
+
const judge = unitsOf ?? ((globs) => readGlobListUnits(projectRoot, globs, exclude));
|
|
26
|
+
const lists = [];
|
|
27
|
+
const marks = [];
|
|
28
|
+
for (const { label, source } of sources) {
|
|
29
|
+
const files = source.files ?? [];
|
|
30
|
+
if (files.length > 0) {
|
|
31
|
+
lists.push({ list: qualifyListPath(label, 'files'), ...(multi ? { label } : {}), globs: files, units: judge(files) });
|
|
32
|
+
}
|
|
33
|
+
const to = source.to?.files ?? [];
|
|
34
|
+
if (to.length > 0) {
|
|
35
|
+
lists.push({
|
|
36
|
+
list: qualifyListPath(label, 'to.files'),
|
|
37
|
+
label: multi ? `${label} to.files` : 'to.files',
|
|
38
|
+
globs: to,
|
|
39
|
+
units: readGlobListUnits(projectRoot, to, exclude),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
marks.push(...qualifyUnitMarks(source.expectEmptyUnits ?? [], label));
|
|
43
|
+
}
|
|
44
|
+
return { ...(subject !== undefined ? { subject } : {}), lists, marks };
|
|
45
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-provenance.d.ts","sourceRoot":"","sources":["../../src/generated/check-provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"check-provenance.d.ts","sourceRoot":"","sources":["../../src/generated/check-provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAI7D,0CAA0C;AAC1C,MAAM,MAAM,qBAAqB;AAC/B,qEAAqE;AACnE,gBAAgB;AAClB,mFAAmF;GACjF,YAAY;AACd,qEAAqE;GACnE,kBAAkB;AACpB,0EAA0E;GACxE,cAAc;AAChB,4DAA4D;GAC1D,uBAAuB,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQ7E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,cAAc,GAAE,uBAA4B,GAC3C;IAAE,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgF7D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { globListWalkGlobs } from "../scan/glob.js";
|
|
1
2
|
import { safeCompile } from "../util/safe-regex.js";
|
|
2
3
|
/** The head of a file, as the header contract sees it. */
|
|
3
4
|
function headOf(content, withinLines) {
|
|
@@ -11,7 +12,8 @@ function headOf(content, withinLines) {
|
|
|
11
12
|
*/
|
|
12
13
|
export function deriveOutsideGlobs(generatedGlob) {
|
|
13
14
|
const exts = new Set();
|
|
14
|
-
|
|
15
|
+
// Inclusion globs only: a `!gen/**/*.hand.ts` names no extension to search.
|
|
16
|
+
for (const g of globListWalkGlobs(generatedGlob)) {
|
|
15
17
|
const m = /\.([A-Za-z0-9]+)$/.exec(g);
|
|
16
18
|
if (m)
|
|
17
19
|
exts.add(m[1]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IMatchedFiles } from '../util/matched-files.js';
|
|
2
|
+
/**
|
|
3
|
+
* Read a REGENERATED temp tree (what a `generatedArtifacts[].regen` wrote into
|
|
4
|
+
* `{TMP}`): every file under `root`, keyed by POSIX path relative to it.
|
|
5
|
+
*
|
|
6
|
+
* Same contract as the one reader (`readMatchingFiles`): a file it did not read
|
|
7
|
+
* (over {@link MAX_REGEN_FILE_BYTES}, or unreadable) is returned in `unread`
|
|
8
|
+
* with its reason, never dropped. It used to be dropped, so a regen that wrote
|
|
9
|
+
* a NEW 2.1MB file read "byte-identical to a fresh regen ✓" while the same
|
|
10
|
+
* file at 100 bytes failed as "regen produces it but it is not committed".
|
|
11
|
+
* The caller decides what an unread output means: one keyed onto a committed
|
|
12
|
+
* file cannot be byte-compared (a coverage gap), one keyed onto nothing
|
|
13
|
+
* committed is drift its path alone proves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readRegenTree(root: string): IMatchedFiles;
|
|
16
|
+
//# sourceMappingURL=read-regen-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-regen-tree.d.ts","sourceRoot":"","sources":["../../src/generated/read-regen-tree.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAuCzD"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
import { UnreadFileReason } from "../util/unread-file-reason.js";
|
|
4
|
+
import { MAX_REGEN_FILE_BYTES } from "../util/walk-files.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read a REGENERATED temp tree (what a `generatedArtifacts[].regen` wrote into
|
|
7
|
+
* `{TMP}`): every file under `root`, keyed by POSIX path relative to it.
|
|
8
|
+
*
|
|
9
|
+
* Same contract as the one reader (`readMatchingFiles`): a file it did not read
|
|
10
|
+
* (over {@link MAX_REGEN_FILE_BYTES}, or unreadable) is returned in `unread`
|
|
11
|
+
* with its reason, never dropped. It used to be dropped, so a regen that wrote
|
|
12
|
+
* a NEW 2.1MB file read "byte-identical to a fresh regen ✓" while the same
|
|
13
|
+
* file at 100 bytes failed as "regen produces it but it is not committed".
|
|
14
|
+
* The caller decides what an unread output means: one keyed onto a committed
|
|
15
|
+
* file cannot be byte-compared (a coverage gap), one keyed onto nothing
|
|
16
|
+
* committed is drift its path alone proves.
|
|
17
|
+
*/
|
|
18
|
+
export function readRegenTree(root) {
|
|
19
|
+
const files = new Map();
|
|
20
|
+
const unread = [];
|
|
21
|
+
const visit = (abs) => {
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = readdirSync(abs, { withFileTypes: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const child = nodePath.join(abs, e.name);
|
|
31
|
+
if (e.isDirectory()) {
|
|
32
|
+
visit(child);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!e.isFile())
|
|
36
|
+
continue;
|
|
37
|
+
const rel = nodePath.relative(root, child).split(nodePath.sep).join('/');
|
|
38
|
+
let size;
|
|
39
|
+
try {
|
|
40
|
+
size = statSync(child).size;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
unread.push({ path: rel, reason: UnreadFileReason.Unreadable });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (size > MAX_REGEN_FILE_BYTES) {
|
|
47
|
+
unread.push({ path: rel, reason: UnreadFileReason.OverRegenCap, bytes: size });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
files.set(rel, readFileSync(child, 'utf8'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
unread.push({ path: rel, reason: UnreadFileReason.Unreadable, bytes: size });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
visit(root);
|
|
59
|
+
unread.sort((a, b) => a.path.localeCompare(b.path));
|
|
60
|
+
return { files, unread: Object.freeze(unread) };
|
|
61
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IGeneratedArtifactRule } from '@shrkcrft/core';
|
|
2
|
+
import type { IReadScope } from '../util/read-scope.js';
|
|
2
3
|
/** One writer's slice of a mixed generated tree. */
|
|
3
4
|
export interface IGeneratedSourceSlice {
|
|
4
5
|
/** Label for output — the writer's `id`, or `source[<index>]`. */
|
|
@@ -43,6 +44,13 @@ export interface IGeneratedScan {
|
|
|
43
44
|
readonly outside: ReadonlyMap<string, string>;
|
|
44
45
|
/** Globs actually used for the outside scan (derived or configured). */
|
|
45
46
|
readonly outsideGlobs: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* The rule's read scope: every file read (committed generated files, plus
|
|
49
|
+
* the outside candidates when `forbidOutside` is on), and every matched file
|
|
50
|
+
* the reader did NOT read (over the read cap, or unreadable). An unread
|
|
51
|
+
* committed file was never header-checked or byte-compared.
|
|
52
|
+
*/
|
|
53
|
+
readonly readScope: IReadScope;
|
|
46
54
|
}
|
|
47
55
|
/**
|
|
48
56
|
* Read + CLASSIFY the committed side of a generated-artifact rule.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-generated.d.ts","sourceRoot":"","sources":["../../src/generated/scan-generated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"scan-generated.d.ts","sourceRoot":"","sources":["../../src/generated/scan-generated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,sBAAsB,EAC5B,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,cAAc,CAkHhB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { globListSelects } from "../scan/glob.js";
|
|
2
2
|
import { safeCompile } from "../util/safe-regex.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readSelectedFiles } from "../util/read-selected-files.js";
|
|
4
|
+
import { mergeReadScopes, unreadEntryMatches, unreadEntryWhollyMatches } from "../util/read-scope-coverage.js";
|
|
4
5
|
import { deriveOutsideGlobs } from "./check-provenance.js";
|
|
5
6
|
/**
|
|
6
7
|
* Read + CLASSIFY the committed side of a generated-artifact rule.
|
|
@@ -19,7 +20,10 @@ import { deriveOutsideGlobs } from "./check-provenance.js";
|
|
|
19
20
|
*/
|
|
20
21
|
export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
21
22
|
const exclude = new Set(excludeDirs);
|
|
22
|
-
|
|
23
|
+
// The files `generatedGlob` SELECTS: a `!` entry (`!gen/**/*.hand.ts`)
|
|
24
|
+
// excludes, so a hand file carved out of the tree is never header-checked.
|
|
25
|
+
const generatedRead = readSelectedFiles(projectRoot, rule.generatedGlob, exclude);
|
|
26
|
+
const generated = generatedRead.files;
|
|
23
27
|
const blessPatterns = rule.handMaintained ?? [];
|
|
24
28
|
// The in-file marker is read from the same head window the header contract
|
|
25
29
|
// uses, so "the top of the file" means one thing for both classifications.
|
|
@@ -37,7 +41,7 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
|
37
41
|
const markedHandMaintained = [];
|
|
38
42
|
const checkable = new Map();
|
|
39
43
|
for (const [path, content] of generated) {
|
|
40
|
-
if (blessPatterns.length > 0 &&
|
|
44
|
+
if (blessPatterns.length > 0 && globListSelects(path, blessPatterns)) {
|
|
41
45
|
handMaintained.push(path);
|
|
42
46
|
continue;
|
|
43
47
|
}
|
|
@@ -52,13 +56,17 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
|
52
56
|
markedHandMaintained.sort();
|
|
53
57
|
// A bless is pinned to a NAMED file (the schema forbids a wildcard basename),
|
|
54
58
|
// so one that matches nothing points at a file that has been renamed away.
|
|
59
|
+
// A file over the read cap was still MATCHED, so a bless naming it is live.
|
|
60
|
+
// So is one that could match beneath a directory the walk could not list.
|
|
61
|
+
const matchedPaths = [...generated.keys()];
|
|
55
62
|
const staleHandMaintained = blessPatterns
|
|
56
|
-
.filter((pattern) => !
|
|
63
|
+
.filter((pattern) => !matchedPaths.some((p) => globListSelects(p, [pattern])) &&
|
|
64
|
+
!generatedRead.unread.some((u) => unreadEntryMatches(u, [pattern])))
|
|
57
65
|
.sort();
|
|
58
66
|
const slices = (rule.sources ?? []).map((src, i) => {
|
|
59
67
|
const files = new Map();
|
|
60
68
|
for (const [path, content] of checkable) {
|
|
61
|
-
if (
|
|
69
|
+
if (globListSelects(path, src.glob))
|
|
62
70
|
files.set(path, content);
|
|
63
71
|
}
|
|
64
72
|
return {
|
|
@@ -73,7 +81,7 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
|
73
81
|
// everything its glob matched, so nothing there can be unclassified.
|
|
74
82
|
const unclassified = slices.length === 0
|
|
75
83
|
? []
|
|
76
|
-
: [...checkable.keys()].filter((p) => !slices.some((s) =>
|
|
84
|
+
: [...checkable.keys()].filter((p) => !slices.some((s) => globListSelects(p, s.glob))).sort();
|
|
77
85
|
const wantOutside = rule.provenanceHeader?.forbidOutside === true;
|
|
78
86
|
if (!wantOutside) {
|
|
79
87
|
return {
|
|
@@ -86,18 +94,24 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
|
86
94
|
slices,
|
|
87
95
|
outside: new Map(),
|
|
88
96
|
outsideGlobs: [],
|
|
97
|
+
readScope: { read: generated.size, unread: generatedRead.unread },
|
|
89
98
|
};
|
|
90
99
|
}
|
|
91
100
|
const outsideGlobs = rule.provenanceHeader?.outsideGlob && rule.provenanceHeader.outsideGlob.length > 0
|
|
92
101
|
? [...rule.provenanceHeader.outsideGlob]
|
|
93
102
|
: deriveOutsideGlobs(rule.generatedGlob);
|
|
94
|
-
const all =
|
|
103
|
+
const all = readSelectedFiles(projectRoot, outsideGlobs, exclude);
|
|
95
104
|
const outside = new Map();
|
|
96
|
-
for (const [path, content] of all) {
|
|
97
|
-
|
|
105
|
+
for (const [path, content] of all.files) {
|
|
106
|
+
// A file `generatedGlob` excludes is OUTSIDE the generated tree: carrying
|
|
107
|
+
// the "do not edit" header there is exactly a mislabel.
|
|
108
|
+
if (globListSelects(path, rule.generatedGlob))
|
|
98
109
|
continue;
|
|
99
110
|
outside.set(path, content);
|
|
100
111
|
}
|
|
112
|
+
// An unlistable directory is "outside" unless the generated glob covers ALL
|
|
113
|
+
// of it (it may hold both); `mergeReadScopes` counts a shared path once.
|
|
114
|
+
const outsideUnread = all.unread.filter((u) => !unreadEntryWhollyMatches(u, rule.generatedGlob));
|
|
101
115
|
return {
|
|
102
116
|
generated,
|
|
103
117
|
checkable,
|
|
@@ -108,5 +122,6 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
|
|
|
108
122
|
slices,
|
|
109
123
|
outside,
|
|
110
124
|
outsideGlobs,
|
|
125
|
+
readScope: mergeReadScopes(generated.size + outside.size, [generatedRead.unread, outsideUnread]),
|
|
111
126
|
};
|
|
112
127
|
}
|