@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,29 +1,46 @@
1
+ import { LANGUAGE_ROWS } from "./contract/contract.generated.ts";
2
+ import { globExpression } from "./text/glob.ts";
3
+
1
4
  export const JAVASCRIPT_FILE = /\.(?:js|jsx|mjs|cjs)$/u;
2
5
 
3
- /** A minified file by its name; any other is one by its size and its mean line length. */
4
- const MINIFIED_NAME = /\.min\.(?:js|mjs|cjs)$/u;
6
+ /** The patterns the TypeScript row of `languages.json` skips files by, each matched against a name alone. */
7
+ const MINIFIED_NAMES = LANGUAGE_ROWS.ts.skippedFiles.map((pattern) => ({
8
+ pattern,
9
+ expression: globExpression(pattern),
10
+ }));
5
11
  const MINIFIED_BYTES = 10_240;
6
12
  const MINIFIED_LINE = 500;
7
13
 
8
- /** Whether one name is a minified file's, which decides it without its text. */
9
- export function minifiedByName(fileName: string): boolean {
10
- return MINIFIED_NAME.test(fileName);
14
+ /** What a minified file is decided by where its text is not read. */
15
+ const LONG_LINES = "long lines";
16
+
17
+ /** The skipped-file pattern one file's name matches, which decides it minified without its text. */
18
+ export function minifiedByName(fileName: string): string | undefined {
19
+ const name = fileName.slice(fileName.lastIndexOf("/") + 1);
20
+ return MINIFIED_NAMES.find((one) => one.expression.test(name))?.pattern;
11
21
  }
12
22
 
13
- /** Whether one JavaScript file is minified: by its name, or by its text's size and mean line length. */
14
- export function minified(fileName: string, text: string): boolean {
23
+ /**
24
+ * What decides one JavaScript file minified: the skipped-file pattern its name matches, or
25
+ * {@link LONG_LINES} for a text of the size whose mean line is longer than the limit;
26
+ * undefined for a file that is not minified.
27
+ */
28
+ export function minifiedBy(fileName: string, text: string): string | undefined {
15
29
  if (!JAVASCRIPT_FILE.test(fileName)) {
16
- return false;
30
+ return undefined;
17
31
  }
18
- if (minifiedByName(fileName)) {
19
- return true;
32
+ const named = minifiedByName(fileName);
33
+ if (named !== undefined) {
34
+ return named;
20
35
  }
21
36
  // A UTF-16 unit encodes to at most three UTF-8 bytes, so a shorter text is below the size.
22
37
  if (text.length * 3 < MINIFIED_BYTES) {
23
- return false;
38
+ return undefined;
24
39
  }
25
40
  const lines = text.split(/\r?\n/u).length;
26
- return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE;
41
+ return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE
42
+ ? LONG_LINES
43
+ : undefined;
27
44
  }
28
45
 
29
46
  /** The length of a text in UTF-8, a lone surrogate counting as the three bytes of U+FFFD. */
@@ -1,5 +1,5 @@
1
1
  import type { SourceFile } from "@typescript/native/ast";
2
- import { conventionFailuresFor } from "../conventions.ts";
2
+ import { conventionFailuresFor } from "../entries/conventions.ts";
3
3
  import type { Host } from "../host.ts";
4
4
  import { relativePath, resolvePath } from "../paths.ts";
5
5
  import { diagnosticsOf, ownedAs, type Engine, type ProjectView } from "../session.ts";
@@ -11,7 +11,13 @@ import {
11
11
  renderDiagnostic,
12
12
  type LocatedDiagnostic,
13
13
  } from "./diagnostics.ts";
14
- import { targetRelative, type LoadedConsumer, type NotBuilt, type Origin } from "./discover.ts";
14
+ import {
15
+ targetRelative,
16
+ unreadableDetail,
17
+ type LoadedConsumer,
18
+ type NotBuilt,
19
+ type Origin,
20
+ } from "./discover.ts";
15
21
  import { missingModule, pathsOf, type ModuleContext } from "./expected-module.ts";
16
22
  import { FailureExit, failureLine, type Failure } from "../failure.ts";
17
23
  import type { ProgramPlan, RunPlan } from "./plan.ts";
@@ -21,8 +27,12 @@ import { ownErrors } from "./workspace-run.ts";
21
27
 
22
28
  /** What keeps one configuration from being analyzed. */
23
29
  type Failed =
24
- /** Its project carries an error that leaves it unloaded. */
25
- | { readonly kind: "load"; readonly errors: readonly [LocatedDiagnostic, ...LocatedDiagnostic[]] }
30
+ /** Its project carries an error that leaves it unloaded, its configuration unreadable or not. */
31
+ | {
32
+ readonly kind: "load";
33
+ readonly errors: readonly [LocatedDiagnostic, ...LocatedDiagnostic[]];
34
+ readonly unreadable: boolean;
35
+ }
26
36
  | { readonly kind: "setup"; readonly failures: readonly [Failure, ...Failure[]] }
27
37
  /** The snapshot opened no project for it. */
28
38
  | { readonly kind: "unopened" };
@@ -162,7 +172,7 @@ function classifyTarget<Brand>(
162
172
  const raw = projectErrors(diagnosticsOf(project));
163
173
  const load = nonEmpty(raw.load);
164
174
  if (load !== undefined) {
165
- return { kind: "load", errors: load };
175
+ return { kind: "load", errors: load, unreadable: raw.unreadable };
166
176
  }
167
177
  const reading = readErrors(project, plan, ownErrors(raw.check, referenceOnly), {
168
178
  host,
@@ -236,7 +246,7 @@ function classifyConsumer<Brand>(
236
246
  const raw = projectErrors(diagnosticsOf(project));
237
247
  const load = nonEmpty(raw.load);
238
248
  if (load !== undefined) {
239
- return { kind: "load", errors: load };
249
+ return { kind: "load", errors: load, unreadable: raw.unreadable };
240
250
  }
241
251
  let faulted: string | undefined;
242
252
  const reading = readErrors(project, plan, raw.check, {
@@ -283,7 +293,8 @@ function ending(plan: ProgramPlan, failed: Failed, root: string): readonly Failu
283
293
  return plan.consumer === undefined
284
294
  ? [
285
295
  {
286
- failureClass: "no-program",
296
+ failureClass:
297
+ failed.kind === "load" && failed.unreadable ? "unreadable-configuration" : "no-program",
287
298
  detail: `${what}: correct the configuration, or name the configurations to analyze`,
288
299
  },
289
300
  ]
@@ -295,6 +306,30 @@ function ending(plan: ProgramPlan, failed: Failed, root: string): readonly Failu
295
306
  ];
296
307
  }
297
308
 
309
+ /** The setup failures a dropped derived configuration counts as, where it counts as any. */
310
+ function droppedFailures(
311
+ failed: Failed,
312
+ plan: ProgramPlan,
313
+ root: string,
314
+ ): { readonly failures?: readonly [Failure, ...Failure[]] } {
315
+ if (failed.kind === "setup") {
316
+ return { failures: failed.failures };
317
+ }
318
+ return failed.kind === "load" && failed.unreadable
319
+ ? {
320
+ failures: [
321
+ {
322
+ failureClass: "unreadable-configuration",
323
+ detail: unreadableDetail(
324
+ plan.id,
325
+ `cannot be read as a compiler configuration: ${droppedBy(failed, root)}`,
326
+ ),
327
+ },
328
+ ],
329
+ }
330
+ : {};
331
+ }
332
+
298
333
  /** The first line of the error that drops one derived configuration. */
299
334
  function droppedBy(failed: Failed, root: string): string {
300
335
  return failed.kind === "load"
@@ -326,7 +361,6 @@ export function classify(
326
361
  const analyzed: Analyzed<unknown>[] = [];
327
362
  const consumed: Consumed<unknown>[] = [];
328
363
  const ended = new Map<Failed["kind"], { plan: ProgramPlan; failed: Failed }[]>();
329
- const dropped: Failed[] = [];
330
364
  const unopened = run.plans.filter((plan) => !views.has(plan.configFile));
331
365
  const outcomes = [
332
366
  ...run.plans.flatMap((plan) => {
@@ -355,8 +389,12 @@ export function classify(
355
389
  } else if (POLICY[plan.origin][outcome.kind] === "end") {
356
390
  ended.set(outcome.kind, [...(ended.get(outcome.kind) ?? []), { plan, failed: outcome }]);
357
391
  } else {
358
- dropped.push(outcome);
359
- notBuilt.push({ id: plan.id, configFile: plan.configFile, error: droppedBy(outcome, root) });
392
+ notBuilt.push({
393
+ id: plan.id,
394
+ configFile: plan.configFile,
395
+ error: droppedBy(outcome, root),
396
+ ...droppedFailures(outcome, plan, root),
397
+ });
360
398
  }
361
399
  }
362
400
  for (const kind of ["unopened", "setup", "load"] as const) {
@@ -369,9 +407,7 @@ export function classify(
369
407
  );
370
408
  }
371
409
  }
372
- const setupDropped = nonEmpty(
373
- dropped.flatMap((one) => (one.kind === "setup" ? one.failures : [])),
374
- );
410
+ const setupDropped = nonEmpty(notBuilt.flatMap((one) => one.failures ?? []));
375
411
  const [first] = notBuilt;
376
412
  if (analyzed.length === 0 && first !== undefined) {
377
413
  throw new FailureExit(
@@ -121,10 +121,18 @@ export function diagnosticErrors(sets: {
121
121
  return [...sets.configParsing, ...sets.syntactic, ...sets.semantic].filter(isError);
122
122
  }
123
123
 
124
+ /**
125
+ * The code of a configuration whose only parse error is that its inputs name no file: it reads
126
+ * as written and holds no program.
127
+ */
128
+ const NO_INPUTS = 18003;
129
+
124
130
  /** Why one open project cannot be analyzed as it stands, split by whether it loaded at all. */
125
131
  interface ProjectErrors {
126
132
  /** The configuration's own errors and the errors in no file the table reads as a load failure. */
127
133
  readonly load: readonly LocatedDiagnostic[];
134
+ /** Whether the compiler cannot read the configuration: a parse error other than {@link NO_INPUTS}. */
135
+ readonly unreadable: boolean;
128
136
  /** The errors in the files of a project that loaded. */
129
137
  readonly check: readonly LocatedDiagnostic[];
130
138
  /** The libraries the configuration names that the compiler cannot find, of a project that loaded. */
@@ -135,9 +143,11 @@ interface ProjectErrors {
135
143
  export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
136
144
  const unplaced = (kind: DiagnosticClass): readonly Diagnostic[] =>
137
145
  sets.unplaced.filter((diagnostic) => classOf(diagnostic, false) === kind);
138
- const load = [...sets.configParsing.filter(isError), ...unplaced("load")];
146
+ const parsing = sets.configParsing.filter(isError);
147
+ const load = [...parsing, ...unplaced("load")];
139
148
  return {
140
149
  load: locatedIn(load, sets.locate),
150
+ unreadable: parsing.some(({ code }) => code !== NO_INPUTS),
141
151
  check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
142
152
  unfound: load.length > 0 ? [] : locatedIn(unplaced("unfound-library"), sets.locate),
143
153
  };
@@ -2,13 +2,13 @@ import type { BuildConfiguration, ProjectConfiguration } from "../config.ts";
2
2
  import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
3
3
  import { filesBelow, filesIn } from "../fs/tree.ts";
4
4
  import type { Host } from "../host.ts";
5
- import { packageScope } from "../inventory.ts";
5
+ import { packageScope } from "./package-scope.ts";
6
6
  import { dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
7
7
  import { LANGUAGE, message } from "../product.ts";
8
8
  import type { Module, Scope } from "../scope.ts";
9
9
  import type { Engine } from "../session.ts";
10
10
  import { globExpression } from "../text/glob.ts";
11
- import { failureExit, type FailureExit } from "../failure.ts";
11
+ import { failureExit, type Failure, type FailureExit } from "../failure.ts";
12
12
  import { MANIFEST } from "../fs/manifest.ts";
13
13
 
14
14
  /** The names of the files a program of the language is read from. */
@@ -72,6 +72,8 @@ export interface NotBuilt {
72
72
  readonly configFile: string;
73
73
  /** The error's first line, every path below the target root spelled relative to it. */
74
74
  readonly error: string;
75
+ /** The setup failures that dropped it, where one did. */
76
+ readonly failures?: readonly [Failure, ...Failure[]];
75
77
  }
76
78
 
77
79
  /**
@@ -165,11 +167,9 @@ function readConfiguration(
165
167
  try {
166
168
  const parsed = engine.parseConfigFile(configFile);
167
169
  const circular = parsed.errors.find(({ code }) => code === CIRCULAR_EXTENDS);
168
- // The compiler leaves the message's `{0}` unfilled:
169
- // https://github.com/microsoft/TypeScript/pull/64755
170
170
  return circular === undefined
171
171
  ? { parsed }
172
- : { reason: `TS${String(circular.code)}: ${circular.text.replace("{0}", () => configFile)}` };
172
+ : { reason: `TS${String(circular.code)}: ${circular.text}` };
173
173
  } catch (error: unknown) {
174
174
  return { reason: message(error) };
175
175
  }
@@ -274,11 +274,17 @@ export function targetWithoutSources(target: string): FailureExit {
274
274
  }
275
275
 
276
276
  /** A target configuration a matrix or the invocation names that cannot be built, ending the run. */
277
- function unbuilt(named: string, reason: string): never {
278
- throw failureExit(
279
- "no-program",
280
- `${named} ${reason}: correct the configuration, or name the configurations to analyze`,
281
- );
277
+ function unbuilt(
278
+ named: string,
279
+ reason: string,
280
+ failureClass: Failure["failureClass"] = "no-program",
281
+ ): never {
282
+ throw failureExit(failureClass, unreadableDetail(named, reason));
283
+ }
284
+
285
+ /** What fails and the fix, for a configuration `named` that `reason` keeps from being built. */
286
+ export function unreadableDetail(named: string, reason: string): string {
287
+ return `${named} ${reason}: correct the configuration, or name the configurations to analyze`;
282
288
  }
283
289
 
284
290
  /**
@@ -321,7 +327,11 @@ function declaredProjects(
321
327
  }
322
328
  const read = readConfiguration(engine, configFile);
323
329
  if ("reason" in read) {
324
- unbuilt(named, `cannot be read as a compiler configuration: ${read.reason}`);
330
+ unbuilt(
331
+ named,
332
+ `cannot be read as a compiler configuration: ${read.reason}`,
333
+ "unreadable-configuration",
334
+ );
325
335
  }
326
336
  references.set(configFile, referencesOf(read.parsed));
327
337
  const configuration: Configuration = {
@@ -377,13 +387,19 @@ function derivedProjects(
377
387
  [...fromScope, ...configFilesUnder(host, targetRoot)],
378
388
  (reference) => relativePath(rootDir, reference) !== undefined,
379
389
  (configFile, reason) => {
390
+ const why = `cannot be read as a compiler configuration: ${reason}`;
380
391
  if (fromScope.includes(configFile)) {
381
- unbuilt(configFile, `cannot be read as a compiler configuration: ${reason}`);
392
+ unbuilt(configFile, why, "unreadable-configuration");
382
393
  }
394
+ const failure: Failure = {
395
+ failureClass: "unreadable-configuration",
396
+ detail: targetRelative(unreadableDetail(idOf(configFile), why), absoluteRoot),
397
+ };
383
398
  notBuilt.push({
384
399
  id: idOf(configFile),
385
400
  configFile,
386
401
  error: targetRelative(reason, absoluteRoot),
402
+ failures: [failure],
387
403
  });
388
404
  },
389
405
  );
@@ -0,0 +1,74 @@
1
+ import { manifestAt } from "../fs/manifest.ts";
2
+ import type { Host } from "../host.ts";
3
+ import { dirnamePath, joinPath } from "../paths.ts";
4
+ import type { Module } from "../ref.ts";
5
+
6
+ /** One manifest that names a package, and the directory below the root that holds it. */
7
+ interface Named {
8
+ readonly name: string;
9
+ readonly at: string;
10
+ }
11
+
12
+ /**
13
+ * Resolves each file's package scope: the name of the nearest manifest at or above its
14
+ * directory up to the target root, and the file's path below that manifest. Where no
15
+ * manifest up to the root carries a name, the package is a single dot and the path is read
16
+ * from the root, which cannot collide, because a package name never starts with a dot. One
17
+ * directory is read at most once.
18
+ */
19
+ export function packageScope(host: Host, root: string): (path: string) => Module {
20
+ const named = new Map<string, Named | undefined>();
21
+
22
+ const manifestName = (dir: string): string | undefined => {
23
+ const name = manifestAt(host, joinPath(root, dir))?.["name"];
24
+ return typeof name === "string" && name !== "" ? name : undefined;
25
+ };
26
+
27
+ const nearest = (dir: string): Named | undefined => {
28
+ if (named.has(dir)) {
29
+ return named.get(dir);
30
+ }
31
+ const name = manifestName(dir);
32
+ const found =
33
+ name !== undefined ? { name, at: dir } : dir === "" ? undefined : nearest(parentOf(dir));
34
+ named.set(dir, found);
35
+ return found;
36
+ };
37
+
38
+ return (path) => {
39
+ const found = nearest(parentOf(path));
40
+ if (found === undefined) {
41
+ return { package: ".", path };
42
+ }
43
+ return { package: found.name, path: found.at === "" ? path : path.slice(found.at.length + 1) };
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Whether Node reads each file's `.js` siblings as ECMAScript modules: the `type` of the
49
+ * nearest manifest at or above its directory up to the target root is `module`
50
+ * (https://nodejs.org/api/packages.html#type). One directory is read at most once.
51
+ */
52
+ export function packageTypeIsModule(host: Host, root: string): (path: string) => boolean {
53
+ const typed = new Map<string, boolean>();
54
+
55
+ const nearest = (dir: string): boolean => {
56
+ const known = typed.get(dir);
57
+ if (known !== undefined) {
58
+ return known;
59
+ }
60
+ const manifest = manifestAt(host, joinPath(root, dir));
61
+ const found =
62
+ manifest !== undefined ? manifest["type"] === "module" : dir !== "" && nearest(parentOf(dir));
63
+ typed.set(dir, found);
64
+ return found;
65
+ };
66
+
67
+ return (path) => nearest(parentOf(path));
68
+ }
69
+
70
+ /** The directory one path below the root sits in, and the root itself as the empty path. */
71
+ function parentOf(path: string): string {
72
+ const dir = dirnamePath(path);
73
+ return dir === "." ? "" : dir;
74
+ }
package/src/load/plan.ts CHANGED
@@ -1,10 +1,19 @@
1
1
  import type { CreateSnapshotProgramParams } from "@typescript/native/sync";
2
2
  import type { PartialConfig } from "../config.ts";
3
- import { readConventions, type ConventionManifest, type Conventions } from "../conventions.ts";
3
+ import {
4
+ readConventions,
5
+ type ConventionManifest,
6
+ type Conventions,
7
+ } from "../entries/conventions.ts";
4
8
  import { holdsManifest, inInstalledPackages, MANIFEST, readManifestAt } from "../fs/manifest.ts";
5
9
  import { gitIgnored } from "../git-ignore.ts";
6
10
  import type { Host } from "../host.ts";
7
- import { packageEntries, readManifest, type Manifest, type ManifestEntry } from "../manifest.ts";
11
+ import {
12
+ packageEntries,
13
+ readManifest,
14
+ type Manifest,
15
+ type ManifestEntry,
16
+ } from "../roots/manifest.ts";
8
17
  import {
9
18
  ancestors,
10
19
  dirnamePath,
@@ -6,14 +6,14 @@
6
6
  */
7
7
 
8
8
  import { readCommands } from "../commands/read.ts";
9
- import { CONVENTION_ROWS } from "../convention-rows.ts";
10
- import type { Conventions } from "../conventions.ts";
9
+ import { CONVENTION_ROWS } from "../entries/convention-rows.ts";
10
+ import type { Conventions } from "../entries/conventions.ts";
11
11
  import { holdsManifest, MANIFEST, readManifestAt, type ManifestRead } from "../fs/manifest.ts";
12
12
  import { filesBelow } from "../fs/tree.ts";
13
13
  import type { Host } from "../host.ts";
14
- import { JAVASCRIPT_FILE, minified, minifiedByName } from "../javascript-file.ts";
14
+ import { JAVASCRIPT_FILE, minifiedBy, minifiedByName } from "../javascript-file.ts";
15
15
  import { isRecord } from "../json-document.ts";
16
- import { manifestCommands } from "../manifest.ts";
16
+ import { manifestCommands } from "../roots/manifest.ts";
17
17
  import { ancestors, dirnamePath, joinPath, relativePath } from "../paths.ts";
18
18
  import type { Engine } from "../session.ts";
19
19
  import { bytewise } from "../text/compare.ts";
@@ -50,11 +50,11 @@ interface CandidateContext {
50
50
 
51
51
  /** Whether one file is minified build output; one that cannot be read is left to the compiler. */
52
52
  function minifiedFile(host: Host, file: string): boolean {
53
- if (minifiedByName(file)) {
53
+ if (minifiedByName(file) !== undefined) {
54
54
  return true;
55
55
  }
56
56
  try {
57
- return minified(file, host.readFile(file));
57
+ return minifiedBy(file, host.readFile(file)) !== undefined;
58
58
  } catch {
59
59
  return false;
60
60
  }
@@ -23,7 +23,7 @@ import {
23
23
  type Node,
24
24
  type SourceFile,
25
25
  } from "@typescript/native/ast";
26
- import type { ParseFile } from "../configuration-files.ts";
26
+ import type { ParseFile } from "../entries/configuration-files.ts";
27
27
  import type { Host } from "../host.ts";
28
28
  import { isRecord } from "../json-document.ts";
29
29
  import { ancestors, dirnamePath, joinPath } from "../paths.ts";
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { emittedFrom } from "./emit-map.ts";
10
10
  import type { Host } from "../host.ts";
11
- import { readManifest, type Manifest, type ManifestEntry } from "../manifest.ts";
11
+ import { readManifest, type Manifest, type ManifestEntry } from "../roots/manifest.ts";
12
12
  import { joinPath, relativePath } from "../paths.ts";
13
13
  import type { WorkspaceMember } from "./workspace.ts";
14
14
  import type { WorkspaceResolver } from "./workspace-resolution.ts";
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Host } from "../host.ts";
9
- import type { Manifest, ManifestEntry } from "../manifest.ts";
9
+ import type { Manifest, ManifestEntry } from "../roots/manifest.ts";
10
10
  import { relativePath } from "../paths.ts";
11
11
  import type { Engine } from "../session.ts";
12
12
  import type { LocatedDiagnostic } from "./diagnostics.ts";
package/src/matrix.ts CHANGED
@@ -9,7 +9,7 @@ import { graphOf, OUTSIDE, type Graph } from "./graph.ts";
9
9
  import type { InventorySymbol } from "./inventory.ts";
10
10
  import { byPosition } from "./position.ts";
11
11
  import type { Reference } from "./references.ts";
12
- import type { Root } from "./roots.ts";
12
+ import type { Root } from "./roots/root-set.ts";
13
13
  import {
14
14
  reachedWithTheTests,
15
15
  sweep,
@@ -198,19 +198,12 @@ export function matrixOf(per: readonly Configured[]): Matrix {
198
198
  }
199
199
 
200
200
  /**
201
- * Answers which declarations of the run are dead in every configuration that holds them.
202
- *
203
- * Liveness is a question about one configuration, because a file another configuration
204
- * does not compile makes no reference there and a root it does not hold seeds nothing
205
- * there, so each configuration is swept on its own graph under the run's input. A
206
- * declaration is a candidate when every configuration holding it judged it one: a
207
- * reference under one configuration is a reference, so what one project uses is never
208
- * reported, and a configuration that does not hold a declaration says nothing about it.
209
- * The candidate's relation is reference counting where every configuration found it so,
210
- * and reachability otherwise; its counts are the union's, one per reference.
211
- *
212
- * The components are computed once, over the union and the intersected dead set, so one
213
- * dead component is one entry of the run with one identifier.
201
+ * Which declarations of the run are dead in every configuration that holds them. Each
202
+ * configuration is swept on its own graph, since a file it does not compile references nothing
203
+ * there and a root it does not hold seeds nothing. A declaration is a candidate when every
204
+ * configuration holding it judged it one; its relation is reference counting where every one
205
+ * found it so, else reachability, and its counts are the union's. Components are computed once,
206
+ * over the union and the intersected dead set, so one dead component has one identifier.
214
207
  */
215
208
  export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
216
209
  const per = matrix.graphs.map((graph) => sweep(graph, input));
@@ -19,7 +19,7 @@ import {
19
19
  type Node,
20
20
  type SourceFile,
21
21
  } from "@typescript/native/ast";
22
- import { RESOLVED_EXTENSIONS, type ParseFile } from "./configuration-files.ts";
22
+ import { RESOLVED_EXTENSIONS, type ParseFile } from "./entries/configuration-files.ts";
23
23
  import { emittedFrom } from "./load/emit-map.ts";
24
24
  import type { Host } from "./host.ts";
25
25
  import type { Inventory } from "./inventory.ts";
package/src/paths.ts CHANGED
@@ -97,6 +97,24 @@ export function resolvePath(against: string, path: string): string {
97
97
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
98
98
  }
99
99
 
100
+ /**
101
+ * A URL reference's scheme. A reference with none that does not start with a solidus is
102
+ * relative, the bare name included: https://url.spec.whatwg.org/#relative-url-string
103
+ */
104
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
105
+
106
+ /**
107
+ * The path a relative URL reference names against `dir`, its query and its fragment
108
+ * removed, which name no part of the file; undefined where the reference is not relative.
109
+ */
110
+ export function relativeURLPath(dir: string, reference: string): string | undefined {
111
+ if (reference === "" || reference.startsWith("/") || URL_SCHEME.test(reference)) {
112
+ return undefined;
113
+ }
114
+ const cut = reference.search(/[?#]/u);
115
+ return resolvePath(dir, cut < 0 ? reference : reference.slice(0, cut));
116
+ }
117
+
100
118
  /**
101
119
  * The relative path before the query of a relative module specifier such as
102
120
  * `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
@@ -0,0 +1,119 @@
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
+
7
+ import {
8
+ isBindingElement,
9
+ isCallExpression,
10
+ isExportSpecifier,
11
+ isIdentifier,
12
+ isImportSpecifier,
13
+ isPropertyAccessExpression,
14
+ isStringLiteral,
15
+ isVariableDeclaration,
16
+ type Identifier,
17
+ type Node,
18
+ } from "@typescript/native/ast";
19
+ import { parenthesesRemoved, unwrapped } from "./unwrap.ts";
20
+
21
+ /** A name of the platform the analysis reads calls of. */
22
+ type PlatformName = "require" | "createRequire" | "URL";
23
+
24
+ /** What one written name is bound to, as far as its reader can tell. */
25
+ export interface Bound {
26
+ /** Its own name, and each one an import, a re-export or a destructuring carries it by. */
27
+ readonly names: ReadonlySet<string>;
28
+ /** The declarations of the target it resolves to, a variable's initializer read through. */
29
+ readonly own: readonly Node[];
30
+ /** Whether the type checker proves it the target's own, a function or a class it writes with a body. */
31
+ readonly provenOwn: boolean;
32
+ }
33
+
34
+ /** The binding of each written name, `undefined` where nothing answers which declaration it names. */
35
+ export type BindingOf = (written: Identifier) => Bound | undefined;
36
+
37
+ /** 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. */
38
+ export function carriedNames(node: Node): readonly string[] {
39
+ if (isImportSpecifier(node) || isExportSpecifier(node)) {
40
+ return [(node.propertyName ?? node.name).text];
41
+ }
42
+ const property = isBindingElement(node) ? node.propertyName : undefined;
43
+ return property !== undefined && (isIdentifier(property) || isStringLiteral(property))
44
+ ? [property.text]
45
+ : [];
46
+ }
47
+
48
+ /**
49
+ * A written name's binding as a file states it, with no checker to ask, so nothing is
50
+ * proven the target's own (conventions.3): `declarationOf` names the declaration a name
51
+ * resolves to, and a property is read by how it is written alone.
52
+ */
53
+ export function lexicalBinding(
54
+ declarationOf: (written: Identifier) => Node | undefined,
55
+ ): BindingOf {
56
+ return (written) => {
57
+ const parent = written.parent;
58
+ if (isPropertyAccessExpression(parent) && parent.name === written) {
59
+ return undefined;
60
+ }
61
+ const declaration = declarationOf(written);
62
+ return {
63
+ names: new Set([
64
+ written.text,
65
+ ...(declaration === undefined ? [] : carriedNames(declaration)),
66
+ ]),
67
+ own: declaration === undefined ? [] : [declaration],
68
+ provenOwn: false,
69
+ };
70
+ };
71
+ }
72
+
73
+ /** The name a callee or a constructor is written with, parentheses removed: an identifier or a property name. */
74
+ export function writtenName(expression: Node): Identifier | undefined {
75
+ const at = parenthesesRemoved(expression);
76
+ if (isIdentifier(at)) {
77
+ return at;
78
+ }
79
+ return isPropertyAccessExpression(at) && isIdentifier(at.name) ? at.name : undefined;
80
+ }
81
+
82
+ /**
83
+ * Whether a call or construction through `callee` is one of the platform's `name`: never
84
+ * where its binding is proven the target's own; otherwise where it is written or carried
85
+ * by that name, or resolves to a variable of the target whose initializer is such a
86
+ * callee or, for `require`, a call of a `createRequire`. A callee nothing binds is read by
87
+ * how it is written.
88
+ */
89
+ export function callsPlatformBy(
90
+ name: PlatformName,
91
+ callee: Identifier,
92
+ bindingOf: BindingOf,
93
+ seen = new Set<Node>(),
94
+ ): boolean {
95
+ const bound = bindingOf(callee);
96
+ if (bound?.provenOwn === true) {
97
+ return false;
98
+ }
99
+ if (callee.text === name || bound?.names.has(name) === true) {
100
+ return true;
101
+ }
102
+ return (bound?.own ?? []).some((node) => {
103
+ if (seen.has(node) || !isVariableDeclaration(node) || node.initializer === undefined) {
104
+ return false;
105
+ }
106
+ seen.add(node);
107
+ const value = unwrapped(node.initializer);
108
+ if (isCallExpression(value)) {
109
+ const called = writtenName(value.expression);
110
+ return (
111
+ name === "require" &&
112
+ called !== undefined &&
113
+ callsPlatformBy("createRequire", called, bindingOf, seen)
114
+ );
115
+ }
116
+ const written = writtenName(value);
117
+ return written !== undefined && callsPlatformBy(name, written, bindingOf, seen);
118
+ });
119
+ }