@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.22
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/src/alias-chain.js +33 -2
- package/dist/src/analysis.js +18 -77
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contract/contract.generated.js +5 -4
- package/dist/src/dependencies.js +1 -1
- package/dist/src/exemptions/argument-places.js +131 -0
- package/dist/src/{calls.js → exemptions/calls.js} +126 -179
- package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
- package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
- package/dist/src/exemptions/detectors.js +48 -0
- package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
- package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
- package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
- package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
- package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
- package/dist/src/exemptions/installed-globals.js +173 -0
- package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
- package/dist/src/exemptions/lifecycle.js +150 -0
- package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
- package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
- package/dist/src/exemptions/sites.js +182 -0
- package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
- package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
- package/dist/src/findings/intra-function.js +50 -37
- package/dist/src/index/sites.js +1 -1
- package/dist/src/index/values.js +124 -0
- package/dist/src/intra-function-parts.js +1149 -222
- package/dist/src/liveness/sweep.js +2 -2
- package/dist/src/query.js +48 -0
- package/dist/src/roots/configured-declarations.js +1 -1
- package/dist/src/verbs/print-retained.js +23 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +38 -2
- package/src/analysis.ts +19 -100
- package/src/bindings.ts +1 -1
- package/src/conformance-record.ts +2 -2
- package/src/contract/contract.generated.ts +5 -4
- package/src/dependencies.ts +1 -1
- package/src/exemptions/argument-places.ts +198 -0
- package/src/{calls.ts → exemptions/calls.ts} +190 -224
- package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
- package/src/exemptions/decorator.ts +56 -0
- package/src/exemptions/detectors.ts +75 -0
- package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
- package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
- package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
- package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
- package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
- package/src/exemptions/installed-globals.ts +255 -0
- package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
- package/src/exemptions/lifecycle.ts +286 -0
- package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
- package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
- package/src/exemptions/sites.ts +293 -0
- package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
- package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
- package/src/facts.ts +1 -1
- package/src/findings/intra-function.ts +59 -37
- package/src/index/sites.ts +1 -1
- package/src/index/values.ts +170 -0
- package/src/intra-function-parts.ts +1435 -247
- package/src/liveness/sweep.ts +2 -2
- package/src/query.ts +109 -10
- package/src/roots/configured-declarations.ts +1 -2
- package/src/stores.ts +1 -1
- package/src/verbs/print-retained.ts +29 -1
- package/dist/src/exempt.js +0 -164
- package/dist/src/value-flow.js +0 -74
- package/src/decorator.ts +0 -78
- package/src/exempt.ts +0 -295
- package/src/value-flow.ts +0 -104
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The calls and decorators of one project that the configured classes read: what each
|
|
3
|
-
*
|
|
4
|
-
* is attached to. A class is passed by each
|
|
5
|
-
*
|
|
6
|
-
* decorator written as a call is a call.
|
|
3
|
+
* call invokes, a call through a method of `Function.prototype` included, and which of the
|
|
4
|
+
* target's classes a call passes or a decorator is attached to. A class is passed by each
|
|
5
|
+
* value an argument can carry ({@link carriedValues}), named directly or through
|
|
6
|
+
* variables, and a decorator written as a call is a call.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
isArrayLiteralExpression,
|
|
11
11
|
isArrowFunction,
|
|
12
|
-
isBinaryExpression,
|
|
13
12
|
isBindingElement,
|
|
14
13
|
isCallExpression,
|
|
15
|
-
isClassDeclaration,
|
|
16
|
-
isConditionalExpression,
|
|
17
14
|
isDecorator,
|
|
18
15
|
isFunctionDeclaration,
|
|
19
16
|
isFunctionExpression,
|
|
20
17
|
isIdentifier,
|
|
21
18
|
isMethodDeclaration,
|
|
22
|
-
isObjectLiteralExpression,
|
|
23
19
|
isParameterDeclaration,
|
|
24
|
-
isParenthesizedExpression,
|
|
25
20
|
isPropertyAccessExpression,
|
|
26
21
|
isPropertyAssignment,
|
|
27
22
|
isPropertyDeclaration,
|
|
28
23
|
isShorthandPropertyAssignment,
|
|
29
|
-
isOmittedExpression,
|
|
30
|
-
isSpreadAssignment,
|
|
31
24
|
isSpreadElement,
|
|
32
25
|
isVariableDeclaration,
|
|
33
26
|
type CallExpression,
|
|
@@ -35,7 +28,6 @@ import {
|
|
|
35
28
|
type Expression,
|
|
36
29
|
type Node,
|
|
37
30
|
type ParameterDeclaration,
|
|
38
|
-
SyntaxKind,
|
|
39
31
|
} from "@typescript/native/ast";
|
|
40
32
|
import {
|
|
41
33
|
ObjectFlags,
|
|
@@ -43,23 +35,13 @@ import {
|
|
|
43
35
|
type Symbol as TSSymbol,
|
|
44
36
|
type Type,
|
|
45
37
|
} from "@typescript/native/sync";
|
|
46
|
-
import type
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
interface PassedValue {
|
|
54
|
-
/** Where the value is written, which is where a record of it sits. */
|
|
55
|
-
readonly site: Node;
|
|
56
|
-
/** The name the value is resolved through, the shorthand assignment that holds it, or none. */
|
|
57
|
-
readonly name: Node | undefined;
|
|
58
|
-
readonly shorthand: boolean;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** A passed value that names its declaration. */
|
|
62
|
-
type NamedValue = PassedValue & { readonly name: Node };
|
|
38
|
+
import { resolvedTargets, type AliasChains } from "../alias-chain.ts";
|
|
39
|
+
import { carriedValues, nameOf, type CarriedValue } from "../index/values.ts";
|
|
40
|
+
import type { Inventory } from "../inventory.ts";
|
|
41
|
+
import { must } from "../query.ts";
|
|
42
|
+
import type { ProjectView } from "../session.ts";
|
|
43
|
+
import { parenthesesRemoved, unwrapped } from "../unwrap.ts";
|
|
44
|
+
import type { ProjectSites } from "./sites.ts";
|
|
63
45
|
|
|
64
46
|
/** One class of the target a call passes or a decorator is attached to, and where. */
|
|
65
47
|
interface ClassUse {
|
|
@@ -68,21 +50,10 @@ interface ClassUse {
|
|
|
68
50
|
readonly site: Node;
|
|
69
51
|
}
|
|
70
52
|
|
|
71
|
-
/** The name node a callee or a decorator expression resolves through, where it has one. */
|
|
72
|
-
export function calleeName(expression: Expression): Node | undefined {
|
|
73
|
-
if (isParenthesizedExpression(expression)) {
|
|
74
|
-
return calleeName(expression.expression);
|
|
75
|
-
}
|
|
76
|
-
if (isPropertyAccessExpression(expression)) {
|
|
77
|
-
return expression.name;
|
|
78
|
-
}
|
|
79
|
-
return isIdentifier(expression) ? expression : undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
53
|
/** The callee one decorator names: its expression, or the callee of the call it writes. */
|
|
83
54
|
export function decoratorCallee(decorator: Decorator): Node | undefined {
|
|
84
55
|
const expression = decorator.expression;
|
|
85
|
-
return
|
|
56
|
+
return nameOf(isCallExpression(expression) ? expression.expression : expression);
|
|
86
57
|
}
|
|
87
58
|
|
|
88
59
|
/** How one decorator is spelled in a record: its expression, without a call's arguments. */
|
|
@@ -97,127 +68,146 @@ export function decoratorsOf(node: Node): readonly Decorator[] {
|
|
|
97
68
|
return ((node as { readonly modifiers?: readonly Node[] }).modifiers ?? []).filter(isDecorator);
|
|
98
69
|
}
|
|
99
70
|
|
|
71
|
+
/** The methods of `Function.prototype` that invoke a function with arguments of their own. */
|
|
72
|
+
const FUNCTION_METHODS: ReadonlySet<string> = new Set(["call", "apply", "bind"]);
|
|
73
|
+
|
|
74
|
+
/** The interfaces of the standard library that declare those methods for a function value. */
|
|
75
|
+
const FUNCTION_INTERFACES: ReadonlySet<string> = new Set([
|
|
76
|
+
"Function",
|
|
77
|
+
"CallableFunction",
|
|
78
|
+
"NewableFunction",
|
|
79
|
+
]);
|
|
80
|
+
|
|
100
81
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* to nothing, are absent from the answer. Each alias is followed once. A question the
|
|
104
|
-
* checker does not answer stops the pass, as {@link must} stops.
|
|
82
|
+
* One call as the function it invokes sees it: the expression naming that function,
|
|
83
|
+
* and the argument each of its parameters binds, in order.
|
|
105
84
|
*/
|
|
106
|
-
export
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.symbolsAt(nodes.map((node) => project.handle(node)))
|
|
114
|
-
.map(must)
|
|
115
|
-
.forEach((symbol, index) => {
|
|
116
|
-
const node = nodes[index];
|
|
117
|
-
if (symbol === undefined || node === undefined) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
let target: TSSymbol | undefined = symbol;
|
|
121
|
-
if ((symbol.flags & SymbolFlags.Alias) !== 0) {
|
|
122
|
-
if (!followed.has(symbol.id)) {
|
|
123
|
-
const aliased = must(project.queries.aliased(symbol));
|
|
124
|
-
followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
|
|
125
|
-
}
|
|
126
|
-
target = followed.get(symbol.id);
|
|
127
|
-
}
|
|
128
|
-
if (target !== undefined) {
|
|
129
|
-
targets.set(node, target);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
return targets;
|
|
85
|
+
export interface Invocation {
|
|
86
|
+
readonly callee: Expression;
|
|
87
|
+
readonly arguments: readonly Expression[];
|
|
88
|
+
/** Whether the call reaches the callee through a method of `Function.prototype`. */
|
|
89
|
+
readonly indirect: boolean;
|
|
90
|
+
/** Whether the last argument is a list the parameters bind the elements of, as `apply` takes. */
|
|
91
|
+
readonly listed: boolean;
|
|
133
92
|
}
|
|
134
93
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return value;
|
|
140
|
-
}
|
|
141
|
-
const aliased = must(project.queries.aliased(value));
|
|
142
|
-
return aliased.declarations.length === 0 ? undefined : aliased;
|
|
94
|
+
/** A call through a method of `Function.prototype`: the method's name and the invocation. */
|
|
95
|
+
interface MethodForm {
|
|
96
|
+
readonly method: Node;
|
|
97
|
+
readonly invocation: Invocation;
|
|
143
98
|
}
|
|
144
99
|
|
|
145
100
|
/**
|
|
146
|
-
* The
|
|
147
|
-
*
|
|
101
|
+
* The invocation a call writes through a method of `Function.prototype`, read from
|
|
102
|
+
* the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
|
|
103
|
+
* passes `a` though nothing calls the bound function there, because the bound function
|
|
104
|
+
* carries `a` to wherever it is called and that call is not followed, and
|
|
105
|
+
* `f.bind(self, a)(b)`, which passes `b` after it. An `apply` argument
|
|
106
|
+
* that is no array literal is the value of the first parameter, whose elements it
|
|
107
|
+
* carries. Whether the method is the prototype's is resolution's to say.
|
|
148
108
|
*/
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} else if (isSpreadAssignment(property)) {
|
|
160
|
-
passedIn(property.expression, found);
|
|
161
|
-
} else if (isShorthandPropertyAssignment(property)) {
|
|
162
|
-
found.push({ site: property.name, name: property, shorthand: true });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} else if (!isOmittedExpression(expression)) {
|
|
166
|
-
found.push({ site: expression, name: calleeName(expression), shorthand: false });
|
|
109
|
+
function throughMethod(call: CallExpression): MethodForm | undefined {
|
|
110
|
+
const expression = parenthesesRemoved(call.expression);
|
|
111
|
+
if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
|
|
112
|
+
const [, ...rest] = call.arguments;
|
|
113
|
+
let passed: readonly Expression[] = rest;
|
|
114
|
+
let listed = false;
|
|
115
|
+
if (expression.name.text === "apply") {
|
|
116
|
+
const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
|
|
117
|
+
listed = list !== undefined && !isArrayLiteralExpression(list);
|
|
118
|
+
passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
|
|
167
119
|
}
|
|
120
|
+
return {
|
|
121
|
+
method: expression.name,
|
|
122
|
+
invocation: { callee: expression.expression, arguments: passed, indirect: true, listed },
|
|
123
|
+
};
|
|
168
124
|
}
|
|
125
|
+
if (isCallExpression(expression)) {
|
|
126
|
+
const bound = parenthesesRemoved(expression.expression);
|
|
127
|
+
if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
|
|
128
|
+
return {
|
|
129
|
+
method: bound.name,
|
|
130
|
+
invocation: {
|
|
131
|
+
callee: bound.expression,
|
|
132
|
+
arguments: [...expression.arguments.slice(1), ...call.arguments],
|
|
133
|
+
indirect: true,
|
|
134
|
+
listed: false,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
169
140
|
}
|
|
170
141
|
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
142
|
+
/** What each call of one project invokes. */
|
|
143
|
+
export interface Invocations {
|
|
144
|
+
/** The function one call invokes and the arguments its parameters bind. */
|
|
145
|
+
readonly of: (call: CallExpression) => Invocation;
|
|
146
|
+
/** The declaration the invoked function's name resolves to, every alias followed. */
|
|
147
|
+
readonly callee: (call: CallExpression) => TSSymbol | undefined;
|
|
148
|
+
}
|
|
177
149
|
|
|
178
150
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* whole drops a class structurally equal to, or a subtype of, another branch's.
|
|
151
|
+
* What each of a project's calls invokes. A call through `call`, `apply` or `bind`
|
|
152
|
+
* invokes the function the method is read from only where the method is the standard
|
|
153
|
+
* library's: the program's own `call` and `Reflect.apply` pass nothing on.
|
|
183
154
|
*/
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
if (operator === SyntaxKind.CommaToken) {
|
|
195
|
-
return leavesOf(expression.right);
|
|
155
|
+
export function invocations<Brand>(
|
|
156
|
+
project: ProjectView<Brand>,
|
|
157
|
+
calls: readonly CallExpression[],
|
|
158
|
+
): Invocations {
|
|
159
|
+
const forms = new Map<CallExpression, MethodForm>();
|
|
160
|
+
for (const call of calls) {
|
|
161
|
+
const form = throughMethod(call);
|
|
162
|
+
if (form !== undefined) {
|
|
163
|
+
forms.set(call, form);
|
|
196
164
|
}
|
|
197
165
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
166
|
+
const callees = resolvedTargets(project, [
|
|
167
|
+
...calls.flatMap((call) => nameOf(call.expression) ?? []),
|
|
168
|
+
...[...forms.values()].flatMap((form) => {
|
|
169
|
+
const name = nameOf(form.invocation.callee);
|
|
170
|
+
return name === undefined ? [form.method] : [form.method, name];
|
|
171
|
+
}),
|
|
172
|
+
]);
|
|
173
|
+
const standard = (method: TSSymbol): boolean =>
|
|
174
|
+
method.declarations.some(
|
|
175
|
+
(handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true,
|
|
176
|
+
) && FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "");
|
|
177
|
+
const of = (call: CallExpression): Invocation => {
|
|
178
|
+
const form = forms.get(call);
|
|
179
|
+
const method = form === undefined ? undefined : callees.get(form.method);
|
|
180
|
+
return form !== undefined && method !== undefined && standard(method)
|
|
181
|
+
? form.invocation
|
|
182
|
+
: { callee: call.expression, arguments: call.arguments, indirect: false, listed: false };
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
of,
|
|
186
|
+
callee: (call) => {
|
|
187
|
+
const name = nameOf(of(call).callee);
|
|
188
|
+
return name === undefined ? undefined : callees.get(name);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
206
191
|
}
|
|
207
192
|
|
|
208
|
-
/** The
|
|
209
|
-
|
|
210
|
-
|
|
193
|
+
/** The declaration a shorthand property's value resolves to, an import followed to what it names. */
|
|
194
|
+
function shorthandTarget<Brand>(project: ProjectView<Brand>, node: Node): TSSymbol | undefined {
|
|
195
|
+
const value = must(project.shorthandValueAt(project.handle(node)));
|
|
196
|
+
if (value === undefined || (value.flags & SymbolFlags.Alias) === 0) {
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
const aliased = must(project.queries.aliased(value));
|
|
200
|
+
return aliased.declarations.length === 0 ? undefined : aliased;
|
|
211
201
|
}
|
|
212
202
|
|
|
213
203
|
/**
|
|
214
|
-
* What one
|
|
204
|
+
* What one carried value holds: the declaration its name resolves to, and the classes and
|
|
215
205
|
* functions the checker's type at it holds ({@link constructedAt}). `known` holds names
|
|
216
206
|
* already resolved in a batch.
|
|
217
207
|
*/
|
|
218
208
|
function heldBy<Brand>(
|
|
219
209
|
project: ProjectView<Brand>,
|
|
220
|
-
value:
|
|
210
|
+
value: CarriedValue,
|
|
221
211
|
known?: ReadonlyMap<Node, TSSymbol>,
|
|
222
212
|
): { readonly named: TSSymbol | undefined; readonly typed: readonly TSSymbol[] } {
|
|
223
213
|
const { name } = value;
|
|
@@ -233,34 +223,58 @@ function heldBy<Brand>(
|
|
|
233
223
|
};
|
|
234
224
|
}
|
|
235
225
|
|
|
236
|
-
/**
|
|
237
|
-
* The classes of the target each call passes, by the call: each class a value an argument
|
|
238
|
-
* can be holds as a constructor, as the checker types it. A name whose type erases the
|
|
239
|
-
* class is followed through what its declaration binds ({@link boundTo}) and, for a
|
|
240
|
-
* parameter, through what each call of its function passes at its place, so a class handed
|
|
241
|
-
* to a wrapper is passed. A class is used at each value that passes it. A class of another
|
|
242
|
-
* program passes nothing.
|
|
243
|
-
*/
|
|
226
|
+
/** The classes of the target each call passes, by the call, as {@link classesCarried} reads its invocation's arguments. */
|
|
244
227
|
export function classesPassed<Brand>(
|
|
245
228
|
project: ProjectView<Brand>,
|
|
246
229
|
held: Inventory,
|
|
247
230
|
chains: AliasChains,
|
|
231
|
+
sites: Pick<ProjectSites, "calls" | "invocations">,
|
|
248
232
|
calls: readonly CallExpression[],
|
|
249
233
|
): ReadonlyMap<CallExpression, readonly ClassUse[]> {
|
|
250
|
-
const
|
|
234
|
+
const carried = classesCarried(
|
|
235
|
+
project,
|
|
236
|
+
held,
|
|
237
|
+
chains,
|
|
238
|
+
sites,
|
|
239
|
+
calls.map((call) => sites.invocations.of(call).arguments),
|
|
240
|
+
);
|
|
241
|
+
return new Map(calls.map((call, at) => [call, carried[at] ?? []]));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The classes of the target each group of written values carries: each class a value
|
|
246
|
+
* can carry holds as a constructor, as the checker types it. A name whose type erases
|
|
247
|
+
* the class is followed through what its declaration binds ({@link boundTo}) and, for a
|
|
248
|
+
* parameter, through what each call of its function passes at its place, so a class
|
|
249
|
+
* handed to a wrapper is passed. A class is used at each value that carries it. A class
|
|
250
|
+
* of another program is carried by nothing.
|
|
251
|
+
*/
|
|
252
|
+
export function classesCarried<Brand>(
|
|
253
|
+
project: ProjectView<Brand>,
|
|
254
|
+
held: Inventory,
|
|
255
|
+
chains: AliasChains,
|
|
256
|
+
sites: Pick<ProjectSites, "calls" | "invocations">,
|
|
257
|
+
groups: readonly (readonly Expression[])[],
|
|
258
|
+
): readonly (readonly ClassUse[])[] {
|
|
259
|
+
const { invocations: invoked } = sites;
|
|
260
|
+
const passed = groups.map((written) => written.flatMap(carriedValues));
|
|
251
261
|
const targets = resolvedTargets(
|
|
252
262
|
project,
|
|
253
|
-
|
|
263
|
+
passed.flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))),
|
|
254
264
|
);
|
|
255
265
|
const classes = new Set(
|
|
256
266
|
held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id),
|
|
257
267
|
);
|
|
258
268
|
let calling: CallsOf | undefined;
|
|
259
269
|
const callsOfFunction = (symbol: TSSymbol): readonly CallExpression[] => {
|
|
260
|
-
calling ??= callsByDeclaration(
|
|
270
|
+
calling ??= callsByDeclaration(
|
|
271
|
+
project,
|
|
272
|
+
sites.calls.map((one) => one.call),
|
|
273
|
+
invoked,
|
|
274
|
+
);
|
|
261
275
|
return symbol.declarations.flatMap((handle) => calling?.get(keyOf(handle)) ?? []);
|
|
262
276
|
};
|
|
263
|
-
const classesOf = (value:
|
|
277
|
+
const classesOf = (value: CarriedValue, seen: Set<number>): readonly string[] => {
|
|
264
278
|
const { named, typed } = heldBy(project, value, targets);
|
|
265
279
|
return [
|
|
266
280
|
...typed.flatMap((symbol) => chains.declarationsOf(symbol).filter((id) => classes.has(id))),
|
|
@@ -274,14 +288,14 @@ export function classesPassed<Brand>(
|
|
|
274
288
|
}
|
|
275
289
|
seen.add(symbol.id);
|
|
276
290
|
const through = (written: Expression): readonly string[] =>
|
|
277
|
-
|
|
291
|
+
carriedValues(written).flatMap((value) => classesOf(value, seen));
|
|
278
292
|
return symbol.declarations.flatMap((handle) => {
|
|
279
293
|
const node = project.declarationAt(handle)?.node;
|
|
280
294
|
if (node === undefined) {
|
|
281
295
|
return [];
|
|
282
296
|
}
|
|
283
297
|
if (isParameterDeclaration(node)) {
|
|
284
|
-
return argumentsAt(project, node, callsOfFunction).flatMap(through);
|
|
298
|
+
return argumentsAt(project, node, callsOfFunction, invoked).flatMap(through);
|
|
285
299
|
}
|
|
286
300
|
const bound = boundTo(project, node);
|
|
287
301
|
return [
|
|
@@ -290,17 +304,15 @@ export function classesPassed<Brand>(
|
|
|
290
304
|
];
|
|
291
305
|
});
|
|
292
306
|
};
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
for (const
|
|
297
|
-
|
|
298
|
-
uses.set(`${id}\u0000${String(value.site.pos)}`, { id, site: value.site });
|
|
299
|
-
}
|
|
307
|
+
return passed.map((values) => {
|
|
308
|
+
const uses = new Map<string, ClassUse>();
|
|
309
|
+
for (const value of values) {
|
|
310
|
+
for (const id of classesOf(value, new Set())) {
|
|
311
|
+
uses.set(`${id}\u0000${String(value.site.pos)}`, { id, site: value.site });
|
|
300
312
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
);
|
|
313
|
+
}
|
|
314
|
+
return [...uses.values()];
|
|
315
|
+
});
|
|
304
316
|
}
|
|
305
317
|
|
|
306
318
|
/**
|
|
@@ -371,19 +383,18 @@ function keyOf(handle: { readonly path: string; readonly index: number }): strin
|
|
|
371
383
|
}
|
|
372
384
|
|
|
373
385
|
/**
|
|
374
|
-
* Every call of the project by the declarations its
|
|
375
|
-
* method it names, or else each function the checker's type at it holds, wherever
|
|
376
|
-
* value came from. A name is also followed through what its binding copies
|
|
386
|
+
* Every call of the project by the declarations its invoked function can be: the function
|
|
387
|
+
* or the method it names, or else each function the checker's type at it holds, wherever
|
|
388
|
+
* that value came from. A name is also followed through what its binding copies
|
|
377
389
|
* ({@link boundTo}), which is how a call reaches a function an annotation erases.
|
|
378
390
|
* Only the target's own declarations are keys, as only its own functions are asked for.
|
|
379
391
|
*/
|
|
380
|
-
function callsByDeclaration<Brand>(
|
|
381
|
-
|
|
392
|
+
function callsByDeclaration<Brand>(
|
|
393
|
+
project: ProjectView<Brand>,
|
|
394
|
+
calls: readonly CallExpression[],
|
|
395
|
+
invoked: Invocations,
|
|
396
|
+
): CallsOf {
|
|
382
397
|
const own = project.ownPaths();
|
|
383
|
-
const callees = resolvedTargets(
|
|
384
|
-
project,
|
|
385
|
-
calls.flatMap((call) => calleeName(call.expression) ?? []),
|
|
386
|
-
);
|
|
387
398
|
const named = new Map<number, readonly string[]>();
|
|
388
399
|
const keysOf = (symbol: TSSymbol, seen: Set<number>): readonly string[] => {
|
|
389
400
|
const known = named.get(symbol.id);
|
|
@@ -398,7 +409,7 @@ function callsByDeclaration<Brand>(project: ProjectView<Brand>): CallsOf {
|
|
|
398
409
|
const bound = node === undefined ? { symbols: [] } : boundTo(project, node);
|
|
399
410
|
const copied = [
|
|
400
411
|
...bound.symbols,
|
|
401
|
-
...(bound.written === undefined ? [] :
|
|
412
|
+
...(bound.written === undefined ? [] : carriedValues(bound.written)).flatMap((value) => {
|
|
402
413
|
const { named: next, typed } = heldBy(project, value);
|
|
403
414
|
return [...typed, ...(next === undefined ? [] : [next])];
|
|
404
415
|
}),
|
|
@@ -408,21 +419,17 @@ function callsByDeclaration<Brand>(project: ProjectView<Brand>): CallsOf {
|
|
|
408
419
|
named.set(symbol.id, keys);
|
|
409
420
|
return keys;
|
|
410
421
|
};
|
|
411
|
-
const calleeOf = (call: CallExpression): TSSymbol | undefined => {
|
|
412
|
-
const name = calleeName(call.expression);
|
|
413
|
-
return name === undefined ? undefined : callees.get(name);
|
|
414
|
-
};
|
|
415
422
|
const declared = (call: CallExpression): boolean =>
|
|
416
|
-
((
|
|
423
|
+
((invoked.callee(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
|
|
417
424
|
const typed = calls.filter((call) => !declared(call));
|
|
418
425
|
const types = new Map(
|
|
419
426
|
project.queries
|
|
420
|
-
.typesAt(typed.map((call) => unwrapped(call.
|
|
427
|
+
.typesAt(typed.map((call) => unwrapped(invoked.of(call).callee)))
|
|
421
428
|
.map((type, index) => [typed[index], must(type)]),
|
|
422
429
|
);
|
|
423
430
|
const found = new Map<string, CallExpression[]>();
|
|
424
431
|
for (const call of calls) {
|
|
425
|
-
const callee =
|
|
432
|
+
const callee = invoked.callee(call);
|
|
426
433
|
const held = [
|
|
427
434
|
...(callee === undefined ? [] : [callee]),
|
|
428
435
|
...(types.has(call) ? heldIn(project, types.get(call)) : []),
|
|
@@ -436,15 +443,17 @@ function callsByDeclaration<Brand>(project: ProjectView<Brand>): CallsOf {
|
|
|
436
443
|
|
|
437
444
|
/**
|
|
438
445
|
* What each call of the function a parameter belongs to passes at the parameter's place,
|
|
439
|
-
* every argument from there on for a rest parameter
|
|
440
|
-
* type at its own declaration names, so a function
|
|
441
|
-
* A `this` parameter takes no place, and an
|
|
442
|
-
* at every place the spread's length can
|
|
446
|
+
* every argument from there on for a rest parameter, as the call's invocation binds them.
|
|
447
|
+
* The function is the one the checker's type at its own declaration names, so a function
|
|
448
|
+
* with no name of its own is called too. A `this` parameter takes no place, and an
|
|
449
|
+
* argument at or after a spread argument passes at every place the spread's length can
|
|
450
|
+
* move it to.
|
|
443
451
|
*/
|
|
444
452
|
function argumentsAt<Brand>(
|
|
445
453
|
project: ProjectView<Brand>,
|
|
446
454
|
parameter: ParameterDeclaration,
|
|
447
455
|
callsOfFunction: (symbol: TSSymbol) => readonly CallExpression[],
|
|
456
|
+
invoked: Invocations,
|
|
448
457
|
): readonly Expression[] {
|
|
449
458
|
const owner = parameter.parent;
|
|
450
459
|
if (
|
|
@@ -462,7 +471,7 @@ function argumentsAt<Brand>(
|
|
|
462
471
|
const rest = parameter.dotDotDotToken !== undefined;
|
|
463
472
|
return [...calls].flatMap((call) => {
|
|
464
473
|
let spreads = 0;
|
|
465
|
-
return call.arguments.flatMap((argument, index) => {
|
|
474
|
+
return invoked.of(call).arguments.flatMap((argument, index) => {
|
|
466
475
|
const spread = isSpreadElement(argument);
|
|
467
476
|
const earliest = index - spreads;
|
|
468
477
|
spreads += spread ? 1 : 0;
|
|
@@ -472,46 +481,3 @@ function argumentsAt<Brand>(
|
|
|
472
481
|
});
|
|
473
482
|
});
|
|
474
483
|
}
|
|
475
|
-
|
|
476
|
-
/** One class declaration of the target and the decorators written on it. */
|
|
477
|
-
interface DecoratedClass {
|
|
478
|
-
readonly id: string;
|
|
479
|
-
readonly decorators: readonly Decorator[];
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/** Every class declaration of the target that carries a decorator. */
|
|
483
|
-
export function decoratedClasses<Brand>(
|
|
484
|
-
project: ProjectView<Brand>,
|
|
485
|
-
held: Inventory,
|
|
486
|
-
): readonly DecoratedClass[] {
|
|
487
|
-
const found: DecoratedClass[] = [];
|
|
488
|
-
for (const file of project.ownSourceFiles()) {
|
|
489
|
-
const visit = (node: Node): void => {
|
|
490
|
-
if (isClassDeclaration(node)) {
|
|
491
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
492
|
-
const decorators = decoratorsOf(node);
|
|
493
|
-
if (id !== undefined && decorators.length > 0) {
|
|
494
|
-
found.push({ id, decorators });
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
node.forEachChild(visit);
|
|
498
|
-
};
|
|
499
|
-
file.forEachChild(visit);
|
|
500
|
-
}
|
|
501
|
-
return found;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/** Every call expression of the project's own files, decorators written as calls included. */
|
|
505
|
-
export function callsOf<Brand>(project: ProjectView<Brand>): readonly CallExpression[] {
|
|
506
|
-
const found: CallExpression[] = [];
|
|
507
|
-
for (const file of project.ownSourceFiles()) {
|
|
508
|
-
const visit = (node: Node): void => {
|
|
509
|
-
if (isCallExpression(node)) {
|
|
510
|
-
found.push(node);
|
|
511
|
-
}
|
|
512
|
-
node.forEachChild(visit);
|
|
513
|
-
};
|
|
514
|
-
file.forEachChild(visit);
|
|
515
|
-
}
|
|
516
|
-
return found;
|
|
517
|
-
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decorator class: a member a decorator expression is attached to, and every member
|
|
3
|
+
* of a class one is attached to but the member a parameter property declares, is held
|
|
4
|
+
* back, because the decorator may reach the member by its name. The rule is read from
|
|
5
|
+
* the tree alone. The decorator's own use of what it decorates is the reference pass's
|
|
6
|
+
* `decorator` reference; this class records only that the declaration is held back,
|
|
7
|
+
* static members of a decorated class included.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { decoratorsOf, spelledDecorator } from "./calls.ts";
|
|
11
|
+
import type { DetectorInput, Evidence } from "./lifecycle.ts";
|
|
12
|
+
import { isParameterProperty, type InventorySymbol, type SymbolKind } from "../inventory.ts";
|
|
13
|
+
import { renderPosition } from "../position.ts";
|
|
14
|
+
|
|
15
|
+
/** The kinds of declaration a decorator is attached to and holds back. */
|
|
16
|
+
const DECORATED: ReadonlySet<SymbolKind> = new Set(["class", "method", "class-member"]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The decorator detector: each decorated member, and each member of a decorated
|
|
20
|
+
* class, recorded at the decorator.
|
|
21
|
+
*/
|
|
22
|
+
export function decorator<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
23
|
+
const { held, sites, targetRoot } = input;
|
|
24
|
+
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
25
|
+
const membersOf = new Map<string, InventorySymbol[]>();
|
|
26
|
+
for (const symbol of held.symbols) {
|
|
27
|
+
if (symbol.kind === "method" || symbol.kind === "class-member") {
|
|
28
|
+
const members = membersOf.get(symbol.parent);
|
|
29
|
+
if (members === undefined) {
|
|
30
|
+
membersOf.set(symbol.parent, [symbol]);
|
|
31
|
+
} else {
|
|
32
|
+
members.push(symbol);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// A decorated class's container passes each constructor argument, which keeps the
|
|
37
|
+
// parameter, not the member a parameter property stores it in. A parameter property's
|
|
38
|
+
// decorator is attached to its parameter, and to no member.
|
|
39
|
+
return sites.decorated.flatMap(({ id, node }) => {
|
|
40
|
+
const symbol = byId.get(id);
|
|
41
|
+
if (symbol === undefined || !DECORATED.has(symbol.kind) || isParameterProperty(node)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const members =
|
|
45
|
+
symbol.kind === "class"
|
|
46
|
+
? (membersOf.get(symbol.id) ?? []).filter(
|
|
47
|
+
(member) => !sites.parameterProperties.has(member.id),
|
|
48
|
+
)
|
|
49
|
+
: [symbol];
|
|
50
|
+
return decoratorsOf(node).flatMap((attached) => {
|
|
51
|
+
const detail = `decorated by ${spelledDecorator(attached)}`;
|
|
52
|
+
const site = renderPosition(node.getSourceFile(), targetRoot, attached.getStart());
|
|
53
|
+
return members.map((member) => ({ id: member.id, detail, site }));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|