@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16

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 (149) hide show
  1. package/dist/src/alias-chain.js +2 -2
  2. package/dist/src/analysis.js +43 -38
  3. package/dist/src/commands/read.js +6 -7
  4. package/dist/src/conformance-record.js +1 -1
  5. package/dist/src/consumers.js +4 -2
  6. package/dist/src/contract/config.generated.js +1 -1
  7. package/dist/src/contract/contract.generated.js +120 -2
  8. package/dist/src/dependencies.js +81 -49
  9. package/dist/src/entries/build-scripts.js +222 -0
  10. package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
  11. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  12. package/dist/src/entries/conventions.js +739 -0
  13. package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
  14. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
  15. package/dist/src/entries/run-directories.js +77 -0
  16. package/dist/src/entries/sources.js +144 -0
  17. package/dist/src/entries/static-module.js +669 -0
  18. package/dist/src/entries/test-runners.js +684 -0
  19. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  20. package/dist/src/extensions.js +37 -0
  21. package/dist/src/facts.js +38 -0
  22. package/dist/src/file-facts.js +1 -1
  23. package/dist/src/findings/emitters.js +5 -8
  24. package/dist/src/findings/self-check.js +36 -20
  25. package/dist/src/findings-pass.js +5 -6
  26. package/dist/src/framework-lifecycle.js +1 -1
  27. package/dist/src/fs/manifest.js +15 -20
  28. package/dist/src/generated-file.js +172 -29
  29. package/dist/src/graph.js +2 -5
  30. package/dist/src/index.js +1 -1
  31. package/dist/src/injection-container.js +1 -1
  32. package/dist/src/inventory/commonjs.js +507 -0
  33. package/dist/src/inventory/enumerate.js +167 -0
  34. package/dist/src/inventory/syntax.js +236 -0
  35. package/dist/src/inventory.js +305 -705
  36. package/dist/src/javascript-file.js +26 -11
  37. package/dist/src/load/classify.js +28 -9
  38. package/dist/src/load/diagnostics.js +8 -1
  39. package/dist/src/load/discover.js +16 -8
  40. package/dist/src/load/package-scope.js +56 -0
  41. package/dist/src/load/plan.js +2 -2
  42. package/dist/src/load/synthesize.js +5 -5
  43. package/dist/src/load/workspace-manifests.js +1 -1
  44. package/dist/src/matrix.js +6 -13
  45. package/dist/src/outside-tests.js +1 -1
  46. package/dist/src/paths.js +16 -0
  47. package/dist/src/platform-calls.js +78 -0
  48. package/dist/src/platform-symbols.js +140 -0
  49. package/dist/src/position.js +41 -0
  50. package/dist/src/ref.js +11 -20
  51. package/dist/src/references.js +281 -50
  52. package/dist/src/resolve.js +1 -1
  53. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
  54. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  55. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  56. package/dist/src/roots/published-reach.js +412 -0
  57. package/dist/src/roots/root-set.js +489 -0
  58. package/dist/src/roots/unseen-loads.js +208 -0
  59. package/dist/src/serialization-contract.js +1 -1
  60. package/dist/src/session.js +3 -0
  61. package/dist/src/source-files.js +5 -9
  62. package/dist/src/suppress-inline.js +33 -81
  63. package/dist/src/suppress.js +30 -32
  64. package/dist/src/test-support.js +4 -2
  65. package/dist/src/verbs/analyze.js +13 -1
  66. package/dist/src/verbs/describe.js +5 -3
  67. package/jsr.json +1 -1
  68. package/package.json +3 -3
  69. package/src/alias-chain.ts +2 -2
  70. package/src/analysis.ts +77 -53
  71. package/src/automatic-types.ts +1 -1
  72. package/src/commands/read.ts +6 -8
  73. package/src/conformance-record.ts +1 -1
  74. package/src/consumers.ts +5 -1
  75. package/src/contract/config.generated.ts +1 -1
  76. package/src/contract/contract.generated.ts +139 -2
  77. package/src/dependencies.ts +98 -50
  78. package/src/entries/build-scripts.ts +292 -0
  79. package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
  80. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  81. package/src/{conventions.ts → entries/conventions.ts} +347 -522
  82. package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
  83. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
  84. package/src/entries/run-directories.ts +108 -0
  85. package/src/entries/sources.ts +266 -0
  86. package/src/entries/static-module.ts +857 -0
  87. package/src/entries/test-runners.ts +868 -0
  88. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  89. package/src/exempt.ts +3 -2
  90. package/src/extensions.ts +45 -0
  91. package/src/facts.ts +44 -0
  92. package/src/file-facts.ts +2 -2
  93. package/src/findings/emitters.ts +14 -10
  94. package/src/findings/self-check.ts +41 -22
  95. package/src/findings/visibility-narrowing.ts +1 -1
  96. package/src/findings-pass.ts +5 -12
  97. package/src/framework-lifecycle.ts +1 -1
  98. package/src/fs/manifest.ts +25 -21
  99. package/src/generated-file.ts +240 -36
  100. package/src/graph.ts +3 -6
  101. package/src/index.ts +1 -1
  102. package/src/injection-container.ts +1 -1
  103. package/src/inventory/commonjs.ts +696 -0
  104. package/src/inventory/enumerate.ts +203 -0
  105. package/src/inventory/syntax.ts +311 -0
  106. package/src/inventory.ts +424 -878
  107. package/src/javascript-file.ts +29 -12
  108. package/src/load/classify.ts +49 -13
  109. package/src/load/diagnostics.ts +11 -1
  110. package/src/load/discover.ts +28 -12
  111. package/src/load/package-scope.ts +74 -0
  112. package/src/load/plan.ts +11 -2
  113. package/src/load/synthesize.ts +6 -6
  114. package/src/load/test-globals.ts +1 -1
  115. package/src/load/workspace-manifests.ts +1 -1
  116. package/src/load/workspace-run.ts +1 -1
  117. package/src/matrix.ts +7 -14
  118. package/src/outside-tests.ts +1 -1
  119. package/src/paths.ts +18 -0
  120. package/src/platform-calls.ts +119 -0
  121. package/src/platform-symbols.ts +205 -0
  122. package/src/position.ts +42 -0
  123. package/src/ref.ts +11 -20
  124. package/src/references.ts +383 -73
  125. package/src/report.ts +1 -1
  126. package/src/resolve.ts +1 -1
  127. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
  128. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  129. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  130. package/src/roots/published-reach.ts +481 -0
  131. package/src/{roots.ts → roots/root-set.ts} +203 -630
  132. package/src/roots/unseen-loads.ts +307 -0
  133. package/src/serialization-contract.ts +1 -1
  134. package/src/session.ts +17 -1
  135. package/src/source-files.ts +5 -10
  136. package/src/suppress-inline.ts +53 -89
  137. package/src/suppress.ts +48 -33
  138. package/src/test-support.ts +8 -3
  139. package/src/verbs/analyze.ts +17 -4
  140. package/src/verbs/describe.ts +5 -2
  141. package/src/verbs/explain.ts +1 -1
  142. package/dist/src/build-scripts.js +0 -418
  143. package/dist/src/conventions.js +0 -907
  144. package/dist/src/entry-points.js +0 -513
  145. package/dist/src/roots.js +0 -865
  146. package/dist/src/unseen-loads.js +0 -100
  147. package/src/build-scripts.ts +0 -536
  148. package/src/entry-points.ts +0 -661
  149. package/src/unseen-loads.ts +0 -134
