@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.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/dist/src/analysis.js +6 -16
- package/dist/src/component-layer.js +1 -1
- package/dist/src/configuration-files.js +227 -27
- package/dist/src/contextual-uses.js +41 -7
- package/dist/src/convention-rows.js +6 -0
- package/dist/src/conventions.js +32 -0
- package/dist/src/dependencies.js +12 -8
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +35 -4
- package/dist/src/findings/emitters.js +0 -4
- package/dist/src/findings/self-check.js +14 -10
- package/dist/src/indexed-access.js +8 -2
- package/dist/src/inventory.js +3 -12
- package/dist/src/references.js +2 -2
- package/dist/src/report.js +3 -0
- package/dist/src/roots.js +0 -1
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/session.js +0 -1
- package/dist/src/suppress-inline.js +1 -1
- package/dist/src/suppress.js +15 -16
- package/dist/src/workspace-programs.js +0 -3
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +10 -30
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +1 -1
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +246 -28
- package/src/contextual-uses.ts +63 -9
- package/src/convention-rows.ts +19 -0
- package/src/conventions.ts +43 -0
- package/src/dependencies.ts +14 -10
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/entry-points.ts +1 -1
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +1 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +46 -4
- package/src/findings/emitters.ts +3 -8
- package/src/findings/self-check.ts +17 -14
- package/src/findings-pass.ts +2 -2
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/indexed-access.ts +10 -2
- package/src/inventory.ts +5 -33
- package/src/json-document.ts +1 -1
- package/src/manifest.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/references.ts +7 -6
- package/src/report.ts +8 -4
- package/src/roots.ts +0 -3
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/session.ts +0 -2
- package/src/stores.ts +1 -1
- package/src/suppress-inline.ts +3 -3
- package/src/suppress.ts +21 -24
- package/src/template-field.ts +1 -1
- package/src/value-reach.ts +1 -1
- package/src/verbs/verb.ts +1 -4
- package/src/workspace-programs.ts +2 -9
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/src/contextual-uses.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* An object-literal property writes the member of its name on each constituent of the
|
|
4
4
|
* literal's contextual type that declares one. A value reaching a position typed by another
|
|
5
5
|
* object type reads each of its members that type declares, through array elements and
|
|
6
|
-
*
|
|
7
|
-
* interface-satisfaction class's evidence instead.
|
|
6
|
+
* the parameters and results of functions and constructors too; a class instance there is
|
|
7
|
+
* the interface-satisfaction class's evidence instead.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -18,12 +18,13 @@ import {
|
|
|
18
18
|
isPropertyAssignment,
|
|
19
19
|
isSetAccessorDeclaration,
|
|
20
20
|
isShorthandPropertyAssignment,
|
|
21
|
+
isSpreadAssignment,
|
|
21
22
|
isStringLiteral,
|
|
22
23
|
type Expression,
|
|
23
24
|
type Node,
|
|
24
25
|
type ObjectLiteralExpression,
|
|
25
26
|
} from "@typescript/native/unstable/ast";
|
|
26
|
-
import { SignatureKind, type Type } from "@typescript/native/unstable/sync";
|
|
27
|
+
import { SignatureKind, SymbolFlags, type Type } from "@typescript/native/unstable/sync";
|
|
27
28
|
import type { AliasChains } from "./alias-chain.ts";
|
|
28
29
|
import type { Inventory, SymbolKind } from "./inventory.ts";
|
|
29
30
|
import { isAnswered, type PropertyTables } from "./query.ts";
|
|
@@ -45,14 +46,14 @@ export interface FlowAt {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/** One use the contextual types make: the node it is written at and the members it names. */
|
|
48
|
-
|
|
49
|
+
interface ContextualUse {
|
|
49
50
|
readonly at: Node;
|
|
50
51
|
readonly from: string;
|
|
51
52
|
readonly ids: readonly string[];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/** What the contextual types of one project's literals and values use, and what asking cost. */
|
|
55
|
-
|
|
56
|
+
interface ContextualUses {
|
|
56
57
|
/** Each literal property that writes a member, by the property's node. */
|
|
57
58
|
readonly writes: readonly ContextualUse[];
|
|
58
59
|
/** Each value whose members a position typed by another object type reads. */
|
|
@@ -93,8 +94,9 @@ function propertyName(
|
|
|
93
94
|
return undefined;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
/** The parameter and result types of one call signature, each where the checker answered. */
|
|
97
|
+
/** The parameter and result types of one call or construct signature, each where the checker answered. */
|
|
97
98
|
interface Called {
|
|
99
|
+
readonly kind: SignatureKind;
|
|
98
100
|
readonly parameters: readonly (Type | undefined)[];
|
|
99
101
|
readonly result: Type | undefined;
|
|
100
102
|
}
|
|
@@ -116,6 +118,8 @@ interface Member {
|
|
|
116
118
|
readonly handles: ReadonlySet<string>;
|
|
117
119
|
/** Whether a declaration of it is in one of the target's own files. */
|
|
118
120
|
readonly own: boolean;
|
|
121
|
+
/** Whether the checker reads it as an accessor. */
|
|
122
|
+
readonly accessor: boolean;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/**
|
|
@@ -135,6 +139,11 @@ export function contextualUses<Brand>(
|
|
|
135
139
|
const queries = project.queries;
|
|
136
140
|
const ownPaths = project.ownPaths();
|
|
137
141
|
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
142
|
+
const privateNames = new Set(
|
|
143
|
+
held.symbols
|
|
144
|
+
.filter((symbol) => symbol.visibility === "private-name")
|
|
145
|
+
.map((symbol) => symbol.id),
|
|
146
|
+
);
|
|
138
147
|
let batches = 0;
|
|
139
148
|
let lookups = 0;
|
|
140
149
|
const batchesOf = (count: number): number => Math.ceil(count / cap);
|
|
@@ -177,6 +186,7 @@ export function contextualUses<Brand>(
|
|
|
177
186
|
property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`),
|
|
178
187
|
),
|
|
179
188
|
own: property.declarations.some((handle) => ownPaths.has(handle.path)),
|
|
189
|
+
accessor: (property.flags & SymbolFlags.Accessor) !== 0,
|
|
180
190
|
});
|
|
181
191
|
}
|
|
182
192
|
tables.set(type.id, table);
|
|
@@ -269,13 +279,22 @@ export function contextualUses<Brand>(
|
|
|
269
279
|
};
|
|
270
280
|
|
|
271
281
|
const calls = new Map<number, Called | undefined>();
|
|
272
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* The parameter and return types of a type's one call signature, or of its one construct
|
|
284
|
+
* signature where it has no call signature, read once per type.
|
|
285
|
+
*/
|
|
273
286
|
const callOf = (type: Type): Called | undefined => {
|
|
274
287
|
if (calls.has(type.id)) {
|
|
275
288
|
return calls.get(type.id);
|
|
276
289
|
}
|
|
277
290
|
lookups += 1;
|
|
278
|
-
|
|
291
|
+
let kind = SignatureKind.Call;
|
|
292
|
+
let signatures = queries.signaturesOf(type, kind);
|
|
293
|
+
if (isAnswered(signatures) && signatures.length === 0) {
|
|
294
|
+
lookups += 1;
|
|
295
|
+
kind = SignatureKind.Construct;
|
|
296
|
+
signatures = queries.signaturesOf(type, kind);
|
|
297
|
+
}
|
|
279
298
|
let called: Called | undefined;
|
|
280
299
|
const [signature, ...more] = isAnswered(signatures) ? signatures : [];
|
|
281
300
|
if (signature !== undefined && more.length === 0) {
|
|
@@ -285,6 +304,7 @@ export function contextualUses<Brand>(
|
|
|
285
304
|
const result = queries.returnTypeOf(signature);
|
|
286
305
|
lookups += parameters.length + 1;
|
|
287
306
|
called = {
|
|
307
|
+
kind,
|
|
288
308
|
parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
|
|
289
309
|
result: isAnswered(result) ? result : undefined,
|
|
290
310
|
};
|
|
@@ -330,7 +350,7 @@ export function contextualUses<Brand>(
|
|
|
330
350
|
continue;
|
|
331
351
|
}
|
|
332
352
|
const other = called === undefined ? undefined : callOf(part);
|
|
333
|
-
if (called === undefined || other
|
|
353
|
+
if (called === undefined || other?.kind !== called.kind) {
|
|
334
354
|
continue;
|
|
335
355
|
}
|
|
336
356
|
other.parameters.forEach((parameter, index) => {
|
|
@@ -413,5 +433,39 @@ export function contextualUses<Brand>(
|
|
|
413
433
|
reads.push({ at: flow.value, from: flow.from, ids: [...ids] });
|
|
414
434
|
}
|
|
415
435
|
});
|
|
436
|
+
const spreads = literals.flatMap(({ literal, from }) =>
|
|
437
|
+
literal.properties.flatMap((property) =>
|
|
438
|
+
isSpreadAssignment(property) ? [{ value: property.expression, from }] : [],
|
|
439
|
+
),
|
|
440
|
+
);
|
|
441
|
+
batches += batchesOf(spreads.length);
|
|
442
|
+
const spreadTypes = queries.typesAt(
|
|
443
|
+
spreads.map((one) => one.value),
|
|
444
|
+
cap,
|
|
445
|
+
);
|
|
446
|
+
spreads.forEach(({ value, from }, index) => {
|
|
447
|
+
const type = spreadTypes[index];
|
|
448
|
+
if (type === undefined || !isAnswered(type)) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// A spread gets every own enumerable property, an overwritten key's getter included.
|
|
452
|
+
// So it reads each data member but a class's accessor, which sits on the prototype with
|
|
453
|
+
// the class's methods (never data members), and a private name, which is no property
|
|
454
|
+
// (https://tc39.es/ecma262/#sec-copydataproperties).
|
|
455
|
+
const own = (member: Member, id: string): boolean =>
|
|
456
|
+
!privateNames.has(id) && !(member.accessor && kinds.get(id) === "class-member");
|
|
457
|
+
const ids = [
|
|
458
|
+
...new Set(
|
|
459
|
+
partsOf(type).flatMap((part) =>
|
|
460
|
+
[...membersOf(part).values()].flatMap((member) =>
|
|
461
|
+
member.ids.filter((id) => own(member, id)),
|
|
462
|
+
),
|
|
463
|
+
),
|
|
464
|
+
),
|
|
465
|
+
];
|
|
466
|
+
if (ids.length > 0) {
|
|
467
|
+
reads.push({ at: value, from, ids });
|
|
468
|
+
}
|
|
469
|
+
});
|
|
416
470
|
return { writes, reads, batches, lookups };
|
|
417
471
|
}
|
package/src/convention-rows.ts
CHANGED
|
@@ -53,6 +53,16 @@ export interface PackageKey {
|
|
|
53
53
|
readonly files: readonly string[];
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** One configuration property whose strings name files the test runner loads beside the tests. */
|
|
57
|
+
export interface TestFileReading extends MoveReading {
|
|
58
|
+
/**
|
|
59
|
+
* The properties that move the directory the strings are read against, the first one the
|
|
60
|
+
* file writes deciding, read against the manifest's directory, which stands where none is
|
|
61
|
+
* written. A value that is not one string literal leaves the file's strings unread.
|
|
62
|
+
*/
|
|
63
|
+
readonly roots: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
/** One directory a row's globs name that a configuration property moves. */
|
|
57
67
|
export interface DirectoryMove {
|
|
58
68
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -91,6 +101,8 @@ export interface ConventionRow {
|
|
|
91
101
|
* string there names no file the tool runs or loads.
|
|
92
102
|
*/
|
|
93
103
|
readonly selections?: readonly MoveReading[];
|
|
104
|
+
/** The properties whose strings name a file the test runner loads beside the tests, a test file. */
|
|
105
|
+
readonly testFiles?: readonly TestFileReading[];
|
|
94
106
|
/**
|
|
95
107
|
* The members whose strings name a package the tool loads: the package itself, a
|
|
96
108
|
* subpath of it, or the package followed by `:` and a name.
|
|
@@ -144,6 +156,12 @@ const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
|
|
|
144
156
|
const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
|
|
145
157
|
const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
|
|
146
158
|
|
|
159
|
+
/**
|
|
160
|
+
* The properties Vitest takes its project root from, `test.root` before Vite's `root`; it
|
|
161
|
+
* resolves `setupFiles` against that root (https://vitest.dev/config/setupfiles).
|
|
162
|
+
*/
|
|
163
|
+
const VITEST_ROOTS = ["test.root", "root"] as const;
|
|
164
|
+
|
|
147
165
|
/** The entry files of an Angular CLI project, at the workspace root or below `projects`. */
|
|
148
166
|
const ANGULAR_ENTRIES = [
|
|
149
167
|
"src/{main,main.server,server,polyfills,test}.ts",
|
|
@@ -642,5 +660,6 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
642
660
|
{ property: "test.coverage.include", files: [VITEST_CONFIG] },
|
|
643
661
|
{ property: "test.coverage.exclude", files: [VITEST_CONFIG] },
|
|
644
662
|
],
|
|
663
|
+
testFiles: [{ property: "test.setupFiles", files: [VITEST_CONFIG], roots: VITEST_ROOTS }],
|
|
645
664
|
},
|
|
646
665
|
];
|
package/src/conventions.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
type OptionsCall,
|
|
56
56
|
type PackageKey,
|
|
57
57
|
type ShortNameReading,
|
|
58
|
+
type TestFileReading,
|
|
58
59
|
} from "./convention-rows.ts";
|
|
59
60
|
import { documentStrings } from "./configuration-files.ts";
|
|
60
61
|
import { globExpression } from "./glob.ts";
|
|
@@ -119,6 +120,8 @@ export interface Conventions {
|
|
|
119
120
|
readonly uses: readonly string[];
|
|
120
121
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
121
122
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
123
|
+
/** The files, absolute, an applied row's test-file properties name. */
|
|
124
|
+
readonly testFiles: ReadonlySet<string>;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/** The strings one configuration file holds at selection keys, which root no file. */
|
|
@@ -608,6 +611,43 @@ function holdsValue(held: Node, value: true | string): boolean {
|
|
|
608
611
|
);
|
|
609
612
|
}
|
|
610
613
|
|
|
614
|
+
/** The directory one file's test-file strings are read against, as {@link TestFileReading} states it. */
|
|
615
|
+
function testFileRoot(file: SourceFile, dir: string, reading: TestFileReading): string | undefined {
|
|
616
|
+
for (const property of reading.roots) {
|
|
617
|
+
const values = valuesAt(file, property, reading.call);
|
|
618
|
+
if (values.length > 0) {
|
|
619
|
+
const texts = new Set(values.map(literalText));
|
|
620
|
+
const [text] = texts;
|
|
621
|
+
return texts.size === 1 && text !== undefined ? resolvePath(dir, text) : undefined;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return dir;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** The files an applied row's test-file properties name. */
|
|
628
|
+
function testFilesNamed(
|
|
629
|
+
host: Host,
|
|
630
|
+
parse: ParseSource,
|
|
631
|
+
dir: string,
|
|
632
|
+
readings: readonly TestFileReading[],
|
|
633
|
+
into: Set<string>,
|
|
634
|
+
): void {
|
|
635
|
+
for (const reading of readings) {
|
|
636
|
+
for (const { file } of readingFiles(host, parse, dir, reading.files)) {
|
|
637
|
+
const at = testFileRoot(file, dir, reading);
|
|
638
|
+
if (at === undefined) {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
for (const node of stringsAt(file, reading.property, reading.call)) {
|
|
642
|
+
const text = literalText(node);
|
|
643
|
+
if (text !== undefined && text !== "") {
|
|
644
|
+
into.add(resolvePath(at, text));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
611
651
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
612
652
|
function selectedStrings(
|
|
613
653
|
host: Host,
|
|
@@ -953,6 +993,7 @@ export function readConventions(
|
|
|
953
993
|
const failures: ConventionFailure[] = [];
|
|
954
994
|
const uses = new Set<string>();
|
|
955
995
|
const selected = new Map<string, { starts: Set<number>; texts: string[] }>();
|
|
996
|
+
const testFiles = new Set<string>();
|
|
956
997
|
for (const manifest of manifests) {
|
|
957
998
|
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
958
999
|
const versions = new Map<string, string | undefined>();
|
|
@@ -1025,6 +1066,7 @@ export function readConventions(
|
|
|
1025
1066
|
uses.add(name);
|
|
1026
1067
|
}
|
|
1027
1068
|
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
1069
|
+
testFilesNamed(host, parse, manifest.dir, row.testFiles ?? [], testFiles);
|
|
1028
1070
|
const excludes = [
|
|
1029
1071
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
1030
1072
|
].map(globExpression);
|
|
@@ -1055,6 +1097,7 @@ export function readConventions(
|
|
|
1055
1097
|
failures,
|
|
1056
1098
|
uses: [...uses].sort(compare),
|
|
1057
1099
|
selected,
|
|
1100
|
+
testFiles,
|
|
1058
1101
|
};
|
|
1059
1102
|
}
|
|
1060
1103
|
|
package/src/dependencies.ts
CHANGED
|
@@ -59,7 +59,7 @@ const SECTIONS: readonly (readonly [string, DependencySection])[] = [
|
|
|
59
59
|
const PROJECT_OWNER = "";
|
|
60
60
|
|
|
61
61
|
/** One dependency a manifest the run reads declares. */
|
|
62
|
-
|
|
62
|
+
interface DeclaredDependency {
|
|
63
63
|
readonly name: string;
|
|
64
64
|
readonly section: DependencySection;
|
|
65
65
|
/** Where the manifest writes the dependency's key, below the target root. */
|
|
@@ -78,7 +78,7 @@ export function configurationNeeds(packages: readonly string[]): ProjectNeeds {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** What the installed copy of one declared dependency states about itself. */
|
|
81
|
-
|
|
81
|
+
interface InstalledDependency {
|
|
82
82
|
/** The commands its manifest's `bin` declares, by name. */
|
|
83
83
|
readonly commands: readonly string[];
|
|
84
84
|
}
|
|
@@ -366,16 +366,20 @@ export function projectNeeds<Brand>(
|
|
|
366
366
|
use(ownersOf(file, held, node), dependenciesNamed(text, names));
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
for (const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
for (const { strings, manifestDirs } of configuration.documents) {
|
|
370
|
+
for (const manifestDir of manifestDirs) {
|
|
371
|
+
const names = declaredAt(joinPath(manifestDir, "package.json"));
|
|
372
|
+
for (const text of strings) {
|
|
373
|
+
use([PROJECT_OWNER], dependenciesNamed(text, names));
|
|
374
|
+
}
|
|
373
375
|
}
|
|
374
376
|
}
|
|
375
|
-
for (const { file,
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
for (const { file, manifestDirs } of configuration.outside) {
|
|
378
|
+
for (const manifestDir of manifestDirs) {
|
|
379
|
+
const names = declaredAt(joinPath(manifestDir, "package.json"));
|
|
380
|
+
for (const { text } of moduleStrings(file)) {
|
|
381
|
+
use([PROJECT_OWNER], dependenciesNamed(text, names));
|
|
382
|
+
}
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
return { packages, uses, unanswered };
|
package/src/discover.ts
CHANGED
|
@@ -34,7 +34,7 @@ export class DiscoveryError extends Error {
|
|
|
34
34
|
const ERROR_CATEGORY: number = DiagnosticCategory.Error;
|
|
35
35
|
|
|
36
36
|
/** Whether one diagnostic is an error, which is what fails a run closed. */
|
|
37
|
-
|
|
37
|
+
function isError(diagnostic: Diagnostic): boolean {
|
|
38
38
|
return diagnostic.category === ERROR_CATEGORY;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -113,7 +113,7 @@ export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/** One project a run analyzes. */
|
|
116
|
-
|
|
116
|
+
interface DiscoveredProject {
|
|
117
117
|
/**
|
|
118
118
|
* The name a run gives the project: the identifier the build matrix declares for
|
|
119
119
|
* it, or, for a project discovery derived, its configuration file's path below the
|
|
@@ -162,7 +162,7 @@ export function isGuess(discovery: Discovery, configFile: string): boolean {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/** Every project discovery found, and where each came from. */
|
|
165
|
-
|
|
165
|
+
interface Discovery {
|
|
166
166
|
/** The projects, in discovery order, each once. */
|
|
167
167
|
readonly projects: readonly DiscoveredProject[];
|
|
168
168
|
/**
|
package/src/edges.ts
CHANGED
|
@@ -39,7 +39,7 @@ const REFERENCE_WANT =
|
|
|
39
39
|
"a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
|
|
40
40
|
|
|
41
41
|
/** An edges document the Contract refuses: a malformed input, answered with the usage code. */
|
|
42
|
-
|
|
42
|
+
class EdgesError extends ConfigError {
|
|
43
43
|
constructor(at: string, text: string, want: string) {
|
|
44
44
|
super("malformed", EDGES_FILE, `${at}: ${text}: want ${want}`);
|
|
45
45
|
this.name = "EdgesError";
|
|
@@ -174,7 +174,7 @@ export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/** One side's verdict, as an evaluation names it. */
|
|
177
|
-
|
|
177
|
+
type EdgeState = "live" | "dead" | "absent";
|
|
178
178
|
|
|
179
179
|
/** One edge evaluation: one side of this language, its state, and the finding it holds. */
|
|
180
180
|
export interface EdgeEvaluation<F extends Finding = Finding> extends EdgeSide {
|
|
@@ -184,7 +184,7 @@ export interface EdgeEvaluation<F extends Finding = Finding> extends EdgeSide {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/** The findings a report publishes, and one evaluation per declared side of this language. */
|
|
187
|
-
|
|
187
|
+
interface Evaluated<F extends Finding> {
|
|
188
188
|
readonly findings: readonly F[];
|
|
189
189
|
/** Ordered by edge, then by side, each compared bytewise. */
|
|
190
190
|
readonly evaluations: readonly EdgeEvaluation<F>[];
|
package/src/entry-points.ts
CHANGED
package/src/exempt-classes.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The exemption classes of the Contract's vocabulary, in its order: each class's name,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* reason the fixed severity codes are, and a test holds them equal to the Contract
|
|
8
|
-
* release this analyzer implements. A class name is a type, so a detector table or a
|
|
9
|
-
* record naming a class the vocabulary does not hold, or one that runs on another
|
|
10
|
-
* language only, does not compile.
|
|
2
|
+
* The exemption classes of the Contract's vocabulary, in its order: each class's name, its
|
|
3
|
+
* languages and, for a TypeScript class, which private members it may retain. The rows are
|
|
4
|
+
* written out, as the fixed severity codes are, and a test holds them equal to the Contract.
|
|
5
|
+
* A class name is a type, so a table or a record naming a class outside the vocabulary, or
|
|
6
|
+
* one of another language only, does not compile.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
|
-
import type {
|
|
9
|
+
import type { Language } from "./config.ts";
|
|
14
10
|
|
|
15
11
|
/**
|
|
16
12
|
* Whether a class may retain a member declared with the `private` modifier, and one
|
|
@@ -27,7 +23,6 @@ export interface TypeScriptVisibility {
|
|
|
27
23
|
interface ExemptionClassRow {
|
|
28
24
|
readonly class: string;
|
|
29
25
|
readonly languages: readonly Language[];
|
|
30
|
-
readonly confidence: Confidence;
|
|
31
26
|
/** Present exactly on a class that runs on TypeScript. */
|
|
32
27
|
readonly typescriptVisibility?: TypeScriptVisibility;
|
|
33
28
|
}
|
|
@@ -37,59 +32,50 @@ export const EXEMPTION_CLASSES = [
|
|
|
37
32
|
{
|
|
38
33
|
class: "interface-satisfaction",
|
|
39
34
|
languages: ["go", "ts"],
|
|
40
|
-
confidence: "certain",
|
|
41
35
|
typescriptVisibility: { private: false, privateName: false },
|
|
42
36
|
},
|
|
43
|
-
{ class: "encoding-reflection", languages: ["go"]
|
|
44
|
-
{ class: "format-verb-contract", languages: ["go"]
|
|
45
|
-
{ class: "errors-duck-typing", languages: ["go"]
|
|
37
|
+
{ class: "encoding-reflection", languages: ["go"] },
|
|
38
|
+
{ class: "format-verb-contract", languages: ["go"] },
|
|
39
|
+
{ class: "errors-duck-typing", languages: ["go"] },
|
|
46
40
|
{
|
|
47
41
|
class: "enum-group",
|
|
48
42
|
languages: ["go", "ts"],
|
|
49
|
-
confidence: "certain",
|
|
50
43
|
typescriptVisibility: { private: false, privateName: false },
|
|
51
44
|
},
|
|
52
45
|
{
|
|
53
46
|
class: "generated-file",
|
|
54
47
|
languages: ["go", "ts"],
|
|
55
|
-
confidence: "certain",
|
|
56
48
|
typescriptVisibility: { private: true, privateName: false },
|
|
57
49
|
},
|
|
58
|
-
{ class: "linkname-cgo-asm-plugin", languages: ["go"]
|
|
50
|
+
{ class: "linkname-cgo-asm-plugin", languages: ["go"] },
|
|
59
51
|
{
|
|
60
52
|
class: "template-field",
|
|
61
53
|
languages: ["go", "ts"],
|
|
62
|
-
confidence: "possible",
|
|
63
54
|
typescriptVisibility: { private: true, privateName: false },
|
|
64
55
|
},
|
|
65
56
|
{
|
|
66
57
|
class: "reflective-lookup",
|
|
67
58
|
languages: ["go", "ts"],
|
|
68
|
-
confidence: "possible",
|
|
69
59
|
typescriptVisibility: { private: true, privateName: false },
|
|
70
60
|
},
|
|
71
61
|
{
|
|
72
62
|
class: "decorator",
|
|
73
63
|
languages: ["ts"],
|
|
74
|
-
confidence: "certain",
|
|
75
64
|
typescriptVisibility: { private: true, privateName: false },
|
|
76
65
|
},
|
|
77
66
|
{
|
|
78
67
|
class: "injection-container",
|
|
79
68
|
languages: ["ts"],
|
|
80
|
-
confidence: "certain",
|
|
81
69
|
typescriptVisibility: { private: true, privateName: false },
|
|
82
70
|
},
|
|
83
71
|
{
|
|
84
72
|
class: "framework-lifecycle",
|
|
85
73
|
languages: ["ts"],
|
|
86
|
-
confidence: "certain",
|
|
87
74
|
typescriptVisibility: { private: true, privateName: false },
|
|
88
75
|
},
|
|
89
76
|
{
|
|
90
77
|
class: "serialization-contract",
|
|
91
78
|
languages: ["ts"],
|
|
92
|
-
confidence: "certain",
|
|
93
79
|
typescriptVisibility: { private: true, privateName: false },
|
|
94
80
|
},
|
|
95
81
|
] as const satisfies readonly ExemptionClassRow[];
|
package/src/exempt.ts
CHANGED
|
@@ -64,9 +64,7 @@ export type Detector = <Brand>(input: DetectorInput<Brand>) => readonly Evidence
|
|
|
64
64
|
export type Detectors = ReadonlyMap<TSExemptionClass, Detector>;
|
|
65
65
|
|
|
66
66
|
/** What decides which records of one project stand. */
|
|
67
|
-
|
|
68
|
-
/** The classes the configuration switches off, whose detectors do not run. */
|
|
69
|
-
readonly disabled: ReadonlySet<ExemptionClass>;
|
|
67
|
+
interface Holding {
|
|
70
68
|
readonly mode: Mode;
|
|
71
69
|
/** The paths the reference pass classified as test files. */
|
|
72
70
|
readonly testFiles: ReadonlySet<string>;
|
|
@@ -154,23 +152,8 @@ function detected<Brand>(
|
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
/**
|
|
158
|
-
* The records one project's detectors found, each class in the vocabulary's order and
|
|
159
|
-
* none of a switched-off class. Evidence on a private member the class's row says it
|
|
160
|
-
* cannot retain is dropped first, whatever the detector found. Under a production mode
|
|
161
|
-
* a record whose evidence is written in a test file holds nothing, as a reference a
|
|
162
|
-
* test file makes is none there.
|
|
163
|
-
*/
|
|
164
|
-
export function computeExemptions<Brand>(
|
|
165
|
-
input: DetectorInput<Brand>,
|
|
166
|
-
detectors: Detectors,
|
|
167
|
-
holding: Holding,
|
|
168
|
-
): readonly Exemption[] {
|
|
169
|
-
return detectExemptions(input, detectors, holding).records;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
155
|
/** One project's records, and the evidence a production mode found in a test file. */
|
|
173
|
-
|
|
156
|
+
interface Detected {
|
|
174
157
|
readonly records: readonly Exemption[];
|
|
175
158
|
/**
|
|
176
159
|
* Under a production mode, each record whose evidence is written in a test file. It
|
|
@@ -180,18 +163,11 @@ export interface Detected {
|
|
|
180
163
|
readonly inTestFiles: readonly Exemption[];
|
|
181
164
|
}
|
|
182
165
|
|
|
183
|
-
/** The records of {@link computeExemptions}, beside the test-file evidence it set aside. */
|
|
184
|
-
export function detectExemptions<Brand>(
|
|
185
|
-
input: DetectorInput<Brand>,
|
|
186
|
-
detectors: Detectors,
|
|
187
|
-
holding: Holding,
|
|
188
|
-
): Detected {
|
|
189
|
-
return heldRecords(evidenceRecords(input, detectors, holding.disabled), holding);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
166
|
/**
|
|
193
167
|
* Every record one project's detectors found, one per piece of evidence, before the
|
|
194
|
-
* mode decides which of them hold: what {@link heldRecords} reads.
|
|
168
|
+
* mode decides which of them hold: what {@link heldRecords} reads. Classes run in the
|
|
169
|
+
* vocabulary's order and a switched-off class runs not at all; evidence on a private
|
|
170
|
+
* member the class's row says it cannot retain is dropped, whatever the detector found.
|
|
195
171
|
*/
|
|
196
172
|
export function evidenceRecords<Brand>(
|
|
197
173
|
input: DetectorInput<Brand>,
|
package/src/file-facts.ts
CHANGED
|
@@ -22,7 +22,7 @@ const DECLARATION_FILE = /\.d\.(?:[^./]+\.)?[mc]?ts$/u;
|
|
|
22
22
|
const LINE_BREAK = /\r\n|[\n\r\u2028\u2029]/gu;
|
|
23
23
|
|
|
24
24
|
/** One source file below the target root. */
|
|
25
|
-
|
|
25
|
+
interface TreeFile {
|
|
26
26
|
/** The path below the target root, with the solidus as separator. */
|
|
27
27
|
readonly path: string;
|
|
28
28
|
/** The stable reference of the file's module. */
|
package/src/finding.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface FindingPosition {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** What a finding is about. */
|
|
28
|
-
|
|
28
|
+
interface FindingSubject {
|
|
29
29
|
/** The stable symbol reference, or for a configured root the root as written. */
|
|
30
30
|
readonly ref: string;
|
|
31
31
|
/** The subject kind, from the closed vocabulary of the finding schema. */
|
|
@@ -65,6 +65,8 @@ export interface FindingDetails {
|
|
|
65
65
|
readonly entry?: SuppressionEntry;
|
|
66
66
|
/** On the intra-function kinds: the external rules that report the same kind, as the vocabulary lists them. */
|
|
67
67
|
readonly overlap?: readonly string[];
|
|
68
|
+
/** On a deletable finding: the dependencies whose last use deleting the subject removes. */
|
|
69
|
+
readonly removesLastUseOf?: readonly string[];
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/** One suppression in the four-member form the ignore file and the baseline share. */
|