@cplieger/deadset-ts 5.0.0 → 5.1.0
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 +77 -10
- package/dist/src/configuration-files.js +107 -3
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +240 -0
- package/dist/src/convention-rows.js +6 -0
- package/dist/src/conventions.js +123 -14
- package/dist/src/dependencies.js +8 -2
- package/dist/src/destructuring.js +6 -6
- package/dist/src/entry-points.js +8 -5
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +21 -17
- package/dist/src/findings/completion.js +27 -3
- package/dist/src/findings/interfaces.js +6 -2
- package/dist/src/findings/reads-and-writes.js +53 -9
- package/dist/src/findings/unused-declarations.js +40 -1
- package/dist/src/generated-file.js +24 -0
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +4 -1
- package/dist/src/kinds.js +4 -3
- package/dist/src/query.js +15 -0
- package/dist/src/references.js +199 -10
- package/dist/src/resolve.js +12 -11
- package/dist/src/roots.js +1 -1
- package/dist/src/stores.js +3 -0
- package/dist/src/value-flow.js +74 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +92 -13
- package/src/configuration-files.ts +136 -1
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +354 -0
- package/src/convention-rows.ts +8 -0
- package/src/conventions.ts +161 -13
- package/src/dependencies.ts +14 -1
- package/src/destructuring.ts +16 -6
- package/src/entry-points.ts +16 -3
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +48 -17
- package/src/file-facts.ts +2 -0
- package/src/findings/completion.ts +31 -3
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +51 -1
- package/src/generated-file.ts +27 -0
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +4 -1
- package/src/kinds.ts +5 -3
- package/src/query.ts +26 -0
- package/src/references.ts +254 -21
- package/src/resolve.ts +14 -13
- package/src/roots.ts +11 -1
- package/src/stores.ts +9 -0
- package/src/value-flow.ts +104 -0
- package/src/version.ts +1 -1
package/src/resolve.ts
CHANGED
|
@@ -262,18 +262,19 @@ function readStrings<T extends string>(
|
|
|
262
262
|
);
|
|
263
263
|
}
|
|
264
264
|
const seen = new Set<string>();
|
|
265
|
-
for (const entry of value as unknown[]) {
|
|
265
|
+
for (const [index, entry] of (value as unknown[]).entries()) {
|
|
266
|
+
const at = `${path}[${String(index)}]`;
|
|
266
267
|
if (typeof entry !== "string") {
|
|
267
|
-
throw malformed(label,
|
|
268
|
+
throw malformed(label, at, "is not a string");
|
|
268
269
|
}
|
|
269
270
|
if (entry === "") {
|
|
270
|
-
throw malformed(label,
|
|
271
|
+
throw malformed(label, at, "is empty");
|
|
271
272
|
}
|
|
272
273
|
if (seen.has(entry)) {
|
|
273
|
-
throw malformed(label,
|
|
274
|
+
throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
|
|
274
275
|
}
|
|
275
276
|
if (allowed !== undefined && !(allowed as readonly string[]).includes(entry)) {
|
|
276
|
-
throw malformed(label,
|
|
277
|
+
throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
|
|
277
278
|
}
|
|
278
279
|
seen.add(entry);
|
|
279
280
|
}
|
|
@@ -288,12 +289,12 @@ function readComponentExtensions(
|
|
|
288
289
|
label: string,
|
|
289
290
|
): readonly string[] | undefined {
|
|
290
291
|
const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
|
|
291
|
-
const wrong = extensions?.
|
|
292
|
-
if (wrong !==
|
|
292
|
+
const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
|
|
293
|
+
if (extensions !== undefined && wrong !== -1) {
|
|
293
294
|
throw malformed(
|
|
294
295
|
label,
|
|
295
|
-
|
|
296
|
-
`${JSON.stringify(wrong)} is not a full stop followed by letters and digits`,
|
|
296
|
+
`ts.component_extensions[${String(wrong)}]`,
|
|
297
|
+
`${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`,
|
|
297
298
|
);
|
|
298
299
|
}
|
|
299
300
|
return extensions;
|
|
@@ -307,12 +308,12 @@ function readDisabledConventions(
|
|
|
307
308
|
label: string,
|
|
308
309
|
): readonly string[] | undefined {
|
|
309
310
|
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
310
|
-
const unknown = names?.
|
|
311
|
-
if (unknown !==
|
|
311
|
+
const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
|
|
312
|
+
if (names !== undefined && unknown !== -1) {
|
|
312
313
|
throw malformed(
|
|
313
314
|
label,
|
|
314
|
-
|
|
315
|
-
`${JSON.stringify(unknown)} names no convention row this analyzer carries`,
|
|
315
|
+
`ts.disabled_conventions[${String(unknown)}]`,
|
|
316
|
+
`${JSON.stringify(names[unknown])} names no convention row this analyzer carries`,
|
|
316
317
|
);
|
|
317
318
|
}
|
|
318
319
|
return names;
|
package/src/roots.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { SourceFile } from "@typescript/native/unstable/ast";
|
|
12
12
|
import { aliasChains } from "./alias-chain.ts";
|
|
13
|
+
import type { ParseFile } from "./configuration-files.ts";
|
|
13
14
|
import { conventionEntries, type Conventions } from "./conventions.ts";
|
|
14
15
|
import { entryPoints, type EntryRule } from "./entry-points.ts";
|
|
15
16
|
import { globExpression } from "./glob.ts";
|
|
@@ -112,6 +113,8 @@ export interface RootOptions {
|
|
|
112
113
|
readonly conventions?: Conventions | undefined;
|
|
113
114
|
/** `ts.test_files` from the resolved configuration. */
|
|
114
115
|
readonly testFiles: readonly string[];
|
|
116
|
+
/** The parser the configuration files no program holds are read with, where they are read. */
|
|
117
|
+
readonly parse?: ParseFile | undefined;
|
|
115
118
|
/** Whether the target is a library, so a consumer outside it reaches its published API. */
|
|
116
119
|
readonly publishedAPI: boolean;
|
|
117
120
|
}
|
|
@@ -268,7 +271,14 @@ export function roots<Brand>(
|
|
|
268
271
|
: conventionEntries(options.conventions, files)) {
|
|
269
272
|
enter(file, "convention", row, true);
|
|
270
273
|
}
|
|
271
|
-
for (const point of entryPoints(
|
|
274
|
+
for (const point of entryPoints(
|
|
275
|
+
project,
|
|
276
|
+
files,
|
|
277
|
+
options.testFiles,
|
|
278
|
+
options.host,
|
|
279
|
+
targetRoot,
|
|
280
|
+
options.parse,
|
|
281
|
+
)) {
|
|
272
282
|
enter(point.file, point.rule, point.source, point.exports);
|
|
273
283
|
}
|
|
274
284
|
for (const file of files.byPath.values()) {
|
package/src/stores.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface Stores {
|
|
|
22
22
|
* configuration. A production mode counts none a test file made.
|
|
23
23
|
*/
|
|
24
24
|
readonly references: readonly Reference[];
|
|
25
|
+
/** The declarations a test file reads, which a production mode counts no read of. */
|
|
26
|
+
readonly readInTests: ReadonlySet<string>;
|
|
25
27
|
/**
|
|
26
28
|
* The class members declared by a getter or a setter. A store into one runs the
|
|
27
29
|
* setter, which is code rather than state.
|
|
@@ -71,6 +73,13 @@ export function storesOf(
|
|
|
71
73
|
references: per.flatMap((one) =>
|
|
72
74
|
one.references.filter((reference) => !mode.production || !reference.test),
|
|
73
75
|
),
|
|
76
|
+
readInTests: new Set(
|
|
77
|
+
per.flatMap((one) =>
|
|
78
|
+
one.references
|
|
79
|
+
.filter((reference) => reference.test && reference.use !== "write")
|
|
80
|
+
.map((reference) => reference.to),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
74
83
|
accessors: new Set(per.flatMap((one) => one.accessors)),
|
|
75
84
|
exempt: new Set(exempt.map((record) => record.id)),
|
|
76
85
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The values a program hands to a position the checker types from its context: the
|
|
3
|
+
* initializer of a declaration whose type is written, an assignment's right side, an
|
|
4
|
+
* argument, a returned value, an arrow function's expression body, an array element, an
|
|
5
|
+
* object property, and the operand of `as` or `satisfies`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isArrayLiteralExpression,
|
|
10
|
+
isArrowFunction,
|
|
11
|
+
isAsExpression,
|
|
12
|
+
isBinaryExpression,
|
|
13
|
+
isCallExpression,
|
|
14
|
+
isConditionalExpression,
|
|
15
|
+
isIdentifier,
|
|
16
|
+
isNewExpression,
|
|
17
|
+
isParameterDeclaration,
|
|
18
|
+
isParenthesizedExpression,
|
|
19
|
+
isPropertyAssignment,
|
|
20
|
+
isPropertyDeclaration,
|
|
21
|
+
isReturnStatement,
|
|
22
|
+
isSatisfiesExpression,
|
|
23
|
+
isShorthandPropertyAssignment,
|
|
24
|
+
isSpreadElement,
|
|
25
|
+
isVariableDeclaration,
|
|
26
|
+
SyntaxKind,
|
|
27
|
+
type Expression,
|
|
28
|
+
type Node,
|
|
29
|
+
} from "@typescript/native/unstable/ast";
|
|
30
|
+
|
|
31
|
+
/** The operators of an assignment whose right side is stored into the left side's type. */
|
|
32
|
+
const STORES: ReadonlySet<SyntaxKind> = new Set([
|
|
33
|
+
SyntaxKind.EqualsToken,
|
|
34
|
+
SyntaxKind.QuestionQuestionEqualsToken,
|
|
35
|
+
SyntaxKind.BarBarEqualsToken,
|
|
36
|
+
SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** Whether a declaration writes its type, so its initializer is read in that type's context. */
|
|
40
|
+
function writesType(node: Node): boolean {
|
|
41
|
+
return (node as { readonly type?: Node }).type !== undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The values one written expression hands on: the expression itself, or what it
|
|
46
|
+
* evaluates to through parentheses, a conditional and the operators whose result is
|
|
47
|
+
* one of their operands.
|
|
48
|
+
*/
|
|
49
|
+
export function valuesOf(expression: Expression): readonly Expression[] {
|
|
50
|
+
if (isParenthesizedExpression(expression)) {
|
|
51
|
+
return valuesOf(expression.expression);
|
|
52
|
+
}
|
|
53
|
+
if (isConditionalExpression(expression)) {
|
|
54
|
+
return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
|
|
55
|
+
}
|
|
56
|
+
if (isBinaryExpression(expression)) {
|
|
57
|
+
const operator = expression.operatorToken.kind;
|
|
58
|
+
if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
|
|
59
|
+
return [...valuesOf(expression.left), ...valuesOf(expression.right)];
|
|
60
|
+
}
|
|
61
|
+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
|
|
62
|
+
return valuesOf(expression.right);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [expression];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The expressions one node hands to a position typed by its context. */
|
|
69
|
+
export function handedOn(node: Node): readonly Expression[] {
|
|
70
|
+
if (
|
|
71
|
+
(isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
|
|
72
|
+
node.initializer !== undefined &&
|
|
73
|
+
writesType(node)
|
|
74
|
+
) {
|
|
75
|
+
return [node.initializer];
|
|
76
|
+
}
|
|
77
|
+
if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
|
|
78
|
+
return [node.right];
|
|
79
|
+
}
|
|
80
|
+
if (isCallExpression(node) || isNewExpression(node)) {
|
|
81
|
+
return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
|
|
82
|
+
}
|
|
83
|
+
if (isReturnStatement(node)) {
|
|
84
|
+
return node.expression === undefined ? [] : [node.expression];
|
|
85
|
+
}
|
|
86
|
+
if (isArrowFunction(node)) {
|
|
87
|
+
return node.body.kind === SyntaxKind.Block ? [] : [node.body];
|
|
88
|
+
}
|
|
89
|
+
if (isArrayLiteralExpression(node)) {
|
|
90
|
+
return node.elements.filter(
|
|
91
|
+
(element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (isPropertyAssignment(node)) {
|
|
95
|
+
return [node.initializer];
|
|
96
|
+
}
|
|
97
|
+
if (isShorthandPropertyAssignment(node)) {
|
|
98
|
+
return isIdentifier(node.name) ? [node.name] : [];
|
|
99
|
+
}
|
|
100
|
+
if (isAsExpression(node) || isSatisfiesExpression(node)) {
|
|
101
|
+
return [node.expression];
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
104
|
+
}
|
package/src/version.ts
CHANGED