@cplieger/deadset-ts 3.12.1 → 4.0.0

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 (120) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +217 -48
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/components.js +4 -2
  8. package/dist/src/configured-declarations.js +56 -7
  9. package/dist/src/conformance-record.js +2 -2
  10. package/dist/src/dependencies.js +34 -17
  11. package/dist/src/deprecation.js +35 -24
  12. package/dist/src/destructuring.js +37 -29
  13. package/dist/src/discover.js +105 -28
  14. package/dist/src/edges.js +4 -1
  15. package/dist/src/entry-points.js +7 -0
  16. package/dist/src/enum-group.js +46 -40
  17. package/dist/src/exempt.js +42 -1
  18. package/dist/src/findings/completion.js +37 -8
  19. package/dist/src/findings/emitters.js +20 -6
  20. package/dist/src/findings/interfaces.js +7 -1
  21. package/dist/src/findings/intra-function.js +91 -2
  22. package/dist/src/findings/self-check.js +30 -7
  23. package/dist/src/findings/unused-declarations.js +15 -3
  24. package/dist/src/findings-pass.js +18 -11
  25. package/dist/src/framework-lifecycle.js +8 -6
  26. package/dist/src/implementations.js +45 -23
  27. package/dist/src/interface-satisfaction.js +30 -27
  28. package/dist/src/intra-function-parts.js +660 -0
  29. package/dist/src/inventory.js +85 -4
  30. package/dist/src/json-chunks.js +76 -0
  31. package/dist/src/kinds.js +337 -35
  32. package/dist/src/partial.js +23 -0
  33. package/dist/src/query.js +207 -0
  34. package/dist/src/references.js +92 -20
  35. package/dist/src/reflective-lookup.js +46 -4
  36. package/dist/src/report.js +14 -3
  37. package/dist/src/reporters/annotations.js +9 -5
  38. package/dist/src/reporters/json.js +3 -2
  39. package/dist/src/reporters/sarif.js +22 -16
  40. package/dist/src/reporters/template.js +112 -12
  41. package/dist/src/reporters/text.js +8 -6
  42. package/dist/src/roots.js +27 -11
  43. package/dist/src/serialization-contract.js +53 -32
  44. package/dist/src/session.js +79 -39
  45. package/dist/src/signatures.js +32 -0
  46. package/dist/src/suppress-file.js +29 -7
  47. package/dist/src/suppress-inline.js +8 -1
  48. package/dist/src/suppress.js +27 -0
  49. package/dist/src/sweep.js +4 -1
  50. package/dist/src/value-reach.js +44 -25
  51. package/dist/src/verbs/analyze.js +39 -14
  52. package/dist/src/verbs/describe.js +12 -8
  53. package/dist/src/verbs/explain.js +22 -9
  54. package/dist/src/verbs/print-projects.js +40 -7
  55. package/dist/src/verbs/print-retained.js +6 -2
  56. package/dist/src/verbs/print-roots.js +5 -1
  57. package/dist/src/version.js +1 -1
  58. package/jsr.json +1 -1
  59. package/package.json +3 -3
  60. package/src/alias-chain.ts +75 -11
  61. package/src/analysis.ts +385 -104
  62. package/src/calls.ts +10 -9
  63. package/src/class-members.ts +10 -11
  64. package/src/components.ts +4 -2
  65. package/src/configured-declarations.ts +80 -8
  66. package/src/conformance-record.ts +2 -2
  67. package/src/dependencies.ts +42 -20
  68. package/src/deprecation.ts +53 -27
  69. package/src/destructuring.ts +50 -39
  70. package/src/discover.ts +177 -33
  71. package/src/edges.ts +4 -1
  72. package/src/entry-points.ts +7 -0
  73. package/src/enum-group.ts +56 -42
  74. package/src/exempt.ts +50 -2
  75. package/src/finding.ts +5 -1
  76. package/src/findings/completion.ts +46 -10
  77. package/src/findings/emitter.ts +3 -0
  78. package/src/findings/emitters.ts +27 -6
  79. package/src/findings/interfaces.ts +7 -1
  80. package/src/findings/intra-function.ts +106 -3
  81. package/src/findings/self-check.ts +48 -7
  82. package/src/findings/unused-declarations.ts +18 -4
  83. package/src/findings-pass.ts +26 -11
  84. package/src/framework-lifecycle.ts +10 -6
  85. package/src/host.ts +5 -3
  86. package/src/implementations.ts +59 -23
  87. package/src/interface-satisfaction.ts +30 -27
  88. package/src/intra-function-parts.ts +889 -0
  89. package/src/inventory.ts +97 -4
  90. package/src/json-chunks.ts +87 -0
  91. package/src/kinds.ts +355 -41
  92. package/src/partial.ts +40 -0
  93. package/src/query.ts +522 -0
  94. package/src/references.ts +119 -38
  95. package/src/reflective-lookup.ts +56 -6
  96. package/src/report.ts +32 -4
  97. package/src/reporters/annotations.ts +23 -21
  98. package/src/reporters/json.ts +3 -2
  99. package/src/reporters/reporter.ts +2 -2
  100. package/src/reporters/sarif.ts +27 -18
  101. package/src/reporters/template.ts +119 -14
  102. package/src/reporters/text.ts +12 -12
  103. package/src/roots.ts +34 -12
  104. package/src/serialization-contract.ts +59 -40
  105. package/src/session.ts +146 -58
  106. package/src/signatures.ts +55 -0
  107. package/src/suppress-file.ts +40 -8
  108. package/src/suppress-inline.ts +8 -1
  109. package/src/suppress.ts +31 -0
  110. package/src/sweep.ts +9 -4
  111. package/src/value-reach.ts +66 -28
  112. package/src/verbs/analyze.ts +51 -14
  113. package/src/verbs/describe.ts +13 -9
  114. package/src/verbs/explain.ts +20 -9
  115. package/src/verbs/print-projects.ts +53 -7
  116. package/src/verbs/print-retained.ts +6 -2
  117. package/src/verbs/print-roots.ts +5 -1
  118. package/src/version.ts +1 -1
  119. package/dist/src/reporters/rules.js +0 -178
  120. package/src/reporters/rules.ts +0 -195
