@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
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/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
package/src/suppress.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
* withholds every such finding anyway, and stale otherwise.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type CODE,
|
|
12
|
+
KIND_ROWS,
|
|
13
|
+
SYMBOL_SUBJECTS,
|
|
14
|
+
type Subject,
|
|
15
|
+
} from "./contract/contract.generated.ts";
|
|
16
|
+
import { UsageError } from "./usage.ts";
|
|
11
17
|
import type { Finding } from "./finding.ts";
|
|
12
18
|
import { positionKey, type Position } from "./position.ts";
|
|
13
19
|
import { isRef } from "./ref.ts";
|
|
@@ -15,15 +21,6 @@ import { isRef } from "./ref.ts";
|
|
|
15
21
|
/** Which document a suppression is written in, spelled as a finding carries it. */
|
|
16
22
|
export type Mechanism = "inline" | "ignore" | "baseline";
|
|
17
23
|
|
|
18
|
-
/** The code a suppression that carries no reason is reported under. */
|
|
19
|
-
export const SUPPRESSION_WITHOUT_REASON = "DS1701";
|
|
20
|
-
|
|
21
|
-
/** The code an ignore entry or a baseline row that names no path is reported under. */
|
|
22
|
-
export const UNSCOPED_ENTRY = "DS1702";
|
|
23
|
-
|
|
24
|
-
/** The code a suppression that withholds nothing is reported under. */
|
|
25
|
-
export const STALE_SUPPRESSION = "DS1703";
|
|
26
|
-
|
|
27
24
|
/**
|
|
28
25
|
* One suppression record: one code bound to one site. A directive naming two codes is two
|
|
29
26
|
* records with one reason, and a directive above a line that declares two symbols is one
|
|
@@ -49,7 +46,7 @@ export interface SuppressionRecord {
|
|
|
49
46
|
* nothing, and its finding reports the suppression as written.
|
|
50
47
|
*/
|
|
51
48
|
export interface Refusal {
|
|
52
|
-
readonly reported: typeof
|
|
49
|
+
readonly reported: typeof CODE.suppressionWithoutReason | typeof CODE.unscopedIgnoreEntry;
|
|
53
50
|
readonly code: string;
|
|
54
51
|
/** The reference the entry names, empty for a directive. */
|
|
55
52
|
readonly symbol: string;
|
|
@@ -71,9 +68,9 @@ export interface Suppressions {
|
|
|
71
68
|
* instruction the analysis cannot carry out. It is a malformed input, so the command line
|
|
72
69
|
* answers it with the usage code.
|
|
73
70
|
*/
|
|
74
|
-
export class SuppressionError extends
|
|
71
|
+
export class SuppressionError extends UsageError {
|
|
75
72
|
constructor(mechanism: Mechanism, at: string, text: string, want: string) {
|
|
76
|
-
super(
|
|
73
|
+
super(`${mechanism} ${at}: ${text}: want ${want}`);
|
|
77
74
|
this.name = "SuppressionError";
|
|
78
75
|
}
|
|
79
76
|
}
|
|
@@ -115,6 +112,7 @@ export const GO_REF_EXPRESSIONS: readonly string[] = [
|
|
|
115
112
|
`^go://${GO_PATH}#${GO_IDENT}$`,
|
|
116
113
|
`^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})+$`,
|
|
117
114
|
`^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})?\\[${GO_IDENT}\\]$`,
|
|
115
|
+
`^go://${GO_PATH}#(?:${GO_IDENT}(?:\\.${GO_IDENT})?)?\\(${GO_IDENT}\\)$`,
|
|
118
116
|
`^go://${GO_PATH}#${GO_FILE}:file$`,
|
|
119
117
|
`^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:require$`,
|
|
120
118
|
`^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:replace$`,
|
|
@@ -157,48 +155,48 @@ export interface Ledger {
|
|
|
157
155
|
readonly withheld: (finding: Finding) => boolean;
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
]
|
|
158
|
+
/** The subject of the kind each live code names. */
|
|
159
|
+
const SUBJECT_OF_CODE: ReadonlyMap<string, Subject> = new Map(
|
|
160
|
+
KIND_ROWS.map((row): [string, Subject] => [row.code, row.subject]),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
/** The subject a finding about one symbol kind is about. */
|
|
164
|
+
function subjectOf(kind: string): Subject | undefined {
|
|
165
|
+
return Object.hasOwn(SYMBOL_SUBJECTS, kind)
|
|
166
|
+
? SYMBOL_SUBJECTS[kind as keyof typeof SYMBOL_SUBJECTS]
|
|
167
|
+
: undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Whether a subject is a row of a document rather than a declaration of the program. */
|
|
171
|
+
function rowOf(subject: Subject | undefined): boolean {
|
|
172
|
+
return subject === "file" || subject === "dependency" || subject === "record";
|
|
173
|
+
}
|
|
169
174
|
|
|
170
175
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* finding about one.
|
|
176
|
+
* Whether a finding's subject is a row of a document rather than a declaration. Apart
|
|
177
|
+
* from a dependency row, nothing withholds a finding about one.
|
|
174
178
|
*/
|
|
175
|
-
const ROW_SUBJECTS: ReadonlyMap<string, readonly string[]> = new Map([
|
|
176
|
-
["file", ["DS1501", "DS1502"]],
|
|
177
|
-
...DEPENDENCY_SUBJECTS,
|
|
178
|
-
["root", ["DS1704"]],
|
|
179
|
-
["configured-declaration", ["DS1706"]],
|
|
180
|
-
["suppression", ["DS1701", "DS1702", "DS1703"]],
|
|
181
|
-
["edge", ["DS1705"]],
|
|
182
|
-
]);
|
|
183
|
-
|
|
184
|
-
/** Whether a finding's subject is a row of a document rather than a declaration. */
|
|
185
179
|
export function isRowSubject(kind: string): boolean {
|
|
186
|
-
return
|
|
180
|
+
return rowOf(subjectOf(kind));
|
|
187
181
|
}
|
|
188
182
|
|
|
189
183
|
/** Whether a code reports a row of a document. */
|
|
190
184
|
export function isRowCode(code: string): boolean {
|
|
191
|
-
return
|
|
185
|
+
return rowOf(SUBJECT_OF_CODE.get(code));
|
|
192
186
|
}
|
|
193
187
|
|
|
194
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* Whether a finding's subject is a dependency row. An ignore entry or a baseline row
|
|
190
|
+
* naming the row's reference and file withholds its finding and marks nothing, and an
|
|
191
|
+
* inline directive never binds one.
|
|
192
|
+
*/
|
|
195
193
|
export function isDependencySubject(kind: string): boolean {
|
|
196
|
-
return
|
|
194
|
+
return subjectOf(kind) === "dependency";
|
|
197
195
|
}
|
|
198
196
|
|
|
199
197
|
/** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
|
|
200
198
|
export function isDependencyCode(code: string): boolean {
|
|
201
|
-
return
|
|
199
|
+
return SUBJECT_OF_CODE.get(code) === "dependency";
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
/** Whether a code reports a row of a document that no record of any mechanism can name. */
|
|
@@ -207,33 +205,20 @@ export function isUnsuppressibleCode(code: string): boolean {
|
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
/**
|
|
210
|
-
*
|
|
208
|
+
* Whether a finding's subject is a part of the declaration its reference names: a part
|
|
211
209
|
* has no reference of its own, so a finding about one names the declaration that holds
|
|
212
210
|
* it, and the part is decided inside that declaration whatever uses it.
|
|
213
211
|
*/
|
|
214
|
-
const PART_SUBJECTS: ReadonlySet<string> = new Set([
|
|
215
|
-
"parameter",
|
|
216
|
-
"receiver",
|
|
217
|
-
"result",
|
|
218
|
-
"statement",
|
|
219
|
-
"case",
|
|
220
|
-
"store",
|
|
221
|
-
]);
|
|
222
|
-
|
|
223
|
-
/** Whether a finding's subject is a part of the declaration its reference names. */
|
|
224
212
|
export function isPartSubject(kind: string): boolean {
|
|
225
|
-
return
|
|
213
|
+
return subjectOf(kind) === "part";
|
|
226
214
|
}
|
|
227
215
|
|
|
228
|
-
/** The prefix of the codes of the part kinds, the intra-function range. */
|
|
229
|
-
const PART_KIND_PREFIX = "DS18";
|
|
230
|
-
|
|
231
216
|
/**
|
|
232
217
|
* Whether a code is a part kind, whose record withholds its finding and marks nothing:
|
|
233
218
|
* the record says the part is wanted, not that the declaration holding it is.
|
|
234
219
|
*/
|
|
235
220
|
export function isPartKind(code: string): boolean {
|
|
236
|
-
return
|
|
221
|
+
return SUBJECT_OF_CODE.get(code) === "part";
|
|
237
222
|
}
|
|
238
223
|
|
|
239
224
|
/** One finding's identity: its code and the position of the thing it names. */
|
|
@@ -243,29 +228,33 @@ function claimKey(finding: Finding): string {
|
|
|
243
228
|
|
|
244
229
|
/**
|
|
245
230
|
* Whether one record names one finding. A record reaches a finding in two ways: it bound
|
|
246
|
-
* the declaration at the finding's position, or it names the finding's reference
|
|
247
|
-
*
|
|
248
|
-
* finding
|
|
249
|
-
*
|
|
231
|
+
* the declaration at the finding's position, or it names the finding's reference, or the
|
|
232
|
+
* reference of the declaration holding a part that has one of its own, and the file the
|
|
233
|
+
* finding is reported in, each exactly. The second is how an entry copied from a finding
|
|
234
|
+
* about a part of a declaration reaches the part, and the only way any record reaches a
|
|
235
|
+
* dependency row.
|
|
250
236
|
*/
|
|
251
237
|
export function pairs(record: SuppressionRecord, finding: Finding): boolean {
|
|
252
238
|
if (record.code !== finding.code) {
|
|
253
239
|
return false;
|
|
254
240
|
}
|
|
255
|
-
if (
|
|
241
|
+
if (isDependencySubject(finding.symbol.kind)) {
|
|
256
242
|
return (
|
|
257
243
|
record.mechanism !== "inline" &&
|
|
258
244
|
record.symbol === finding.symbol.ref &&
|
|
259
245
|
record.path === finding.position.path
|
|
260
246
|
);
|
|
261
247
|
}
|
|
262
|
-
if (
|
|
248
|
+
if (isRowSubject(finding.symbol.kind)) {
|
|
263
249
|
return false;
|
|
264
250
|
}
|
|
265
251
|
if (record.bound !== "" && record.bound === positionKey(finding.position)) {
|
|
266
252
|
return true;
|
|
267
253
|
}
|
|
268
|
-
return
|
|
254
|
+
return (
|
|
255
|
+
(record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
|
|
256
|
+
record.path === finding.position.path
|
|
257
|
+
);
|
|
269
258
|
}
|
|
270
259
|
|
|
271
260
|
/**
|
package/src/sweep.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { TSExemptionClass } from "./exempt-classes.ts";
|
|
10
10
|
import { namesACaller, OUTSIDE, type Edge, type Graph } from "./graph.ts";
|
|
11
|
+
import type { InventorySymbol } from "./inventory.ts";
|
|
11
12
|
import type { Position } from "./position.ts";
|
|
12
13
|
|
|
13
14
|
/** The liveness relation that decided a declaration, as a finding spells it. */
|
|
@@ -32,6 +33,8 @@ export interface Exemption {
|
|
|
32
33
|
* The record holds only while one of them is called through its interface.
|
|
33
34
|
*/
|
|
34
35
|
readonly implementing?: Implementing;
|
|
36
|
+
/** Whether the record holds only where a write the mode counts stores into the declaration. */
|
|
37
|
+
readonly whileWritten?: true;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/** The interface methods one retained class member implements, and whether its body is empty. */
|
|
@@ -178,23 +181,45 @@ export function sweep(graph: Graph, input: SweepInput): Liveness {
|
|
|
178
181
|
return { liveUnder: live, candidates };
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The directory a test runner reads a manual mock from, whose exports it puts in place of
|
|
186
|
+
* the module's: https://jestjs.io/docs/manual-mocks
|
|
187
|
+
*/
|
|
188
|
+
const MANUAL_MOCKS = "__mocks__";
|
|
189
|
+
|
|
190
|
+
/** Whether a test runner calls or reads one export of a test file: a function, or any export of a manual mock. */
|
|
191
|
+
export function runnerCalls(symbol: InventorySymbol): boolean {
|
|
192
|
+
return symbol.kind === "function" || symbol.position.path.split("/").includes(MANUAL_MOCKS);
|
|
193
|
+
}
|
|
194
|
+
|
|
181
195
|
/**
|
|
182
196
|
* Per declaration of the graph, whether the run that counts test references reaches it.
|
|
183
|
-
* That run's roots are the graph's own and each test file,
|
|
184
|
-
*
|
|
185
|
-
*
|
|
197
|
+
* That run's roots are the graph's own and, of each test file, its top level and every
|
|
198
|
+
* function it exports, which a test runner may call, and every export of a manual mock,
|
|
199
|
+
* which the runner reads; any other export of a test file, and test-support code, which
|
|
200
|
+
* runs where a test imports it, are judged as any file's are. `held` seeds it as a mark
|
|
201
|
+
* seeds a sweep; with `namespaces` false a file read reaches none of the file's exports.
|
|
186
202
|
*/
|
|
187
|
-
export function reachedWithTheTests(
|
|
203
|
+
export function reachedWithTheTests(
|
|
204
|
+
graph: Graph,
|
|
205
|
+
held: readonly string[],
|
|
206
|
+
namespaces = true,
|
|
207
|
+
): readonly boolean[] {
|
|
188
208
|
const tests = graph.symbols.flatMap((symbol, at) => {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
return graph.test[at] === true &&
|
|
209
|
+
const exported = symbol.exported && graph.symbols[graph.parent[at] ?? OUTSIDE]?.kind === "file";
|
|
210
|
+
const called = exported && runnerCalls(symbol);
|
|
211
|
+
return graph.test[at] === true &&
|
|
212
|
+
graph.support[at] !== true &&
|
|
213
|
+
(symbol.kind === "file" || called)
|
|
192
214
|
? [{ at, kind: "test-runner" as const }]
|
|
193
215
|
: [];
|
|
194
216
|
});
|
|
195
|
-
return reachable(
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
return reachable(
|
|
218
|
+
{ ...graph, rooted: [...graph.rooted, ...tests] },
|
|
219
|
+
flagged(graph, held),
|
|
220
|
+
{ production: false },
|
|
221
|
+
namespaces,
|
|
222
|
+
);
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
/**
|
|
@@ -224,7 +249,12 @@ function flagged(graph: Graph, ids: readonly string[]): boolean[] {
|
|
|
224
249
|
* included, while a root reaches through the references the mode counts. The held seed
|
|
225
250
|
* runs first, so what it reaches is expanded under the wider rule.
|
|
226
251
|
*/
|
|
227
|
-
function reachable(
|
|
252
|
+
function reachable(
|
|
253
|
+
graph: Graph,
|
|
254
|
+
held: readonly boolean[],
|
|
255
|
+
mode: Mode,
|
|
256
|
+
namespaces = true,
|
|
257
|
+
): readonly boolean[] {
|
|
228
258
|
const reached = graph.symbols.map(() => false);
|
|
229
259
|
const expanded = graph.symbols.map(() => false);
|
|
230
260
|
const queue: number[] = [];
|
|
@@ -253,7 +283,7 @@ function reachable(graph: Graph, held: readonly boolean[], mode: Mode): readonly
|
|
|
253
283
|
if (!holding && mode.production && edge.test) {
|
|
254
284
|
continue;
|
|
255
285
|
}
|
|
256
|
-
enter(edge.to, !edge.evaluation);
|
|
286
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
257
287
|
}
|
|
258
288
|
}
|
|
259
289
|
};
|
|
@@ -269,7 +299,7 @@ function reachable(graph: Graph, held: readonly boolean[], mode: Mode): readonly
|
|
|
269
299
|
}
|
|
270
300
|
// A consumer's reference reaches what it names as one of the target's own does.
|
|
271
301
|
for (const edge of callsOf(graph, mode)) {
|
|
272
|
-
enter(edge.to, !edge.evaluation);
|
|
302
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
273
303
|
}
|
|
274
304
|
walk(false);
|
|
275
305
|
return reached;
|
|
@@ -277,10 +307,11 @@ function reachable(graph: Graph, held: readonly boolean[], mode: Mode): readonly
|
|
|
277
307
|
|
|
278
308
|
/**
|
|
279
309
|
* Admits every test declaration whose set of referenced production declarations is
|
|
280
|
-
* non-empty and wholly dead, and returns which it admitted.
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
310
|
+
* non-empty and wholly dead, and returns which it admitted. An ambient declaration describes
|
|
311
|
+
* code the target does not implement, so it is no production declaration here. The set is
|
|
312
|
+
* read from every reference the test makes, which a production sweep does not count. A
|
|
313
|
+
* test-support declaration a test referencing a live target also references is a live
|
|
314
|
+
* target of every test referencing it. A marked or exempt test is never admitted.
|
|
284
315
|
*/
|
|
285
316
|
function admitTestsOfDeadCode(
|
|
286
317
|
graph: Graph,
|
|
@@ -293,7 +324,11 @@ function admitTestsOfDeadCode(
|
|
|
293
324
|
let live = 0;
|
|
294
325
|
if (testCode(at) && graph.subject[at] === true && held[at] !== true) {
|
|
295
326
|
for (const edge of graph.out[at] ?? []) {
|
|
296
|
-
if (
|
|
327
|
+
if (
|
|
328
|
+
testCode(edge.to) ||
|
|
329
|
+
graph.subject[edge.to] !== true ||
|
|
330
|
+
graph.ambient[edge.to] === true
|
|
331
|
+
) {
|
|
297
332
|
continue;
|
|
298
333
|
}
|
|
299
334
|
targets += 1;
|
package/src/symbol-keys.ts
CHANGED
|
@@ -2,24 +2,31 @@
|
|
|
2
2
|
* The members a unique symbol keys, used through element accesses. `value[key]`, where
|
|
3
3
|
* `key` is a constant of a `unique symbol` type, reads the member of the receiver's type
|
|
4
4
|
* whose computed name is that same constant, and a store through the access writes it.
|
|
5
|
-
* No name node resolves to such a member: the key's name resolves to the constant.
|
|
5
|
+
* No name node resolves to such a member: the key's name resolves to the constant. An
|
|
6
|
+
* iteration reads the member its well-known iteration symbol keys.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
isComputedPropertyName,
|
|
11
|
+
isForOfStatement,
|
|
10
12
|
isIdentifier,
|
|
11
13
|
isNoSubstitutionTemplateLiteral,
|
|
12
14
|
isNumericLiteral,
|
|
13
|
-
isParenthesizedExpression,
|
|
14
15
|
isPropertyAccessExpression,
|
|
16
|
+
isSpreadElement,
|
|
15
17
|
isStringLiteral,
|
|
16
18
|
type ElementAccessExpression,
|
|
19
|
+
type Expression,
|
|
17
20
|
type Node,
|
|
21
|
+
type SourceFile,
|
|
18
22
|
} from "@typescript/native/ast";
|
|
23
|
+
import { parenthesesRemoved } from "./unwrap.ts";
|
|
19
24
|
import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
|
|
20
25
|
import type { AliasChains } from "./alias-chain.ts";
|
|
21
|
-
import type
|
|
26
|
+
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
27
|
+
import { renderPosition } from "./position.ts";
|
|
22
28
|
import { isAnswered, type PropertyTables } from "./query.ts";
|
|
29
|
+
import type { Reference } from "./references.ts";
|
|
23
30
|
import type { ProjectView } from "./session.ts";
|
|
24
31
|
|
|
25
32
|
/** One element access whose key is a name, and how the access uses the member it selects. */
|
|
@@ -45,10 +52,7 @@ interface KeyedUse {
|
|
|
45
52
|
|
|
46
53
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
47
54
|
export function keyNameOf(argument: Node): Node | undefined {
|
|
48
|
-
|
|
49
|
-
while (isParenthesizedExpression(at)) {
|
|
50
|
-
at = at.expression;
|
|
51
|
-
}
|
|
55
|
+
const at = parenthesesRemoved(argument);
|
|
52
56
|
if (isIdentifier(at)) {
|
|
53
57
|
return at;
|
|
54
58
|
}
|
|
@@ -170,3 +174,123 @@ export function keyedUses<Brand>(
|
|
|
170
174
|
return ids.length === 0 ? [] : [{ access: one.access, ids: [...new Set(ids)] }];
|
|
171
175
|
});
|
|
172
176
|
}
|
|
177
|
+
|
|
178
|
+
/** The well-known symbol a value's iteration calls, by the construct that iterates it. */
|
|
179
|
+
type Protocol = "iterator" | "asyncIterator";
|
|
180
|
+
|
|
181
|
+
/** The member name `[Symbol.<name>]` spells, where a computed name spells one. */
|
|
182
|
+
function wellKnownName(name: Node | undefined): string | undefined {
|
|
183
|
+
if (name === undefined || !isComputedPropertyName(name)) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
const key = name.expression;
|
|
187
|
+
return isPropertyAccessExpression(key) &&
|
|
188
|
+
isIdentifier(key.expression) &&
|
|
189
|
+
key.expression.text === "Symbol" &&
|
|
190
|
+
isIdentifier(key.name)
|
|
191
|
+
? key.name.text
|
|
192
|
+
: undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The references the iterations of one project's own files make to the iteration members
|
|
197
|
+
* of the values they iterate: a `for…of` statement and a spread element read
|
|
198
|
+
* `[Symbol.iterator]`, and a `for await…of` statement reads `[Symbol.asyncIterator]` and,
|
|
199
|
+
* which it falls back to, `[Symbol.iterator]`. Each is made by the declaration holding it.
|
|
200
|
+
*/
|
|
201
|
+
export function iterationReferences<Brand>(
|
|
202
|
+
project: ProjectView<Brand>,
|
|
203
|
+
held: Inventory,
|
|
204
|
+
chains: AliasChains,
|
|
205
|
+
root: string,
|
|
206
|
+
testFiles: ReadonlySet<string>,
|
|
207
|
+
): readonly Reference[] {
|
|
208
|
+
const queries = project.queries;
|
|
209
|
+
const ownPaths = project.ownPaths();
|
|
210
|
+
const iterated: {
|
|
211
|
+
readonly file: SourceFile;
|
|
212
|
+
readonly from: string;
|
|
213
|
+
readonly operand: Expression;
|
|
214
|
+
readonly protocols: readonly Protocol[];
|
|
215
|
+
}[] = [];
|
|
216
|
+
for (const file of project.ownSourceFiles()) {
|
|
217
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
218
|
+
if (fileId === undefined) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const visit = (node: Node, from: string): void => {
|
|
222
|
+
const holder = held.declarations.get(nodeKey(file, node)) ?? from;
|
|
223
|
+
if (isForOfStatement(node)) {
|
|
224
|
+
const protocols: readonly Protocol[] =
|
|
225
|
+
node.awaitModifier === undefined ? ["iterator"] : ["asyncIterator", "iterator"];
|
|
226
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols });
|
|
227
|
+
} else if (isSpreadElement(node)) {
|
|
228
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols: ["iterator"] });
|
|
229
|
+
}
|
|
230
|
+
node.forEachChild((child) => {
|
|
231
|
+
visit(child, holder);
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
file.forEachChild((child) => {
|
|
235
|
+
visit(child, fileId);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (iterated.length === 0) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const types = queries.typesAt(iterated.map((one) => one.operand));
|
|
242
|
+
const members = new Map<
|
|
243
|
+
number,
|
|
244
|
+
readonly { readonly name: string; readonly ids: readonly string[] }[]
|
|
245
|
+
>();
|
|
246
|
+
const membersOf = (
|
|
247
|
+
type: Type,
|
|
248
|
+
): readonly { readonly name: string; readonly ids: readonly string[] }[] => {
|
|
249
|
+
const known = members.get(type.id);
|
|
250
|
+
if (known !== undefined) {
|
|
251
|
+
return known;
|
|
252
|
+
}
|
|
253
|
+
const keyed: { name: string; ids: readonly string[] }[] = [];
|
|
254
|
+
const properties = queries.propertiesOf(type);
|
|
255
|
+
for (const property of isAnswered(properties) ? properties : []) {
|
|
256
|
+
for (const handle of property.declarations) {
|
|
257
|
+
const node = ownPaths.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
258
|
+
const name = wellKnownName((node as { readonly name?: Node } | undefined)?.name);
|
|
259
|
+
if (name !== undefined) {
|
|
260
|
+
keyed.push({ name, ids: chains.declarationsOf(property) });
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
members.set(type.id, keyed);
|
|
266
|
+
return keyed;
|
|
267
|
+
};
|
|
268
|
+
const found: Reference[] = [];
|
|
269
|
+
iterated.forEach((one, index) => {
|
|
270
|
+
const type = types[index];
|
|
271
|
+
if (type === undefined || !isAnswered(type)) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const parts =
|
|
275
|
+
type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [type];
|
|
276
|
+
const position = renderPosition(one.file, root, one.operand.getStart());
|
|
277
|
+
for (const part of isAnswered(parts) ? parts : []) {
|
|
278
|
+
for (const member of membersOf(part)) {
|
|
279
|
+
if (!(one.protocols as readonly string[]).includes(member.name)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
for (const to of member.ids) {
|
|
283
|
+
found.push({
|
|
284
|
+
from: one.from,
|
|
285
|
+
to,
|
|
286
|
+
position,
|
|
287
|
+
use: "read",
|
|
288
|
+
resolution: "keyed",
|
|
289
|
+
test: testFiles.has(position.path),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
return found;
|
|
296
|
+
}
|
package/src/template-field.ts
CHANGED
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { readComponent } from "./component-files.ts";
|
|
16
|
-
import {
|
|
16
|
+
import type { ResolvedConfig, TemplateDelimiters } from "./config.ts";
|
|
17
17
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
18
18
|
import type { Host } from "./host.ts";
|
|
19
19
|
import type { SymbolKind } from "./inventory.ts";
|
|
20
20
|
import { memberNames } from "./member-names.ts";
|
|
21
21
|
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.ts";
|
|
22
22
|
import { isComponentFile, type Position } from "./position.ts";
|
|
23
|
+
import { bytewise } from "./text/compare.ts";
|
|
24
|
+
import { filesBelow } from "./fs/tree.ts";
|
|
25
|
+
import { UsageError } from "./usage.ts";
|
|
23
26
|
|
|
24
27
|
/** One template, by its path below the target root. */
|
|
25
28
|
interface TemplateFile {
|
|
@@ -50,14 +53,6 @@ const READ_BY_MARKUP: ReadonlySet<SymbolKind> = new Set([...READ_BY_A_TEMPLATE,
|
|
|
50
53
|
/** One identifier, as the grammar of the analyzed language spells one. */
|
|
51
54
|
const IDENTIFIER = /[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*/uy;
|
|
52
55
|
|
|
53
|
-
/** Two strings ordered bytewise. */
|
|
54
|
-
function compare(a: string, b: string): number {
|
|
55
|
-
if (a === b) {
|
|
56
|
-
return 0;
|
|
57
|
-
}
|
|
58
|
-
return a < b ? -1 : 1;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
56
|
/**
|
|
62
57
|
* Every template under the configured directories, read once for the run, in path
|
|
63
58
|
* order and each file once. A directory is named relative to the target root, and
|
|
@@ -66,25 +61,22 @@ function compare(a: string, b: string): number {
|
|
|
66
61
|
* exemption it was configured for off. A symbolic link to a directory is not
|
|
67
62
|
* followed.
|
|
68
63
|
*/
|
|
69
|
-
export function readTemplates(
|
|
64
|
+
export function readTemplates(
|
|
65
|
+
host: Host,
|
|
66
|
+
targetRoot: string,
|
|
67
|
+
analysis: ResolvedConfig["analysis"],
|
|
68
|
+
): Templates {
|
|
70
69
|
const files = new Map<string, string>();
|
|
71
70
|
const read = (dir: string): void => {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
for (const entry of entries) {
|
|
76
|
-
const path = joinPath(dir, entry.name);
|
|
77
|
-
if (entry.directory) {
|
|
78
|
-
read(path);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
71
|
+
for (const below of filesBelow(host, joinPath(targetRoot, dir), () => true)) {
|
|
72
|
+
const path = joinPath(dir, below);
|
|
81
73
|
const absolute = joinPath(targetRoot, path);
|
|
82
74
|
if (!files.has(path) && host.kindOf(absolute) === "file") {
|
|
83
75
|
files.set(path, host.readFile(absolute));
|
|
84
76
|
}
|
|
85
77
|
}
|
|
86
78
|
};
|
|
87
|
-
for (const configured of analysis.
|
|
79
|
+
for (const configured of analysis.template_dirs) {
|
|
88
80
|
const dir = normalizePath(configured);
|
|
89
81
|
if (
|
|
90
82
|
isAbsolutePath(dir) ||
|
|
@@ -92,19 +84,17 @@ export function readTemplates(host: Host, targetRoot: string, analysis: Analysis
|
|
|
92
84
|
dir.startsWith("../") ||
|
|
93
85
|
host.kindOf(joinPath(targetRoot, dir)) !== "directory"
|
|
94
86
|
) {
|
|
95
|
-
throw new
|
|
96
|
-
"malformed",
|
|
97
|
-
"analysis.template_dirs",
|
|
87
|
+
throw new UsageError(
|
|
98
88
|
`analysis.template_dirs: ${JSON.stringify(configured)} is not a directory below the target root`,
|
|
99
89
|
);
|
|
100
90
|
}
|
|
101
91
|
read(dir);
|
|
102
92
|
}
|
|
103
93
|
return {
|
|
104
|
-
delimiters: analysis.
|
|
94
|
+
delimiters: analysis.template_delimiters,
|
|
105
95
|
files: [...files]
|
|
106
96
|
.map(([path, text]) => ({ path, text }))
|
|
107
|
-
.sort((a, b) =>
|
|
97
|
+
.sort((a, b) => bytewise(a.path, b.path)),
|
|
108
98
|
};
|
|
109
99
|
}
|
|
110
100
|
|