@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
@@ -6,18 +6,23 @@
6
6
  * file a tool loads needs each dependency its strings spell. A package one dependency pulls in for
7
7
  * itself is that dependency's need, not the target's.
8
8
  */
9
- import { isCallExpression, isExportDeclaration, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNamespaceExport, isPropertyAccessExpression, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/ast";
10
- import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, } from "./configuration-files.js";
9
+ import { isArrayLiteralExpression, isCallExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNamespaceExport, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, isNoSubstitutionTemplateLiteral, SyntaxKind, } from "@typescript/native/ast";
10
+ import { automaticTypes } from "./automatic-types.js";
11
+ import { configurationFiles, declaredIn as declaredBeside, dependenciesNamed, moduleStrings, outsideChain, } from "./configuration-files.js";
11
12
  import { nodeKey, packageScope } from "./inventory.js";
12
- import { scriptTokens } from "./manifest.js";
13
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
13
+ import { ancestors, dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath, } from "./paths.js";
14
14
  import { UNANSWERED } from "./query.js";
15
15
  import { sourceFilesOf } from "./source-files.js";
16
- import { workflowTokens } from "./workflow-steps.js";
17
- const MANIFEST = "package.json";
18
- const MODULES_DIR = "node_modules";
19
- const MODULES_SEGMENT = `/${MODULES_DIR}/`;
16
+ import { manifestCommands } from "./manifest.js";
17
+ import { workflowCommands, workflowTokens } from "./workflow-steps.js";
18
+ import { bytewise } from "./text/compare.js";
19
+ import { isRecord } from "./json-document.js";
20
+ import { inInstalledPackages, INSTALLED_PACKAGES, MANIFEST, readDocument } from "./fs/manifest.js";
21
+ import { commandsOf, scriptTokens } from "./text/shell.js";
22
+ const MODULES_SEGMENT = `/${INSTALLED_PACKAGES}/`;
20
23
  const TYPES_SCOPE = "@types/";