@@ -1,26 +1,41 @@
1
+ import { LANGUAGE_ROWS } from "./contract/contract.generated.js";
2
+ import { globExpression } from "./text/glob.js";
1
3
  export const JAVASCRIPT_FILE = /\.(?:js|jsx|mjs|cjs)$/u;
2
- /** A minified file by its name; any other is one by its size and its mean line length. */
3
- const MINIFIED_NAME = /\.min\.(?:js|mjs|cjs)$/u;
4
+ /** The patterns the TypeScript row of `languages.json` skips files by, each matched against a name alone. */
5
+ const MINIFIED_NAMES = LANGUAGE_ROWS.ts.skippedFiles.map((pattern) => ({
6
+ pattern,
7
+ expression: globExpression(pattern),
8
+ }));
4
9
  const MINIFIED_BYTES = 10_240;
5
10
  const MINIFIED_LINE = 500;
6
- /** Whether one name is a minified file's, which decides it without its text. */
11
+ /** What a minified file is decided by where its text is not read. */
12
+ const LONG_LINES = "long lines";
13
+ /** The skipped-file pattern one file's name matches, which decides it minified without its text. */
7
14
  export function minifiedByName(fileName) {
8
- return MINIFIED_NAME.test(fileName);
15
+ const name = fileName.slice(fileName.lastIndexOf("/") + 1);
16
+ return MINIFIED_NAMES.find((one) => one.expression.test(name))?.pattern;
9
17
  }
