@cplieger/deadset-ts 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/analysis.js +79 -37
- package/dist/src/config.js +3 -0
- package/dist/src/configuration-files.js +24 -25
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +46 -18
- package/dist/src/convention-rows.js +7 -0
- package/dist/src/conventions.js +97 -33
- package/dist/src/dependencies.js +8 -0
- package/dist/src/entry-point-gaps.js +10 -26
- package/dist/src/entry-points.js +4 -0
- package/dist/src/exempt.js +23 -8
- package/dist/src/file-facts.js +23 -20
- package/dist/src/findings/completion.js +27 -11
- package/dist/src/findings/interfaces.js +2 -6
- package/dist/src/findings/intra-function.js +15 -3
- package/dist/src/findings/unused-declarations.js +5 -6
- package/dist/src/findings/visibility-narrowing.js +6 -1
- package/dist/src/generated-file.js +72 -12
- package/dist/src/graph.js +6 -5
- package/dist/src/html-entries.js +137 -0
- package/dist/src/inventory.js +13 -3
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +34 -5
- package/dist/src/matrix.js +9 -3
- package/dist/src/namespace-objects.js +158 -0
- package/dist/src/outside-tests.js +139 -0
- package/dist/src/overrides.js +104 -0
- package/dist/src/paths.js +17 -0
- package/dist/src/references.js +150 -4
- package/dist/src/roots.js +172 -39
- package/dist/src/session.js +4 -3
- package/dist/src/sweep.js +17 -0
- package/dist/src/template-field.js +60 -3
- package/dist/src/test-support.js +35 -23
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +122 -49
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +29 -25
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +51 -20
- package/src/convention-rows.ts +15 -0
- package/src/conventions.ts +119 -30
- package/src/dependencies.ts +9 -0
- package/src/entry-point-gaps.ts +11 -28
- package/src/entry-points.ts +7 -1
- package/src/exempt.ts +29 -12
- package/src/file-facts.ts +25 -21
- package/src/findings/completion.ts +31 -11
- package/src/findings/interfaces.ts +2 -9
- package/src/findings/intra-function.ts +17 -3
- package/src/findings/unused-declarations.ts +5 -8
- package/src/findings/visibility-narrowing.ts +7 -1
- package/src/generated-file.ts +80 -12
- package/src/graph.ts +9 -8
- package/src/html-entries.ts +170 -0
- package/src/inventory.ts +15 -3
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +49 -7
- package/src/matrix.ts +21 -3
- package/src/namespace-objects.ts +216 -0
- package/src/outside-tests.ts +191 -0
- package/src/overrides.ts +136 -0
- package/src/paths.ts +18 -0
- package/src/references.ts +206 -9
- package/src/roots.ts +210 -53
- package/src/session.ts +4 -2
- package/src/sweep.ts +19 -0
- package/src/template-field.ts +65 -3
- package/src/test-support.ts +34 -22
- package/src/version.ts +1 -1
package/dist/src/conventions.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* directory a configuration property moves read from the property's literal value.
|
|
6
6
|
* No configuration file is run.
|
|
7
7
|
*/
|
|
8
|
-
import { isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
9
|
import { lineOf } from "./component-files.js";
|
|
10
10
|
import { CONVENTION_ROWS, } from "./convention-rows.js";
|
|
11
11
|
import { globExpression } from "./glob.js";
|
|
@@ -288,12 +288,32 @@ function objectOf(node, declared, throughCalls, seen = new Set()) {
|
|
|
288
288
|
}
|
|
289
289
|
return at;
|
|
290
290
|
}
|
|
291
|
+
/** The text of a string literal or a template literal with no substitution, or undefined. */
|
|
292
|
+
function literalText(node) {
|
|
293
|
+
const at = unwrapped(node);
|
|
294
|
+
return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The literal texts one value at a path's end writes: the literal itself, or, where
|
|
298
|
+
* `elements`, each element of an array literal of literals. Undefined for any other value.
|
|
299
|
+
*/
|
|
300
|
+
function literalTexts(held, elements) {
|
|
301
|
+
const one = literalText(held);
|
|
302
|
+
if (one !== undefined) {
|
|
303
|
+
return [one];
|
|
304
|
+
}
|
|
305
|
+
if (!elements || !isArrayLiteralExpression(held)) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
const texts = held.elements.map(literalText);
|
|
309
|
+
return texts.every((text) => text !== undefined) ? texts : undefined;
|
|
310
|
+
}
|
|
291
311
|
/**
|
|
292
312
|
* Reads the values at one path of keys below one object literal into `values`, and
|
|
293
313
|
* answers the first member that does not write its value out: a spread, a value that is
|
|
294
314
|
* not a literal at the path's end, or one that is not an object literal before it.
|
|
295
315
|
*/
|
|
296
|
-
function readPath(object, keys, values) {
|
|
316
|
+
function readPath(object, keys, values, elements) {
|
|
297
317
|
const [key, ...rest] = keys;
|
|
298
318
|
for (const member of object.properties) {
|
|
299
319
|
if (isSpreadAssignment(member) || computesKey(member)) {
|
|
@@ -304,16 +324,17 @@ function readPath(object, keys, values) {
|
|
|
304
324
|
}
|
|
305
325
|
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
306
326
|
if (rest.length === 0) {
|
|
307
|
-
|
|
327
|
+
const texts = held === undefined ? undefined : literalTexts(held, elements);
|
|
328
|
+
if (texts === undefined) {
|
|
308
329
|
return member;
|
|
309
330
|
}
|
|
310
|
-
values.push(
|
|
331
|
+
values.push(...texts);
|
|
311
332
|
}
|
|
312
333
|
else {
|
|
313
334
|
if (held === undefined || !isObjectLiteralExpression(held)) {
|
|
314
335
|
return member;
|
|
315
336
|
}
|
|
316
|
-
const failed = readPath(held, rest, values);
|
|
337
|
+
const failed = readPath(held, rest, values, elements);
|
|
317
338
|
if (failed !== undefined) {
|
|
318
339
|
return failed;
|
|
319
340
|
}
|
|
@@ -324,11 +345,12 @@ function readPath(object, keys, values) {
|
|
|
324
345
|
/**
|
|
325
346
|
* Every value one file writes at one property's path inside the object it is read from:
|
|
326
347
|
* the options argument of every call `call` names, or the file's default export. A string
|
|
327
|
-
* literal or a template literal with no substitution is the value
|
|
328
|
-
*
|
|
329
|
-
*
|
|
348
|
+
* literal or a template literal with no substitution is the value, and so is each element
|
|
349
|
+
* of an array literal of them where `elements`. A read object, or an object along the
|
|
350
|
+
* path, that is not written out as an object literal, or that spreads another object into
|
|
351
|
+
* itself, is not a literal, and neither is any other value at the path.
|
|
330
352
|
*/
|
|
331
|
-
export function readProperty(file, property, call) {
|
|
353
|
+
export function readProperty(file, property, call, elements = false) {
|
|
332
354
|
const declared = declaredValues(file);
|
|
333
355
|
const objects = call === undefined
|
|
334
356
|
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
@@ -339,7 +361,7 @@ export function readProperty(file, property, call) {
|
|
|
339
361
|
continue;
|
|
340
362
|
}
|
|
341
363
|
const notLiteral = isObjectLiteralExpression(object)
|
|
342
|
-
? readPath(object, property.split("."), values)
|
|
364
|
+
? readPath(object, property.split("."), values, elements)
|
|
343
365
|
: object;
|
|
344
366
|
if (notLiteral !== undefined) {
|
|
345
367
|
return { values, notLiteral };
|
|
@@ -392,29 +414,18 @@ function configurationFiles(host, dir, glob) {
|
|
|
392
414
|
function resolveMove(host, parse, dir, row, move, resolved) {
|
|
393
415
|
const set = [];
|
|
394
416
|
for (const reading of move.readings) {
|
|
395
|
-
for (const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
const file = parse(path, text);
|
|
406
|
-
const read = readProperty(file, reading.property, reading.call);
|
|
407
|
-
if (read.notLiteral !== undefined) {
|
|
408
|
-
const line = lineOf(text, read.notLiteral.getStart());
|
|
409
|
-
return {
|
|
410
|
-
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
411
|
-
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
412
|
-
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
413
|
-
`files in ts.entry_files`,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
set.push(...read.values);
|
|
417
|
+
for (const { name, text, file } of readingFiles(host, parse, dir, reading.files)) {
|
|
418
|
+
const read = readProperty(file, reading.property, reading.call);
|
|
419
|
+
if (read.notLiteral !== undefined) {
|
|
420
|
+
const line = lineOf(text, read.notLiteral.getStart());
|
|
421
|
+
return {
|
|
422
|
+
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
423
|
+
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
424
|
+
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
425
|
+
`files in ts.entry_files`,
|
|
426
|
+
};
|
|
417
427
|
}
|
|
428
|
+
set.push(...read.values);
|
|
418
429
|
}
|
|
419
430
|
}
|
|
420
431
|
const dirs = [];
|
|
@@ -427,6 +438,55 @@ function resolveMove(host, parse, dir, row, move, resolved) {
|
|
|
427
438
|
}
|
|
428
439
|
return { dirs: [...new Set(dirs)] };
|
|
429
440
|
}
|
|
441
|
+
/** The texts each configuration file a reading names holds, parsed, in name order. */
|
|
442
|
+
function readingFiles(host, parse, dir, files) {
|
|
443
|
+
const found = [];
|
|
444
|
+
for (const glob of files) {
|
|
445
|
+
for (const name of configurationFiles(host, dir, glob)) {
|
|
446
|
+
const path = joinPath(dir, name);
|
|
447
|
+
let text;
|
|
448
|
+
try {
|
|
449
|
+
text = host.readFile(path);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
found.push({ name, text, file: parse(path, text) });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return found;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The dependencies one manifest declares that an applied row's short names use: the
|
|
461
|
+
* package each literal value stands for, and, for a value that is not a literal, every
|
|
462
|
+
* declared dependency the row's package name stands for with some value.
|
|
463
|
+
*/
|
|
464
|
+
function shortNameUses(host, parse, dir, manifest, readings) {
|
|
465
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
466
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
467
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
468
|
+
}));
|
|
469
|
+
const used = new Set();
|
|
470
|
+
for (const reading of readings) {
|
|
471
|
+
const [before = "", after = ""] = reading.package.split("{}");
|
|
472
|
+
for (const { file } of readingFiles(host, parse, dir, reading.files)) {
|
|
473
|
+
const read = readProperty(file, reading.property, reading.call, true);
|
|
474
|
+
for (const value of read.values) {
|
|
475
|
+
used.add(`${before}${value}${after}`);
|
|
476
|
+
}
|
|
477
|
+
if (read.notLiteral !== undefined) {
|
|
478
|
+
for (const name of declared) {
|
|
479
|
+
if (name.length >= before.length + after.length &&
|
|
480
|
+
name.startsWith(before) &&
|
|
481
|
+
name.endsWith(after)) {
|
|
482
|
+
used.add(name);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
489
|
+
}
|
|
430
490
|
/** Two strings ordered bytewise. */
|
|
431
491
|
function compare(a, b) {
|
|
432
492
|
if (a === b) {
|
|
@@ -443,6 +503,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
443
503
|
const globs = [];
|
|
444
504
|
const generated = [];
|
|
445
505
|
const failures = [];
|
|
506
|
+
const uses = new Set();
|
|
446
507
|
for (const manifest of manifests) {
|
|
447
508
|
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
448
509
|
const versions = new Map();
|
|
@@ -500,6 +561,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
500
561
|
version,
|
|
501
562
|
manifest: manifest.path,
|
|
502
563
|
});
|
|
564
|
+
for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
|
|
565
|
+
uses.add(name);
|
|
566
|
+
}
|
|
503
567
|
const excludes = [
|
|
504
568
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
505
569
|
].map(globExpression);
|
|
@@ -520,7 +584,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
520
584
|
unique.sort((a, b) => compare(a.name, b.name) ||
|
|
521
585
|
compare(a.manifest, b.manifest) ||
|
|
522
586
|
compare(JSON.stringify(a), JSON.stringify(b)));
|
|
523
|
-
return { applied: unique, globs, generated, failures };
|
|
587
|
+
return { applied: unique, globs, generated, failures, uses: [...uses].sort(compare) };
|
|
524
588
|
}
|
|
525
589
|
/**
|
|
526
590
|
* Every own file of one project below a directory an applied row names as generated, by
|
package/dist/src/dependencies.js
CHANGED
|
@@ -24,6 +24,14 @@ const SECTIONS = [
|
|
|
24
24
|
];
|
|
25
25
|
/** The owner of a use no declaration of the inventory holds: a configuration's. */
|
|
26
26
|
const PROJECT_OWNER = "";
|
|
27
|
+
/** What a configuration's own reading needs: packages no deletion of a declaration removes. */
|
|
28
|
+
export function configurationNeeds(packages) {
|
|
29
|
+
return {
|
|
30
|
+
packages: new Set(packages),
|
|
31
|
+
uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
|
|
32
|
+
unanswered: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
27
35
|
function isRecord(value) {
|
|
28
36
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
37
|
}
|
|
@@ -13,9 +13,9 @@ const EVERY_CONVENTION = "every file it loads by its own convention beyond its c
|
|
|
13
13
|
const UNREAD = "No rule reads it beyond a configuration file the file-name form or a string enters, so a " +
|
|
14
14
|
"file only it loads is reported as never imported unless ts.entry_files or roots.patterns " +
|
|
15
15
|
"names it.";
|
|
16
|
-
/** Why a configuration's file names that are not
|
|
17
|
-
const
|
|
18
|
-
"
|
|
16
|
+
/** Why a configuration's file names that are not literals are declined. */
|
|
17
|
+
const NOT_LITERAL = "A string of a configuration file names a file only where it is a literal, so a file a " +
|
|
18
|
+
"computed value names is reported as never imported.";
|
|
19
19
|
/** The tools whose own conventions are not read. */
|
|
20
20
|
const UNREAD_TOOLS = [
|
|
21
21
|
"angular",
|
|
@@ -211,11 +211,6 @@ const PARTLY_ENTERED = [
|
|
|
211
211
|
reason: "A testMatch that is not a string literal leaves the test-file set unknown, and an " +
|
|
212
212
|
"unknown set enters nothing.",
|
|
213
213
|
},
|
|
214
|
-
{
|
|
215
|
-
tool: "playwright",
|
|
216
|
-
convention: "a globalSetup or globalTeardown file named without a leading ./ or ../",
|
|
217
|
-
reason: NOT_RELATIVE,
|
|
218
|
-
},
|
|
219
214
|
{
|
|
220
215
|
tool: "playwright",
|
|
221
216
|
convention: "the testDir and testMatch of an entry in projects",
|
|
@@ -272,20 +267,14 @@ const PARTLY_ENTERED = [
|
|
|
272
267
|
},
|
|
273
268
|
{
|
|
274
269
|
tool: "vite",
|
|
275
|
-
convention: "an entry point its configuration
|
|
276
|
-
reason:
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
tool: "vite",
|
|
280
|
-
convention: "the module scripts an index.html loads",
|
|
281
|
-
reason: "No rule reads an HTML file, so a script only index.html loads is reported as never " +
|
|
282
|
-
"imported unless ts.entry_files names it.",
|
|
270
|
+
convention: "an entry point its configuration computes",
|
|
271
|
+
reason: NOT_LITERAL,
|
|
283
272
|
},
|
|
284
273
|
{
|
|
285
274
|
tool: "vitest",
|
|
286
|
-
convention: "the benchmark
|
|
287
|
-
reason: "A test file is one ts.test_files classifies, so a *.bench.*
|
|
288
|
-
"
|
|
275
|
+
convention: "the benchmark files",
|
|
276
|
+
reason: "A test file is one ts.test_files classifies, so a *.bench.* file is entered only " +
|
|
277
|
+
"where a configured pattern names it.",
|
|
289
278
|
},
|
|
290
279
|
{
|
|
291
280
|
tool: "vitest",
|
|
@@ -293,11 +282,6 @@ const PARTLY_ENTERED = [
|
|
|
293
282
|
reason: "A string is read against the configuration file's directory, so a setup file named " +
|
|
294
283
|
"against a root the configuration moves is not entered.",
|
|
295
284
|
},
|
|
296
|
-
{
|
|
297
|
-
tool: "vitest",
|
|
298
|
-
convention: "a setup file named without a leading ./ or ../",
|
|
299
|
-
reason: NOT_RELATIVE,
|
|
300
|
-
},
|
|
301
285
|
{
|
|
302
286
|
tool: "vitest",
|
|
303
287
|
convention: "the test files a configuration's include member names",
|
|
@@ -306,8 +290,8 @@ const PARTLY_ENTERED = [
|
|
|
306
290
|
},
|
|
307
291
|
{
|
|
308
292
|
tool: "webpack",
|
|
309
|
-
convention: "an entry point its configuration
|
|
310
|
-
reason:
|
|
293
|
+
convention: "an entry point its configuration computes",
|
|
294
|
+
reason: NOT_LITERAL,
|
|
311
295
|
},
|
|
312
296
|
];
|
|
313
297
|
/** Two strings ordered bytewise. */
|
package/dist/src/entry-points.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAss
|
|
|
14
14
|
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
15
15
|
import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
|
|
16
16
|
import { globExpression } from "./glob.js";
|
|
17
|
+
import { htmlEntries } from "./html-entries.js";
|
|
17
18
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
18
19
|
import { UNANSWERED } from "./query.js";
|
|
19
20
|
/** The extensions the test runner and the browser-test runner load a configuration from. */
|
|
@@ -406,5 +407,8 @@ export function entryPoints(project, files, testFiles, host, targetRoot, parse)
|
|
|
406
407
|
}
|
|
407
408
|
found.push(...workersOf(project, file, files));
|
|
408
409
|
}
|
|
410
|
+
for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
|
|
411
|
+
found.push({ file, rule: "html-entry", source, exports: true });
|
|
412
|
+
}
|
|
409
413
|
return found;
|
|
410
414
|
}
|
package/dist/src/exempt.js
CHANGED
|
@@ -92,12 +92,18 @@ export function computeExemptions(input, detectors, holding) {
|
|
|
92
92
|
}
|
|
93
93
|
/** The records of {@link computeExemptions}, beside the test-file evidence it set aside. */
|
|
94
94
|
export function detectExemptions(input, detectors, holding) {
|
|
95
|
+
return heldRecords(evidenceRecords(input, detectors, holding.disabled), holding);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Every record one project's detectors found, one per piece of evidence, before the
|
|
99
|
+
* mode decides which of them hold: what {@link heldRecords} reads.
|
|
100
|
+
*/
|
|
101
|
+
export function evidenceRecords(input, detectors, disabled) {
|
|
95
102
|
const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
|
|
96
103
|
const found = [];
|
|
97
|
-
const inTestFiles = [];
|
|
98
104
|
for (const exemptionClass of TS_EXEMPTION_CLASSES) {
|
|
99
105
|
const detect = detectors.get(exemptionClass);
|
|
100
|
-
if (detect === undefined ||
|
|
106
|
+
if (detect === undefined || disabled.has(exemptionClass)) {
|
|
101
107
|
continue;
|
|
102
108
|
}
|
|
103
109
|
const allowed = typescriptVisibilityOf(exemptionClass);
|
|
@@ -105,17 +111,26 @@ export function detectExemptions(input, detectors, holding) {
|
|
|
105
111
|
if (!retainable(allowed, visibility.get(evidence.id))) {
|
|
106
112
|
continue;
|
|
107
113
|
}
|
|
108
|
-
|
|
114
|
+
found.push({
|
|
109
115
|
id: evidence.id,
|
|
110
116
|
class: exemptionClass,
|
|
111
117
|
detail: evidence.detail,
|
|
112
118
|
site: evidence.site,
|
|
113
119
|
...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return found;
|
|
124
|
+
}
|
|
125
|
+
/** The records that hold under one mode, beside the evidence a production mode found in a test file. */
|
|
126
|
+
export function heldRecords(records, holding) {
|
|
127
|
+
const found = [];
|
|
128
|
+
const inTestFiles = [];
|
|
129
|
+
for (const record of records) {
|
|
130
|
+
if (holding.mode.production && holding.testFiles.has(record.site.path)) {
|
|
131
|
+
inTestFiles.push(record);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
119
134
|
found.push(record);
|
|
120
135
|
}
|
|
121
136
|
}
|
package/dist/src/file-facts.js
CHANGED
|
@@ -26,12 +26,10 @@ function lastLine(text) {
|
|
|
26
26
|
return line;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* read is skipped, as discovery skips it
|
|
31
|
-
* with it: a tree the analysis may not enter holds no file it can report.
|
|
29
|
+
* The path below the target root of every source file there, in path order. A directory
|
|
30
|
+
* that cannot be read is skipped, as discovery skips it.
|
|
32
31
|
*/
|
|
33
|
-
export function
|
|
34
|
-
const scopeOf = packageScope(host, targetRoot);
|
|
32
|
+
export function sourcePaths(host, targetRoot) {
|
|
35
33
|
const found = [];
|
|
36
34
|
const pending = [""];
|
|
37
35
|
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
@@ -50,24 +48,29 @@ export function sourceTree(host, targetRoot) {
|
|
|
50
48
|
}
|
|
51
49
|
continue;
|
|
52
50
|
}
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
let text;
|
|
57
|
-
try {
|
|
58
|
-
text = host.readFile(joinPath(targetRoot, path));
|
|
51
|
+
if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
|
|
52
|
+
found.push(path);
|
|
59
53
|
}
|
|
60
|
-
catch {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
found.push({
|
|
64
|
-
path,
|
|
65
|
-
ref: renderRef(scopeOf(path), { of: "module" }),
|
|
66
|
-
endLine: lastLine(text),
|
|
67
|
-
});
|
|
68
54
|
}
|
|
69
55
|
}
|
|
70
|
-
return found.sort((a, b) => (a
|
|
56
|
+
return found.sort((a, b) => (a < b ? -1 : 1));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every source file below the target root, in path order. A file that cannot be read is
|
|
60
|
+
* skipped: a tree the analysis may not enter holds no file it can report.
|
|
61
|
+
*/
|
|
62
|
+
export function sourceTree(host, targetRoot) {
|
|
63
|
+
const scopeOf = packageScope(host, targetRoot);
|
|
64
|
+
return sourcePaths(host, targetRoot).flatMap((path) => {
|
|
65
|
+
let text;
|
|
66
|
+
try {
|
|
67
|
+
text = host.readFile(joinPath(targetRoot, path));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return [{ path, ref: renderRef(scopeOf(path), { of: "module" }), endLine: lastLine(text) }];
|
|
73
|
+
});
|
|
71
74
|
}
|
|
72
75
|
/** Whether one file changes what the program sees by being included in it. */
|
|
73
76
|
function actsByInclusion(file) {
|
|
@@ -39,23 +39,29 @@ function publishedIds(union) {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* The reachability class of one declaration of the run. A declaration of a library's
|
|
42
|
-
* published API, a public member of a published type included,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* in the loaded program, a private member and a `#private` name among them, and is
|
|
48
|
-
* `certain`.
|
|
42
|
+
* published API, a public member of a published type included, is `certain` where the
|
|
43
|
+
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
44
|
+
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
45
|
+
* consumer set. A declaration of test-support code that test code references, outside the
|
|
46
|
+
* test-of-dead-code rule, is `possible`. Every other declaration is `certain`.
|
|
49
47
|
*/
|
|
50
48
|
function reachabilityClasses(input) {
|
|
51
49
|
const published = publishedIds(input.swept.matrix.union);
|
|
50
|
+
const usedByTests = new Set(input.swept.sweep.candidates
|
|
51
|
+
.filter((candidate) => candidate.heldByTests === candidate.id)
|
|
52
|
+
.map((candidate) => candidate.id));
|
|
52
53
|
const { declared } = input.boundary.consumers;
|
|
53
54
|
const outside = declared.length === 0
|
|
54
55
|
? "possible"
|
|
55
56
|
: everyDeclaredLoaded(input.boundary)
|
|
56
57
|
? "certain"
|
|
57
58
|
: "probable";
|
|
58
|
-
return (id) =>
|
|
59
|
+
return (id) => {
|
|
60
|
+
if (usedByTests.has(id)) {
|
|
61
|
+
return "possible";
|
|
62
|
+
}
|
|
63
|
+
return published.has(id) ? outside : "certain";
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
/**
|
|
61
67
|
* The component each dead declaration of the run falls in, and none for a live one, with
|
|
@@ -117,6 +123,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
117
123
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
118
124
|
let minted = swept.sweep.components.length;
|
|
119
125
|
const dead = new Set(swept.sweep.components.map((component) => component.id));
|
|
126
|
+
const fallingWith = new Map();
|
|
127
|
+
for (const { heldByTests } of swept.sweep.candidates) {
|
|
128
|
+
if (heldByTests !== undefined) {
|
|
129
|
+
fallingWith.set(heldByTests, (fallingWith.get(heldByTests) ?? 0) + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
120
132
|
const done = findings.map((finding) => {
|
|
121
133
|
const row = kinds.get(finding.code);
|
|
122
134
|
if (row === undefined) {
|
|
@@ -134,8 +146,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
134
146
|
const judged = !part && union.subject[at] === true ? symbol : undefined;
|
|
135
147
|
const candidate = judged === undefined ? undefined : candidates.get(judged.id);
|
|
136
148
|
const found = symbol === undefined || part ? undefined : componentOf(symbol.id);
|
|
137
|
-
// A test file's declaration is a component member only as a test of dead code
|
|
138
|
-
|
|
149
|
+
// A test file's declaration is a component member only as a test of dead code, and
|
|
150
|
+
// test code keeps the test-support code it references out of every component.
|
|
151
|
+
if (candidate !== undefined &&
|
|
152
|
+
found === undefined &&
|
|
153
|
+
union.test[at] !== true &&
|
|
154
|
+
candidate.heldByTests === undefined) {
|
|
139
155
|
throw new Error(`${candidate.id} is judged dead and falls in no dead component`);
|
|
140
156
|
}
|
|
141
157
|
let component = found;
|
|
@@ -144,7 +160,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
144
160
|
component = {
|
|
145
161
|
id: componentId(minted),
|
|
146
162
|
root: true,
|
|
147
|
-
symbolCount: 1,
|
|
163
|
+
symbolCount: symbol?.kind === "interface" ? (fallingWith.get(symbol.id) ?? 1) : 1,
|
|
148
164
|
deletableLines: 0,
|
|
149
165
|
};
|
|
150
166
|
}
|
|
@@ -26,7 +26,6 @@ function indexed(input) {
|
|
|
26
26
|
return {
|
|
27
27
|
symbolOf,
|
|
28
28
|
inTest: (id) => union.test[union.at(id)] === true,
|
|
29
|
-
inSupport: (id) => union.support[union.at(id)] === true,
|
|
30
29
|
candidateOf: (id) => candidates.get(id),
|
|
31
30
|
implementationsOf: (id) => [...(input.implementations.classes.get(id) ?? [])]
|
|
32
31
|
.map(symbolOf)
|
|
@@ -56,17 +55,14 @@ function findingOf(code, symbol, message, implementations) {
|
|
|
56
55
|
* interface, whose members fall with it unreported; and `DS1203`, a method of a live
|
|
57
56
|
* interface no reference names, unless it is a marker, a method whose every
|
|
58
57
|
* implementation has an empty body. A test file's declaration is neither kind's
|
|
59
|
-
* subject, because a production sweep counts none of the references it can have
|
|
60
|
-
* neither is a test-support declaration test code references, which that code keeps live.
|
|
58
|
+
* subject, because a production sweep counts none of the references it can have.
|
|
61
59
|
*/
|
|
62
60
|
export const interfaces = (input) => {
|
|
63
61
|
const run = indexed(input);
|
|
64
62
|
const found = [];
|
|
65
63
|
for (const candidate of input.swept.sweep.candidates) {
|
|
66
64
|
const symbol = run.symbolOf(candidate.id);
|
|
67
|
-
if (symbol === undefined ||
|
|
68
|
-
run.inTest(candidate.id) ||
|
|
69
|
-
(run.inSupport(candidate.id) && candidate.testRefs > 0)) {
|
|
65
|
+
if (symbol === undefined || run.inTest(candidate.id)) {
|
|
70
66
|
continue;
|
|
71
67
|
}
|
|
72
68
|
if (symbol.kind === "interface") {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KINDS } from "../kinds.js";
|
|
2
2
|
import { UNUSED_PARAMETER, UNUSED_RESULT } from "../intra-function-parts.js";
|
|
3
|
+
import { reachedWithTheTests } from "../sweep.js";
|
|
3
4
|
/** A list of rules as a sentence names them. */
|
|
4
5
|
function spelled(rules) {
|
|
5
6
|
if (rules.length < 2) {
|
|
@@ -39,12 +40,23 @@ function reported(input, part, callersUnknown) {
|
|
|
39
40
|
/**
|
|
40
41
|
* The findings of the intra-function family, `DS1800` to `DS1899`, each about a part of
|
|
41
42
|
* the declaration its reference names, at the part's own position, whatever the sweep
|
|
42
|
-
* decided about
|
|
43
|
-
*
|
|
43
|
+
* decided about a production declaration. A part of test code is reported only where the
|
|
44
|
+
* run that counts test references reaches its declaration and that is no test of dead
|
|
45
|
+
* code. Each message and the finding's details name the other rules that report the kind.
|
|
44
46
|
*/
|
|
45
47
|
export const intraFunction = (input) => {
|
|
46
48
|
const union = input.swept.matrix.union;
|
|
47
49
|
const byId = new Map(union.symbols.map((symbol) => [symbol.id, symbol]));
|
|
50
|
+
const reached = reachedWithTheTests(union, [
|
|
51
|
+
...input.swept.exempt.map((record) => record.id),
|
|
52
|
+
...input.swept.heldByUnanswered,
|
|
53
|
+
]);
|
|
54
|
+
const testsOfDeadCode = new Set(input.swept.sweep.candidates.filter((one) => one.testOfDeadCode).map((one) => one.id));
|
|
55
|
+
const judged = (id) => {
|
|
56
|
+
const at = union.at(id);
|
|
57
|
+
const testCode = union.test[at] === true || union.support[at] === true;
|
|
58
|
+
return !testCode || (reached[at] === true && !testsOfDeadCode.has(id));
|
|
59
|
+
};
|
|
48
60
|
const unknown = new Set(union.rooted.flatMap((root) => {
|
|
49
61
|
const symbol = union.symbols[root.at];
|
|
50
62
|
return symbol === undefined ? [] : [symbol.id];
|
|
@@ -68,7 +80,7 @@ export const intraFunction = (input) => {
|
|
|
68
80
|
for (const part of input.intraFunction.parts) {
|
|
69
81
|
const declaration = byId.get(part.declaration);
|
|
70
82
|
const overlap = KINDS.get(part.code)?.overlap ?? [];
|
|
71
|
-
if (declaration === undefined || !reported(input, part, unknown)) {
|
|
83
|
+
if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
|
|
72
84
|
continue;
|
|
73
85
|
}
|
|
74
86
|
found.push({
|
|
@@ -40,9 +40,9 @@ const TEST_OF_DEAD_CODE = "every target symbol this test references is reported
|
|
|
40
40
|
/**
|
|
41
41
|
* The code one candidate is reported under, or none, each symbol once under the most
|
|
42
42
|
* specific code: a test of dead code first; no other test-file declaration, member of a
|
|
43
|
-
* dead container or enum member nothing names
|
|
44
|
-
* references; then deprecation, a test reference, and the unreferenced
|
|
45
|
-
* unimportable unused export being the unreachable kind's.
|
|
43
|
+
* dead container or enum member nothing names; a test-support declaration test code
|
|
44
|
+
* references as test-only use; then deprecation, a test reference, and the unreferenced
|
|
45
|
+
* codes, an unimportable unused export being the unreachable kind's.
|
|
46
46
|
*/
|
|
47
47
|
function codeOf(judged) {
|
|
48
48
|
const { candidate, symbol } = judged;
|
|
@@ -56,8 +56,8 @@ function codeOf(judged) {
|
|
|
56
56
|
if (symbol.kind === "enum-member" && !referenced) {
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
|
-
if (
|
|
60
|
-
return
|
|
59
|
+
if (candidate.heldByTests === candidate.id) {
|
|
60
|
+
return "DS1004";
|
|
61
61
|
}
|
|
62
62
|
if (candidate.productionRefs === 0 && judged.deprecated) {
|
|
63
63
|
return "DS1006";
|
|
@@ -180,7 +180,6 @@ function familyFindings(input, fallen) {
|
|
|
180
180
|
candidate,
|
|
181
181
|
symbol,
|
|
182
182
|
test: union.test[at] === true,
|
|
183
|
-
support: union.support[at] === true,
|
|
184
183
|
containerDead: containerDead && !fallen,
|
|
185
184
|
deprecated: input.deprecated.has(symbol.id),
|
|
186
185
|
unreachable: unreachable.has(symbol.id),
|
|
@@ -168,7 +168,8 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
168
168
|
* The visibility-narrowing family over one run: `DS1101`, `DS1103` and `DS1104`. A finding
|
|
169
169
|
* about a symbol a declared cross-language edge names is reported here like any other, and
|
|
170
170
|
* the edge's evaluation then holds it pending, because the edge stands for a reference from
|
|
171
|
-
* outside the file and the package whose use is decided in the other language.
|
|
171
|
+
* outside the file and the package whose use is decided in the other language. A
|
|
172
|
+
* declaration of a generated file is reported only where generated files are included.
|
|
172
173
|
*/
|
|
173
174
|
export function narrowings(input) {
|
|
174
175
|
const facts = factsOf(input.swept.matrix.union);
|
|
@@ -176,8 +177,12 @@ export function narrowings(input) {
|
|
|
176
177
|
const closed = input.config.consumersComplete && declared.every((id) => loaded.includes(id));
|
|
177
178
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
178
179
|
const candidates = new Map(input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
180
|
+
const generated = input.config.analysis.generatedFiles === "include" ? undefined : input.files.generated;
|
|
179
181
|
const findings = [];
|
|
180
182
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
183
|
+
if (generated?.has(symbol.position.path) === true) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
181
186
|
const candidate = candidates.get(symbol.id);
|
|
182
187
|
const found = candidate === undefined
|
|
183
188
|
? narrowing(facts, at, symbol, world)
|