@cplieger/deadset-ts 7.0.0-dev.2 → 7.0.0-dev.4
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/README.md +1 -1
- package/dist/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +89 -44
- package/dist/src/augmentations.js +14 -12
- package/dist/src/calls.js +2 -2
- package/dist/src/class-members.js +1 -1
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +6 -6
- package/dist/src/configured-declarations.js +2 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +71 -14
- package/dist/src/convention-rows.js +6 -2
- package/dist/src/conventions.js +4 -2
- package/dist/src/dependencies.js +81 -27
- package/dist/src/deprecation.js +1 -1
- package/dist/src/destructuring.js +2 -2
- package/dist/src/discover.js +1 -1
- package/dist/src/element-access.js +341 -0
- package/dist/src/entry-points.js +2 -2
- package/dist/src/enum-group.js +2 -2
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/findings/completion.js +46 -15
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/self-check.js +17 -4
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/framework-lifecycle.js +1 -1
- package/dist/src/html-entries.js +1 -1
- package/dist/src/implementations.js +2 -2
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -125
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +2 -2
- package/dist/src/intra-function-parts.js +26 -12
- package/dist/src/inventory.js +26 -1
- 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/member-names.js +1 -1
- package/dist/src/module-declarations.js +1 -1
- package/dist/src/namespace-objects.js +1 -1
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +2 -2
- package/dist/src/query.js +4 -1
- package/dist/src/references.js +13 -2
- package/dist/src/reflective-lookup.js +2 -2
- package/dist/src/report.js +1 -1
- package/dist/src/reporters/sarif.js +4 -0
- package/dist/src/roots.js +1 -1
- package/dist/src/serialization-contract.js +45 -16
- package/dist/src/session.js +15 -8
- package/dist/src/signatures.js +1 -1
- package/dist/src/stores.js +1 -1
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +14 -5
- package/dist/src/suppress.js +54 -24
- package/dist/src/symbol-keys.js +1 -1
- package/dist/src/type-errors.js +1 -1
- package/dist/src/type-query-alias.js +1 -1
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-flow.js +1 -1
- package/dist/src/value-reach.js +8 -8
- package/dist/src/verbs/analyze.js +11 -9
- package/dist/src/workspace-programs.js +2 -2
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +135 -52
- package/src/augmentations.ts +14 -11
- package/src/calls.ts +2 -2
- package/src/class-members.ts +2 -2
- package/src/component-layer.ts +1 -1
- package/src/components.ts +103 -21
- package/src/configuration-files.ts +6 -5
- package/src/configured-declarations.ts +2 -6
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +82 -18
- package/src/convention-rows.ts +6 -2
- package/src/conventions.ts +11 -2
- package/src/decorator.ts +1 -1
- package/src/dependencies.ts +108 -31
- package/src/deprecation.ts +1 -1
- package/src/destructuring.ts +2 -2
- package/src/discover.ts +2 -2
- package/src/element-access.ts +446 -0
- package/src/entry-points.ts +2 -2
- package/src/enum-group.ts +2 -2
- package/src/erased-stores.ts +413 -0
- package/src/file-facts.ts +3 -1
- package/src/findings/completion.ts +54 -20
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +1 -1
- package/src/findings/self-check.ts +18 -4
- package/src/findings-pass.ts +12 -5
- package/src/framework-lifecycle.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/implementations.ts +2 -2
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +304 -146
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +2 -2
- package/src/intra-function-parts.ts +42 -17
- package/src/inventory.ts +34 -2
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +6 -21
- package/src/matrix.ts +34 -2
- package/src/member-names.ts +1 -1
- package/src/module-declarations.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +2 -2
- package/src/position.ts +1 -1
- package/src/query.ts +12 -2
- package/src/references.ts +25 -3
- package/src/reflective-lookup.ts +2 -2
- package/src/report.ts +3 -1
- package/src/reporters/sarif.ts +4 -0
- package/src/roots.ts +2 -2
- package/src/serialization-contract.ts +55 -16
- package/src/session.ts +39 -32
- package/src/signatures.ts +1 -1
- package/src/source-files.ts +1 -1
- package/src/stores.ts +1 -1
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +15 -5
- package/src/suppress.ts +63 -28
- package/src/symbol-keys.ts +2 -2
- package/src/type-errors.ts +2 -2
- package/src/type-query-alias.ts +1 -1
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-flow.ts +1 -1
- package/src/value-reach.ts +12 -8
- package/src/verbs/analyze.ts +15 -9
- package/src/workflow-steps.ts +1 -1
- package/src/workspace-programs.ts +4 -4
|
@@ -10,6 +10,15 @@ import { byPosition, positionKey } from "../position.js";
|
|
|
10
10
|
import { declarationKey } from "../ref.js";
|
|
11
11
|
import { FAMILY_KEY_LENGTH } from "../resolve.js";
|
|
12
12
|
import { isPartSubject, isRowSubject } from "../suppress.js";
|
|
13
|
+
/** The fixability grammar/analysis.md "Fixability" gives a finding about a held declaration. */
|
|
14
|
+
const MANUAL = "manual";
|
|
15
|
+
/** The member kinds an object property can declare, which an `in` expression tests by name. */
|
|
16
|
+
const IN_TESTED = new Set([
|
|
17
|
+
"class-member",
|
|
18
|
+
"method",
|
|
19
|
+
"type-member",
|
|
20
|
+
"interface-method",
|
|
21
|
+
]);
|
|
13
22
|
/** The classes ranked by the claim each makes, the strongest highest. */
|
|
14
23
|
const RANK = { certain: 3, probable: 2, possible: 1 };
|
|
15
24
|
/** The weaker of two classes, which is a class capped by a ceiling. */
|
|
@@ -38,18 +47,39 @@ function publishedIds(union) {
|
|
|
38
47
|
.filter((root) => root.kind === "published-api")
|
|
39
48
|
.flatMap((root) => union.symbols[root.at]?.id ?? []));
|
|
40
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The members whose name an `in` expression of a production file tests by a literal: a
|
|
52
|
+
* value narrowed that way is read back by the name, which no type links to the member.
|
|
53
|
+
*/
|
|
54
|
+
function testedByIn(input) {
|
|
55
|
+
const names = input.files.inOperands ?? new Set();
|
|
56
|
+
const union = input.swept.matrix.union;
|
|
57
|
+
if (names.size === 0) {
|
|
58
|
+
return names;
|
|
59
|
+
}
|
|
60
|
+
return new Set(union.symbols.flatMap((symbol, at) => {
|
|
61
|
+
const parent = union.symbols[union.parent[at] ?? OUTSIDE];
|
|
62
|
+
return IN_TESTED.has(symbol.kind) &&
|
|
63
|
+
parent !== undefined &&
|
|
64
|
+
symbol.name.startsWith(`${parent.name}.`) &&
|
|
65
|
+
names.has(symbol.name.slice(parent.name.length + 1))
|
|
66
|
+
? [symbol.id]
|
|
67
|
+
: [];
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
41
70
|
/**
|
|
42
71
|
* The reachability class of one declaration of the run. A declaration of a library's
|
|
43
72
|
* published API, a public member of a published type included, is `certain` where the
|
|
44
73
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
45
74
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
46
|
-
* consumer set.
|
|
47
|
-
*
|
|
75
|
+
* consumer set. Test-support code test code references, outside the test-of-dead-code
|
|
76
|
+
* rule, what it contains, and a member an `in` expression tests by name are `possible`.
|
|
48
77
|
*/
|
|
49
78
|
function reachabilityClasses(input) {
|
|
79
|
+
const tested = testedByIn(input);
|
|
50
80
|
const published = publishedIds(input.swept.matrix.union);
|
|
51
81
|
const usedByTests = new Set(input.swept.sweep.candidates
|
|
52
|
-
.filter((candidate) => candidate.heldByTests
|
|
82
|
+
.filter((candidate) => candidate.heldByTests !== undefined)
|
|
53
83
|
.map((candidate) => candidate.id));
|
|
54
84
|
const { declared } = input.boundary.consumers;
|
|
55
85
|
const outside = declared.length === 0
|
|
@@ -58,7 +88,7 @@ function reachabilityClasses(input) {
|
|
|
58
88
|
? "certain"
|
|
59
89
|
: "probable";
|
|
60
90
|
return (id) => {
|
|
61
|
-
if (usedByTests.has(id)) {
|
|
91
|
+
if (usedByTests.has(id) || tested.has(id)) {
|
|
62
92
|
return "possible";
|
|
63
93
|
}
|
|
64
94
|
return published.has(id) ? outside : "certain";
|
|
@@ -145,6 +175,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
145
175
|
const classOf = reachabilityClasses(input);
|
|
146
176
|
const ceilingOf = testCeilings(input, classOf);
|
|
147
177
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
178
|
+
const held = new Set(swept.sweep.components.filter((one) => one.held).flatMap((one) => one.members));
|
|
148
179
|
let minted = swept.sweep.components.length;
|
|
149
180
|
const dead = new Set(swept.sweep.components.map((component) => component.id));
|
|
150
181
|
const fallingWith = new Map();
|
|
@@ -197,7 +228,14 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
197
228
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
198
229
|
const reachabilityClass = judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
|
|
199
230
|
const generated = input.files.generated?.has(finding.position.path) === true;
|
|
200
|
-
const fixability = generated
|
|
231
|
+
const fixability = generated
|
|
232
|
+
? "none"
|
|
233
|
+
: found !== undefined &&
|
|
234
|
+
symbol !== undefined &&
|
|
235
|
+
held.has(symbol.id) &&
|
|
236
|
+
row.findingFixabilities?.includes(MANUAL) === true
|
|
237
|
+
? MANUAL
|
|
238
|
+
: row.fixability;
|
|
201
239
|
const lastUses = fixability === "deletable" && candidate !== undefined
|
|
202
240
|
? input.dependencies.lastUses.get(candidate.id)
|
|
203
241
|
: undefined;
|
|
@@ -259,24 +297,17 @@ function dialed(finding, minConfidence) {
|
|
|
259
297
|
* below the minimum confidence is withheld, and so is every finding of the component of a
|
|
260
298
|
* root the dials withhold, because a symbol that falls with a root is dead only through it
|
|
261
299
|
* and its deletion does not compile while the root stays. A withheld finding that is no
|
|
262
|
-
* root withholds itself alone.
|
|
263
|
-
* would report there, completed, is withheld by its own severity or confidence.
|
|
300
|
+
* root withholds itself alone.
|
|
264
301
|
*/
|
|
265
302
|
export function dialsOf(input, findings) {
|
|
266
303
|
const minConfidence = input.config.analysis.minConfidence;
|
|
267
|
-
const classOf = reachabilityClasses(input);
|
|
268
|
-
const ceilingOf = testCeilings(input, classOf);
|
|
269
304
|
const components = new Set(findings
|
|
270
305
|
.filter((finding) => finding.component.root && dialed(finding, minConfidence))
|
|
271
306
|
.map((finding) => finding.component.id));
|
|
272
307
|
return {
|
|
273
|
-
|
|
308
|
+
allows: (code) => {
|
|
274
309
|
const row = KINDS.get(code);
|
|
275
|
-
return
|
|
276
|
-
dialed({
|
|
277
|
-
severity: severityOf(input, code, row.defaultSeverity),
|
|
278
|
-
confidence: capped(capped(classOf(id), row.maxClass), ceilingOf(id)),
|
|
279
|
-
}, minConfidence));
|
|
310
|
+
return row !== undefined && severityOf(input, code, row.defaultSeverity) === "allow";
|
|
280
311
|
},
|
|
281
312
|
withholds: (finding) => dialed(finding, minConfidence) || components.has(finding.component.id),
|
|
282
313
|
};
|
|
@@ -12,7 +12,7 @@ const MESSAGES = {
|
|
|
12
12
|
*/
|
|
13
13
|
function unusedDependencies(dependencies) {
|
|
14
14
|
return dependencies.declared
|
|
15
|
-
.filter((dependency) => !dependencies.needed.has(dependency
|
|
15
|
+
.filter((dependency) => !dependencies.needed.has(declarationKey(dependency)) &&
|
|
16
16
|
!dependencies.ran.has(declarationKey(dependency)))
|
|
17
17
|
.map((dependency) => ({
|
|
18
18
|
code: UNUSED_DEPENDENCY,
|
|
@@ -64,7 +64,7 @@ function dialsOver(input, found, suppressed, unmarked) {
|
|
|
64
64
|
const before = dialsOf(suppressed.unmarked, unmarked);
|
|
65
65
|
const fromUnmarked = new Set(unmarked);
|
|
66
66
|
return {
|
|
67
|
-
|
|
67
|
+
allows: marked.allows,
|
|
68
68
|
withholds: (finding) => fromUnmarked.has(finding) ? before.withholds(finding) : marked.withholds(finding),
|
|
69
69
|
};
|
|
70
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
2
2
|
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isDependencyCode, isUnsuppressibleCode, STALE_SUPPRESSION, SUPPRESSION_WITHOUT_REASON, } from "../suppress.js";
|
|
4
4
|
/** The code a configured root that matches nothing is reported under. */
|
|
5
5
|
const UNMATCHED_ROOT = "DS1704";
|
|
6
6
|
/** The code a configured declaration that names nothing is reported under. */
|
|
@@ -87,14 +87,27 @@ export function staleSuppressions(stale, files) {
|
|
|
87
87
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
88
88
|
.filter((code) => !codes.includes(code))
|
|
89
89
|
.sort();
|
|
90
|
-
const rows = codes.filter(
|
|
90
|
+
const rows = codes.filter(isUnsuppressibleCode);
|
|
91
|
+
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
91
92
|
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
92
93
|
const remedy = settable.length === 0
|
|
93
94
|
? "fix what the finding names instead"
|
|
94
95
|
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
95
96
|
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
96
|
-
const
|
|
97
|
-
|
|
97
|
+
const because = [
|
|
98
|
+
...(rows.length > 0
|
|
99
|
+
? [
|
|
100
|
+
`${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}`,
|
|
101
|
+
]
|
|
102
|
+
: []),
|
|
103
|
+
...(dependencies.length > 0
|
|
104
|
+
? [
|
|
105
|
+
`${spelled(dependencies)} ${dependencies.length === 1 ? "reports a dependency row" : "report dependency rows"}, which only an ignore entry or a baseline row can name`,
|
|
106
|
+
]
|
|
107
|
+
: []),
|
|
108
|
+
];
|
|
109
|
+
const instead = because.length > 0
|
|
110
|
+
? `, because ${because.join("; ")}${declared === "" ? "" : `; ${declared}`}`
|
|
98
111
|
: declared === ""
|
|
99
112
|
? ""
|
|
100
113
|
: `, and ${declared}`;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { fails } from "./config.js";
|
|
7
7
|
import { decidedFindings } from "./findings/emitters.js";
|
|
8
8
|
import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
|
|
9
|
-
import { isRowSubject, pairs, suppressionCounts } from "./suppress.js";
|
|
9
|
+
import { isDependencySubject, isRowSubject, pairs, suppressionCounts, } from "./suppress.js";
|
|
10
10
|
import { EXIT_CLEAN, EXIT_FINDINGS } from "./verbs/verb.js";
|
|
11
11
|
/** The exit code of a report holding at least one pending finding. */
|
|
12
12
|
export const EXIT_PENDING = 4;
|
|
@@ -78,14 +78,15 @@ export function verdictOf(result, config) {
|
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* The rows one round of a baseline write records from its findings, in their order. A
|
|
81
|
-
* finding about a row of a document is not recorded: no record
|
|
82
|
-
* recording it would be stale on the first read back. Two findings
|
|
83
|
-
* same row, two parts of one declaration under one code, record it
|
|
81
|
+
* finding about a row of a document other than a dependency row is not recorded: no record
|
|
82
|
+
* withholds one, so a row recording it would be stale on the first read back. Two findings
|
|
83
|
+
* that would record the same row, two parts of one declaration under one code, record it
|
|
84
|
+
* once.
|
|
84
85
|
*/
|
|
85
86
|
export function recordedFindings(findings) {
|
|
86
87
|
const rows = new Map();
|
|
87
88
|
for (const finding of findings) {
|
|
88
|
-
if (isRowSubject(finding.symbol.kind)) {
|
|
89
|
+
if (isRowSubject(finding.symbol.kind) && !isDependencySubject(finding.symbol.kind)) {
|
|
89
90
|
continue;
|
|
90
91
|
}
|
|
91
92
|
const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* through a chain, a class its `bases` name. A component's members are its type's, so a
|
|
6
6
|
* member a base class declares answers for every subclass.
|
|
7
7
|
*/
|
|
8
|
-
import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, } from "@typescript/native/
|
|
8
|
+
import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, } from "@typescript/native/ast";
|
|
9
9
|
import { aliasChains } from "./alias-chain.js";
|
|
10
10
|
import { calleeName, callsOf, classesPassed, decoratedClasses, decoratorCallee } from "./calls.js";
|
|
11
11
|
import { classMembers, memberComponent, typesAt } from "./class-members.js";
|
package/dist/src/html-entries.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* classic script's that names its file by `src`. An HTML file is read as text and no
|
|
4
4
|
* markup is parsed, so a `<script>` inside a comment is read as well.
|
|
5
5
|
*/
|
|
6
|
-
import { isExportDeclaration, isImportDeclaration, isStringLiteral, } from "@typescript/native/
|
|
6
|
+
import { isExportDeclaration, isImportDeclaration, isStringLiteral, } from "@typescript/native/ast";
|
|
7
7
|
import { resolveRelativeSpecifier } from "./configuration-files.js";
|
|
8
8
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
9
9
|
/** The directory no HTML file is read below. */
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* type is assignable to, asked only of a class carrying every member the interface
|
|
6
6
|
* requires and one it declares, and never of a generic side.
|
|
7
7
|
*/
|
|
8
|
-
import { isClassDeclaration, isHeritageClause, isInterfaceDeclaration, isMethodDeclaration, SyntaxKind, } from "@typescript/native/
|
|
9
|
-
import { SymbolFlags } from "@typescript/native/
|
|
8
|
+
import { isClassDeclaration, isHeritageClause, isInterfaceDeclaration, isMethodDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
9
|
+
import { SymbolFlags } from "@typescript/native/sync";
|
|
10
10
|
import { aliasChains } from "./alias-chain.js";
|
|
11
11
|
import { nodeKey } from "./inventory.js";
|
|
12
12
|
import { must, Unanswerable } from "./query.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The property names `in` tests in a project's production files. A value narrowed by
|
|
3
|
+
* `"name" in value` is read back by that name, often through `unknown`, where no type
|
|
4
|
+
* links the read to a declaration, so only a text match can tell which member it reads.
|
|
5
|
+
*/
|
|
6
|
+
import { isBinaryExpression, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { sourceFilesOf } from "./source-files.js";
|
|
8
|
+
/**
|
|
9
|
+
* The text of each string literal, or template literal with no substitution, written as
|
|
10
|
+
* the left operand of an `in` expression in a file of the project that is no test file.
|
|
11
|
+
* `testFiles` holds the test files by their path below the target root.
|
|
12
|
+
*/
|
|
13
|
+
export function inOperandNames(project, targetRoot, testFiles) {
|
|
14
|
+
const tests = new Set(testFiles);
|
|
15
|
+
const found = new Set();
|
|
16
|
+
const visit = (node) => {
|
|
17
|
+
if (isBinaryExpression(node) &&
|
|
18
|
+
node.operatorToken.kind === SyntaxKind.InKeyword &&
|
|
19
|
+
(isStringLiteral(node.left) || isNoSubstitutionTemplateLiteral(node.left))) {
|
|
20
|
+
found.add(node.left.text);
|
|
21
|
+
}
|
|
22
|
+
node.forEachChild(visit);
|
|
23
|
+
};
|
|
24
|
+
for (const [path, file] of sourceFilesOf(project, targetRoot).byPath) {
|
|
25
|
+
if (!tests.has(path)) {
|
|
26
|
+
file.forEachChild(visit);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return [...found].sort();
|
|
30
|
+
}
|