@cplieger/deadset-ts 7.2.0-dev.2 → 7.2.0-dev.4
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/dist/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +10 -6
- package/dist/src/component-files.js +96 -31
- package/dist/src/configuration-files.js +5 -3
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/dependencies.js +2 -2
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +68 -86
- package/dist/src/edges.js +1 -1
- package/dist/src/intra-function-parts.js +17 -6
- package/dist/src/inventory.js +11 -0
- package/dist/src/json-document.js +5 -5
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +7 -2
- package/dist/src/paths.js +9 -0
- package/dist/src/references.js +25 -12
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/serialization-contract.js +43 -12
- package/dist/src/session.js +19 -1
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +2 -2
- package/dist/src/suppress-inline.js +3 -1
- package/dist/src/template-field.js +9 -6
- package/dist/src/type-errors.js +82 -28
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +3 -1
- package/dist/src/version-range.js +10 -5
- package/dist/src/workspace-resolution.js +19 -3
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +32 -6
- package/src/component-files.ts +116 -31
- package/src/configuration-files.ts +15 -6
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +27 -27
- package/src/dependencies.ts +2 -2
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +92 -87
- package/src/edges.ts +1 -1
- package/src/intra-function-parts.ts +21 -5
- package/src/inventory.ts +12 -0
- package/src/json-document.ts +7 -7
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +10 -2
- package/src/paths.ts +10 -0
- package/src/references.ts +28 -16
- package/src/reflective-lookup.ts +9 -7
- package/src/reporters/template.ts +21 -13
- package/src/serialization-contract.ts +53 -13
- package/src/session.ts +22 -1
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +2 -2
- package/src/suppress-inline.ts +3 -1
- package/src/template-field.ts +9 -6
- package/src/type-errors.ts +94 -35
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +6 -1
- package/src/version-range.ts +10 -5
- package/src/workspace-resolution.ts +24 -4
- package/src/workspace.ts +1 -1
package/src/contextual-uses.ts
CHANGED
|
@@ -319,14 +319,15 @@ export function contextualUses<Brand>(
|
|
|
319
319
|
return found;
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
-
const calls = new Map<number, Called
|
|
322
|
+
const calls = new Map<number, readonly Called[]>();
|
|
323
323
|
/**
|
|
324
|
-
* The parameter and return types of a type's
|
|
325
|
-
*
|
|
324
|
+
* The parameter and return types of each of a type's call signatures, or of each of its
|
|
325
|
+
* construct signatures where it has no call signature, read once per type.
|
|
326
326
|
*/
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
const callsOf = (type: Type): readonly Called[] => {
|
|
328
|
+
const known = calls.get(type.id);
|
|
329
|
+
if (known !== undefined) {
|
|
330
|
+
return known;
|
|
330
331
|
}
|
|
331
332
|
lookups += 1;
|
|
332
333
|
let kind = SignatureKind.Call;
|
|
@@ -336,31 +337,29 @@ export function contextualUses<Brand>(
|
|
|
336
337
|
kind = SignatureKind.Construct;
|
|
337
338
|
signatures = queries.signaturesOf(type, kind);
|
|
338
339
|
}
|
|
339
|
-
|
|
340
|
-
const [signature, ...more] = isAnswered(signatures) ? signatures : [];
|
|
341
|
-
if (signature !== undefined && more.length === 0) {
|
|
340
|
+
const called = (isAnswered(signatures) ? signatures : []).map((signature): Called => {
|
|
342
341
|
const parameters = signature.parameters.map((_parameter, index) =>
|
|
343
342
|
queries.parameterType(signature, index),
|
|
344
343
|
);
|
|
345
344
|
const result = queries.returnTypeOf(signature);
|
|
346
345
|
lookups += parameters.length + 1;
|
|
347
|
-
|
|
346
|
+
return {
|
|
348
347
|
kind,
|
|
349
348
|
parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
|
|
350
349
|
result: isAnswered(result) ? result : undefined,
|
|
351
350
|
};
|
|
352
|
-
}
|
|
351
|
+
});
|
|
353
352
|
calls.set(type.id, called);
|
|
354
353
|
return called;
|
|
355
354
|
};
|
|
356
355
|
|
|
357
356
|
/**
|
|
358
357
|
* Each object type a value of `source` is checked as against the object types of
|
|
359
|
-
* `target`: the value's own, and through array elements,
|
|
360
|
-
* checked the other way,
|
|
361
|
-
*
|
|
362
|
-
* A source that is one of the target's own object types matches
|
|
363
|
-
* pair is compared once, which ends the walk over a recursive type.
|
|
358
|
+
* `target`: the value's own, and through array elements, every pair of signatures
|
|
359
|
+
* (parameters checked the other way, then results), which reads more than the overload
|
|
360
|
+
* the checker picks, and the type arguments of two instantiations by place, as an array
|
|
361
|
+
* reaches `Iterable<T>`. A source that is one of the target's own object types matches
|
|
362
|
+
* it as it stands. Each pair is compared once, which ends the walk over a recursive type.
|
|
364
363
|
*/
|
|
365
364
|
const compared = (
|
|
366
365
|
source: Type | undefined,
|
|
@@ -385,7 +384,7 @@ export function contextualUses<Brand>(
|
|
|
385
384
|
into.push({ source: one, targets, outside: true });
|
|
386
385
|
}
|
|
387
386
|
const element = elementOf(one);
|
|
388
|
-
const called = element === undefined ?
|
|
387
|
+
const called = element === undefined ? callsOf(one) : [];
|
|
389
388
|
const sourceArguments = typeArgumentsOf(one);
|
|
390
389
|
for (const part of targets) {
|
|
391
390
|
const partElement = elementOf(part);
|
|
@@ -399,17 +398,17 @@ export function contextualUses<Brand>(
|
|
|
399
398
|
sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
|
|
400
399
|
compared(argument, targetArguments[index], into, seen);
|
|
401
400
|
});
|
|
402
|
-
if (element !== undefined) {
|
|
401
|
+
if (element !== undefined || called.length === 0) {
|
|
403
402
|
continue;
|
|
404
403
|
}
|
|
405
|
-
const other
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
for (const other of callsOf(part)) {
|
|
405
|
+
for (const signature of called.filter((one) => one.kind === other.kind)) {
|
|
406
|
+
other.parameters.forEach((parameter, index) => {
|
|
407
|
+
compared(parameter, signature.parameters[index], into, seen);
|
|
408
|
+
});
|
|
409
|
+
compared(signature.result, other.result, into, seen);
|
|
410
|
+
}
|
|
408
411
|
}
|
|
409
|
-
other.parameters.forEach((parameter, index) => {
|
|
410
|
-
compared(parameter, called.parameters[index], into, seen);
|
|
411
|
-
});
|
|
412
|
-
compared(called.result, other.result, into, seen);
|
|
413
412
|
}
|
|
414
413
|
}
|
|
415
414
|
};
|
|
@@ -431,7 +430,7 @@ export function contextualUses<Brand>(
|
|
|
431
430
|
if (element !== undefined) {
|
|
432
431
|
return mayCarry(element, seen);
|
|
433
432
|
}
|
|
434
|
-
return
|
|
433
|
+
return callsOf(part).length > 0 || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
|
|
435
434
|
});
|
|
436
435
|
};
|
|
437
436
|
|
|
@@ -467,11 +466,12 @@ export function contextualUses<Brand>(
|
|
|
467
466
|
continue;
|
|
468
467
|
}
|
|
469
468
|
for (const [name, member] of sourceMembers) {
|
|
469
|
+
// A member the position shares with the source is no read of it. A member with no
|
|
470
|
+
// declaration, as a mapped type over literal keys has, is never one it shares.
|
|
470
471
|
const read = tables.some((table) => {
|
|
471
472
|
const declared = table.get(name);
|
|
472
473
|
return (
|
|
473
474
|
declared !== undefined &&
|
|
474
|
-
declared.handles.size > 0 &&
|
|
475
475
|
![...declared.handles].some((handle) => member.handles.has(handle))
|
|
476
476
|
);
|
|
477
477
|
});
|
package/src/dependencies.ts
CHANGED
|
@@ -376,8 +376,8 @@ export function projectNeeds<Brand>(
|
|
|
376
376
|
sourceFilesOf(project, targetRoot),
|
|
377
377
|
parse,
|
|
378
378
|
);
|
|
379
|
-
for (const { file } of configuration.modules) {
|
|
380
|
-
const names = declaredAt(
|
|
379
|
+
for (const { file, manifestDir } of configuration.modules) {
|
|
380
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
381
381
|
for (const { node, text } of moduleStrings(file)) {
|
|
382
382
|
use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
|
|
383
383
|
}
|
package/src/destructuring.ts
CHANGED
|
@@ -24,7 +24,12 @@ import {
|
|
|
24
24
|
type BinaryExpression,
|
|
25
25
|
type Node,
|
|
26
26
|
} from "@typescript/native/ast";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
SignatureKind,
|
|
29
|
+
TypeFlags,
|
|
30
|
+
type Symbol as TSSymbol,
|
|
31
|
+
type Type,
|
|
32
|
+
} from "@typescript/native/sync";
|
|
28
33
|
import {
|
|
29
34
|
isAnswered,
|
|
30
35
|
propertyTables,
|
|
@@ -34,6 +39,10 @@ import {
|
|
|
34
39
|
} from "./query.ts";
|
|
35
40
|
import type { ProjectView } from "./session.ts";
|
|
36
41
|
|
|
42
|
+
/** The types of an iteration result's `value` that carry no element of the iterable. */
|
|
43
|
+
const NO_ELEMENT =
|
|
44
|
+
TypeFlags.AnyOrUnknown | TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Null | TypeFlags.Never;
|
|
45
|
+
|
|
37
46
|
/** One property one object pattern reads. */
|
|
38
47
|
export interface PropertyRead {
|
|
39
48
|
/** The pattern, whose destructured value declares the property. */
|
|
@@ -298,11 +307,93 @@ export function destructuring<Brand>(
|
|
|
298
307
|
return isAnswered(elements) ? elements[0] : UNANSWERED;
|
|
299
308
|
};
|
|
300
309
|
|
|
310
|
+
/** The type of the one property of a type that `matches` accepts, unanswered where two do. */
|
|
311
|
+
const propertyTypeOf = (owner: Type, matches: (property: TSSymbol) => boolean): Found => {
|
|
312
|
+
const table = tableOf(owner);
|
|
313
|
+
if (!isAnswered(table)) {
|
|
314
|
+
return UNANSWERED;
|
|
315
|
+
}
|
|
316
|
+
const [property, ...more] = [...table.values()].filter(matches);
|
|
317
|
+
if (property === undefined) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
if (more.length > 0) {
|
|
321
|
+
return UNANSWERED;
|
|
322
|
+
}
|
|
323
|
+
patternLookups += 1;
|
|
324
|
+
return queries.typeOfSymbol(property);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const resultOf = (callee: Found): Found => {
|
|
328
|
+
if (callee === undefined || callee === UNANSWERED) {
|
|
329
|
+
return callee;
|
|
330
|
+
}
|
|
331
|
+
patternLookups += 1;
|
|
332
|
+
const signatures = queries.signaturesOf(callee, SignatureKind.Call);
|
|
333
|
+
if (!isAnswered(signatures)) {
|
|
334
|
+
return UNANSWERED;
|
|
335
|
+
}
|
|
336
|
+
const [signature] = signatures;
|
|
337
|
+
if (signature === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
patternLookups += 1;
|
|
341
|
+
return queries.returnTypeOf(signature);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const iterations = new Map<number, Found>();
|
|
345
|
+
/**
|
|
346
|
+
* The type a `for…of` takes from one iterable that is no array, read through the
|
|
347
|
+
* iteration protocol: the `value` of what the `next()` of its `[Symbol.iterator]()`
|
|
348
|
+
* answers. A constituent of that answer whose value is {@link NO_ELEMENT} carries no
|
|
349
|
+
* element; where more than one carries one, the element is left unanswered, so a read
|
|
350
|
+
* through it is resolved by name.
|
|
351
|
+
*/
|
|
352
|
+
const iteratedOf = (type: Type): Found => {
|
|
353
|
+
if (iterations.has(type.id)) {
|
|
354
|
+
return iterations.get(type.id);
|
|
355
|
+
}
|
|
356
|
+
// The checker keys a member a unique symbol names `__@<name>@<symbol id>`, and escapes
|
|
357
|
+
// a written key that starts with `__` with one more `_`, so no string key matches; a
|
|
358
|
+
// unique symbol of the program's own named `iterator` does, which leaves two matches:
|
|
359
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/checker/checker.go#L23096-L23108
|
|
360
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/ast/symbol.go#L91-L103
|
|
361
|
+
const iterator = resultOf(
|
|
362
|
+
propertyTypeOf(type, (one) => one.escapedName.startsWith("__@iterator@")),
|
|
363
|
+
);
|
|
364
|
+
const result =
|
|
365
|
+
iterator === undefined || iterator === UNANSWERED
|
|
366
|
+
? iterator
|
|
367
|
+
: resultOf(propertyTypeOf(iterator, (one) => one.name === "next"));
|
|
368
|
+
let found: Found = result;
|
|
369
|
+
if (result !== undefined && result !== UNANSWERED) {
|
|
370
|
+
let parts: Answer<readonly Type[]> = [result];
|
|
371
|
+
if (result.isUnionType()) {
|
|
372
|
+
patternLookups += 1;
|
|
373
|
+
parts = queries.constituents(result);
|
|
374
|
+
}
|
|
375
|
+
const values: readonly Found[] = isAnswered(parts)
|
|
376
|
+
? parts.map((part) => propertyTypeOf(part, (one) => one.name === "value"))
|
|
377
|
+
: [UNANSWERED];
|
|
378
|
+
const carried = values.filter(
|
|
379
|
+
(value): value is Answer<Type> =>
|
|
380
|
+
value === UNANSWERED || (value !== undefined && (value.flags & NO_ELEMENT) === 0),
|
|
381
|
+
);
|
|
382
|
+
const [only, ...more] = carried;
|
|
383
|
+
found = more.length > 0 ? UNANSWERED : only;
|
|
384
|
+
}
|
|
385
|
+
iterations.set(type.id, found);
|
|
386
|
+
return found;
|
|
387
|
+
};
|
|
388
|
+
|
|
301
389
|
const step = (type: Type, next: Step): Found => {
|
|
302
390
|
const defined = definedOf(type);
|
|
303
391
|
if (defined === undefined || defined === UNANSWERED) {
|
|
304
392
|
return defined;
|
|
305
393
|
}
|
|
394
|
+
if (next.kind === "iterated") {
|
|
395
|
+
return elementOf(defined, undefined) ?? iteratedOf(defined);
|
|
396
|
+
}
|
|
306
397
|
if (next.kind === "property") {
|
|
307
398
|
const table = tableOf(defined);
|
|
308
399
|
if (!isAnswered(table)) {
|
|
@@ -315,7 +406,7 @@ export function destructuring<Brand>(
|
|
|
315
406
|
patternLookups += 1;
|
|
316
407
|
return queries.typeOfSymbol(property);
|
|
317
408
|
}
|
|
318
|
-
return elementOf(defined, next.
|
|
409
|
+
return elementOf(defined, next.index);
|
|
319
410
|
};
|
|
320
411
|
|
|
321
412
|
const resolve = (reads: readonly PropertyRead[]): readonly Answer<TSSymbol | undefined>[] => {
|
package/src/discover.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { DiagnosticCategory } from "@typescript/native/sync";
|
|
|
2
2
|
import type { Diagnostic } from "@typescript/native/sync";
|
|
3
3
|
import type { BuildConfiguration, ProjectConfiguration } from "./config.ts";
|
|
4
4
|
import type { Host } from "./host.ts";
|
|
5
|
-
import { dirnamePath,
|
|
5
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
6
6
|
import type { Scope } from "./scope.ts";
|
|
7
7
|
import { SetupError } from "./setup-failure.ts";
|
|
8
8
|
import type { Engine, ProjectDiagnostics } from "./session.ts";
|
|
@@ -95,20 +95,46 @@ export function diagnosticErrors(sets: {
|
|
|
95
95
|
/** Why one open project cannot be analyzed, split by whether it loaded at all. */
|
|
96
96
|
interface ProjectErrors {
|
|
97
97
|
/**
|
|
98
|
-
* The errors in the configuration itself
|
|
99
|
-
*
|
|
98
|
+
* The errors in the configuration itself, and the errors the compiler places in no file
|
|
99
|
+
* that leave its checker without a type it needs: no input matched, an option that
|
|
100
|
+
* does not parse, a missing global type. A project carrying one did not load, whatever
|
|
101
|
+
* its files say.
|
|
100
102
|
*/
|
|
101
103
|
readonly load: readonly LocatedDiagnostic[];
|
|
102
104
|
/** The errors in the files of a project that loaded: their syntax and their types. */
|
|
103
105
|
readonly check: readonly LocatedDiagnostic[];
|
|
106
|
+
/**
|
|
107
|
+
* The libraries the configuration names that the compiler cannot find, which leave it
|
|
108
|
+
* loaded: a use of a name only such a library declares is an error in its file.
|
|
109
|
+
*/
|
|
110
|
+
readonly unfound: readonly LocatedDiagnostic[];
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The codes of a type library and a lib the configuration names that the compiler
|
|
115
|
+
* cannot find, which it reports while it gathers the program's files:
|
|
116
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go
|
|
117
|
+
*/
|
|
118
|
+
const UNFOUND_LIBRARY: ReadonlySet<number> = new Set([2688, 2726, 2727]);
|
|
119
|
+
|
|
106
120
|
/** The errors one open project carries, each located in the file it names. */
|
|
107
121
|
export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
|
|
108
|
-
|
|
122
|
+
// The compiler numbers its semantic messages from 2000 and its option messages from
|
|
123
|
+
// 5000. A file-less option error, a removed option or an output layout, leaves the
|
|
124
|
+
// program whole, and the compiler checks it all the same:
|
|
125
|
+
// https://github.com/microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-message-codes
|
|
126
|
+
const semantic = sets.unplaced.filter(
|
|
127
|
+
(diagnostic) => isError(diagnostic) && diagnostic.code >= 2000 && diagnostic.code < 3000,
|
|
128
|
+
);
|
|
129
|
+
const unfound = semantic.filter(({ code }) => UNFOUND_LIBRARY.has(code));
|
|
130
|
+
const load = [
|
|
131
|
+
...sets.configParsing.filter(isError),
|
|
132
|
+
...semantic.filter(({ code }) => !UNFOUND_LIBRARY.has(code)),
|
|
133
|
+
];
|
|
109
134
|
return {
|
|
110
135
|
load: locatedIn(load, sets.locate),
|
|
111
136
|
check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
|
|
137
|
+
unfound: load.length > 0 ? [] : locatedIn(unfound, sets.locate),
|
|
112
138
|
};
|
|
113
139
|
}
|
|
114
140
|
|
|
@@ -182,10 +208,6 @@ interface Discovery {
|
|
|
182
208
|
readonly fromScope: readonly string[];
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
186
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
211
|
/**
|
|
190
212
|
* The configuration files below `root`, in ascending path order, skipping an
|
|
191
213
|
* ignored directory. A directory that cannot be read is skipped: a tree the
|
|
@@ -221,45 +243,6 @@ function configFilesUnder(host: Host, root: string): string[] {
|
|
|
221
243
|
return found.sort();
|
|
222
244
|
}
|
|
223
245
|
|
|
224
|
-
/**
|
|
225
|
-
* The configuration files one configuration's `references` name, resolved against
|
|
226
|
-
* that configuration's own directory. A reference naming a directory names the
|
|
227
|
-
* `tsconfig.json` in it, which is the compiler's own rule.
|
|
228
|
-
*/
|
|
229
|
-
function referencesOf(host: Host, configFile: string): string[] {
|
|
230
|
-
let text: string;
|
|
231
|
-
try {
|
|
232
|
-
text = host.readFile(configFile);
|
|
233
|
-
} catch {
|
|
234
|
-
return [];
|
|
235
|
-
}
|
|
236
|
-
let value: unknown;
|
|
237
|
-
try {
|
|
238
|
-
value = JSON.parse(text);
|
|
239
|
-
} catch {
|
|
240
|
-
// What a configuration's contents say is refused by the compiler's own
|
|
241
|
-
// diagnostics, which name the position; nothing is inferred from it here.
|
|
242
|
-
return [];
|
|
243
|
-
}
|
|
244
|
-
if (!isRecord(value) || !Array.isArray(value["references"])) {
|
|
245
|
-
return [];
|
|
246
|
-
}
|
|
247
|
-
const dir = dirnamePath(configFile);
|
|
248
|
-
const found: string[] = [];
|
|
249
|
-
for (const entry of value["references"] as unknown[]) {
|
|
250
|
-
if (!isRecord(entry)) {
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const path = entry["path"];
|
|
254
|
-
if (typeof path !== "string" || path === "") {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const resolved = isAbsolutePath(path) ? path : joinPath(dir, path);
|
|
258
|
-
found.push(resolved.endsWith(".json") ? resolved : joinPath(resolved, "tsconfig.json"));
|
|
259
|
-
}
|
|
260
|
-
return found;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
246
|
/**
|
|
264
247
|
* The configuration file the scope names for the target directly, where its path
|
|
265
248
|
* names one rather than a directory. A target naming a directory is reached by the
|
|
@@ -274,15 +257,16 @@ function scopeConfigFiles(host: Host, scope: Scope): string[] {
|
|
|
274
257
|
|
|
275
258
|
/**
|
|
276
259
|
* Every configuration file reached from `pending`, in the order discovery reads them:
|
|
277
|
-
* each once, each followed by the configurations it references
|
|
278
|
-
* configuration the compiler cannot read ends the run naming it, unless
|
|
279
|
-
* is given, which is then told the configuration and why, and the walk
|
|
280
|
-
* without it.
|
|
260
|
+
* each once, each followed by the configurations it references that `follows` admits,
|
|
261
|
+
* transitively. A configuration the compiler cannot read ends the run naming it, unless
|
|
262
|
+
* `unreadable` is given, which is then told the configuration and why, and the walk
|
|
263
|
+
* goes on without it.
|
|
281
264
|
*/
|
|
282
265
|
function derivedFrom(
|
|
283
266
|
engine: Engine,
|
|
284
267
|
host: Host,
|
|
285
268
|
pending: string[],
|
|
269
|
+
follows: (reference: string) => boolean,
|
|
286
270
|
unreadable?: (configFile: string, reason: string) => void,
|
|
287
271
|
): string[] {
|
|
288
272
|
const seen = new Set<string>();
|
|
@@ -297,19 +281,19 @@ function derivedFrom(
|
|
|
297
281
|
continue;
|
|
298
282
|
}
|
|
299
283
|
seen.add(path);
|
|
300
|
-
const
|
|
301
|
-
if (reason
|
|
284
|
+
const read = readConfiguration(engine, path);
|
|
285
|
+
if ("reason" in read) {
|
|
302
286
|
if (unreadable === undefined) {
|
|
303
287
|
throw new DiscoveryError(
|
|
304
|
-
`${path} cannot be read as a compiler configuration: ${reason}`,
|
|
288
|
+
`${path} cannot be read as a compiler configuration: ${read.reason}`,
|
|
305
289
|
[],
|
|
306
290
|
);
|
|
307
291
|
}
|
|
308
|
-
unreadable(path, reason);
|
|
292
|
+
unreadable(path, read.reason);
|
|
309
293
|
continue;
|
|
310
294
|
}
|
|
311
295
|
found.push(path);
|
|
312
|
-
pending.push(...
|
|
296
|
+
pending.push(...read.references.filter(follows));
|
|
313
297
|
}
|
|
314
298
|
return found;
|
|
315
299
|
}
|
|
@@ -335,7 +319,7 @@ export function consumerProjects(engine: Engine, host: Host, root: string): read
|
|
|
335
319
|
if (kind !== "directory") {
|
|
336
320
|
throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
|
|
337
321
|
}
|
|
338
|
-
const found = derivedFrom(engine, host, configFilesUnder(host, root));
|
|
322
|
+
const found = derivedFrom(engine, host, configFilesUnder(host, root), () => true);
|
|
339
323
|
if (found.length === 0) {
|
|
340
324
|
throw new DiscoveryError(
|
|
341
325
|
`the consumer ${root} holds no compiler configuration, so none of its references can be read`,
|
|
@@ -353,39 +337,52 @@ function derivedId(targetRoot: string, configFile: string): string {
|
|
|
353
337
|
return relativePath(targetRoot, configFile) ?? configFile;
|
|
354
338
|
}
|
|
355
339
|
|
|
356
|
-
/**
|
|
357
|
-
|
|
340
|
+
/**
|
|
341
|
+
* The configuration files one configuration's `references` name, or why the compiler
|
|
342
|
+
* cannot read the configuration.
|
|
343
|
+
*/
|
|
344
|
+
function readConfiguration(
|
|
345
|
+
engine: Engine,
|
|
346
|
+
configFile: string,
|
|
347
|
+
): { readonly references: readonly string[] } | { readonly reason: string } {
|
|
358
348
|
try {
|
|
359
|
-
engine.parseConfigFile(configFile);
|
|
360
|
-
|
|
349
|
+
const parsed = engine.parseConfigFile(configFile);
|
|
350
|
+
// The parse answers a directory reference as the directory; the compiler reads a
|
|
351
|
+
// path not ending `.json` as its `tsconfig.json`:
|
|
352
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
|
|
353
|
+
return {
|
|
354
|
+
references: (parsed.projectReferences ?? []).map(({ path }) =>
|
|
355
|
+
path.endsWith(".json") ? path : joinPath(path, "tsconfig.json"),
|
|
356
|
+
),
|
|
357
|
+
};
|
|
361
358
|
} catch (error: unknown) {
|
|
362
|
-
return error instanceof Error ? error.message : String(error);
|
|
359
|
+
return { reason: error instanceof Error ? error.message : String(error) };
|
|
363
360
|
}
|
|
364
361
|
}
|
|
365
362
|
|
|
366
363
|
/**
|
|
367
|
-
*
|
|
368
|
-
* opened from a configuration the compiler cannot read would be analyzed with
|
|
369
|
-
* type information.
|
|
364
|
+
* The references of one compiler configuration file, or ends the run naming it: a
|
|
365
|
+
* project opened from a configuration the compiler cannot read would be analyzed with
|
|
366
|
+
* no type information.
|
|
370
367
|
*/
|
|
371
|
-
function parseOrRefuse(engine: Engine, configFile: string, named: string):
|
|
372
|
-
const
|
|
373
|
-
if (reason
|
|
374
|
-
throw new DiscoveryError(
|
|
368
|
+
function parseOrRefuse(engine: Engine, configFile: string, named: string): readonly string[] {
|
|
369
|
+
const read = readConfiguration(engine, configFile);
|
|
370
|
+
if ("reason" in read) {
|
|
371
|
+
throw new DiscoveryError(
|
|
372
|
+
`${named} cannot be read as a compiler configuration: ${read.reason}`,
|
|
373
|
+
[],
|
|
374
|
+
);
|
|
375
375
|
}
|
|
376
|
+
return read.references;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
/**
|
|
379
|
-
* The projects a build matrix declares, in
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* naming both: the compiler builds a referenced project's sources into the program
|
|
386
|
-
* that references it without reporting their diagnostics, so the project the matrix
|
|
387
|
-
* left out would be analyzed with its errors unread. An entry naming a file an
|
|
388
|
-
* earlier entry named adds nothing, so the earlier identifier stands.
|
|
380
|
+
* The projects a build matrix declares, in its order, each entry a file below the target
|
|
381
|
+
* root: an entry naming an absent file, a directory or a file the compiler cannot read
|
|
382
|
+
* ends the run naming it, and one naming an earlier entry's file adds nothing. A
|
|
383
|
+
* reference below the root the matrix does not name ends the run naming both, because the
|
|
384
|
+
* compiler builds a referenced project's sources into the referencing program without
|
|
385
|
+
* reporting their diagnostics. A reference outside the root is an outside project.
|
|
389
386
|
*/
|
|
390
387
|
function declaredProjects(
|
|
391
388
|
engine: Engine,
|
|
@@ -395,6 +392,7 @@ function declaredProjects(
|
|
|
395
392
|
): Discovery {
|
|
396
393
|
const seen = new Set<string>();
|
|
397
394
|
const projects: DiscoveredProject[] = [];
|
|
395
|
+
const references = new Map<string, readonly string[]>();
|
|
398
396
|
for (const entry of entries) {
|
|
399
397
|
const configFile = joinPath(targetRoot, entry.project);
|
|
400
398
|
const named = `the project ${JSON.stringify(entry.id)} (${configFile})`;
|
|
@@ -411,11 +409,13 @@ function declaredProjects(
|
|
|
411
409
|
continue;
|
|
412
410
|
}
|
|
413
411
|
seen.add(configFile);
|
|
414
|
-
parseOrRefuse(engine, configFile, named);
|
|
412
|
+
references.set(configFile, parseOrRefuse(engine, configFile, named));
|
|
415
413
|
projects.push({ id: entry.id, configFile });
|
|
416
414
|
}
|
|
417
415
|
for (const project of projects) {
|
|
418
|
-
const unnamed =
|
|
416
|
+
const unnamed = references
|
|
417
|
+
.get(project.configFile)
|
|
418
|
+
?.find((path) => !seen.has(path) && relativePath(targetRoot, path) !== undefined);
|
|
419
419
|
if (unnamed !== undefined) {
|
|
420
420
|
throw new DiscoveryError(
|
|
421
421
|
`the project ${JSON.stringify(project.id)} (${project.configFile}) references ` +
|
|
@@ -435,21 +435,26 @@ function declaredProjects(
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
/**
|
|
438
|
-
* The projects a run derives where the matrix declares none, in
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* each
|
|
442
|
-
*
|
|
443
|
-
* it cannot be read; one the walk found is dropped and named.
|
|
438
|
+
* The projects a run derives where the matrix declares none, in discovery order: the
|
|
439
|
+
* paths the scope names, every compiler configuration under the target root outside an
|
|
440
|
+
* ignored directory, then each configuration below the root those reference,
|
|
441
|
+
* transitively, each once. A reference outside the root is an outside project, whose
|
|
442
|
+
* sources the compiler builds into the referencing program. A configuration the scope
|
|
443
|
+
* names ends the run where it cannot be read; one the walk found is dropped and named.
|
|
444
444
|
*/
|
|
445
445
|
function derivedProjects(engine: Engine, host: Host, scope: Scope): Discovery {
|
|
446
446
|
const targetRoot = scope.target.path;
|
|
447
447
|
const fromScope = scopeConfigFiles(host, scope);
|
|
448
448
|
const notBuilt: NotBuilt[] = [];
|
|
449
|
+
const rootDir = resolvePath(
|
|
450
|
+
host.workingDirectory(),
|
|
451
|
+
fromScope.length > 0 ? dirnamePath(targetRoot) : targetRoot,
|
|
452
|
+
);
|
|
449
453
|
const configFiles = derivedFrom(
|
|
450
454
|
engine,
|
|
451
455
|
host,
|
|
452
456
|
[...fromScope, ...configFilesUnder(host, targetRoot)],
|
|
457
|
+
(reference) => relativePath(rootDir, reference) !== undefined,
|
|
453
458
|
(configFile, reason) => {
|
|
454
459
|
if (fromScope.includes(configFile)) {
|
|
455
460
|
throw new DiscoveryError(
|
package/src/edges.ts
CHANGED
|
@@ -156,7 +156,7 @@ export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide
|
|
|
156
156
|
|
|
157
157
|
const sides: EdgeSide[] = [];
|
|
158
158
|
(edges as readonly unknown[]).forEach((value, index) => {
|
|
159
|
-
const offset = walk.
|
|
159
|
+
const offset = walk.starts[index];
|
|
160
160
|
const at =
|
|
161
161
|
offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
|
|
162
162
|
const edge = readEdge(value, at);
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
isExpressionStatement,
|
|
30
30
|
isFunctionDeclaration,
|
|
31
31
|
isFunctionExpression,
|
|
32
|
+
isFunctionLikeDeclaration,
|
|
32
33
|
isHeritageClause,
|
|
33
34
|
isIdentifier,
|
|
34
35
|
isIfStatement,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
isQualifiedName,
|
|
50
51
|
isReturnStatement,
|
|
51
52
|
isShorthandPropertyAssignment,
|
|
53
|
+
isSourceFile,
|
|
52
54
|
isStringLiteral,
|
|
53
55
|
isSwitchStatement,
|
|
54
56
|
isThrowStatement,
|
|
@@ -77,6 +79,7 @@ import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
|
|
|
77
79
|
import type { Reference } from "./references.ts";
|
|
78
80
|
import type { ProjectView } from "./session.ts";
|
|
79
81
|
import { signatureOf, type SignatureNode } from "./signatures.ts";
|
|
82
|
+
import { spellsNoBinding } from "./spelled-names.ts";
|
|
80
83
|
|
|
81
84
|
/** The subject kind of each part this module reads. */
|
|
82
85
|
type PartKind = "parameter" | "result" | "statement" | "store" | "case";
|
|
@@ -268,13 +271,12 @@ function caseKey(expression: Node): string | undefined {
|
|
|
268
271
|
return undefined;
|
|
269
272
|
}
|
|
270
273
|
|
|
271
|
-
/** Every identifier below one node whose text one of the names spells
|
|
274
|
+
/** Every identifier below one node whose text one of the names spells as a reference. */
|
|
272
275
|
function identifiersNaming(node: Node, names: ReadonlySet<string>): Identifier[] {
|
|
273
276
|
const found: Identifier[] = [];
|
|
274
277
|
const visit = (at: Node): void => {
|
|
275
278
|
if (isIdentifier(at)) {
|
|
276
|
-
|
|
277
|
-
if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
|
|
279
|
+
if (names.has(at.text) && !spellsNoBinding(at)) {
|
|
278
280
|
found.push(at);
|
|
279
281
|
}
|
|
280
282
|
return;
|
|
@@ -310,6 +312,16 @@ function inNestedFunction(node: Node, outer: Node): boolean {
|
|
|
310
312
|
return false;
|
|
311
313
|
}
|
|
312
314
|
|
|
315
|
+
/** The nearest function around one node whose `arguments` object it names: an arrow has none. */
|
|
316
|
+
function ownArgumentsOf(node: Node): Node | undefined {
|
|
317
|
+
for (let at = node.parent; !isSourceFile(at); at = at.parent) {
|
|
318
|
+
if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
|
|
319
|
+
return at;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
313
325
|
/** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
|
|
314
326
|
function isStub(signature: SignatureNode): boolean {
|
|
315
327
|
const body = signature.body;
|
|
@@ -876,9 +888,13 @@ function parameterParts(
|
|
|
876
888
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
877
889
|
inValue: boolean,
|
|
878
890
|
): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
|
|
879
|
-
// A function that reads `arguments` reads every parameter by position.
|
|
891
|
+
// A function that reads its `arguments` reads every parameter by position. A nested
|
|
892
|
+
// function other than an arrow has an `arguments` object of its own.
|
|
880
893
|
const readsArguments =
|
|
881
|
-
!isArrowFunction(signature) &&
|
|
894
|
+
!isArrowFunction(signature) &&
|
|
895
|
+
identifiersNaming(signature, new Set(["arguments"])).some(
|
|
896
|
+
(use) => ownArgumentsOf(use) === signature,
|
|
897
|
+
);
|
|
882
898
|
const read = reader.names.map(
|
|
883
899
|
({ name }) =>
|
|
884
900
|
readsArguments ||
|
package/src/inventory.ts
CHANGED
|
@@ -459,6 +459,18 @@ function nameNodeOf(node: Node): Node | undefined {
|
|
|
459
459
|
return (node as { readonly name?: Node }).name;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
/**
|
|
463
|
+
* The fragment component one declaration's name spells in a reference, as the inventory
|
|
464
|
+
* spells it, or undefined for a name the grammar does not represent.
|
|
465
|
+
*/
|
|
466
|
+
export function declaredComponent(node: Node): string | undefined {
|
|
467
|
+
const component = componentOf(nameNodeOf(node));
|
|
468
|
+
if (component === undefined) {
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
return component.computed ? computedComponent(component.text) : nameComponent(component.text);
|
|
472
|
+
}
|
|
473
|
+
|
|
462
474
|
/**
|
|
463
475
|
* One node's identity inside one program, which is how two readings of that program
|
|
464
476
|
* name one declaration.
|