@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,29 +1,20 @@
|
|
|
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
|
-
import { isArrayLiteralExpression, isArrowFunction,
|
|
8
|
+
import { isArrayLiteralExpression, isArrowFunction, isBindingElement, isCallExpression, isDecorator, isFunctionDeclaration, isFunctionExpression, isIdentifier, isMethodDeclaration, isParameterDeclaration, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isShorthandPropertyAssignment, isSpreadElement, isVariableDeclaration, } from "@typescript/native/ast";
|
|
9
9
|
import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
export function calleeName(expression) {
|
|
15
|
-
if (isParenthesizedExpression(expression)) {
|
|
16
|
-
return calleeName(expression.expression);
|
|
17
|
-
}
|
|
18
|
-
if (isPropertyAccessExpression(expression)) {
|
|
19
|
-
return expression.name;
|
|
20
|
-
}
|
|
21
|
-
return isIdentifier(expression) ? expression : undefined;
|
|
22
|
-
}
|
|
10
|
+
import { resolvedTargets } from "../alias-chain.js";
|
|
11
|
+
import { carriedValues, nameOf } from "../index/values.js";
|
|
12
|
+
import { must } from "../query.js";
|
|
13
|
+
import { parenthesesRemoved, unwrapped } from "../unwrap.js";
|
|
23
14
|
/** The callee one decorator names: its expression, or the callee of the call it writes. */
|
|
24
15
|
export function decoratorCallee(decorator) {
|
|
25
16
|
const expression = decorator.expression;
|
|
26
|
-
return
|
|
17
|
+
return nameOf(isCallExpression(expression) ? expression.expression : expression);
|
|
27
18
|
}
|
|
28
19
|
/** How one decorator is spelled in a record: its expression, without a call's arguments. */
|
|
29
20
|
export function spelledDecorator(decorator) {
|
|
@@ -35,36 +26,90 @@ export function spelledDecorator(decorator) {
|
|
|
35
26
|
export function decoratorsOf(node) {
|
|
36
27
|
return (node.modifiers ?? []).filter(isDecorator);
|
|
37
28
|
}
|
|
29
|
+
/** The methods of `Function.prototype` that invoke a function with arguments of their own. */
|
|
30
|
+
const FUNCTION_METHODS = new Set(["call", "apply", "bind"]);
|
|
31
|
+
/** The interfaces of the standard library that declare those methods for a function value. */
|
|
32
|
+
const FUNCTION_INTERFACES = new Set([
|
|
33
|
+
"Function",
|
|
34
|
+
"CallableFunction",
|
|
35
|
+
"NewableFunction",
|
|
36
|
+
]);
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
38
|
+
* The invocation a call writes through a method of `Function.prototype`, read from
|
|
39
|
+
* the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
|
|
40
|
+
* passes `a` though nothing calls the bound function there, because the bound function
|
|
41
|
+
* carries `a` to wherever it is called and that call is not followed, and
|
|
42
|
+
* `f.bind(self, a)(b)`, which passes `b` after it. An `apply` argument
|
|
43
|
+
* that is no array literal is the value of the first parameter, whose elements it
|
|
44
|
+
* carries. Whether the method is the prototype's is resolution's to say.
|
|
43
45
|
*/
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
function throughMethod(call) {
|
|
47
|
+
const expression = parenthesesRemoved(call.expression);
|
|
48
|
+
if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
|
|
49
|
+
const [, ...rest] = call.arguments;
|
|
50
|
+
let passed = rest;
|
|
51
|
+
let listed = false;
|
|
52
|
+
if (expression.name.text === "apply") {
|
|
53
|
+
const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
|
|
54
|
+
listed = list !== undefined && !isArrayLiteralExpression(list);
|
|
55
|
+
passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
return {
|
|
58
|
+
method: expression.name,
|
|
59
|
+
invocation: { callee: expression.expression, arguments: passed, indirect: true, listed },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (isCallExpression(expression)) {
|
|
63
|
+
const bound = parenthesesRemoved(expression.expression);
|
|
64
|
+
if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
|
|
65
|
+
return {
|
|
66
|
+
method: bound.name,
|
|
67
|
+
invocation: {
|
|
68
|
+
callee: bound.expression,
|
|
69
|
+
arguments: [...expression.arguments.slice(1), ...call.arguments],
|
|
70
|
+
indirect: true,
|
|
71
|
+
listed: false,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
62
74
|
}
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* What each of a project's calls invokes. A call through `call`, `apply` or `bind`
|
|
80
|
+
* invokes the function the method is read from only where the method is the standard
|
|
81
|
+
* library's: the program's own `call` and `Reflect.apply` pass nothing on.
|
|
82
|
+
*/
|
|
83
|
+
export function invocations(project, calls) {
|
|
84
|
+
const forms = new Map();
|
|
85
|
+
for (const call of calls) {
|
|
86
|
+
const form = throughMethod(call);
|
|
87
|
+
if (form !== undefined) {
|
|
88
|
+
forms.set(call, form);
|
|
65
89
|
}
|
|
66
|
-
}
|
|
67
|
-
|
|
90
|
+
}
|
|
91
|
+
const callees = resolvedTargets(project, [
|
|
92
|
+
...calls.flatMap((call) => nameOf(call.expression) ?? []),
|
|
93
|
+
...[...forms.values()].flatMap((form) => {
|
|
94
|
+
const name = nameOf(form.invocation.callee);
|
|
95
|
+
return name === undefined ? [form.method] : [form.method, name];
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
const standard = (method) => method.declarations.some((handle) => project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true) && FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "");
|
|
99
|
+
const of = (call) => {
|
|
100
|
+
const form = forms.get(call);
|
|
101
|
+
const method = form === undefined ? undefined : callees.get(form.method);
|
|
102
|
+
return form !== undefined && method !== undefined && standard(method)
|
|
103
|
+
? form.invocation
|
|
104
|
+
: { callee: call.expression, arguments: call.arguments, indirect: false, listed: false };
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
of,
|
|
108
|
+
callee: (call) => {
|
|
109
|
+
const name = nameOf(of(call).callee);
|
|
110
|
+
return name === undefined ? undefined : callees.get(name);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
68
113
|
}
|
|
69
114
|
/** The declaration a shorthand property's value resolves to, an import followed to what it names. */
|
|
70
115
|
function shorthandTarget(project, node) {
|
|
@@ -76,74 +121,7 @@ function shorthandTarget(project, node) {
|
|
|
76
121
|
return aliased.declarations.length === 0 ? undefined : aliased;
|
|
77
122
|
}
|
|
78
123
|
/**
|
|
79
|
-
*
|
|
80
|
-
* array or object literal it can be holds, at any depth.
|
|
81
|
-
*/
|
|
82
|
-
function passedIn(written, found) {
|
|
83
|
-
for (const expression of leavesOf(written)) {
|
|
84
|
-
if (isArrayLiteralExpression(expression)) {
|
|
85
|
-
for (const element of expression.elements) {
|
|
86
|
-
passedIn(element, found);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else if (isObjectLiteralExpression(expression)) {
|
|
90
|
-
for (const property of expression.properties) {
|
|
91
|
-
if (isPropertyAssignment(property)) {
|
|
92
|
-
passedIn(property.initializer, found);
|
|
93
|
-
}
|
|
94
|
-
else if (isSpreadAssignment(property)) {
|
|
95
|
-
passedIn(property.expression, found);
|
|
96
|
-
}
|
|
97
|
-
else if (isShorthandPropertyAssignment(property)) {
|
|
98
|
-
found.push({ site: property.name, name: property, shorthand: true });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else if (!isOmittedExpression(expression)) {
|
|
103
|
-
found.push({ site: expression, name: calleeName(expression), shorthand: false });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/** The operators whose value is one of their operands. */
|
|
108
|
-
const CHOOSING = new Set([
|
|
109
|
-
SyntaxKind.QuestionQuestionToken,
|
|
110
|
-
SyntaxKind.BarBarToken,
|
|
111
|
-
SyntaxKind.AmpersandAmpersandToken,
|
|
112
|
-
]);
|
|
113
|
-
/**
|
|
114
|
-
* The expressions one value can be: itself, or each branch of a conditional, each operand
|
|
115
|
-
* of `??`, `||` and `&&` and the last of a comma, inside any wrapper that changes no value.
|
|
116
|
-
* Each is read alone because the checker reduces a union by subtype, so the type at the
|
|
117
|
-
* whole drops a class structurally equal to, or a subtype of, another branch's.
|
|
118
|
-
*/
|
|
119
|
-
function leavesOf(written) {
|
|
120
|
-
const expression = unwrapped(isSpreadElement(written) ? written.expression : written);
|
|
121
|
-
if (isConditionalExpression(expression)) {
|
|
122
|
-
return [...leavesOf(expression.whenTrue), ...leavesOf(expression.whenFalse)];
|
|
123
|
-
}
|
|
124
|
-
if (isBinaryExpression(expression)) {
|
|
125
|
-
const operator = expression.operatorToken.kind;
|
|
126
|
-
if (CHOOSING.has(operator)) {
|
|
127
|
-
return [...leavesOf(expression.left), ...leavesOf(expression.right)];
|
|
128
|
-
}
|
|
129
|
-
if (operator === SyntaxKind.CommaToken) {
|
|
130
|
-
return leavesOf(expression.right);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return [expression];
|
|
134
|
-
}
|
|
135
|
-
/** The values one argument passes, a spread argument's included. */
|
|
136
|
-
function leavesPassed(argument) {
|
|
137
|
-
const found = [];
|
|
138
|
-
passedIn(argument, found);
|
|
139
|
-
return found;
|
|
140
|
-
}
|
|
141
|
-
/** The values one argument passes that name their declaration. */
|
|
142
|
-
export function valuesPassed(argument) {
|
|
143
|
-
return leavesPassed(argument).filter((value) => value.name !== undefined);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* What one passed value holds: the declaration its name resolves to, and the classes and
|
|
124
|
+
* What one carried value holds: the declaration its name resolves to, and the classes and
|
|
147
125
|
* functions the checker's type at it holds ({@link constructedAt}). `known` holds names
|
|
148
126
|
* already resolved in a batch.
|
|
149
127
|
*/
|
|
@@ -160,21 +138,27 @@ function heldBy(project, value, known) {
|
|
|
160
138
|
typed: constructedAt(project, value.site),
|
|
161
139
|
};
|
|
162
140
|
}
|
|
141
|
+
/** The classes of the target each call passes, by the call, as {@link classesCarried} reads its invocation's arguments. */
|
|
142
|
+
export function classesPassed(project, held, chains, sites, calls) {
|
|
143
|
+
const carried = classesCarried(project, held, chains, sites, calls.map((call) => sites.invocations.of(call).arguments));
|
|
144
|
+
return new Map(calls.map((call, at) => [call, carried[at] ?? []]));
|
|
145
|
+
}
|
|
163
146
|
/**
|
|
164
|
-
* The classes of the target each
|
|
165
|
-
* can
|
|
166
|
-
* class is followed through what its declaration binds ({@link boundTo}) and, for a
|
|
167
|
-
* parameter, through what each call of its function passes at its place, so a class
|
|
168
|
-
* to a wrapper is passed. A class is used at each value that
|
|
169
|
-
* program
|
|
147
|
+
* The classes of the target each group of written values carries: each class a value
|
|
148
|
+
* can carry holds as a constructor, as the checker types it. A name whose type erases
|
|
149
|
+
* the class is followed through what its declaration binds ({@link boundTo}) and, for a
|
|
150
|
+
* parameter, through what each call of its function passes at its place, so a class
|
|
151
|
+
* handed to a wrapper is passed. A class is used at each value that carries it. A class
|
|
152
|
+
* of another program is carried by nothing.
|
|
170
153
|
*/
|
|
171
|
-
export function
|
|
172
|
-
const
|
|
173
|
-
const
|
|
154
|
+
export function classesCarried(project, held, chains, sites, groups) {
|
|
155
|
+
const { invocations: invoked } = sites;
|
|
156
|
+
const passed = groups.map((written) => written.flatMap(carriedValues));
|
|
157
|
+
const targets = resolvedTargets(project, passed.flat().flatMap((value) => (value.shorthand ? [] : (value.name ?? []))));
|
|
174
158
|
const classes = new Set(held.symbols.filter((symbol) => symbol.kind === "class").map((symbol) => symbol.id));
|
|
175
159
|
let calling;
|
|
176
160
|
const callsOfFunction = (symbol) => {
|
|
177
|
-
calling ??= callsByDeclaration(project);
|
|
161
|
+
calling ??= callsByDeclaration(project, sites.calls.map((one) => one.call), invoked);
|
|
178
162
|
return symbol.declarations.flatMap((handle) => calling?.get(keyOf(handle)) ?? []);
|
|
179
163
|
};
|
|
180
164
|
const classesOf = (value, seen) => {
|
|
@@ -190,14 +174,14 @@ export function classesPassed(project, held, chains, calls) {
|
|
|
190
174
|
return ids;
|
|
191
175
|
}
|
|
192
176
|
seen.add(symbol.id);
|
|
193
|
-
const through = (written) =>
|
|
177
|
+
const through = (written) => carriedValues(written).flatMap((value) => classesOf(value, seen));
|
|
194
178
|
return symbol.declarations.flatMap((handle) => {
|
|
195
179
|
const node = project.declarationAt(handle)?.node;
|
|
196
180
|
if (node === undefined) {
|
|
197
181
|
return [];
|
|
198
182
|
}
|
|
199
183
|
if (isParameterDeclaration(node)) {
|
|
200
|
-
return argumentsAt(project, node, callsOfFunction).flatMap(through);
|
|
184
|
+
return argumentsAt(project, node, callsOfFunction, invoked).flatMap(through);
|
|
201
185
|
}
|
|
202
186
|
const bound = boundTo(project, node);
|
|
203
187
|
return [
|
|
@@ -206,15 +190,15 @@ export function classesPassed(project, held, chains, calls) {
|
|
|
206
190
|
];
|
|
207
191
|
});
|
|
208
192
|
};
|
|
209
|
-
return
|
|
193
|
+
return passed.map((values) => {
|
|
210
194
|
const uses = new Map();
|
|
211
195
|
for (const value of values) {
|
|
212
196
|
for (const id of classesOf(value, new Set())) {
|
|
213
197
|
uses.set(`${id}\u0000${String(value.site.pos)}`, { id, site: value.site });
|
|
214
198
|
}
|
|
215
199
|
}
|
|
216
|
-
return [
|
|
217
|
-
})
|
|
200
|
+
return [...uses.values()];
|
|
201
|
+
});
|
|
218
202
|
}
|
|
219
203
|
/**
|
|
220
204
|
* The symbols of the classes and functions the checker's type at one expression holds as
|
|
@@ -269,16 +253,14 @@ function keyOf(handle) {
|
|
|
269
253
|
return `${handle.path}#${String(handle.index)}`;
|
|
270
254
|
}
|
|
271
255
|
/**
|
|
272
|
-
* Every call of the project by the declarations its
|
|
273
|
-
* method it names, or else each function the checker's type at it holds, wherever
|
|
274
|
-
* value came from. A name is also followed through what its binding copies
|
|
256
|
+
* Every call of the project by the declarations its invoked function can be: the function
|
|
257
|
+
* or the method it names, or else each function the checker's type at it holds, wherever
|
|
258
|
+
* that value came from. A name is also followed through what its binding copies
|
|
275
259
|
* ({@link boundTo}), which is how a call reaches a function an annotation erases.
|
|
276
260
|
* Only the target's own declarations are keys, as only its own functions are asked for.
|
|
277
261
|
*/
|
|
278
|
-
function callsByDeclaration(project) {
|
|
279
|
-
const calls = callsOf(project);
|
|
262
|
+
function callsByDeclaration(project, calls, invoked) {
|
|
280
263
|
const own = project.ownPaths();
|
|
281
|
-
const callees = resolvedTargets(project, calls.flatMap((call) => calleeName(call.expression) ?? []));
|
|
282
264
|
const named = new Map();
|
|
283
265
|
const keysOf = (symbol, seen) => {
|
|
284
266
|
const known = named.get(symbol.id);
|
|
@@ -293,7 +275,7 @@ function callsByDeclaration(project) {
|
|
|
293
275
|
const bound = node === undefined ? { symbols: [] } : boundTo(project, node);
|
|
294
276
|
const copied = [
|
|
295
277
|
...bound.symbols,
|
|
296
|
-
...(bound.written === undefined ? [] :
|
|
278
|
+
...(bound.written === undefined ? [] : carriedValues(bound.written)).flatMap((value) => {
|
|
297
279
|
const { named: next, typed } = heldBy(project, value);
|
|
298
280
|
return [...typed, ...(next === undefined ? [] : [next])];
|
|
299
281
|
}),
|
|
@@ -303,18 +285,14 @@ function callsByDeclaration(project) {
|
|
|
303
285
|
named.set(symbol.id, keys);
|
|
304
286
|
return keys;
|
|
305
287
|
};
|
|
306
|
-
const
|
|
307
|
-
const name = calleeName(call.expression);
|
|
308
|
-
return name === undefined ? undefined : callees.get(name);
|
|
309
|
-
};
|
|
310
|
-
const declared = (call) => ((calleeOf(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
|
|
288
|
+
const declared = (call) => ((invoked.callee(call)?.flags ?? 0) & (SymbolFlags.Function | SymbolFlags.Method)) !== 0;
|
|
311
289
|
const typed = calls.filter((call) => !declared(call));
|
|
312
290
|
const types = new Map(project.queries
|
|
313
|
-
.typesAt(typed.map((call) => unwrapped(call.
|
|
291
|
+
.typesAt(typed.map((call) => unwrapped(invoked.of(call).callee)))
|
|
314
292
|
.map((type, index) => [typed[index], must(type)]));
|
|
315
293
|
const found = new Map();
|
|
316
294
|
for (const call of calls) {
|
|
317
|
-
const callee =
|
|
295
|
+
const callee = invoked.callee(call);
|
|
318
296
|
const held = [
|
|
319
297
|
...(callee === undefined ? [] : [callee]),
|
|
320
298
|
...(types.has(call) ? heldIn(project, types.get(call)) : []),
|
|
@@ -327,12 +305,13 @@ function callsByDeclaration(project) {
|
|
|
327
305
|
}
|
|
328
306
|
/**
|
|
329
307
|
* What each call of the function a parameter belongs to passes at the parameter's place,
|
|
330
|
-
* every argument from there on for a rest parameter
|
|
331
|
-
* type at its own declaration names, so a function
|
|
332
|
-
* A `this` parameter takes no place, and an
|
|
333
|
-
* at every place the spread's length can
|
|
308
|
+
* every argument from there on for a rest parameter, as the call's invocation binds them.
|
|
309
|
+
* The function is the one the checker's type at its own declaration names, so a function
|
|
310
|
+
* with no name of its own is called too. A `this` parameter takes no place, and an
|
|
311
|
+
* argument at or after a spread argument passes at every place the spread's length can
|
|
312
|
+
* move it to.
|
|
334
313
|
*/
|
|
335
|
-
function argumentsAt(project, parameter, callsOfFunction) {
|
|
314
|
+
function argumentsAt(project, parameter, callsOfFunction, invoked) {
|
|
336
315
|
const owner = parameter.parent;
|
|
337
316
|
if (!isFunctionDeclaration(owner) &&
|
|
338
317
|
!isMethodDeclaration(owner) &&
|
|
@@ -347,7 +326,7 @@ function argumentsAt(project, parameter, callsOfFunction) {
|
|
|
347
326
|
const rest = parameter.dotDotDotToken !== undefined;
|
|
348
327
|
return [...calls].flatMap((call) => {
|
|
349
328
|
let spreads = 0;
|
|
350
|
-
return call.arguments.flatMap((argument, index) => {
|
|
329
|
+
return invoked.of(call).arguments.flatMap((argument, index) => {
|
|
351
330
|
const spread = isSpreadElement(argument);
|
|
352
331
|
const earliest = index - spreads;
|
|
353
332
|
spreads += spread ? 1 : 0;
|
|
@@ -357,35 +336,3 @@ function argumentsAt(project, parameter, callsOfFunction) {
|
|
|
357
336
|
});
|
|
358
337
|
});
|
|
359
338
|
}
|
|
360
|
-
/** Every class declaration of the target that carries a decorator. */
|
|
361
|
-
export function decoratedClasses(project, held) {
|
|
362
|
-
const found = [];
|
|
363
|
-
for (const file of project.ownSourceFiles()) {
|
|
364
|
-
const visit = (node) => {
|
|
365
|
-
if (isClassDeclaration(node)) {
|
|
366
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
367
|
-
const decorators = decoratorsOf(node);
|
|
368
|
-
if (id !== undefined && decorators.length > 0) {
|
|
369
|
-
found.push({ id, decorators });
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
node.forEachChild(visit);
|
|
373
|
-
};
|
|
374
|
-
file.forEachChild(visit);
|
|
375
|
-
}
|
|
376
|
-
return found;
|
|
377
|
-
}
|
|
378
|
-
/** Every call expression of the project's own files, decorators written as calls included. */
|
|
379
|
-
export function callsOf(project) {
|
|
380
|
-
const found = [];
|
|
381
|
-
for (const file of project.ownSourceFiles()) {
|
|
382
|
-
const visit = (node) => {
|
|
383
|
-
if (isCallExpression(node)) {
|
|
384
|
-
found.push(node);
|
|
385
|
-
}
|
|
386
|
-
node.forEachChild(visit);
|
|
387
|
-
};
|
|
388
|
-
file.forEachChild(visit);
|
|
389
|
-
}
|
|
390
|
-
return found;
|
|
391
|
-
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* static members of a decorated class included.
|
|
8
8
|
*/
|
|
9
9
|
import { decoratorsOf, spelledDecorator } from "./calls.js";
|
|
10
|
-
import { isParameterProperty
|
|
11
|
-
import { renderPosition } from "
|
|
10
|
+
import { isParameterProperty } from "../inventory.js";
|
|
11
|
+
import { renderPosition } from "../position.js";
|
|
12
12
|
/** The kinds of declaration a decorator is attached to and holds back. */
|
|
13
13
|
const DECORATED = new Set(["class", "method", "class-member"]);
|
|
14
14
|
/**
|
|
@@ -16,7 +16,7 @@ const DECORATED = new Set(["class", "method", "class-member"]);
|
|
|
16
16
|
* class, recorded at the decorator.
|
|
17
17
|
*/
|
|
18
18
|
export function decorator(input) {
|
|
19
|
-
const {
|
|
19
|
+
const { held, sites, targetRoot } = input;
|
|
20
20
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
21
21
|
const membersOf = new Map();
|
|
22
22
|
for (const symbol of held.symbols) {
|
|
@@ -30,32 +30,21 @@ export function decorator(input) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
const decorated = [];
|
|
34
33
|
// A decorated class's container passes each constructor argument, which keeps the
|
|
35
|
-
// parameter, not the member a parameter property stores it in.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
node.forEachChild(visit);
|
|
55
|
-
};
|
|
56
|
-
file.forEachChild(visit);
|
|
57
|
-
}
|
|
58
|
-
return decorated.flatMap(({ symbol, detail, site }) => (symbol.kind === "class"
|
|
59
|
-
? (membersOf.get(symbol.id) ?? []).filter((member) => !parameterMembers.has(member.id))
|
|
60
|
-
: [symbol]).map((member) => ({ id: member.id, detail, site })));
|
|
34
|
+
// parameter, not the member a parameter property stores it in. A parameter property's
|
|
35
|
+
// decorator is attached to its parameter, and to no member.
|
|
36
|
+
return sites.decorated.flatMap(({ id, node }) => {
|
|
37
|
+
const symbol = byId.get(id);
|
|
38
|
+
if (symbol === undefined || !DECORATED.has(symbol.kind) || isParameterProperty(node)) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const members = symbol.kind === "class"
|
|
42
|
+
? (membersOf.get(symbol.id) ?? []).filter((member) => !sites.parameterProperties.has(member.id))
|
|
43
|
+
: [symbol];
|
|
44
|
+
return decoratorsOf(node).flatMap((attached) => {
|
|
45
|
+
const detail = `decorated by ${spelledDecorator(attached)}`;
|
|
46
|
+
const site = renderPosition(node.getSourceFile(), targetRoot, attached.getStart());
|
|
47
|
+
return members.map((member) => ({ id: member.id, detail, site }));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
61
50
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exemption classes this analyzer detects, each by its detector, and what their
|
|
3
|
+
* detection reads of the run once for every project. A class without a detector here
|
|
4
|
+
* retains nothing.
|
|
5
|
+
*/
|
|
6
|
+
import { decorator } from "./decorator.js";
|
|
7
|
+
import { enumGroup } from "./enum-group.js";
|
|
8
|
+
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
9
|
+
import { generatedFile } from "./generated-file.js";
|
|
10
|
+
import { injectionContainer } from "./injection-container.js";
|
|
11
|
+
import { interfaceSatisfaction } from "./interface-satisfaction.js";
|
|
12
|
+
import { detect } from "./lifecycle.js";
|
|
13
|
+
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
14
|
+
import { serializationContract } from "./serialization-contract.js";
|
|
15
|
+
import { readTemplates, templateField } from "./template-field.js";
|
|
16
|
+
export const DETECTORS = new Map([
|
|
17
|
+
["interface-satisfaction", interfaceSatisfaction],
|
|
18
|
+
["enum-group", enumGroup],
|
|
19
|
+
["generated-file", generatedFile],
|
|
20
|
+
["template-field", templateField],
|
|
21
|
+
["reflective-lookup", reflectiveLookup],
|
|
22
|
+
["decorator", decorator],
|
|
23
|
+
["injection-container", injectionContainer],
|
|
24
|
+
["framework-lifecycle", frameworkLifecycle],
|
|
25
|
+
["serialization-contract", serializationContract],
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* The detection of one run: what every project's detectors read of the run, read once,
|
|
29
|
+
* and a function that runs them over one project. A switched-off class reads nothing,
|
|
30
|
+
* its templates included, and generated files are held only where the configuration
|
|
31
|
+
* leaves them out of the analysis.
|
|
32
|
+
*/
|
|
33
|
+
export function detection(host, targetRoot, config, consumers, detectors) {
|
|
34
|
+
const disabled = new Set(config.exemptions.disabled);
|
|
35
|
+
const templates = disabled.has("template-field")
|
|
36
|
+
? { delimiters: config.analysis.template_delimiters, files: [] }
|
|
37
|
+
: readTemplates(host, targetRoot, config.analysis);
|
|
38
|
+
const included = config.analysis.generated_files === "include";
|
|
39
|
+
return ({ project, held, generated }) => detect({
|
|
40
|
+
project,
|
|
41
|
+
held,
|
|
42
|
+
targetRoot,
|
|
43
|
+
templates,
|
|
44
|
+
ts: config.ts,
|
|
45
|
+
consumers,
|
|
46
|
+
...(included ? {} : { generated }),
|
|
47
|
+
}, detectors, disabled);
|
|
48
|
+
}
|
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
* function it annotates, where a number counts too. An enum's own value converts nothing.
|
|
8
8
|
*/
|
|
9
9
|
import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
-
import { parenthesesRemoved } from "
|
|
10
|
+
import { parenthesesRemoved } from "../unwrap.js";
|
|
11
11
|
import { TypeFlags } from "@typescript/native/sync";
|
|
12
|
-
import { aliasChains } from "
|
|
13
|
-
import { nodeKey } from "
|
|
14
|
-
import { renderPosition } from "
|
|
15
|
-
import { isAnswered, UNANSWERED } from "
|
|
12
|
+
import { aliasChains } from "../alias-chain.js";
|
|
13
|
+
import { nodeKey } from "../inventory.js";
|
|
14
|
+
import { renderPosition } from "../position.js";
|
|
15
|
+
import { isAnswered, UNANSWERED } from "../query.js";
|
|
16
|
+
import { ERASED } from "./value-reach.js";
|
|
16
17
|
/** The operand types a conversion produces an enum value from. */
|
|
17
18
|
const NUMBER = TypeFlags.NumberLike;
|
|
18
19
|
const STRING = TypeFlags.StringLike;
|
|
19
|
-
const DECODED = TypeFlags.Any | TypeFlags.Unknown;
|
|
20
20
|
/**
|
|
21
21
|
* The name node a type node names its declaration by: the identifier of a type
|
|
22
22
|
* reference, the right side of a qualified one, through parentheses. A type that is no
|
|
@@ -128,7 +128,7 @@ function detailOf(project, site, enumName, operand) {
|
|
|
128
128
|
if (parts.some((part) => (part.flags & STRING) !== 0)) {
|
|
129
129
|
return site.form === "assertion" ? "converted from string" : undefined;
|
|
130
130
|
}
|
|
131
|
-
if (parts.some((part) => (part.flags &
|
|
131
|
+
if (parts.some((part) => (part.flags & ERASED) !== 0)) {
|
|
132
132
|
return `decoded as ${enumName}`;
|
|
133
133
|
}
|
|
134
134
|
return undefined;
|
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
* declare hands the value to the code that declares it.
|
|
8
8
|
*/
|
|
9
9
|
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
-
import { SymbolFlags
|
|
11
|
-
import { must } from "
|
|
12
|
-
import { checkedTypes } from "./value-reach.js";
|
|
13
|
-
import { parenthesesRemoved } from "
|
|
14
|
-
/** The types that keep nothing of the value they are given. */
|
|
15
|
-
const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
10
|
+
import { SymbolFlags } from "@typescript/native/sync";
|
|
11
|
+
import { must } from "../query.js";
|
|
12
|
+
import { checkedTypes, ERASED } from "./value-reach.js";
|
|
13
|
+
import { parenthesesRemoved } from "../unwrap.js";
|
|
16
14
|
/** The array methods whose arguments become elements of the array they are called on. */
|
|
17
15
|
const ELEMENT_STORES = new Set(["push", "unshift"]);
|
|
18
16
|
/** The name an element access's literal index spells, or none. */
|