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

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 (122) hide show
  1. package/dist/src/analysis.js +108 -10
  2. package/dist/src/augmentations.js +62 -18
  3. package/dist/src/automatic-types.js +189 -0
  4. package/dist/src/base-url.js +18 -0
  5. package/dist/src/components.js +7 -6
  6. package/dist/src/config.js +1 -0
  7. package/dist/src/configuration-files.js +50 -12
  8. package/dist/src/conformance-record.js +2 -2
  9. package/dist/src/convention-rows.js +98 -0
  10. package/dist/src/conventions.js +101 -5
  11. package/dist/src/decorator.js +21 -18
  12. package/dist/src/dependencies.js +183 -28
  13. package/dist/src/discover.js +132 -11
  14. package/dist/src/entry-point-gaps.js +7 -10
  15. package/dist/src/entry-points.js +112 -11
  16. package/dist/src/findings/completion.js +172 -40
  17. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  18. package/dist/src/findings/emitters.js +1 -1
  19. package/dist/src/findings/intra-function.js +44 -13
  20. package/dist/src/findings/non-code-artifacts.js +66 -33
  21. package/dist/src/findings/unused-declarations.js +30 -5
  22. package/dist/src/findings/visibility-narrowing.js +29 -5
  23. package/dist/src/generated-file.js +11 -0
  24. package/dist/src/git-ignore.js +86 -0
  25. package/dist/src/graph.js +4 -1
  26. package/dist/src/import-bindings.js +97 -0
  27. package/dist/src/indexed-access.js +130 -28
  28. package/dist/src/interface-satisfaction.js +30 -12
  29. package/dist/src/intra-function-parts.js +297 -45
  30. package/dist/src/inventory.js +52 -8
  31. package/dist/src/kinds.js +13 -13
  32. package/dist/src/matrix.js +57 -10
  33. package/dist/src/mdx-entries.js +197 -0
  34. package/dist/src/module-declarations.js +9 -13
  35. package/dist/src/overrides.js +41 -10
  36. package/dist/src/print.js +1 -0
  37. package/dist/src/ref.js +10 -0
  38. package/dist/src/references.js +38 -4
  39. package/dist/src/report.js +4 -2
  40. package/dist/src/resolve.js +2 -0
  41. package/dist/src/roots.js +110 -38
  42. package/dist/src/schema.js +1 -0
  43. package/dist/src/serialization-contract.js +54 -8
  44. package/dist/src/session.js +7 -0
  45. package/dist/src/source-files.js +39 -14
  46. package/dist/src/suppress-file.js +10 -2
  47. package/dist/src/suppress-inline.js +18 -2
  48. package/dist/src/suppress.js +8 -5
  49. package/dist/src/sweep.js +27 -10
  50. package/dist/src/symbol-keys.js +103 -2
  51. package/dist/src/test-support.js +119 -18
  52. package/dist/src/type-errors.js +34 -7
  53. package/dist/src/type-parameter-reads.js +172 -0
  54. package/dist/src/verbs/analyze.js +16 -0
  55. package/dist/src/verbs/explain.js +4 -2
  56. package/dist/src/version.js +1 -1
  57. package/dist/src/workflow-steps.js +119 -22
  58. package/dist/src/workspace-programs.js +7 -3
  59. package/dist/src/workspace-resolution.js +53 -10
  60. package/jsr.json +1 -1
  61. package/package.json +2 -2
  62. package/src/analysis.ts +146 -11
  63. package/src/augmentations.ts +76 -22
  64. package/src/automatic-types.ts +246 -0
  65. package/src/base-url.ts +24 -0
  66. package/src/components.ts +7 -6
  67. package/src/config.ts +3 -0
  68. package/src/configuration-files.ts +66 -12
  69. package/src/conformance-record.ts +2 -2
  70. package/src/convention-rows.ts +128 -0
  71. package/src/conventions.ts +121 -7
  72. package/src/decorator.ts +28 -18
  73. package/src/dependencies.ts +247 -41
  74. package/src/discover.ts +156 -13
  75. package/src/entry-point-gaps.ts +8 -11
  76. package/src/entry-points.ts +133 -11
  77. package/src/finding.ts +15 -0
  78. package/src/findings/completion.ts +222 -46
  79. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  80. package/src/findings/emitters.ts +1 -1
  81. package/src/findings/intra-function.ts +56 -14
  82. package/src/findings/non-code-artifacts.ts +81 -41
  83. package/src/findings/unused-declarations.ts +36 -5
  84. package/src/findings/visibility-narrowing.ts +40 -5
  85. package/src/generated-file.ts +16 -0
  86. package/src/git-ignore.ts +98 -0
  87. package/src/graph.ts +6 -0
  88. package/src/host.ts +5 -9
  89. package/src/import-bindings.ts +128 -0
  90. package/src/indexed-access.ts +152 -30
  91. package/src/interface-satisfaction.ts +33 -13
  92. package/src/intra-function-parts.ts +394 -59
  93. package/src/inventory.ts +71 -7
  94. package/src/kinds.ts +13 -13
  95. package/src/matrix.ts +104 -15
  96. package/src/mdx-entries.ts +241 -0
  97. package/src/module-declarations.ts +11 -13
  98. package/src/overrides.ts +51 -12
  99. package/src/print.ts +1 -0
  100. package/src/ref.ts +12 -0
  101. package/src/references.ts +48 -3
  102. package/src/report.ts +16 -3
  103. package/src/resolve.ts +3 -0
  104. package/src/roots.ts +163 -45
  105. package/src/schema.ts +1 -0
  106. package/src/serialization-contract.ts +55 -4
  107. package/src/session.ts +7 -0
  108. package/src/source-files.ts +66 -16
  109. package/src/suppress-file.ts +12 -1
  110. package/src/suppress-inline.ts +18 -1
  111. package/src/suppress.ts +10 -5
  112. package/src/sweep.ts +32 -11
  113. package/src/symbol-keys.ts +129 -2
  114. package/src/test-support.ts +155 -18
  115. package/src/type-errors.ts +38 -6
  116. package/src/type-parameter-reads.ts +227 -0
  117. package/src/verbs/analyze.ts +22 -0
  118. package/src/verbs/explain.ts +5 -2
  119. package/src/version.ts +1 -1
  120. package/src/workflow-steps.ts +127 -22
  121. package/src/workspace-programs.ts +7 -3
  122. package/src/workspace-resolution.ts +62 -10
