@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.
Files changed (226) hide show
  1. package/dist/baseline/compute-baseline.d.ts +8 -0
  2. package/dist/baseline/compute-baseline.d.ts.map +1 -1
  3. package/dist/baseline/compute-baseline.js +8 -7
  4. package/dist/baseline/diff-baseline.d.ts +28 -0
  5. package/dist/baseline/diff-baseline.d.ts.map +1 -1
  6. package/dist/baseline/diff-baseline.js +29 -0
  7. package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
  8. package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
  9. package/dist/evaluate/boundary-unit-finding.js +21 -0
  10. package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
  11. package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
  12. package/dist/evaluate/boundary-unit-kind.js +14 -0
  13. package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
  14. package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
  15. package/dist/evaluate/evaluate-boundaries.js +481 -53
  16. package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
  17. package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
  18. package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
  19. package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
  20. package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
  21. package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
  22. package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
  23. package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
  24. package/dist/evaluate/i-boundary-unit-finding.js +1 -0
  25. package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
  26. package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
  27. package/dist/evaluate/settle-boundary-rule.js +73 -0
  28. package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
  29. package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
  30. package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
  31. package/dist/extract/code-zones.d.ts +95 -3
  32. package/dist/extract/code-zones.d.ts.map +1 -1
  33. package/dist/extract/code-zones.js +313 -4
  34. package/dist/extract/extract-tokens.d.ts +30 -0
  35. package/dist/extract/extract-tokens.d.ts.map +1 -1
  36. package/dist/extract/extract-tokens.js +233 -27
  37. package/dist/extract/i-labeled-source.d.ts +12 -0
  38. package/dist/extract/i-labeled-source.d.ts.map +1 -0
  39. package/dist/extract/i-labeled-source.js +1 -0
  40. package/dist/extract/import-edges.d.ts +1 -0
  41. package/dist/extract/import-edges.d.ts.map +1 -1
  42. package/dist/extract/import-edges.js +20 -7
  43. package/dist/extract/inspect-source.d.ts +32 -3
  44. package/dist/extract/inspect-source.d.ts.map +1 -1
  45. package/dist/extract/inspect-source.js +50 -7
  46. package/dist/extract/parse-imports.d.ts +34 -12
  47. package/dist/extract/parse-imports.d.ts.map +1 -1
  48. package/dist/extract/parse-imports.js +119 -20
  49. package/dist/extract/scan-literals.d.ts +12 -1
  50. package/dist/extract/scan-literals.d.ts.map +1 -1
  51. package/dist/extract/scan-literals.js +15 -1
  52. package/dist/extract/source-liveness-request.d.ts +24 -0
  53. package/dist/extract/source-liveness-request.d.ts.map +1 -0
  54. package/dist/extract/source-liveness-request.js +45 -0
  55. package/dist/generated/check-provenance.d.ts.map +1 -1
  56. package/dist/generated/check-provenance.js +3 -1
  57. package/dist/generated/read-regen-tree.d.ts +16 -0
  58. package/dist/generated/read-regen-tree.d.ts.map +1 -0
  59. package/dist/generated/read-regen-tree.js +61 -0
  60. package/dist/generated/scan-generated.d.ts +8 -0
  61. package/dist/generated/scan-generated.d.ts.map +1 -1
  62. package/dist/generated/scan-generated.js +25 -10
  63. package/dist/index.d.ts +48 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +51 -0
  66. package/dist/model/boundary-intended-empty-line.d.ts +10 -0
  67. package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
  68. package/dist/model/boundary-intended-empty-line.js +11 -0
  69. package/dist/model/boundary-markable-list.d.ts +15 -0
  70. package/dist/model/boundary-markable-list.d.ts.map +1 -0
  71. package/dist/model/boundary-markable-list.js +15 -0
  72. package/dist/model/boundary-rule-input-keys.d.ts +11 -0
  73. package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
  74. package/dist/model/boundary-rule-input-keys.js +29 -0
  75. package/dist/model/boundary-rule-input.d.ts +23 -0
  76. package/dist/model/boundary-rule-input.d.ts.map +1 -0
  77. package/dist/model/boundary-rule-input.js +1 -0
  78. package/dist/model/boundary-rule-key-problems.d.ts +11 -0
  79. package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
  80. package/dist/model/boundary-rule-key-problems.js +36 -0
  81. package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
  82. package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
  83. package/dist/model/boundary-rule-marker-problems.js +94 -0
  84. package/dist/model/boundary-rule-scope.d.ts +88 -0
  85. package/dist/model/boundary-rule-scope.d.ts.map +1 -0
  86. package/dist/model/boundary-rule-scope.js +175 -0
  87. package/dist/model/boundary-rule.d.ts +120 -5
  88. package/dist/model/boundary-rule.d.ts.map +1 -1
  89. package/dist/model/boundary-rule.js +148 -1
  90. package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
  91. package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
  92. package/dist/model/boundary-unit-problem-issue.js +16 -0
  93. package/dist/model/normalize-boundary-rule.d.ts +21 -0
  94. package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
  95. package/dist/model/normalize-boundary-rule.js +62 -0
  96. package/dist/policy/evaluate-policy.d.ts +80 -3
  97. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  98. package/dist/policy/evaluate-policy.js +152 -19
  99. package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
  100. package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
  101. package/dist/policy/i-policy-rule-liveness.js +1 -0
  102. package/dist/policy/run-policy.d.ts +1 -1
  103. package/dist/policy/run-policy.d.ts.map +1 -1
  104. package/dist/policy/run-policy.js +171 -21
  105. package/dist/registry/load-boundary-rules.d.ts +29 -1
  106. package/dist/registry/load-boundary-rules.d.ts.map +1 -1
  107. package/dist/registry/load-boundary-rules.js +38 -8
  108. package/dist/scan/glob.d.ts +90 -0
  109. package/dist/scan/glob.d.ts.map +1 -1
  110. package/dist/scan/glob.js +205 -0
  111. package/dist/scan/i-glob-unit-measure.d.ts +22 -0
  112. package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
  113. package/dist/scan/i-glob-unit-measure.js +1 -0
  114. package/dist/scan/import-pattern.d.ts +109 -0
  115. package/dist/scan/import-pattern.d.ts.map +1 -0
  116. package/dist/scan/import-pattern.js +191 -0
  117. package/dist/scan/node-builtin-package-names.d.ts +13 -0
  118. package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
  119. package/dist/scan/node-builtin-package-names.js +25 -0
  120. package/dist/scan/scan-imports.d.ts +36 -3
  121. package/dist/scan/scan-imports.d.ts.map +1 -1
  122. package/dist/scan/scan-imports.js +114 -45
  123. package/dist/util/blank-run-hazard-finding.d.ts +34 -0
  124. package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
  125. package/dist/util/blank-run-hazard-finding.js +1 -0
  126. package/dist/util/blank-run-hazard.d.ts +29 -0
  127. package/dist/util/blank-run-hazard.d.ts.map +1 -0
  128. package/dist/util/blank-run-hazard.js +450 -0
  129. package/dist/util/dead-glob-units.d.ts +46 -0
  130. package/dist/util/dead-glob-units.d.ts.map +1 -0
  131. package/dist/util/dead-glob-units.js +116 -0
  132. package/dist/util/glob-list-liveness-input.d.ts +31 -0
  133. package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
  134. package/dist/util/glob-list-liveness-input.js +65 -0
  135. package/dist/util/i-dead-glob-unit.d.ts +24 -0
  136. package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
  137. package/dist/util/i-dead-glob-unit.js +1 -0
  138. package/dist/util/i-glob-list-units.d.ts +25 -0
  139. package/dist/util/i-glob-list-units.d.ts.map +1 -0
  140. package/dist/util/i-glob-list-units.js +1 -0
  141. package/dist/util/i-glob-liveness-list.d.ts +23 -0
  142. package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
  143. package/dist/util/i-glob-liveness-list.js +1 -0
  144. package/dist/util/i-glob-liveness-request.d.ts +11 -0
  145. package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
  146. package/dist/util/i-glob-liveness-request.js +1 -0
  147. package/dist/util/i-glob-negation.d.ts +13 -0
  148. package/dist/util/i-glob-negation.d.ts.map +1 -0
  149. package/dist/util/i-glob-negation.js +1 -0
  150. package/dist/util/matched-files.d.ts +22 -0
  151. package/dist/util/matched-files.d.ts.map +1 -0
  152. package/dist/util/matched-files.js +1 -0
  153. package/dist/util/negation-cause.d.ts +15 -0
  154. package/dist/util/negation-cause.d.ts.map +1 -0
  155. package/dist/util/negation-cause.js +21 -0
  156. package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
  157. package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
  158. package/dist/util/plane-scan-exclude-dirs.js +22 -0
  159. package/dist/util/read-glob-list-liveness.d.ts +20 -0
  160. package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
  161. package/dist/util/read-glob-list-liveness.js +20 -0
  162. package/dist/util/read-scope-coverage.d.ts +90 -0
  163. package/dist/util/read-scope-coverage.d.ts.map +1 -0
  164. package/dist/util/read-scope-coverage.js +173 -0
  165. package/dist/util/read-scope.d.ts +14 -0
  166. package/dist/util/read-scope.d.ts.map +1 -0
  167. package/dist/util/read-scope.js +1 -0
  168. package/dist/util/read-selected-files.d.ts +16 -0
  169. package/dist/util/read-selected-files.d.ts.map +1 -0
  170. package/dist/util/read-selected-files.js +25 -0
  171. package/dist/util/settle-glob-lists.d.ts +12 -0
  172. package/dist/util/settle-glob-lists.d.ts.map +1 -0
  173. package/dist/util/settle-glob-lists.js +13 -0
  174. package/dist/util/unread-file-reason.d.ts +26 -0
  175. package/dist/util/unread-file-reason.d.ts.map +1 -0
  176. package/dist/util/unread-file-reason.js +26 -0
  177. package/dist/util/unread-file.d.ts +10 -0
  178. package/dist/util/unread-file.d.ts.map +1 -0
  179. package/dist/util/unread-file.js +1 -0
  180. package/dist/util/walk-files.d.ts +74 -10
  181. package/dist/util/walk-files.d.ts.map +1 -1
  182. package/dist/util/walk-files.js +137 -31
  183. package/dist/wiring/evaluate-wiring.d.ts +79 -5
  184. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  185. package/dist/wiring/evaluate-wiring.js +267 -12
  186. package/dist/wiring/explain-wiring.d.ts +49 -5
  187. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  188. package/dist/wiring/explain-wiring.js +118 -10
  189. package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
  190. package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
  191. package/dist/wiring/i-idiom-role-coverage.js +1 -0
  192. package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
  193. package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
  194. package/dist/wiring/i-registration-query-verdict.js +1 -0
  195. package/dist/wiring/i-registration-roles.d.ts +71 -0
  196. package/dist/wiring/i-registration-roles.d.ts.map +1 -0
  197. package/dist/wiring/i-registration-roles.js +1 -0
  198. package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
  199. package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
  200. package/dist/wiring/measure-idiom-role-coverage.js +20 -0
  201. package/dist/wiring/measure-registration-roles.d.ts +25 -0
  202. package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
  203. package/dist/wiring/measure-registration-roles.js +124 -0
  204. package/dist/wiring/plan-wiring-fix.js +2 -2
  205. package/dist/wiring/registration-graph.d.ts +12 -0
  206. package/dist/wiring/registration-graph.d.ts.map +1 -1
  207. package/dist/wiring/registration-graph.js +16 -5
  208. package/dist/wiring/registration-query-verdict.d.ts +23 -0
  209. package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
  210. package/dist/wiring/registration-query-verdict.js +118 -0
  211. package/dist/wiring/registry-query.d.ts +8 -0
  212. package/dist/wiring/registry-query.d.ts.map +1 -1
  213. package/dist/wiring/registry-query.js +12 -5
  214. package/dist/wiring/scan-wiring-files.d.ts +4 -2
  215. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  216. package/dist/wiring/scan-wiring-files.js +52 -15
  217. package/dist/wiring/sink-imports.d.ts +11 -0
  218. package/dist/wiring/sink-imports.d.ts.map +1 -1
  219. package/dist/wiring/sink-imports.js +11 -2
  220. package/dist/wiring/trace-literal.d.ts +6 -0
  221. package/dist/wiring/trace-literal.d.ts.map +1 -1
  222. package/dist/wiring/trace-literal.js +5 -2
  223. package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
  224. package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
  225. package/dist/wiring/wiring-labeled-sources.js +20 -0
  226. 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;AAGxB,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,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,CAwEhB"}
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
- sites = byRegex(source, files);
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, '[', files, (el) => elementToken(el));
63
+ sites = byBracketLiteral(anchor, '[', scanned, (el) => elementToken(el));
27
64
  break;
