@cplieger/deadset-ts 5.2.3 → 6.0.1
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 +28 -5
- package/dist/src/augmentations.js +50 -0
- package/dist/src/components.js +15 -15
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +17 -2
- package/dist/src/conventions.js +82 -1
- package/dist/src/dependencies.js +42 -26
- package/dist/src/entry-points.js +28 -14
- package/dist/src/exempt.js +1 -0
- package/dist/src/findings/completion.js +9 -4
- package/dist/src/findings/dependencies-and-module-machinery.js +2 -2
- package/dist/src/findings/emitters.js +64 -25
- package/dist/src/findings/interfaces.js +4 -3
- package/dist/src/findings/intra-function.js +15 -2
- package/dist/src/findings/reads-and-writes.js +3 -2
- package/dist/src/findings/unused-declarations.js +13 -1
- package/dist/src/findings/visibility-narrowing.js +2 -1
- package/dist/src/findings-pass.js +1 -0
- package/dist/src/graph.js +6 -6
- package/dist/src/implementations.js +1 -1
- package/dist/src/indexed-access.js +215 -0
- package/dist/src/interface-satisfaction.js +85 -23
- package/dist/src/intra-function-parts.js +139 -30
- package/dist/src/inventory.js +6 -1
- package/dist/src/kinds.js +13 -12
- package/dist/src/matrix.js +34 -9
- package/dist/src/module-declarations.js +180 -0
- package/dist/src/outside-tests.js +17 -15
- package/dist/src/overrides.js +70 -9
- package/dist/src/query.js +7 -0
- package/dist/src/ref.js +9 -0
- package/dist/src/references.js +6 -0
- package/dist/src/report.js +6 -1
- package/dist/src/reporters/sarif.js +6 -2
- package/dist/src/reporters/text.js +23 -3
- package/dist/src/roots.js +50 -10
- package/dist/src/suppress-file.js +3 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +82 -0
- package/dist/src/workspace-programs.js +6 -31
- package/jsr.json +1 -1
- package/package.json +8 -2
- package/src/analysis.ts +40 -4
- package/src/augmentations.ts +72 -0
- package/src/components.ts +15 -13
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +25 -3
- package/src/conventions.ts +106 -2
- package/src/dependencies.ts +51 -31
- package/src/entry-points.ts +32 -19
- package/src/exempt.ts +4 -1
- package/src/findings/completion.ts +11 -4
- package/src/findings/dependencies-and-module-machinery.ts +2 -2
- package/src/findings/emitters.ts +89 -27
- package/src/findings/interfaces.ts +7 -6
- package/src/findings/intra-function.ts +20 -2
- package/src/findings/reads-and-writes.ts +7 -2
- package/src/findings/unused-declarations.ts +16 -1
- package/src/findings/visibility-narrowing.ts +2 -1
- package/src/findings-pass.ts +4 -1
- package/src/graph.ts +6 -6
- package/src/implementations.ts +1 -1
- package/src/indexed-access.ts +295 -0
- package/src/interface-satisfaction.ts +118 -27
- package/src/intra-function-parts.ts +196 -35
- package/src/inventory.ts +8 -0
- package/src/kinds.ts +14 -12
- package/src/matrix.ts +51 -8
- package/src/module-declarations.ts +241 -0
- package/src/outside-tests.ts +18 -16
- package/src/overrides.ts +80 -14
- package/src/query.ts +28 -0
- package/src/ref.ts +10 -0
- package/src/references.ts +6 -0
- package/src/report.ts +11 -1
- package/src/reporters/sarif.ts +9 -2
- package/src/reporters/text.ts +25 -3
- package/src/roots.ts +56 -7
- package/src/suppress-file.ts +3 -6
- package/src/sweep.ts +12 -0
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +104 -0
- package/src/workspace-programs.ts +6 -35
package/src/conventions.ts
CHANGED
|
@@ -50,9 +50,11 @@ import {
|
|
|
50
50
|
CONVENTION_ROWS,
|
|
51
51
|
type ConventionRow,
|
|
52
52
|
type DirectoryMove,
|
|
53
|
+
type MoveReading,
|
|
53
54
|
type OptionsCall,
|
|
54
55
|
type ShortNameReading,
|
|
55
56
|
} from "./convention-rows.ts";
|
|
57
|
+
import { documentStrings } from "./configuration-files.ts";
|
|
56
58
|
import { globExpression } from "./glob.ts";
|
|
57
59
|
import type { Host } from "./host.ts";
|
|
58
60
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.ts";
|
|
@@ -108,8 +110,18 @@ export interface Conventions {
|
|
|
108
110
|
readonly globs: readonly ConventionGlob[];
|
|
109
111
|
readonly generated: readonly GeneratedDirectory[];
|
|
110
112
|
readonly failures: readonly ConventionFailure[];
|
|
111
|
-
/** The dependencies the applied rows' short names use, by name, each once. */
|
|
113
|
+
/** The dependencies the applied rows' short names and manifest keys use, by name, each once. */
|
|
112
114
|
readonly uses: readonly string[];
|
|
115
|
+
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
116
|
+
readonly selected: ReadonlyMap<string, Selected>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The strings one configuration file holds at selection keys, which root no file. */
|
|
120
|
+
export interface Selected {
|
|
121
|
+
/** Where each string starts in the file's text. */
|
|
122
|
+
readonly starts: ReadonlySet<number>;
|
|
123
|
+
/** Each string's text, once per occurrence. */
|
|
124
|
+
readonly texts: readonly string[];
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
/** One file a row roots. */
|
|
@@ -520,6 +532,86 @@ function readPath(
|
|
|
520
532
|
return undefined;
|
|
521
533
|
}
|
|
522
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Every string literal one file writes at one property's path inside the objects it is
|
|
537
|
+
* read from, as {@link readProperty} reads them: the value itself, or each element of an
|
|
538
|
+
* array value, whatever else the path holds.
|
|
539
|
+
*/
|
|
540
|
+
function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
541
|
+
const declared = declaredValues(file);
|
|
542
|
+
const objects =
|
|
543
|
+
call === undefined
|
|
544
|
+
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
545
|
+
: optionsArguments(file, call).map((one) =>
|
|
546
|
+
one === undefined ? undefined : objectOf(one, declared, false),
|
|
547
|
+
);
|
|
548
|
+
const found: Node[] = [];
|
|
549
|
+
const visit = (object: Node, keys: readonly string[]): void => {
|
|
550
|
+
if (!isObjectLiteralExpression(object)) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const [key, ...rest] = keys;
|
|
554
|
+
for (const member of object.properties) {
|
|
555
|
+
if (!isPropertyAssignment(member) || keyOf(member) !== key) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
const held = unwrapped(member.initializer);
|
|
559
|
+
if (rest.length > 0) {
|
|
560
|
+
visit(held, rest);
|
|
561
|
+
} else if (literalText(held) !== undefined) {
|
|
562
|
+
found.push(held);
|
|
563
|
+
} else if (isArrayLiteralExpression(held)) {
|
|
564
|
+
found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
for (const object of objects) {
|
|
569
|
+
if (object !== undefined) {
|
|
570
|
+
visit(object, property.split("."));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return found;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
577
|
+
function selectedStrings(
|
|
578
|
+
host: Host,
|
|
579
|
+
parse: ParseSource,
|
|
580
|
+
dir: string,
|
|
581
|
+
readings: readonly MoveReading[],
|
|
582
|
+
into: Map<string, { starts: Set<number>; texts: string[] }>,
|
|
583
|
+
): void {
|
|
584
|
+
for (const reading of readings) {
|
|
585
|
+
for (const { name, file } of readingFiles(host, parse, dir, reading.files)) {
|
|
586
|
+
const path = joinPath(dir, name);
|
|
587
|
+
const held = into.get(path) ?? { starts: new Set<number>(), texts: [] };
|
|
588
|
+
for (const node of stringsAt(file, reading.property, reading.call)) {
|
|
589
|
+
held.starts.add(node.getStart());
|
|
590
|
+
held.texts.push(literalText(node) ?? "");
|
|
591
|
+
}
|
|
592
|
+
into.set(path, held);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* The dependencies one manifest declares that a string at any depth of one of its members
|
|
599
|
+
* names, alone or followed by `/` and a subpath, a member's name aside.
|
|
600
|
+
*/
|
|
601
|
+
function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly string[] {
|
|
602
|
+
if (!isRecord(manifest)) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
const declared = DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
606
|
+
const held = manifest[section];
|
|
607
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
608
|
+
});
|
|
609
|
+
const strings = keys.flatMap((key) => documentStrings(manifest[key]));
|
|
610
|
+
return declared.filter((name) =>
|
|
611
|
+
strings.some((text) => text === name || text.startsWith(`${name}/`)),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
523
615
|
/** What one configuration file states about one property. */
|
|
524
616
|
interface PropertyReading {
|
|
525
617
|
/** The literal values the file sets the property to. */
|
|
@@ -740,6 +832,7 @@ export function readConventions(
|
|
|
740
832
|
const generated: GeneratedDirectory[] = [];
|
|
741
833
|
const failures: ConventionFailure[] = [];
|
|
742
834
|
const uses = new Set<string>();
|
|
835
|
+
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
743
836
|
for (const manifest of manifests) {
|
|
744
837
|
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
745
838
|
const versions = new Map<string, string | undefined>();
|
|
@@ -801,6 +894,10 @@ export function readConventions(
|
|
|
801
894
|
for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
|
|
802
895
|
uses.add(name);
|
|
803
896
|
}
|
|
897
|
+
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
898
|
+
uses.add(name);
|
|
899
|
+
}
|
|
900
|
+
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
804
901
|
const excludes = [
|
|
805
902
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
806
903
|
].map(globExpression);
|
|
@@ -824,7 +921,14 @@ export function readConventions(
|
|
|
824
921
|
compare(a.manifest, b.manifest) ||
|
|
825
922
|
compare(JSON.stringify(a), JSON.stringify(b)),
|
|
826
923
|
);
|
|
827
|
-
return {
|
|
924
|
+
return {
|
|
925
|
+
applied: unique,
|
|
926
|
+
globs,
|
|
927
|
+
generated,
|
|
928
|
+
failures,
|
|
929
|
+
uses: [...uses].sort(compare),
|
|
930
|
+
selected,
|
|
931
|
+
};
|
|
828
932
|
}
|
|
829
933
|
|
|
830
934
|
/**
|
package/src/dependencies.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The dependencies the target's manifest
|
|
3
|
-
* need from outside the target. A file needs a package it
|
|
4
|
-
* augments or references the types of, by name or through the
|
|
5
|
-
* resolves to; a compiler configuration needs one it names, and a
|
|
6
|
-
* tool loads needs each dependency its strings spell. A package one
|
|
7
|
-
* for itself is that dependency's need, not the target's.
|
|
2
|
+
* The dependencies the target's manifest and its workspace members' manifests declare, and
|
|
3
|
+
* what the projects' own files need from outside the target. A file needs a package it
|
|
4
|
+
* imports, re-exports from, augments or references the types of, by name or through the
|
|
5
|
+
* file the specifier resolves to; a compiler configuration needs one it names, and a
|
|
6
|
+
* configuration file a tool loads needs each dependency its strings spell. A package one
|
|
7
|
+
* dependency pulls in for itself is that dependency's need, not the target's.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import type { FindingPosition } from "./finding.ts";
|
|
33
33
|
import type { Host } from "./host.ts";
|
|
34
34
|
import { nodeKey, packageScope, type Inventory } from "./inventory.ts";
|
|
35
|
-
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
|
|
35
|
+
import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
36
36
|
import type { DependencySection, Module } from "./ref.ts";
|
|
37
37
|
import { UNANSWERED } from "./query.ts";
|
|
38
38
|
import type { Handle, ProjectView } from "./session.ts";
|
|
@@ -53,12 +53,14 @@ const SECTIONS: readonly (readonly [string, DependencySection])[] = [
|
|
|
53
53
|
/** The owner of a use no declaration of the inventory holds: a configuration's. */
|
|
54
54
|
const PROJECT_OWNER = "";
|
|
55
55
|
|
|
56
|
-
/** One dependency the
|
|
56
|
+
/** One dependency a manifest the run reads declares. */
|
|
57
57
|
export interface DeclaredDependency {
|
|
58
58
|
readonly name: string;
|
|
59
59
|
readonly section: DependencySection;
|
|
60
60
|
/** Where the manifest writes the dependency's key, below the target root. */
|
|
61
61
|
readonly position: FindingPosition;
|
|
62
|
+
/** The scope the dependency's reference is written in: its manifest's. */
|
|
63
|
+
readonly manifest: Module;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/** What a configuration's own reading needs: packages no deletion of a declaration removes. */
|
|
@@ -97,9 +99,7 @@ export interface ProjectNeeds {
|
|
|
97
99
|
|
|
98
100
|
/** The run's dependency evidence. */
|
|
99
101
|
export interface Dependencies {
|
|
100
|
-
/**
|
|
101
|
-
readonly manifest: Module;
|
|
102
|
-
/** Every declared dependency, in the order the manifest writes them. */
|
|
102
|
+
/** Every declared dependency, manifest by manifest, in the order each writes them. */
|
|
103
103
|
readonly declared: readonly DeclaredDependency[];
|
|
104
104
|
/** The installed copy of each declared dependency the run found, by name. */
|
|
105
105
|
readonly installed: ReadonlyMap<string, InstalledDependency>;
|
|
@@ -608,8 +608,13 @@ function installedOf(host: Host, root: string, name: string): InstalledDependenc
|
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
-
/** Every dependency the manifest text declares, in the order the text writes them. */
|
|
612
|
-
function declaredIn(
|
|
611
|
+
/** Every dependency the manifest text at `path` declares, in the order the text writes them. */
|
|
612
|
+
function declaredIn(
|
|
613
|
+
text: string,
|
|
614
|
+
manifest: Record<string, unknown>,
|
|
615
|
+
path: string,
|
|
616
|
+
scope: Module,
|
|
617
|
+
): DeclaredDependency[] {
|
|
613
618
|
const sections = membersAt(text, skipWhitespace(text, 0));
|
|
614
619
|
const declared: { readonly dependency: DeclaredDependency; readonly at: number }[] = [];
|
|
615
620
|
for (const [member, section] of SECTIONS) {
|
|
@@ -621,7 +626,12 @@ function declaredIn(text: string, manifest: Record<string, unknown>): DeclaredDe
|
|
|
621
626
|
for (const [name, { key }] of membersAt(text, held.value)) {
|
|
622
627
|
const { line, column } = lineAndColumn(text, key);
|
|
623
628
|
declared.push({
|
|
624
|
-
dependency: {
|
|
629
|
+
dependency: {
|
|
630
|
+
name,
|
|
631
|
+
section,
|
|
632
|
+
position: { path, line, column, endLine: line },
|
|
633
|
+
manifest: scope,
|
|
634
|
+
},
|
|
625
635
|
at: key,
|
|
626
636
|
});
|
|
627
637
|
}
|
|
@@ -630,11 +640,10 @@ function declaredIn(text: string, manifest: Record<string, unknown>): DeclaredDe
|
|
|
630
640
|
}
|
|
631
641
|
|
|
632
642
|
/**
|
|
633
|
-
* The run's dependency evidence: the target's manifest
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
* Only the manifest at the target root is read, as for entry points. A manifest the
|
|
643
|
+
* The run's dependency evidence: the target's manifest and each workspace member's in
|
|
644
|
+
* `members` below the target root, read with the position of every dependency each
|
|
645
|
+
* declares, the installed copy of each found from its manifest's directory, what the
|
|
646
|
+
* projects need, and the last uses each deletion candidate holds. A manifest the
|
|
638
647
|
* program cannot read declares nothing.
|
|
639
648
|
*/
|
|
640
649
|
export function dependenciesOf(
|
|
@@ -642,20 +651,31 @@ export function dependenciesOf(
|
|
|
642
651
|
targetRoot: string,
|
|
643
652
|
perProject: readonly ProjectNeeds[],
|
|
644
653
|
candidates: readonly string[],
|
|
654
|
+
members: readonly string[] = [],
|
|
645
655
|
): Dependencies {
|
|
646
|
-
const read = readJSON(host, joinPath(targetRoot, MANIFEST));
|
|
647
|
-
const manifest = isRecord(read?.value) ? read.value : undefined;
|
|
648
|
-
const scope = packageScope(host, targetRoot)(MANIFEST);
|
|
649
|
-
const declared =
|
|
650
|
-
read !== undefined && manifest !== undefined ? declaredIn(read.text, manifest) : [];
|
|
651
|
-
|
|
652
656
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
657
|
+
const scopeOf = packageScope(host, targetRoot);
|
|
658
|
+
const manifests = new Map<string, string>([[root, MANIFEST]]);
|
|
659
|
+
for (const dir of members) {
|
|
660
|
+
const below = relativePath(root, dir);
|
|
661
|
+
if (below !== undefined && !manifests.has(dir)) {
|
|
662
|
+
manifests.set(dir, `${below}/${MANIFEST}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const declared: DeclaredDependency[] = [];
|
|
653
666
|
const installed = new Map<string, InstalledDependency>();
|
|
654
|
-
for (const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
667
|
+
for (const [dir, path] of manifests) {
|
|
668
|
+
const read = readJSON(host, joinPath(dir, MANIFEST));
|
|
669
|
+
if (read === undefined || !isRecord(read.value)) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
|
|
673
|
+
declared.push(dependency);
|
|
674
|
+
if (!installed.has(dependency.name)) {
|
|
675
|
+
const found = installedOf(host, dir, dependency.name);
|
|
676
|
+
if (found !== undefined) {
|
|
677
|
+
installed.set(dependency.name, found);
|
|
678
|
+
}
|
|
659
679
|
}
|
|
660
680
|
}
|
|
661
681
|
}
|
|
@@ -691,5 +711,5 @@ export function dependenciesOf(
|
|
|
691
711
|
);
|
|
692
712
|
}
|
|
693
713
|
}
|
|
694
|
-
return {
|
|
714
|
+
return { declared, installed, needed, lastUses };
|
|
695
715
|
}
|
package/src/entry-points.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
moduleStrings,
|
|
37
37
|
type ParseFile,
|
|
38
38
|
} from "./configuration-files.ts";
|
|
39
|
+
import type { Selected } from "./conventions.ts";
|
|
39
40
|
import { globExpression } from "./glob.ts";
|
|
40
41
|
import type { Host } from "./host.ts";
|
|
41
42
|
import { htmlEntries } from "./html-entries.ts";
|
|
@@ -43,6 +44,7 @@ import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
|
43
44
|
import { UNANSWERED } from "./query.ts";
|
|
44
45
|
import type { ProjectView } from "./session.ts";
|
|
45
46
|
import type { SourceFiles } from "./source-files.ts";
|
|
47
|
+
import { workflowEntries } from "./workflow-steps.ts";
|
|
46
48
|
|
|
47
49
|
/** Which rule entered a file. */
|
|
48
50
|
export type EntryRule =
|
|
@@ -52,14 +54,14 @@ export type EntryRule =
|
|
|
52
54
|
| "configuration-string"
|
|
53
55
|
/** The test runner: its workspace configuration, its setup files and the test files. */
|
|
54
56
|
| "test-runner"
|
|
55
|
-
/** The mutation tester's configuration under a name the configuration form does not cover. */
|
|
56
|
-
| "mutation-testing"
|
|
57
57
|
/** The test files below the browser-test runner's test directory. */
|
|
58
58
|
| "browser-tests"
|
|
59
59
|
/** A worker or service worker a call addresses by a string literal. */
|
|
60
60
|
| "worker"
|
|
61
61
|
/** A file a module script of an HTML file below the target root loads. */
|
|
62
|
-
| "html-entry"
|
|
62
|
+
| "html-entry"
|
|
63
|
+
/** A file a `run:` step of a workflow in the target root's `.github/workflows` names. */
|
|
64
|
+
| "workflow-step";
|
|
63
65
|
|
|
64
66
|
/** One file a rule enters. */
|
|
65
67
|
export interface EntryPoint {
|
|
@@ -90,9 +92,6 @@ interface Convention {
|
|
|
90
92
|
/** The extensions the test runner and the browser-test runner load a configuration from. */
|
|
91
93
|
const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"] as const;
|
|
92
94
|
|
|
93
|
-
/** The extensions the mutation tester's configuration is loaded from. */
|
|
94
|
-
const SCRIPT_EXTENSIONS = ["js", "mjs", "cjs"] as const;
|
|
95
|
-
|
|
96
95
|
/** The tool file names the configuration form does not cover. */
|
|
97
96
|
const CONVENTIONS: readonly Convention[] = [
|
|
98
97
|
{
|
|
@@ -101,12 +100,6 @@ const CONVENTIONS: readonly Convention[] = [
|
|
|
101
100
|
extensions: MODULE_EXTENSIONS,
|
|
102
101
|
testRunner: true,
|
|
103
102
|
},
|
|
104
|
-
{
|
|
105
|
-
rule: "mutation-testing",
|
|
106
|
-
stems: ["stryker.conf", ".stryker.config", ".stryker.conf"],
|
|
107
|
-
extensions: SCRIPT_EXTENSIONS,
|
|
108
|
-
testRunner: false,
|
|
109
|
-
},
|
|
110
103
|
];
|
|
111
104
|
|
|
112
105
|
/** The stem of the browser-test runner's configuration, whose test directory is read. */
|
|
@@ -262,16 +255,35 @@ function isBrowserTestConfig(name: string): boolean {
|
|
|
262
255
|
/**
|
|
263
256
|
* The files the configuration files of one project enter: each configuration module,
|
|
264
257
|
* with what it exports, and each own file a relative string of a configuration module
|
|
265
|
-
* or a JSON configuration file names, read against that file's directory.
|
|
258
|
+
* or a JSON configuration file names, read against that file's directory. A string at a
|
|
259
|
+
* selection key of an applied convention row names no file.
|
|
266
260
|
*/
|
|
267
261
|
function configurationEntries(
|
|
268
262
|
host: Host,
|
|
269
263
|
targetRoot: string,
|
|
270
264
|
files: SourceFiles,
|
|
271
265
|
parse: ParseFile | undefined,
|
|
266
|
+
selected: ReadonlyMap<string, Selected>,
|
|
272
267
|
): readonly EntryPoint[] {
|
|
273
268
|
const { modules, documents, outside } = configurationFiles(host, targetRoot, files, parse);
|
|
274
269
|
const found: EntryPoint[] = [];
|
|
270
|
+
const unselected = (file: SourceFile): readonly string[] => {
|
|
271
|
+
const starts = selected.get(file.fileName)?.starts;
|
|
272
|
+
return moduleStrings(file)
|
|
273
|
+
.filter((one) => starts?.has(one.node.getStart()) !== true)
|
|
274
|
+
.map((one) => one.text);
|
|
275
|
+
};
|
|
276
|
+
const unselectedIn = (path: string, texts: readonly string[]): readonly string[] => {
|
|
277
|
+
const left = [...(selected.get(path)?.texts ?? [])];
|
|
278
|
+
return texts.filter((text) => {
|
|
279
|
+
const at = left.indexOf(text);
|
|
280
|
+
if (at < 0) {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
left.splice(at, 1);
|
|
284
|
+
return false;
|
|
285
|
+
});
|
|
286
|
+
};
|
|
275
287
|
const strings = (dir: string, texts: readonly string[]): void => {
|
|
276
288
|
for (const text of texts) {
|
|
277
289
|
for (const file of filesNamedBy(files, dir, text)) {
|
|
@@ -281,13 +293,10 @@ function configurationEntries(
|
|
|
281
293
|
};
|
|
282
294
|
for (const { file, form } of modules) {
|
|
283
295
|
found.push({ file, rule: "configuration-file", source: form, exports: true });
|
|
284
|
-
strings(
|
|
285
|
-
dirnamePath(file.fileName),
|
|
286
|
-
moduleStrings(file).map((one) => one.text),
|
|
287
|
-
);
|
|
296
|
+
strings(dirnamePath(file.fileName), unselected(file));
|
|
288
297
|
}
|
|
289
298
|
for (const document of documents) {
|
|
290
|
-
strings(dirnamePath(document.path), document.strings);
|
|
299
|
+
strings(dirnamePath(document.path), unselectedIn(document.path, document.strings));
|
|
291
300
|
}
|
|
292
301
|
for (const { file } of outside) {
|
|
293
302
|
strings(
|
|
@@ -523,8 +532,9 @@ export function entryPoints<Brand>(
|
|
|
523
532
|
host: Host,
|
|
524
533
|
targetRoot: string,
|
|
525
534
|
parse?: ParseFile,
|
|
535
|
+
selected: ReadonlyMap<string, Selected> = new Map(),
|
|
526
536
|
): readonly EntryPoint[] {
|
|
527
|
-
const found: EntryPoint[] = [...configurationEntries(host, targetRoot, files, parse)];
|
|
537
|
+
const found: EntryPoint[] = [...configurationEntries(host, targetRoot, files, parse, selected)];
|
|
528
538
|
const tests = testFiles.map((pattern) => ({ pattern, expression: globExpression(pattern) }));
|
|
529
539
|
for (const [path, file] of files.byPath) {
|
|
530
540
|
const name = basename(path);
|
|
@@ -548,5 +558,8 @@ export function entryPoints<Brand>(
|
|
|
548
558
|
for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
|
|
549
559
|
found.push({ file, rule: "html-entry", source, exports: true });
|
|
550
560
|
}
|
|
561
|
+
for (const { file, source } of workflowEntries(host, targetRoot, files)) {
|
|
562
|
+
found.push({ file, rule: "workflow-step", source, exports: true });
|
|
563
|
+
}
|
|
551
564
|
return found;
|
|
552
565
|
}
|
package/src/exempt.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { byPosition, positionKey, type Position } from "./position.ts";
|
|
|
24
24
|
import { Unanswerable } from "./query.ts";
|
|
25
25
|
import type { ProjectView } from "./session.ts";
|
|
26
26
|
import type { Templates } from "./template-field.ts";
|
|
27
|
-
import { sweep, type Exemption, type Mode, type SweepInput } from "./sweep.ts";
|
|
27
|
+
import { sweep, type Exemption, type Implementing, type Mode, type SweepInput } from "./sweep.ts";
|
|
28
28
|
|
|
29
29
|
/** One declaration a detector holds back, and where and why. */
|
|
30
30
|
export interface Evidence {
|
|
@@ -35,6 +35,8 @@ export interface Evidence {
|
|
|
35
35
|
readonly site: Position;
|
|
36
36
|
/** The component file, below the target root, whose markup holds the evidence. */
|
|
37
37
|
readonly whileLive?: string;
|
|
38
|
+
/** Where the evidence is an implementation of interface methods, which and how. */
|
|
39
|
+
readonly implementing?: Implementing;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/** What a detector reads of one project, while the project's view is open. */
|
|
@@ -214,6 +216,7 @@ export function evidenceRecords<Brand>(
|
|
|
214
216
|
detail: evidence.detail,
|
|
215
217
|
site: evidence.site,
|
|
216
218
|
...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
|
|
219
|
+
...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
|
|
217
220
|
});
|
|
218
221
|
}
|
|
219
222
|
}
|
|
@@ -10,7 +10,8 @@ import type { Cascade, Confidence, Severity } from "../config.ts";
|
|
|
10
10
|
import type { CompletedFinding, Finding, FindingComponent, PositionedSymbol } from "../finding.ts";
|
|
11
11
|
import { OUTSIDE, type Graph } from "../graph.ts";
|
|
12
12
|
import { KINDS, type KindRow } from "../kinds.ts";
|
|
13
|
-
import { byPosition } from "../position.ts";
|
|
13
|
+
import { byPosition, positionKey } from "../position.ts";
|
|
14
|
+
import { declarationKey } from "../ref.ts";
|
|
14
15
|
import { FAMILY_KEY_LENGTH } from "../resolve.ts";
|
|
15
16
|
import { isPartSubject, isRowSubject, type Dials } from "../suppress.ts";
|
|
16
17
|
import type { Boundary } from "./boundary.ts";
|
|
@@ -149,7 +150,9 @@ export function completed(
|
|
|
149
150
|
): readonly CompletedFinding[] {
|
|
150
151
|
const { swept } = input;
|
|
151
152
|
const union = swept.matrix.union;
|
|
152
|
-
const positions = new Map(
|
|
153
|
+
const positions = new Map(
|
|
154
|
+
union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]),
|
|
155
|
+
);
|
|
153
156
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
154
157
|
const classOf = reachabilityClasses(input);
|
|
155
158
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
@@ -168,10 +171,14 @@ export function completed(
|
|
|
168
171
|
throw new Error(`${finding.code} names no live row of the issue-kind vocabulary`);
|
|
169
172
|
}
|
|
170
173
|
// A row's reference may spell a declaration's, as an ignore entry naming one does, and
|
|
171
|
-
// a row is still never the declaration.
|
|
174
|
+
// a row is still never the declaration. Two declarations of one file can share a
|
|
175
|
+
// reference, so the one the finding is positioned at is the subject where there is one.
|
|
176
|
+
const declared = union.at(positionKey(finding.position));
|
|
172
177
|
const at = isRowSubject(finding.symbol.kind)
|
|
173
178
|
? OUTSIDE
|
|
174
|
-
:
|
|
179
|
+
: union.symbols[declared]?.ref === finding.symbol.ref
|
|
180
|
+
? declared
|
|
181
|
+
: (positions.get(declarationKey(finding.symbol.ref, finding.position.path)) ?? OUTSIDE);
|
|
175
182
|
// A part names the declaration that holds it, and is decided inside it rather than by
|
|
176
183
|
// the declaration's liveness, so it carries none of the declaration's sweep facts.
|
|
177
184
|
const part = isPartSubject(finding.symbol.kind);
|
|
@@ -37,7 +37,7 @@ function neededBy(dependencies: Dependencies): ReadonlySet<string> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* A dependency, development dependency or peer dependency of the
|
|
40
|
+
* A dependency, development dependency or peer dependency of a manifest the run reads
|
|
41
41
|
* that the target does not need, at the position of its key.
|
|
42
42
|
*/
|
|
43
43
|
function unusedDependencies(dependencies: Dependencies): Finding[] {
|
|
@@ -48,7 +48,7 @@ function unusedDependencies(dependencies: Dependencies): Finding[] {
|
|
|
48
48
|
code: UNUSED_DEPENDENCY,
|
|
49
49
|
position: dependency.position,
|
|
50
50
|
symbol: {
|
|
51
|
-
ref: renderRef(
|
|
51
|
+
ref: renderRef(dependency.manifest, {
|
|
52
52
|
of: "dependency",
|
|
53
53
|
name: dependency.name,
|
|
54
54
|
section: dependency.section,
|