@@ -6,8 +6,9 @@
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
13
  import { scriptTokens } from "./manifest.js";
13
14
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
@@ -18,6 +19,8 @@ const MANIFEST = "package.json";
18
19
  const MODULES_DIR = "node_modules";
19
20
  const MODULES_SEGMENT = `/${MODULES_DIR}/`;
20
21
  const TYPES_SCOPE = "@types/";
22
+ /** A script token naming a JavaScript file by a path no expansion changes. */
23
+ const SCRIPT_FILE = /^[^$`{*?[~]+\.(?:js|mjs|cjs)$/u;
21
24
  /** The manifest sections a dependency is declared in, each by the selector its reference spells. */
22
25
  const SECTIONS = [
23
26
  ["dependencies", "dependency"],
@@ -30,7 +33,7 @@ const PROJECT_OWNER = "";
30
33
  export function configurationNeeds(packages) {
31
34
  return {
32
35
  uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
33
- at: new Map([[ANYWHERE, new Set(packages)]]),
36
+ at: new Map([[ANYWHERE, new Map(packages.map((one) => [one, new Set([PROJECT_OWNER])]))]]),
34
37
  unanswered: [],
35
38
  };
36
39
  }
@@ -99,12 +102,35 @@ function typePackages(name, programPackages) {
99
102
  }
100
103
  return [typed, named];
101
104
  }
105
+ /**
106
+ * The strings the `extends` member of one compiler configuration file holds, read as the
107
+ * compiler reads the file, comments and trailing commas allowed; none where it cannot be read.
108
+ */
109
+ function extendsOf(host, path, parse) {
110
+ let text;
111
+ try {
112
+ text = host.readFile(path);
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ const [statement] = parse(path, text).statements;
118
+ const document = statement !== undefined && isExpressionStatement(statement) ? statement.expression : undefined;
119
+ if (document === undefined || !isObjectLiteralExpression(document)) {
120
+ return [];
121
+ }
122
+ const value = document.properties
123
+ .filter(isPropertyAssignment)
124
+ .findLast((property) => isLiteralText(property.name) && property.name.text === "extends")?.initializer;
125
+ const entries = value !== undefined && isArrayLiteralExpression(value) ? value.elements : [value];
126
+ return entries.flatMap((entry) => entry !== undefined && isLiteralText(entry) ? [entry.text] : []);
127
+ }
102
128
  /**
103
129
  * The packages the compiler configuration of one project names: its type packages,
104
130
  * its JSX import source, the helper library it imports, its plugins, and every
105
131
  * package a configuration file it extends through is read from.
106
132
  */
107
- function configurationPackages(host, configFile, options, programPackages) {
133
+ function configurationPackages(host, configFile, options, programPackages, parse) {
108
134
  const found = new Set();
109
135
  const add = (name) => {
110
136
  const named = typeof name === "string" ? packageOfSpecifier(name) : undefined;
@@ -139,12 +165,7 @@ function configurationPackages(host, configFile, options, programPackages) {
139
165
  continue;
140
166
  }
141
167
  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
- }
168
+ for (const entry of extendsOf(host, file, parse)) {
148
169
  if (entry.startsWith(".") || isAbsolutePath(entry)) {
149
170
  const local = resolvePath(dirnamePath(file), entry);
150
171
  pending.push(local.endsWith(".json") ? local : `${local}.json`);
@@ -207,13 +228,19 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
207
228
  programPackages.add(named);
208
229
  }
209
230
  }
210
- const packages = configurationPackages(host, project.configFile, project.program.getCompilerOptions(), programPackages);
231
+ const options = project.program.getCompilerOptions();
232
+ const packages = configurationPackages(host, project.configFile, options, programPackages, parse);
233
+ const automatic = automaticTypes(host, project.configFile, options, parse);
211
234
  const uses = new Map();
212
235
  const at = new Map();
213
236
  const use = (owners, named, path) => {
214
237
  for (const one of named) {
215
- at.set(path, (at.get(path) ?? new Set()).add(one));
238
+ const inFile = at.get(path) ?? new Map();
239
+ at.set(path, inFile);
240
+ const holders = inFile.get(one) ?? new Set();
241
+ inFile.set(one, holders);
216
242
  for (const owner of owners) {
243
+ holders.add(owner);
217
244
  let set = uses.get(owner);
218
245
  if (set === undefined) {
219
246
  set = new Set();
@@ -229,7 +256,7 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
229
256
  const inFile = (owners, named) => {
230
257
  use(owners, named, file.fileName);
231
258
  };
232
- if (fileNeeds(project, held, file, programPackages, inFile)) {
259
+ if (fileNeeds(project, held, file, { programPackages, automatic }, inFile)) {
233
260
  unanswered.push(file.fileName);
234
261
  }
235
262
  }
@@ -243,7 +270,8 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
243
270
  }
244
271
  return names;
245
272
  };
246
- const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot), parse);
273
+ const files = sourceFilesOf(project, targetRoot);
274
+ const configuration = configurationFiles(host, targetRoot, files, parse);
247
275
  for (const { file, manifestDir } of configuration.modules) {
248
276
  const names = declaredAt(joinPath(manifestDir, MANIFEST));
249
277
  for (const { node, text } of moduleStrings(file)) {
@@ -258,7 +286,8 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
258
286
  }
259
287
  }
260
288
  }
261
- for (const { file, manifestDirs } of configuration.outside) {
289
+ const ranFiles = outsideChain(host, targetRoot, files, parse, scriptRunFiles(host, targetRoot, files));
290
+ for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
262
291
  for (const manifestDir of manifestDirs) {
263
292
  const names = declaredAt(joinPath(manifestDir, "package.json"));
264
293
  for (const { text } of moduleStrings(file)) {
@@ -268,6 +297,48 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
268
297
  }
269
298
  return { uses, at, unanswered };
270
299
  }
300
+ /**
301
+ * The JavaScript files no program holds that a script of a manifest at or above one of the
302
+ * project's files runs, each with the directory of that manifest, and those a workflow
303
+ * step or git hook runs, with the target root's: a token naming such a file below the
304
+ * target root and in no `node_modules`, read against the manifest's directory.
305
+ */
306
+ function scriptRunFiles(host, targetRoot, files) {
307
+ const root = resolvePath(host.workingDirectory(), targetRoot);
308
+ const dirs = new Set([root]);
309
+ for (const name of files.byName.keys()) {
310
+ for (let dir = dirnamePath(name); !dirs.has(dir); dir = dirnamePath(dir)) {
311
+ if (relativePath(root, dir) === undefined) {
312
+ break;
313
+ }
314
+ dirs.add(dir);
315
+ }
316
+ }
317
+ const tokens = new Map([
318
+ [root, new Set(workflowTokens(host, root))],
319
+ ]);
320
+ for (const dir of dirs) {
321
+ const read = readJSON(host, joinPath(dir, MANIFEST));
322
+ if (read !== undefined && isRecord(read.value)) {
323
+ const own = scriptTokensOf(read.value);
324
+ tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
325
+ }
326
+ }
327
+ const found = [];
328
+ for (const [manifestDir, held] of tokens) {
329
+ for (const token of held) {
330
+ const path = SCRIPT_FILE.test(token) ? resolvePath(manifestDir, token) : undefined;
331
+ const below = path === undefined ? undefined : relativePath(root, path);
332
+ if (path !== undefined &&
333
+ below?.split("/").includes(MODULES_DIR) === false &&
334
+ !files.byName.has(path) &&
335
+ host.kindOf(path) === "file") {
336
+ found.push({ path, manifestDir });
337
+ }
338
+ }
339
+ }
340
+ return found;
341
+ }
271
342
  /** The declarations of the inventory a use written at one node belongs to. */
272
343
  function ownersOf(file, held, node) {
273
344
  const parent = node.parent;
@@ -291,8 +362,27 @@ function ownersOf(file, held, node) {
291
362
  }
292
363
  return [PROJECT_OWNER];
293
364
  }
294
- /** Records the needs of one file, and answers whether a specifier of it went unanswered. */
295
- function fileNeeds(project, held, file, programPackages, use) {
365
+ /**
366
+ * Whether one identifier stands for a value or type of the scope it is written in, rather
367
+ * than naming a member, a property or a label.
368
+ */
369
+ function isScopeName(node) {
370
+ const parent = node.parent;
371
+ if (parent === undefined || isShorthandPropertyAssignment(parent)) {
372
+ return true;
373
+ }
374
+ const named = parent;
375
+ return (named.name !== node &&
376
+ (parent.kind !== SyntaxKind.QualifiedName || named.right !== node) &&
377
+ named.propertyName !== node &&
378
+ named.label !== node);
379
+ }
380
+ /**
381
+ * Records the needs of one file, and answers whether a specifier of it went unanswered. A
382
+ * module specifier or a name of the file's scope that resolves to nothing, and that an
383
+ * automatically included type package declares, uses that package.
384
+ */
385
+ function fileNeeds(project, held, file, { programPackages, automatic }, use) {
296
386
  const specifiers = [...file.imports, ...file.moduleAugmentations].filter(isLiteralText);
297
387
  const bindings = [];
298
388
  const bound = new Set();
@@ -309,16 +399,22 @@ function fileNeeds(project, held, file, programPackages, use) {
309
399
  }
310
400
  const names = new Set(bindings.map(({ node }) => node.text));
311
401
  const candidates = [];
402
+ const globals = [];
312
403
  const visit = (node) => {
313
- if (isIdentifier(node) && names.has(node.text) && !bound.has(node)) {
314
- candidates.push(node);
404
+ if (isIdentifier(node) && !bound.has(node)) {
405
+ if (names.has(node.text)) {
406
+ candidates.push(node);
407
+ }
408
+ else if (automatic.globals.has(node.text) && isScopeName(node)) {
409
+ globals.push(node);
410
+ }
315
411
  }
316
412
  node.forEachChild(visit);
317
413
  };
318
- if (names.size > 0) {
414
+ if (names.size > 0 || automatic.globals.size > 0) {
319
415
  visit(file);
320
416
  }
321
- const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates];
417
+ const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates, ...globals];
322
418
  const handles = asked.map((node) => project.handle(node));
323
419
  const resolved = project.symbolsAt(handles);
324
420
  const packagesOf = new Map();
@@ -329,6 +425,9 @@ function fileNeeds(project, held, file, programPackages, use) {
329
425
  named.add(byName);
330
426
  }
331
427
  const symbol = resolved[index];
428
+ if (symbol === undefined) {
429
+ automatic.modules.get(specifier.text)?.forEach((one) => named.add(one));
430
+ }
332
431
  for (const declaration of typeof symbol === "object" ? symbol.declarations : []) {
333
432
  const byPath = packageOfPath(declaration.path);
334
433
  if (byPath !== undefined) {
@@ -364,7 +463,7 @@ function fileNeeds(project, held, file, programPackages, use) {
364
463
  let symbol = resolved[offset + index];
365
464
  const parent = node.parent;
366
465
  if (parent !== undefined && isShorthandPropertyAssignment(parent) && parent.name === node) {
367
- symbol = project.shorthandValueAt(project.handle(node));
466
+ symbol = project.shorthandValueAt(project.handle(parent));
368
467
  }
369
468
  const named = symbol === UNANSWERED || (unresolvedNames.has(node.text) && symbol === undefined)
370
469
  ? byName.get(node.text)
@@ -376,6 +475,13 @@ function fileNeeds(project, held, file, programPackages, use) {
376
475
  use(ownersOf(file, held, node), named);
377
476
  }
378
477
  });
478
+ const unresolvedAt = offset + candidates.length;
479
+ globals.forEach((node, index) => {
480
+ const symbol = resolved[unresolvedAt + index];
481
+ if (symbol === undefined || symbol === UNANSWERED) {
482
+ use(ownersOf(file, held, node), automatic.globals.get(node.text) ?? []);
483
+ }
484
+ });
379
485
  for (const reference of file.typeReferenceDirectives) {
380
486
  use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
381
487
  }
@@ -567,6 +673,18 @@ function manifestDir(dependency) {
567
673
  const dir = dirnamePath(dependency.position.path);
568
674
  return dir === "." ? "" : dir;
569
675
  }
676
+ /**
677
+ * The declarations of one name a use written at `below` needs: those of the nearest
678
+ * manifest at or above it that declares the name, or every one for a use no file holds.
679
+ */
680
+ function nearestDeclaring(declarations, below) {
681
+ if (below === undefined) {
682
+ return declarations;
683
+ }
684
+ const enclosing = declarations.filter((dependency) => isInside(below, manifestDir(dependency)));
685
+ const nearest = Math.max(-1, ...enclosing.map((dependency) => manifestDir(dependency).length));
686
+ return enclosing.filter((dependency) => manifestDir(dependency).length === nearest);
687
+ }
570
688
  /**
571
689
  * Adds to `needed` each declared dependency that the installed manifest of a needed or
572
690
  * run declared dependency names as a peer, until no further one joins: the declaration of
@@ -646,6 +764,8 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
646
764
  // A declaration several projects compile is one user, and a use any project makes is
647
765
  // a use, so the uses are merged by declaration before anything is counted.
648
766
  const needed = new Set();
767
+ const live = new Set();
768
+ const holders = new Map();
649
769
  const byName = new Map();
650
770
  for (const dependency of declared) {
651
771
  byName.set(dependency.name, [...(byName.get(dependency.name) ?? []), dependency]);
@@ -656,7 +776,10 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
656
776
  // or above its file declares, and what the configuration needs is never deleted.
657
777
  const unanswered = project.unanswered.flatMap((file) => relativePath(root, file) ?? []);
658
778
  const unknownDeclared = declared.filter((dependency) => unanswered.some((file) => isInside(file, manifestDir(dependency))));
659
- unknownDeclared.forEach((dependency) => needed.add(declarationKey(dependency)));
779
+ for (const dependency of unknownDeclared) {
780
+ needed.add(declarationKey(dependency));
781
+ live.add(declarationKey(dependency));
782
+ }
660
783
  const unknown = unknownDeclared.map((dependency) => dependency.name);
661
784
  const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)]];
662
785
  for (const [id, used] of [...project.uses, ...held]) {
@@ -666,16 +789,32 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
666
789
  }
667
790
  for (const [path, named] of project.at) {
668
791
  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));
792
+ for (const [one, owners] of named) {
793
+ // An import none of whose bindings is used is no evidence about the code using it.
794
+ const unheld = owners.size === 0;
795
+ for (const dependency of nearestDeclaring(byName.get(one) ?? [], below)) {
796
+ const key = declarationKey(dependency);
797
+ needed.add(key);
798
+ if (unheld) {
799
+ live.add(key);
800
+ }
801
+ else {
802
+ const held = holders.get(key) ?? new Set();
803
+ owners.forEach((owner) => held.add(owner));
804
+ holders.set(key, held);
673
805
  }
674
806
  }
675
807
  }
676
808
  }
677
809
  }
678
810
  addNeededPeers(declared, peers, needed, ran);
811
+ addNeededPeers(declared, peers, live, ran);
812
+ const heldBy = new Map();
813
+ for (const [key, held] of holders) {
814
+ if (!live.has(key) && !ran.has(key)) {
815
+ heldBy.set(key, [...held].sort((a, b) => (a < b ? -1 : 1)));
816
+ }
817
+ }
679
818
  const users = new Map();
680
819
  for (const used of uses.values()) {
681
820
  used.forEach((one) => users.set(one, (users.get(one) ?? 0) + 1));
@@ -688,5 +827,21 @@ export function dependenciesOf(host, targetRoot, perProject, candidates, members
688
827
  lastUses.set(id, sole.sort((a, b) => (a < b ? -1 : 1)));
689
828
  }
690
829
  }
691
- return { declared, needed, ran, unbuilt, lastUses };
830
+ const owners = new Map();
831
+ for (const [owner, used] of uses) {
832
+ for (const one of used) {
833
+ if (names.has(one)) {
834
+ owners.set(one, (owners.get(one) ?? new Set()).add(owner));
835
+ }
836
+ }
837
+ }
838
+ const fileLastUses = new Map();
839
+ for (const [one, held] of [...owners].sort(([a], [b]) => (a < b ? -1 : 1))) {
840
+ const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
841
+ const [path] = paths;
842
+ if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
843
+ fileLastUses.set(path, [...(fileLastUses.get(path) ?? []), one]);
844
+ }
845
+ }
846
+ return { declared, needed, ran, unbuilt, lastUses, fileLastUses, heldBy };
692
847
  }
@@ -1,6 +1,8 @@
1
1
  import { DiagnosticCategory } from "@typescript/native/sync";
2
- import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
2
+ import { baseUrlPaths } from "./base-url.js";
3
+ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
3
4
  import { SetupError } from "./setup-failure.js";
5
+ import { writtenPath } from "./workspace-programs.js";
4
6
  /** Directory names discovery does not descend into. */
5
7
  export const IGNORED_DIRECTORIES = new Set(["node_modules", ".git"]);
6
8
  /** A compiler configuration file discovery recognises under a root. */
@@ -62,10 +64,17 @@ export function diagnosticErrors(sets) {
62
64
  }
63
65
  /**
64
66
  * The codes of a type library and a lib the configuration names that the compiler
65
- * cannot find, which it reports while it gathers the program's files:
66
- * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go
67
+ * cannot find, which it reports while it gathers the program's files,
68
+ * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go,
69
+ * and {@link MISSING_GLOBAL_TYPE}, which the checker reports.
67
70
  */
68
- const UNFOUND_LIBRARY = new Set([2688, 2726, 2727]);
71
+ const UNFOUND_LIBRARY = new Set([2318, 2688, 2726, 2727]);
72
+ /**
73
+ * The code of a global type the checker finds in no library the program holds. The checker
74
+ * types every use of one against nothing and places no error in the file, so the analysis
75
+ * skips each file of such a project whole.
76
+ */
77
+ export const MISSING_GLOBAL_TYPE = 2318;
69
78
  /** The errors one open project carries, each located in the file it names. */
70
79
  export function projectErrors(sets) {
71
80
  // The compiler numbers its semantic messages from 2000 and its option messages from
@@ -215,19 +224,101 @@ export function consumerProjects(engine, host, root) {
215
224
  function derivedId(targetRoot, configFile) {
216
225
  return relativePath(targetRoot, configFile) ?? configFile;
217
226
  }
227
+ /**
228
+ * The code of a configuration whose `extends` chain returns to a configuration it already
229
+ * passed. The parse reports it and returns, and a snapshot opened from it never answers:
230
+ * https://github.com/microsoft/TypeScript/issues/64754
231
+ */
232
+ const CIRCULAR_EXTENDS = 18000;
233
+ /** The configuration files one parsed configuration's own `extends` names by path. */
234
+ function extendedPaths(parsed, configFile) {
235
+ const { raw } = parsed;
236
+ const named = typeof raw === "object" && raw !== null ? Reflect.get(raw, "extends") : [];
237
+ return (Array.isArray(named) ? named : [named])
238
+ .filter((one) => typeof one === "string")
239
+ .filter((one) => one.startsWith("./") || one.startsWith("../") || isAbsolutePath(one))
240
+ .map((one) => resolvePath(dirnamePath(configFile), one));
241
+ }
242
+ /**
243
+ * The `extends` chain from `configFile` to the first configuration it reaches twice, each
244
+ * link by path, or undefined where no chain written by path returns.
245
+ */
246
+ function extendsCycle(engine, configFile, parsed) {
247
+ const walk = (chain, at) => {
248
+ for (const next of extendedPaths(at, chain.at(-1) ?? configFile)) {
249
+ if (chain.includes(next)) {
250
+ return [...chain, next];
251
+ }
252
+ let read;
253
+ try {
254
+ read = engine.parseConfigFile(next);
255
+ }
256
+ catch {
257
+ continue;
258
+ }
259
+ const cycle = walk([...chain, next], read);
260
+ if (cycle !== undefined) {
261
+ return cycle;
262
+ }
263
+ }
264
+ return undefined;
265
+ };
266
+ return walk([configFile], parsed);
267
+ }
268
+ /** The configuration files one parsed configuration's `references` name. */
269
+ function referencesOf(parsed) {
270
+ // The parse answers a directory reference as the directory; the compiler reads a
271
+ // path not ending `.json` as its `tsconfig.json`:
272
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
273
+ return (parsed.projectReferences ?? []).map(({ path }) => path.endsWith(".json") ? path : joinPath(path, "tsconfig.json"));
274
+ }
275
+ /** Why one parsed configuration's own `extends` chain cannot be opened, where it is circular. */
276
+ function circularity(engine, configFile, parsed) {
277
+ const chain = extendsCycle(engine, configFile, parsed);
278
+ if (chain === undefined && !parsed.errors.some(({ code }) => code === CIRCULAR_EXTENDS)) {
279
+ return undefined;
280
+ }
281
+ return `TS${String(CIRCULAR_EXTENDS)}: Circularity detected while resolving configuration: ${(chain ?? [configFile]).join(" -> ")}`;
282
+ }
283
+ /**
284
+ * Why a configuration the `references` reach, transitively, cannot be opened, where one's
285
+ * `extends` chain is circular: the compiler opens every referenced project with the one
286
+ * that references it.
287
+ */
288
+ function referencedCircularity(engine, references, visited) {
289
+ for (const reference of references) {
290
+ if (visited.has(reference)) {
291
+ continue;
292
+ }
293
+ visited.add(reference);
294
+ let parsed;
295
+ try {
296
+ parsed = engine.parseConfigFile(reference);
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ const reason = circularity(engine, reference, parsed) ??
302
+ referencedCircularity(engine, referencesOf(parsed), visited);
303
+ if (reason !== undefined) {
304
+ return reason;
305
+ }
306
+ }
307
+ return undefined;
308
+ }
218
309
  /**
219
310
  * The configuration files one configuration's `references` name, or why the compiler
220
- * cannot read the configuration.
311
+ * cannot read the configuration: it throws, or its `extends` chain or that of a
312
+ * configuration it references is circular, which the analysis refuses before any
313
+ * snapshot is asked to open it.
221
314
  */
222
315
  function readConfiguration(engine, configFile) {
223
316
  try {
224
317
  const parsed = engine.parseConfigFile(configFile);
225
- // The parse answers a directory reference as the directory; the compiler reads a
226
- // path not ending `.json` as its `tsconfig.json`:
227
- // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
228
- return {
229
- references: (parsed.projectReferences ?? []).map(({ path }) => path.endsWith(".json") ? path : joinPath(path, "tsconfig.json")),
230
- };
318
+ const references = referencesOf(parsed);
319
+ const reason = circularity(engine, configFile, parsed) ??
320
+ referencedCircularity(engine, references, new Set([configFile]));
321
+ return reason === undefined ? { references } : { reason };
231
322
  }
232
323
  catch (error) {
233
324
  return { reason: error instanceof Error ? error.message : String(error) };
@@ -345,3 +436,33 @@ export function discoverProjects(engine, host, scope, configurations = []) {
345
436
  }
346
437
  return derivedProjects(engine, host, scope);
347
438
  }
439
+ /**
440
+ * How the snapshot reads each configuration of `configFiles` that sets `baseUrl` and
441
+ * `programs` does not already describe: a configuration only the snapshot holds, which
442
+ * extends it and states the `paths` {@link baseUrlPaths} gives, so its imports resolve as
443
+ * the compilers before version 7 resolve them. A configuration the compiler cannot read is
444
+ * left for its project to refuse.
445
+ */
446
+ export function withBaseUrlPaths(engine, host, configFiles, programs) {
447
+ const found = new Map(programs);
448
+ for (const configFile of configFiles) {
449
+ if (found.has(configFile)) {
450
+ continue;
451
+ }
452
+ let paths;
453
+ try {
454
+ paths = baseUrlPaths(engine.parseConfigFile(configFile).options);
455
+ }
456
+ catch {
457
+ continue;
458
+ }
459
+ if (paths !== undefined) {
460
+ found.set(configFile, {
461
+ kind: "configuration",
462
+ file: writtenPath(host, configFile),
463
+ text: `${JSON.stringify({ extends: configFile, compilerOptions: { paths } }, null, 2)}\n`,
464
+ });
465
+ }
466
+ }
467
+ return found;
468
+ }
@@ -43,7 +43,6 @@ const UNREAD_TOOLS = [
43
43
  "cypress",
44
44
  "danger",
45
45
  "dependency-cruiser",
46
- "docusaurus",
47
46
  "dotenv",
48
47
  "drizzle",
49
48
  "electron-vite",
@@ -97,14 +96,12 @@ const UNREAD_TOOLS = [
97
96
  "nuxtjs-i18n",
98
97
  "nx",
99
98
  "nyc",
100
- "oclif",
101
99
  "openapi-ts",
102
100
  "openclaw",
103
101
  "orval",
104
102
  "oxfmt",
105
103
  "oxlint",
106
104
  "panda-css",
107
- "parcel",
108
105
  "payload",
109
106
  "pino",
110
107
  "playwright-ct",
@@ -149,7 +146,6 @@ const UNREAD_TOOLS = [
149
146
  "swc",
150
147
  "syncpack",
151
148
  "tailwind",
152
- "tanstack-router",
153
149
  "taskfile",
154
150
  "tauri",
155
151
  "temporal",
@@ -193,18 +189,19 @@ const UNREAD_TOOLS = [
193
189
  ];
194
190
  /** The conventions declined for a tool some of whose conventions are entered. */
195
191
  const PARTLY_ENTERED = [
192
+ {
193
+ tool: "docusaurus",
194
+ convention: "a page or theme directory a plugin's configuration moves, and the docs",
195
+ reason: "The convention row reads src/pages and src/theme only, and a document's imports are " +
196
+ "read from every Markdown and MDX file, so a module only a moved directory holds is " +
197
+ "reported as never imported unless ts.entry_files names it.",
198
+ },
196
199
  {
197
200
  tool: "eslint",
198
201
  convention: "a legacy configuration written in YAML or in JSON with comments",
199
202
  reason: "Only a configuration module and a strict JSON configuration file are read, so a " +
200
203
  "dependency only such a file names is reported as unused.",
201
204
  },
202
- {
203
- tool: "eslint",
204
- convention: "a plugin or shareable configuration named without its package's prefix",
205
- reason: "A string uses a dependency only where it spells the dependency's name, so a " +
206
- "dependency only a shortened name loads is reported as unused.",
207
- },
208
205
  {
209
206
  tool: "playwright",
210
207
  convention: "a testMatch written as a regular expression",