10
- /** Whether one JavaScript file is minified: by its name, or by its text's size and mean line length. */
11
- export function minified(fileName, text) {
18
+ /**
19
+ * What decides one JavaScript file minified: the skipped-file pattern its name matches, or
20
+ * {@link LONG_LINES} for a text of the size whose mean line is longer than the limit;
21
+ * undefined for a file that is not minified.
22
+ */
23
+ export function minifiedBy(fileName, text) {
12
24
  if (!JAVASCRIPT_FILE.test(fileName)) {
13
- return false;
25
+ return undefined;
14
26
  }
15
- if (minifiedByName(fileName)) {
16
- return true;
27
+ const named = minifiedByName(fileName);
28
+ if (named !== undefined) {
29
+ return named;
17
30
  }
18
31
  // A UTF-16 unit encodes to at most three UTF-8 bytes, so a shorter text is below the size.
19
32
  if (text.length * 3 < MINIFIED_BYTES) {
20
- return false;
33
+ return undefined;
21
34
  }
22
35
  const lines = text.split(/\r?\n/u).length;
23
- return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE;
36
+ return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE
37
+ ? LONG_LINES
38
+ : undefined;
24
39
  }
25
40
  /** The length of a text in UTF-8, a lone surrogate counting as the three bytes of U+FFFD. */
26
41
  function utf8Bytes(text) {
@@ -1,9 +1,9 @@
1
- import { conventionFailuresFor } from "../conventions.js";
1
+ import { conventionFailuresFor } from "../entries/conventions.js";
2
2
  import { relativePath, resolvePath } from "../paths.js";
3
3
  import { diagnosticsOf, ownedAs } from "../session.js";
4
4
  import { skipAt } from "../type-errors.js";
5
5
  import { classOf, MISSING_GLOBAL_TYPE, projectErrors, renderDiagnostic, } from "./diagnostics.js";
6
- import { targetRelative } from "./discover.js";
6
+ import { targetRelative, unreadableDetail, } from "./discover.js";
7
7
  import { missingModule, pathsOf } from "./expected-module.js";
8
8
  import { FailureExit, failureLine } from "../failure.js";
9
9
  import { JAVASCRIPT_FILE } from "../javascript-file.js";
@@ -80,7 +80,7 @@ function classifyTarget(engine, host, run, plan, view, unfound) {
80
80
  const raw = projectErrors(diagnosticsOf(project));
81
81
  const load = nonEmpty(raw.load);
82
82
  if (load !== undefined) {
83
- return { kind: "load", errors: load };
83
+ return { kind: "load", errors: load, unreadable: raw.unreadable };
84
84
  }
85
85
  const reading = readErrors(project, plan, ownErrors(raw.check, referenceOnly), {
86
86
  host,
@@ -134,7 +134,7 @@ function classifyConsumer(engine, host, run, plan, consumer, project) {
134
134
  const raw = projectErrors(diagnosticsOf(project));
135
135
  const load = nonEmpty(raw.load);
136
136
  if (load !== undefined) {
137
- return { kind: "load", errors: load };
137
+ return { kind: "load", errors: load, unreadable: raw.unreadable };
138
138
  }
139
139
  let faulted;
140
140
  const reading = readErrors(project, plan, raw.check, {
@@ -173,7 +173,7 @@ function ending(plan, failed, root) {
173
173
  return plan.consumer === undefined
174
174
  ? [
175
175
  {
176
- failureClass: "no-program",
176
+ failureClass: failed.kind === "load" && failed.unreadable ? "unreadable-configuration" : "no-program",
177
177
  detail: `${what}: correct the configuration, or name the configurations to analyze`,
178
178
  },
179
179
  ]
@@ -184,6 +184,22 @@ function ending(plan, failed, root) {
184
184
  },
185
185
  ];
186
186
  }
187
+ /** The setup failures a dropped derived configuration counts as, where it counts as any. */
188
+ function droppedFailures(failed, plan, root) {
189
+ if (failed.kind === "setup") {
190
+ return { failures: failed.failures };
191
+ }
192
+ return failed.kind === "load" && failed.unreadable
193
+ ? {
194
+ failures: [
195
+ {
196
+ failureClass: "unreadable-configuration",
197
+ detail: unreadableDetail(plan.id, `cannot be read as a compiler configuration: ${droppedBy(failed, root)}`),
198
+ },
199
+ ],
200
+ }
201
+ : {};
202
+ }
187
203
  /** The first line of the error that drops one derived configuration. */
188
204
  function droppedBy(failed, root) {
189
205
  return failed.kind === "load"
@@ -206,7 +222,6 @@ export function classify(engine, host, run, views) {
206
222
  const analyzed = [];
207
223
  const consumed = [];
208
224
  const ended = new Map();
209
- const dropped = [];
210
225
  const unopened = run.plans.filter((plan) => !views.has(plan.configFile));
211
226
  const outcomes = [
212
227
  ...run.plans.flatMap((plan) => {
@@ -237,8 +252,12 @@ export function classify(engine, host, run, views) {
237
252
  ended.set(outcome.kind, [...(ended.get(outcome.kind) ?? []), { plan, failed: outcome }]);
238
253
  }
239
254
  else {
240
- dropped.push(outcome);
241
- notBuilt.push({ id: plan.id, configFile: plan.configFile, error: droppedBy(outcome, root) });
255
+ notBuilt.push({
256
+ id: plan.id,
257
+ configFile: plan.configFile,
258
+ error: droppedBy(outcome, root),
259
+ ...droppedFailures(outcome, plan, root),
260
+ });
242
261
  }
243
262
  }
244
263
  for (const kind of ["unopened", "setup", "load"]) {
@@ -248,7 +267,7 @@ export function classify(engine, host, run, views) {
248
267
  throw new FailureExit(kind === "unopened" ? [failures[0]] : failures, all.flatMap(({ failed }) => (failed.kind === "load" ? failed.errors : [])));
249
268
  }
250
269
  }
251
- const setupDropped = nonEmpty(dropped.flatMap((one) => (one.kind === "setup" ? one.failures : [])));
270
+ const setupDropped = nonEmpty(notBuilt.flatMap((one) => one.failures ?? []));
252
271
  const [first] = notBuilt;
253
272
  if (analyzed.length === 0 && first !== undefined) {
254
273
  throw new FailureExit(setupDropped ?? [
@@ -68,12 +68,19 @@ function locatedIn(diagnostics, locate) {
68
68
  export function diagnosticErrors(sets) {
69
69
  return [...sets.configParsing, ...sets.syntactic, ...sets.semantic].filter(isError);
70
70
  }
71
+ /**
72
+ * The code of a configuration whose only parse error is that its inputs name no file: it reads
73
+ * as written and holds no program.
74
+ */
75
+ const NO_INPUTS = 18003;
71
76
  /** The errors one open project carries, each located in the file it names. */
72
77
  export function projectErrors(sets) {
73
78
  const unplaced = (kind) => sets.unplaced.filter((diagnostic) => classOf(diagnostic, false) === kind);
74
- const load = [...sets.configParsing.filter(isError), ...unplaced("load")];
79
+ const parsing = sets.configParsing.filter(isError);
80
+ const load = [...parsing, ...unplaced("load")];
75
81
  return {
76
82
  load: locatedIn(load, sets.locate),
83
+ unreadable: parsing.some(({ code }) => code !== NO_INPUTS),
77
84
  check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
78
85
  unfound: load.length > 0 ? [] : locatedIn(unplaced("unfound-library"), sets.locate),
79
86
  };
@@ -1,6 +1,6 @@
1
1
  import { LANGUAGE_ROWS } from "../contract/contract.generated.js";
2
2
  import { filesBelow, filesIn } from "../fs/tree.js";
3
- import { packageScope } from "../inventory.js";
3
+ import { packageScope } from "./package-scope.js";
4
4
  import { dirnamePath, joinPath, relativePath, resolvePath } from "../paths.js";
5
5
  import { LANGUAGE, message } from "../product.js";
6
6
  import { globExpression } from "../text/glob.js";
@@ -66,11 +66,9 @@ function readConfiguration(engine, configFile) {
66
66
  try {
67
67
  const parsed = engine.parseConfigFile(configFile);
68
68
  const circular = parsed.errors.find(({ code }) => code === CIRCULAR_EXTENDS);
69
- // The compiler leaves the message's `{0}` unfilled:
70
- // https://github.com/microsoft/TypeScript/pull/64755
71
69
  return circular === undefined
72
70
  ? { parsed }
73
- : { reason: `TS${String(circular.code)}: ${circular.text.replace("{0}", () => configFile)}` };
71
+ : { reason: `TS${String(circular.code)}: ${circular.text}` };
74
72
  }
75
73
  catch (error) {
76
74
  return { reason: message(error) };
@@ -137,8 +135,12 @@ export function targetWithoutSources(target) {
137
135
  return failureExit("no-program", `${target} holds no compiler configuration, no file named ${MARKERS.join(", ")}, and no JavaScript file: run from the directory that holds one, or name the languages to analyze`);
138
136
  }
139
137
  /** A target configuration a matrix or the invocation names that cannot be built, ending the run. */
140
- function unbuilt(named, reason) {
141
- throw failureExit("no-program", `${named} ${reason}: correct the configuration, or name the configurations to analyze`);
138
+ function unbuilt(named, reason, failureClass = "no-program") {
139
+ throw failureExit(failureClass, unreadableDetail(named, reason));
140
+ }
141
+ /** What fails and the fix, for a configuration `named` that `reason` keeps from being built. */
142
+ export function unreadableDetail(named, reason) {
143
+ return `${named} ${reason}: correct the configuration, or name the configurations to analyze`;
142
144
  }
143
145
  /**
144
146
  * The projects a build matrix declares, in its order, each a file below the target root, a
@@ -172,7 +174,7 @@ function declaredProjects(engine, host, targetRoot, entries) {
172
174
  }
173
175
  const read = readConfiguration(engine, configFile);
174
176
  if ("reason" in read) {
175
- unbuilt(named, `cannot be read as a compiler configuration: ${read.reason}`);
177
+ unbuilt(named, `cannot be read as a compiler configuration: ${read.reason}`, "unreadable-configuration");
176
178
  }
177
179
  references.set(configFile, referencesOf(read.parsed));
178
180
  const configuration = {
@@ -212,13 +214,19 @@ function derivedProjects(engine, host, scope) {
212
214
  const idOf = (configFile) => relativePath(targetRoot, configFile) ?? configFile;
213
215
  const notBuilt = [];
214
216
  const found = derivedFrom(engine, host, [...fromScope, ...configFilesUnder(host, targetRoot)], (reference) => relativePath(rootDir, reference) !== undefined, (configFile, reason) => {
217
+ const why = `cannot be read as a compiler configuration: ${reason}`;
215
218
  if (fromScope.includes(configFile)) {
216
- unbuilt(configFile, `cannot be read as a compiler configuration: ${reason}`);
219
+ unbuilt(configFile, why, "unreadable-configuration");
217
220
  }
221
+ const failure = {
222
+ failureClass: "unreadable-configuration",
223
+ detail: targetRelative(unreadableDetail(idOf(configFile), why), absoluteRoot),
224
+ };
218
225
  notBuilt.push({
219
226
  id: idOf(configFile),
220
227
  configFile,
221
228
  error: targetRelative(reason, absoluteRoot),
229
+ failures: [failure],
222
230
  });
223
231
  });
224
232
  const configurations = found.map(({ configFile, parsed }) => ({
@@ -0,0 +1,56 @@
1
+ import { manifestAt } from "../fs/manifest.js";
2
+ import { dirnamePath, joinPath } from "../paths.js";
3
+ /**
4
+ * Resolves each file's package scope: the name of the nearest manifest at or above its
5
+ * directory up to the target root, and the file's path below that manifest. Where no
6
+ * manifest up to the root carries a name, the package is a single dot and the path is read
7
+ * from the root, which cannot collide, because a package name never starts with a dot. One
8
+ * directory is read at most once.
9
+ */
10
+ export function packageScope(host, root) {
11
+ const named = new Map();
12
+ const manifestName = (dir) => {
13
+ const name = manifestAt(host, joinPath(root, dir))?.["name"];
14
+ return typeof name === "string" && name !== "" ? name : undefined;
15
+ };
16
+ const nearest = (dir) => {
17
+ if (named.has(dir)) {
18
+ return named.get(dir);
19
+ }
20
+ const name = manifestName(dir);
21
+ const found = name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
22
+ named.set(dir, found);
23
+ return found;
24
+ };
25
+ return (path) => {
26
+ const found = nearest(parentOf(path));
27
+ if (found === undefined) {
28
+ return { package: ".", path };
29
+ }
30
+ return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
31
+ };
32
+ }
33
+ /**
34
+ * Whether Node reads each file's `.js` siblings as ECMAScript modules: the `type` of the
35
+ * nearest manifest at or above its directory up to the target root is `module`
36
+ * (https://nodejs.org/api/packages.html#type). One directory is read at most once.
37
+ */
38
+ export function packageTypeIsModule(host, root) {
39
+ const typed = new Map();
40
+ const nearest = (dir) => {
41
+ const known = typed.get(dir);
42
+ if (known !== undefined) {
43
+ return known;
44
+ }
45
+ const manifest = manifestAt(host, joinPath(root, dir));
46
+ const found = manifest !== undefined ? manifest["type"] === "module" : dir !== "" && nearest(parentOf(dir));
47
+ typed.set(dir, found);
48
+ return found;
49
+ };
50
+ return (path) => nearest(parentOf(path));
51
+ }
52
+ /** The directory one path below the root sits in, and the root itself as the empty path. */
53
+ function parentOf(path) {
54
+ const dir = dirnamePath(path);
55
+ return dir === "." ? "" : dir;
56
+ }
@@ -1,7 +1,7 @@
1
- import { readConventions } from "../conventions.js";
1
+ import { readConventions, } from "../entries/conventions.js";
2
2
  import { holdsManifest, inInstalledPackages, MANIFEST, readManifestAt } from "../fs/manifest.js";
3
3
  import { gitIgnored } from "../git-ignore.js";
4
- import { packageEntries, readManifest } from "../manifest.js";
4
+ import { packageEntries, readManifest, } from "../roots/manifest.js";
5
5
  import { ancestors, dirnamePath, joinPath, normalizePath, relativePath, resolvePath, } from "../paths.js";
6
6
  import { bytewise } from "../text/compare.js";
7
7
  import { globExpression } from "../text/glob.js";
@@ -5,12 +5,12 @@
5
5
  * is written for.
6
6
  */
7
7
  import { readCommands } from "../commands/read.js";
8
- import { CONVENTION_ROWS } from "../convention-rows.js";
8
+ import { CONVENTION_ROWS } from "../entries/convention-rows.js";
9
9
  import { holdsManifest, MANIFEST, readManifestAt } from "../fs/manifest.js";
10
10
  import { filesBelow } from "../fs/tree.js";
11
- import { JAVASCRIPT_FILE, minified, minifiedByName } from "../javascript-file.js";
11
+ import { JAVASCRIPT_FILE, minifiedBy, minifiedByName } from "../javascript-file.js";
12
12
  import { isRecord } from "../json-document.js";
13
- import { manifestCommands } from "../manifest.js";
13
+ import { manifestCommands } from "../roots/manifest.js";
14
14
  import { ancestors, dirnamePath, joinPath, relativePath } from "../paths.js";
15
15
  import { bytewise } from "../text/compare.js";
16
16
  import { writtenPath } from "./workspace-programs.js";
@@ -33,11 +33,11 @@ const FORMED_OPTIONS = {
33
33
  };
34
34
  /** Whether one file is minified build output; one that cannot be read is left to the compiler. */
35
35
  function minifiedFile(host, file) {
36
- if (minifiedByName(file)) {
36
+ if (minifiedByName(file) !== undefined) {
37
37
  return true;
38
38
  }
39
39
  try {
40
- return minified(file, host.readFile(file));
40
+ return minifiedBy(file, host.readFile(file)) !== undefined;
41
41
  }
42
42
  catch {
43
43
  return false;
@@ -6,7 +6,7 @@
6
6
  * workspace imports, so only its commands are entry points.
7
7
  */
8
8
  import { emittedFrom } from "./emit-map.js";
9
- import { readManifest } from "../manifest.js";
9
+ import { readManifest } from "../roots/manifest.js";
10
10
  import { joinPath, relativePath } from "../paths.js";
11
11
  import { MANIFEST } from "../fs/manifest.js";
12
12
  /** The one character a subpath pattern of a manifest's `exports` holds. */
@@ -81,19 +81,12 @@ export function matrixOf(per) {
81
81
  };
82
82
  }
83
83
  /**
84
- * Answers which declarations of the run are dead in every configuration that holds them.
85
- *
86
- * Liveness is a question about one configuration, because a file another configuration
87
- * does not compile makes no reference there and a root it does not hold seeds nothing
88
- * there, so each configuration is swept on its own graph under the run's input. A
89
- * declaration is a candidate when every configuration holding it judged it one: a
90
- * reference under one configuration is a reference, so what one project uses is never
91
- * reported, and a configuration that does not hold a declaration says nothing about it.
92
- * The candidate's relation is reference counting where every configuration found it so,
93
- * and reachability otherwise; its counts are the union's, one per reference.
94
- *
95
- * The components are computed once, over the union and the intersected dead set, so one
96
- * dead component is one entry of the run with one identifier.
84
+ * Which declarations of the run are dead in every configuration that holds them. Each
85
+ * configuration is swept on its own graph, since a file it does not compile references nothing
86
+ * there and a root it does not hold seeds nothing. A declaration is a candidate when every
87
+ * configuration holding it judged it one; its relation is reference counting where every one
88
+ * found it so, else reachability, and its counts are the union's. Components are computed once,
89
+ * over the union and the intersected dead set, so one dead component has one identifier.
97
90
  */
98
91
  export function sweepMatrix(matrix, input) {
99
92
  const per = matrix.graphs.map((graph) => sweep(graph, input));
@@ -7,7 +7,7 @@
7
7
  * it names is not known.
8
8
  */
9
9
  import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
10
- import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
10
+ import { RESOLVED_EXTENSIONS } from "./entries/configuration-files.js";
11
11
  import { emittedFrom } from "./load/emit-map.js";
12
12
  import { namesImported } from "./module-declarations.js";
13
13
  import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
package/dist/src/paths.js CHANGED
@@ -88,6 +88,22 @@ export function extensionOf(path) {
88
88
  export function resolvePath(against, path) {
89
89
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
90
90
  }
91
+ /**
92
+ * A URL reference's scheme. A reference with none that does not start with a solidus is
93
+ * relative, the bare name included: https://url.spec.whatwg.org/#relative-url-string
94
+ */
95
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
96
+ /**
97
+ * The path a relative URL reference names against `dir`, its query and its fragment
98
+ * removed, which name no part of the file; undefined where the reference is not relative.
99
+ */
100
+ export function relativeURLPath(dir, reference) {
101
+ if (reference === "" || reference.startsWith("/") || URL_SCHEME.test(reference)) {
102
+ return undefined;
103
+ }
104
+ const cut = reference.search(/[?#]/u);
105
+ return resolvePath(dir, cut < 0 ? reference : reference.slice(0, cut));
106
+ }
91
107
  /**
92
108
  * The relative path before the query of a relative module specifier such as
93
109
  * `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Which calls are the platform's `require` and which constructions its `URL` (roots.23,
3
+ * references-ts.1), decided once over the bindings of the names a callee is written with: the
4
+ * checker's in a program, the file's own scopes in a module read outside every program.
5
+ */
6
+ import { isBindingElement, isCallExpression, isExportSpecifier, isIdentifier, isImportSpecifier, isPropertyAccessExpression, isStringLiteral, isVariableDeclaration, } from "@typescript/native/ast";
7
+ import { parenthesesRemoved, unwrapped } from "./unwrap.js";
8
+ /** The name one declaration carries a binding by beside its own: the export an import or a re-export names, or the property a destructuring reads. */
9
+ export function carriedNames(node) {
10
+ if (isImportSpecifier(node) || isExportSpecifier(node)) {
11
+ return [(node.propertyName ?? node.name).text];
12
+ }
13
+ const property = isBindingElement(node) ? node.propertyName : undefined;
14
+ return property !== undefined && (isIdentifier(property) || isStringLiteral(property))
15
+ ? [property.text]
16
+ : [];
17
+ }
18
+ /**
19
+ * A written name's binding as a file states it, with no checker to ask, so nothing is
20
+ * proven the target's own (conventions.3): `declarationOf` names the declaration a name
21
+ * resolves to, and a property is read by how it is written alone.
22
+ */
23
+ export function lexicalBinding(declarationOf) {
24
+ return (written) => {
25
+ const parent = written.parent;
26
+ if (isPropertyAccessExpression(parent) && parent.name === written) {
27
+ return undefined;
28
+ }
29
+ const declaration = declarationOf(written);
30
+ return {
31
+ names: new Set([
32
+ written.text,
33
+ ...(declaration === undefined ? [] : carriedNames(declaration)),
34
+ ]),
35
+ own: declaration === undefined ? [] : [declaration],
36
+ provenOwn: false,
37
+ };
38
+ };
39
+ }
40
+ /** The name a callee or a constructor is written with, parentheses removed: an identifier or a property name. */
41
+ export function writtenName(expression) {
42
+ const at = parenthesesRemoved(expression);
43
+ if (isIdentifier(at)) {
44
+ return at;
45
+ }
46
+ return isPropertyAccessExpression(at) && isIdentifier(at.name) ? at.name : undefined;
47
+ }
48
+ /**
49
+ * Whether a call or construction through `callee` is one of the platform's `name`: never
50
+ * where its binding is proven the target's own; otherwise where it is written or carried
51
+ * by that name, or resolves to a variable of the target whose initializer is such a
52
+ * callee or, for `require`, a call of a `createRequire`. A callee nothing binds is read by
53
+ * how it is written.
54
+ */
55
+ export function callsPlatformBy(name, callee, bindingOf, seen = new Set()) {
56
+ const bound = bindingOf(callee);
57
+ if (bound?.provenOwn === true) {
58
+ return false;
59
+ }
60
+ if (callee.text === name || bound?.names.has(name) === true) {
61
+ return true;
62
+ }
63
+ return (bound?.own ?? []).some((node) => {
64
+ if (seen.has(node) || !isVariableDeclaration(node) || node.initializer === undefined) {
65
+ return false;
66
+ }
67
+ seen.add(node);
68
+ const value = unwrapped(node.initializer);
69
+ if (isCallExpression(value)) {
70
+ const called = writtenName(value.expression);
71
+ return (name === "require" &&
72
+ called !== undefined &&
73
+ callsPlatformBy("createRequire", called, bindingOf, seen));
74
+ }
75
+ const written = writtenName(value);
76
+ return written !== undefined && callsPlatformBy(name, written, bindingOf, seen);
77
+ });
78
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The platform's names read through a program's checker: its `require` and `URL`, as
3
+ * `platform-calls.ts` decides them, its worker constructors and `navigator`, and Node's
4
+ * `Worker`.
5
+ */
6
+ import { isArrowFunction, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isMethodDeclaration, isModuleDeclaration, isPropertyAssignment, isPropertyDeclaration, isSourceFile, isStringLiteralLikeNode, isVariableDeclaration, } from "@typescript/native/ast";
7
+ import { SymbolFlags } from "@typescript/native/sync";
8
+ import { UNANSWERED } from "./query.js";
9
+ import { boundExports, NAMESPACE } from "./entries/static-module.js";
10
+ import { isAmbient } from "./inventory.js";
11
+ import { callsPlatformBy, carriedNames } from "./platform-calls.js";
12
+ import { unwrapped } from "./unwrap.js";
13
+ /** The symbol one symbol names through its aliases, or the symbol itself where it is none. */
14
+ function dealiased(project, symbol, asked) {
15
+ if ((symbol.flags & SymbolFlags.Alias) === 0) {
16
+ return symbol;
17
+ }
18
+ asked();
19
+ return project.queries.aliased(symbol);
20
+ }
21
+ /** Each declaration of `symbol` in a file of the target, read in this project's program. */
22
+ function ownDeclarations(project, symbol) {
23
+ const own = project.ownPaths();
24
+ return symbol.declarations.flatMap((handle) => {
25
+ const node = own.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
26
+ return node === undefined ? [] : [node];
27
+ });
28
+ }
29
+ /**
30
+ * Whether one declaration writes a function or a class with a body (roots.23): a function
31
+ * or method declaration with a body, a class declaration, or a variable, a property or a
32
+ * class field whose initializer is a function, an arrow function or a class expression,
33
+ * under no `declare` modifier and in no declaration file.
34
+ */
35
+ function writesBody(node) {
36
+ const value = (isVariableDeclaration(node) || isPropertyAssignment(node) || isPropertyDeclaration(node)) &&
37
+ node.initializer !== undefined
38
+ ? unwrapped(node.initializer)
39
+ : node;
40
+ return ((isFunctionDeclaration(value) || isMethodDeclaration(value)
41
+ ? value.body !== undefined
42
+ : isFunctionExpression(value) ||
43
+ isArrowFunction(value) ||
44
+ isClassDeclaration(value) ||
45
+ isClassExpression(value)) && !isAmbient(value.getSourceFile(), value));
46
+ }
47
+ /**
48
+ * What the checker binds `symbol` to, one alias step at a time: every name a step of the
49
+ * chain carries it by, and the declarations of the target its last step reaches, which
50
+ * prove it the target's own where one {@link writesBody}. A chain that ends at no
51
+ * declaration, or that the checker leaves unanswered, reaches none.
52
+ */
53
+ function boundBy(project, symbol, asked) {
54
+ if (typeof symbol !== "object") {
55
+ return undefined;
56
+ }
57
+ const names = new Set();
58
+ const stepped = new Set();
59
+ for (let at = symbol; at !== undefined && !stepped.has(at.id);) {
60
+ stepped.add(at.id);
61
+ names.add(at.name);
62
+ const own = ownDeclarations(project, at);
63
+ for (const node of own) {
64
+ carriedNames(node).forEach((one) => names.add(one));
65
+ }
66
+ if ((at.flags & SymbolFlags.Alias) === 0) {
67
+ return { names, own, provenOwn: own.some(writesBody) };
68
+ }
69
+ asked();
70
+ const next = project.aliasStepOf(at);
71
+ at = typeof next === "object" ? next : undefined;
72
+ }
73
+ return { names, own: [], provenOwn: false };
74
+ }
75
+ /** Each written name's binding, as this project's checker answers it. */
76
+ function checkerBinding(project, asked) {
77
+ return (written) => {
78
+ asked();
79
+ return boundBy(project, project.symbolsAt([project.handle(written)])[0], asked);
80
+ };
81
+ }
82
+ /**
83
+ * Whether a call or construction through `callee`, the batch's answer for which is `symbol`,
84
+ * is one of the platform's `require` or `URL` (roots.23, references-ts.1), as
85
+ * {@link callsPlatformBy} decides it over this project's checker, each request it puts
86
+ * counted by `asked`.
87
+ */
88
+ export function callsPlatform(project, name, callee, symbol, asked) {
89
+ const others = checkerBinding(project, asked);
90
+ return callsPlatformBy(name, callee, (written) => written === callee ? boundBy(project, symbol, asked) : others(written));
91
+ }
92
+ /**
93
+ * Whether a worker's constructor or `navigator`, the batch's answer for which is `symbol`, is
94
+ * the platform's (references-ts.2): a name the checker leaves unanswered, or one whose symbol
95
+ * is no alias and has a declaration outside the target's files.
96
+ */
97
+ export function isPlatformGlobal(project, symbol) {
98
+ if (symbol === UNANSWERED) {
99
+ return true;
100
+ }
101
+ const own = project.ownPaths();
102
+ return (symbol !== undefined &&
103
+ (symbol.flags & SymbolFlags.Alias) === 0 &&
104
+ symbol.declarations.some((handle) => !own.has(handle.path)));
105
+ }
106
+ /** The module names Node's worker threads are imported by. */
107
+ const THREADS = new Set(["worker_threads", "node:worker_threads"]);
108
+ function namesThreads(node) {
109
+ return node !== undefined && isStringLiteralLikeNode(node) && THREADS.has(node.text);
110
+ }
111
+ /** Whether one declaration is written in the body of `declare module "worker_threads"`. */
112
+ function inThreadsModule(node) {
113
+ for (let at = node; !isSourceFile(at); at = at.parent) {
114
+ if (isModuleDeclaration(at) && namesThreads(at.name)) {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ }
120
+ /**
121
+ * Whether a `Worker` constructor, the batch's answer for which is `symbol`, is Node's
122
+ * (references-ts.2): its symbol resolves to a declaration of `worker_threads`, or one of its
123
+ * own declarations binds the module's `Worker`, or `holder`, the namespace it is read
124
+ * through, binds the module, each binding read as {@link boundExports} reads one, which
125
+ * decides where the module has no declaration. Each request it puts is counted by `asked`.
126
+ */
127
+ export function startsThread(project, symbol, holder, asked) {
128
+ const bindingOf = checkerBinding(project, asked);
129
+ const declared = (one, member) => typeof one === "object" &&
130
+ ownDeclarations(project, one).some((node) => boundExports(node, (specifier) => THREADS.has(specifier), bindingOf).includes(member));
131
+ if (declared(symbol, "Worker") || declared(holder, NAMESPACE)) {
132
+ return true;
133
+ }
134
+ const named = typeof symbol === "object" ? dealiased(project, symbol, asked) : undefined;
135
+ return (typeof named === "object" &&
136
+ named.declarations.some((handle) => {
137
+ const node = project.declarationAt(handle)?.node;
138
+ return node !== undefined && inThreadsModule(node);
139
+ }));
140
+ }