@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) 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 +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  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 +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
@@ -1,10 +1,13 @@
1
1
  /**
2
- * Test-support code: the files only tests reach. A test reaches each one through a chain
3
- * of references, and every reference to a declaration of one, an import for its effects
4
- * included, is written in a test file or in another such file, so it is judged with the
5
- * tests: what it references is referenced by test code.
2
+ * Test-support code: the files only tests reach that carry test evidence of their own. A
3
+ * test reaches each one through a chain of references, and every reference to a
4
+ * declaration of one, an import for its effects included, is written in a test file or in
5
+ * another such file, so it is judged with the tests: what it references is referenced by
6
+ * test code.
6
7
  */
7
8
 
9
+ import { CONVENTION_ROWS, TEST_LIBRARIES, TEST_LIBRARY_SCOPES } from "./convention-rows.ts";
10
+ import { packageOfSpecifier } from "./dependencies.ts";
8
11
  import type { InventorySymbol } from "./inventory.ts";
9
12
  import type { Reference } from "./references.ts";
10
13
  import type { Root } from "./roots.ts";
@@ -12,19 +15,144 @@ import type { Root } from "./roots.ts";
12
15
  /** The root kind that never takes a file out of test-support code: the skip only withholds. */
13
16
  const SKIP_ROOT = "type-error";
14
17
 
