@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
|
@@ -793,7 +793,7 @@ class Execution {
|
|
|
793
793
|
case "lit":
|
|
794
794
|
return arg.value;
|
|
795
795
|
case "pipe":
|
|
796
|
-
return this.pipeline(arg.pipe, dot
|
|
796
|
+
return this.pipeline(arg.pipe, dot);
|
|
797
797
|
case "field":
|
|
798
798
|
return this.field(arg.recv === undefined ? dot : this.arg(arg.recv, dot), arg.names);
|
|
799
799
|
case "func":
|
|
@@ -837,19 +837,18 @@ class Execution {
|
|
|
837
837
|
}
|
|
838
838
|
return call(head.name, operands());
|
|
839
839
|
}
|
|
840
|
-
pipeline(pipe, dot
|
|
840
|
+
pipeline(pipe, dot) {
|
|
841
841
|
let value;
|
|
842
842
|
for (const command of pipe.commands) {
|
|
843
843
|
value = this.command(command, dot, value);
|
|
844
844
|
}
|
|
845
845
|
const result = value ?? null;
|
|
846
|
-
const
|
|
847
|
-
if (only !== undefined && (declares || pipe.assign)) {
|
|
846
|
+
for (const name of pipe.vars) {
|
|
848
847
|
if (pipe.assign) {
|
|
849
|
-
this.assign(
|
|
848
|
+
this.assign(name, result);
|
|
850
849
|
}
|
|
851
850
|
else {
|
|
852
|
-
this.scopes.at(-1)?.set(
|
|
851
|
+
this.scopes.at(-1)?.set(name, result);
|
|
853
852
|
}
|
|
854
853
|
}
|
|
855
854
|
return result;
|
|
@@ -861,7 +860,7 @@ class Execution {
|
|
|
861
860
|
this.write(node.text);
|
|
862
861
|
break;
|
|
863
862
|
case "action": {
|
|
864
|
-
const value = this.pipeline(node.pipe, dot
|
|
863
|
+
const value = this.pipeline(node.pipe, dot);
|
|
865
864
|
if (node.pipe.vars.length === 0) {
|
|
866
865
|
this.write(formatted(value, false));
|
|
867
866
|
}
|
|
@@ -874,7 +873,7 @@ class Execution {
|
|
|
874
873
|
case "with": {
|
|
875
874
|
this.scopes.push(new Map());
|
|
876
875
|
try {
|
|
877
|
-
const value = this.pipeline(node.pipe, dot
|
|
876
|
+
const value = this.pipeline(node.pipe, dot);
|
|
878
877
|
const taken = truthy(value);
|
|
879
878
|
this.run(taken ? node.list : node.otherwise, taken && node.k === "with" ? value : dot);
|
|
880
879
|
}
|
|
@@ -892,7 +891,9 @@ class Execution {
|
|
|
892
891
|
range(node, dot) {
|
|
893
892
|
this.scopes.push(new Map());
|
|
894
893
|
try {
|
|
895
|
-
|
|
894
|
+
// Go's `evalPipeline` gives every variable the range declares or assigns the whole value first:
|
|
895
|
+
// https://github.com/golang/go/blob/go1.27.2/src/text/template/exec.go#L525
|
|
896
|
+
const value = this.pipeline(node.pipe, dot);
|
|
896
897
|
let entries;
|
|
897
898
|
if (value === null) {
|
|
898
899
|
entries = [];
|
|
@@ -913,14 +914,22 @@ class Execution {
|
|
|
913
914
|
return;
|
|
914
915
|
}
|
|
915
916
|
const [first, second] = node.pipe.vars;
|
|
917
|
+
const scope = this.scopes.at(-1);
|
|
918
|
+
const bind = (name, held) => {
|
|
919
|
+
if (node.pipe.assign) {
|
|
920
|
+
this.assign(name, held);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
scope?.set(name, held);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
916
926
|
for (const [key, element] of entries) {
|
|
917
|
-
const scope = this.scopes.at(-1);
|
|
918
927
|
if (second !== undefined && first !== undefined) {
|
|
919
|
-
|
|
920
|
-
|
|
928
|
+
bind(first, key);
|
|
929
|
+
bind(second, element);
|
|
921
930
|
}
|
|
922
931
|
else if (first !== undefined) {
|
|
923
|
-
|
|
932
|
+
bind(first, element);
|
|
924
933
|
}
|
|
925
934
|
try {
|
|
926
935
|
this.run(node.list, element);
|
|
@@ -16,7 +16,7 @@ import { nodeKey } from "./inventory.js";
|
|
|
16
16
|
import { renderPosition } from "./position.js";
|
|
17
17
|
import { must } from "./query.js";
|
|
18
18
|
import { erasedStores } from "./erased-stores.js";
|
|
19
|
-
import { checkedTypes, valueReach } from "./value-reach.js";
|
|
19
|
+
import { checkedTypes, CONVERSION_MEMBERS, valueReach } from "./value-reach.js";
|
|
20
20
|
/** The serializer every run reads, whatever the configuration names. */
|
|
21
21
|
const JSON_STRINGIFY = { shape: "global", global: "JSON.stringify" };
|
|
22
22
|
/** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
|
|
@@ -313,13 +313,14 @@ function destinations(input, chains, walked) {
|
|
|
313
313
|
const inAnalysis = (path) => own.has(path) || input.consumers.some((root) => path === root || path.startsWith(`${root}/`));
|
|
314
314
|
const implemented = implementedOwners(project, inAnalysis);
|
|
315
315
|
/**
|
|
316
|
-
* Whether every
|
|
317
|
-
* declaration has no body, so the code that runs is
|
|
318
|
-
* member of an interface or an object type a class
|
|
319
|
-
* analysis implements; `settled` false skips that
|
|
316
|
+
* Whether every one of `declarations`, all of the callee's by default, is outside the
|
|
317
|
+
* analysis or ambient. An ambient declaration has no body, so the code that runs is
|
|
318
|
+
* outside the analysis, unless it is a member of an interface or an object type a class
|
|
319
|
+
* or a typed object literal of the analysis implements; `settled` false skips that
|
|
320
|
+
* question, which asks the checker.
|
|
320
321
|
*/
|
|
321
|
-
const outside = (target, settled
|
|
322
|
-
|
|
322
|
+
const outside = (target, settled, declarations = target.declarations) => declarations.length > 0 &&
|
|
323
|
+
declarations.every((handle) => {
|
|
323
324
|
if (!inAnalysis(handle.path)) {
|
|
324
325
|
return true;
|
|
325
326
|
}
|
|
@@ -333,6 +334,12 @@ function destinations(input, chains, walked) {
|
|
|
333
334
|
const owner = must(queries.parentOf(target));
|
|
334
335
|
return owner === undefined || !implemented().has(owner.id);
|
|
335
336
|
});
|
|
337
|
+
/**
|
|
338
|
+
* Whether some one declaration of a callee is outside the analysis or ambient, without
|
|
339
|
+
* asking the checker. A project's declaration merged into a library's symbol leaves the
|
|
340
|
+
* library's beside it, so a call can still resolve to the library's.
|
|
341
|
+
*/
|
|
342
|
+
const mayBeOutside = (target) => target.declarations.some((handle) => outside(target, false, [handle]));
|
|
336
343
|
const invocation = (call) => {
|
|
337
344
|
const form = forms.get(call);
|
|
338
345
|
const method = form === undefined ? undefined : callees.get(form.method);
|
|
@@ -340,7 +347,7 @@ function destinations(input, chains, walked) {
|
|
|
340
347
|
// program's own `call` and `Reflect.apply` invoke nothing here.
|
|
341
348
|
if (form !== undefined &&
|
|
342
349
|
method !== undefined &&
|
|
343
|
-
|
|
350
|
+
mayBeOutside(method) &&
|
|
344
351
|
FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")) {
|
|
345
352
|
return form.invocation;
|
|
346
353
|
}
|
|
@@ -350,6 +357,30 @@ function destinations(input, chains, walked) {
|
|
|
350
357
|
const name = calleeName(invocation(call).callee);
|
|
351
358
|
return name === undefined ? undefined : callees.get(name);
|
|
352
359
|
};
|
|
360
|
+
const signatures = new Map();
|
|
361
|
+
const resolvedOf = (call) => {
|
|
362
|
+
if (!signatures.has(call)) {
|
|
363
|
+
signatures.set(call, must(queries.resolvedSignature(call)));
|
|
364
|
+
}
|
|
365
|
+
return signatures.get(call);
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* The declarations of a callee that the declaration of a direct call's resolved
|
|
369
|
+
* signature lies in, or all of them where it lies in none: a declaration merged into
|
|
370
|
+
* the callee's symbol that the call does not resolve to does not decide where it runs.
|
|
371
|
+
*/
|
|
372
|
+
const calledDeclarations = (call, target) => {
|
|
373
|
+
const declaration = invocation(call).indirect ? undefined : resolvedOf(call)?.declaration;
|
|
374
|
+
const signature = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
375
|
+
if (declaration === undefined || signature === undefined) {
|
|
376
|
+
return target.declarations;
|
|
377
|
+
}
|
|
378
|
+
const containing = target.declarations.filter((handle) => {
|
|
379
|
+
const node = handle.path === declaration.path ? project.declarationAt(handle)?.node : undefined;
|
|
380
|
+
return node !== undefined && node.pos <= signature.pos && signature.end <= node.end;
|
|
381
|
+
});
|
|
382
|
+
return containing.length === 0 ? target.declarations : containing;
|
|
383
|
+
};
|
|
353
384
|
const erasedArguments = new Map();
|
|
354
385
|
const erasedAt = (call, argument) => {
|
|
355
386
|
let indexes = erasedArguments.get(call);
|
|
@@ -362,7 +393,7 @@ function destinations(input, chains, walked) {
|
|
|
362
393
|
? type === undefined
|
|
363
394
|
? []
|
|
364
395
|
: must(queries.signaturesOf(type, SignatureKind.Call))
|
|
365
|
-
: [
|
|
396
|
+
: [resolvedOf(call)].filter((one) => one !== undefined);
|
|
366
397
|
indexes = new Set(invoked.arguments.flatMap((_unused, index) => signatures.some((signature) => {
|
|
367
398
|
const parameter = must(queries.parameterType(signature, index));
|
|
368
399
|
return parameter !== undefined && (parameter.flags & ERASED) !== 0;
|
|
@@ -384,7 +415,7 @@ function destinations(input, chains, walked) {
|
|
|
384
415
|
if (entry !== undefined) {
|
|
385
416
|
return { reach: "properties", callee: spelledEntry(entry.entry, held) };
|
|
386
417
|
}
|
|
387
|
-
if (outside(target, true)) {
|
|
418
|
+
if (outside(target, true, calledDeclarations(call, target))) {
|
|
388
419
|
return erasedAt(call, argument)
|
|
389
420
|
? {
|
|
390
421
|
reach: "conversions",
|
|
@@ -419,7 +450,7 @@ function destinations(input, chains, walked) {
|
|
|
419
450
|
if (target === undefined) {
|
|
420
451
|
return false;
|
|
421
452
|
}
|
|
422
|
-
if (named.some((one) => names(one, target, chains)) ||
|
|
453
|
+
if (named.some((one) => names(one, target, chains)) || mayBeOutside(target)) {
|
|
423
454
|
return true;
|
|
424
455
|
}
|
|
425
456
|
return chains
|
|
@@ -509,7 +540,7 @@ export function serializationContract(input) {
|
|
|
509
540
|
// A destination holds back a data member and `toJSON`, and `toString` as well where
|
|
510
541
|
// the value left the analysis.
|
|
511
542
|
const holdsBack = (flags, name, to) => (flags & SymbolFlags.Property) !== 0 ||
|
|
512
|
-
((flags &
|
|
543
|
+
((flags & CONVERSION_MEMBERS) !== 0 &&
|
|
513
544
|
(name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
|
|
514
545
|
const found = [];
|
|
515
546
|
for (const flow of flows) {
|
package/dist/src/session.js
CHANGED
|
@@ -189,12 +189,30 @@ export function ownedAs(project, own) {
|
|
|
189
189
|
},
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The program's diagnostics placed in no file. The per-file sets never carry one; the
|
|
194
|
+
* client answers its `fileName` as `null`, and the global set repeats the program set:
|
|
195
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/api/session.go#L3726-L3752
|
|
196
|
+
*/
|
|
197
|
+
function unplacedOf(program) {
|
|
198
|
+
const seen = new Set();
|
|
199
|
+
return [...program.getGlobalDiagnostics(), ...program.getProgramDiagnostics()]
|
|
200
|
+
.filter((diagnostic) => diagnostic.fileName == null)
|
|
201
|
+
.filter((diagnostic) => {
|
|
202
|
+
const key = `${String(diagnostic.code)}\u0000${diagnostic.text}`;
|
|
203
|
+
const fresh = !seen.has(key);
|
|
204
|
+
seen.add(key);
|
|
205
|
+
return fresh;
|
|
206
|
+
})
|
|
207
|
+
.map((diagnostic) => ({ ...diagnostic, fileName: undefined }));
|
|
208
|
+
}
|
|
209
|
+
/** The diagnostic sets one project carries, read before any analysis. */
|
|
193
210
|
export function diagnosticsOf(project) {
|
|
194
211
|
return {
|
|
195
212
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
196
213
|
semantic: project.program.getSemanticDiagnostics(),
|
|
197
214
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
215
|
+
unplaced: unplacedOf(project.program),
|
|
198
216
|
locate: (fileName, offset) => {
|
|
199
217
|
const file = project.program.getSourceFile(fileName);
|
|
200
218
|
if (file === undefined || offset < 0 || offset > file.text.length) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isBindingElement, isBreakStatement, isClassDeclaration, isClassExpression, isContinueStatement, isEnumDeclaration, isEnumMember, isExportDeclaration, isExportSpecifier, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isImportClause, isImportEqualsDeclaration, isImportSpecifier, isInterfaceDeclaration, isJsxAttribute, isLabeledStatement, isMetaProperty, isMethodDeclaration, isMethodSignatureDeclaration, isModuleDeclaration, isNamespaceExport, isNamespaceExportDeclaration, isNamespaceImport, isParameterDeclaration, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isPropertySignatureDeclaration, isQualifiedName, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeParameterDeclaration, isVariableDeclaration, } from "@typescript/native/ast";
|
|
2
|
+
/**
|
|
3
|
+
* Whether one identifier spells a name without referring to a binding of it: a member's
|
|
4
|
+
* name after a dot, a property or member an object literal, a class, a type or an enum
|
|
5
|
+
* declares, the name a declaration binds, the property a binding element destructures,
|
|
6
|
+
* an import or export specifier's name in another module or under a rename, a JSX
|
|
7
|
+
* attribute's name, a statement label, or the name of a meta-property. A shorthand
|
|
8
|
+
* property and an unrenamed local export are no such spelling: each reads the binding.
|
|
9
|
+
*/
|
|
10
|
+
export function spellsNoBinding(node) {
|
|
11
|
+
const parent = node.parent;
|
|
12
|
+
if (isPropertyAccessExpression(parent) || isMetaProperty(parent)) {
|
|
13
|
+
return parent.name === node;
|
|
14
|
+
}
|
|
15
|
+
if (isQualifiedName(parent)) {
|
|
16
|
+
return parent.right === node;
|
|
17
|
+
}
|
|
18
|
+
if (isBindingElement(parent)) {
|
|
19
|
+
return parent.propertyName === node || parent.name === node;
|
|
20
|
+
}
|
|
21
|
+
if (isImportSpecifier(parent)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (isExportSpecifier(parent)) {
|
|
25
|
+
return ((isExportDeclaration(parent.parent.parent) &&
|
|
26
|
+
parent.parent.parent.moduleSpecifier !== undefined) ||
|
|
27
|
+
(parent.propertyName !== undefined && parent.name === node));
|
|
28
|
+
}
|
|
29
|
+
if (isPropertyAssignment(parent) ||
|
|
30
|
+
isPropertyDeclaration(parent) ||
|
|
31
|
+
isMethodDeclaration(parent) ||
|
|
32
|
+
isPropertySignatureDeclaration(parent) ||
|
|
33
|
+
isMethodSignatureDeclaration(parent) ||
|
|
34
|
+
isGetAccessorDeclaration(parent) ||
|
|
35
|
+
isSetAccessorDeclaration(parent) ||
|
|
36
|
+
isEnumMember(parent) ||
|
|
37
|
+
isJsxAttribute(parent) ||
|
|
38
|
+
isVariableDeclaration(parent) ||
|
|
39
|
+
isParameterDeclaration(parent) ||
|
|
40
|
+
isFunctionDeclaration(parent) ||
|
|
41
|
+
isFunctionExpression(parent) ||
|
|
42
|
+
isClassDeclaration(parent) ||
|
|
43
|
+
isClassExpression(parent) ||
|
|
44
|
+
isInterfaceDeclaration(parent) ||
|
|
45
|
+
isTypeAliasDeclaration(parent) ||
|
|
46
|
+
isEnumDeclaration(parent) ||
|
|
47
|
+
isModuleDeclaration(parent) ||
|
|
48
|
+
isTypeParameterDeclaration(parent) ||
|
|
49
|
+
isImportClause(parent) ||
|
|
50
|
+
isNamespaceImport(parent) ||
|
|
51
|
+
isImportEqualsDeclaration(parent) ||
|
|
52
|
+
isNamespaceExport(parent) ||
|
|
53
|
+
isNamespaceExportDeclaration(parent)) {
|
|
54
|
+
return parent.name === node;
|
|
55
|
+
}
|
|
56
|
+
return ((isLabeledStatement(parent) || isBreakStatement(parent) || isContinueStatement(parent)) &&
|
|
57
|
+
parent.label === node);
|
|
58
|
+
}
|
|
@@ -51,7 +51,7 @@ function isObject(value) {
|
|
|
51
51
|
*/
|
|
52
52
|
function readRecord(value, site, byReference, shape) {
|
|
53
53
|
const refuse = (text, want) => {
|
|
54
|
-
throw new SuppressionError(shape.mechanism,
|
|
54
|
+
throw new SuppressionError(shape.mechanism, siteText(site), text, want);
|
|
55
55
|
};
|
|
56
56
|
if (!isObject(value)) {
|
|
57
57
|
return refuse(JSON.stringify(value), shape.recordWant);
|
|
@@ -159,7 +159,7 @@ function readDocument(host, targetRoot, symbols, shape) {
|
|
|
159
159
|
const records = [];
|
|
160
160
|
const refusals = [];
|
|
161
161
|
held.forEach((value, index) => {
|
|
162
|
-
const offset = walk.
|
|
162
|
+
const offset = walk.starts[index];
|
|
163
163
|
const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
|
|
164
164
|
const read = readRecord(value, site, byReference, shape);
|
|
165
165
|
records.push(...read.records);
|
|
@@ -100,7 +100,9 @@ function lineComments(file) {
|
|
|
100
100
|
const spans = [];
|
|
101
101
|
const visit = (node) => {
|
|
102
102
|
if (LITERALS.has(node.kind)) {
|
|
103
|
-
|
|
103
|
+
// JSX text carries no trivia: its first code unit is at `pos`, even a `//`.
|
|
104
|
+
const start = node.kind === SyntaxKind.JsxText ? node.pos : tokenStart(text, node.pos);
|
|
105
|
+
spans.push({ start, end: node.end });
|
|
104
106
|
}
|
|
105
107
|
node.forEachChild(visit);
|
|
106
108
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { readComponent } from "./component-files.js";
|
|
15
15
|
import { ConfigError } from "./config.js";
|
|
16
16
|
import { memberNames } from "./member-names.js";
|
|
17
|
-
import { isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
17
|
+
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
18
|
import { isComponentFile } from "./position.js";
|
|
19
19
|
/** The kinds of declaration an action can name. */
|
|
20
20
|
const READ_BY_A_TEMPLATE = new Set([
|
|
@@ -87,7 +87,10 @@ function stringEnd(text, from) {
|
|
|
87
87
|
}
|
|
88
88
|
return at + 1;
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* The end of the `${` substitution whose expression starts at `from`: past its closing brace,
|
|
92
|
+
* or one past the text's end where none closes it, so the expression is the text to its end.
|
|
93
|
+
*/
|
|
91
94
|
function substitutionEnd(text, from) {
|
|
92
95
|
let depth = 1;
|
|
93
96
|
let at = from;
|
|
@@ -112,7 +115,7 @@ function substitutionEnd(text, from) {
|
|
|
112
115
|
}
|
|
113
116
|
at += 1;
|
|
114
117
|
}
|
|
115
|
-
return
|
|
118
|
+
return text.length + 1;
|
|
116
119
|
}
|
|
117
120
|
/** The end of the template literal at `from`; each `${…}` expression goes to `substitution`. */
|
|
118
121
|
function templateEnd(text, from, substitution) {
|
|
@@ -198,8 +201,8 @@ function namesOf(text, delimiters) {
|
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
/** Every identifier the markup of one component file writes in an action. */
|
|
201
|
-
function markupNamesOf(
|
|
202
|
-
return readComponent(text).markup.flatMap((one) => identifiersOf(one.text, true).map(({ name, offset }) => ({
|
|
204
|
+
function markupNamesOf(file) {
|
|
205
|
+
return readComponent(file.text, extensionOf(file.path)).markup.flatMap((one) => identifiersOf(one.text, true).map(({ name, offset }) => ({
|
|
203
206
|
name,
|
|
204
207
|
offset: one.start + offset,
|
|
205
208
|
action: one.action,
|
|
@@ -238,7 +241,7 @@ export function templateField(input) {
|
|
|
238
241
|
names: namesOf(file.text, templates.delimiters),
|
|
239
242
|
markup: false,
|
|
240
243
|
})),
|
|
241
|
-
...components.map((file) => ({ file, names: markupNamesOf(file
|
|
244
|
+
...components.map((file) => ({ file, names: markupNamesOf(file), markup: true })),
|
|
242
245
|
];
|
|
243
246
|
for (const { file, names, markup } of scanned) {
|
|
244
247
|
for (const named of names) {
|
package/dist/src/type-errors.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* What the errors a project's own files carry do to the run. An import of a module the
|
|
3
3
|
* project expects and nothing provides is a setup failure. Any other error in an own file
|
|
4
4
|
* is a type error: the function, method or file-level statement holding it is skipped,
|
|
5
|
-
* which only ever withholds a finding.
|
|
6
|
-
*
|
|
5
|
+
* which only ever withholds a finding. `projectErrors` decides, before any of these, which
|
|
6
|
+
* errors the compiler places in no file leave the project unloaded.
|
|
7
7
|
*/
|
|
8
8
|
import { isClassDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
|
|
9
9
|
import { packageOfSpecifier } from "./dependencies.js";
|
|
@@ -23,7 +23,7 @@ const MANIFEST_SECTIONS = [
|
|
|
23
23
|
export function insideSkipped(skipped, position) {
|
|
24
24
|
return skipped.some((unit) => unit.path === position.path && unit.fromLine <= position.line && position.line <= unit.toLine);
|
|
25
25
|
}
|
|
26
|
-
/** The string literal holding one offset, innermost first. */
|
|
26
|
+
/** The string literal holding one offset, innermost first, a doc comment's included. */
|
|
27
27
|
function literalAt(file, offset) {
|
|
28
28
|
let found;
|
|
29
29
|
const visit = (node) => {
|
|
@@ -33,32 +33,55 @@ function literalAt(file, offset) {
|
|
|
33
33
|
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
34
34
|
found = node.text;
|
|
35
35
|
}
|
|
36
|
+
// `forEachChild` does not enter a doc comment, where a JavaScript file imports types.
|
|
37
|
+
for (const doc of node.jsDoc ?? []) {
|
|
38
|
+
visit(doc);
|
|
39
|
+
}
|
|
36
40
|
node.forEachChild(visit);
|
|
37
41
|
};
|
|
38
42
|
file.forEachChild(visit);
|
|
39
43
|
return found;
|
|
40
44
|
}
|
|
41
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* What one `paths` key's `*` matches in one specifier, `""` for a key with no `*` that
|
|
47
|
+
* is the specifier, or `undefined` where the key does not match it.
|
|
48
|
+
*/
|
|
42
49
|
function mappedBy(key, specifier) {
|
|
43
50
|
const star = key.indexOf("*");
|
|
44
51
|
if (star < 0) {
|
|
45
|
-
return key === specifier;
|
|
52
|
+
return key === specifier ? "" : undefined;
|
|
46
53
|
}
|
|
47
54
|
const prefix = key.slice(0, star);
|
|
48
55
|
const suffix = key.slice(star + 1);
|
|
49
|
-
return
|
|
56
|
+
return specifier.length >= prefix.length + suffix.length &&
|
|
50
57
|
specifier.startsWith(prefix) &&
|
|
51
|
-
specifier.endsWith(suffix)
|
|
58
|
+
specifier.endsWith(suffix)
|
|
59
|
+
? specifier.slice(prefix.length, specifier.length - suffix.length)
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One `paths` target, its first `*` replaced by `star` taken literally, as the compiler does:
|
|
64
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L1282
|
|
65
|
+
*/
|
|
66
|
+
function substituted(target, star) {
|
|
67
|
+
const at = target.indexOf("*");
|
|
68
|
+
return at < 0 ? target : target.slice(0, at) + star + target.slice(at + 1);
|
|
52
69
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Whether a path names something on disk, as written or with any extension; under
|
|
72
|
+
* `fileOnly`, only a file counts.
|
|
73
|
+
*/
|
|
74
|
+
function provided(host, path, fileOnly) {
|
|
75
|
+
const counts = (at) => fileOnly ? host.kindOf(at) === "file" : host.kindOf(at) !== "absent";
|
|
76
|
+
if (counts(path)) {
|
|
56
77
|
return true;
|
|
57
78
|
}
|
|
58
79
|
const dir = dirnamePath(path);
|
|
59
80
|
const stem = `${path.slice(dir.length + 1)}.`;
|
|
60
81
|
try {
|
|
61
|
-
return host
|
|
82
|
+
return host
|
|
83
|
+
.readDirectory(dir)
|
|
84
|
+
.some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
|
|
62
85
|
}
|
|
63
86
|
catch {
|
|
64
87
|
return false;
|
|
@@ -90,6 +113,20 @@ function declaringManifest(host, dir, targetRoot, name) {
|
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether a package is installed for an importer in `dir`: in the `node_modules` of
|
|
118
|
+
* `dir` or of any ancestor, above the target root too, as Node and the compiler look.
|
|
119
|
+
*/
|
|
120
|
+
function installed(host, dir, name) {
|
|
121
|
+
for (let at = dir;; at = dirnamePath(at)) {
|
|
122
|
+
if (host.kindOf(joinPath(at, "node_modules", name)) === "directory") {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (dirnamePath(at) === at) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
93
130
|
/**
|
|
94
131
|
* Why one import that the compiler cannot resolve names a module the project expects,
|
|
95
132
|
* with the fix, or undefined where the module is one the project does not expect.
|
|
@@ -98,17 +135,31 @@ function expectedModule(context, importer, specifier) {
|
|
|
98
135
|
const { host, targetRoot } = context;
|
|
99
136
|
const fix = "run the generator or the build that writes it";
|
|
100
137
|
if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
|
|
101
|
-
return provided(host, resolvePath(dirnamePath(importer), specifier))
|
|
138
|
+
return provided(host, resolvePath(dirnamePath(importer), specifier), false)
|
|
102
139
|
? undefined
|
|
103
140
|
: `which names no file of the target: ${fix}`;
|
|
104
141
|
}
|
|
105
142
|
if (specifier.startsWith("#")) {
|
|
106
143
|
return `which the manifest's imports map to no file: ${fix}`;
|
|
107
144
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
145
|
+
const mapped = context.paths.entries.flatMap(({ key, targets }) => {
|
|
146
|
+
const star = mappedBy(key, specifier);
|
|
147
|
+
return star === undefined
|
|
148
|
+
? []
|
|
149
|
+
: targets.map((target) => resolvePath(context.paths.base, substituted(target, star)));
|
|
150
|
+
});
|
|
111
151
|
const name = packageOfSpecifier(specifier);
|
|
152
|
+
// The compiler looks up in `node_modules` what `paths` does not resolve, so the error
|
|
153
|
+
// of an installed package's import is the import's own, a subpath its exports omit:
|
|
154
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L548-L575
|
|
155
|
+
if (name !== undefined && installed(host, dirnamePath(importer), name)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (mapped.length > 0) {
|
|
159
|
+
return mapped.some((path) => provided(host, path, true))
|
|
160
|
+
? undefined
|
|
161
|
+
: `which the compiler configuration's paths map to no file: ${fix}`;
|
|
162
|
+
}
|
|
112
163
|
const manifest = name === undefined
|
|
113
164
|
? undefined
|
|
114
165
|
: declaringManifest(host, dirnamePath(importer), targetRoot, name);
|
|
@@ -143,14 +194,8 @@ export function readErrors(project, errors, context) {
|
|
|
143
194
|
const files = new Map(project.ownSourceFiles().map((file) => [file.fileName, file]));
|
|
144
195
|
const missing = [];
|
|
145
196
|
const skips = [];
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const { diagnostic, at } = located;
|
|
149
|
-
if (diagnostic.fileName === undefined) {
|
|
150
|
-
failing.push(located);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const file = files.get(diagnostic.fileName);
|
|
197
|
+
for (const { diagnostic, at } of errors) {
|
|
198
|
+
const file = diagnostic.fileName === undefined ? undefined : files.get(diagnostic.fileName);
|
|
154
199
|
if (file === undefined || diagnostic.reportsUnnecessary === true) {
|
|
155
200
|
continue;
|
|
156
201
|
}
|
|
@@ -176,7 +221,7 @@ export function readErrors(project, errors, context) {
|
|
|
176
221
|
});
|
|
177
222
|
}
|
|
178
223
|
}
|
|
179
|
-
return { missing, skips
|
|
224
|
+
return { missing, skips };
|
|
180
225
|
}
|
|
181
226
|
/** The lines each skip withholds, below the target root. */
|
|
182
227
|
export function skippedUnits(skips, targetRoot) {
|
|
@@ -241,13 +286,22 @@ export function skippedRoots(project, held, skips) {
|
|
|
241
286
|
}
|
|
242
287
|
return [...kept];
|
|
243
288
|
}
|
|
244
|
-
/**
|
|
289
|
+
/** One compiler configuration's `paths` mapping, empty where it cannot be read. */
|
|
245
290
|
export function pathsOf(engine, configFile) {
|
|
291
|
+
const base = dirnamePath(configFile);
|
|
246
292
|
try {
|
|
247
|
-
const
|
|
248
|
-
|
|
293
|
+
const { options } = engine.parseConfigFile(configFile);
|
|
294
|
+
const { paths } = options;
|
|
295
|
+
// The parse answers `pathsBasePath`, the directory of the configuration that writes
|
|
296
|
+
// `paths` (an extended one included), though the client's option type omits it:
|
|
297
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/tsoptions/tsconfigparsing.go#L1120
|
|
298
|
+
const declaredAt = options.pathsBasePath;
|
|
299
|
+
return {
|
|
300
|
+
base: typeof declaredAt === "string" ? declaredAt : base,
|
|
301
|
+
entries: Object.entries(paths ?? {}).map(([key, targets]) => ({ key, targets })),
|
|
302
|
+
};
|
|
249
303
|
}
|
|
250
304
|
catch {
|
|
251
|
-
return [];
|
|
305
|
+
return { base, entries: [] };
|
|
252
306
|
}
|
|
253
307
|
}
|
package/dist/src/value-reach.js
CHANGED
|
@@ -10,12 +10,22 @@ import { isAsExpression, isParenthesizedExpression, isSatisfiesExpression, isTyp
|
|
|
10
10
|
import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
|
|
11
11
|
import { classNames, typesAt } from "./class-members.js";
|
|
12
12
|
import { must } from "./query.js";
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The object types whose members the walk reads whatever declares them. A reverse-mapped
|
|
15
|
+
* type has no symbol: it is the shape the checker infers from an argument the target
|
|
16
|
+
* writes against a homomorphic mapped type.
|
|
17
|
+
*/
|
|
18
|
+
const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped | ObjectFlags.ReverseMapped;
|
|
15
19
|
/** The symbols of an anonymous object type the target writes as a type. */
|
|
16
20
|
const LITERAL_SYMBOLS = SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral;
|
|
17
21
|
/** The two conversion methods a serializer or a formatter resolves on a value's own shape. */
|
|
18
22
|
const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
|
|
23
|
+
/**
|
|
24
|
+
* The members that can answer a conversion: a serializer reads the name with `[[Get]]`
|
|
25
|
+
* and calls what it answers, so a getter answers it as a method does:
|
|
26
|
+
* https://tc39.es/ecma262/#sec-serializejsonproperty (step 2).
|
|
27
|
+
*/
|
|
28
|
+
export const CONVERSION_MEMBERS = SymbolFlags.Method | SymbolFlags.GetAccessor;
|
|
19
29
|
/**
|
|
20
30
|
* The types each value is checked against by the `satisfies` expressions and type assertions
|
|
21
31
|
* it is written as, through parentheses, and the innermost operand's own type where there is
|
|
@@ -92,7 +102,9 @@ export function valueReach(project, held, stored = () => []) {
|
|
|
92
102
|
if ((type.objectFlags & ObjectFlags.Anonymous) !== 0) {
|
|
93
103
|
return (symbol.flags & LITERAL_SYMBOLS) !== 0;
|
|
94
104
|
}
|
|
95
|
-
|
|
105
|
+
// A class the inventory holds is read as a class before this, so an own class
|
|
106
|
+
// here is one it does not hold, such as a class declared in a function.
|
|
107
|
+
return ((symbol.flags & (SymbolFlags.Interface | SymbolFlags.Class)) !== 0 &&
|
|
96
108
|
symbol.declarations.every((handle) => own.has(handle.path)));
|
|
97
109
|
};
|
|
98
110
|
/** The types a value of one object type holds in its data members. */
|
|
@@ -115,9 +127,9 @@ export function valueReach(project, held, stored = () => []) {
|
|
|
115
127
|
(target.isObjectType() && (target.objectFlags & ObjectFlags.Tuple) !== 0));
|
|
116
128
|
return !isList && isWritten(type);
|
|
117
129
|
};
|
|
118
|
-
/** The data members and conversion
|
|
130
|
+
/** The data members and conversion members of one written object type. */
|
|
119
131
|
const carried = (type) => must(queries.propertiesOf(type)).filter((property) => (property.flags & SymbolFlags.Property) !== 0 ||
|
|
120
|
-
((property.flags &
|
|
132
|
+
((property.flags & CONVERSION_MEMBERS) !== 0 && CONVERSION_NAMES.has(property.name)));
|
|
121
133
|
/** The types a value of one type holds, read once per type. */
|
|
122
134
|
const childrenOf = (type) => {
|
|
123
135
|
const known = children.get(type.id);
|
|
@@ -161,6 +161,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
161
161
|
let partial;
|
|
162
162
|
let warnings;
|
|
163
163
|
let unread;
|
|
164
|
+
let unfound;
|
|
164
165
|
try {
|
|
165
166
|
const root = runRelative(invoked, targetRoot);
|
|
166
167
|
const mode = { production: true };
|
|
@@ -178,6 +179,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
178
179
|
const { result, run } = analysis;
|
|
179
180
|
warnings = run.componentWarnings;
|
|
180
181
|
unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
|
|
182
|
+
unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
|
|
181
183
|
partial = partialNotes({
|
|
182
184
|
notBuilt: run.notBuilt,
|
|
183
185
|
unanswered: run.unanswered,
|
|
@@ -243,7 +245,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
243
245
|
for (const note of partial) {
|
|
244
246
|
err.write(`deadset-ts: ${note}\n`);
|
|
245
247
|
}
|
|
246
|
-
for (const line of unread) {
|
|
248
|
+
for (const line of [...unread, ...unfound]) {
|
|
247
249
|
err.write(`deadset-ts: ${line}\n`);
|
|
248
250
|
}
|
|
249
251
|
for (const warning of warnings) {
|