@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.21
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 +4 -3
- 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/index/sites.js +1 -1
- package/dist/src/index/values.js +124 -0
- 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 +1 -1
- 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 +4 -3
- 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/index/sites.ts +1 -1
- package/src/index/values.ts +170 -0
- 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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one walk of a project's own files every detector shares: its calls and the class
|
|
3
|
+
* `this` stands for at each, its classes, the values it hands to positions typed by
|
|
4
|
+
* their context, its functions' parameters, its decorated declarations and its
|
|
5
|
+
* assignments to properties. The walk reads syntax only; what a call invokes through a
|
|
6
|
+
* method of `Function.prototype` asks the checker, once per project, at the first
|
|
7
|
+
* detector that needs it.
|
|
8
|
+
*/
|
|
9
|
+
import { isArrowFunction, isBinaryExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isElementAccessExpression, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyDeclaration, isSetAccessorDeclaration, isStringLiteral, isVariableDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
+
import { handedOn, valuesOf } from "../index/values.js";
|
|
11
|
+
import { isParameterProperty, nodeKey } from "../inventory.js";
|
|
12
|
+
import { parenthesesRemoved } from "../unwrap.js";
|
|
13
|
+
import { decoratorsOf, invocations } from "./calls.js";
|
|
14
|
+
/** The property one assignment's left side names on its receiver, where a written key names it. */
|
|
15
|
+
function storeOf(node) {
|
|
16
|
+
if (!isBinaryExpression(node) || node.operatorToken.kind !== SyntaxKind.EqualsToken) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const left = parenthesesRemoved(node.left);
|
|
20
|
+
if (isPropertyAccessExpression(left) && isIdentifier(left.name)) {
|
|
21
|
+
return { store: node, receiver: left.expression, key: left.name.text, value: node.right };
|
|
22
|
+
}
|
|
23
|
+
if (isElementAccessExpression(left) &&
|
|
24
|
+
(isStringLiteral(left.argumentExpression) ||
|
|
25
|
+
isNoSubstitutionTemplateLiteral(left.argumentExpression))) {
|
|
26
|
+
return {
|
|
27
|
+
store: node,
|
|
28
|
+
receiver: left.expression,
|
|
29
|
+
key: left.argumentExpression.text,
|
|
30
|
+
value: node.right,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Whether one node declares a static member, whose `this` is the class itself. */
|
|
36
|
+
function isStatic(node) {
|
|
37
|
+
const flags = node.modifierFlags ?? 0;
|
|
38
|
+
return (flags & ModifierFlags.Static) !== 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether one node opens a body whose `this` is no instance of the enclosing class: a
|
|
42
|
+
* function that is not an arrow, a static block, a static member, and a method or an
|
|
43
|
+
* accessor of an object literal, whose `this` is the literal.
|
|
44
|
+
*/
|
|
45
|
+
function resetsThis(node) {
|
|
46
|
+
return (isFunctionDeclaration(node) ||
|
|
47
|
+
isFunctionExpression(node) ||
|
|
48
|
+
isClassStaticBlockDeclaration(node) ||
|
|
49
|
+
isStatic(node) ||
|
|
50
|
+
((isMethodDeclaration(node) ||
|
|
51
|
+
isGetAccessorDeclaration(node) ||
|
|
52
|
+
isSetAccessorDeclaration(node)) &&
|
|
53
|
+
isObjectLiteralExpression(node.parent)));
|
|
54
|
+
}
|
|
55
|
+
/** The parameters and declaring node of one function-like declaration of the project. */
|
|
56
|
+
function functionOf(node) {
|
|
57
|
+
if (isFunctionDeclaration(node) ||
|
|
58
|
+
(isMethodDeclaration(node) && !isObjectLiteralExpression(node.parent))) {
|
|
59
|
+
return { declared: node, parameters: node.parameters };
|
|
60
|
+
}
|
|
61
|
+
if (isVariableDeclaration(node) &&
|
|
62
|
+
node.initializer !== undefined &&
|
|
63
|
+
(isArrowFunction(node.initializer) || isFunctionExpression(node.initializer))) {
|
|
64
|
+
return { declared: node, parameters: node.initializer.parameters };
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function walk(project, held) {
|
|
69
|
+
const calls = [];
|
|
70
|
+
const classes = [];
|
|
71
|
+
const flows = [];
|
|
72
|
+
const parameters = [];
|
|
73
|
+
const decorated = [];
|
|
74
|
+
const stores = [];
|
|
75
|
+
const parameterProperties = new Set();
|
|
76
|
+
const initialized = new Set();
|
|
77
|
+
for (const file of project.ownSourceFiles()) {
|
|
78
|
+
const visit = (node, self) => {
|
|
79
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
80
|
+
let inner = self;
|
|
81
|
+
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
82
|
+
inner = id;
|
|
83
|
+
if (id !== undefined) {
|
|
84
|
+
classes.push({ id, node });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (resetsThis(node)) {
|
|
88
|
+
inner = undefined;
|
|
89
|
+
}
|
|
90
|
+
if (id !== undefined) {
|
|
91
|
+
if (isParameterProperty(node)) {
|
|
92
|
+
parameterProperties.add(id);
|
|
93
|
+
}
|
|
94
|
+
if (decoratorsOf(node).length > 0) {
|
|
95
|
+
decorated.push({ id, node });
|
|
96
|
+
}
|
|
97
|
+
if (isPropertyDeclaration(node) && node.initializer !== undefined) {
|
|
98
|
+
initialized.add(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (node.kind === SyntaxKind.CallExpression) {
|
|
102
|
+
calls.push({ call: node, self: inner });
|
|
103
|
+
}
|
|
104
|
+
const store = storeOf(node);
|
|
105
|
+
if (store !== undefined) {
|
|
106
|
+
stores.push(store);
|
|
107
|
+
}
|
|
108
|
+
for (const written of handedOn(node)) {
|
|
109
|
+
for (const value of valuesOf(written)) {
|
|
110
|
+
flows.push({ value, self: value.kind === SyntaxKind.ThisKeyword ? inner : undefined });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const fn = functionOf(node);
|
|
114
|
+
const fnId = fn === undefined ? undefined : held.declarations.get(nodeKey(file, fn.declared));
|
|
115
|
+
if (fn !== undefined && fnId !== undefined) {
|
|
116
|
+
fn.parameters
|
|
117
|
+
.filter((parameter) => !(isIdentifier(parameter.name) && parameter.name.text === "this"))
|
|
118
|
+
.forEach((parameter, index) => {
|
|
119
|
+
if (isIdentifier(parameter.name)) {
|
|
120
|
+
parameters.push({
|
|
121
|
+
fn: fnId,
|
|
122
|
+
index,
|
|
123
|
+
rest: parameter.dotDotDotToken !== undefined,
|
|
124
|
+
name: parameter.name,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
node.forEachChild((child) => {
|
|
130
|
+
visit(child, inner);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
file.forEachChild((child) => {
|
|
134
|
+
visit(child, undefined);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
calls,
|
|
139
|
+
classes,
|
|
140
|
+
flows,
|
|
141
|
+
parameters,
|
|
142
|
+
decorated,
|
|
143
|
+
stores,
|
|
144
|
+
parameterProperties,
|
|
145
|
+
initialized,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** The sites of one project's own files, each walk and each checker question made once. */
|
|
149
|
+
export function projectSites(project, held) {
|
|
150
|
+
let walked;
|
|
151
|
+
let invoked;
|
|
152
|
+
const sites = () => (walked ??= walk(project, held));
|
|
153
|
+
return {
|
|
154
|
+
get calls() {
|
|
155
|
+
return sites().calls;
|
|
156
|
+
},
|
|
157
|
+
get classes() {
|
|
158
|
+
return sites().classes;
|
|
159
|
+
},
|
|
160
|
+
get flows() {
|
|
161
|
+
return sites().flows;
|
|
162
|
+
},
|
|
163
|
+
get parameters() {
|
|
164
|
+
return sites().parameters;
|
|
165
|
+
},
|
|
166
|
+
get decorated() {
|
|
167
|
+
return sites().decorated;
|
|
168
|
+
},
|
|
169
|
+
get stores() {
|
|
170
|
+
return sites().stores;
|
|
171
|
+
},
|
|
172
|
+
get parameterProperties() {
|
|
173
|
+
return sites().parameterProperties;
|
|
174
|
+
},
|
|
175
|
+
get initialized() {
|
|
176
|
+
return sites().initialized;
|
|
177
|
+
},
|
|
178
|
+
get invocations() {
|
|
179
|
+
return (invoked ??= invocations(project, sites().calls.map((one) => one.call)));
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* instance members of classes and the members of interfaces and object types, on any
|
|
12
12
|
* type, because nothing ties the name to a type.
|
|
13
13
|
*/
|
|
14
|
-
import { readComponent } from "
|
|
15
|
-
import { memberNames } from "
|
|
16
|
-
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "
|
|
17
|
-
import { isComponentFile } from "
|
|
18
|
-
import { bytewise } from "
|
|
19
|
-
import { filesBelow } from "
|
|
20
|
-
import { UsageError } from "
|
|
14
|
+
import { readComponent } from "../component-files.js";
|
|
15
|
+
import { memberNames } from "../member-names.js";
|
|
16
|
+
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "../paths.js";
|
|
17
|
+
import { isComponentFile } from "../position.js";
|
|
18
|
+
import { bytewise } from "../text/compare.js";
|
|
19
|
+
import { filesBelow } from "../fs/tree.js";
|
|
20
|
+
import { UsageError } from "../usage.js";
|
|
21
21
|
/** The kinds of declaration an action can name. */
|
|
22
22
|
const READ_BY_A_TEMPLATE = new Set([
|
|
23
23
|
"method",
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* The classes of the target a value of one type carries, and the members of the other
|
|
3
3
|
* object types the target writes that it carries, as a serializer walking the value reaches
|
|
4
4
|
* them: through constituents, array and tuple elements, the data members and index
|
|
5
|
-
* signatures of the types the target writes,
|
|
6
|
-
* it erases them. The walk stops at `any`, `unknown`,
|
|
7
|
-
* interface or class of another program, so an
|
|
5
|
+
* signatures of the types the target writes, a class's polymorphic `this` type, and the values
|
|
6
|
+
* `stored` gives for a type where it erases them. The walk stops at `any`, `unknown`, any
|
|
7
|
+
* other type parameter, a function and an interface or class of another program, so an
|
|
8
|
+
* instance held only there is missed.
|
|
8
9
|
*/
|
|
9
10
|
import { isAsExpression, isParenthesizedExpression, isSatisfiesExpression, isTypeAssertion, } from "@typescript/native/ast";
|
|
10
|
-
import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
|
|
11
|
-
import { classNames, typesAt } from "
|
|
12
|
-
import { must } from "
|
|
11
|
+
import { ObjectFlags, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
|
|
12
|
+
import { classNames, typesAt } from "../class-members.js";
|
|
13
|
+
import { must } from "../query.js";
|
|
13
14
|
/**
|
|
14
15
|
* The object types whose members the walk reads whatever declares them. A reverse-mapped
|
|
15
16
|
* type has no symbol: it is the shape the checker infers from an argument the target
|
|
@@ -19,7 +20,9 @@ const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped | ObjectFlags.Rev
|
|
|
19
20
|
/** The symbols of an anonymous object type the target writes as a type. */
|
|
20
21
|
const LITERAL_SYMBOLS = SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral;
|
|
21
22
|
/** The two conversion methods a serializer or a formatter resolves on a value's own shape. */
|
|
22
|
-
const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
|
|
23
|
+
export const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
|
|
24
|
+
/** The types that keep nothing of a value they are given, or that a decode gives a value. */
|
|
25
|
+
export const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
23
26
|
/**
|
|
24
27
|
* The members that can answer a conversion: a serializer reads the name with `[[Get]]`
|
|
25
28
|
* and calls what it answers, so a getter answers it as a method does:
|
|
@@ -140,6 +143,10 @@ export function valueReach(project, held, stored = () => []) {
|
|
|
140
143
|
if (type.isUnionType() || type.isIntersectionType()) {
|
|
141
144
|
found = must(queries.constituents(type));
|
|
142
145
|
}
|
|
146
|
+
else if (type.isTypeParameter() && type.isThisType === true) {
|
|
147
|
+
// A value of a class's polymorphic `this` type is an instance of the class.
|
|
148
|
+
found = [must(queries.constraintOf(type))].filter((one) => one !== undefined);
|
|
149
|
+
}
|
|
143
150
|
else if (type.isObjectType()) {
|
|
144
151
|
const target = targetOf(type);
|
|
145
152
|
const isList = type.isTypeReference() &&
|
package/dist/src/index/sites.js
CHANGED
|
@@ -15,7 +15,7 @@ import { writtenName } from "../platform-calls.js";
|
|
|
15
15
|
import { isLoadCall } from "../roots/unseen-loads.js";
|
|
16
16
|
import { keyNameOf } from "../symbol-keys.js";
|
|
17
17
|
import { parenthesesRemoved } from "../unwrap.js";
|
|
18
|
-
import { handedOn, valuesOf } from "
|
|
18
|
+
import { handedOn, valuesOf } from "./values.js";
|
|
19
19
|
/**
|
|
20
20
|
* The operators whose left side one assignment stores into. A compound assignment is
|
|
21
21
|
* among them: where its value is discarded it reads its target only to write the result
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a written expression evaluates to, the one definition every pass shares: the
|
|
3
|
+
* values it can be, the values a call's argument carries inside the literals it writes,
|
|
4
|
+
* and the positions a program hands a value to that the checker types from their context.
|
|
5
|
+
*/
|
|
6
|
+
import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isBinaryExpression, isCallExpression, isConditionalExpression, isIdentifier, isNewExpression, isNonNullExpression, isObjectLiteralExpression, isOmittedExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
/** The operators of an assignment whose right side is stored into the left side's type. */
|
|
8
|
+
const STORES = new Set([
|
|
9
|
+
SyntaxKind.EqualsToken,
|
|
10
|
+
SyntaxKind.QuestionQuestionEqualsToken,
|
|
11
|
+
SyntaxKind.BarBarEqualsToken,
|
|
12
|
+
SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
13
|
+
]);
|
|
14
|
+
/** Whether a declaration writes its type, so its initializer is read in that type's context. */
|
|
15
|
+
function writesType(node) {
|
|
16
|
+
return node.type !== undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The expressions one written value can evaluate to, each read alone because the checker
|
|
20
|
+
* reduces a union by subtype, so the type at the whole drops a class another branch's
|
|
21
|
+
* extends: the operand of a spread, of parentheses and of `!`, each branch of a
|
|
22
|
+
* conditional, each operand of `??` and `||`, and the right operand of `&&` and of a
|
|
23
|
+
* comma. The left operand of `&&` is its value only where it is falsy, which no object
|
|
24
|
+
* is. An assertion is a value of its own, whose type its reader decides.
|
|
25
|
+
*/
|
|
26
|
+
export function valuesOf(expression) {
|
|
27
|
+
if (isSpreadElement(expression) || isParenthesizedExpression(expression)) {
|
|
28
|
+
return valuesOf(expression.expression);
|
|
29
|
+
}
|
|
30
|
+
if (isNonNullExpression(expression)) {
|
|
31
|
+
return valuesOf(expression.expression);
|
|
32
|
+
}
|
|
33
|
+
if (isConditionalExpression(expression)) {
|
|
34
|
+
return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
|
|
35
|
+
}
|
|
36
|
+
if (isBinaryExpression(expression)) {
|
|
37
|
+
const operator = expression.operatorToken.kind;
|
|
38
|
+
if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
|
|
39
|
+
return [...valuesOf(expression.left), ...valuesOf(expression.right)];
|
|
40
|
+
}
|
|
41
|
+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
|
|
42
|
+
return valuesOf(expression.right);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [expression];
|
|
46
|
+
}
|
|
47
|
+
/** The name node a callee, a decorator expression or a value resolves through, where it has one. */
|
|
48
|
+
export function nameOf(expression) {
|
|
49
|
+
if (isParenthesizedExpression(expression)) {
|
|
50
|
+
return nameOf(expression.expression);
|
|
51
|
+
}
|
|
52
|
+
if (isPropertyAccessExpression(expression)) {
|
|
53
|
+
return expression.name;
|
|
54
|
+
}
|
|
55
|
+
return isIdentifier(expression) ? expression : undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The values one argument carries: each value it can be ({@link valuesOf}), or what an
|
|
59
|
+
* array or object literal it can be holds, at any depth, through spreads and shorthand
|
|
60
|
+
* properties. Each value is read inside every assertion around it.
|
|
61
|
+
*/
|
|
62
|
+
export function carriedValues(argument) {
|
|
63
|
+
return valuesOf(argument).flatMap((value) => {
|
|
64
|
+
let at = value;
|
|
65
|
+
while (isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
66
|
+
at = at.expression;
|
|
67
|
+
}
|
|
68
|
+
if (at !== value) {
|
|
69
|
+
return carriedValues(at);
|
|
70
|
+
}
|
|
71
|
+
if (isArrayLiteralExpression(value)) {
|
|
72
|
+
return value.elements.flatMap(carriedValues);
|
|
73
|
+
}
|
|
74
|
+
if (isObjectLiteralExpression(value)) {
|
|
75
|
+
return value.properties.flatMap((property) => {
|
|
76
|
+
if (isPropertyAssignment(property)) {
|
|
77
|
+
return carriedValues(property.initializer);
|
|
78
|
+
}
|
|
79
|
+
if (isSpreadAssignment(property)) {
|
|
80
|
+
return carriedValues(property.expression);
|
|
81
|
+
}
|
|
82
|
+
return isShorthandPropertyAssignment(property)
|
|
83
|
+
? [{ site: property.name, name: property, shorthand: true }]
|
|
84
|
+
: [];
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return isOmittedExpression(value)
|
|
88
|
+
? []
|
|
89
|
+
: [{ site: value, name: nameOf(value), shorthand: false }];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** The expressions one node hands to a position typed by its context. */
|
|
93
|
+
export function handedOn(node) {
|
|
94
|
+
if ((isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
|
|
95
|
+
node.initializer !== undefined &&
|
|
96
|
+
writesType(node)) {
|
|
97
|
+
return [node.initializer];
|
|
98
|
+
}
|
|
99
|
+
if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
|
|
100
|
+
return [node.right];
|
|
101
|
+
}
|
|
102
|
+
if (isCallExpression(node) || isNewExpression(node)) {
|
|
103
|
+
return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
|
|
104
|
+
}
|
|
105
|
+
if (isReturnStatement(node)) {
|
|
106
|
+
return node.expression === undefined ? [] : [node.expression];
|
|
107
|
+
}
|
|
108
|
+
if (isArrowFunction(node)) {
|
|
109
|
+
return node.body.kind === SyntaxKind.Block ? [] : [node.body];
|
|
110
|
+
}
|
|
111
|
+
if (isArrayLiteralExpression(node)) {
|
|
112
|
+
return node.elements.filter((element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression);
|
|
113
|
+
}
|
|
114
|
+
if (isPropertyAssignment(node)) {
|
|
115
|
+
return [node.initializer];
|
|
116
|
+
}
|
|
117
|
+
if (isShorthandPropertyAssignment(node)) {
|
|
118
|
+
return isIdentifier(node.name) ? [node.name] : [];
|
|
119
|
+
}
|
|
120
|
+
if (isAsExpression(node) || isSatisfiesExpression(node)) {
|
|
121
|
+
return [node.expression];
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* first: a caller, an exemption or a live component file's markup, a suppression mark, and an
|
|
7
7
|
* unanswered checker question; each Contract rule "live only through" one is a view up to it.
|
|
8
8
|
*/
|
|
9
|
-
import { exemptionsOf
|
|
9
|
+
import { exemptionsOf } from "../exemptions/lifecycle.js";
|
|
10
10
|
import { edgeWithheldBy } from "../tests/file-class.js";
|
|
11
11
|
import { componentsOf } from "./components.js";
|
|
12
12
|
import { namesACaller, OUTSIDE } from "./graph.js";
|
|
@@ -367,7 +367,7 @@ function seedsOf(graph, policy) {
|
|
|
367
367
|
if (record.whileLive !== undefined) {
|
|
368
368
|
gate(record.whileLive, seed);
|
|
369
369
|
}
|
|
370
|
-
else if (
|
|
370
|
+
else if (record.unanswered === true) {
|
|
371
371
|
unanswered.push(seed);
|
|
372
372
|
}
|
|
373
373
|
else {
|
package/dist/src/query.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* form where it has one and otherwise as one batch, one round trip per capped run.
|
|
8
8
|
*/
|
|
9
9
|
import { isIdentifier } from "@typescript/native/ast";
|
|
10
|
+
import { IndexKind, } from "@typescript/native/sync";
|
|
10
11
|
/**
|
|
11
12
|
* The greatest number of locations one batched request asks about. A batch is
|
|
12
13
|
* answered by an array of its own length, so an uncapped batch over a large file
|
|
@@ -118,6 +119,41 @@ function partQuestion(type) {
|
|
|
118
119
|
}
|
|
119
120
|
return undefined;
|
|
120
121
|
}
|
|
122
|
+
/** How deep {@link mappedParameters} reads, as a recursive mapped type makes a new one at each level. */
|
|
123
|
+
const MAPPED_DEPTH = 4;
|
|
124
|
+
/** The type parameters a mapped type is over, as {@link Queries.mappedParametersOf} names them. */
|
|
125
|
+
function mappedParameters(checker, mapped) {
|
|
126
|
+
const own = new Set();
|
|
127
|
+
const found = new Map();
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
const visit = (type, depth) => {
|
|
130
|
+
if (depth > MAPPED_DEPTH || seen.has(type.id)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
seen.add(type.id);
|
|
134
|
+
if (type.isMappedType()) {
|
|
135
|
+
own.add(type.getTypeParameter().id);
|
|
136
|
+
}
|
|
137
|
+
if (type.isTypeParameter() && !type.isThisType) {
|
|
138
|
+
found.set(type.id, type);
|
|
139
|
+
}
|
|
140
|
+
const aliased = type
|
|
141
|
+
.aliasTypeArguments;
|
|
142
|
+
const inner = [
|
|
143
|
+
...(type.isUnionType() || type.isIntersectionType() ? type.getTypes() : []),
|
|
144
|
+
...(type.isTypeReference() ? checker.getTypeArguments(type) : []),
|
|
145
|
+
...(aliased === undefined || aliased.length === 0 ? [] : type.getAliasTypeArguments()),
|
|
146
|
+
...(partQuestion(type)?.() ?? []),
|
|
147
|
+
];
|
|
148
|
+
for (const part of inner) {
|
|
149
|
+
if (part !== undefined) {
|
|
150
|
+
visit(part, depth + 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
visit(mapped, 0);
|
|
155
|
+
return [...found.values()].filter((parameter) => !own.has(parameter.id));
|
|
156
|
+
}
|
|
121
157
|
/**
|
|
122
158
|
* The guarded questions over one project's checker. `configFile` names the project
|
|
123
159
|
* in the log, `batch` sends many questions in one round trip, `log` is the run's,
|
|
@@ -244,6 +280,7 @@ export function queriesOf(checker, configFile, batch, log, ask = ASK_DIRECTLY) {
|
|
|
244
280
|
constraintOf: (type) => type.isTypeParameter()
|
|
245
281
|
? one("getConstraint", () => typeLocation(type), () => type.getConstraint())
|
|
246
282
|
: undefined,
|
|
283
|
+
baseConstraintOf: (type) => one("getBaseConstraintOfType", () => typeLocation(type), () => checker.getBaseConstraintOfType(type)),
|
|
247
284
|
indexedAccessOf: (type) => type.isIndexedAccessType()
|
|
248
285
|
? one("getObjectType", () => typeLocation(type), () => ({ object: type.getObjectType(), index: type.getIndexType() }))
|
|
249
286
|
: undefined,
|
|
@@ -272,6 +309,17 @@ export function queriesOf(checker, configFile, batch, log, ask = ASK_DIRECTLY) {
|
|
|
272
309
|
? undefined
|
|
273
310
|
: one("getTarget", () => `signature ${String(signature.id)}`, () => signature.getTarget()),
|
|
274
311
|
typeAtPosition: (signature, index) => one("getTypeParameterAtPosition", () => `signature ${String(signature.id)} position ${String(index)}`, () => signature.getTypeParameterAtPosition(index)),
|
|
312
|
+
restTypeOf: (signature) => signature.hasRestParameter
|
|
313
|
+
? one("getTypeOfSymbol", () => `signature ${String(signature.id)} rest parameter`, () => {
|
|
314
|
+
const rest = signature.getParameters().at(-1);
|
|
315
|
+
return rest === undefined ? undefined : checker.getTypeOfSymbol(rest);
|
|
316
|
+
})
|
|
317
|
+
: undefined,
|
|
318
|
+
numberIndexOf: (type) => one("getIndexTypeOfType", () => typeLocation(type), () => checker.getIndexTypeOfType(type, IndexKind.Number)),
|
|
319
|
+
mappedParametersOf: (type) => type.isMappedType()
|
|
320
|
+
? one("getConstraintType", () => typeLocation(type), () => mappedParameters(checker, type))
|
|
321
|
+
: [],
|
|
322
|
+
propertyTypeOf: (type, name) => one("getTypeOfPropertyOfType", () => `${typeLocation(type)} property ${name}`, () => checker.getTypeOfPropertyOfType(type, name)),
|
|
275
323
|
signatureOf: (declaration) => one("getSignatureFromDeclaration", () => nodeLocation(declaration), () => checker.getSignatureFromDeclaration(declaration)),
|
|
276
324
|
returnTypeOf: (signature) => one("getReturnTypeOfSignature", () => `signature ${String(signature.id)}`, () => checker.getReturnTypeOfSignature(signature)),
|
|
277
325
|
typeToString: (type) => one("typeToString", () => typeLocation(type), () => checker.typeToString(type)),
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
8
8
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
9
|
-
import { resolvedTargets } from "../
|
|
9
|
+
import { resolvedTargets } from "../alias-chain.js";
|
|
10
10
|
import { declaredComponent } from "../inventory.js";
|
|
11
11
|
import { must, Unanswerable } from "../query.js";
|
|
12
12
|
import { nameComponent } from "../ref.js";
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { EXIT_CLEAN } from "../contract/contract.generated.js";
|
|
2
2
|
import { productionMode, runSweep } from "../analysis.js";
|
|
3
|
-
import { retainedLines } from "../exempt.js";
|
|
4
3
|
import { partialNotes } from "../partial.js";
|
|
4
|
+
import { positionKey } from "../position.js";
|
|
5
5
|
import { resolvePath } from "../paths.js";
|
|
6
6
|
import { resolve } from "../resolve.js";
|
|
7
7
|
import {} from "./verb.js";
|
|
8
8
|
import { diagnose } from "../product.js";
|
|
9
|
+
/**
|
|
10
|
+
* One line per held-back declaration, in site order: its reference and position, then
|
|
11
|
+
* each record's class, site and detail, all separated by tabs.
|
|
12
|
+
*/
|
|
13
|
+
function retainedLines(symbols, retained) {
|
|
14
|
+
const held = new Map();
|
|
15
|
+
for (const record of retained) {
|
|
16
|
+
held.set(record.id, [...(held.get(record.id) ?? []), record]);
|
|
17
|
+
}
|
|
18
|
+
return symbols.flatMap((symbol) => {
|
|
19
|
+
const records = held.get(symbol.id);
|
|
20
|
+
if (records === undefined) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const fields = records.flatMap((record) => [
|
|
24
|
+
record.class,
|
|
25
|
+
positionKey(record.site),
|
|
26
|
+
record.detail,
|
|
27
|
+
]);
|
|
28
|
+
return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
|
|
29
|
+
});
|
|
30
|
+
}
|
|
9
31
|
/**
|
|
10
32
|
* Writes every declaration an exemption held back in the production sweep a report is
|
|
11
33
|
* built from, one per line, and nothing where no exemption held one back. One fact
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/alias-chain.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where an import or export chain leads in one project: from one symbol to each
|
|
3
3
|
* declaration of the target it is, and along the chain of aliases it heads to each
|
|
4
|
-
* declaration every link of that chain is.
|
|
4
|
+
* declaration every link of that chain is, and what a name resolves to through them.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
isImportSpecifier,
|
|
13
13
|
isNamespaceExport,
|
|
14
14
|
isNamespaceImport,
|
|
15
|
+
type Node,
|
|
15
16
|
} from "@typescript/native/ast";
|
|
16
17
|
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
|
|
17
18
|
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
18
|
-
import { UNANSWERED, type Answer } from "./query.ts";
|
|
19
|
+
import { must, UNANSWERED, type Answer } from "./query.ts";
|
|
19
20
|
import type { ProjectView } from "./session.ts";
|
|
20
21
|
|
|
21
22
|
/** One declaration a chain reaches, and whether a step along an alias reached it. */
|
|
@@ -195,3 +196,38 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
195
196
|
nextDeclared,
|
|
196
197
|
};
|
|
197
198
|
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* What each of a batch of nodes resolves to, every alias followed to the declaration
|
|
202
|
+
* it stands for. A node the checker resolves to nothing, and an alias that resolves
|
|
203
|
+
* to nothing, are absent from the answer. Each alias is followed once. A question the
|
|
204
|
+
* checker does not answer stops the pass, as {@link must} stops.
|
|
205
|
+
*/
|
|
206
|
+
export function resolvedTargets<Brand>(
|
|
207
|
+
project: ProjectView<Brand>,
|
|
208
|
+
nodes: readonly Node[],
|
|
209
|
+
): ReadonlyMap<Node, TSSymbol> {
|
|
210
|
+
const followed = new Map<number, TSSymbol | undefined>();
|
|
211
|
+
const targets = new Map<Node, TSSymbol>();
|
|
212
|
+
project
|
|
213
|
+
.symbolsAt(nodes.map((node) => project.handle(node)))
|
|
214
|
+
.map(must)
|
|
215
|
+
.forEach((symbol, index) => {
|
|
216
|
+
const node = nodes[index];
|
|
217
|
+
if (symbol === undefined || node === undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let target: TSSymbol | undefined = symbol;
|
|
221
|
+
if ((symbol.flags & SymbolFlags.Alias) !== 0) {
|
|
222
|
+
if (!followed.has(symbol.id)) {
|
|
223
|
+
const aliased = must(project.queries.aliased(symbol));
|
|
224
|
+
followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
|
|
225
|
+
}
|
|
226
|
+
target = followed.get(symbol.id);
|
|
227
|
+
}
|
|
228
|
+
if (target !== undefined) {
|
|
229
|
+
targets.set(node, target);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return targets;
|
|
233
|
+
}
|