18
+ /**
19
+ * The test runners and test libraries an import of which is test evidence: each runner a
20
+ * convention row reads test files for, and the convention table's test libraries.
21
+ */
22
+ const TEST_PACKAGES: ReadonlySet<string> = new Set([
23
+ ...CONVENTION_ROWS.filter((row) => row.testFiles !== undefined).map((row) => row.package),
24
+ ...TEST_LIBRARIES,
25
+ ]);
26
+
27
+ /**
28
+ * The Node.js built-in modules a bare specifier names, which are no package. The set is
29
+ * closed: Node.js adds a new built-in under the `node:` scheme alone
30
+ * (https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix).
31
+ */
32
+ const NODE_BUILTINS: ReadonlySet<string> = new Set([
33
+ "assert",
34
+ "async_hooks",
35
+ "buffer",
36
+ "child_process",
37
+ "cluster",
38
+ "console",
39
+ "constants",
40
+ "crypto",
41
+ "dgram",
42
+ "diagnostics_channel",
43
+ "dns",
44
+ "domain",
45
+ "events",
46
+ "fs",
47
+ "http",
48
+ "http2",
49
+ "https",
50
+ "inspector",
51
+ "module",
52
+ "net",
53
+ "os",
54
+ "path",
55
+ "perf_hooks",
56
+ "process",
57
+ "punycode",
58
+ "querystring",
59
+ "readline",
60
+ "repl",
61
+ "stream",
62
+ "string_decoder",
63
+ "sys",
64
+ "timers",
65
+ "tls",
66
+ "trace_events",
67
+ "tty",
68
+ "url",
69
+ "util",
70
+ "v8",
71
+ "vm",
72
+ "wasi",
73
+ "worker_threads",
74
+ "zlib",
75
+ ]);
76
+
77
+ /** What a file imports, read for test evidence. */
78
+ export interface TestEvidence {
79
+ /** Per file below the target root, the module specifiers its imports write. */
80
+ readonly imports: ReadonlyMap<string, readonly string[]>;
81
+ /**
82
+ * Whether the manifest nearest the file, below the target root, that declares the
83
+ * package declares it under `devDependencies`.
84
+ */
85
+ readonly devDependency: (path: string, name: string) => boolean;
86
+ }
87
+
88
+ /** Whether a specifier names a test runner or a test library. */
89
+ function namesTestPackage(specifier: string): boolean {
90
+ const name = specifier.startsWith("node:") ? specifier : packageOfSpecifier(specifier);
91
+ return (
92
+ name !== undefined &&
93
+ (TEST_PACKAGES.has(name) || TEST_LIBRARY_SCOPES.some((scope) => name.startsWith(scope)))
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Whether a file carries test evidence: an import of a test runner or a test library, or
99
+ * package imports, Node built-ins aside, that are all devDependencies no file imports
100
+ * outside the test files and `onlyTests`, the files only tests reach.
101
+ */
102
+ function evidenceReader(
103
+ tests: ReadonlySet<string>,
104
+ evidence: TestEvidence,
105
+ ): (onlyTests: ReadonlySet<string>, path: string) => boolean {
106
+ const importers = new Map<string, string[]>();
107
+ for (const [path, specifiers] of evidence.imports) {
108
+ for (const specifier of specifiers) {
109
+ const name = packageOfSpecifier(specifier);
110
+ if (name !== undefined) {
111
+ importers.set(name, [...(importers.get(name) ?? []), path]);
112
+ }
113
+ }
114
+ }
115
+ return (onlyTests, path) => {
116
+ const specifiers = evidence.imports.get(path) ?? [];
117
+ if (specifiers.some(namesTestPackage)) {
118
+ return true;
119
+ }
120
+ const names = specifiers.flatMap((one) => packageOfSpecifier(one) ?? []);
121
+ const packages = names.filter((name) => !NODE_BUILTINS.has(name));
122
+ return (
123
+ packages.length > 0 &&
124
+ packages.every(
125
+ (name) =>
126
+ evidence.devDependency(path, name) &&
127
+ (importers.get(name) ?? []).every((one) => tests.has(one) || onlyTests.has(one)),
128
+ )
129
+ );
130
+ };
131
+ }
132
+
133
+ /** The files only tests reach, split by whether each carries test evidence of its own. */
134
+ interface TestReached {
135
+ /** The test-support files: test code. */
136
+ readonly support: ReadonlySet<string>;
137
+ /** The files only tests reach that are no test-support file: production code only tests use. */
138
+ readonly testUsed: ReadonlySet<string>;
139
+ }
140
+
15
141
  /**
16
142
  * The files of one project that are test-support code, below the target root: the
17
143
  * largest set of files a test file reaches, none of them a test file or holding a root,
18
- * whose every user outside the set is a test file. A root has a caller outside the
19
- * reference graph: a runtime, a tool, a configuration, or a consumer of a library's
20
- * published API. `testFiles` are the files the test-file rules classified.
144
+ * each carrying test evidence of its own, whose every user outside the set is a test
145
+ * file; and the files only tests reach that are not among them. A root has a caller
146
+ * outside the reference graph: a runtime, a tool, a configuration, or a consumer of a
147
+ * library's published API. `testFiles` are the files the test-file rules classified.
21
148
  */
22
149
  export function testSupportFiles(
23
150
  symbols: readonly InventorySymbol[],
24
151
  references: readonly Reference[],
25
152
  roots: readonly Root[],
26
153
  testFiles: readonly string[],
27
- ): ReadonlySet<string> {
154
+ evidence: TestEvidence,
155
+ ): TestReached {
28
156
  const fileOf = new Map(symbols.map((symbol) => [symbol.id, symbol.position.path]));
29
157
  const users = new Map<string, Set<string>>();
30
158
  const uses = new Map<string, Set<string>>();
@@ -59,18 +187,27 @@ export function testSupportFiles(
59
187
  }
60
188
  }
61
189
  }
62
- const support = new Set([...reached].filter((path) => !rooted.has(path)));
63
- for (let changed = true; changed;) {
64
- changed = false;
65
- for (const path of support) {
66
- const from = users.get(path) ?? new Set<string>();
67
- if (![...from].every((one) => tests.has(one) || support.has(one))) {
68
- support.delete(path);
69
- changed = true;
190
+ const withoutOutsideUsers = (files: Set<string>): Set<string> => {
191
+ for (let changed = true; changed;) {
192
+ changed = false;
193
+ for (const path of files) {
194
+ const from = users.get(path) ?? new Set<string>();
195
+ if (![...from].every((one) => tests.has(one) || files.has(one))) {
196
+ files.delete(path);
197
+ changed = true;
198
+ }
70
199
  }
71
200
  }
72
- }
73
- return support;
201
+ return files;
202
+ };
203
+ // Evidence reads the files only tests reach, evidence or none, so it is fixed before the
204
+ // files without evidence leave the set.
205
+ const onlyTests = withoutOutsideUsers(new Set([...reached].filter((path) => !rooted.has(path))));
206
+ const carries = evidenceReader(tests, evidence);
207
+ const support = withoutOutsideUsers(
208
+ new Set([...onlyTests].filter((path) => carries(onlyTests, path))),
209
+ );
210
+ return { support, testUsed: new Set([...onlyTests].filter((path) => !support.has(path))) };
74
211
  }
75
212
 
76
213
  /** The references with every one a test-support file makes counted as a test reference. */
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Two strings in the order of their UTF-8 bytes, the order every set of a run is read in.
3
+ * That is code point order, which UTF-16 code unit order (`<` on strings) breaks above
4
+ * U+FFFF: a surrogate unit is below U+E000 while the code point it encodes is above U+FFFF.
5
+ */
6
+ export function bytewise(a: string, b: string): number {
7
+ if (a === b) {
8
+ return 0;
9
+ }
10
+ const length = Math.min(a.length, b.length);
11
+ for (let at = 0; at < length; at += 1) {
12
+ const x = a.charCodeAt(at);
13
+ const y = b.charCodeAt(at);
14
+ if (x !== y) {
15
+ return ranked(x) < ranked(y) ? -1 : 1;
16
+ }
17
+ }
18
+ return a.length < b.length ? -1 : 1;
19
+ }
20
+
21
+ /** One UTF-16 unit moved so that a surrogate ranks above every unit of U+E000 to U+FFFF. */
22
+ function ranked(unit: number): number {
23
+ if (unit >= 0xe000) {
24
+ return unit - 0x800;
25
+ }
26
+ return unit >= 0xd800 ? unit + 0x2000 : unit;
27
+ }
@@ -0,0 +1,210 @@
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
+ type GlobForm = "configured" | "gitignore";
10
+
11
+ /**
12
+ * One character of a pattern as the expression syntax reads it literally. The set escaped
13
+ * is closed: under the unicode flag an escape of any other character is refused outright.
14
+ */
15
+ function quoted(character: string): string {
16
+ return /[.*+?^${}()|[\]\\/]/u.test(character) ? `\\${character}` : character;
17
+ }
18
+
19
+ /**
20
+ * The ASCII ranges each POSIX class a bracket expression may name holds, as git's own
21
+ * character table reads them, each range written as its first and last character.
22
+ */
23
+ const CLASSES: ReadonlyMap<string, string> = new Map([
24
+ ["alnum", "09AZaz"],
25
+ ["alpha", "AZaz"],
26
+ ["blank", "\t\t "],
27
+ ["cntrl", "\0\x1f\x7f\x7f"],
28
+ ["digit", "09"],
29
+ ["graph", "!~"],
30
+ ["lower", "az"],
31
+ ["print", " ~"],
32
+ ["punct", "!/:@[`{~"],
33
+ ["space", "\t\n\r\r "],
34
+ ["upper", "AZ"],
35
+ ["xdigit", "09AFaf"],
36
+ ]);
37
+
38
+ /** What a pattern git cannot read matches: nothing, as git aborts the whole match. */
39
+ const NOTHING = "(?!)";
40
+
41
+ /** One code point as a member of an expression class. */
42
+ function member(point: number): string {
43
+ return `\\u{${point.toString(16)}}`;
44
+ }
45
+
46
+ /**
47
+ * The class a bracket expression opening at `open` denotes, and the index past its `]`,
48
+ * read as git's wildmatch reads it: a leading `!` or `^` negates it, a `]` first is a
49
+ * member, `\` escapes one character, `x-y` is a range (one whose `y` is below `x` holds
50
+ * `x` alone) and `[:name:]` a POSIX class. An unclosed bracket or an unknown class name
51
+ * matches nothing. Neither form matches the solidus.
52
+ */
53
+ function bracket(pattern: string, open: number): { source: string; end: number } {
54
+ let at = open + 1;
55
+ const negated = pattern[at] === "!" || pattern[at] === "^";
56
+ if (negated) {
57
+ at += 1;
58
+ }
59
+ const next = (): number | undefined => {
60
+ const point = pattern.codePointAt(at);
61
+ at += point !== undefined && point > 0xffff ? 2 : 1;
62
+ return point;
63
+ };
64
+ let members = "";
65
+ let low: number | undefined;
66
+ for (let first = true; ; first = false) {
67
+ let point = next();
68
+ if (point === undefined) {
69
+ return { source: NOTHING, end: pattern.length };
70
+ }
71
+ if (point === 0x5d && !first) {
72
+ return { source: negated ? `[^/${members}]` : `(?!/)[${members}]`, end: at };
73
+ }
74
+ if (point === 0x2d && low !== undefined && at < pattern.length && pattern[at] !== "]") {
75
+ let high = next();
76
+ if (high === 0x5c) {
77
+ high = next();
78
+ }
79
+ if (high === undefined) {
80
+ return { source: NOTHING, end: pattern.length };
81
+ }
82
+ members += high >= low ? `${member(low)}-${member(high)}` : "";
83
+ low = undefined;
84
+ continue;
85
+ }
86
+ if (point === 0x5b && pattern[at] === ":") {
87
+ const close = pattern.indexOf("]", at + 1);
88
+ if (close < 0) {
89
+ return { source: NOTHING, end: pattern.length };
90
+ }
91
+ if (close > at + 1 && pattern[close - 1] === ":") {
92
+ const ranges = CLASSES.get(pattern.slice(at + 1, close - 1));
93
+ if (ranges === undefined) {
94
+ return { source: NOTHING, end: pattern.length };
95
+ }
96
+ for (let one = 0; one < ranges.length; one += 2) {
97
+ members += `${member(ranges.charCodeAt(one))}-${member(ranges.charCodeAt(one + 1))}`;
98
+ }
99
+ low = undefined;
100
+ at = close + 1;
101
+ continue;
102
+ }
103
+ }
104
+ if (point === 0x5c) {
105
+ point = next();
106
+ if (point === undefined) {
107
+ return { source: NOTHING, end: pattern.length };
108
+ }
109
+ }
110
+ members += member(point);
111
+ low = point;
112
+ }
113
+ }
114
+
115
+ /** The expression source of one pattern with no brace list. */
116
+ function source(pattern: string, form: GlobForm): string {
117
+ let text = "";
118
+ let at = 0;
119
+ while (at < pattern.length) {
120
+ if (pattern.startsWith("**/", at)) {
121
+ text += "(?:[^/]*/)*";
122
+ at += 3;
123
+ continue;
124
+ }
125
+ if (pattern.startsWith("**", at)) {
126
+ text += ".*";
127
+ at += 2;
128
+ continue;
129
+ }
130
+ const character = pattern[at] ?? "";
131
+ if (form === "gitignore" && character === "[") {
132
+ const read = bracket(pattern, at);
133
+ text += read.source;
134
+ at = read.end;
135
+ continue;
136
+ }
137
+ if (form === "gitignore" && character === "\\" && at + 1 < pattern.length) {
138
+ text += quoted(pattern[at + 1] ?? "");
139
+ at += 2;
140
+ continue;
141
+ }
142
+ at += 1;
143
+ text += character === "*" ? "[^/]*" : character === "?" ? "[^/]" : quoted(character);
144
+ }
145
+ return text;
146
+ }
147
+
148
+ /** A pattern with every brace list expanded into its alternatives; a lone `{` stands for itself. */
149
+ export function braceExpanded(pattern: string): readonly string[] {
150
+ const open = pattern.indexOf("{");
151
+ const close = open < 0 ? -1 : pattern.indexOf("}", open);
152
+ if (close < 0) {
153
+ return [pattern];
154
+ }
155
+ const before = pattern.slice(0, open);
156
+ const after = pattern.slice(close + 1);
157
+ return pattern
158
+ .slice(open + 1, close)
159
+ .split(",")
160
+ .flatMap((member) => braceExpanded(before + member + after));
161
+ }
162
+
163
+ /** The expression one whole-path pattern of the configured form denotes. */
164
+ export function globExpression(pattern: string): RegExp {
165
+ const alternatives = braceExpanded(pattern).map((one) => source(one, "configured"));
166
+ return new RegExp(`^(?:${alternatives.join("|")})$`, "u");
167
+ }
168
+
169
+ /** The expression one `.gitignore` pattern denotes over a path below its file's directory. */
170
+ export function gitignoreExpression(pattern: string): RegExp {
171
+ return new RegExp(`^${source(pattern, "gitignore")}$`, "u");
172
+ }
173
+
174
+ const SEGMENTS = new Map<string, RegExp>();
175
+
176
+ /**
177
+ * Whether one directory name matches one segment of a workspace's member pattern, its brace
178
+ * lists already expanded. Neither `*` nor `?` matches a leading dot, as npm's workspaces read
179
+ * them: https://docs.npmjs.com/cli/using-npm/workspaces
180
+ */
181
+ export function segmentMatches(segment: string, name: string): boolean {
182
+ if (name.startsWith(".") && !segment.startsWith(".")) {
183
+ return false;
184
+ }
185
+ let expression = SEGMENTS.get(segment);
186
+ if (expression === undefined) {
187
+ expression = globExpression(segment);
188
+ SEGMENTS.set(segment, expression);
189
+ }
190
+ return expression.test(name);
191
+ }
192
+
193
+ /**
194
+ * Whether one path's segments match a member pattern's, `**` standing for any run of segments
195
+ * that starts with no dot.
196
+ */
197
+ export function pathMatches(pattern: readonly string[], path: readonly string[]): boolean {
198
+ const [head, ...rest] = pattern;
199
+ if (head === undefined) {
200
+ return path.length === 0;
201
+ }
202
+ if (head === "**") {
203
+ return (
204
+ pathMatches(rest, path) ||
205
+ (path.length > 0 && !(path[0] ?? "").startsWith(".") && pathMatches(pattern, path.slice(1)))
206
+ );
207
+ }
208
+ const [name, ...below] = path;
209
+ return name !== undefined && segmentMatches(head, name) && pathMatches(rest, below);
210
+ }