@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.
- package/README.md +4 -4
- package/dist/bin/node-host.js +10 -7
- package/dist/src/alias-chain.js +59 -7
- package/dist/src/analysis.js +217 -48
- package/dist/src/calls.js +23 -22
- package/dist/src/class-members.js +10 -11
- package/dist/src/components.js +4 -2
- package/dist/src/configured-declarations.js +56 -7
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/dependencies.js +34 -17
- package/dist/src/deprecation.js +35 -24
- package/dist/src/destructuring.js +37 -29
- package/dist/src/discover.js +105 -28
- package/dist/src/edges.js +4 -1
- package/dist/src/entry-points.js +7 -0
- package/dist/src/enum-group.js +46 -40
- package/dist/src/exempt.js +42 -1
- package/dist/src/findings/completion.js +37 -8
- package/dist/src/findings/emitters.js +20 -6
- package/dist/src/findings/interfaces.js +7 -1
- package/dist/src/findings/intra-function.js +91 -2
- package/dist/src/findings/self-check.js +30 -7
- package/dist/src/findings/unused-declarations.js +15 -3
- package/dist/src/findings-pass.js +18 -11
- package/dist/src/framework-lifecycle.js +8 -6
- package/dist/src/implementations.js +45 -23
- package/dist/src/interface-satisfaction.js +30 -27
- package/dist/src/intra-function-parts.js +660 -0
- package/dist/src/inventory.js +85 -4
- package/dist/src/json-chunks.js +76 -0
- package/dist/src/kinds.js +337 -35
- package/dist/src/partial.js +23 -0
- package/dist/src/query.js +207 -0
- package/dist/src/references.js +92 -20
- package/dist/src/reflective-lookup.js +46 -4
- package/dist/src/report.js +14 -3
- package/dist/src/reporters/annotations.js +9 -5
- package/dist/src/reporters/json.js +3 -2
- package/dist/src/reporters/sarif.js +22 -16
- package/dist/src/reporters/template.js +112 -12
- package/dist/src/reporters/text.js +8 -6
- package/dist/src/roots.js +27 -11
- package/dist/src/serialization-contract.js +53 -32
- package/dist/src/session.js +79 -39
- package/dist/src/signatures.js +32 -0
- package/dist/src/suppress-file.js +29 -7
- package/dist/src/suppress-inline.js +8 -1
- package/dist/src/suppress.js +27 -0
- package/dist/src/sweep.js +4 -1
- package/dist/src/value-reach.js +44 -25
- package/dist/src/verbs/analyze.js +39 -14
- package/dist/src/verbs/describe.js +12 -8
- package/dist/src/verbs/explain.js +22 -9
- package/dist/src/verbs/print-projects.js +40 -7
- package/dist/src/verbs/print-retained.js +6 -2
- package/dist/src/verbs/print-roots.js +5 -1
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +75 -11
- package/src/analysis.ts +385 -104
- package/src/calls.ts +10 -9
- package/src/class-members.ts +10 -11
- package/src/components.ts +4 -2
- package/src/configured-declarations.ts +80 -8
- package/src/conformance-record.ts +2 -2
- package/src/dependencies.ts +42 -20
- package/src/deprecation.ts +53 -27
- package/src/destructuring.ts +50 -39
- package/src/discover.ts +177 -33
- package/src/edges.ts +4 -1
- package/src/entry-points.ts +7 -0
- package/src/enum-group.ts +56 -42
- package/src/exempt.ts +50 -2
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +46 -10
- package/src/findings/emitter.ts +3 -0
- package/src/findings/emitters.ts +27 -6
- package/src/findings/interfaces.ts +7 -1
- package/src/findings/intra-function.ts +106 -3
- package/src/findings/self-check.ts +48 -7
- package/src/findings/unused-declarations.ts +18 -4
- package/src/findings-pass.ts +26 -11
- package/src/framework-lifecycle.ts +10 -6
- package/src/host.ts +5 -3
- package/src/implementations.ts +59 -23
- package/src/interface-satisfaction.ts +30 -27
- package/src/intra-function-parts.ts +889 -0
- package/src/inventory.ts +97 -4
- package/src/json-chunks.ts +87 -0
- package/src/kinds.ts +355 -41
- package/src/partial.ts +40 -0
- package/src/query.ts +522 -0
- package/src/references.ts +119 -38
- package/src/reflective-lookup.ts +56 -6
- package/src/report.ts +32 -4
- package/src/reporters/annotations.ts +23 -21
- package/src/reporters/json.ts +3 -2
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/sarif.ts +27 -18
- package/src/reporters/template.ts +119 -14
- package/src/reporters/text.ts +12 -12
- package/src/roots.ts +34 -12
- package/src/serialization-contract.ts +59 -40
- package/src/session.ts +146 -58
- package/src/signatures.ts +55 -0
- package/src/suppress-file.ts +40 -8
- package/src/suppress-inline.ts +8 -1
- package/src/suppress.ts +31 -0
- package/src/sweep.ts +9 -4
- package/src/value-reach.ts +66 -28
- package/src/verbs/analyze.ts +51 -14
- package/src/verbs/describe.ts +13 -9
- package/src/verbs/explain.ts +20 -9
- package/src/verbs/print-projects.ts +53 -7
- package/src/verbs/print-retained.ts +6 -2
- package/src/verbs/print-roots.ts +5 -1
- package/src/version.ts +1 -1
- package/dist/src/reporters/rules.js +0 -178
- package/src/reporters/rules.ts +0 -195
package/src/destructuring.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
242
|
+
let table: Answer<ReadonlyMap<string, TSSymbol>> = new Map<string, TSSymbol>();
|
|
247
243
|
if ((type.flags & TypeFlags.AnyOrUnknown) === 0) {
|
|
248
244
|
patternLookups += 1;
|
|
249
|
-
|
|
250
|
-
|
|
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):
|
|
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
|
|
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):
|
|
266
|
+
const elementOf = (type: Type, index: number | undefined): Found => {
|
|
270
267
|
if (!type.isTypeReference()) {
|
|
271
268
|
return undefined;
|
|
272
269
|
}
|
|
273
|
-
|
|
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
|
-
|
|
275
|
+
const elements = queries.typeArguments(type);
|
|
276
|
+
return isAnswered(elements) ? elements[index] : UNANSWERED;
|
|
280
277
|
}
|
|
281
278
|
patternLookups += 1;
|
|
282
|
-
|
|
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
|
-
|
|
287
|
+
const elements = queries.typeArguments(type);
|
|
288
|
+
return isAnswered(elements) ? elements[0] : UNANSWERED;
|
|
287
289
|
};
|
|
288
290
|
|
|
289
|
-
const step = (type: Type, next: Step):
|
|
291
|
+
const step = (type: Type, next: Step): Found => {
|
|
290
292
|
const defined = definedOf(type);
|
|
291
|
-
if (defined === undefined) {
|
|
292
|
-
return
|
|
293
|
+
if (defined === undefined || defined === UNANSWERED) {
|
|
294
|
+
return defined;
|
|
293
295
|
}
|
|
294
296
|
if (next.kind === "property") {
|
|
295
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
331
|
+
if (node !== undefined) {
|
|
325
332
|
answered.set(node, type);
|
|
326
333
|
}
|
|
327
334
|
});
|
|
328
|
-
const patternTypes = new Map<Node,
|
|
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 ?
|
|
344
|
+
type = type === undefined || type === UNANSWERED ? type : step(type, next);
|
|
338
345
|
}
|
|
339
|
-
patternTypes.set(pattern, type === undefined ?
|
|
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
|
-
|
|
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
|
|
23
|
+
readonly diagnostics: readonly LocatedDiagnostic[];
|
|
24
24
|
|
|
25
|
-
constructor(message: string, diagnostics: readonly
|
|
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 ===
|
|
37
|
+
return diagnostic.category === ERROR_CATEGORY;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
/** One diagnostic
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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 {
|
|
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
|
|
314
|
-
|
|
315
|
-
|
|
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(
|
|
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
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
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
|
-
|
|
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>();
|
package/src/entry-points.ts
CHANGED
|
@@ -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 &&
|