@@ -25,6 +25,7 @@ import {
25
25
  type Node,
26
26
  } from "@typescript/native/unstable/ast";
27
27
  import { TypeFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
28
+ import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
28
29
  import type { ProjectView } from "./session.ts";
29
30
 
30
31
  /** One property one object pattern reads. */
@@ -212,97 +213,102 @@ interface DestructuringCost {
212
213
  interface Destructuring {
213
214
  /**
214
215
  * The property symbol each read names, in the order given, `undefined` where the
215
- * destructured value's type declares no property of that name or is not known.
216
+ * destructured value's type declares no property of that name or is not known, and
217
+ * {@link UNANSWERED} where a question about the value's type went unanswered.
216
218
  */
217
- resolve(reads: readonly PropertyRead[]): readonly (TSSymbol | undefined)[];
219
+ resolve(reads: readonly PropertyRead[]): readonly Answer<TSSymbol | undefined>[];
218
220
  readonly cost: DestructuringCost;
219
221
  }
220
222
 
223
+ /** A type along a destructuring, or the mark that a question about it went unanswered. */
224
+ type Found = Answer<Type | undefined>;
225
+
221
226
  /**
222
227
  * The resolution over one project, its batches capped at `cap`. Property tables are
223
228
  * read once per type for the whole project, since one type is destructured in many
224
229
  * places and a table answers every name of it.
225
230
  */
226
231
  export function destructuring<Brand>(project: ProjectView<Brand>, cap: number): Destructuring {
227
- const checker = project.checker;
228
- const tables = new Map<number, ReadonlyMap<string, TSSymbol>>();
232
+ const queries = project.queries;
233
+ const tables = new Map<number, Answer<ReadonlyMap<string, TSSymbol>>>();
229
234
  let patternBatches = 0;
230
235
  let patternLookups = 0;
231
236
 
232
- const typesAt = (nodes: readonly Node[]): (Type | undefined)[] => {
233
- const types: (Type | undefined)[] = [];
234
- for (let from = 0; from < nodes.length; from += cap) {
235
- types.push(...checker.getTypeAtLocation(nodes.slice(from, from + cap)));
236
- patternBatches += 1;
237
- }
238
- return types;
239
- };
240
-
241
- const tableOf = (type: Type): ReadonlyMap<string, TSSymbol> => {
237
+ const tableOf = (type: Type): Answer<ReadonlyMap<string, TSSymbol>> => {
242
238
  const known = tables.get(type.id);
243
239
  if (known !== undefined) {
244
240
  return known;
245
241
  }
246
- const table = new Map<string, TSSymbol>();
242
+ let table: Answer<ReadonlyMap<string, TSSymbol>> = new Map<string, TSSymbol>();
247
243
  if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
248
244
  patternLookups += 1;
249
- for (const property of checker.getPropertiesOfType(type)) {
250
- table.set(property.name, property);
251
- }
245
+ const properties = queries.propertiesOf(type);
246
+ table = isAnswered(properties)
247
+ ? new Map(properties.map((property) => [property.name, property]))
248
+ : UNANSWERED;
252
249
  }
253
250
  tables.set(type.id, table);
254
251
  return table;
255
252
  };
256
253
 
257
- const definedOf = (type: Type): Type | undefined => {
254
+ const definedOf = (type: Type): Found => {
258
255
  if ((type.flags & TypeFlags.Union) === 0) {
259
256
  return type;
260
257
  }
261
258
  patternLookups += 1;
262
- return checker.getNonNullableType(type);
259
+ return queries.nonNullable(type);
263
260
  };
264
261
 
265
262
  /**
266
263
  * The type one element of an array or tuple value has: a tuple's element at `index`,
267
264
  * an array's element type at any index, and `undefined` for a tuple iterated whole.
268
265
  */
269
- const elementOf = (type: Type, index: number | undefined): Type | undefined => {
266
+ const elementOf = (type: Type, index: number | undefined): Found => {
270
267
  if (!type.isTypeReference()) {
271
268
  return undefined;
272
269
  }
273
- patternLookups += 1;
274
- if (checker.isTupleType(type)) {
270
+ if (type.isTupleType()) {
275
271
  if (index === undefined) {
276
272
  return undefined;
277
273
  }
278
274
  patternLookups += 1;
279
- return checker.getTypeArguments(type)[index];
275
+ const elements = queries.typeArguments(type);
276
+ return isAnswered(elements) ? elements[index] : UNANSWERED;
280
277
  }
281
278
  patternLookups += 1;
282
- if (!checker.isArrayType(type)) {
279
+ const array = queries.isArray(type);
280
+ if (!isAnswered(array)) {
281
+ return UNANSWERED;
282
+ }
283
+ if (!array) {
283
284
  return undefined;
284
285
  }
285
286
  patternLookups += 1;
286
- return checker.getTypeArguments(type)[0];
287
+ const elements = queries.typeArguments(type);
288
+ return isAnswered(elements) ? elements[0] : UNANSWERED;
287
289
  };
288
290
 
289
- const step = (type: Type, next: Step): Type | undefined => {
291
+ const step = (type: Type, next: Step): Found => {
290
292
  const defined = definedOf(type);
291
- if (defined === undefined) {
292
- return undefined;
293
+ if (defined === undefined || defined === UNANSWERED) {
294
+ return defined;
293
295
  }
294
296
  if (next.kind === "property") {
295
- const property = tableOf(defined).get(next.name);
297
+ const table = tableOf(defined);
298
+ if (!isAnswered(table)) {
299
+ return UNANSWERED;
300
+ }
301
+ const property = table.get(next.name);
296
302
  if (property === undefined) {
297
303
  return undefined;
298
304
  }
299
305
  patternLookups += 1;
300
- return checker.getTypeOfSymbol(property);
306
+ return queries.typeOfSymbol(property);
301
307
  }
302
308
  return elementOf(defined, next.kind === "element" ? next.index : undefined);
303
309
  };
304
310
 
305
- const resolve = (reads: readonly PropertyRead[]): readonly (TSSymbol | undefined)[] => {
311
+ const resolve = (reads: readonly PropertyRead[]): readonly Answer<TSSymbol | undefined>[] => {
306
312
  const patterns = [...new Set(reads.map((read) => read.pattern))];
307
313
  const sources = new Map<Node, Source | undefined>();
308
314
  const asking = new Set<Node>();
@@ -317,15 +323,16 @@ export function destructuring<Brand>(project: ProjectView<Brand>, cap: number):
317
323
  asking.add(source.expression);
318
324
  }
319
325
  }
320
- const answered = new Map<Node, Type>();
321
326
  const asked = [...asking];
322
- typesAt(asked).forEach((type, index) => {
327
+ patternBatches += Math.ceil(asked.length / cap);
328
+ const answered = new Map<Node, Found>();
329
+ queries.typesAt(asked, cap).forEach((type, index) => {
323
330
  const node = asked[index];
324
- if (type !== undefined && node !== undefined) {
331
+ if (node !== undefined) {
325
332
  answered.set(node, type);
326
333
  }
327
334
  });
328
- const patternTypes = new Map<Node, Type | undefined>();
335
+ const patternTypes = new Map<Node, Found>();
329
336
  for (const pattern of patterns) {
330
337
  const source = sources.get(pattern);
331
338
  if (source === undefined) {
@@ -334,13 +341,17 @@ export function destructuring<Brand>(project: ProjectView<Brand>, cap: number):
334
341
  }
335
342
  let type = answered.get(source.expression);
336
343
  for (const next of source.steps) {
337
- type = type === undefined ? undefined : step(type, next);
344
+ type = type === undefined || type === UNANSWERED ? type : step(type, next);
338
345
  }
339
- patternTypes.set(pattern, type === undefined ? undefined : definedOf(type));
346
+ patternTypes.set(pattern, type === undefined || type === UNANSWERED ? type : definedOf(type));
340
347
  }
341
348
  return reads.map((read) => {
342
349
  const type = patternTypes.get(read.pattern);
343
- return type === undefined ? undefined : tableOf(type).get(read.name);
350
+ if (type === undefined || type === UNANSWERED) {
351
+ return type;
352
+ }
353
+ const table = tableOf(type);
354
+ return isAnswered(table) ? table.get(read.name) : UNANSWERED;
344
355
  });
345
356
  };
346
357
 
package/src/discover.ts CHANGED
@@ -4,7 +4,7 @@ import type { BuildConfiguration, ProjectConfiguration } from "./config.ts";
4
4
  import type { Host } from "./host.ts";
5
5
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
6
6
  import type { Scope } from "./scope.ts";
7
- import type { Engine } from "./session.ts";
7
+ import type { Engine, ProjectDiagnostics } from "./session.ts";
8
8
 
9
9
  /** Directory names discovery does not descend into. */
10
10
  export const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set(["node_modules", ".git"]);
@@ -20,24 +20,60 @@ const CONFIG_FILE = /^tsconfig.*\.json$/u;
20
20
  */
21
21
  export class DiscoveryError extends Error {
22
22
  /** The diagnostics the compiler reported, in the order it reported them. */
23
- readonly diagnostics: readonly Diagnostic[];
23
+ readonly diagnostics: readonly LocatedDiagnostic[];
24
24
 
25
- constructor(message: string, diagnostics: readonly Diagnostic[]) {
25
+ constructor(message: string, diagnostics: readonly LocatedDiagnostic[]) {
26
26
  super(message);
27
27
  this.name = "DiscoveryError";
28
28
  this.diagnostics = diagnostics;
29
29
  }
30
30
  }
31
31
 
32
+ /** The category an error diagnostic carries, which the protocol sends as a number. */
33
+ const ERROR_CATEGORY: number = DiagnosticCategory.Error;
34
+
32
35
  /** Whether one diagnostic is an error, which is what fails a run closed. */
33
36
  export function isError(diagnostic: Diagnostic): boolean {
34
- return diagnostic.category === DiagnosticCategory.Error;
37
+ return diagnostic.category === ERROR_CATEGORY;
35
38
  }
36
39
 
37
- /** One diagnostic as a line naming its position, its code and its message. */
38
- export function renderDiagnostic(diagnostic: Diagnostic): string {
39
- const where = diagnostic.fileName ?? "<no file>";
40
- return `${where}:${String(diagnostic.pos)}: TS${String(diagnostic.code)}: ${diagnostic.text}`;
40
+ /** One diagnostic, with the line and column its offset falls on where it names a file. */
41
+ export interface LocatedDiagnostic {
42
+ readonly diagnostic: Diagnostic;
43
+ /** Where the diagnostic starts, counted from one; absent where it names no position. */
44
+ readonly at?: { readonly line: number; readonly column: number } | undefined;
45
+ }
46
+
47
+ /**
48
+ * One diagnostic as a line naming its file, its line and column, its code and its
49
+ * message: `path:line:column: TScode: message`, the position left out where the
50
+ * diagnostic names none and the file where it names none. `display` spells the
51
+ * file's path; it answers the path itself where it is not given.
52
+ */
53
+ export function renderDiagnostic(
54
+ located: LocatedDiagnostic,
55
+ display: (path: string) => string = (path) => path,
56
+ ): string {
57
+ const { diagnostic, at } = located;
58
+ const message = `TS${String(diagnostic.code)}: ${diagnostic.text}`;
59
+ if (diagnostic.fileName === undefined) {
60
+ return message;
61
+ }
62
+ const where = display(diagnostic.fileName);
63
+ return at === undefined
64
+ ? `${where}: ${message}`
65
+ : `${where}:${String(at.line)}:${String(at.column)}: ${message}`;
66
+ }
67
+
68
+ /** Each diagnostic with the line and column its offset falls on. */
69
+ function locatedIn(
70
+ diagnostics: readonly Diagnostic[],
71
+ locate: (fileName: string, offset: number) => { line: number; column: number } | undefined,
72
+ ): readonly LocatedDiagnostic[] {
73
+ return diagnostics.map((diagnostic) => ({
74
+ diagnostic,
75
+ at: diagnostic.fileName === undefined ? undefined : locate(diagnostic.fileName, diagnostic.pos),
76
+ }));
41
77
  }
42
78
 
43
79
  /**
@@ -55,6 +91,26 @@ export function diagnosticErrors(sets: {
55
91
  return [...sets.configParsing, ...sets.syntactic, ...sets.semantic].filter(isError);
56
92
  }
57
93
 
94
+ /** Why one open project cannot be analyzed, split by whether it loaded at all. */
95
+ interface ProjectErrors {
96
+ /**
97
+ * The errors in the configuration itself: no input matched, an option that does not
98
+ * parse. A project carrying one did not load, whatever its files say.
99
+ */
100
+ readonly load: readonly LocatedDiagnostic[];
101
+ /** The errors in the files of a project that loaded: their syntax and their types. */
102
+ readonly check: readonly LocatedDiagnostic[];
103
+ }
104
+
105
+ /** The errors one open project carries, each located in the file it names. */
106
+ export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
107
+ const load = sets.configParsing.filter(isError);
108
+ return {
109
+ load: locatedIn(load, sets.locate),
110
+ check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
111
+ };
112
+ }
113
+
58
114
  /** One project a run analyzes. */
59
115
  export interface DiscoveredProject {
60
116
  /**
@@ -68,10 +124,54 @@ export interface DiscoveredProject {
68
124
  readonly configFile: string;
69
125
  }
70
126
 
127
+ /**
128
+ * One project discovery derived and the run could not build, with the first line of
129
+ * the error that dropped it.
130
+ */
131
+ export interface NotBuilt {
132
+ /** The name the project would have carried, as {@link DiscoveredProject.id} spells it. */
133
+ readonly id: string;
134
+ /** The compiler configuration file, absolute. */
135
+ readonly configFile: string;
136
+ /**
137
+ * The error's first line, every path below the target root spelled relative to it, so
138
+ * a report naming it carries no path of the machine it ran on.
139
+ */
140
+ readonly error: string;
141
+ }
142
+
143
+ /**
144
+ * The first line of one error about the target, every mention of the target root
145
+ * spelled relative to it: a path below the root loses the root and its separator, and
146
+ * the root itself is `.`.
147
+ */
148
+ export function targetRelative(text: string, targetRoot: string): string {
149
+ const root = targetRoot.endsWith("/") ? targetRoot.slice(0, -1) : targetRoot;
150
+ const [first = ""] = text.split(/\r?\n/u);
151
+ const spelled = first.split(`${root}/`).join("").split(root).join(".").trim();
152
+ return spelled === "" ? "the configuration could not be built" : spelled;
153
+ }
154
+
155
+ /**
156
+ * Whether one configuration is the analyzer's own guess: discovery derived it and the
157
+ * scope did not name it. A guess that does not load is dropped; any other ends the run.
158
+ */
159
+ export function isGuess(discovery: Discovery, configFile: string): boolean {
160
+ return discovery.derived && !discovery.fromScope.includes(configFile);
161
+ }
162
+
71
163
  /** Every project discovery found, and where each came from. */
72
164
  export interface Discovery {
73
165
  /** The projects, in discovery order, each once. */
74
166
  readonly projects: readonly DiscoveredProject[];
167
+ /**
168
+ * Whether discovery derived the projects rather than reading them from a declared
169
+ * matrix. A derived project that fails to load is dropped and named in
170
+ * {@link Discovery.notBuilt}; a declared one ends the run.
171
+ */
172
+ readonly derived: boolean;
173
+ /** The derived configurations the compiler could not read, in discovery order. */
174
+ readonly notBuilt: readonly NotBuilt[];
75
175
  /** The configuration file of each project, in the same order. */
76
176
  readonly configFiles: readonly string[];
77
177
  /**
@@ -174,9 +274,16 @@ function scopeConfigFiles(host: Host, scope: Scope): string[] {
174
274
  /**
175
275
  * Every configuration file reached from `pending`, in the order discovery reads them:
176
276
  * each once, each followed by the configurations it references, transitively. A
177
- * configuration the compiler cannot read ends the run naming it.
277
+ * configuration the compiler cannot read ends the run naming it, unless `unreadable`
278
+ * is given, which is then told the configuration and why, and the walk goes on
279
+ * without it.
178
280
  */
179
- function derivedFrom(engine: Engine, host: Host, pending: string[]): string[] {
281
+ function derivedFrom(
282
+ engine: Engine,
283
+ host: Host,
284
+ pending: string[],
285
+ unreadable?: (configFile: string, reason: string) => void,
286
+ ): string[] {
180
287
  const seen = new Set<string>();
181
288
  const found: string[] = [];
182
289
  while (pending.length > 0) {
@@ -189,7 +296,17 @@ function derivedFrom(engine: Engine, host: Host, pending: string[]): string[] {
189
296
  continue;
190
297
  }
191
298
  seen.add(path);
192
- parseOrRefuse(engine, path, path);
299
+ const reason = unreadableReason(engine, path);
300
+ if (reason !== undefined) {
301
+ if (unreadable === undefined) {
302
+ throw new DiscoveryError(
303
+ `${path} cannot be read as a compiler configuration: ${reason}`,
304
+ [],
305
+ );
306
+ }
307
+ unreadable(path, reason);
308
+ continue;
309
+ }
193
310
  found.push(path);
194
311
  pending.push(...referencesOf(host, path));
195
312
  }
@@ -232,20 +349,25 @@ function derivedId(targetRoot: string, configFile: string): string {
232
349
  return relativePath(targetRoot, configFile) ?? configFile;
233
350
  }
234
351
 
352
+ /** Why the compiler cannot read one configuration file, or undefined where it can. */
353
+ function unreadableReason(engine: Engine, configFile: string): string | undefined {
354
+ try {
355
+ engine.parseConfigFile(configFile);
356
+ return undefined;
357
+ } catch (error: unknown) {
358
+ return error instanceof Error ? error.message : String(error);
359
+ }
360
+ }
361
+
235
362
  /**
236
363
  * Resolves one compiler configuration file, or ends the run naming it: a project
237
364
  * opened from a configuration the compiler cannot read would be analyzed with no
238
365
  * type information.
239
366
  */
240
367
  function parseOrRefuse(engine: Engine, configFile: string, named: string): void {
241
- try {
242
- engine.parseConfigFile(configFile);
243
- } catch (error: unknown) {
244
- throw new DiscoveryError(
245
- `${named} cannot be read as a compiler configuration: ` +
246
- (error instanceof Error ? error.message : String(error)),
247
- [],
248
- );
368
+ const reason = unreadableReason(engine, configFile);
369
+ if (reason !== undefined) {
370
+ throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ${reason}`, []);
249
371
  }
250
372
  }
251
373
 
@@ -299,26 +421,50 @@ function declaredProjects(
299
421
  );
300
422
  }
301
423
  }
302
- return { projects, configFiles: projects.map((project) => project.configFile), fromScope: [] };
424
+ return {
425
+ projects,
426
+ derived: false,
427
+ notBuilt: [],
428
+ configFiles: projects.map((project) => project.configFile),
429
+ fromScope: [],
430
+ };
303
431
  }
304
432
 
305
433
  /**
306
434
  * The projects a run derives where the matrix declares none, in the order discovery
307
435
  * reads them: the paths the scope names, then every compiler configuration under
308
436
  * the target root that is not inside an ignored directory, then the configurations
309
- * each of those references, transitively. Each configuration appears once.
437
+ * each of those references, transitively. Each configuration appears once. A
438
+ * configuration the scope names directly is the caller's own and ends the run where
439
+ * it cannot be read; one the walk found is dropped and named.
310
440
  */
311
441
  function derivedProjects(engine: Engine, host: Host, scope: Scope): Discovery {
442
+ const targetRoot = scope.target.path;
312
443
  const fromScope = scopeConfigFiles(host, scope);
313
- const configFiles = derivedFrom(engine, host, [
314
- ...fromScope,
315
- ...configFilesUnder(host, scope.target.path),
316
- ]);
444
+ const notBuilt: NotBuilt[] = [];
445
+ const configFiles = derivedFrom(
446
+ engine,
447
+ host,
448
+ [...fromScope, ...configFilesUnder(host, targetRoot)],
449
+ (configFile, reason) => {
450
+ if (fromScope.includes(configFile)) {
451
+ throw new DiscoveryError(
452
+ `${configFile} cannot be read as a compiler configuration: ${reason}`,
453
+ [],
454
+ );
455
+ }
456
+ notBuilt.push({
457
+ id: derivedId(targetRoot, configFile),
458
+ configFile,
459
+ error: targetRelative(reason, resolvePath(host.workingDirectory(), targetRoot)),
460
+ });
461
+ },
462
+ );
317
463
  const projects = configFiles.map((configFile) => ({
318
- id: derivedId(scope.target.path, configFile),
464
+ id: derivedId(targetRoot, configFile),
319
465
  configFile,
320
466
  }));
321
- return { projects, configFiles, fromScope };
467
+ return { projects, derived: true, notBuilt, configFiles, fromScope };
322
468
  }
323
469
 
324
470
  /**
@@ -331,12 +477,10 @@ function derivedProjects(engine: Engine, host: Host, scope: Scope): Discovery {
331
477
  * scope and the target tree. A platform entry names a configuration of another
332
478
  * language and plays no part here.
333
479
  *
334
- * A configuration file the compiler cannot read ends the run with the failure code,
335
- * so a project that would have been analyzed with no type information is never
336
- * analyzed at all, whether the matrix declared it or discovery derived it. What a
337
- * configuration's own contents say is refused once its project is open, by
338
- * {@link diagnosticErrors} over the three sets a program reports, because a
339
- * resolution carries no diagnostics.
480
+ * A configuration file the compiler cannot read is never analyzed. One the matrix or
481
+ * the scope names ends the run with the failure code; a guess is dropped and named in
482
+ * {@link Discovery.notBuilt}. What a configuration's own contents say is read once its
483
+ * project is open, by {@link diagnosticErrors}, because a resolution carries none.
340
484
  *
341
485
  * Nothing reads a build output: a project is the files its configuration names, so a
342
486
  * package whose sources ship as TypeScript is analyzed as it stands.
package/src/edges.ts CHANGED
@@ -13,6 +13,7 @@ import type { InventorySymbol } from "./inventory.ts";
13
13
  import { positionAt, walkDocument } from "./json-document.ts";
14
14
  import { joinPath } from "./paths.ts";
15
15
  import { positionKey } from "./position.ts";
16
+ import { isPartSubject } from "./suppress.ts";
16
17
 
17
18
  /** The name and the location the Contract fixes for the edges document. */
18
19
  export const EDGES_FILE = "deadset-edges.json";
@@ -219,7 +220,9 @@ export function evaluateEdges<F extends Finding>(
219
220
  }
220
221
  const held = new Map<string, number>();
221
222
  findings.forEach((finding, at) => {
222
- held.set(positionKey(finding.position), at);
223
+ if (!isPartSubject(finding.symbol.kind)) {
224
+ held.set(positionKey(finding.position), at);
225
+ }
223
226
  });
224
227
 
225
228
  const pending = new Set<number>();
@@ -32,6 +32,7 @@ import {
32
32
  import { SymbolFlags } from "@typescript/native/unstable/sync";
33
33
  import { globExpression } from "./glob.ts";
34
34
  import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
35
+ import { UNANSWERED } from "./query.ts";
35
36
  import type { ProjectView } from "./session.ts";
36
37
  import type { SourceFiles } from "./source-files.ts";
37
38
 
@@ -435,8 +436,14 @@ function workersOf<Brand>(
435
436
  const names = sites.flatMap((site) => site.platformNames);
436
437
  const symbols = project.symbolsAt(names.map((name) => project.handle(name)));
437
438
  const platform = new Set<Node>();
439
+ // A name the checker could not resolve is read as the platform's, so a worker it
440
+ // stands on is rooted rather than reported.
438
441
  names.forEach((name, index) => {
439
442
  const symbol = symbols[index];
443
+ if (symbol === UNANSWERED) {
444
+ platform.add(name);
445
+ return;
446
+ }
440
447
  if (
441
448
  symbol !== undefined &&
442
449
  (symbol.flags & SymbolFlags.Alias) === 0 &&