28
65
  case 'object-keys':
29
- sites = byBracketLiteral(anchor, '{', files, (el) => source.capture === 'value' ? elementValue(el) : elementToken(el));
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', files);
69
+ sites = byEnumMembers(anchor, source.capture === 'value', scanned);
33
70
  break;
34
71
  case 'export-names':
35
- sites = byExportNames(files);
72
+ sites = byExportNames(scanned);
36
73
  break;
37
74
  case 'call-args':
38
- sites = byCallArgs(anchor, source.argIndex ?? 0, false, files);
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, files);
78
+ sites = byCallArgs(anchor, source.argIndex ?? 0, true, scanned);
42
79
  break;
43
80
  case 'string-union-members':
44
- sites = byStringUnion(anchor, files);
81
+ sites = byStringUnion(anchor, scanned);
45
82
  break;
46
83
  case 'json-path':
47
- sites = byJsonPath(source.jsonPath, files);
84
+ sites = byJsonPath(source.jsonPath, scanned);
48
85
  break;
49
86
  case 'filenames':
50
- sites = byFilenames(source, files);
87
+ sites = byFilenames(source, scanned);
51
88
  break;
52
89
  case 'import-edges': {
53
- const edges = extractImportEdges(source, files, {
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
- /** Capture-group-1 of a pattern, per file. */
124
- function byRegex(source, files) {
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 === undefined || token === '')
138
- continue;
139
- sites.push({ token, file: f.path, line: lineOf(f.content, m.index) });
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 head = new RegExp(`(?<![\\w$])${escapeRegex(anchor)}\\s*(?::[^=\\n]*)?[:=]\\s*(?:[A-Za-z_$][\\w$.]*\\s*\\(\\s*)?\\${open}`, 'g');
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 = new RegExp(`\\benum\\s+${escapeRegex(anchor)}\\s*\\{`, 'g');
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 head = decorator
229
- ? new RegExp(`@${escapeRegex(anchor)}\\s*\\(`, 'g')
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 = new RegExp(`\\btype\\s+${escapeRegex(anchor)}\\s*(?:<[^>]*>)?\\s*=`, 'g');
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 {};
@@ -45,5 +45,6 @@ export declare function extractImportEdges(source: IWiringSource, files: readonl
45
45
  sites: IExtractedSite[];
46
46
  error?: string;
47
47
  hint?: string;
48
+ blankedChars?: number;
48
49
  };
49
50
  //# sourceMappingURL=import-edges.d.ts.map
@@ -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;AAkFxD,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,CAAA;CAAE,CAgF5D"}
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 { matchesAny } from "../scan/glob.js";
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 { parseImportStatements } from "./parse-imports.js";
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
- if (matchesAny(resolved.path, to.files))
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 (matchesAny(resolved.path + ext, to.files))
64
+ if (globListSelects(resolved.path + ext, to.files))
64
65
  return true;
65
- if (matchesAny(`${resolved.path}/index${ext}`, to.files))
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
- for (const statement of parseImportStatements(file.content)) {
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 { IWiringSource } from '@shrkcrft/core';
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, source: IWiringSource, excludeDirs?: readonly string[]): ISourceInspection;
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,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGzE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,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,MAAM,EAAE,aAAa,EACrB,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,iBAAiB,CAoBnB"}
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 { matchesAny } from "../scan/glob.js";
2
- import { readMatchingFiles } from "../util/walk-files.js";
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, source, excludeDirs = []) {
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 cache = readMatchingFiles(projectRoot, globs, new Set(excludeDirs));
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
+ }