@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
|
@@ -26,6 +26,15 @@ export interface IExtractContext {
|
|
|
26
26
|
/** Outcome of running one source: the sites, or a configuration error. */
|
|
27
27
|
export interface IExtractResult {
|
|
28
28
|
readonly sites: readonly IExtractedSite[];
|
|
29
|
+
/**
|
|
30
|
+
* How many characters a non-`all` `scan` zone blanked before extraction.
|
|
31
|
+
*
|
|
32
|
+
* Surfaced (not merely applied) because a zone is the one setting that can
|
|
33
|
+
* make a rule match LESS while still reading green: an author who sees the
|
|
34
|
+
* number drop can tell "my pattern was matching prose" from "my glob went
|
|
35
|
+
* stale". `explain` prints it.
|
|
36
|
+
*/
|
|
37
|
+
readonly blankedChars?: number;
|
|
29
38
|
/** Set when the SOURCE is misconfigured (never thrown — rules degrade). */
|
|
30
39
|
readonly error?: string;
|
|
31
40
|
/**
|
|
@@ -45,4 +54,25 @@ export interface IExtractResult {
|
|
|
45
54
|
* an `error` and no sites.
|
|
46
55
|
*/
|
|
47
56
|
export declare function extractTokens(source: IWiringSource, files: readonly IExtractFileEntry[], context?: IExtractContext): IExtractResult;
|
|
57
|
+
/**
|
|
58
|
+
* Wall-clock cap for ONE file's `regex-capture` pass under a non-`all` scan
|
|
59
|
+
* zone. Checked between matches; a file that trips it contributes nothing and
|
|
60
|
+
* is NAMED (the source errors), so a partial token set can never read green.
|
|
61
|
+
*/
|
|
62
|
+
export declare const ZONED_REGEX_FILE_BUDGET_MS = 1000;
|
|
63
|
+
/**
|
|
64
|
+
* The head regexes the anchor-based extractors run — possibly on a blanked
|
|
65
|
+
* buffer. One factory, so the linearity lock (`r75-blank-run-linearity`)
|
|
66
|
+
* times and lints the exact patterns that run, not copies of them.
|
|
67
|
+
*/
|
|
68
|
+
export declare function extractorHeadPatterns(anchor: string): {
|
|
69
|
+
readonly arrayLiteral: RegExp;
|
|
70
|
+
readonly objectLiteral: RegExp;
|
|
71
|
+
readonly enumBody: RegExp;
|
|
72
|
+
readonly call: RegExp;
|
|
73
|
+
readonly decorator: RegExp;
|
|
74
|
+
readonly stringUnion: RegExp;
|
|
75
|
+
readonly exportDecl: RegExp;
|
|
76
|
+
readonly exportClause: RegExp;
|
|
77
|
+
};
|
|
48
78
|
//# sourceMappingURL=extract-tokens.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-tokens.d.ts","sourceRoot":"","sources":["../../src/extract/extract-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"extract-tokens.d.ts","sourceRoot":"","sources":["../../src/extract/extract-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAMxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AASrE,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,OAAO,GAAE,eAAoB,GAC5B,cAAc,CAiHhB;AA+CD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAmN/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAgBA"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { resolveExtractorAnchor, resolveExtractorKind, validateWiringSource, } from '@shrkcrft/core';
|
|
2
2
|
import { safeCompile } from "../util/safe-regex.js";
|
|
3
|
+
import { findBlankRunHazards } from "../util/blank-run-hazard.js";
|
|
4
|
+
import { blankOutsideZone } from "./code-zones.js";
|
|
3
5
|
import { extractImportEdges } from "./import-edges.js";
|
|
4
6
|
import { elementToken, elementValue, escapeRegex, lineOf, scanBalanced, } from "./scan-literals.js";
|
|
5
7
|
/**
|
|
@@ -17,46 +19,85 @@ export function extractTokens(source, files, context = {}) {
|
|
|
17
19
|
return { sites: [], error };
|
|
18
20
|
const kind = resolveExtractorKind(source);
|
|
19
21
|
const anchor = resolveExtractorAnchor(source);
|
|
22
|
+
// Zone the CONTENT once, up front, instead of teaching every extractor about
|
|
23
|
+
// comments. Blanking preserves length and newlines, so each extractor still
|
|
24
|
+
// reports the true `file:line` and none of them needs zone logic of its own.
|
|
25
|
+
const zone = source.scan ?? 'all';
|
|
26
|
+
let blankedChars = 0;
|
|
27
|
+
// `import-edges` zones itself (round 11, 6.1a#import-edges-scan): it judges a
|
|
28
|
+
// statement by where its KEYWORD starts, through the one import parser.
|
|
29
|
+
// Pre-blanking here erased every specifier (a string) under `scan: 'code'`,
|
|
30
|
+
// and the rule silently extracted nothing.
|
|
31
|
+
const scanned = zone === 'all' || kind === 'import-edges'
|
|
32
|
+
? files
|
|
33
|
+
: files.map((f) => {
|
|
34
|
+
const blanked = blankOutsideZone(f.content, zone);
|
|
35
|
+
blankedChars += blanked.blankedChars;
|
|
36
|
+
return { path: f.path, content: blanked.content };
|
|
37
|
+
});
|
|
20
38
|
let sites;
|
|
39
|
+
// A user pattern run on a BLANKED buffer can backtrack O(run²) on the
|
|
40
|
+
// whitespace runs the zone manufactures. Lint it (the hint reaches `gates
|
|
41
|
+
// coverage` / `explain`), and bound each file: a file over budget is a
|
|
42
|
+
// named, loud skip — the rule errors, never a silent partial.
|
|
43
|
+
let regexHint;
|
|
21
44
|
switch (kind) {
|
|
22
|
-
case 'regex-capture':
|
|
23
|
-
|
|
45
|
+
case 'regex-capture': {
|
|
46
|
+
const zoned = zone !== 'all';
|
|
47
|
+
const hazards = zoned ? findBlankRunHazards(source.pattern) : [];
|
|
48
|
+
if (hazards.length > 0)
|
|
49
|
+
regexHint = blankRunHazardHint(zone, hazards);
|
|
50
|
+
const run = byRegex(source, scanned, zoned, hazards);
|
|
51
|
+
if (run.predicted.length + run.ran.length > 0) {
|
|
52
|
+
return {
|
|
53
|
+
sites: [],
|
|
54
|
+
error: zonedOverBudgetError(zone, run, hazards),
|
|
55
|
+
...(regexHint ? { hint: regexHint } : {}),
|
|
56
|
+
...zoneMeta(zone, blankedChars),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
sites = run.sites;
|
|
24
60
|
break;
|
|
61
|
+
}
|
|
25
62
|
case 'array-members':
|
|
26
|
-
sites = byBracketLiteral(anchor, '[',
|
|
63
|
+
sites = byBracketLiteral(anchor, '[', scanned, (el) => elementToken(el));
|
|
27
64
|
break;
|
|
28
65
|
case 'object-keys':
|
|
29
|
-
sites = byBracketLiteral(anchor, '{',
|
|
66
|
+
sites = byBracketLiteral(anchor, '{', scanned, (el) => source.capture === 'value' ? elementValue(el) : elementToken(el));
|
|
30
67
|
break;
|
|
31
68
|
case 'enum-members':
|
|
32
|
-
sites = byEnumMembers(anchor, source.capture === 'value',
|
|
69
|
+
sites = byEnumMembers(anchor, source.capture === 'value', scanned);
|
|
33
70
|
break;
|
|
34
71
|
case 'export-names':
|
|
35
|
-
sites = byExportNames(
|
|
72
|
+
sites = byExportNames(scanned);
|
|
36
73
|
break;
|
|
37
74
|
case 'call-args':
|
|
38
|
-
sites = byCallArgs(anchor, source.argIndex ?? 0, false,
|
|
75
|
+
sites = byCallArgs(anchor, source.argIndex ?? 0, false, scanned);
|
|
39
76
|
break;
|
|
40
77
|
case 'decorator-args':
|
|
41
|
-
sites = byCallArgs(anchor, source.argIndex ?? 0, true,
|
|
78
|
+
sites = byCallArgs(anchor, source.argIndex ?? 0, true, scanned);
|
|
42
79
|
break;
|
|
43
80
|
case 'string-union-members':
|
|
44
|
-
sites = byStringUnion(anchor,
|
|
81
|
+
sites = byStringUnion(anchor, scanned);
|
|
45
82
|
break;
|
|
46
83
|
case 'json-path':
|
|
47
|
-
sites = byJsonPath(source.jsonPath,
|
|
84
|
+
sites = byJsonPath(source.jsonPath, scanned);
|
|
48
85
|
break;
|
|
49
86
|
case 'filenames':
|
|
50
|
-
sites = byFilenames(source,
|
|
87
|
+
sites = byFilenames(source, scanned);
|
|
51
88
|
break;
|
|
52
89
|
case 'import-edges': {
|
|
53
|
-
const edges = extractImportEdges(source,
|
|
90
|
+
const edges = extractImportEdges(source, scanned, {
|
|
54
91
|
...(context.tsconfigPaths ? { tsconfigPaths: context.tsconfigPaths } : {}),
|
|
55
92
|
});
|
|
56
93
|
if (edges.error)
|
|
57
94
|
return { sites: [], error: edges.error };
|
|
95
|
+
// It zones itself, so what its zone blanked is its own figure — the
|
|
96
|
+
// `scan: code (N chars blanked)` note must not read 0 over a zone that
|
|
97
|
+
// dropped a commented-out edge.
|
|
98
|
+
blankedChars += edges.blankedChars ?? 0;
|
|
58
99
|
if (edges.hint)
|
|
59
|
-
return { sites: edges.sites, hint: edges.hint };
|
|
100
|
+
return { sites: edges.sites, hint: edges.hint, ...zoneMeta(zone, blankedChars) };
|
|
60
101
|
sites = edges.sites;
|
|
61
102
|
break;
|
|
62
103
|
}
|
|
@@ -81,7 +122,11 @@ export function extractTokens(source, files, context = {}) {
|
|
|
81
122
|
});
|
|
82
123
|
}
|
|
83
124
|
}
|
|
84
|
-
return { sites };
|
|
125
|
+
return { sites, ...zoneMeta(zone, blankedChars), ...(regexHint ? { hint: regexHint } : {}) };
|
|
126
|
+
}
|
|
127
|
+
/** The zone provenance to attach to a result — omitted entirely when unzoned. */
|
|
128
|
+
function zoneMeta(zone, blankedChars) {
|
|
129
|
+
return zone === 'all' ? {} : { blankedChars };
|
|
85
130
|
}
|
|
86
131
|
/**
|
|
87
132
|
* One id per FILE, derived from its path.
|
|
@@ -120,13 +165,144 @@ function byFilenames(source, files) {
|
|
|
120
165
|
}
|
|
121
166
|
return sites;
|
|
122
167
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Wall-clock cap for ONE file's `regex-capture` pass under a non-`all` scan
|
|
170
|
+
* zone. Checked between matches; a file that trips it contributes nothing and
|
|
171
|
+
* is NAMED (the source errors), so a partial token set can never read green.
|
|
172
|
+
*/
|
|
173
|
+
export const ZONED_REGEX_FILE_BUDGET_MS = 1000;
|
|
174
|
+
/**
|
|
175
|
+
* For a pattern the blank-run lint flags: a file whose PREDICTED backtracking
|
|
176
|
+
* work (priced by each hazard's reach, below) exceeds this is skipped BEFORE
|
|
177
|
+
* the regex runs. One `exec` over such a buffer cannot be interrupted (a
|
|
178
|
+
* 16 KB newline-crossing run already costs ~300–470 ms), so the prediction is
|
|
179
|
+
* what makes the cap a real bound.
|
|
180
|
+
*/
|
|
181
|
+
const ZONED_HAZARD_COST_LIMIT = 2e8;
|
|
182
|
+
/** Blank stretches shorter than this are ordinary indentation — not worth pricing. */
|
|
183
|
+
const MIN_PRICED_RUN = 64;
|
|
184
|
+
function blankRunCosts(content) {
|
|
185
|
+
let run = 0;
|
|
186
|
+
let segment = 0;
|
|
187
|
+
let newlines = 0;
|
|
188
|
+
let runCost = 0;
|
|
189
|
+
let lineCost = 0;
|
|
190
|
+
let linesTimesRun = 0;
|
|
191
|
+
for (let i = 0; i <= content.length; i += 1) {
|
|
192
|
+
const c = i < content.length ? content.charCodeAt(i) : 0;
|
|
193
|
+
if (c === 0x20) {
|
|
194
|
+
run += 1;
|
|
195
|
+
segment += 1;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (segment >= MIN_PRICED_RUN)
|
|
199
|
+
lineCost += segment * segment;
|
|
200
|
+
segment = 0;
|
|
201
|
+
if (c === 0x0a) {
|
|
202
|
+
run += 1;
|
|
203
|
+
newlines += 1;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (run >= MIN_PRICED_RUN) {
|
|
207
|
+
runCost += run * run;
|
|
208
|
+
linesTimesRun += (newlines + 1) * run;
|
|
209
|
+
}
|
|
210
|
+
run = 0;
|
|
211
|
+
newlines = 0;
|
|
212
|
+
}
|
|
213
|
+
return { run: runCost, line: lineCost, linesTimesRun };
|
|
214
|
+
}
|
|
215
|
+
/** Price every flagged hazard by its OWN reach; the file costs what its worst hazard costs. */
|
|
216
|
+
function predictedHazardCost(hazards, costs) {
|
|
217
|
+
let worst = { cost: 0, measure: 'Σ run²' };
|
|
218
|
+
for (const h of hazards) {
|
|
219
|
+
const priced = !h.crossesNewline
|
|
220
|
+
? { cost: costs.line, measure: 'Σ line-run²' }
|
|
221
|
+
: h.shape === 'leading' && h.fromLineStart
|
|
222
|
+
? { cost: costs.linesTimesRun, measure: 'Σ lines×run' }
|
|
223
|
+
: { cost: costs.run, measure: 'Σ run²' };
|
|
224
|
+
if (priced.cost > worst.cost)
|
|
225
|
+
worst = priced;
|
|
226
|
+
}
|
|
227
|
+
return worst;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The remedy for each hazard shape present. A LEADING quantifier is fixed by
|
|
231
|
+
* a literal anchor — never by another quantifier: a leading `[ \t]*` is still
|
|
232
|
+
* tried at every offset of a blanked line.
|
|
233
|
+
*/
|
|
234
|
+
function hazardRemedy(hazards) {
|
|
235
|
+
const remedies = [];
|
|
236
|
+
if (hazards.some((h) => h.shape === 'leading')) {
|
|
237
|
+
remedies.push('anchor the alternative on a literal — begin it with the token the whitespace precedes ' +
|
|
238
|
+
'(`foo[ \\t]*\\(`, not `[ \\t]*foo\\(`), since a leading whitespace quantifier of any class is tried at every offset');
|
|
239
|
+
}
|
|
240
|
+
if (hazards.some((h) => h.shape === 'adjacent')) {
|
|
241
|
+
remedies.push('collapse the touching whitespace quantifiers into one');
|
|
242
|
+
}
|
|
243
|
+
return remedies.join('; ');
|
|
244
|
+
}
|
|
245
|
+
/** The hint a hazardous user pattern earns under a zone — surfaced through `gates coverage` / `explain`. */
|
|
246
|
+
function blankRunHazardHint(zone, hazards) {
|
|
247
|
+
const h = hazards[0];
|
|
248
|
+
const reach = hazards.every((x) => !x.crossesNewline)
|
|
249
|
+
? ' It stops at each newline, so only a very long blanked line is costly.'
|
|
250
|
+
: '';
|
|
251
|
+
return (`pattern backtracking hazard under scan: '${zone}': ${h.message}. Blanked comments and strings become ` +
|
|
252
|
+
`long whitespace runs; ${hazardRemedy(hazards)} (or scan: 'all').${reach}`);
|
|
253
|
+
}
|
|
254
|
+
function namedFiles(files) {
|
|
255
|
+
return files.slice(0, 5).join(', ') + (files.length > 5 ? `, … (+${files.length - 5} more)` : '');
|
|
256
|
+
}
|
|
257
|
+
function formatCost(n) {
|
|
258
|
+
return n.toExponential(1).replace('e+', 'e');
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The error for files a zoned `regex-capture` could not judge — loud, named,
|
|
262
|
+
* never a silent partial. The two causes are worded apart: a PREDICTED skip
|
|
263
|
+
* never ran the regex (so it spent nothing), a STOPPED file ran past the cap.
|
|
264
|
+
*/
|
|
265
|
+
function zonedOverBudgetError(zone, run, hazards) {
|
|
266
|
+
const parts = [];
|
|
267
|
+
if (run.predicted.length > 0) {
|
|
268
|
+
const worst = run.predicted.reduce((a, b) => (b.cost > a.cost ? b : a));
|
|
269
|
+
parts.push(`predicted over budget, so the regex was never run (${worst.measure} = ${formatCost(worst.cost)} > ` +
|
|
270
|
+
`${formatCost(ZONED_HAZARD_COST_LIMIT)}): ${namedFiles(run.predicted.map((p) => p.path))}`);
|
|
271
|
+
}
|
|
272
|
+
if (run.ran.length > 0) {
|
|
273
|
+
parts.push(`ran past the ${ZONED_REGEX_FILE_BUDGET_MS} ms per-file cap and was stopped: ${namedFiles(run.ran)}`);
|
|
274
|
+
}
|
|
275
|
+
const total = run.predicted.length + run.ran.length;
|
|
276
|
+
const why = hazards.length > 0 ? ` — ${hazards[0].message}` : '';
|
|
277
|
+
const fix = hazards.length > 0 ? hazardRemedy(hazards) : 'simplify the pattern';
|
|
278
|
+
return (`regex-capture under scan: '${zone}' skipped ${total} file(s): over budget — ${parts.join('; ')}${why}. ` +
|
|
279
|
+
`${fix.charAt(0).toUpperCase()}${fix.slice(1)}, or use scan: 'all'.`);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Capture-group-1 of a pattern, per file. Under a zone (`zoned`), each file is
|
|
283
|
+
* bounded: a FLAGGED pattern skips a file whose predicted work — each hazard
|
|
284
|
+
* priced by its own reach, so a line-bounded one pays per line — is over the
|
|
285
|
+
* limit, and any pattern stops a file that runs past the per-file budget.
|
|
286
|
+
* Both are named, never a quietly smaller token set.
|
|
287
|
+
*/
|
|
288
|
+
function byRegex(source, files, zoned, hazards) {
|
|
125
289
|
const { re } = safeCompile(source.pattern, source.flags);
|
|
126
290
|
if (!re)
|
|
127
|
-
return [];
|
|
291
|
+
return { sites: [], predicted: [], ran: [] };
|
|
128
292
|
const sites = [];
|
|
293
|
+
const predicted = [];
|
|
294
|
+
const ran = [];
|
|
129
295
|
for (const f of files) {
|
|
296
|
+
if (zoned && hazards.length > 0) {
|
|
297
|
+
const p = predictedHazardCost(hazards, blankRunCosts(f.content));
|
|
298
|
+
if (p.cost > ZONED_HAZARD_COST_LIMIT) {
|
|
299
|
+
predicted.push({ path: f.path, ...p });
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const startedAt = Date.now();
|
|
304
|
+
const fileSites = [];
|
|
305
|
+
let over = false;
|
|
130
306
|
re.lastIndex = 0;
|
|
131
307
|
let m;
|
|
132
308
|
while ((m = re.exec(f.content)) !== null) {
|
|
@@ -134,12 +310,42 @@ function byRegex(source, files) {
|
|
|
134
310
|
if (m.index === re.lastIndex)
|
|
135
311
|
re.lastIndex += 1;
|
|
136
312
|
const token = m[1];
|
|
137
|
-
if (token
|
|
138
|
-
|
|
139
|
-
|
|
313
|
+
if (token !== undefined && token !== '') {
|
|
314
|
+
fileSites.push({ token, file: f.path, line: lineOf(f.content, m.index) });
|
|
315
|
+
}
|
|
316
|
+
if (zoned && Date.now() - startedAt > ZONED_REGEX_FILE_BUDGET_MS) {
|
|
317
|
+
over = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (over) {
|
|
322
|
+
ran.push(f.path);
|
|
323
|
+
continue;
|
|
140
324
|
}
|
|
325
|
+
sites.push(...fileSites);
|
|
141
326
|
}
|
|
142
|
-
return sites;
|
|
327
|
+
return { sites, predicted, ran };
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The head regexes the anchor-based extractors run — possibly on a blanked
|
|
331
|
+
* buffer. One factory, so the linearity lock (`r75-blank-run-linearity`)
|
|
332
|
+
* times and lints the exact patterns that run, not copies of them.
|
|
333
|
+
*/
|
|
334
|
+
export function extractorHeadPatterns(anchor) {
|
|
335
|
+
const a = escapeRegex(anchor);
|
|
336
|
+
const literalHead = (open) => new RegExp(`(?<![\\w$])${a}\\s*(?::[^=\\n]*)?[:=]\\s*(?:[A-Za-z_$][\\w$.]*\\s*\\(\\s*)?\\${open}`, 'g');
|
|
337
|
+
return {
|
|
338
|
+
arrayLiteral: literalHead('['),
|
|
339
|
+
objectLiteral: literalHead('{'),
|
|
340
|
+
enumBody: new RegExp(`\\benum\\s+${a}\\s*\\{`, 'g'),
|
|
341
|
+
call: new RegExp(`(?<![\\w$.@])${a}\\s*\\(`, 'g'),
|
|
342
|
+
decorator: new RegExp(`@${a}\\s*\\(`, 'g'),
|
|
343
|
+
// Collapsed from `\s*(?:<[^>]*>)?\s*=`: two whitespace quantifiers with
|
|
344
|
+
// only an optional group between them re-partition a blank run.
|
|
345
|
+
stringUnion: new RegExp(`\\btype\\s+${a}\\s*(?:<[^>]*>\\s*)?=`, 'g'),
|
|
346
|
+
exportDecl: EXPORT_DECL,
|
|
347
|
+
exportClause: EXPORT_CLAUSE,
|
|
348
|
+
};
|
|
143
349
|
}
|
|
144
350
|
/**
|
|
145
351
|
* Elements of every `<anchor> = <open> … ` / `<anchor>: <open> … ` literal.
|
|
@@ -151,7 +357,8 @@ function byRegex(source, files) {
|
|
|
151
357
|
*/
|
|
152
358
|
function byBracketLiteral(anchor, open, files, tokenOf) {
|
|
153
359
|
const sites = [];
|
|
154
|
-
const
|
|
360
|
+
const heads = extractorHeadPatterns(anchor);
|
|
361
|
+
const head = open === '[' ? heads.arrayLiteral : heads.objectLiteral;
|
|
155
362
|
for (const f of files) {
|
|
156
363
|
head.lastIndex = 0;
|
|
157
364
|
let m;
|
|
@@ -174,7 +381,7 @@ function byBracketLiteral(anchor, open, files, tokenOf) {
|
|
|
174
381
|
/** Members of `enum <anchor> { … }` — the member name, or its assigned value. */
|
|
175
382
|
function byEnumMembers(anchor, wantValue, files) {
|
|
176
383
|
const sites = [];
|
|
177
|
-
const head =
|
|
384
|
+
const head = extractorHeadPatterns(anchor).enumBody;
|
|
178
385
|
for (const f of files) {
|
|
179
386
|
head.lastIndex = 0;
|
|
180
387
|
let m;
|
|
@@ -225,9 +432,8 @@ function byExportNames(files) {
|
|
|
225
432
|
/** Argument `argIndex` of every `anchor(…)` call — or `@anchor(…)` decorator. */
|
|
226
433
|
function byCallArgs(anchor, argIndex, decorator, files) {
|
|
227
434
|
const sites = [];
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
: new RegExp(`(?<![\\w$.@])${escapeRegex(anchor)}\\s*\\(`, 'g');
|
|
435
|
+
const heads = extractorHeadPatterns(anchor);
|
|
436
|
+
const head = decorator ? heads.decorator : heads.call;
|
|
231
437
|
for (const f of files) {
|
|
232
438
|
head.lastIndex = 0;
|
|
233
439
|
let m;
|
|
@@ -251,7 +457,7 @@ function byCallArgs(anchor, argIndex, decorator, files) {
|
|
|
251
457
|
/** The string literals of `type <anchor> = 'a' | 'b' | 'c'`. */
|
|
252
458
|
function byStringUnion(anchor, files) {
|
|
253
459
|
const sites = [];
|
|
254
|
-
const head =
|
|
460
|
+
const head = extractorHeadPatterns(anchor).stringUnion;
|
|
255
461
|
const member = /['"`]([^'"`]+)['"`]/g;
|
|
256
462
|
for (const f of files) {
|
|
257
463
|
head.lastIndex = 0;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
/**
|
|
3
|
+
* One extraction source of a gate rule, named by the side it plays —
|
|
4
|
+
* `declared`, `registered[1]`, `chain[0]`, `source`, `consumer`, a role, or
|
|
5
|
+
* `compute.source`. The label qualifies the source's marker lists
|
|
6
|
+
* (`declared.files`) and prefixes its units in a multi-source report.
|
|
7
|
+
*/
|
|
8
|
+
export interface ILabeledSource {
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly source: IWiringSource;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=i-labeled-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-labeled-source.d.ts","sourceRoot":"","sources":["../../src/extract/i-labeled-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-edges.d.ts","sourceRoot":"","sources":["../../src/extract/import-edges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAG7F,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qCAAqC;AACrC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"import-edges.d.ts","sourceRoot":"","sources":["../../src/extract/import-edges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAG7F,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qCAAqC;AACrC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAmFxD,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,OAAO,GAAE,kBAAuB,GAC/B;IAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CA4FnF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { globListSelects } from "../scan/glob.js";
|
|
2
2
|
import { resolveAliasCandidates } from "../scan/tsconfig-aliases.js";
|
|
3
3
|
import { safeCompile } from "../util/safe-regex.js";
|
|
4
|
-
import {
|
|
4
|
+
import { parseImportStatementsWithMeta } from "./parse-imports.js";
|
|
5
5
|
/** Normalize a relative specifier against the importing file's directory. */
|
|
6
6
|
function resolveRelative(fromFile, specifier) {
|
|
7
7
|
const dir = fromFile.includes('/') ? fromFile.slice(0, fromFile.lastIndexOf('/')) : '';
|
|
@@ -55,14 +55,15 @@ function matchesTarget(resolved, to, modulePattern) {
|
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
if (to.files && to.files.length > 0 && resolved.path !== undefined) {
|
|
58
|
-
|
|
58
|
+
// `to.files` is a glob LIST like any other: a `!` entry subtracts.
|
|
59
|
+
if (globListSelects(resolved.path, to.files))
|
|
59
60
|
return true;
|
|
60
61
|
// Specifiers routinely omit the extension, so probe the usual endings
|
|
61
62
|
// rather than forcing every config to spell `*.{ts,tsx,…}` itself.
|
|
62
63
|
for (const ext of ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx']) {
|
|
63
|
-
if (
|
|
64
|
+
if (globListSelects(resolved.path + ext, to.files))
|
|
64
65
|
return true;
|
|
65
|
-
if (
|
|
66
|
+
if (globListSelects(`${resolved.path}/index${ext}`, to.files))
|
|
66
67
|
return true;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
@@ -92,9 +93,20 @@ export function extractImportEdges(source, files, context = {}) {
|
|
|
92
93
|
return { sites: [], error: `to.match ${compiled.error}` };
|
|
93
94
|
symbolPattern = compiled.re;
|
|
94
95
|
}
|
|
96
|
+
// Zoning is judged by where the statement's KEYWORD starts and whether its
|
|
97
|
+
// specifier is a real string — the same authority `check boundaries` uses
|
|
98
|
+
// (round 11, 6.1a#import-edges-scan). The extractor receives RAW content:
|
|
99
|
+
// pre-blanking with `scan: 'code'` used to erase every specifier (a string)
|
|
100
|
+
// and turn the rule vacuous. Unset → comment-aware, matching `check
|
|
101
|
+
// boundaries`; `scan: 'all'` is the raw escape hatch. (`strings` / `comments`
|
|
102
|
+
// are rejected at config load: an import statement is code.)
|
|
103
|
+
const zone = source.scan === 'all' ? 'all' : 'code';
|
|
95
104
|
const sites = [];
|
|
105
|
+
let blankedChars = 0;
|
|
96
106
|
for (const file of files) {
|
|
97
|
-
|
|
107
|
+
const parsed = parseImportStatementsWithMeta(file.content, { zone });
|
|
108
|
+
blankedChars += parsed.blankedChars;
|
|
109
|
+
for (const statement of parsed.statements) {
|
|
98
110
|
const resolved = resolveSpecifier(file.path, statement.specifier, context.tsconfigPaths);
|
|
99
111
|
if (!matchesTarget(resolved, to, modulePattern))
|
|
100
112
|
continue;
|
|
@@ -151,7 +163,8 @@ export function extractImportEdges(source, files, context = {}) {
|
|
|
151
163
|
hint: '0 edges via `to.files` — that matches an import\'s DIRECTLY-resolved path, so a symbol ' +
|
|
152
164
|
're-exported through a barrel/package resolves to the package entry, not the deep file. ' +
|
|
153
165
|
'Target by `to.module` + `to.match` instead.',
|
|
166
|
+
blankedChars,
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
|
-
return { sites };
|
|
169
|
+
return { sites, blankedChars };
|
|
157
170
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import type { IGlobListUnits } from '../util/i-glob-list-units.js';
|
|
3
|
+
import type { IUnreadFile } from '../util/unread-file.js';
|
|
2
4
|
import { type IExtractedSite } from './extract-tokens.js';
|
|
3
5
|
/** What one source actually resolved to against the live tree. */
|
|
4
6
|
export interface ISourceInspection {
|
|
5
|
-
/** Files the globs matched (after the shared walk's skip rules). */
|
|
7
|
+
/** Files the globs matched and the reader READ (after the shared walk's skip rules). */
|
|
6
8
|
readonly filesScanned: number;
|
|
9
|
+
/**
|
|
10
|
+
* Files the globs matched that the reader did NOT read (over the read cap,
|
|
11
|
+
* or unreadable). A source with any is not "matched nothing" and not fully
|
|
12
|
+
* examined: its coverage names them (`readScopeCoverage`).
|
|
13
|
+
*/
|
|
14
|
+
readonly unread: readonly IUnreadFile[];
|
|
7
15
|
/** Distinct ids extracted, sorted. */
|
|
8
16
|
readonly ids: readonly string[];
|
|
9
17
|
/** Every capture site, in stable (file, line) order. */
|
|
@@ -26,5 +34,26 @@ export interface ISourceInspection {
|
|
|
26
34
|
* extract?" has one answer computed one way. A rule that matched 0 is then a
|
|
27
35
|
* fact the tooling can report, not something an author has to notice.
|
|
28
36
|
*/
|
|
29
|
-
export declare function inspectSource(projectRoot: string,
|
|
37
|
+
export declare function inspectSource(projectRoot: string, rawSource: IWiringSource, excludeDirs?: readonly string[]): ISourceInspection;
|
|
38
|
+
/**
|
|
39
|
+
* ONE source's glob units: the dead ones (each with its reason), the live
|
|
40
|
+
* negations with what each excludes, and how many globs were checked — through
|
|
41
|
+
* the one dead-unit decision, `globListUnits`.
|
|
42
|
+
*
|
|
43
|
+
* It reads the same walk {@link inspectSource} reads (inside
|
|
44
|
+
* `withFileReadCache` a second call over the same globs is a memo hit), so the
|
|
45
|
+
* answer is about the files the source actually SEES — after `SKIP_DIRS` and
|
|
46
|
+
* `excludeDirs`. The walk is the list's POSITIVE set (every file an inclusion
|
|
47
|
+
* glob matched), so a negation is judged by what it removes from it, never by
|
|
48
|
+
* what it "matches". A file over the read cap was still MATCHED, so a glob that
|
|
49
|
+
* matched only such a file is not dead. Only `files[]` walk globs count: an
|
|
50
|
+
* `import-edges` `to.files` matches resolved specifiers, not walked files.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sourceGlobUnits(projectRoot: string, source: IWiringSource, excludeDirs?: readonly string[]): IGlobListUnits;
|
|
53
|
+
/**
|
|
54
|
+
* The globs of ONE source that do nothing — a dead unit inside a rule whose
|
|
55
|
+
* other globs may still keep it connected. The labels of
|
|
56
|
+
* {@link sourceGlobUnits}`.dead` (a dead negation keeps its `!`).
|
|
57
|
+
*/
|
|
58
|
+
export declare function sourceDeadGlobs(projectRoot: string, source: IWiringSource, excludeDirs?: readonly string[]): readonly string[];
|
|
30
59
|
//# sourceMappingURL=inspect-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-source.d.ts","sourceRoot":"","sources":["../../src/extract/inspect-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"inspect-source.d.ts","sourceRoot":"","sources":["../../src/extract/inspect-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGzE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,sCAAsC;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,EACxB,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,iBAAiB,CAyBnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,cAAc,CAEhB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,SAAS,MAAM,EAAE,CAEnB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { normalizeWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import { readSelectedFiles } from "../util/read-selected-files.js";
|
|
3
|
+
import { readGlobListUnits } from "../util/dead-glob-units.js";
|
|
3
4
|
import { extractTokens } from "./extract-tokens.js";
|
|
4
5
|
import { loadTsconfigPaths } from "../scan/tsconfig-aliases.js";
|
|
5
6
|
/**
|
|
@@ -11,12 +12,17 @@ import { loadTsconfigPaths } from "../scan/tsconfig-aliases.js";
|
|
|
11
12
|
* extract?" has one answer computed one way. A rule that matched 0 is then a
|
|
12
13
|
* fact the tooling can report, not something an author has to notice.
|
|
13
14
|
*/
|
|
14
|
-
export function inspectSource(projectRoot,
|
|
15
|
+
export function inspectSource(projectRoot, rawSource, excludeDirs = []) {
|
|
16
|
+
// The engine entry normalises idempotently (round 13): a loaded source comes
|
|
17
|
+
// back equal; a hand-built one with a `{ pattern, expectEmpty }` entry reads
|
|
18
|
+
// its pattern as the glob, and a malformed entry is a misconfiguration —
|
|
19
|
+
// never a glob reader handed an object.
|
|
20
|
+
const normalized = normalizeWiringSource(rawSource);
|
|
21
|
+
if (!normalized.ok)
|
|
22
|
+
return { filesScanned: 0, unread: [], ids: [], sites: [], error: normalized.error.message };
|
|
23
|
+
const source = normalized.value;
|
|
15
24
|
const globs = source.files ?? [];
|
|
16
|
-
const
|
|
17
|
-
const files = [...cache.entries()]
|
|
18
|
-
.filter(([path]) => matchesAny(path, globs))
|
|
19
|
-
.map(([path, content]) => ({ path, content }));
|
|
25
|
+
const { files, unread } = sourceFiles(projectRoot, globs, excludeDirs);
|
|
20
26
|
// `import-edges` resolves alias specifiers the way the compiler would, so it
|
|
21
27
|
// needs the project's tsconfig paths. Loading it here (rather than inside the
|
|
22
28
|
// extractor) keeps every extractor a pure function of its inputs.
|
|
@@ -24,9 +30,46 @@ export function inspectSource(projectRoot, source, excludeDirs = []) {
|
|
|
24
30
|
const sites = [...res.sites].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.token.localeCompare(b.token));
|
|
25
31
|
return {
|
|
26
32
|
filesScanned: files.length,
|
|
33
|
+
unread,
|
|
27
34
|
ids: [...new Set(sites.map((s) => s.token))].sort(),
|
|
28
35
|
sites,
|
|
29
36
|
...(res.error ? { error: res.error } : {}),
|
|
30
37
|
...(res.hint ? { hint: res.hint } : {}),
|
|
31
38
|
};
|
|
32
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* ONE source's glob units: the dead ones (each with its reason), the live
|
|
42
|
+
* negations with what each excludes, and how many globs were checked — through
|
|
43
|
+
* the one dead-unit decision, `globListUnits`.
|
|
44
|
+
*
|
|
45
|
+
* It reads the same walk {@link inspectSource} reads (inside
|
|
46
|
+
* `withFileReadCache` a second call over the same globs is a memo hit), so the
|
|
47
|
+
* answer is about the files the source actually SEES — after `SKIP_DIRS` and
|
|
48
|
+
* `excludeDirs`. The walk is the list's POSITIVE set (every file an inclusion
|
|
49
|
+
* glob matched), so a negation is judged by what it removes from it, never by
|
|
50
|
+
* what it "matches". A file over the read cap was still MATCHED, so a glob that
|
|
51
|
+
* matched only such a file is not dead. Only `files[]` walk globs count: an
|
|
52
|
+
* `import-edges` `to.files` matches resolved specifiers, not walked files.
|
|
53
|
+
*/
|
|
54
|
+
export function sourceGlobUnits(projectRoot, source, excludeDirs = []) {
|
|
55
|
+
return readGlobListUnits(projectRoot, source.files ?? [], new Set(excludeDirs));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The globs of ONE source that do nothing — a dead unit inside a rule whose
|
|
59
|
+
* other globs may still keep it connected. The labels of
|
|
60
|
+
* {@link sourceGlobUnits}`.dead` (a dead negation keeps its `!`).
|
|
61
|
+
*/
|
|
62
|
+
export function sourceDeadGlobs(projectRoot, source, excludeDirs = []) {
|
|
63
|
+
return sourceGlobUnits(projectRoot, source, excludeDirs).dead.map((u) => u.glob);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The files a source's glob list SELECTS (an inclusion glob matches, no
|
|
67
|
+
* negation does), after the shared walk's skip rules: read, and unread.
|
|
68
|
+
*/
|
|
69
|
+
function sourceFiles(projectRoot, globs, excludeDirs) {
|
|
70
|
+
const selected = readSelectedFiles(projectRoot, globs, new Set(excludeDirs));
|
|
71
|
+
return {
|
|
72
|
+
files: [...selected.files.entries()].map(([path, content]) => ({ path, content })),
|
|
73
|
+
unread: [...selected.unread],
|
|
74
|
+
};
|
|
75
|
+
}
|