@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3
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/analysis.js +95 -60
- package/dist/src/augmentations.js +14 -12
- package/dist/src/component-layer.js +1 -1
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +230 -30
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +104 -13
- package/dist/src/convention-rows.js +10 -0
- package/dist/src/conventions.js +35 -1
- package/dist/src/dependencies.js +90 -32
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/element-access.js +341 -0
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +78 -16
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -5
- package/dist/src/findings/self-check.js +27 -10
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -119
- package/dist/src/intra-function-parts.js +25 -11
- package/dist/src/inventory.js +28 -12
- package/dist/src/keyof-comparisons.js +170 -0
- package/dist/src/kinds.js +17 -7
- package/dist/src/manifest.js +6 -16
- package/dist/src/matrix.js +20 -2
- package/dist/src/query.js +3 -0
- package/dist/src/references.js +14 -3
- package/dist/src/report.js +4 -1
- package/dist/src/reporters/sarif.js +4 -0
- package/dist/src/roots.js +0 -1
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/serialization-contract.js +43 -14
- package/dist/src/session.js +12 -8
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +14 -5
- package/dist/src/suppress.js +64 -35
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-reach.js +7 -7
- package/dist/src/verbs/analyze.js +11 -9
- package/dist/src/workspace-programs.js +0 -3
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/analysis.ts +145 -82
- package/src/augmentations.ts +14 -11
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +104 -22
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +250 -31
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +135 -17
- package/src/convention-rows.ts +23 -0
- package/src/conventions.ts +53 -1
- package/src/dependencies.ts +119 -38
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/element-access.ts +446 -0
- package/src/entry-points.ts +1 -1
- package/src/erased-stores.ts +413 -0
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +3 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +97 -21
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +4 -9
- package/src/findings/self-check.ts +31 -14
- package/src/findings-pass.ts +14 -7
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +303 -137
- package/src/intra-function-parts.ts +41 -16
- package/src/inventory.ts +37 -33
- package/src/json-document.ts +1 -1
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +7 -22
- package/src/matrix.ts +34 -2
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/query.ts +10 -0
- package/src/references.ts +30 -7
- package/src/report.ts +11 -5
- package/src/reporters/sarif.ts +4 -0
- package/src/roots.ts +0 -3
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/serialization-contract.ts +53 -14
- package/src/session.ts +34 -31
- package/src/stores.ts +1 -1
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +17 -7
- package/src/suppress.ts +79 -47
- package/src/template-field.ts +1 -1
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-reach.ts +12 -8
- package/src/verbs/analyze.ts +15 -9
- package/src/verbs/verb.ts +1 -4
- package/src/workspace-programs.ts +2 -9
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The members read where code the target cannot see receives a value of a target type. An
|
|
3
|
+
* object literal argument of a call or a `new` whose callee has no implementation in the
|
|
4
|
+
* target has every member of the parameter type it is checked against read, nested
|
|
5
|
+
* literals included. An `as` expression over an object no file of the project declares, and
|
|
6
|
+
* a `new` through an unimplemented construct signature, hand the target a value the
|
|
7
|
+
* outside code holds, so every member of the type it is given is read.
|
|
8
|
+
*/
|
|
9
|
+
import { isArrowFunction, isAsExpression, isCallExpression, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isSatisfiesExpression, isTypeReferenceNode, isVariableDeclaration, } from "@typescript/native/unstable/ast";
|
|
10
|
+
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
11
|
+
import { nodeKey } from "./inventory.js";
|
|
12
|
+
import { renderPosition } from "./position.js";
|
|
13
|
+
import { UNANSWERED } from "./query.js";
|
|
14
|
+
/** The value an `as` expression asserts a type of, through every `as` and parenthesis around it. */
|
|
15
|
+
function castOperand(node) {
|
|
16
|
+
let at = node;
|
|
17
|
+
while (isAsExpression(at) || isParenthesizedExpression(at)) {
|
|
18
|
+
at = at.expression;
|
|
19
|
+
}
|
|
20
|
+
return at;
|
|
21
|
+
}
|
|
22
|
+
/** Whether a declaration node carries a body of statements or an expression. */
|
|
23
|
+
function hasBody(node) {
|
|
24
|
+
return node.body !== undefined;
|
|
25
|
+
}
|
|
26
|
+
function isFunctionLike(node) {
|
|
27
|
+
return (node !== undefined &&
|
|
28
|
+
(isArrowFunction(node) ||
|
|
29
|
+
isFunctionExpression(node) ||
|
|
30
|
+
isFunctionDeclaration(node) ||
|
|
31
|
+
isClassExpression(node)));
|
|
32
|
+
}
|
|
33
|
+
/** The name a declaration is declared under, where it has one an identifier spells. */
|
|
34
|
+
function nameOf(node) {
|
|
35
|
+
const name = node.name;
|
|
36
|
+
return name !== undefined && isIdentifier(name) ? name.text : undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether an overload signature has its implementation beside it: a declaration of the same
|
|
40
|
+
* name and kind, in the same statement list or class body, that carries a body.
|
|
41
|
+
*/
|
|
42
|
+
function overloadImplemented(node) {
|
|
43
|
+
if (!isFunctionDeclaration(node) && !isMethodDeclaration(node)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const name = nameOf(node);
|
|
47
|
+
let implemented = false;
|
|
48
|
+
node.parent.forEachChild((sibling) => {
|
|
49
|
+
implemented ||= sibling.kind === node.kind && nameOf(sibling) === name && hasBody(sibling);
|
|
50
|
+
});
|
|
51
|
+
return implemented;
|
|
52
|
+
}
|
|
53
|
+
/** Every object literal one argument holds, the argument itself included, through nested property values. */
|
|
54
|
+
function literalsOf(argument) {
|
|
55
|
+
if (!isObjectLiteralExpression(argument)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
argument,
|
|
60
|
+
...argument.properties.flatMap((property) => isPropertyAssignment(property) ? literalsOf(property.initializer) : []),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The reads the object-literal arguments of one project's own files make of the members of
|
|
65
|
+
* the types they are checked against, where the callee has no implementation in the target.
|
|
66
|
+
*/
|
|
67
|
+
export function unimplementedCalleeReads(project, held, chains, root, testFiles) {
|
|
68
|
+
const queries = project.queries;
|
|
69
|
+
const own = project.ownPaths();
|
|
70
|
+
const passing = [];
|
|
71
|
+
const implementing = [];
|
|
72
|
+
const asserted = [];
|
|
73
|
+
const constructed = [];
|
|
74
|
+
const handing = [];
|
|
75
|
+
for (const file of project.ownSourceFiles()) {
|
|
76
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
77
|
+
if (fileId === undefined) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const visit = (node, from) => {
|
|
81
|
+
const holder = held.declarations.get(nodeKey(file, node)) ?? from;
|
|
82
|
+
if ((isCallExpression(node) || isNewExpression(node)) &&
|
|
83
|
+
(node.arguments ?? []).some((argument) => isObjectLiteralExpression(argument))) {
|
|
84
|
+
passing.push({ file, from: holder, call: node });
|
|
85
|
+
}
|
|
86
|
+
if (isNewExpression(node)) {
|
|
87
|
+
constructed.push({ file, from: holder, call: node });
|
|
88
|
+
}
|
|
89
|
+
if ((isCallExpression(node) || isNewExpression(node)) &&
|
|
90
|
+
(node.arguments ?? []).some((argument) => isFunctionLike(argument) || isIdentifier(argument))) {
|
|
91
|
+
handing.push(node);
|
|
92
|
+
}
|
|
93
|
+
if (isAsExpression(node) && !isAsExpression(node.parent)) {
|
|
94
|
+
asserted.push({ file, from: holder, node, operand: castOperand(node) });
|
|
95
|
+
}
|
|
96
|
+
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
97
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
98
|
+
if (isHeritageClause(clause)) {
|
|
99
|
+
implementing.push(...clause.types.map((one) => isTypeReferenceNode(one) ? one.typeName : one.expression));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// An object literal typed as an interface implements that interface's methods.
|
|
104
|
+
if (isVariableDeclaration(node) &&
|
|
105
|
+
node.type !== undefined &&
|
|
106
|
+
isTypeReferenceNode(node.type) &&
|
|
107
|
+
node.initializer !== undefined &&
|
|
108
|
+
isObjectLiteralExpression(node.initializer)) {
|
|
109
|
+
implementing.push(node.type.typeName);
|
|
110
|
+
}
|
|
111
|
+
if ((isSatisfiesExpression(node) || isAsExpression(node)) &&
|
|
112
|
+
isObjectLiteralExpression(node.expression) &&
|
|
113
|
+
isTypeReferenceNode(node.type)) {
|
|
114
|
+
implementing.push(node.type.typeName);
|
|
115
|
+
}
|
|
116
|
+
node.forEachChild((child) => {
|
|
117
|
+
visit(child, holder);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
file.forEachChild((child) => {
|
|
121
|
+
visit(child, fileId);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (passing.length === 0 && asserted.length === 0 && constructed.length === 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const resolved = (symbol) => {
|
|
128
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
132
|
+
return symbol;
|
|
133
|
+
}
|
|
134
|
+
const target = queries.aliased(symbol);
|
|
135
|
+
return target === UNANSWERED ? undefined : target;
|
|
136
|
+
};
|
|
137
|
+
const declarationsOf = (symbol) => (symbol?.declarations ?? []).flatMap((handle) => {
|
|
138
|
+
const node = project.declarationAt(handle)?.node;
|
|
139
|
+
return node === undefined ? [] : [node];
|
|
140
|
+
});
|
|
141
|
+
const implemented = new Set(queries.symbolsAt(implementing).flatMap((symbol) => declarationsOf(resolved(symbol))));
|
|
142
|
+
/** Whether a declaration is a function or a class of the target, or a variable or property it initializes. */
|
|
143
|
+
const ownFunction = (node) => own.has(node.getSourceFile().fileName) &&
|
|
144
|
+
(isFunctionLike(node) ||
|
|
145
|
+
((isVariableDeclaration(node) || isPropertyAssignment(node) || isPropertyDeclaration(node)) &&
|
|
146
|
+
isFunctionLike(node.initializer)));
|
|
147
|
+
/**
|
|
148
|
+
* Whether a function or a class of the target reaches one parameter of a function of the
|
|
149
|
+
* target, passed for it by a call that names the function. A call through another name
|
|
150
|
+
* is not followed, so its callee keeps no implementation and its literal reads more.
|
|
151
|
+
*/
|
|
152
|
+
const reachedParameters = new Map();
|
|
153
|
+
const parameterReached = (parameter) => {
|
|
154
|
+
const known = reachedParameters.get(parameter);
|
|
155
|
+
if (known !== undefined) {
|
|
156
|
+
return known;
|
|
157
|
+
}
|
|
158
|
+
const fn = parameter.parent;
|
|
159
|
+
const fnName = nameOf(fn) ?? (isVariableDeclaration(fn.parent) ? nameOf(fn.parent) : undefined);
|
|
160
|
+
const at = fn.parameters?.indexOf(parameter) ?? -1;
|
|
161
|
+
const passed = handing.flatMap((call) => {
|
|
162
|
+
const callee = call.expression;
|
|
163
|
+
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
164
|
+
if (fnName === undefined || !isIdentifier(name) || name.text !== fnName) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
const signature = queries.resolvedSignature(call);
|
|
168
|
+
const handle = signature === UNANSWERED || signature === undefined ? undefined : signature.declaration;
|
|
169
|
+
const argument = handle === undefined || project.declarationAt(handle)?.node !== fn
|
|
170
|
+
? undefined
|
|
171
|
+
: (call.arguments ?? [])[at];
|
|
172
|
+
return argument === undefined || at < 0 ? [] : [argument];
|
|
173
|
+
});
|
|
174
|
+
const named = passed.filter((argument) => isIdentifier(argument));
|
|
175
|
+
const reached = passed.some((argument) => isFunctionLike(argument)) ||
|
|
176
|
+
queries
|
|
177
|
+
.symbolsAt(named)
|
|
178
|
+
.some((symbol) => declarationsOf(resolved(symbol)).some((node) => ownFunction(node)));
|
|
179
|
+
reachedParameters.set(parameter, reached);
|
|
180
|
+
return reached;
|
|
181
|
+
};
|
|
182
|
+
/** Whether some function, class or object literal of the target stands for a signature. */
|
|
183
|
+
const standsFor = (passed) => {
|
|
184
|
+
const signature = queries.resolvedSignature(passed.call);
|
|
185
|
+
const handle = signature === UNANSWERED || signature === undefined ? undefined : signature.declaration;
|
|
186
|
+
const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
187
|
+
if (declaration === undefined) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (hasBody(declaration) || overloadImplemented(declaration)) {
|
|
191
|
+
return own.has(handle?.path ?? "");
|
|
192
|
+
}
|
|
193
|
+
const holder = declaration.parent;
|
|
194
|
+
if (holder !== undefined && implemented.has(holder)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const callee = passed.call.expression;
|
|
198
|
+
const name = isPropertyAccessExpression(callee) ? callee.name : callee;
|
|
199
|
+
const [named] = queries.symbolsAt([name]);
|
|
200
|
+
return declarationsOf(resolved(named)).some((node) => isParameterDeclaration(node)
|
|
201
|
+
? own.has(node.getSourceFile().fileName) && parameterReached(node)
|
|
202
|
+
: ownFunction(node) && !isFunctionLike(node));
|
|
203
|
+
};
|
|
204
|
+
const literals = [];
|
|
205
|
+
for (const passed of passing) {
|
|
206
|
+
if (standsFor(passed)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
for (const argument of passed.call.arguments ?? []) {
|
|
210
|
+
literals.push(...literalsOf(argument).map((literal) => ({ passed, literal })));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const contexts = queries.contextualTypes(literals.map((one) => one.literal));
|
|
214
|
+
const found = [];
|
|
215
|
+
literals.forEach(({ passed, literal }, at) => {
|
|
216
|
+
const context = contexts[at];
|
|
217
|
+
if (context === UNANSWERED || context === undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const parts = queries.constituents(context);
|
|
221
|
+
const position = renderPosition(passed.file, root, literal.getStart());
|
|
222
|
+
for (const part of parts === UNANSWERED ? [context] : parts) {
|
|
223
|
+
const properties = queries.propertiesOf(part);
|
|
224
|
+
for (const property of properties === UNANSWERED ? [] : properties) {
|
|
225
|
+
for (const to of chains.declarationsOf(property)) {
|
|
226
|
+
found.push({
|
|
227
|
+
from: passed.from,
|
|
228
|
+
to,
|
|
229
|
+
position,
|
|
230
|
+
use: "read",
|
|
231
|
+
resolution: "contextual",
|
|
232
|
+
test: testFiles.has(position.path),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
found.push(...handedOver());
|
|
239
|
+
return found;
|
|
240
|
+
/** Whether a type is an object no file of the project declares, a platform object's among them. */
|
|
241
|
+
function outsideValue(type) {
|
|
242
|
+
if (!type.isObjectType()) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
const symbol = queries.symbolOfType(type);
|
|
246
|
+
const declared = symbol === UNANSWERED ? undefined : symbol;
|
|
247
|
+
return declared?.declarations.every((handle) => !own.has(handle.path)) === true;
|
|
248
|
+
}
|
|
249
|
+
/** Whether a type is an object type a file of the project declares. */
|
|
250
|
+
function ownObject(type) {
|
|
251
|
+
const known = type === UNANSWERED ? undefined : type;
|
|
252
|
+
if (!known?.isObjectType()) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const symbol = queries.symbolOfType(known);
|
|
256
|
+
const declared = symbol === UNANSWERED ? undefined : symbol;
|
|
257
|
+
return declared?.declarations.some((handle) => own.has(handle.path)) === true;
|
|
258
|
+
}
|
|
259
|
+
/** The reads of every member of each target type an outside value is handed over as. */
|
|
260
|
+
function handedOver() {
|
|
261
|
+
const operandTypes = queries.typesAt(asserted.map((one) => one.operand));
|
|
262
|
+
const fromCasts = asserted.filter((_one, at) => {
|
|
263
|
+
const type = operandTypes[at];
|
|
264
|
+
return type !== UNANSWERED && type !== undefined && outsideValue(type);
|
|
265
|
+
});
|
|
266
|
+
const newTypes = queries.typesAt(constructed.map((one) => one.call));
|
|
267
|
+
// A construct signature whose declared result is one of its type parameters names no
|
|
268
|
+
// type the target declares, so only a result the signature itself names is handed over.
|
|
269
|
+
const fromConstructors = constructed
|
|
270
|
+
.filter((one, at) => {
|
|
271
|
+
if (!ownObject(newTypes[at]) || standsFor(one)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const signature = queries.resolvedSignature(one.call);
|
|
275
|
+
const result = signature === UNANSWERED || signature === undefined
|
|
276
|
+
? undefined
|
|
277
|
+
: queries.returnTypeOf(signature);
|
|
278
|
+
return result !== UNANSWERED && result !== undefined && !result.isTypeParameter();
|
|
279
|
+
})
|
|
280
|
+
.map((one) => ({ file: one.file, from: one.from, node: one.call }));
|
|
281
|
+
const handed = [...fromCasts, ...fromConstructors];
|
|
282
|
+
const types = queries.typesAt(handed.map((one) => one.node));
|
|
283
|
+
const reads = [];
|
|
284
|
+
handed.forEach((one, at) => {
|
|
285
|
+
const type = types[at];
|
|
286
|
+
if (type === UNANSWERED || type === undefined) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const position = renderPosition(one.file, root, one.node.getStart());
|
|
290
|
+
const parts = queries.constituents(type);
|
|
291
|
+
for (const part of parts === UNANSWERED ? [type] : parts) {
|
|
292
|
+
if (!ownObject(part)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const properties = queries.propertiesOf(part);
|
|
296
|
+
for (const property of properties === UNANSWERED ? [] : properties) {
|
|
297
|
+
for (const to of chains.declarationsOf(property)) {
|
|
298
|
+
reads.push({
|
|
299
|
+
from: one.from,
|
|
300
|
+
to,
|
|
301
|
+
position,
|
|
302
|
+
use: "read",
|
|
303
|
+
resolution: "contextual",
|
|
304
|
+
test: testFiles.has(position.path),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
return reads;
|
|
311
|
+
}
|
|
312
|
+
}
|
package/dist/src/value-reach.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The classes of the target a value of one type carries, and the members of the other
|
|
3
|
-
* object types the target writes that it carries, as a serializer walking the value
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* instance held only there is missed.
|
|
3
|
+
* object types the target writes that it carries, as a serializer walking the value reaches
|
|
4
|
+
* them: through constituents, array and tuple elements, the data members and index
|
|
5
|
+
* signatures of the types the target writes, and the values `stored` gives for a type where
|
|
6
|
+
* it erases them. The walk stops at `any`, `unknown`, a type parameter, a function and an
|
|
7
|
+
* interface or class of another program, so an instance held only there is missed.
|
|
9
8
|
*/
|
|
10
9
|
import { ObjectFlags, SymbolFlags, } from "@typescript/native/unstable/sync";
|
|
11
10
|
import { classNames, typesAt } from "./class-members.js";
|
|
@@ -28,7 +27,7 @@ function arrayTargets(project) {
|
|
|
28
27
|
* The walk over one project's types, against the classes of `held`, the same
|
|
29
28
|
* project's inventory.
|
|
30
29
|
*/
|
|
31
|
-
export function valueReach(project, held) {
|
|
30
|
+
export function valueReach(project, held, stored = () => []) {
|
|
32
31
|
const queries = project.queries;
|
|
33
32
|
const names = [...classNames(project, held)];
|
|
34
33
|
const declared = typesAt(project, names.map(([, node]) => node));
|
|
@@ -110,6 +109,7 @@ export function valueReach(project, held) {
|
|
|
110
109
|
found = must(queries.typeArguments(type));
|
|
111
110
|
}
|
|
112
111
|
}
|
|
112
|
+
found = [...found, ...stored(type)];
|
|
113
113
|
children.set(type.id, found);
|
|
114
114
|
return found;
|
|
115
115
|
};
|
|
@@ -10,7 +10,7 @@ import { json } from "../reporters/json.js";
|
|
|
10
10
|
import { RENDERINGS } from "../reporters/reporters.js";
|
|
11
11
|
import { parseTemplate, TemplateError } from "../reporters/template.js";
|
|
12
12
|
import { resolve } from "../resolve.js";
|
|
13
|
-
import { rowReason, writeBaseline } from "../suppress-file.js";
|
|
13
|
+
import { otherSchemeRows, rowReason, writeBaseline } from "../suppress-file.js";
|
|
14
14
|
import { CONTRACT_VERSION } from "../version.js";
|
|
15
15
|
import { EXIT_CLEAN, EXIT_FAILURE } from "./verb.js";
|
|
16
16
|
/** The options `analyze` takes beside the ones every verb takes. */
|
|
@@ -116,13 +116,12 @@ function templateOf(host, invoked, named, formats) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
* Analyzes the scope
|
|
120
|
-
* `--
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* under `--exit-code=off`, and a rendering that cannot be written ends the run with 3.
|
|
119
|
+
* Analyzes the scope and writes the JSON report to the path `--report` names, each
|
|
120
|
+
* `--format` rendering beside it with the format's suffix appended, and under
|
|
121
|
+
* `--baseline-write` the baseline a fixpoint over the analysis records, the report being its
|
|
122
|
+
* first round. Diagnostics go to the error stream and nothing to the output stream; the
|
|
123
|
+
* verdict is the exit code, or 0 under `--exit-code=off`, and a rendering that cannot be
|
|
124
|
+
* written ends the run with 3.
|
|
126
125
|
*/
|
|
127
126
|
export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repeated }) => {
|
|
128
127
|
const reportPath = option("report");
|
|
@@ -227,7 +226,10 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
227
226
|
}
|
|
228
227
|
}
|
|
229
228
|
if (documents.baseline !== undefined) {
|
|
230
|
-
host.
|
|
229
|
+
const kept = host.kindOf(documents.baseline) === "file"
|
|
230
|
+
? otherSchemeRows(host.readFile(documents.baseline))
|
|
231
|
+
: [];
|
|
232
|
+
host.writeDocument(documents.baseline, writeBaseline(recorded, analyzerProvenance(version), kept));
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
235
|
catch (error) {
|
|
@@ -205,8 +205,6 @@ function scan(engine, program, reading, resolver) {
|
|
|
205
205
|
reading.unresolved.push({
|
|
206
206
|
file: usage.file,
|
|
207
207
|
specifier,
|
|
208
|
-
member: answer.member,
|
|
209
|
-
subpath: answer.subpath,
|
|
210
208
|
reason: answer.reason,
|
|
211
209
|
});
|
|
212
210
|
}
|
|
@@ -309,7 +307,6 @@ export function workspacePrograms(engine, host, configFiles, resolver) {
|
|
|
309
307
|
readings: readings.map((reading) => ({
|
|
310
308
|
configFile: reading.configFile,
|
|
311
309
|
fileNames: reading.parsed.fileNames,
|
|
312
|
-
entries: reading.entries,
|
|
313
310
|
unresolved: reading.unresolved,
|
|
314
311
|
componentsUnread: unread.get(reading.configFile),
|
|
315
312
|
})),
|
|
@@ -259,7 +259,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
259
259
|
return {
|
|
260
260
|
kind: "unresolved",
|
|
261
261
|
member: name,
|
|
262
|
-
subpath,
|
|
263
262
|
reason: `${String(linked.dirs.length)} workspace packages are named ${name} (${linked.dirs.join(", ")}) and nothing links the importer to one of them; install the workspace, or declare the dependency with a range only one of them satisfies`,
|
|
264
263
|
};
|
|
265
264
|
}
|
|
@@ -295,7 +294,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
295
294
|
return {
|
|
296
295
|
kind: "unresolved",
|
|
297
296
|
member: name,
|
|
298
|
-
subpath,
|
|
299
297
|
reason: built
|
|
300
298
|
? `its manifest names ${named}, output no tsconfig*.json of the package maps back to source, and nothing installed links the importer to it; install the workspace, or point an exports condition the importing configuration's customConditions lists at its source`
|
|
301
299
|
: member.configurations.length === 0
|
package/dist/src/workspace.js
CHANGED
|
@@ -256,7 +256,6 @@ function configurationsOf(host, dir, parse) {
|
|
|
256
256
|
? []
|
|
257
257
|
: [
|
|
258
258
|
{
|
|
259
|
-
configFile,
|
|
260
259
|
fileNames: new Set(parsed.fileNames),
|
|
261
260
|
layout: layoutOf(configFile, parsed),
|
|
262
261
|
},
|
|
@@ -338,8 +337,6 @@ export function findWorkspace(host, targetRoot, parse) {
|
|
|
338
337
|
return found;
|
|
339
338
|
};
|
|
340
339
|
return {
|
|
341
|
-
root: declared.root,
|
|
342
|
-
declaredIn: declared.file,
|
|
343
340
|
members,
|
|
344
341
|
named,
|
|
345
342
|
memberOf: (path) => {
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"typecheck:tests": "tsc -p tsconfig.test.json",
|
|
25
25
|
"test": "vitest --run",
|
|
26
26
|
"test:starters": "vitest --run --config starters/vitest.config.ts",
|
|
27
|
+
"test:mutation": "stryker run",
|
|
27
28
|
"lint": "eslint .",
|
|
28
29
|
"lint:prettier": "prettier --check .",
|
|
29
30
|
"lint:fix": "eslint . --fix && prettier --write ."
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"fast-check": "4.10.2",
|
|
45
46
|
"prettier": "3.9.9",
|
|
46
47
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
47
|
-
"typescript-eslint": "8.71.
|
|
48
|
+
"typescript-eslint": "8.71.1",
|
|
48
49
|
"vitest": "5.0.3"
|
|
49
50
|
},
|
|
50
51
|
"publishConfig": {
|