@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
@@ -0,0 +1,92 @@
1
+ import type { Host } from "../host.ts";
2
+ import { isRecord } from "../json-document.ts";
3
+ import { joinPath } from "../paths.ts";
4
+ import { message } from "../product.ts";
5
+
6
+ /** The file a package's manifest is read from. */
7
+ export const MANIFEST = "package.json";
8
+
9
+ /** The directory a package manager installs packages into, whose files are no project's own. */
10
+ export const INSTALLED_PACKAGES = "node_modules";
11
+
12
+ /** The manifest members a package declares the packages it depends on under. */
13
+ export const DEPENDENCY_SECTIONS = [
14
+ "dependencies",
15
+ "devDependencies",
16
+ "peerDependencies",
17
+ "optionalDependencies",
18
+ ] as const;
19
+
20
+ /** Whether one path passes through an installed-packages directory. */
21
+ export function inInstalledPackages(path: string): boolean {
22
+ return path.split("/").includes(INSTALLED_PACKAGES);
23
+ }
24
+
25
+ /** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
26
+ export function readDocument(
27
+ host: Host,
28
+ path: string,
29
+ ): { text: string; value: unknown } | undefined {
30
+ let text: string;
31
+ try {
32
+ text = host.readFile(path);
33
+ } catch {
34
+ return undefined;
35
+ }
36
+ try {
37
+ return { text, value: JSON.parse(text) as unknown };
38
+ } catch {
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+ /** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
44
+ export function readJSON(host: Host, path: string): unknown {
45
+ return readDocument(host, path)?.value;
46
+ }
47
+
48
+ /**
49
+ * The manifest in one directory, decoded, or undefined where none reads as an object. A
50
+ * manifest this program cannot read declares nothing, the same answer as an absent one:
51
+ * the package manager owns the refusal of a manifest it rejects.
52
+ */
53
+ export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
54
+ const value = readJSON(host, joinPath(dir, MANIFEST));
55
+ return isRecord(value) ? value : undefined;
56
+ }
57
+
58
+ /**
59
+ * What one directory's manifest reads as: none, an object, a file that does not parse as one,
60
+ * or a file that cannot be read, with the read's error. Either of the last two still bounds
61
+ * the package it would declare.
62
+ */
63
+ export type ManifestRead =
64
+ | { readonly kind: "absent" }
65
+ | { readonly kind: "read"; readonly manifest: Record<string, unknown> }
66
+ | { readonly kind: "malformed" }
67
+ | { readonly kind: "unread"; readonly reason: string };
68
+
69
+ /** The manifest in one directory, as {@link ManifestRead} tells its states apart. */
70
+ export function readManifestAt(host: Host, dir: string): ManifestRead {
71
+ if (!holdsManifest(host, dir)) {
72
+ return { kind: "absent" };
73
+ }
74
+ let text: string;
75
+ try {
76
+ text = host.readFile(joinPath(dir, MANIFEST));
77
+ } catch (error: unknown) {
78
+ return { kind: "unread", reason: message(error) };
79
+ }
80
+ let value: unknown;
81
+ try {
82
+ value = JSON.parse(text);
83
+ } catch {
84
+ return { kind: "malformed" };
85
+ }
86
+ return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
87
+ }
88
+
89
+ /** Whether one directory holds a manifest file. */
90
+ export function holdsManifest(host: Host, dir: string): boolean {
91
+ return host.kindOf(joinPath(dir, MANIFEST)) === "file";
92
+ }
package/src/fs/tree.ts ADDED
@@ -0,0 +1,110 @@
1
+ import type { DirectoryEntry, Host } from "../host.ts";
2
+ import { ancestors, joinPath, resolvePath } from "../paths.ts";
3
+ import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
4
+ import { LANGUAGE } from "../product.ts";
5
+ import { bytewise } from "../text/compare.ts";
6
+ import { globExpression } from "../text/glob.ts";
7
+
8
+ /** The directories no walk of the tree enters, by the patterns the language's row names. */
9
+ const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
10
+
11
+ function skipped(name: string): boolean {
12
+ return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
13
+ }
14
+
15
+ /** A git repository's working tree root, and the `info/exclude` file git reads for it. */
16
+ interface Repository {
17
+ readonly root: string;
18
+ /** Undefined where a `.git` file names no repository directory. */
19
+ readonly exclude: string | undefined;
20
+ }
21
+
22
+ /** One file's text without its line ending, or undefined where it cannot be read. */
23
+ function lineOf(host: Host, path: string): string | undefined {
24
+ try {
25
+ return host.readFile(path).replace(/\r?\n$/u, "");
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * The repository whose working tree holds `dir`: the nearest directory at or above it with a
33
+ * `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
34
+ * directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
35
+ * the directory shared with the main worktree, which holds `info/exclude`:
36
+ * https://git-scm.com/docs/gitrepository-layout
37
+ */
38
+ export function repositoryOf(host: Host, dir: string): Repository | undefined {
39
+ for (const root of ancestors(dir)) {
40
+ const dotGit = joinPath(root, ".git");
41
+ const kind = host.kindOf(dotGit);
42
+ if (kind === "directory") {
43
+ return { root, exclude: joinPath(dotGit, "info/exclude") };
44
+ }
45
+ if (kind === "file") {
46
+ const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
47
+ const gitDir = named === undefined ? undefined : resolvePath(root, named);
48
+ const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
49
+ const shared =
50
+ gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
51
+ return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ /** The entries of one directory, none where it cannot be read. */
58
+ export function entriesOf(host: Host, dir: string): readonly DirectoryEntry[] {
59
+ try {
60
+ return host.readDirectory(dir);
61
+ } catch {
62
+ return [];
63
+ }
64
+ }
65
+
66
+ /** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
67
+ export function filesIn(
68
+ host: Host,
69
+ dir: string,
70
+ accepts: (name: string) => boolean,
71
+ ): readonly string[] {
72
+ return entriesOf(host, dir)
73
+ .filter((entry) => !entry.directory && accepts(entry.name))
74
+ .map((entry) => entry.name)
75
+ .sort(bytewise);
76
+ }
77
+
78
+ /** The names of the directories directly in `dir` a walk enters, in bytewise order. */
79
+ export function directoriesIn(host: Host, dir: string): readonly string[] {
80
+ return entriesOf(host, dir)
81
+ .filter((entry) => entry.directory && !skipped(entry.name))
82
+ .map((entry) => entry.name)
83
+ .sort(bytewise);
84
+ }
85
+
86
+ /**
87
+ * Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
88
+ * order, read in no skipped directory.
89
+ */
90
+ export function filesBelow(
91
+ host: Host,
92
+ root: string,
93
+ accepts: (name: string) => boolean,
94
+ ): readonly string[] {
95
+ const found: string[] = [];
96
+ const pending = [""];
97
+ for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
98
+ for (const entry of entriesOf(host, joinPath(root, dir))) {
99
+ const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
100
+ if (!entry.directory) {
101
+ if (accepts(entry.name)) {
102
+ found.push(path);
103
+ }
104
+ } else if (!skipped(entry.name)) {
105
+ pending.push(path);
106
+ }
107
+ }
108
+ }
109
+ return found.sort(bytewise);
110
+ }
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * The generated-file class on TypeScript: every declaration in a file that marks itself
3
- * generated, or below a directory an applied convention row names as generated, is
4
- * retained, because a generator owns the file and a deletion there is undone at the next
3
+ * generated, below a directory an applied convention row names as generated, or minified,
4
+ * is retained, because a generator owns the file and a deletion there is undone at the next
5
5
  * generation.
6
6
  */
7
7
 
8
8
  import type { DetectorInput, Evidence } from "./exempt.ts";
9
+ import { minified } from "./javascript-file.ts";
9
10
  import type { SourceFiles } from "./source-files.ts";
10
11
 
11
12
  /** The generated-code header, matched against each line of a leading comment. */
@@ -14,6 +15,15 @@ const HEADER_LINE = /^\/\/ Code generated .* DO NOT EDIT\.$/u;
14
15
  /** The documentation tag `@generated` as a word of a comment. */
15
16
  const GENERATED_TAG = /(?<![\w@$])@generated(?![\w$-])/u;
16
17
 
18
+ /** A first comment naming the tool that wrote the file. */
19
+ const GENERATED_BY = /^\/\/ Generated by \S/u;
20
+
21
+ /** The word a generator's notice holds, beside one of {@link KEEP_OUT}. */
22
+ const GENERATED_WORD = /\bgenerated\b/iu;
23
+
24
+ /** The phrases a generator's notice asks a reader not to edit the file by. */
25
+ const KEEP_OUT = /\b(?:do not edit|will be overwritten|should not make any changes)\b/iu;
26
+
17
27
  /** The comments before a text's first token, a leading hashbang line skipped. */
18
28
  function leadingComments(text: string): readonly string[] {
19
29
  const comments: string[] = [];
@@ -53,13 +63,20 @@ export function generatedByComment(text: string): string | undefined {
53
63
  if (comments.some((one) => GENERATED_TAG.test(one))) {
54
64
  return "generated by the @generated tag its leading comments hold";
55
65
  }
66
+ const notice = comments.join("\n");
67
+ if (
68
+ GENERATED_BY.test(comments[0] ?? "") ||
69
+ (GENERATED_WORD.test(notice) && KEEP_OUT.test(notice))
70
+ ) {
71
+ return "generated by the notice its leading comments hold";
72
+ }
56
73
  return undefined;
57
74
  }
58
75
 
59
76
  /**
60
77
  * Every own file of one project that is generated, by its path below the target root,
61
78
  * with the detail a retention in it names: its leading comments' mark, else the row
62
- * naming its directory as generated.
79
+ * naming its directory as generated, else its minified text.
63
80
  */
64
81
  export function generatedFilesOf(
65
82
  files: SourceFiles,
@@ -71,7 +88,9 @@ export function generatedFilesOf(
71
88
  const detail =
72
89
  generatedByComment(file.text) ??
73
90
  (row === undefined
74
- ? undefined
91
+ ? minified(file.fileName, file.text)
92
+ ? "generated as a minified JavaScript file"
93
+ : undefined
75
94
  : `generated below a directory the convention row ${row} names`);
76
95
  if (detail !== undefined) {
77
96
  found.set(path, detail);
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Which files git ignores below a tree's root, read as git reads its ignore files:
3
+ * https://git-scm.com/docs/gitignore#_pattern_format
4
+ */
5
+
6
+ import type { Host } from "./host.ts";
7
+ import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.ts";
8
+ import { message } from "./product.ts";
9
+ import { gitignoreExpression } from "./text/glob.ts";
10
+ import { repositoryOf } from "./fs/tree.ts";
11
+
12
+ /** One pattern line of an ignore file. */
13
+ interface Rule {
14
+ readonly negated: boolean;
15
+ /** Whether the pattern ends with `/`, so it matches a directory alone. */
16
+ readonly directoryOnly: boolean;
17
+ /** The expression over a path below the file's own directory. */
18
+ readonly expression: RegExp;
19
+ }
20
+
21
+ /** The rules one ignore file's text writes, in order. */
22
+ function rulesOf(text: string): readonly Rule[] {
23
+ const rules: Rule[] = [];
24
+ for (const raw of text.split(/\r?\n/u)) {
25
+ let line = raw.replace(/(?<!\\)\s+$/u, "");
26
+ if (line === "" || line.startsWith("#")) {
27
+ continue;
28
+ }
29
+ const negated = line.startsWith("!");
30
+ line = negated ? line.slice(1) : line;
31
+ const directoryOnly = line.endsWith("/");
32
+ line = line.replace(/\/+$/u, "");
33
+ if (line === "") {
34
+ continue;
35
+ }
36
+ // A separator at the start or in the middle anchors the pattern to the file's
37
+ // directory; with none, the pattern matches at any depth below it.
38
+ const anchored = line.includes("/");
39
+ const pattern = line.replace(/^\//u, "");
40
+ rules.push({
41
+ negated,
42
+ directoryOnly,
43
+ expression: gitignoreExpression(anchored ? pattern : `**/${pattern}`),
44
+ });
45
+ }
46
+ return rules;
47
+ }
48
+
49
+ /** Told each ignore file that exists and cannot be read, with why. */
50
+ type UnreadIgnoreFile = (path: string, reason: string) => void;
51
+
52
+ function rulesIn(host: Host, path: string, unread: UnreadIgnoreFile): readonly Rule[] {
53
+ try {
54
+ return rulesOf(host.readFile(path));
55
+ } catch (error: unknown) {
56
+ if (host.kindOf(path) !== "absent") {
57
+ unread(path, message(error));
58
+ }
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Whether git ignores one absolute path below `root`: by the repository's `info/exclude`, then
65
+ * the `.gitignore` files from its root down to the path's directory, a later pattern overriding
66
+ * an earlier one, though a file below an ignored directory stays ignored, as git cannot re-include
67
+ * it. A path outside `root` is not ignored. With no repository, the files at and below `root` are
68
+ * read; with one, the directories above `root` give patterns but are not tested. An unreadable
69
+ * ignore file ignores nothing, and `unread` is told it.
70
+ */
71
+ export function gitIgnored(
72
+ host: Host,
73
+ root: string,
74
+ unread: UnreadIgnoreFile = () => undefined,
75
+ ): (path: string) => boolean {
76
+ const found = repositoryOf(host, root);
77
+ const repository = found?.root;
78
+ const above =
79
+ repository === undefined ? [] : [...ancestors(dirnamePath(root), repository)].reverse();
80
+ const excluded = found?.exclude === undefined ? [] : rulesIn(host, found.exclude, unread);
81
+ const read = new Map<string, readonly Rule[]>();
82
+ const rulesAt = (dir: string): readonly Rule[] => {
83
+ let rules = read.get(dir);
84
+ if (rules === undefined) {
85
+ rules = rulesIn(host, joinPath(dir, ".gitignore"), unread);
86
+ read.set(dir, rules);
87
+ }
88
+ return rules;
89
+ };
90
+ const decided = (path: string, directory: boolean): boolean => {
91
+ const sources: [string, readonly Rule[]][] = [
92
+ ...(repository === undefined ? [] : [[repository, excluded] as [string, readonly Rule[]]]),
93
+ ...[...above, ...[...ancestors(dirnamePath(path), root)].reverse()].map(
94
+ (dir): [string, readonly Rule[]] => [dir, rulesAt(dir)],
95
+ ),
96
+ ];
97
+ let ignored = false;
98
+ for (const [dir, rules] of sources) {
99
+ const below = relativePath(dir, path) ?? "";
100
+ for (const rule of rules) {
101
+ if ((directory || !rule.directoryOnly) && rule.expression.test(below)) {
102
+ ignored = !rule.negated;
103
+ }
104
+ }
105
+ }
106
+ return ignored;
107
+ };
108
+ return (path) => {
109
+ const below = relativePath(root, path);
110
+ if (below === undefined) {
111
+ return false;
112
+ }
113
+ const parts = below.split("/");
114
+ for (let count = 1; count <= parts.length; count += 1) {
115
+ const at = joinPath(root, parts.slice(0, count).join("/"));
116
+ if (decided(at, count < parts.length)) {
117
+ return true;
118
+ }
119
+ }
120
+ return false;
121
+ };
122
+ }
package/src/graph.ts CHANGED
@@ -41,9 +41,9 @@ const SWEPT: Readonly<Record<SymbolKind, boolean>> = {
41
41
  * Whether a root of each kind names a caller, which holds it live under both relations: a
42
42
  * tool reads what it loads and what its strings name, a runtime runs a test, setup or
43
43
  * worker file, a page's module script, a command, a script or a workflow step, the manifest
44
- * names what enters the package, an alias global stands for its export, and an entry file,
45
- * a configured root and a pattern assert a caller. A library's published API only supposes
46
- * an unseen consumer, so it is live under reachability alone.
44
+ * names what enters the package, an alias global stands for its export, an outside
45
+ * declaration names a global interface merging into it, and an entry file, a configured root
46
+ * and a pattern assert a caller. A published API supposes a consumer: reachability alone.
47
47
  */
48
48
  const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
49
49
  "entry-file": true,
@@ -56,13 +56,17 @@ const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
56
56
  pattern: true,
57
57
  "type-error": true,
58
58
  "type-query-alias": true,
59
+ "global-augmentation": true,
59
60
  "configuration-file": true,
60
61
  "configuration-string": true,
62
+ "bundler-entry": true,
61
63
  "test-runner": true,
62
64
  "browser-tests": true,
63
65
  worker: true,
64
66
  "html-entry": true,
65
67
  "workflow-step": true,
68
+ "computed-import": true,
69
+ "document-import": true,
66
70
  };
67
71
 
68
72
  /** Whether the sweep judges a declaration of one kind. */
@@ -112,6 +116,8 @@ export interface Graph {
112
116
  readonly test: readonly boolean[];
113
117
  /** Per declaration, whether a test-support file holds it. */
114
118
  readonly support: readonly boolean[];
119
+ /** Per declaration, whether it is ambient, describing code the target does not implement. */
120
+ readonly ambient: readonly boolean[];
115
121
  /** Per declaration, whether the sweep judges its liveness. */
116
122
  readonly subject: readonly boolean[];
117
123
  /**
@@ -145,6 +151,7 @@ export function graphOf(
145
151
  roots: readonly Root[],
146
152
  testFiles: readonly string[],
147
153
  supportFiles: readonly string[] = [],
154
+ ambient: ReadonlySet<string> = new Set(),
148
155
  ): Graph {
149
156
  const supports = new Set(supportFiles);
150
157
  const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
@@ -199,6 +206,7 @@ export function graphOf(
199
206
  parent,
200
207
  test: symbols.map((symbol) => tests.has(symbol.position.path)),
201
208
  support: symbols.map((symbol) => supports.has(symbol.position.path)),
209
+ ambient: symbols.map((symbol) => ambient.has(symbol.id)),
202
210
  subject: symbols.map((symbol) => swept(symbol.kind)),
203
211
  exportsOf,
204
212
  rooted,
package/src/host.ts CHANGED
@@ -1,5 +1,5 @@
1
- /** What one path names. */
2
- export type PathKind = "file" | "directory" | "absent";
1
+ /** What one path names: `"other"` is anything else there, a named pipe, a device or a socket. */
2
+ export type PathKind = "file" | "directory" | "other" | "absent";
3
3
 
4
4
  /** One entry of a directory. */
5
5
  export interface DirectoryEntry {
@@ -8,24 +8,27 @@ export interface DirectoryEntry {
8
8
  }
9
9
 
10
10
  /**
11
- * The platform one run reads: the filesystem, the directory relative paths
12
- * resolve against, and the version of the package this analyzer was installed
13
- * from.
14
- *
15
- * It is a parameter rather than an import because a published module that reads
16
- * the filesystem directly carries the whole platform's type surface into every
17
- * consumer's compile, and because the run's own reads are then the one thing a
18
- * caller can watch. The command line binds it to the platform it runs on; nothing
19
- * under this module does.
11
+ * The platform one run reads: the filesystem, the working directory and the analyzer's
12
+ * installed version. A run may keep what it reads for the host's life, so a caller whose
13
+ * tree changes passes a new host. It is a parameter, not an import, so a published module
14
+ * carries no platform type surface into a consumer's compile and a caller can watch the
15
+ * run's reads. The command line binds it to the platform; nothing under this module does.
20
16
  */
21
17
  export interface Host {
22
18
  /** The directory a relative path resolves against. */
23
19
  workingDirectory(): string;
24
- /** The text of one file, or a throw naming why it cannot be read. */
25
- readFile(path: string): string;
20
+ /**
21
+ * The text of one file, or a throw naming why it cannot be read. Given `limit`, the
22
+ * text of no more than the file's first `limit` bytes, so a caller bounding a document
23
+ * asks for one byte past its bound and never holds a larger file whole.
24
+ */
25
+ readFile(path: string, limit?: number): string;
26
26
  /** The entries of one directory, or a throw naming why it cannot be read. */
27
27
  readDirectory(path: string): readonly DirectoryEntry[];
28
- /** What one path names, `"absent"` where nothing is there. */
28
+ /**
29
+ * What one path names once every symbolic link along it is followed, `"absent"` where
30
+ * nothing is there, or a throw naming why it cannot be known, as for a link loop.
31
+ */
29
32
  kindOf(path: string): PathKind;
30
33
  /**
31
34
  * The path one path names once every symbolic link along it is followed, or a throw
@@ -12,8 +12,10 @@ import {
12
12
  } from "@typescript/native/ast";
13
13
  import { resolveRelativeSpecifier, type ParseFile } from "./configuration-files.ts";
14
14
  import type { Host } from "./host.ts";
15
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
15
+ import { ancestors, dirnamePath, joinPath, resolvePath } from "./paths.ts";
16
16
  import type { SourceFiles } from "./source-files.ts";
17
+ import { filesBelow } from "./fs/tree.ts";
18
+ import { holdsManifest } from "./fs/manifest.ts";
17
19
 
18
20
  /** One file a script loads, and the value or specifier that named it. */
19
21
  interface HTMLEntry {
@@ -21,9 +23,6 @@ interface HTMLEntry {
21
23
  readonly source: string;
22
24
  }
23
25
 
24
- /** The directory no HTML file is read below. */
25
- const SKIPPED = "node_modules";
26
-
27
26
  /** A `<script` start tag, its attribute text captured. */
28
27
  const SCRIPT_TAG = /<script(?=[\s>/])([^>]*)>/giu;
29
28
 
@@ -42,28 +41,6 @@ const CLASSIC_TYPES: ReadonlySet<string> = new Set(["text/javascript", "applicat
42
41
  /** The prefixes an inline script's specifier names a file of the target with. */
43
42
  const FILE_PREFIXES = ["/", "./", "../"] as const;
44
43
 
45
- /** Every HTML file below `dir`, outside every `node_modules` directory, by absolute path. */
46
- function htmlFiles(host: Host, dir: string): readonly string[] {
47
- let entries;
48
- try {
49
- entries = host.readDirectory(dir);
50
- } catch {
51
- return [];
52
- }
53
- const found: string[] = [];
54
- for (const entry of entries) {
55
- const path = joinPath(dir, entry.name);
56
- if (entry.directory) {
57
- if (entry.name !== SKIPPED) {
58
- found.push(...htmlFiles(host, path));
59
- }
60
- } else if (entry.name.endsWith(".html")) {
61
- found.push(path);
62
- }
63
- }
64
- return found.sort();
65
- }
66
-
67
44
  /** Each attribute of one start tag's attribute text, by its lowercase name. */
68
45
  function attributes(text: string): ReadonlyMap<string, string> {
69
46
  const found = new Map<string, string>();
@@ -127,12 +104,7 @@ function scriptValues(parse: ParseFile | undefined, path: string, text: string):
127
104
 
128
105
  /** The directory of the nearest `package.json` at or above `dir`, inside `root`, else `root`. */
129
106
  function manifestDirectory(host: Host, root: string, dir: string): string {
130
- for (let at = dir; relativePath(root, at) !== undefined; at = dirnamePath(at)) {
131
- if (host.kindOf(joinPath(at, "package.json")) === "file") {
132
- return at;
133
- }
134
- }
135
- return root;
107
+ return ancestors(dir, root).find((at) => holdsManifest(host, at)) ?? root;
136
108
  }
137
109
 
138
110
  /**
@@ -150,7 +122,8 @@ export function htmlEntries(
150
122
  ): readonly HTMLEntry[] {
151
123
  const root = resolvePath(host.workingDirectory(), targetRoot);
152
124
  const found: HTMLEntry[] = [];
153
- for (const path of htmlFiles(host, root)) {
125
+ for (const below of filesBelow(host, root, (name) => name.endsWith(".html"))) {
126
+ const path = joinPath(root, below);
154
127
  let text: string;
155
128
  try {
156
129
  text = host.readFile(path);