@cplieger/deadset-ts 7.2.0-dev.3 → 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 +4 -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 +1 -1
- package/src/analysis.ts +32 -6
- package/src/component-files.ts +116 -31
- package/src/configuration-files.ts +14 -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/json-document.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Position } from "./position.ts";
|
|
|
7
7
|
|
|
8
8
|
/** The token walk over a document `JSON.parse` accepted, so every token is well formed. */
|
|
9
9
|
class Walk {
|
|
10
|
-
readonly
|
|
10
|
+
readonly starts: number[] = [];
|
|
11
11
|
/** The member written twice, where one is, as the offset of its name. */
|
|
12
12
|
twice: { readonly name: string; readonly offset: number } | undefined;
|
|
13
13
|
private readonly text: string;
|
|
@@ -41,11 +41,11 @@ class Walk {
|
|
|
41
41
|
|
|
42
42
|
private value(role: "document" | "array" | "record" | "other"): void {
|
|
43
43
|
this.skip();
|
|
44
|
+
if (role === "record") {
|
|
45
|
+
this.starts.push(this.at);
|
|
46
|
+
}
|
|
44
47
|
const char = this.text[this.at];
|
|
45
48
|
if (char === "{") {
|
|
46
|
-
if (role === "record") {
|
|
47
|
-
this.opened.push(this.at);
|
|
48
|
-
}
|
|
49
49
|
this.object(role);
|
|
50
50
|
} else if (char === "[") {
|
|
51
51
|
this.list(role === "array" ? "record" : "other");
|
|
@@ -101,8 +101,8 @@ class Walk {
|
|
|
101
101
|
|
|
102
102
|
/** What a walk of one document found. */
|
|
103
103
|
interface DocumentWalk {
|
|
104
|
-
/** The offset of
|
|
105
|
-
readonly
|
|
104
|
+
/** The offset of each element of the document's array member, in order: an object's `{`. */
|
|
105
|
+
readonly starts: readonly number[];
|
|
106
106
|
/** The first member one object writes twice, at any depth, as the offset of its name. */
|
|
107
107
|
readonly twice: { readonly name: string; readonly offset: number } | undefined;
|
|
108
108
|
}
|
|
@@ -116,7 +116,7 @@ interface DocumentWalk {
|
|
|
116
116
|
export function walkDocument(text: string, array: string): DocumentWalk {
|
|
117
117
|
const walk = new Walk(text, array);
|
|
118
118
|
walk.run();
|
|
119
|
-
return {
|
|
119
|
+
return { starts: walk.starts, twice: walk.twice };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
package/src/namespace-objects.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module namespace objects: the binding of a namespace import, the value an `await` of a
|
|
3
3
|
* dynamic `import()` produces, the first parameter of the first callback passed to `then`
|
|
4
|
-
* on one, and a
|
|
4
|
+
* on one, and a variable binding initialized with one of these. A use reads the exports it
|
|
5
5
|
* names and no other: a property access by name or by a literal index, or a destructured
|
|
6
6
|
* property, names one export, and any other use may read every export.
|
|
7
7
|
*/
|
|
@@ -9,22 +9,26 @@
|
|
|
9
9
|
import {
|
|
10
10
|
isArrowFunction,
|
|
11
11
|
isAwaitExpression,
|
|
12
|
-
isBindingElement,
|
|
13
12
|
isCallExpression,
|
|
13
|
+
isClassStaticBlockDeclaration,
|
|
14
14
|
isElementAccessExpression,
|
|
15
15
|
isExpressionStatement,
|
|
16
16
|
isFunctionExpression,
|
|
17
|
+
isFunctionLikeDeclaration,
|
|
17
18
|
isIdentifier,
|
|
18
19
|
isImportDeclaration,
|
|
20
|
+
isModuleBlock,
|
|
19
21
|
isNamespaceImport,
|
|
20
22
|
isNoSubstitutionTemplateLiteral,
|
|
21
23
|
isObjectBindingPattern,
|
|
22
24
|
isParenthesizedExpression,
|
|
23
25
|
isPropertyAccessExpression,
|
|
24
26
|
isQualifiedName,
|
|
27
|
+
isSourceFile,
|
|
25
28
|
isStringLiteral,
|
|
26
29
|
isVariableDeclaration,
|
|
27
30
|
isVariableDeclarationList,
|
|
31
|
+
isVariableStatement,
|
|
28
32
|
isVoidExpression,
|
|
29
33
|
NodeFlags,
|
|
30
34
|
SyntaxKind,
|
|
@@ -32,6 +36,7 @@ import {
|
|
|
32
36
|
type Node,
|
|
33
37
|
type SourceFile,
|
|
34
38
|
} from "@typescript/native/ast";
|
|
39
|
+
import { spellsNoBinding } from "./spelled-names.ts";
|
|
35
40
|
|
|
36
41
|
/** What one use of a namespace object reads beyond the names the checker resolves. */
|
|
37
42
|
type NamespaceUse =
|
|
@@ -62,23 +67,33 @@ function isLiteralImport(node: Node): boolean {
|
|
|
62
67
|
return argument !== undefined && isLiteralText(argument);
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The node the binding one declaration list declares is visible below: for `let` and
|
|
72
|
+
* `const`, the loop whose head declares it, or else the block or file its statement is
|
|
73
|
+
* in; for `var`, the function, namespace body, static block or file that holds it.
|
|
74
|
+
*/
|
|
75
|
+
function scopeOf(list: Node): Node {
|
|
76
|
+
if ((list.flags & (NodeFlags.Let | NodeFlags.Const | NodeFlags.Using)) !== 0) {
|
|
77
|
+
return isVariableStatement(list.parent) ? list.parent.parent : list.parent;
|
|
78
|
+
}
|
|
79
|
+
let at = list.parent;
|
|
80
|
+
while (
|
|
81
|
+
!isSourceFile(at) &&
|
|
82
|
+
!isFunctionLikeDeclaration(at) &&
|
|
83
|
+
!isModuleBlock(at) &&
|
|
84
|
+
!isClassStaticBlockDeclaration(at)
|
|
85
|
+
) {
|
|
86
|
+
at = at.parent;
|
|
87
|
+
}
|
|
88
|
+
return at;
|
|
69
89
|
}
|
|
70
90
|
|
|
71
|
-
/** Every identifier below `scope` that spells `name
|
|
91
|
+
/** Every identifier below `scope` that spells `name` as a reference, other than `declared`. */
|
|
72
92
|
function occurrences(scope: Node, name: string, declared: Node): readonly Identifier[] {
|
|
73
93
|
const found: Identifier[] = [];
|
|
74
94
|
const visit = (node: Node): void => {
|
|
75
95
|
if (isIdentifier(node)) {
|
|
76
|
-
|
|
77
|
-
const member =
|
|
78
|
-
(isPropertyAccessExpression(parent) && parent.name === node) ||
|
|
79
|
-
(isQualifiedName(parent) && parent.right === node) ||
|
|
80
|
-
(isBindingElement(parent) && parent.propertyName === node);
|
|
81
|
-
if (node !== declared && node.text === name && !member) {
|
|
96
|
+
if (node !== declared && node.text === name && !spellsNoBinding(node)) {
|
|
82
97
|
found.push(node);
|
|
83
98
|
}
|
|
84
99
|
return;
|
|
@@ -92,8 +107,8 @@ function occurrences(scope: Node, name: string, declared: Node): readonly Identi
|
|
|
92
107
|
/**
|
|
93
108
|
* Every use of the namespace objects one file holds, keyed by the node the use is written
|
|
94
109
|
* at, that reads more than a name the checker resolves: an indexed read by a literal, and
|
|
95
|
-
* any use that may read every export. A shadowing declaration of a binding's
|
|
96
|
-
* its scope is read as the binding, which can only keep an export live.
|
|
110
|
+
* any use that may read every export. A use of a shadowing declaration of a binding's
|
|
111
|
+
* name inside its scope is read as a use of the binding, which can only keep an export live.
|
|
97
112
|
*/
|
|
98
113
|
export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse> {
|
|
99
114
|
const uses = new Map<Node, NamespaceUse>();
|
|
@@ -142,13 +157,11 @@ export function namespaceUses(file: SourceFile): ReadonlyMap<Node, NamespaceUse>
|
|
|
142
157
|
}
|
|
143
158
|
return;
|
|
144
159
|
}
|
|
160
|
+
// A binding that is not `const` is followed too: each assignment to it is an
|
|
161
|
+
// occurrence `value` cannot narrow, so a reassigned binding still reads every export.
|
|
145
162
|
const list = parent.parent;
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
isVariableDeclarationList(list) &&
|
|
149
|
-
(list.flags & NodeFlags.Const) !== 0
|
|
150
|
-
) {
|
|
151
|
-
binding(pattern, scopeOf(parent), specifier, seen);
|
|
163
|
+
if (isIdentifier(pattern) && isVariableDeclarationList(list)) {
|
|
164
|
+
binding(pattern, scopeOf(list), specifier, seen);
|
|
152
165
|
return;
|
|
153
166
|
}
|
|
154
167
|
}
|
package/src/overrides.ts
CHANGED
|
@@ -26,6 +26,11 @@ import { nameComponent } from "./ref.ts";
|
|
|
26
26
|
import type { Reference } from "./references.ts";
|
|
27
27
|
import type { ProjectView } from "./session.ts";
|
|
28
28
|
|
|
29
|
+
const CLASS_DECLARATIONS: ReadonlySet<SyntaxKind> = new Set([
|
|
30
|
+
SyntaxKind.ClassDeclaration,
|
|
31
|
+
SyntaxKind.ClassExpression,
|
|
32
|
+
]);
|
|
33
|
+
|
|
29
34
|
/** The member kinds a subclass overrides. */
|
|
30
35
|
const OVERRIDABLE: ReadonlySet<string> = new Set(["method", "class-member"]);
|
|
31
36
|
|
|
@@ -111,8 +116,11 @@ export function overrideReferences<Brand>(
|
|
|
111
116
|
inTarget.length === 0 &&
|
|
112
117
|
declared !== UNANSWERED &&
|
|
113
118
|
(declared.flags & SymbolFlags.Class) !== 0 &&
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
// The target's augmentation of an outside class merges an interface into it,
|
|
120
|
+
// so only a class declaration says where the class is.
|
|
121
|
+
declared.declarations.some(
|
|
122
|
+
(handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
|
|
123
|
+
)
|
|
116
124
|
) {
|
|
117
125
|
outsideBases.set(id, declared);
|
|
118
126
|
}
|
package/src/paths.ts
CHANGED
|
@@ -67,6 +67,16 @@ export function dirnamePath(path: string): string {
|
|
|
67
67
|
return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The extension of one path's last segment, from its last full stop, or the empty string
|
|
72
|
+
* where it has none. A segment that only starts with a full stop has none.
|
|
73
|
+
*/
|
|
74
|
+
export function extensionOf(path: string): string {
|
|
75
|
+
const name = path.slice(path.lastIndexOf(SEPARATOR) + 1);
|
|
76
|
+
const dot = name.lastIndexOf(".");
|
|
77
|
+
return dot <= 0 ? "" : name.slice(dot);
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
/** One path resolved against a directory, absolute where that directory is. */
|
|
71
81
|
export function resolvePath(against: string, path: string): string {
|
|
72
82
|
return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
|
package/src/references.ts
CHANGED
|
@@ -196,10 +196,10 @@ export interface ReferenceCost {
|
|
|
196
196
|
/** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
|
|
197
197
|
readonly contextualLookups: number;
|
|
198
198
|
/**
|
|
199
|
-
* The requests the passes
|
|
200
|
-
* accesses, key comparisons, instantiated signatures
|
|
201
|
-
* callees. They are
|
|
202
|
-
*
|
|
199
|
+
* The requests the passes after the walk make: the bases overrides are read against,
|
|
200
|
+
* element accesses, key comparisons, instantiated signatures, values handed to
|
|
201
|
+
* unimplemented callees and the modules a `declare module` names. They are counted
|
|
202
|
+
* where the caller measures the client, through `requestCount`, and are 0 otherwise.
|
|
203
203
|
*/
|
|
204
204
|
readonly typedLookups: number;
|
|
205
205
|
}
|
|
@@ -1060,19 +1060,28 @@ export function references<Brand>(
|
|
|
1060
1060
|
const fileIds = new Set(
|
|
1061
1061
|
held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
|
|
1062
1062
|
);
|
|
1063
|
-
let
|
|
1064
|
-
/**
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1063
|
+
let moduleBodies: ReadonlyMap<string, readonly InventorySymbol[]> | undefined;
|
|
1064
|
+
/**
|
|
1065
|
+
* The declarations one module holds directly: a file's top level, or the body of a
|
|
1066
|
+
* `declare module` whose name is a string. Any other declaration holds none here.
|
|
1067
|
+
*/
|
|
1068
|
+
const declaredIn = (moduleId: string): readonly InventorySymbol[] => {
|
|
1069
|
+
if (moduleBodies === undefined) {
|
|
1070
|
+
const byModule = new Map<string, InventorySymbol[]>();
|
|
1068
1071
|
for (const symbol of held.symbols) {
|
|
1069
|
-
if (fileIds.has(symbol.parent)) {
|
|
1070
|
-
|
|
1072
|
+
if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const siblings = byModule.get(symbol.parent);
|
|
1076
|
+
if (siblings === undefined) {
|
|
1077
|
+
byModule.set(symbol.parent, [symbol]);
|
|
1078
|
+
} else {
|
|
1079
|
+
siblings.push(symbol);
|
|
1071
1080
|
}
|
|
1072
1081
|
}
|
|
1073
|
-
|
|
1082
|
+
moduleBodies = byModule;
|
|
1074
1083
|
}
|
|
1075
|
-
return
|
|
1084
|
+
return moduleBodies.get(moduleId) ?? [];
|
|
1076
1085
|
};
|
|
1077
1086
|
|
|
1078
1087
|
const exportsCache = new Map<number, readonly ChainTarget[]>();
|
|
@@ -1431,9 +1440,12 @@ export function references<Brand>(
|
|
|
1431
1440
|
|
|
1432
1441
|
for (const url of urls) {
|
|
1433
1442
|
const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
|
|
1443
|
+
// An augmentation of the global `URL` merges into it, which keeps the
|
|
1444
|
+
// library's declaration; a `URL` the target declares has no other.
|
|
1434
1445
|
if (
|
|
1435
1446
|
typeof constructor === "object" &&
|
|
1436
|
-
constructor.declarations.
|
|
1447
|
+
constructor.declarations.length > 0 &&
|
|
1448
|
+
constructor.declarations.every((handle) => ownPaths.has(handle.path))
|
|
1437
1449
|
) {
|
|
1438
1450
|
continue;
|
|
1439
1451
|
}
|
|
@@ -1604,14 +1616,14 @@ export function references<Brand>(
|
|
|
1604
1616
|
|
|
1605
1617
|
let typedLookups = 0;
|
|
1606
1618
|
if (consumer === undefined) {
|
|
1607
|
-
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1608
1619
|
const before = options.requestCount?.() ?? 0;
|
|
1620
|
+
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1609
1621
|
found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1610
1622
|
found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1611
1623
|
found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
|
|
1612
1624
|
found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
|
|
1613
|
-
typedLookups = (options.requestCount?.() ?? 0) - before;
|
|
1614
1625
|
found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
|
|
1626
|
+
typedLookups = (options.requestCount?.() ?? 0) - before;
|
|
1615
1627
|
}
|
|
1616
1628
|
const scripts = new Set(
|
|
1617
1629
|
walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId),
|
package/src/reflective-lookup.ts
CHANGED
|
@@ -120,8 +120,9 @@ export function reflectiveLookup<Brand>(input: DetectorInput<Brand>): readonly E
|
|
|
120
120
|
return [];
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// Whether each written `Reflect` is the global one, asked in one batch: a
|
|
124
|
-
//
|
|
123
|
+
// Whether each written `Reflect` is the global one, asked in one batch: a name
|
|
124
|
+
// declared only in the project's own files shadows it, and an augmentation of the
|
|
125
|
+
// global merges into it, which keeps the library's declaration.
|
|
125
126
|
const ownFiles = project.ownPaths();
|
|
126
127
|
const reflects = lookups.flatMap((lookup) =>
|
|
127
128
|
lookup.reflect === undefined ? [] : [lookup.reflect],
|
|
@@ -131,11 +132,12 @@ export function reflectiveLookup<Brand>(input: DetectorInput<Brand>): readonly E
|
|
|
131
132
|
project.symbolsAt(reflects.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
132
133
|
const node = reflects[index];
|
|
133
134
|
// A name the checker did not answer for is read as the global, which retains.
|
|
134
|
-
const
|
|
135
|
-
symbol === UNANSWERED
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
const isGlobal =
|
|
136
|
+
symbol === UNANSWERED ||
|
|
137
|
+
(symbol !== undefined &&
|
|
138
|
+
(symbol.declarations.length === 0 ||
|
|
139
|
+
symbol.declarations.some((declaration) => !ownFiles.has(declaration.path))));
|
|
140
|
+
if (node !== undefined && isGlobal) {
|
|
139
141
|
global.add(node);
|
|
140
142
|
}
|
|
141
143
|
});
|
|
@@ -912,7 +912,7 @@ class Execution {
|
|
|
912
912
|
case "lit":
|
|
913
913
|
return arg.value;
|
|
914
914
|
case "pipe":
|
|
915
|
-
return this.pipeline(arg.pipe, dot
|
|
915
|
+
return this.pipeline(arg.pipe, dot);
|
|
916
916
|
case "field":
|
|
917
917
|
return this.field(arg.recv === undefined ? dot : this.arg(arg.recv, dot), arg.names);
|
|
918
918
|
case "func":
|
|
@@ -958,18 +958,17 @@ class Execution {
|
|
|
958
958
|
return call(head.name, operands());
|
|
959
959
|
}
|
|
960
960
|
|
|
961
|
-
private pipeline(pipe: Pipeline, dot: Value
|
|
961
|
+
private pipeline(pipe: Pipeline, dot: Value): Value {
|
|
962
962
|
let value: Value | undefined;
|
|
963
963
|
for (const command of pipe.commands) {
|
|
964
964
|
value = this.command(command, dot, value);
|
|
965
965
|
}
|
|
966
966
|
const result = value ?? null;
|
|
967
|
-
const
|
|
968
|
-
if (only !== undefined && (declares || pipe.assign)) {
|
|
967
|
+
for (const name of pipe.vars) {
|
|
969
968
|
if (pipe.assign) {
|
|
970
|
-
this.assign(
|
|
969
|
+
this.assign(name, result);
|
|
971
970
|
} else {
|
|
972
|
-
this.scopes.at(-1)?.set(
|
|
971
|
+
this.scopes.at(-1)?.set(name, result);
|
|
973
972
|
}
|
|
974
973
|
}
|
|
975
974
|
return result;
|
|
@@ -982,7 +981,7 @@ class Execution {
|
|
|
982
981
|
this.write(node.text);
|
|
983
982
|
break;
|
|
984
983
|
case "action": {
|
|
985
|
-
const value = this.pipeline(node.pipe, dot
|
|
984
|
+
const value = this.pipeline(node.pipe, dot);
|
|
986
985
|
if (node.pipe.vars.length === 0) {
|
|
987
986
|
this.write(formatted(value, false));
|
|
988
987
|
}
|
|
@@ -995,7 +994,7 @@ class Execution {
|
|
|
995
994
|
case "with": {
|
|
996
995
|
this.scopes.push(new Map());
|
|
997
996
|
try {
|
|
998
|
-
const value = this.pipeline(node.pipe, dot
|
|
997
|
+
const value = this.pipeline(node.pipe, dot);
|
|
999
998
|
const taken = truthy(value);
|
|
1000
999
|
this.run(taken ? node.list : node.otherwise, taken && node.k === "with" ? value : dot);
|
|
1001
1000
|
} finally {
|
|
@@ -1013,7 +1012,9 @@ class Execution {
|
|
|
1013
1012
|
private range(node: Control, dot: Value): void {
|
|
1014
1013
|
this.scopes.push(new Map());
|
|
1015
1014
|
try {
|
|
1016
|
-
|
|
1015
|
+
// Go's `evalPipeline` gives every variable the range declares or assigns the whole value first:
|
|
1016
|
+
// https://github.com/golang/go/blob/go1.27.2/src/text/template/exec.go#L525
|
|
1017
|
+
const value = this.pipeline(node.pipe, dot);
|
|
1017
1018
|
let entries: [Value, Value][];
|
|
1018
1019
|
if (value === null) {
|
|
1019
1020
|
entries = [];
|
|
@@ -1031,13 +1032,20 @@ class Execution {
|
|
|
1031
1032
|
return;
|
|
1032
1033
|
}
|
|
1033
1034
|
const [first, second] = node.pipe.vars;
|
|
1035
|
+
const scope = this.scopes.at(-1);
|
|
1036
|
+
const bind = (name: string, held: Value): void => {
|
|
1037
|
+
if (node.pipe.assign) {
|
|
1038
|
+
this.assign(name, held);
|
|
1039
|
+
} else {
|
|
1040
|
+
scope?.set(name, held);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1034
1043
|
for (const [key, element] of entries) {
|
|
1035
|
-
const scope = this.scopes.at(-1);
|
|
1036
1044
|
if (second !== undefined && first !== undefined) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1045
|
+
bind(first, key);
|
|
1046
|
+
bind(second, element);
|
|
1039
1047
|
} else if (first !== undefined) {
|
|
1040
|
-
|
|
1048
|
+
bind(first, element);
|
|
1041
1049
|
}
|
|
1042
1050
|
try {
|
|
1043
1051
|
this.run(node.list, element);
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
SignatureKind,
|
|
46
46
|
SymbolFlags,
|
|
47
47
|
TypeFlags,
|
|
48
|
+
type NodeHandle,
|
|
49
|
+
type Signature,
|
|
48
50
|
type Symbol as TSSymbol,
|
|
49
51
|
type Type,
|
|
50
52
|
} from "@typescript/native/sync";
|
|
@@ -64,7 +66,7 @@ import { renderPosition } from "./position.ts";
|
|
|
64
66
|
import { must } from "./query.ts";
|
|
65
67
|
import type { ProjectView } from "./session.ts";
|
|
66
68
|
import { erasedStores, type ErasedStores } from "./erased-stores.ts";
|
|
67
|
-
import { checkedTypes, valueReach, type Reached } from "./value-reach.ts";
|
|
69
|
+
import { checkedTypes, CONVERSION_MEMBERS, valueReach, type Reached } from "./value-reach.ts";
|
|
68
70
|
|
|
69
71
|
/** The serializer every run reads, whatever the configuration names. */
|
|
70
72
|
const JSON_STRINGIFY: DeclarationEntry = { shape: "global", global: "JSON.stringify" };
|
|
@@ -484,14 +486,19 @@ function destinations<Brand>(
|
|
|
484
486
|
own.has(path) || input.consumers.some((root) => path === root || path.startsWith(`${root}/`));
|
|
485
487
|
const implemented = implementedOwners(project, inAnalysis);
|
|
486
488
|
/**
|
|
487
|
-
* Whether every
|
|
488
|
-
* declaration has no body, so the code that runs is
|
|
489
|
-
* member of an interface or an object type a class
|
|
490
|
-
* analysis implements; `settled` false skips that
|
|
489
|
+
* Whether every one of `declarations`, all of the callee's by default, is outside the
|
|
490
|
+
* analysis or ambient. An ambient declaration has no body, so the code that runs is
|
|
491
|
+
* outside the analysis, unless it is a member of an interface or an object type a class
|
|
492
|
+
* or a typed object literal of the analysis implements; `settled` false skips that
|
|
493
|
+
* question, which asks the checker.
|
|
491
494
|
*/
|
|
492
|
-
const outside = (
|
|
493
|
-
target
|
|
494
|
-
|
|
495
|
+
const outside = (
|
|
496
|
+
target: TSSymbol,
|
|
497
|
+
settled: boolean,
|
|
498
|
+
declarations: readonly NodeHandle[] = target.declarations,
|
|
499
|
+
): boolean =>
|
|
500
|
+
declarations.length > 0 &&
|
|
501
|
+
declarations.every((handle) => {
|
|
495
502
|
if (!inAnalysis(handle.path)) {
|
|
496
503
|
return true;
|
|
497
504
|
}
|
|
@@ -505,6 +512,13 @@ function destinations<Brand>(
|
|
|
505
512
|
const owner = must(queries.parentOf(target));
|
|
506
513
|
return owner === undefined || !implemented().has(owner.id);
|
|
507
514
|
});
|
|
515
|
+
/**
|
|
516
|
+
* Whether some one declaration of a callee is outside the analysis or ambient, without
|
|
517
|
+
* asking the checker. A project's declaration merged into a library's symbol leaves the
|
|
518
|
+
* library's beside it, so a call can still resolve to the library's.
|
|
519
|
+
*/
|
|
520
|
+
const mayBeOutside = (target: TSSymbol): boolean =>
|
|
521
|
+
target.declarations.some((handle) => outside(target, false, [handle]));
|
|
508
522
|
const invocation = (call: CallExpression): Invocation => {
|
|
509
523
|
const form = forms.get(call);
|
|
510
524
|
const method = form === undefined ? undefined : callees.get(form.method);
|
|
@@ -513,7 +527,7 @@ function destinations<Brand>(
|
|
|
513
527
|
if (
|
|
514
528
|
form !== undefined &&
|
|
515
529
|
method !== undefined &&
|
|
516
|
-
|
|
530
|
+
mayBeOutside(method) &&
|
|
517
531
|
FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")
|
|
518
532
|
) {
|
|
519
533
|
return form.invocation;
|
|
@@ -524,6 +538,32 @@ function destinations<Brand>(
|
|
|
524
538
|
const name = calleeName(invocation(call).callee);
|
|
525
539
|
return name === undefined ? undefined : callees.get(name);
|
|
526
540
|
};
|
|
541
|
+
const signatures = new Map<CallExpression, Signature | undefined>();
|
|
542
|
+
const resolvedOf = (call: CallExpression): Signature | undefined => {
|
|
543
|
+
if (!signatures.has(call)) {
|
|
544
|
+
signatures.set(call, must(queries.resolvedSignature(call)));
|
|
545
|
+
}
|
|
546
|
+
return signatures.get(call);
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* The declarations of a callee that the declaration of a direct call's resolved
|
|
550
|
+
* signature lies in, or all of them where it lies in none: a declaration merged into
|
|
551
|
+
* the callee's symbol that the call does not resolve to does not decide where it runs.
|
|
552
|
+
*/
|
|
553
|
+
const calledDeclarations = (call: CallExpression, target: TSSymbol): readonly NodeHandle[] => {
|
|
554
|
+
const declaration = invocation(call).indirect ? undefined : resolvedOf(call)?.declaration;
|
|
555
|
+
const signature =
|
|
556
|
+
declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
557
|
+
if (declaration === undefined || signature === undefined) {
|
|
558
|
+
return target.declarations;
|
|
559
|
+
}
|
|
560
|
+
const containing = target.declarations.filter((handle) => {
|
|
561
|
+
const node =
|
|
562
|
+
handle.path === declaration.path ? project.declarationAt(handle)?.node : undefined;
|
|
563
|
+
return node !== undefined && node.pos <= signature.pos && signature.end <= node.end;
|
|
564
|
+
});
|
|
565
|
+
return containing.length === 0 ? target.declarations : containing;
|
|
566
|
+
};
|
|
527
567
|
const erasedArguments = new Map<CallExpression, ReadonlySet<number>>();
|
|
528
568
|
const erasedAt = (call: CallExpression, argument: number): boolean => {
|
|
529
569
|
let indexes = erasedArguments.get(call);
|
|
@@ -536,7 +576,7 @@ function destinations<Brand>(
|
|
|
536
576
|
? type === undefined
|
|
537
577
|
? []
|
|
538
578
|
: must(queries.signaturesOf(type, SignatureKind.Call))
|
|
539
|
-
: [
|
|
579
|
+
: [resolvedOf(call)].filter((one) => one !== undefined);
|
|
540
580
|
indexes = new Set(
|
|
541
581
|
invoked.arguments.flatMap((_unused, index) =>
|
|
542
582
|
signatures.some((signature) => {
|
|
@@ -563,7 +603,7 @@ function destinations<Brand>(
|
|
|
563
603
|
if (entry !== undefined) {
|
|
564
604
|
return { reach: "properties", callee: spelledEntry(entry.entry, held) };
|
|
565
605
|
}
|
|
566
|
-
if (outside(target, true)) {
|
|
606
|
+
if (outside(target, true, calledDeclarations(call, target))) {
|
|
567
607
|
return erasedAt(call, argument)
|
|
568
608
|
? {
|
|
569
609
|
reach: "conversions",
|
|
@@ -602,7 +642,7 @@ function destinations<Brand>(
|
|
|
602
642
|
if (target === undefined) {
|
|
603
643
|
return false;
|
|
604
644
|
}
|
|
605
|
-
if (named.some((one) => names(one, target, chains)) ||
|
|
645
|
+
if (named.some((one) => names(one, target, chains)) || mayBeOutside(target)) {
|
|
606
646
|
return true;
|
|
607
647
|
}
|
|
608
648
|
return chains
|
|
@@ -733,7 +773,7 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
|
|
|
733
773
|
// the value left the analysis.
|
|
734
774
|
const holdsBack = (flags: SymbolFlags, name: string, to: Destination): boolean =>
|
|
735
775
|
(flags & SymbolFlags.Property) !== 0 ||
|
|
736
|
-
((flags &
|
|
776
|
+
((flags & CONVERSION_MEMBERS) !== 0 &&
|
|
737
777
|
(name === SERIALIZED_CONVERSION || (to.reach === "conversions" && CONVERSIONS.has(name))));
|
|
738
778
|
const found: Evidence[] = [];
|
|
739
779
|
for (const flow of flows) {
|
package/src/session.ts
CHANGED
|
@@ -419,6 +419,8 @@ export interface ProjectDiagnostics {
|
|
|
419
419
|
readonly syntactic: readonly Diagnostic[];
|
|
420
420
|
readonly semantic: readonly Diagnostic[];
|
|
421
421
|
readonly configParsing: readonly Diagnostic[];
|
|
422
|
+
/** The program's diagnostics placed in no file, each once, its `fileName` undefined. */
|
|
423
|
+
readonly unplaced: readonly Diagnostic[];
|
|
422
424
|
/** The line and column of one offset into one of the program's files, counted from one. */
|
|
423
425
|
readonly locate: (
|
|
424
426
|
fileName: string,
|
|
@@ -426,12 +428,31 @@ export interface ProjectDiagnostics {
|
|
|
426
428
|
) => { line: number; column: number } | undefined;
|
|
427
429
|
}
|
|
428
430
|
|
|
429
|
-
/**
|
|
431
|
+
/**
|
|
432
|
+
* The program's diagnostics placed in no file. The per-file sets never carry one; the
|
|
433
|
+
* client answers its `fileName` as `null`, and the global set repeats the program set:
|
|
434
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/api/session.go#L3726-L3752
|
|
435
|
+
*/
|
|
436
|
+
function unplacedOf(program: Program): readonly Diagnostic[] {
|
|
437
|
+
const seen = new Set<string>();
|
|
438
|
+
return [...program.getGlobalDiagnostics(), ...program.getProgramDiagnostics()]
|
|
439
|
+
.filter((diagnostic) => diagnostic.fileName == null)
|
|
440
|
+
.filter((diagnostic) => {
|
|
441
|
+
const key = `${String(diagnostic.code)}\u0000${diagnostic.text}`;
|
|
442
|
+
const fresh = !seen.has(key);
|
|
443
|
+
seen.add(key);
|
|
444
|
+
return fresh;
|
|
445
|
+
})
|
|
446
|
+
.map((diagnostic) => ({ ...diagnostic, fileName: undefined }));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** The diagnostic sets one project carries, read before any analysis. */
|
|
430
450
|
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
431
451
|
return {
|
|
432
452
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
433
453
|
semantic: project.program.getSemanticDiagnostics(),
|
|
434
454
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
455
|
+
unplaced: unplacedOf(project.program),
|
|
435
456
|
locate: (fileName, offset) => {
|
|
436
457
|
const file = project.program.getSourceFile(fileName);
|
|
437
458
|
if (file === undefined || offset < 0 || offset > file.text.length) {
|