@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11

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 (276) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +181 -341
  5. package/dist/src/augmentations.js +106 -13
  6. package/dist/src/automatic-types.js +10 -29
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +2 -3
  10. package/dist/src/config.js +6 -82
  11. package/dist/src/configuration-files.js +28 -176
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +1 -1
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +73 -3
  20. package/dist/src/conventions.js +36 -66
  21. package/dist/src/dependencies.js +103 -40
  22. package/dist/src/edges.js +38 -122
  23. package/dist/src/element-access.js +4 -6
  24. package/dist/src/entry-point-gaps.js +2 -8
  25. package/dist/src/entry-points.js +11 -27
  26. package/dist/src/enum-group.js +3 -10
  27. package/dist/src/erased-stores.js +4 -6
  28. package/dist/src/exempt-classes.js +1 -63
  29. package/dist/src/exempt.js +21 -40
  30. package/dist/src/failure.js +36 -0
  31. package/dist/src/file-facts.js +2 -25
  32. package/dist/src/findings/completion.js +139 -72
  33. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  34. package/dist/src/findings/emitters.js +5 -5
  35. package/dist/src/findings/interfaces.js +4 -5
  36. package/dist/src/findings/intra-function.js +13 -10
  37. package/dist/src/findings/non-code-artifacts.js +7 -8
  38. package/dist/src/findings/reads-and-writes.js +16 -13
  39. package/dist/src/findings/self-check.js +10 -12
  40. package/dist/src/findings/unused-declarations.js +41 -32
  41. package/dist/src/findings/visibility-narrowing.js +19 -16
  42. package/dist/src/findings-pass.js +8 -15
  43. package/dist/src/framework-lifecycle.js +7 -7
  44. package/dist/src/fs/document.js +52 -0
  45. package/dist/src/fs/manifest.js +72 -0
  46. package/dist/src/fs/tree.js +88 -0
  47. package/dist/src/generated-file.js +7 -4
  48. package/dist/src/git-ignore.js +39 -26
  49. package/dist/src/graph.js +5 -3
  50. package/dist/src/html-entries.js +6 -33
  51. package/dist/src/in-operands.js +2 -1
  52. package/dist/src/index.js +1 -1
  53. package/dist/src/injection-container.js +1 -1
  54. package/dist/src/intra-function-parts.js +63 -43
  55. package/dist/src/inventory.js +2 -21
  56. package/dist/src/javascript-file.js +33 -0
  57. package/dist/src/json-document.js +198 -67
  58. package/dist/src/keyof-comparisons.js +4 -6
  59. package/dist/src/kinds.js +3 -366
  60. package/dist/src/load/classify.js +270 -0
  61. package/dist/src/load/diagnostics.js +80 -0
  62. package/dist/src/load/discover.js +277 -0
  63. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  64. package/dist/src/load/expected-module.js +136 -0
  65. package/dist/src/load/load.js +27 -0
  66. package/dist/src/load/module-lookup.js +37 -0
  67. package/dist/src/load/plan.js +313 -0
  68. package/dist/src/load/specifiers.js +115 -0
  69. package/dist/src/load/synthesize.js +140 -0
  70. package/dist/src/load/test-globals.js +207 -0
  71. package/dist/src/load/test-joins.js +82 -0
  72. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  73. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  74. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
  75. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  76. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  77. package/dist/src/manifest.js +49 -54
  78. package/dist/src/matrix.js +76 -4
  79. package/dist/src/mdx-entries.js +3 -26
  80. package/dist/src/outside-tests.js +1 -1
  81. package/dist/src/partial.js +2 -1
  82. package/dist/src/paths.js +26 -15
  83. package/dist/src/position.js +2 -1
  84. package/dist/src/print.js +11 -79
  85. package/dist/src/product.js +13 -0
  86. package/dist/src/query.js +1 -0
  87. package/dist/src/ref.js +1 -0
  88. package/dist/src/references.js +48 -30
  89. package/dist/src/report-schema.js +51 -0
  90. package/dist/src/report.js +57 -41
  91. package/dist/src/reporters/annotations.js +15 -8
  92. package/dist/src/reporters/sarif.js +16 -10
  93. package/dist/src/reporters/template.js +4 -19
  94. package/dist/src/resolve.js +245 -729
  95. package/dist/src/roots.js +273 -87
  96. package/dist/src/run.js +66 -77
  97. package/dist/src/schema.js +320 -343
  98. package/dist/src/scope.js +38 -115
  99. package/dist/src/serialization-contract.js +595 -21
  100. package/dist/src/session.js +34 -29
  101. package/dist/src/source-files.js +1 -1
  102. package/dist/src/suppress-file.js +25 -28
  103. package/dist/src/suppress-inline.js +3 -2
  104. package/dist/src/suppress.js +33 -51
  105. package/dist/src/sweep.js +7 -9
  106. package/dist/src/symbol-keys.js +3 -5
  107. package/dist/src/template-field.js +9 -19
  108. package/dist/src/test-support.js +8 -11
  109. package/dist/src/text/compare.js +26 -0
  110. package/dist/src/text/glob.js +194 -0
  111. package/dist/src/text/named-files.js +54 -0
  112. package/dist/src/text/shell.js +159 -0
  113. package/dist/src/text/yaml.js +393 -0
  114. package/dist/src/type-errors.js +11 -232
  115. package/dist/src/unseen-loads.js +100 -0
  116. package/dist/src/unwrap.js +23 -0
  117. package/dist/src/usage.js +10 -0
  118. package/dist/src/verbs/analyze.js +166 -126
  119. package/dist/src/verbs/describe.js +9 -16
  120. package/dist/src/verbs/explain.js +10 -7
  121. package/dist/src/verbs/print-config.js +2 -1
  122. package/dist/src/verbs/print-projects.js +6 -4
  123. package/dist/src/verbs/print-retained.js +4 -2
  124. package/dist/src/verbs/print-roots.js +4 -2
  125. package/dist/src/verbs/verb.js +1 -5
  126. package/dist/src/verbs/version.js +14 -3
  127. package/dist/src/workflow-steps.js +62 -64
  128. package/jsr.json +1 -1
  129. package/package.json +5 -4
  130. package/src/analysis.ts +245 -502
  131. package/src/augmentations.ts +135 -17
  132. package/src/automatic-types.ts +11 -29
  133. package/src/build-scripts.ts +543 -0
  134. package/src/component-layer.ts +4 -2
  135. package/src/components.ts +2 -4
  136. package/src/config.ts +31 -302
  137. package/src/configuration-files.ts +34 -179
  138. package/src/configured-declarations.ts +18 -23
  139. package/src/conformance-record.ts +1 -1
  140. package/src/consumers.ts +6 -57
  141. package/src/contextual-uses.ts +64 -12
  142. package/src/contract/config.generated.ts +818 -0
  143. package/src/contract/contract.generated.ts +1308 -0
  144. package/src/contract/report.generated.ts +1237 -0
  145. package/src/convention-rows.ts +85 -3
  146. package/src/conventions.ts +46 -84
  147. package/src/dependencies.ts +118 -46
  148. package/src/edges.ts +41 -140
  149. package/src/element-access.ts +4 -7
  150. package/src/entry-point-gaps.ts +3 -9
  151. package/src/entry-points.ts +12 -38
  152. package/src/enum-group.ts +2 -11
  153. package/src/erased-stores.ts +3 -7
  154. package/src/exempt-classes.ts +1 -75
  155. package/src/exempt.ts +36 -50
  156. package/src/failure.ts +63 -0
  157. package/src/file-facts.ts +9 -24
  158. package/src/finding.ts +14 -9
  159. package/src/findings/boundary.ts +2 -0
  160. package/src/findings/completion.ts +181 -84
  161. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  162. package/src/findings/emitter.ts +8 -9
  163. package/src/findings/emitters.ts +7 -7
  164. package/src/findings/interfaces.ts +4 -6
  165. package/src/findings/intra-function.ts +13 -11
  166. package/src/findings/non-code-artifacts.ts +9 -11
  167. package/src/findings/reads-and-writes.ts +20 -16
  168. package/src/findings/self-check.ts +11 -17
  169. package/src/findings/unused-declarations.ts +45 -32
  170. package/src/findings/visibility-narrowing.ts +20 -17
  171. package/src/findings-pass.ts +10 -19
  172. package/src/framework-lifecycle.ts +7 -7
  173. package/src/fs/document.ts +70 -0
  174. package/src/fs/manifest.ts +92 -0
  175. package/src/fs/tree.ts +110 -0
  176. package/src/generated-file.ts +7 -4
  177. package/src/git-ignore.ts +50 -26
  178. package/src/graph.ts +5 -3
  179. package/src/host.ts +12 -5
  180. package/src/html-entries.ts +6 -33
  181. package/src/in-operands.ts +2 -1
  182. package/src/index.ts +1 -1
  183. package/src/injection-container.ts +1 -1
  184. package/src/intra-function-parts.ts +73 -44
  185. package/src/inventory.ts +2 -20
  186. package/src/javascript-file.ts +37 -0
  187. package/src/json-document.ts +241 -76
  188. package/src/keyof-comparisons.ts +3 -7
  189. package/src/kinds.ts +5 -413
  190. package/src/load/classify.ts +397 -0
  191. package/src/load/diagnostics.ts +144 -0
  192. package/src/load/discover.ts +455 -0
  193. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  194. package/src/load/expected-module.ts +188 -0
  195. package/src/load/load.ts +73 -0
  196. package/src/load/module-lookup.ts +50 -0
  197. package/src/load/plan.ts +504 -0
  198. package/src/load/specifiers.ts +168 -0
  199. package/src/load/synthesize.ts +211 -0
  200. package/src/load/test-globals.ts +251 -0
  201. package/src/load/test-joins.ts +98 -0
  202. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  203. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  204. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
  205. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  206. package/src/{workspace.ts → load/workspace.ts} +90 -134
  207. package/src/manifest.ts +81 -75
  208. package/src/matrix.ts +122 -4
  209. package/src/mdx-entries.ts +3 -26
  210. package/src/outside-tests.ts +1 -1
  211. package/src/partial.ts +3 -2
  212. package/src/paths.ts +27 -15
  213. package/src/position.ts +2 -1
  214. package/src/print.ts +12 -92
  215. package/src/product.ts +18 -0
  216. package/src/query.ts +11 -0
  217. package/src/ref.ts +1 -0
  218. package/src/references.ts +52 -31
  219. package/src/report-schema.ts +68 -0
  220. package/src/report.ts +101 -224
  221. package/src/reporters/annotations.ts +22 -11
  222. package/src/reporters/json.ts +1 -1
  223. package/src/reporters/reporter.ts +2 -4
  224. package/src/reporters/reporters.ts +1 -1
  225. package/src/reporters/sarif.ts +29 -16
  226. package/src/reporters/template.ts +4 -20
  227. package/src/reporters/text.ts +3 -3
  228. package/src/resolve.ts +283 -1114
  229. package/src/roots.ts +339 -104
  230. package/src/run.ts +75 -91
  231. package/src/schema.ts +391 -388
  232. package/src/scope.ts +46 -138
  233. package/src/serialization-contract.ts +763 -22
  234. package/src/session.ts +70 -54
  235. package/src/source-files.ts +1 -1
  236. package/src/suppress-file.ts +24 -33
  237. package/src/suppress-inline.ts +2 -2
  238. package/src/suppress.ts +43 -58
  239. package/src/sweep.ts +23 -9
  240. package/src/symbol-keys.ts +2 -5
  241. package/src/template-field.ts +15 -25
  242. package/src/test-support.ts +17 -13
  243. package/src/text/compare.ts +27 -0
  244. package/src/text/glob.ts +210 -0
  245. package/src/text/named-files.inputs.json +1522 -0
  246. package/src/text/named-files.ts +68 -0
  247. package/src/text/shell.ts +161 -0
  248. package/src/text/yaml.ts +423 -0
  249. package/src/type-errors.ts +18 -288
  250. package/src/unseen-loads.ts +134 -0
  251. package/src/unwrap.ts +34 -0
  252. package/src/usage.ts +10 -0
  253. package/src/verbs/analyze.ts +224 -162
  254. package/src/verbs/describe.ts +14 -15
  255. package/src/verbs/explain.ts +10 -11
  256. package/src/verbs/print-config.ts +2 -1
  257. package/src/verbs/print-projects.ts +6 -4
  258. package/src/verbs/print-retained.ts +4 -2
  259. package/src/verbs/print-roots.ts +4 -2
  260. package/src/verbs/verb.ts +2 -8
  261. package/src/verbs/version.ts +15 -3
  262. package/src/workflow-steps.ts +74 -64
  263. package/dist/src/base-url.js +0 -18
  264. package/dist/src/discover.js +0 -468
  265. package/dist/src/glob.js +0 -65
  266. package/dist/src/pnpm-workspace.js +0 -120
  267. package/dist/src/setup-failure.js +0 -23
  268. package/dist/src/test-joins.js +0 -101
  269. package/dist/src/version.js +0 -7
  270. package/src/base-url.ts +0 -24
  271. package/src/discover.ts +0 -653
  272. package/src/glob.ts +0 -67
  273. package/src/pnpm-workspace.ts +0 -135
  274. package/src/setup-failure.ts +0 -39
  275. package/src/test-joins.ts +0 -120
  276. package/src/version.ts +0 -7
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Glob patterns over paths in two forms. The configured form (the configuration, convention
3
+ * rows, bundler entries, workspace member patterns) has `*` and `?` within a segment, `**`
4
+ * across segments and brace lists. A `.gitignore` line has git's form: bracket expressions
5
+ * and backslash escapes, no brace lists (https://git-scm.com/docs/gitignore#_pattern_format).
6
+ * A configured root is not a glob: it names a symbol reference, and is read where it is used.
7
+ */
8
+ /**
9
+ * One character of a pattern as the expression syntax reads it literally. The set escaped
10
+ * is closed: under the unicode flag an escape of any other character is refused outright.
11
+ */
12
+ function quoted(character) {
13
+ return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
14
+ }
15
+ /**
16
+ * The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
17
+ * character table reads them, each range written as its first and last character.
18
+ */
19
+ const CLASSES = new Map([
20
+ ["alnum", "09AZaz"],
21
+ ["alpha", "AZaz"],
22
+ ["blank", "\t\t "],
23
+ ["cntrl", "\0\x1f\x7f\x7f"],
24
+ ["digit", "09"],
25
+ ["graph", "!~"],
26
+ ["lower", "az"],
27
+ ["print", " ~"],
28
+ ["punct", "!/:@[`{~"],
29
+ ["space", "\t\n\r\r "],
30
+ ["upper", "AZ"],
31
+ ["xdigit", "09AFaf"],
32
+ ]);
33
+ /** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
34
+ const NOTHING = "(?!)";
35
+ /** One code point as a member of an expression class. */
36
+ function member(point) {
37
+ return `\\u{${point.toString(16)}}`;
38
+ }
39
+ /**
40
+ * The class a bracket expression opening at `open` denotes, and the index past its `]`,
41
+ * read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
42
+ * member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
43
+ * `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
44
+ * matches nothing. Neither form matches the solidus.
45
+ */
46
+ function bracket(pattern, open) {
47
+ let at = open + 1;
48
+ const negated = pattern[at] === "!" || pattern[at] === "^";
49
+ if (negated) {
50
+ at += 1;
51
+ }
52
+ const next = () => {
53
+ const point = pattern.codePointAt(at);
54
+ at += point !== undefined && point > 0xffff ? 2 : 1;
55
+ return point;
56
+ };
57
+ let members = "";
58
+ let low;
59
+ for (let first = true;; first = false) {
60
+ let point = next();
61
+ if (point === undefined) {
62
+ return { source: NOTHING, end: pattern.length };
63
+ }
64
+ if (point === 0x5d && !first) {
65
+ return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
66
+ }
67
+ if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
68
+ let high = next();
69
+ if (high === 0x5c) {
70
+ high = next();
71
+ }
72
+ if (high === undefined) {
73
+ return { source: NOTHING, end: pattern.length };
74
+ }
75
+ members += high >= low ? `${member(low)}-${member(high)}` : "";
76
+ low = undefined;
77
+ continue;
78
+ }
79
+ if (point === 0x5b && pattern[at] === ":") {
80
+ const close = pattern.indexOf("]", at + 1);
81
+ if (close < 0) {
82
+ return { source: NOTHING, end: pattern.length };
83
+ }
84
+ if (close > at + 1 && pattern[close - 1] === ":") {
85
+ const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
86
+ if (ranges === undefined) {
87
+ return { source: NOTHING, end: pattern.length };
88
+ }
89
+ for (let one = 0; one < ranges.length; one += 2) {
90
+ members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
91
+ }
92
+ low = undefined;
93
+ at = close + 1;
94
+ continue;
95
+ }
96
+ }
97
+ if (point === 0x5c) {
98
+ point = next();
99
+ if (point === undefined) {
100
+ return { source: NOTHING, end: pattern.length };
101
+ }
102
+ }
103
+ members += member(point);
104
+ low = point;
105
+ }
106
+ }
107
+ /** The expression source of one pattern with no brace list. */
108
+ function source(pattern, form) {
109
+ let text = "";
110
+ let at = 0;
111
+ while (at < pattern.length) {
112
+ if (pattern.startsWith("**/", at)) {
113
+ text += "(?:[^/]*/)*";
114
+ at += 3;
115
+ continue;
116
+ }
117
+ if (pattern.startsWith("**", at)) {
118
+ text += ".*";
119
+ at += 2;
120
+ continue;
121
+ }
122
+ const character = pattern[at] ?? "";
123
+ if (form === "gitignore" && character === "[") {
124
+ const read = bracket(pattern, at);
125
+ text += read.source;
126
+ at = read.end;
127
+ continue;
128
+ }
129
+ if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
130
+ text += quoted(pattern[at + 1] ?? "");
131
+ at += 2;
132
+ continue;
133
+ }
134
+ at += 1;
135
+ text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
136
+ }
137
+ return text;
138
+ }
139
+ /** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
140
+ export function braceExpanded(pattern) {
141
+ const open = pattern.indexOf("{");
142
+ const close = open < 0 ? -1 : pattern.indexOf("}", open);
143
+ if (close < 0) {
144
+ return [pattern];
145
+ }
146
+ const before = pattern.slice(0, open);
147
+ const after = pattern.slice(close + 1);
148
+ return pattern
149
+ .slice(open + 1, close)
150
+ .split(",")
151
+ .flatMap((member) => braceExpanded(before + member + after));
152
+ }
153
+ /** The expression one whole-path pattern of the configured form denotes. */
154
+ export function globExpression(pattern) {
155
+ const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
156
+ return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
157
+ }
158
+ /** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
159
+ export function gitignoreExpression(pattern) {
160
+ return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
161
+ }
162
+ const SEGMENTS = new Map();
163
+ /**
164
+ * Whether one directory name matches one segment of a workspace's member pattern, its brace
165
+ * lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
166
+ * them: https://docs.npmjs.com/cli/using-npm/workspaces
167
+ */
168
+ export function segmentMatches(segment, name) {
169
+ if (name.startsWith(".") && !segment.startsWith(".")) {
170
+ return false;
171
+ }
172
+ let expression = SEGMENTS.get(segment);
173
+ if (expression === undefined) {
174
+ expression = globExpression(segment);
175
+ SEGMENTS.set(segment, expression);
176
+ }
177
+ return expression.test(name);
178
+ }
179
+ /**
180
+ * Whether one path's segments match a member pattern's, `**` standing for any run of segments
181
+ * that starts with no dot.
182
+ */
183
+ export function pathMatches(pattern, path) {
184
+ const [head, ...rest] = pattern;
185
+ if (head === undefined) {
186
+ return path.length === 0;
187
+ }
188
+ if (head === "**") {
189
+ return (pathMatches(rest, path) ||
190
+ (path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1))));
191
+ }
192
+ const [name, ...below] = path;
193
+ return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
194
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The files a command's text names, read as plain text: the text is searched for each file's
3
+ * names, which every spelling of its path contains, so no part of it is split, unquoted or
4
+ * skipped first.
5
+ */
6
+ import { dirnamePath, normalizePath, relativePath } from "../paths.js";
7
+ /** The extension a source or an emitted file carries: a JavaScript or TypeScript one, or a declaration's. */
8
+ const SOURCE_EXTENSION = /\.(?:d\.[cm]?ts|[cm]?[jt]s|[jt]sx)$/u;
9
+ /**
10
+ * The names whose presence in a text holds one path below `dir`: its last name without the
11
+ * extension, or with it where nothing else is left, and for an `index` file its directory's
12
+ * last name, and `.` too where that is `dir`. Every spelling of the path relative to `dir`,
13
+ * with or without `./`, `/` or `\`, its extension or its `/index` part, and every longer path
14
+ * ending in one, holds one of them. None is empty, because an empty name is in every text.
15
+ */
16
+ function stemsOf(path, dir) {
17
+ const last = path.slice(path.lastIndexOf("/") + 1);
18
+ const bare = last.replace(SOURCE_EXTENSION, "");
19
+ const name = bare === "" ? last : bare;
20
+ if (name !== "index") {
21
+ return [name];
22
+ }
23
+ const parent = dirnamePath(path);
24
+ return [
25
+ name,
26
+ parent.slice(parent.lastIndexOf("/") + 1),
27
+ ...(parent === normalizePath(dir) ? ["."] : []),
28
+ ].filter((stem) => stem !== "");
29
+ }
30
+ /**
31
+ * Every file of `files` below `dir` that `text` names: one whose {@link stemsOf} a path of it
32
+ * spells appears anywhere in the text. A file the text does not name may be held too, and no
33
+ * file it names is missed.
34
+ */
35
+ export function spelledFiles(text, dir, files) {
36
+ const byStem = new Map();
37
+ for (const { file, paths } of files) {
38
+ const [own] = paths;
39
+ if (own === undefined || relativePath(dir, own) === undefined) {
40
+ continue;
41
+ }
42
+ for (const stem of new Set(paths.flatMap((path) => stemsOf(path, dir)))) {
43
+ byStem.set(stem, [...(byStem.get(stem) ?? []), file]);
44
+ }
45
+ }
46
+ const found = new Set();
47
+ // One pass per stem length: each place of the text is looked up once for every length.
48
+ for (const length of new Set([...byStem.keys()].map((stem) => stem.length))) {
49
+ for (let at = 0; at + length <= text.length; at += 1) {
50
+ byStem.get(text.slice(at, at + length))?.forEach((file) => found.add(file));
51
+ }
52
+ }
53
+ return [...found];
54
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Shell command lines read as text, never run: the commands a line runs, the tokens the
3
+ * Contract splits a command into, and the words POSIX token recognition reads.
4
+ */
5
+ /**
6
+ * The shell's list and pipeline operators, each of which ends one command. An `&` beside a
7
+ * redirection's `<` or `>` (`2>&1`, and bash's `&>log`) is part of the redirection.
8
+ */
9
+ const COMMAND_OPERATORS = /&&|\|\||;|\||(?<![<>])&(?!>)/u;
10
+ /** Where a command splits into tokens: white space and the operators the Contract names. */
11
+ const TOKEN_SEPARATORS = /\s+|&&|\|\||;|\|/u;
12
+ /** A text written inside one pair of quotes. */
13
+ const QUOTED = /^(["'])(.*)\1$/su;
14
+ /** The commands one line runs, the text between two of the shell's list and pipeline operators. */
15
+ export function commandsOf(line) {
16
+ return line.split(COMMAND_OPERATORS);
17
+ }
18
+ /** One text with one pair of enclosing quotes removed. */
19
+ export function unquoted(text) {
20
+ return text.replace(QUOTED, "$2");
21
+ }
22
+ /**
23
+ * Every token one script's command line holds, each with one pair of enclosing quotes
24
+ * removed. A token holding a wildcard is a pattern rather than a path, and names no
25
+ * entry point.
26
+ */
27
+ export function scriptTokens(command) {
28
+ return command
29
+ .split(TOKEN_SEPARATORS)
30
+ .map(unquoted)
31
+ .filter((token) => token !== "" && !token.includes("*"));
32
+ }
33
+ /** One shell line up to its comment: a `#` that starts a word outside quotes. */
34
+ export function withoutComment(line) {
35
+ let quote = "";
36
+ for (let at = 0; at < line.length; at += 1) {
37
+ const char = line[at];
38
+ if (quote !== "") {
39
+ if (char === quote) {
40
+ quote = "";
41
+ }
42
+ else if (char === "\\" && quote === '"') {
43
+ at += 1;
44
+ }
45
+ }
46
+ else if (char === "'" || char === '"') {
47
+ quote = char;
48
+ }
49
+ else if (char === "\\") {
50
+ at += 1;
51
+ }
52
+ else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
53
+ return line.slice(0, at);
54
+ }
55
+ }
56
+ return line;
57
+ }
58
+ /** The characters that end a word outside quotes and separate nothing: blanks. */
59
+ // Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
60
+ const BLANK = /[ \t\r\f\v]/u;
61
+ /** The characters that end a word outside quotes and begin an operator: newline included. */
62
+ const OPERATOR = /[\n;&|()<>]/u;
63
+ /** The characters a backslash escapes inside double quotes; before any other it stands for itself. */
64
+ const DOUBLE_QUOTE_ESCAPES = new Set(["$", "`", '"', "\\"]);
65
+ /**
66
+ * The words of one command line, as POSIX token recognition reads quotes, backslashes,
67
+ * line continuations, comments and operators. A word an expansion may change (an unquoted
68
+ * `$`, backquote, glob character or leading `~`, or a `$` or backquote in double quotes)
69
+ * is computed, and so is the rest of a line whose quote is never closed. A computed word
70
+ * and each operator are undefined, so a flag's file is never read across them.
71
+ */
72
+ export function shellWords(command) {
73
+ const words = [];
74
+ let word;
75
+ let computed = false;
76
+ const end = () => {
77
+ if (word !== undefined) {
78
+ words.push(computed ? undefined : word);
79
+ }
80
+ word = undefined;
81
+ computed = false;
82
+ };
83
+ for (let at = 0; at < command.length; at += 1) {
84
+ const char = command.charAt(at);
85
+ if (char === "\\" && command.charAt(at + 1) === "\n") {
86
+ at += 1;
87
+ }
88
+ else if (BLANK.test(char)) {
89
+ end();
90
+ }
91
+ else if (OPERATOR.test(char)) {
92
+ end();
93
+ words.push(undefined);
94
+ }
95
+ else if (char === "#" && word === undefined) {
96
+ const newline = command.indexOf("\n", at);
97
+ at = newline < 0 ? command.length : newline - 1;
98
+ }
99
+ else if (char === "\\") {
100
+ at += 1;
101
+ word = (word ?? "") + command.charAt(at);
102
+ }
103
+ else if (char === "'" || char === '"') {
104
+ const close = closingQuote(command, at);
105
+ if (close < 0) {
106
+ word ??= "";
107
+ computed = true;
108
+ break;
109
+ }
110
+ const quoted = char === "'"
111
+ ? { text: command.slice(at + 1, close), computed: false }
112
+ : doubleQuoted(command.slice(at + 1, close));
113
+ word = (word ?? "") + quoted.text;
114
+ computed ||= quoted.computed;
115
+ at = close;
116
+ }
117
+ else {
118
+ computed ||= /[$`*?[]/u.test(char) || (char === "~" && word === undefined);
119
+ word = (word ?? "") + char;
120
+ }
121
+ }
122
+ end();
123
+ return words;
124
+ }
125
+ /** The text between one pair of double quotes, unescaped, and whether an expansion may change it. */
126
+ function doubleQuoted(inner) {
127
+ let text = "";
128
+ let computed = false;
129
+ for (let at = 0; at < inner.length; at += 1) {
130
+ const char = inner.charAt(at);
131
+ const next = inner.charAt(at + 1);
132
+ if (char === "\\" && next === "\n") {
133
+ at += 1;
134
+ }
135
+ else if (char === "\\" && DOUBLE_QUOTE_ESCAPES.has(next)) {
136
+ text += next;
137
+ at += 1;
138
+ }
139
+ else {
140
+ computed ||= char === "$" || char === "`";
141
+ text += char;
142
+ }
143
+ }
144
+ return { text, computed };
145
+ }
146
+ /** The index of the quote that closes the one at `open`, or -1 where none does. */
147
+ function closingQuote(command, open) {
148
+ const quote = command.charAt(open);
149
+ for (let at = open + 1; at < command.length; at += 1) {
150
+ const char = command.charAt(at);
151
+ if (char === quote) {
152
+ return at;
153
+ }
154
+ if (quote === '"' && char === "\\") {
155
+ at += 1;
156
+ }
157
+ }
158
+ return -1;
159
+ }