@cplieger/deadset-ts 7.1.0 → 7.2.0-dev.2
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 +6 -2
- package/dist/src/analysis.js +3 -1
- package/dist/src/config.js +4 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/decorator.js +3 -2
- package/dist/src/dependencies.js +0 -4
- package/dist/src/deprecation.js +1 -1
- package/dist/src/findings/completion.js +1 -1
- package/dist/src/findings/interfaces.js +1 -8
- package/dist/src/findings/unused-declarations.js +8 -11
- package/dist/src/html-entries.js +0 -3
- package/dist/src/injection-container.js +28 -25
- package/dist/src/intra-function-parts.js +71 -22
- package/dist/src/inventory.js +19 -6
- package/dist/src/kinds.js +2 -2
- package/dist/src/matrix.js +1 -8
- package/dist/src/module-declarations.js +0 -3
- package/dist/src/ref.js +3 -2
- package/dist/src/references.js +38 -20
- package/dist/src/report.js +1 -1
- package/dist/src/resolve.js +37 -4
- package/dist/src/schema.js +4 -1
- package/dist/src/suppress-file.js +43 -9
- package/dist/src/suppress-inline.js +35 -12
- package/dist/src/suppress.js +3 -2
- package/dist/src/type-errors.js +2 -1
- package/dist/src/verbs/analyze.js +7 -3
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-resolution.js +5 -8
- package/dist/src/workspace.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +7 -2
- package/src/analysis.ts +3 -3
- package/src/config.ts +9 -1
- package/src/conformance-record.ts +2 -2
- package/src/decorator.ts +8 -2
- package/src/dependencies.ts +0 -4
- package/src/deprecation.ts +1 -1
- package/src/findings/completion.ts +1 -4
- package/src/findings/interfaces.ts +1 -9
- package/src/findings/unused-declarations.ts +8 -15
- package/src/html-entries.ts +0 -3
- package/src/injection-container.ts +31 -26
- package/src/intra-function-parts.ts +96 -24
- package/src/inventory.ts +27 -5
- package/src/kinds.ts +2 -2
- package/src/matrix.ts +1 -9
- package/src/module-declarations.ts +0 -3
- package/src/ref.ts +3 -2
- package/src/references.ts +55 -21
- package/src/report.ts +1 -1
- package/src/resolve.ts +56 -6
- package/src/schema.ts +10 -14
- package/src/signatures.ts +6 -1
- package/src/suppress-file.ts +48 -9
- package/src/suppress-inline.ts +57 -15
- package/src/suppress.ts +3 -2
- package/src/type-errors.ts +2 -1
- package/src/verbs/analyze.ts +7 -2
- package/src/version.ts +1 -1
- package/src/workspace-resolution.ts +5 -8
- package/src/workspace.ts +2 -2
|
@@ -79,9 +79,6 @@ function namesModule(specifier, name) {
|
|
|
79
79
|
* the members that hold the export names given.
|
|
80
80
|
*/
|
|
81
81
|
function namedBy(held) {
|
|
82
|
-
if (held.modules.size === 0) {
|
|
83
|
-
return () => [];
|
|
84
|
-
}
|
|
85
82
|
const refs = new Map();
|
|
86
83
|
const children = new Map();
|
|
87
84
|
for (const symbol of held.symbols) {
|
package/dist/src/ref.js
CHANGED
|
@@ -25,9 +25,10 @@ const TS_COMPUTED = String.raw `\[[^\]\r\n\t ]+\]`;
|
|
|
25
25
|
const TS_HEAD = `(?:${TS_IDENT}|${TS_QUOTED})`;
|
|
26
26
|
const TS_MEMBER = `(?:${TS_IDENT}|#${TS_IDENT}|${TS_QUOTED}|${TS_COMPUTED})`;
|
|
27
27
|
const TS_PACKAGE = String.raw `(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+`;
|
|
28
|
-
const
|
|
28
|
+
const TS_DIRS = String.raw `(?:[^/#\r\n]+/)*`;
|
|
29
|
+
const TS_FILE = String.raw `${TS_DIRS}[^/#\r\n]+(?:(?:\.d)?\.[mc]?ts|\.tsx|\.[mc]?js|\.jsx)`;
|
|
29
30
|
const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
|
|
30
|
-
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}
|
|
31
|
+
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
|
|
31
32
|
/**
|
|
32
33
|
* One expression per form the grammar defines, each anchored and each the whole of
|
|
33
34
|
* a reference. Several forms share one expression because they differ in what the
|
package/dist/src/references.js
CHANGED
|
@@ -5,7 +5,7 @@ import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
|
5
5
|
import { specifierCandidates } from "./configuration-files.js";
|
|
6
6
|
import { namespaceUses } from "./namespace-objects.js";
|
|
7
7
|
import { globExpression } from "./glob.js";
|
|
8
|
-
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
8
|
+
import { declarationsByName, isParameterProperty, nodeKey, } from "./inventory.js";
|
|
9
9
|
import { augmentationReferences } from "./augmentations.js";
|
|
10
10
|
import { elementAccessReferences } from "./element-access.js";
|
|
11
11
|
import { indexedAccessReferences } from "./indexed-access.js";
|
|
@@ -336,6 +336,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
336
336
|
const wholes = [];
|
|
337
337
|
const urls = [];
|
|
338
338
|
const decorated = [];
|
|
339
|
+
const parameterWrites = [];
|
|
339
340
|
const namespaces = namespaceUses(file);
|
|
340
341
|
const literals = [];
|
|
341
342
|
const flows = [];
|
|
@@ -419,7 +420,16 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
419
420
|
}
|
|
420
421
|
const held = declarations.get(nodeKey(file, node));
|
|
421
422
|
const outer = enclosing;
|
|
422
|
-
|
|
423
|
+
// A parameter property's annotation, initializer and decorators stay with the
|
|
424
|
+
// parameter when the member goes, so they are written inside the constructor's class.
|
|
425
|
+
if (held !== undefined && isParameterProperty(node)) {
|
|
426
|
+
parameterWrites.push({
|
|
427
|
+
name: node.name,
|
|
428
|
+
from: outer,
|
|
429
|
+
member: held,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else if (held !== undefined) {
|
|
423
433
|
enclosing = held;
|
|
424
434
|
for (const modifier of node.modifiers ?? []) {
|
|
425
435
|
if (isDecorator(modifier)) {
|
|
@@ -441,6 +451,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
441
451
|
wholes,
|
|
442
452
|
urls,
|
|
443
453
|
decorated,
|
|
454
|
+
parameterWrites,
|
|
444
455
|
keyed,
|
|
445
456
|
};
|
|
446
457
|
}
|
|
@@ -523,15 +534,12 @@ function importedNames(file) {
|
|
|
523
534
|
importedCache.set(file, names);
|
|
524
535
|
return names;
|
|
525
536
|
}
|
|
526
|
-
/**
|
|
527
|
-
* The name node of every import binding one file's top level declares, and whether it
|
|
528
|
-
* binds a whole module.
|
|
529
|
-
*/
|
|
537
|
+
/** The name node of every import binding one file's top level declares. */
|
|
530
538
|
function importBindings(file) {
|
|
531
539
|
const found = [];
|
|
532
540
|
for (const statement of file.statements) {
|
|
533
541
|
if (isImportEqualsDeclaration(statement)) {
|
|
534
|
-
found.push({ node: statement.name
|
|
542
|
+
found.push({ node: statement.name });
|
|
535
543
|
continue;
|
|
536
544
|
}
|
|
537
545
|
const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
|
|
@@ -539,17 +547,17 @@ function importBindings(file) {
|
|
|
539
547
|
continue;
|
|
540
548
|
}
|
|
541
549
|
if (clause.name !== undefined) {
|
|
542
|
-
found.push({ node: clause.name
|
|
550
|
+
found.push({ node: clause.name });
|
|
543
551
|
}
|
|
544
552
|
const bindings = clause.namedBindings;
|
|
545
553
|
if (bindings === undefined) {
|
|
546
554
|
continue;
|
|
547
555
|
}
|
|
548
556
|
if (bindings.kind === SyntaxKind.NamespaceImport) {
|
|
549
|
-
found.push({ node: bindings.name
|
|
557
|
+
found.push({ node: bindings.name });
|
|
550
558
|
}
|
|
551
559
|
else {
|
|
552
|
-
found.push(...bindings.elements.map((element) => ({ node: element.name
|
|
560
|
+
found.push(...bindings.elements.map((element) => ({ node: element.name })));
|
|
553
561
|
}
|
|
554
562
|
}
|
|
555
563
|
return found;
|
|
@@ -856,7 +864,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
856
864
|
}
|
|
857
865
|
}
|
|
858
866
|
const keyNames = computedKeyNames(held.symbols);
|
|
859
|
-
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, keyed, } of walked) {
|
|
867
|
+
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, parameterWrites, keyed, } of walked) {
|
|
860
868
|
const test = testFile.get(file) === true;
|
|
861
869
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
862
870
|
const markup = isComponentFile(file);
|
|
@@ -874,6 +882,17 @@ export function references(project, held, targetRoot, options) {
|
|
|
874
882
|
test,
|
|
875
883
|
});
|
|
876
884
|
}
|
|
885
|
+
// The constructor stores its argument in the member a parameter property declares.
|
|
886
|
+
for (const { name, from, member } of parameterWrites) {
|
|
887
|
+
found.push({
|
|
888
|
+
from,
|
|
889
|
+
to: member,
|
|
890
|
+
position: renderPosition(file, root, name.getStart()),
|
|
891
|
+
use: "write",
|
|
892
|
+
resolution: "syntax",
|
|
893
|
+
test,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
877
896
|
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
878
897
|
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
879
898
|
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
@@ -1011,15 +1030,14 @@ export function references(project, held, targetRoot, options) {
|
|
|
1011
1030
|
const chained = symbol === UNANSWERED
|
|
1012
1031
|
? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
1013
1032
|
: chains.chainOf(symbol);
|
|
1014
|
-
// The markup may name any export of a module a binding
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
: chained;
|
|
1033
|
+
// The markup may name any export of a module a binding's chain reaches, whether a
|
|
1034
|
+
// namespace import binds it or a named or default import of a namespace re-export.
|
|
1035
|
+
const targets = chained.flatMap((target) => [
|
|
1036
|
+
target,
|
|
1037
|
+
...declaredIn(target.id)
|
|
1038
|
+
.filter((one) => one.exported)
|
|
1039
|
+
.map((one) => ({ ...target, id: one.id })),
|
|
1040
|
+
]);
|
|
1023
1041
|
for (const target of targets) {
|
|
1024
1042
|
found.push({
|
|
1025
1043
|
from: fileId,
|
package/dist/src/report.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
6
|
/** The version of the report schema this analyzer writes a report to. */
|
|
7
|
-
const SCHEMA_VERSION = "8.
|
|
7
|
+
const SCHEMA_VERSION = "8.2.0";
|
|
8
8
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
9
9
|
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
10
10
|
/** The name this analyzer writes every document under and mints every identifier with. */
|
package/dist/src/resolve.js
CHANGED
|
@@ -21,9 +21,9 @@ const PLATFORM_MEMBERS = ["os", "arch", "tags"];
|
|
|
21
21
|
const PROJECT_MEMBER = "project";
|
|
22
22
|
/**
|
|
23
23
|
* One segment of a project path: not empty, not `.` or `..`, and holding no
|
|
24
|
-
* solidus
|
|
24
|
+
* solidus or control character.
|
|
25
25
|
*/
|
|
26
|
-
const PROJECT_SEGMENT = String.raw `(?:[
|
|
26
|
+
const PROJECT_SEGMENT = String.raw `(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)`;
|
|
27
27
|
/**
|
|
28
28
|
* A project path: segments joined by a solidus, so the path is relative, below
|
|
29
29
|
* the root it is read against, and spelled one way on every machine.
|
|
@@ -151,6 +151,39 @@ function readStrings(parent, name, path, label, minItems, allowed) {
|
|
|
151
151
|
}
|
|
152
152
|
return value;
|
|
153
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The target kind, a string or an object naming every language. The walk has
|
|
156
|
+
* already refused an undeclared member, so an object is refused for the first
|
|
157
|
+
* language it leaves out, then for the first language whose value is refused,
|
|
158
|
+
* each in the vocabulary's language order.
|
|
159
|
+
*/
|
|
160
|
+
function readTargetKind(target, label) {
|
|
161
|
+
const value = member(target, "kind");
|
|
162
|
+
const perLanguage = `an object naming the kind of each of ${spell(LANGUAGES)}`;
|
|
163
|
+
if (value === undefined) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
if (!TARGET_KINDS.includes(value)) {
|
|
168
|
+
throw malformed(label, "target.kind", `${JSON.stringify(value)} is not one of ${spell(TARGET_KINDS)}, nor ${perLanguage}`);
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
if (!isRecord(value)) {
|
|
173
|
+
throw malformed(label, "target.kind", `${JSON.stringify(value)} is neither one of ${spell(TARGET_KINDS)} nor ${perLanguage}`);
|
|
174
|
+
}
|
|
175
|
+
const missing = LANGUAGES.find((language) => !Object.hasOwn(value, language));
|
|
176
|
+
if (missing !== undefined) {
|
|
177
|
+
throw malformed(label, `target.kind.${missing}`, `is not set, and ${perLanguage} names each`);
|
|
178
|
+
}
|
|
179
|
+
for (const language of LANGUAGES) {
|
|
180
|
+
const kind = value[language];
|
|
181
|
+
if (typeof kind !== "string" || !TARGET_KINDS.includes(kind)) {
|
|
182
|
+
throw malformed(label, `target.kind.${language}`, `${JSON.stringify(kind)} is not one of ${spell(TARGET_KINDS)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { go: value["go"], ts: value["ts"] };
|
|
186
|
+
}
|
|
154
187
|
/** A component file's extension: a full stop followed by letters and digits. */
|
|
155
188
|
const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
|
|
156
189
|
function readComponentExtensions(ts, label) {
|
|
@@ -194,7 +227,7 @@ function readProject(entry, at, label) {
|
|
|
194
227
|
const project = requireMember(readString(entry, PROJECT_MEMBER, path, label), path, label);
|
|
195
228
|
if (!PROJECT_PATH.test(project)) {
|
|
196
229
|
throw malformed(label, path, `${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
|
|
197
|
-
"none empty, . or .., and no
|
|
230
|
+
"none empty, . or .., and no control character");
|
|
198
231
|
}
|
|
199
232
|
return { shape: "project", id, project };
|
|
200
233
|
}
|
|
@@ -508,7 +541,7 @@ function readDoc(value, label) {
|
|
|
508
541
|
}
|
|
509
542
|
return {
|
|
510
543
|
contractVersion,
|
|
511
|
-
targetKind:
|
|
544
|
+
targetKind: readTargetKind(target, label),
|
|
512
545
|
languages: readStrings(analysis, "languages", "analysis.languages", label, 0, LANGUAGES),
|
|
513
546
|
minConfidence: readEnum(analysis, "min_confidence", "analysis.min_confidence", label, CONFIDENCES),
|
|
514
547
|
generatedFiles: readEnum(analysis, "generated_files", "analysis.generated_files", label, GENERATED_FILES),
|
package/dist/src/schema.js
CHANGED
|
@@ -15,7 +15,10 @@ export const SCHEMA_ROOT = {
|
|
|
15
15
|
kind: "section",
|
|
16
16
|
members: {
|
|
17
17
|
contract_version: leaf,
|
|
18
|
-
target: {
|
|
18
|
+
target: {
|
|
19
|
+
kind: "section",
|
|
20
|
+
members: { kind: { kind: "object", members: { go: leaf, ts: leaf } } },
|
|
21
|
+
},
|
|
19
22
|
analysis: {
|
|
20
23
|
kind: "section",
|
|
21
24
|
members: {
|
|
@@ -205,11 +205,12 @@ export function rowReason(provenance) {
|
|
|
205
205
|
return `recorded by ${provenance.analyzer} ${provenance.version}`;
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
|
-
* The rows of
|
|
209
|
-
* document order and as written, which a write keeps. A document that is
|
|
210
|
-
* or a row in a shape no record takes, is refused rather
|
|
208
|
+
* The rows of a scheme other than `own` that the baseline document `text` holds, in
|
|
209
|
+
* document order and as written, which a write of `own`'s rows keeps. A document that is
|
|
210
|
+
* not a baseline, or a row in a shape no record takes, of any scheme, is refused rather
|
|
211
|
+
* than dropped.
|
|
211
212
|
*/
|
|
212
|
-
export function otherSchemeRows(text) {
|
|
213
|
+
export function otherSchemeRows(text, own = OWN_SCHEME) {
|
|
213
214
|
const refuse = (detail) => {
|
|
214
215
|
throw new Error(`${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`);
|
|
215
216
|
};
|
|
@@ -233,7 +234,7 @@ export function otherSchemeRows(text) {
|
|
|
233
234
|
}
|
|
234
235
|
// readRecord has checked every member is a string and the code and symbol are present.
|
|
235
236
|
const { code, symbol, path, reason } = value;
|
|
236
|
-
if (code === undefined || symbol === undefined || symbol.startsWith(
|
|
237
|
+
if (code === undefined || symbol === undefined || symbol.startsWith(own)) {
|
|
237
238
|
return [];
|
|
238
239
|
}
|
|
239
240
|
return [
|
|
@@ -246,10 +247,32 @@ export function otherSchemeRows(text) {
|
|
|
246
247
|
];
|
|
247
248
|
});
|
|
248
249
|
}
|
|
250
|
+
/** The reference schemes in the order a baseline holds their rows. */
|
|
251
|
+
const SCHEME_ORDER = ["go://", "ts://"];
|
|
252
|
+
function schemeRank(row) {
|
|
253
|
+
return SCHEME_ORDER.findIndex((scheme) => row.symbol.startsWith(scheme));
|
|
254
|
+
}
|
|
255
|
+
/** A surrogate half no other completes, which Go's encoding/json decodes as U+FFFD. */
|
|
256
|
+
const LONE_SURROGATE = /[\ud800-\udfff]/gu;
|
|
257
|
+
/** One string with every lone surrogate written as U+FFFD. */
|
|
258
|
+
function wellFormed(text) {
|
|
259
|
+
return text.replace(LONE_SURROGATE, "\ufffd");
|
|
260
|
+
}
|
|
249
261
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
|
|
262
|
+
* The pieces of a JSON document with U+2028 and U+2029 escaped, as Go's encoding/json
|
|
263
|
+
* writes them. Neither is JSON syntax, so each stands inside a string.
|
|
264
|
+
*/
|
|
265
|
+
function* separatorsEscaped(pieces) {
|
|
266
|
+
for (const piece of pieces) {
|
|
267
|
+
yield piece.replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* A baseline holding one row per finding, in the order given, which is the report's, and
|
|
272
|
+
* the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
|
|
273
|
+
* the order given, so two analyzers writing one document in either order write the same
|
|
274
|
+
* bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
|
|
275
|
+
* provenance as its reason. A finding
|
|
253
276
|
* missing a value a row needs, and an identity missing its name or version, are refused
|
|
254
277
|
* rather than written, because the grammar requires a reason on every row.
|
|
255
278
|
*/
|
|
@@ -264,5 +287,16 @@ export function writeBaseline(findings, provenance, kept = []) {
|
|
|
264
287
|
}
|
|
265
288
|
return { code: found.code, symbol: found.symbol, path: found.path, reason };
|
|
266
289
|
});
|
|
267
|
-
|
|
290
|
+
const rows = [...kept, ...baseline]
|
|
291
|
+
.sort((a, b) => schemeRank(a) - schemeRank(b))
|
|
292
|
+
.map((row) => {
|
|
293
|
+
const { code, symbol, path, reason } = row;
|
|
294
|
+
return {
|
|
295
|
+
code: wellFormed(code),
|
|
296
|
+
symbol: wellFormed(symbol),
|
|
297
|
+
...(path === undefined ? {} : { path: wellFormed(path) }),
|
|
298
|
+
...(reason === undefined ? {} : { reason: wellFormed(reason) }),
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
return separatorsEscaped(jsonChunks({ description: BASELINE_DESCRIPTION, baseline: rows }, 2));
|
|
268
302
|
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* one or more codes and a reason, and binding to every declaration that begins on the line
|
|
4
4
|
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
|
-
import { SyntaxKind } from "@typescript/native/ast";
|
|
7
|
-
import { nodeKey } from "./inventory.js";
|
|
6
|
+
import { isConstructorDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
8
8
|
import { byPosition, positionKey, renderPosition } from "./position.js";
|
|
9
9
|
import { signatureOf } from "./signatures.js";
|
|
10
|
-
import { SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
|
|
10
|
+
import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
|
|
11
11
|
/**
|
|
12
12
|
* The three expressions the grammar applies to the text of each line comment, in this
|
|
13
13
|
* order: a comment in the namespace, a well-formed directive, and a directive that lacks
|
|
@@ -132,37 +132,53 @@ function lineComments(file) {
|
|
|
132
132
|
}
|
|
133
133
|
return found;
|
|
134
134
|
}
|
|
135
|
+
function bindingKey(one) {
|
|
136
|
+
return `${one.declared?.id ?? ""}\u0000${one.part.id}`;
|
|
137
|
+
}
|
|
135
138
|
/**
|
|
136
139
|
* The declarations of one file by the line each begins on, the line of its first token: a
|
|
137
140
|
* decorator, a modifier or the keyword. A variable's first token is its statement's
|
|
138
141
|
* keyword, and a variable written on a later line of the statement also begins there. A
|
|
139
|
-
* function also begins, for a directive, on the line of each of its parameters
|
|
142
|
+
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
143
|
+
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
140
144
|
*/
|
|
141
145
|
function declarationLines(file, held, symbols) {
|
|
142
146
|
const below = new Map();
|
|
143
147
|
const add = (offset, one) => {
|
|
144
148
|
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
145
149
|
const at = below.get(line) ?? [];
|
|
146
|
-
if (!at.some((known) => known
|
|
150
|
+
if (!at.some((known) => bindingKey(known) === bindingKey(one))) {
|
|
147
151
|
at.push(one);
|
|
148
152
|
}
|
|
149
153
|
below.set(line, at);
|
|
150
154
|
};
|
|
151
|
-
const
|
|
155
|
+
const declaredAt = (node) => {
|
|
152
156
|
const id = held.declarations.get(nodeKey(file, node));
|
|
153
|
-
|
|
157
|
+
return id === undefined ? undefined : symbols.get(id);
|
|
158
|
+
};
|
|
159
|
+
const visit = (node) => {
|
|
160
|
+
const one = declaredAt(node);
|
|
161
|
+
const owner = isConstructorDeclaration(node) ? declaredAt(node.parent) : undefined;
|
|
162
|
+
if (owner !== undefined && isConstructorDeclaration(node)) {
|
|
163
|
+
for (const at of [node, ...node.parameters.filter((one) => !isParameterProperty(one))]) {
|
|
164
|
+
add(at.pos, { declared: undefined, part: owner });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const holder = one !== undefined && isParameterProperty(node) ? declaredAt(node.parent.parent) : one;
|
|
168
|
+
if (one !== undefined && holder !== undefined) {
|
|
169
|
+
add(node.pos, { declared: one, part: holder });
|
|
170
|
+
}
|
|
154
171
|
if (one !== undefined) {
|
|
155
|
-
add(node.pos, one);
|
|
156
172
|
// A parameter on a later line of a wrapped signature takes its directive on the
|
|
157
173
|
// line above it, inside the parameter list, and the directive binds the function.
|
|
158
174
|
for (const parameter of signatureOf(node)?.parameters ?? []) {
|
|
159
|
-
add(parameter.pos, one);
|
|
175
|
+
add(parameter.pos, { declared: one, part: one });
|
|
160
176
|
}
|
|
161
177
|
const list = node.parent;
|
|
162
178
|
if (node.kind === SyntaxKind.VariableDeclaration &&
|
|
163
179
|
list.kind === SyntaxKind.VariableDeclarationList &&
|
|
164
180
|
list.parent.kind === SyntaxKind.VariableStatement) {
|
|
165
|
-
add(list.parent.pos, one);
|
|
181
|
+
add(list.parent.pos, { declared: one, part: one });
|
|
166
182
|
}
|
|
167
183
|
}
|
|
168
184
|
node.forEachChild(visit);
|
|
@@ -223,7 +239,7 @@ export function inlineSuppressions(perProject) {
|
|
|
223
239
|
continue;
|
|
224
240
|
}
|
|
225
241
|
for (const one of written.bound) {
|
|
226
|
-
if (!known.bound.some((other) => other
|
|
242
|
+
if (!known.bound.some((other) => bindingKey(other) === bindingKey(one))) {
|
|
227
243
|
known.bound.push(one);
|
|
228
244
|
}
|
|
229
245
|
}
|
|
@@ -251,8 +267,15 @@ export function inlineSuppressions(perProject) {
|
|
|
251
267
|
}
|
|
252
268
|
break;
|
|
253
269
|
case "directive": {
|
|
254
|
-
const declared = [...bound].sort((a, b) => byPosition(a.position, b.position));
|
|
255
270
|
for (const code of directive.codes) {
|
|
271
|
+
const targets = new Map();
|
|
272
|
+
for (const one of bound) {
|
|
273
|
+
const target = isPartKind(code) ? one.part : one.declared;
|
|
274
|
+
if (target !== undefined) {
|
|
275
|
+
targets.set(target.id, target);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const declared = [...targets.values()].sort((a, b) => byPosition(a.position, b.position));
|
|
256
279
|
const base = {
|
|
257
280
|
code,
|
|
258
281
|
path: site.path,
|
package/dist/src/suppress.js
CHANGED
|
@@ -34,9 +34,10 @@ export function siteText(site) {
|
|
|
34
34
|
export const CODE_FORM = /^DS[0-9]{4}$/u;
|
|
35
35
|
/**
|
|
36
36
|
* A path inside the target: relative to the target root, the solidus as separator, no
|
|
37
|
-
* leading `./`, no trailing solidus, no `.` or `..` segment, never absolute
|
|
37
|
+
* leading `./`, no trailing solidus, no `.` or `..` segment, never absolute, and no
|
|
38
|
+
* control character; a backslash is a character of a name.
|
|
38
39
|
*/
|
|
39
|
-
export const PATH_EXPRESSION = String.raw `^(?:[
|
|
40
|
+
export const PATH_EXPRESSION = String.raw `^(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)(?:/(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+))*$`;
|
|
40
41
|
const PATH_FORM = new RegExp(PATH_EXPRESSION, "u");
|
|
41
42
|
/** Whether one value is a path in the form an entry's `path` takes. */
|
|
42
43
|
export function isEntryPath(value) {
|
package/dist/src/type-errors.js
CHANGED
|
@@ -84,7 +84,8 @@ function declaringManifest(host, dir, targetRoot, name) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
// The target root is not below itself, so the walk stops after reading its manifest.
|
|
88
|
+
if (relativePath(targetRoot, at) === undefined) {
|
|
88
89
|
return undefined;
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { runAnalysis, runBaselineWrite, unansweredCounts, } from "../analysis.js";
|
|
2
|
-
import { ConfigError } from "../config.js";
|
|
2
|
+
import { ConfigError, kindOf } from "../config.js";
|
|
3
3
|
import { CONFORMANCE, DECLARED_GAPS } from "../conformance.js";
|
|
4
4
|
import { moduleIdentity } from "../consumers.js";
|
|
5
5
|
import { analyzerProvenance, verdictOf } from "../findings-pass.js";
|
|
6
6
|
import { joinPath, normalizePath, relativePath, resolvePath } from "../paths.js";
|
|
7
7
|
import { partialNotes } from "../partial.js";
|
|
8
|
-
import { buildReport, capFindings, sortFindings, } from "../report.js";
|
|
8
|
+
import { buildReport, LANGUAGE, capFindings, sortFindings, } from "../report.js";
|
|
9
9
|
import { json } from "../reporters/json.js";
|
|
10
10
|
import { RENDERINGS } from "../reporters/reporters.js";
|
|
11
11
|
import { parseTemplate, TemplateError } from "../reporters/template.js";
|
|
@@ -188,7 +188,11 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
|
|
|
188
188
|
version,
|
|
189
189
|
conformance: CONFORMANCE,
|
|
190
190
|
declaredGaps: DECLARED_GAPS,
|
|
191
|
-
target: {
|
|
191
|
+
target: {
|
|
192
|
+
kind: kindOf(kind, LANGUAGE),
|
|
193
|
+
root,
|
|
194
|
+
identity: moduleIdentity(host, scoped.target),
|
|
195
|
+
},
|
|
192
196
|
configurations: configurationsOf(run.projects, targetRoot),
|
|
193
197
|
notBuilt: notBuiltOf(run.notBuilt, targetRoot),
|
|
194
198
|
loaded: run.consumers.map((consumer) => ({
|
package/dist/src/version.js
CHANGED
|
@@ -19,7 +19,6 @@ const DEPENDENCY_MEMBERS = [
|
|
|
19
19
|
"optionalDependencies",
|
|
20
20
|
];
|
|
21
21
|
const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
|
|
22
|
-
const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
|
|
23
22
|
function isRecord(value) {
|
|
24
23
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
24
|
}
|
|
@@ -39,7 +38,7 @@ export function packageNameOf(specifier) {
|
|
|
39
38
|
return first;
|
|
40
39
|
}
|
|
41
40
|
function inside(dir, path) {
|
|
42
|
-
return
|
|
41
|
+
return relativePath(dir, path) !== undefined;
|
|
43
42
|
}
|
|
44
43
|
/** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
|
|
45
44
|
function exportsEntry(exportsField, subpath) {
|
|
@@ -199,12 +198,10 @@ export function workspaceResolver(host, workspace) {
|
|
|
199
198
|
return found;
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return found;
|
|
207
|
-
}
|
|
201
|
+
const beside = emittedFrom(path, { sourceDirs: [] });
|
|
202
|
+
const found = beside.find((candidate) => host.kindOf(candidate) === "file");
|
|
203
|
+
if (found !== undefined) {
|
|
204
|
+
return found;
|
|
208
205
|
}
|
|
209
206
|
return compiled(member, path) && host.kindOf(path) === "file" ? path : undefined;
|
|
210
207
|
};
|
package/dist/src/workspace.js
CHANGED
|
@@ -198,14 +198,14 @@ function memberDirectories(host, root, patterns) {
|
|
|
198
198
|
}
|
|
199
199
|
return [...included].filter((dir) => {
|
|
200
200
|
const below = relativePath(root, dir);
|
|
201
|
-
const segments = below === undefined
|
|
201
|
+
const segments = below === undefined ? [] : below.split("/");
|
|
202
202
|
return dir === root || !excluded.some((pattern) => pathMatches(pattern, segments));
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
/** Whether a workspace's patterns name one directory below its root, read without listing any. */
|
|
206
206
|
function namesDirectory(root, patterns, dir) {
|
|
207
207
|
const below = relativePath(root, dir);
|
|
208
|
-
if (below === undefined || below
|
|
208
|
+
if (below === undefined || below.startsWith("..")) {
|
|
209
209
|
return false;
|
|
210
210
|
}
|
|
211
211
|
const path = below.split("/");
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0-dev.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@stryker-mutator/core": "10.0.0",
|
|
41
41
|
"@stryker-mutator/typescript-checker": "10.0.0",
|
|
42
42
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
43
|
-
"@types/node": "24.19.
|
|
43
|
+
"@types/node": "24.19.2",
|
|
44
44
|
"eslint": "10.12.0",
|
|
45
45
|
"fast-check": "4.10.2",
|
|
46
46
|
"prettier": "3.9.9",
|
package/src/alias-chain.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isNamespaceImport,
|
|
15
15
|
} from "@typescript/native/ast";
|
|
16
16
|
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/sync";
|
|
17
|
-
import { declarationsByName, nodeKey, type Inventory } from "./inventory.ts";
|
|
17
|
+
import { declarationsByName, isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
18
18
|
import { UNANSWERED, type Answer } from "./query.ts";
|
|
19
19
|
import type { ProjectView } from "./session.ts";
|
|
20
20
|
|
|
@@ -76,7 +76,12 @@ export function aliasChains<Brand>(project: ProjectView<Brand>, held: Inventory)
|
|
|
76
76
|
const node = project.declarationAt(handle)?.node;
|
|
77
77
|
const id =
|
|
78
78
|
node === undefined ? undefined : held.declarations.get(nodeKey(node.getSourceFile(), node));
|
|
79
|
-
|
|
79
|
+
// A parameter property's node declares the parameter too, whose reads are no member's.
|
|
80
|
+
const parameter =
|
|
81
|
+
node !== undefined &&
|
|
82
|
+
isParameterProperty(node) &&
|
|
83
|
+
(symbol.flags & SymbolFlags.Property) === 0;
|
|
84
|
+
if (id !== undefined && !parameter && !ids.includes(id)) {
|
|
80
85
|
ids.push(id);
|
|
81
86
|
}
|
|
82
87
|
}
|
package/src/analysis.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { lineOf, readComponent } from "./component-files.ts";
|
|
10
|
-
import type
|
|
10
|
+
import { kindOf, type Config, type Provenance } from "./config.ts";
|
|
11
11
|
import {
|
|
12
12
|
consumerLoads,
|
|
13
13
|
consumerReferences,
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.ts";
|
|
84
84
|
import type { UnansweredQuestion } from "./query.ts";
|
|
85
85
|
import { byPosition, isComponentFile, type Position } from "./position.ts";
|
|
86
|
-
import type
|
|
86
|
+
import { LANGUAGE, type TypeErrorSkip, type UnansweredCount } from "./report.ts";
|
|
87
87
|
import { SetupError, setupLine, type SetupFailure } from "./setup-failure.ts";
|
|
88
88
|
import {
|
|
89
89
|
pathsOf,
|
|
@@ -377,7 +377,7 @@ function readProjects<Answer>(
|
|
|
377
377
|
conventions,
|
|
378
378
|
testFiles: config.ts.testFiles,
|
|
379
379
|
parse,
|
|
380
|
-
publishedAPI: config.targetKind === "library",
|
|
380
|
+
publishedAPI: config.targetKind !== "" && kindOf(config.targetKind, LANGUAGE) === "library",
|
|
381
381
|
publishedMembers: new Set(
|
|
382
382
|
(workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir),
|
|
383
383
|
),
|
package/src/config.ts
CHANGED
|
@@ -13,6 +13,14 @@ export const REPOSITORY_DOCUMENT = "deadset.json";
|
|
|
13
13
|
*/
|
|
14
14
|
export type TargetKind = "application" | "library";
|
|
15
15
|
|
|
16
|
+
/** The target kind as a source writes it: one kind for every language, or one per language. */
|
|
17
|
+
export type TargetKindSetting = TargetKind | { readonly go: TargetKind; readonly ts: TargetKind };
|
|
18
|
+
|
|
19
|
+
/** The kind a target kind setting gives one language's declarations. */
|
|
20
|
+
export function kindOf(setting: TargetKindSetting, language: Language): TargetKind {
|
|
21
|
+
return typeof setting === "string" ? setting : setting[language];
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
/** The lowest reachability class a finding must reach to be reported. */
|
|
17
25
|
export type Confidence = "certain" | "probable" | "possible";
|
|
18
26
|
|
|
@@ -183,7 +191,7 @@ export interface TSSection {
|
|
|
183
191
|
*/
|
|
184
192
|
export interface Config {
|
|
185
193
|
readonly contractVersion: string;
|
|
186
|
-
readonly targetKind:
|
|
194
|
+
readonly targetKind: TargetKindSetting | "";
|
|
187
195
|
readonly analysis: Analysis;
|
|
188
196
|
readonly consumersComplete: boolean;
|
|
189
197
|
readonly rootPatterns: readonly string[];
|