24
+ /** A script token naming a JavaScript file by a path no expansion changes. */
25
+ const SCRIPT_FILE = /^[^$`{*?[~]+\.(?:js|mjs|cjs)$/u;
21
26
  /** The manifest sections a dependency is declared in, each by the selector its reference spells. */
22
27
  const SECTIONS = [
23
28
  ["dependencies", "dependency"],
@@ -30,31 +35,12 @@ const PROJECT_OWNER = "";
30
35
  export function configurationNeeds(packages) {
31
36
  return {
32
37
  uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
33
- at: new Map([[ANYWHERE, new Set(packages)]]),
38
+ at: new Map([[ANYWHERE, new Map(packages.map((one) => [one, new Set([PROJECT_OWNER])]))]]),
34
39
  unanswered: [],
35
40
  };
36
41
  }
37
42
  /** The key of {@link ProjectNeeds.at} for a use no file of the target governs. */
38
43
  const ANYWHERE = "";
39
- function isRecord(value) {
40
- return typeof value === "object" && value !== null && !Array.isArray(value);
41
- }
42
- /** One JSON document's decoded value, or undefined where it cannot be read or parsed. */
43
- function readJSON(host, path) {
44
- let text;
45
- try {
46
- text = host.readFile(path);
47
- }
48
- catch {
49
- return undefined;
50
- }
51
- try {
52
- return { text, value: JSON.parse(text) };
53
- }
54
- catch {
55
- return undefined;
56
- }
57
- }
58
44
  /**
59
45
  * The package a bare module specifier names: its first segment, or its first two
60
46
  * where it starts with a scope. A relative, absolute or scheme-prefixed specifier and
@@ -99,12 +85,35 @@ function typePackages(name, programPackages) {
99
85
  }
100
86
  return [typed, named];
101
87
  }
88
+ /**
89
+ * The strings the `extends` member of one compiler configuration file holds, read as the
90
+ * compiler reads the file, comments and trailing commas allowed; none where it cannot be read.
91
+ */
92
+ function extendsOf(host, path, parse) {
93
+ let text;
94
+ try {
95
+ text = host.readFile(path);
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ const [statement] = parse(path, text).statements;
101
+ const document = statement !== undefined && isExpressionStatement(statement) ? statement.expression : undefined;
102
+ if (document === undefined || !isObjectLiteralExpression(document)) {
103
+ return [];
104
+ }
105
+ const value = document.properties
106
+ .filter(isPropertyAssignment)
107
+ .findLast((property) => isLiteralText(property.name) && property.name.text === "extends")?.initializer;
108
+ const entries = value !== undefined && isArrayLiteralExpression(value) ? value.elements : [value];
109
+ return entries.flatMap((entry) => entry !== undefined && isLiteralText(entry) ? [entry.text] : []);
110
+ }
102
111
  /**
103
112
  * The packages the compiler configuration of one project names: its type packages,
104
113
  * its JSX import source, the helper library it imports, its plugins, and every
105
114
  * package a configuration file it extends through is read from.
106
115
  */
107
- function configurationPackages(host, configFile, options, programPackages) {
116
+ function configurationPackages(host, configFile, options, programPackages, parse) {
108
117
  const found = new Set();
109
118
  const add = (name) => {
110
119
  const named = typeof name === "string" ? packageOfSpecifier(name) : undefined;
@@ -139,12 +148,7 @@ function configurationPackages(host, configFile, options, programPackages) {
139
148
  continue;
140
149
  }
141
150
  seen.add(file);
142
- const read = readJSON(host, file);
143
- const extended = isRecord(read?.value) ? read.value["extends"] : undefined;
144
- for (const entry of Array.isArray(extended) ? extended : [extended]) {
145
- if (typeof entry !== "string") {
146
- continue;
147
- }
151
+ for (const entry of extendsOf(host, file, parse)) {
148
152
  if (entry.startsWith(".") || isAbsolutePath(entry)) {
149
153
  const local = resolvePath(dirnamePath(file), entry);
150
154
  pending.push(local.endsWith(".json") ? local : `${local}.json`);
@@ -207,13 +211,19 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
207
211
  programPackages.add(named);
208
212
  }
209
213
  }
210
- const packages = configurationPackages(host, project.configFile, project.program.getCompilerOptions(), programPackages);
214
+ const options = project.program.getCompilerOptions();
215
+ const packages = configurationPackages(host, project.configFile, options, programPackages, parse);
216
+ const automatic = automaticTypes(host, project.configFile, options, parse);
211
217
  const uses = new Map();
212
218
  const at = new Map();
213
219
  const use = (owners, named, path) => {
214
220
  for (const one of named) {
215
- at.set(path, (at.get(path) ?? new Set()).add(one));
221
+ const inFile = at.get(path) ?? new Map();
222
+ at.set(path, inFile);
223
+ const holders = inFile.get(one) ?? new Set();
224
+ inFile.set(one, holders);
216
225
  for (const owner of owners) {
226
+ holders.add(owner);
217
227
  let set = uses.get(owner);
218
228
  if (set === undefined) {
219
229
  set = new Set();
@@ -229,7 +239,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
229
239
  const inFile = (owners, named) => {
230
240
  use(owners, named, file.fileName);
231
241
  };
232
- if (fileNeeds(project, held, file, programPackages, inFile)) {
242
+ if (fileNeeds(project, held, file, { programPackages, automatic }, inFile)) {
233
243
  unanswered.push(file.fileName);
234
244
  }
235
245
  }
@@ -243,7 +253,8 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
243
253
  }
244
254
  return names;
245
255
  };
246
- const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot), parse);
256
+ const files = sourceFilesOf(project, targetRoot);
257
+ const configuration = configurationFiles(host, targetRoot, files, parse);
247
258
  for (const { file, manifestDir } of configuration.modules) {
248
259
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
249
260
  for (const { node, text } of moduleStrings(file)) {
@@ -252,15 +263,16 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
252
263
  }
253
264
  for (const { strings, manifestDirs } of configuration.documents) {
254
265
  for (const manifestDir of manifestDirs) {
255
- const names = declaredAt(joinPath(manifestDir, "package.json"));
266
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
256
267
  for (const text of strings) {
257
268
  use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
258
269
  }
259
270
  }
260
271
  }
261
- for (const { file, manifestDirs } of configuration.outside) {
272
+ const ranFiles = outsideChain(host, targetRoot, files, parse, scriptRunFiles(host, targetRoot, files));
273
+ for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
262
274
  for (const manifestDir of manifestDirs) {
263
- const names = declaredAt(joinPath(manifestDir, "package.json"));
275
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
264
276
  for (const { text } of moduleStrings(file)) {
265
277
  use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
266
278
  }
@@ -268,6 +280,49 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
268
280
  }
269
281
  return { uses, at, unanswered };
270
282
  }
283
+ /**
284
+ * The JavaScript files no program holds that a script of a manifest at or above one of the
285
+ * project's files runs, each with the directory of that manifest, and those a workflow
286
+ * step or git hook runs, with the target root's: a token naming such a file below the
287
+ * target root and in no `node_modules`, read against the manifest's directory.
288
+ */
289
+ function scriptRunFiles(host, targetRoot, files) {
290
+ const root = resolvePath(host.workingDirectory(), targetRoot);
291
+ const dirs = new Set([root]);
292
+ for (const name of files.byName.keys()) {
293
+ for (const dir of ancestors(dirnamePath(name), root)) {
294
+ if (dirs.has(dir)) {
295
+ break;
296
+ }
297
+ dirs.add(dir);
298
+ }
299
+ }
300
+ const tokens = new Map([
301
+ [root, new Set(workflowTokens(host, root))],
302
+ ]);
303
+ for (const dir of dirs) {
304
+ const read = readDocument(host, joinPath(dir, MANIFEST));
305
+ if (read !== undefined && isRecord(read.value)) {
306
+ const own = scriptTokensOf(read.value);
307
+ tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
308
+ }
309
+ }
310
+ const found = [];
311
+ for (const [manifestDir, held] of tokens) {
312
+ for (const token of held) {
313
+ const path = SCRIPT_FILE.test(token) ? resolvePath(manifestDir, token) : undefined;
314
+ const below = path === undefined ? undefined : relativePath(root, path);
315
+ if (path !== undefined &&
316
+ below !== undefined &&
317
+ !inInstalledPackages(below) &&
318
+ !files.byName.has(path) &&
319
+ host.kindOf(path) === "file") {
320
+ found.push({ path, manifestDir });
321
+ }
322
+ }
323
+ }
324
+ return found;
325
+ }
271
326
  /** The declarations of the inventory a use written at one node belongs to. */
272
327
  function ownersOf(file, held, node) {
273
328
  const parent = node.parent;
@@ -291,8 +346,27 @@ function ownersOf(file, held, node) {
291
346
  }
292
347
  return [PROJECT_OWNER];
293
348
  }
294
- /** Records the needs of one file, and answers whether a specifier of it went unanswered. */
295
- function fileNeeds(project, held, file, programPackages, use) {
349
+ /**
350
+ * Whether one identifier stands for a value or type of the scope it is written in, rather
351
+ * than naming a member, a property or a label.
352
+ */
353
+ function isScopeName(node) {
354
+ const parent = node.parent;
355
+ if (parent === undefined || isShorthandPropertyAssignment(parent)) {
356
+ return true;
357
+ }
358
+ const named = parent;
359
+ return (named.name !== node &&
360
+ (parent.kind !== SyntaxKind.QualifiedName || named.right !== node) &&
361
+ named.propertyName !== node &&
362
+ named.label !== node);
363
+ }
364
+ /**
365
+ * Records the needs of one file, and answers whether a specifier of it went unanswered. A
366
+ * module specifier or a name of the file's scope that resolves to nothing, and that an
367
+ * automatically included type package declares, uses that package.
368
+ */
369
+ function fileNeeds(project, held, file, { programPackages, automatic }, use) {
296
370
  const specifiers = [...file.imports, ...file.moduleAugmentations].filter(isLiteralText);
297
371
  const bindings = [];
298
372
  const bound = new Set();
@@ -309,16 +383,22 @@ function fileNeeds(project, held, file, programPackages, use) {
309
383
  }
310
384
  const names = new Set(bindings.map(({ node }) => node.text));
311
385
  const candidates = [];
386
+ const globals = [];
312
387
  const visit = (node) => {
313
- if (isIdentifier(node) && names.has(node.text) && !bound.has(node)) {
314
- candidates.push(node);
388
+ if (isIdentifier(node) && !bound.has(node)) {
389
+ if (names.has(node.text)) {
390
+ candidates.push(node);
391
+ }
392
+ else if (automatic.globals.has(node.text) && isScopeName(node)) {
393
+ globals.push(node);
394
+ }
315
395
  }
316
396
  node.forEachChild(visit);
317
397
  };
318
- if (names.size > 0) {
398
+ if (names.size > 0 || automatic.globals.size > 0) {
319
399
  visit(file);
320
400
  }
321
- const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates];
401
+ const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates, ...globals];
322
402
  const handles = asked.map((node) => project.handle(node));
323
403
  const resolved = project.symbolsAt(handles);
324
404
  const packagesOf = new Map();
@@ -329,6 +409,9 @@ function fileNeeds(project, held, file, programPackages, use) {
329
409
  named.add(byName);
330
410
  }
331
411
  const symbol = resolved[index];
412
+ if (symbol === undefined) {
413
+ automatic.modules.get(specifier.text)?.forEach((one) => named.add(one));
414
+ }
332
415
  for (const declaration of typeof symbol === "object" ? symbol.declarations : []) {
333
416
  const byPath = packageOfPath(declaration.path);
334
417
  if (byPath !== undefined) {
@@ -364,7 +447,7 @@ function fileNeeds(project, held, file, programPackages, use) {
364
447
  let symbol = resolved[offset + index];
365
448
  const parent = node.parent;
366
449
  if (parent !== undefined && isShorthandPropertyAssignment(parent) && parent.name === node) {
367
- symbol = project.shorthandValueAt(project.handle(node));
450
+ symbol = project.shorthandValueAt(project.handle(parent));
368
451
  }
369
452
  const named = symbol === UNANSWERED || (unresolvedNames.has(node.text) && symbol === undefined)
370
453
  ? byName.get(node.text)
@@ -376,6 +459,13 @@ function fileNeeds(project, held, file, programPackages, use) {
376
459
  use(ownersOf(file, held, node), named);
377
460
  }
378
461
  });
462
+ const unresolvedAt = offset + candidates.length;
463
+ globals.forEach((node, index) => {
464
+ const symbol = resolved[unresolvedAt + index];
465
+ if (symbol === undefined || symbol === UNANSWERED) {
466
+ use(ownersOf(file, held, node), automatic.globals.get(node.text) ?? []);
467
+ }
468
+ });
379
469
  for (const reference of file.typeReferenceDirectives) {
380
470
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
381
471
  }
@@ -494,8 +584,8 @@ function lineAndColumn(text, offset) {
494
584
  }
495
585
  /** The installed copy of one dependency, read where the package manager puts it for the target. */
496
586
  function installedOf(host, root, name) {
497
- for (let dir = root;; dir = dirnamePath(dir)) {
498
- const read = readJSON(host, joinPath(dir, MODULES_DIR, name, MANIFEST));
587
+ for (const dir of ancestors(root)) {
588
+ const read = readDocument(host, joinPath(dir, INSTALLED_PACKAGES, name, MANIFEST));
499
589
  if (read !== undefined && isRecord(read.value)) {
500
590
  const bin = read.value["bin"];
501
591
  const peers = read.value["peerDependencies"];
@@ -511,10 +601,8 @@ function installedOf(host, root, name) {
511
601
  : [],
512
602
  };
513
603
  }
514
- if (dirnamePath(dir) === dir) {
515
- return undefined;
516
- }
517
604
  }
605
+ return undefined;
518
606
  }
519
607
  /** Every dependency the manifest text at `path` declares, in the order the text writes them. */
520
608
  function declaredIn(text, manifest, path, scope) {
@@ -558,6 +646,82 @@ function scriptTokensOf(manifest) {
558
646
  }
559
647
  return found;
560
648
  }
649
+ /**
650
+ * The runtime flags whose value is a module loaded before the entry, as `--flag value` or
651
+ * `--flag=value`: https://nodejs.org/api/cli.html#--importmodule,
652
+ * https://nodejs.org/api/cli.html#--experimental-loadermodule,
653
+ * https://nodejs.org/api/cli.html#-r---require-module
654
+ */
655
+ const PRELOAD_FLAGS = new Set([
656
+ "--experimental-loader",
657
+ "--import",
658
+ "--loader",
659
+ "--require",
660
+ "-r",
661
+ ]);
662
+ /**
663
+ * The programs that pass a preload flag to the runtime they start: Node itself, and the
664
+ * runners that forward Node's flags or read the same `--require`:
665
+ * https://mochajs.org/#-require-module-r-module, https://tsx.is/node-enhancement,
666
+ * https://typestrong.org/ts-node/docs/options#require,
667
+ * https://github.com/remy/nodemon#running-non-node-scripts
668
+ */
669
+ const PRELOADING_PROGRAMS = new Set([
670
+ "mocha",
671
+ "node",
672
+ "nodemon",
673
+ "ts-node",
674
+ "tsx",
675
+ ]);
676
+ /** The programs that run the command after them, and an environment assignment before it. */
677
+ const LAUNCHERS = new Set(["cross-env", "env", "npx"]);
678
+ const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
679
+ /**
680
+ * The packages `scripts` preload by a runtime flag's bare module specifier, a flag
681
+ * counting only in a command whose program passes it to the runtime, and for Node only
682
+ * before the script it runs, whose own arguments come after it.
683
+ */
684
+ function preloadsOf(scripts) {
685
+ const found = new Set();
686
+ for (const script of scripts) {
687
+ for (const command of commandsOf(script)) {
688
+ const tokens = scriptTokens(command);
689
+ let at = 0;
690
+ while (at < tokens.length &&
691
+ (ASSIGNMENT.test(tokens[at] ?? "") || LAUNCHERS.has(tokens[at] ?? ""))) {
692
+ at += 1;
693
+ }
694
+ const path = tokens[at] ?? "";
695
+ const program = path.slice(path.lastIndexOf("/") + 1);
696
+ if (!PRELOADING_PROGRAMS.has(program)) {
697
+ continue;
698
+ }
699
+ for (at += 1; at < tokens.length; at += 1) {
700
+ const token = tokens[at] ?? "";
701
+ if (!token.startsWith("-")) {
702
+ if (program === "node") {
703
+ break;
704
+ }
705
+ continue;
706
+ }
707
+ const equals = token.indexOf("=");
708
+ const flag = equals < 0 ? token : token.slice(0, equals);
709
+ if (!PRELOAD_FLAGS.has(flag)) {
710
+ continue;
711
+ }
712
+ if (equals < 0) {
713
+ at += 1;
714
+ }
715
+ const value = equals < 0 ? tokens[at] : token.slice(equals + 1);
716
+ const name = value === undefined ? undefined : packageOfSpecifier(value);
717
+ if (name !== undefined) {
718
+ found.add(name);
719
+ }
720
+ }
721
+ }
722
+ }
723
+ return found;
724
+ }
561
725
  /** Whether a path below the target root lies in the directory `dir` below it, `""` being the root. */
562
726
  function isInside(path, dir) {
563
727
  return dir === "" || path.startsWith(`${dir}/`);
@@ -567,6 +731,18 @@ function manifestDir(dependency) {
567
731
  const dir = dirnamePath(dependency.position.path);
568
732
  return dir === "." ? "" : dir;
569
733
  }
734
+ /**
735
+ * The declarations of one name a use written at `below` needs: those of the nearest
736
+ * manifest at or above it that declares the name, or every one for a use no file holds.
737
+ */
738
+ function nearestDeclaring(declarations, below) {
739
+ if (below === undefined) {
740
+ return declarations;
741
+ }
742
+ const enclosing = declarations.filter((dependency) => isInside(below, manifestDir(dependency)));
743
+ const nearest = Math.max(-1, ...enclosing.map((dependency) => manifestDir(dependency).length));
744
+ return enclosing.filter((dependency) => manifestDir(dependency).length === nearest);
745
+ }
570
746
  /**
571
747
  * Adds to `needed` each declared dependency that the installed manifest of a needed or
572
748
  * run declared dependency names as a peer, until no further one joins: the declaration of
@@ -617,22 +793,27 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
617
793
  }
618
794
  }
619
795
  const workflow = new Set(workflowTokens(host, targetRoot));
796
+ const workflowPreloads = preloadsOf(workflowCommands(host, targetRoot));
620
797
  const declared = [];
621
798
  const ran = new Set();
622
799
  const peers = new Map();
623
800
  const uninstalledHere = new Set(nested);
624
801
  for (const [dir, path] of manifests) {
625
- const read = readJSON(host, joinPath(dir, MANIFEST));
802
+ const read = readDocument(host, joinPath(dir, MANIFEST));
626
803
  if (read === undefined || !isRecord(read.value)) {
627
804
  continue;
628
805
  }
629
806
  const tokens = scriptTokensOf(read.value);
807
+ const preloads = preloadsOf(manifestCommands(host, dir, read.value).map(({ command }) => command));
630
808
  for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
631
809
  const found = installedOf(host, dir, dependency.name);
632
810
  if (found === undefined && uninstalledHere.has(dir)) {
633
811
  continue;
634
812
  }
635
813
  declared.push(dependency);
814
+ if (preloads.has(dependency.name) || workflowPreloads.has(dependency.name)) {
815
+ ran.add(declarationKey(dependency));
816
+ }
636
817
  if (found === undefined) {
637
818
  continue;
638
819
  }
@@ -646,6 +827,8 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
646
827
  // A declaration several projects compile is one user, and a use any project makes is
647
828
  // a use, so the uses are merged by declaration before anything is counted.
648
829
  const needed = new Set();
830
+ const live = new Set();
831
+ const holders = new Map();
649
832
  const byName = new Map();
650
833
  for (const dependency of declared) {
651
834
  byName.set(dependency.name, [...(byName.get(dependency.name) ?? []), dependency]);
@@ -656,7 +839,10 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
656
839
  // or above its file declares, and what the configuration needs is never deleted.
657
840
  const unanswered = project.unanswered.flatMap((file) => relativePath(root, file) ?? []);
658
841
  const unknownDeclared = declared.filter((dependency) => unanswered.some((file) => isInside(file, manifestDir(dependency))));
659
- unknownDeclared.forEach((dependency) => needed.add(declarationKey(dependency)));
842
+ for (const dependency of unknownDeclared) {
843
+ needed.add(declarationKey(dependency));
844
+ live.add(declarationKey(dependency));
845
+ }
660
846
  const unknown = unknownDeclared.map((dependency) => dependency.name);
661
847
  const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)]];
662
848
  for (const [id, used] of [...project.uses, ...held]) {
@@ -666,16 +852,32 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
666
852
  }
667
853
  for (const [path, named] of project.at) {
668
854
  const below = path === ANYWHERE ? undefined : relativePath(root, path);
669
- for (const one of named) {
670
- for (const dependency of byName.get(one) ?? []) {
671
- if (below === undefined || isInside(below, manifestDir(dependency))) {
672
- needed.add(declarationKey(dependency));
855
+ for (const [one, owners] of named) {
856
+ // An import none of whose bindings is used is no evidence about the code using it.
857
+ const unheld = owners.size === 0;
858
+ for (const dependency of nearestDeclaring(byName.get(one) ?? [], below)) {
859
+ const key = declarationKey(dependency);
860
+ needed.add(key);
861
+ if (unheld) {
862
+ live.add(key);
863
+ }
864
+ else {
865
+ const held = holders.get(key) ?? new Set();
866
+ owners.forEach((owner) => held.add(owner));
867
+ holders.set(key, held);
673
868
  }
674
869
  }
675
870
  }
676
871
  }
677
872
  }
678
873
  addNeededPeers(declared, peers, needed, ran);
874
+ addNeededPeers(declared, peers, live, ran);
875
+ const heldBy = new Map();
876
+ for (const [key, held] of holders) {
877
+ if (!live.has(key) && !ran.has(key)) {
878
+ heldBy.set(key, [...held].sort(bytewise));
879
+ }
880
+ }
679
881
  const users = new Map();
680
882
  for (const used of uses.values()) {
681
883
  used.forEach((one) => users.set(one, (users.get(one) ?? 0) + 1));
@@ -685,8 +887,24 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
685
887
  for (const id of candidates) {
686
888
  const sole = [...(uses.get(id) ?? [])].filter((one) => users.get(one) === 1 && names.has(one));
687
889
  if (sole.length > 0) {
688
- lastUses.set(id, sole.sort((a, b) => (a < b ? -1 : 1)));
890
+ lastUses.set(id, sole.sort(bytewise));
891
+ }
892
+ }
893
+ const owners = new Map();
894
+ for (const [owner, used] of uses) {
895
+ for (const one of used) {
896
+ if (names.has(one)) {
897
+ owners.set(one, (owners.get(one) ?? new Set()).add(owner));
898
+ }
899
+ }
900
+ }
901
+ const fileLastUses = new Map();
902
+ for (const [one, held] of [...owners].sort(([a], [b]) => bytewise(a, b))) {
903
+ const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
904
+ const [path] = paths;
905
+ if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
906
+ fileLastUses.set(path, [...(fileLastUses.get(path) ?? []), one]);
689
907
  }
690
908
  }
691
- return { declared, needed, ran, unbuilt, lastUses };
909
+ return { declared, needed, ran, unbuilt, lastUses, fileLastUses, heldBy };
692
910
  }