@cplieger/deadset-ts 6.0.1 → 6.1.1-dev.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/dist/src/configured-declarations.js +7 -5
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +67 -1
- package/dist/src/conventions.js +51 -9
- package/dist/src/edges.js +19 -16
- package/dist/src/findings/emitters.js +20 -23
- package/dist/src/inventory.js +6 -1
- package/dist/src/module-declarations.js +3 -3
- package/dist/src/outside-tests.js +5 -11
- package/dist/src/overrides.js +22 -10
- package/dist/src/ref.js +10 -2
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/configured-declarations.ts +7 -5
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +84 -1
- package/src/conventions.ts +77 -9
- package/src/edges.ts +20 -17
- package/src/findings/emitters.ts +28 -44
- package/src/inventory.ts +6 -1
- package/src/module-declarations.ts +3 -3
- package/src/outside-tests.ts +5 -11
- package/src/overrides.ts +26 -11
- package/src/ref.ts +18 -4
- package/src/version.ts +1 -1
package/src/convention-rows.ts
CHANGED
|
@@ -31,6 +31,20 @@ export interface ShortNameReading extends MoveReading {
|
|
|
31
31
|
readonly package: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* One configuration property whose value makes the tool's output import a runtime helper
|
|
36
|
+
* package, so the package is used where the property holds that value.
|
|
37
|
+
*/
|
|
38
|
+
export interface HelperReading extends MoveReading {
|
|
39
|
+
/**
|
|
40
|
+
* The value: `true`, or a string the property holds, alone, as an element of an array,
|
|
41
|
+
* or as the first element of an array that is such an element.
|
|
42
|
+
*/
|
|
43
|
+
readonly value: true | string;
|
|
44
|
+
/** The package the output imports its helpers from. */
|
|
45
|
+
readonly package: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/** One directory a row's globs name that a configuration property moves. */
|
|
35
49
|
export interface DirectoryMove {
|
|
36
50
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -71,6 +85,8 @@ export interface ConventionRow {
|
|
|
71
85
|
readonly selections?: readonly MoveReading[];
|
|
72
86
|
/** The members of a `package.json` the tool reads its configuration from. */
|
|
73
87
|
readonly manifestKeys?: readonly string[];
|
|
88
|
+
/** The settings that make the tool's output import a runtime helper package. */
|
|
89
|
+
readonly helpers?: readonly HelperReading[];
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
const SCRIPT = "{js,jsx,ts,tsx}";
|
|
@@ -105,6 +121,12 @@ const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart
|
|
|
105
121
|
|
|
106
122
|
const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
|
|
107
123
|
|
|
124
|
+
const BABEL_CONFIG = [
|
|
125
|
+
"babel.config.{json,js,cjs,mjs,cts,mts,ts}",
|
|
126
|
+
".babelrc",
|
|
127
|
+
".babelrc.{json,js,cjs,mjs,cts}",
|
|
128
|
+
];
|
|
129
|
+
|
|
108
130
|
const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
|
|
109
131
|
const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
|
|
110
132
|
const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
|
|
@@ -132,6 +154,19 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
132
154
|
},
|
|
133
155
|
],
|
|
134
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "babel",
|
|
159
|
+
package: "@babel/core",
|
|
160
|
+
range: ">=7.0.0 <9.0.0",
|
|
161
|
+
entries: [],
|
|
162
|
+
moves: [],
|
|
163
|
+
helpers: ["@babel/plugin-transform-runtime", "@babel/transform-runtime"].map((value) => ({
|
|
164
|
+
property: "plugins",
|
|
165
|
+
files: BABEL_CONFIG,
|
|
166
|
+
value,
|
|
167
|
+
package: "@babel/runtime",
|
|
168
|
+
})),
|
|
169
|
+
},
|
|
135
170
|
{
|
|
136
171
|
name: "eslint",
|
|
137
172
|
package: "eslint",
|
|
@@ -304,6 +339,21 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
304
339
|
},
|
|
305
340
|
],
|
|
306
341
|
},
|
|
342
|
+
{
|
|
343
|
+
name: "rolldown",
|
|
344
|
+
package: "rolldown",
|
|
345
|
+
range: ">=1.0.0 <2.0.0",
|
|
346
|
+
entries: [],
|
|
347
|
+
moves: [],
|
|
348
|
+
helpers: [
|
|
349
|
+
{
|
|
350
|
+
property: "transform.helpers.mode",
|
|
351
|
+
files: [`rolldown.config.${ANY_MODULE}`],
|
|
352
|
+
value: "Runtime",
|
|
353
|
+
package: "@oxc-project/runtime",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
307
357
|
{
|
|
308
358
|
name: "solidstart",
|
|
309
359
|
package: "@solidjs/start",
|
|
@@ -350,7 +400,10 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
350
400
|
range: ">=7.0.0 <11.0.0",
|
|
351
401
|
entries: [STRYKER_MODULE],
|
|
352
402
|
moves: [],
|
|
353
|
-
selections: [
|
|
403
|
+
selections: [
|
|
404
|
+
{ property: "mutate", files: [STRYKER_CONFIG] },
|
|
405
|
+
{ property: "ignorePatterns", files: [STRYKER_CONFIG] },
|
|
406
|
+
],
|
|
354
407
|
shortNames: [
|
|
355
408
|
{
|
|
356
409
|
property: "testRunner",
|
|
@@ -481,6 +534,36 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
481
534
|
},
|
|
482
535
|
],
|
|
483
536
|
},
|
|
537
|
+
{
|
|
538
|
+
name: "swc",
|
|
539
|
+
package: "@swc/core",
|
|
540
|
+
range: ">=1.0.0 <2.0.0",
|
|
541
|
+
entries: [],
|
|
542
|
+
moves: [],
|
|
543
|
+
helpers: [
|
|
544
|
+
{
|
|
545
|
+
property: "jsc.externalHelpers",
|
|
546
|
+
files: [".swcrc"],
|
|
547
|
+
value: true,
|
|
548
|
+
package: "@swc/helpers",
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "vite",
|
|
554
|
+
package: "vite",
|
|
555
|
+
range: ">=8.0.0 <9.0.0",
|
|
556
|
+
entries: [],
|
|
557
|
+
moves: [],
|
|
558
|
+
helpers: [
|
|
559
|
+
{
|
|
560
|
+
property: "oxc.helpers.mode",
|
|
561
|
+
files: [VITE_CONFIG],
|
|
562
|
+
value: "Runtime",
|
|
563
|
+
package: "@oxc-project/runtime",
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
},
|
|
484
567
|
{
|
|
485
568
|
name: "vitest",
|
|
486
569
|
package: "vitest",
|
package/src/conventions.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
CONVENTION_ROWS,
|
|
51
51
|
type ConventionRow,
|
|
52
52
|
type DirectoryMove,
|
|
53
|
+
type HelperReading,
|
|
53
54
|
type MoveReading,
|
|
54
55
|
type OptionsCall,
|
|
55
56
|
type ShortNameReading,
|
|
@@ -110,7 +111,10 @@ export interface Conventions {
|
|
|
110
111
|
readonly globs: readonly ConventionGlob[];
|
|
111
112
|
readonly generated: readonly GeneratedDirectory[];
|
|
112
113
|
readonly failures: readonly ConventionFailure[];
|
|
113
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* The dependencies the applied rows' short names, manifest keys and helper settings use,
|
|
116
|
+
* by name, each once.
|
|
117
|
+
*/
|
|
114
118
|
readonly uses: readonly string[];
|
|
115
119
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
116
120
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
@@ -136,6 +140,9 @@ type ParseSource = (fileName: string, text: string) => SourceFile;
|
|
|
136
140
|
|
|
137
141
|
const JSON_EXTENSION = ".json";
|
|
138
142
|
|
|
143
|
+
/** A configuration file with no extension that its tool reads as a JSON document. */
|
|
144
|
+
const JSON_RC = /^\.[\w-]+rc$/u;
|
|
145
|
+
|
|
139
146
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
140
147
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
|
|
141
148
|
|
|
@@ -533,11 +540,10 @@ function readPath(
|
|
|
533
540
|
}
|
|
534
541
|
|
|
535
542
|
/**
|
|
536
|
-
* Every
|
|
537
|
-
*
|
|
538
|
-
* array value, whatever else the path holds.
|
|
543
|
+
* Every value one file writes at one property's path inside the objects it is read from,
|
|
544
|
+
* as {@link readProperty} reads them, whatever else the path holds.
|
|
539
545
|
*/
|
|
540
|
-
function
|
|
546
|
+
function valuesAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
541
547
|
const declared = declaredValues(file);
|
|
542
548
|
const objects =
|
|
543
549
|
call === undefined
|
|
@@ -558,10 +564,8 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
|
|
|
558
564
|
const held = unwrapped(member.initializer);
|
|
559
565
|
if (rest.length > 0) {
|
|
560
566
|
visit(held, rest);
|
|
561
|
-
} else
|
|
567
|
+
} else {
|
|
562
568
|
found.push(held);
|
|
563
|
-
} else if (isArrayLiteralExpression(held)) {
|
|
564
|
-
found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
|
|
565
569
|
}
|
|
566
570
|
}
|
|
567
571
|
};
|
|
@@ -573,6 +577,36 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
|
|
|
573
577
|
return found;
|
|
574
578
|
}
|
|
575
579
|
|
|
580
|
+
/** The string literals among values: each value itself, or each element of an array value. */
|
|
581
|
+
function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
582
|
+
return valuesAt(file, property, call).flatMap((held) => {
|
|
583
|
+
if (literalText(held) !== undefined) {
|
|
584
|
+
return [held];
|
|
585
|
+
}
|
|
586
|
+
return isArrayLiteralExpression(held)
|
|
587
|
+
? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
|
|
588
|
+
: [];
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
593
|
+
function holdsValue(held: Node, value: true | string): boolean {
|
|
594
|
+
if (value === true) {
|
|
595
|
+
return held.kind === SyntaxKind.TrueKeyword;
|
|
596
|
+
}
|
|
597
|
+
if (literalText(held) === value) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
return (
|
|
601
|
+
isArrayLiteralExpression(held) &&
|
|
602
|
+
held.elements.some((element) => {
|
|
603
|
+
const one = unwrapped(element);
|
|
604
|
+
const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
|
|
605
|
+
return first !== undefined && literalText(first) === value;
|
|
606
|
+
})
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
576
610
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
577
611
|
function selectedStrings(
|
|
578
612
|
host: Host,
|
|
@@ -760,7 +794,11 @@ function readingFiles(
|
|
|
760
794
|
} catch {
|
|
761
795
|
continue;
|
|
762
796
|
}
|
|
763
|
-
found.push({
|
|
797
|
+
found.push({
|
|
798
|
+
name,
|
|
799
|
+
text,
|
|
800
|
+
file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
|
|
801
|
+
});
|
|
764
802
|
}
|
|
765
803
|
}
|
|
766
804
|
return found;
|
|
@@ -808,6 +846,33 @@ function shortNameUses(
|
|
|
808
846
|
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
809
847
|
}
|
|
810
848
|
|
|
849
|
+
/** The declared dependencies an applied row's helper settings use. */
|
|
850
|
+
function helperUses(
|
|
851
|
+
host: Host,
|
|
852
|
+
parse: ParseSource,
|
|
853
|
+
dir: string,
|
|
854
|
+
manifest: unknown,
|
|
855
|
+
readings: readonly HelperReading[],
|
|
856
|
+
): readonly string[] {
|
|
857
|
+
const declared = new Set(
|
|
858
|
+
DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
859
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
860
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
861
|
+
}),
|
|
862
|
+
);
|
|
863
|
+
return readings
|
|
864
|
+
.filter(
|
|
865
|
+
(reading) =>
|
|
866
|
+
declared.has(reading.package) &&
|
|
867
|
+
readingFiles(host, parse, dir, reading.files).some(({ file }) =>
|
|
868
|
+
valuesAt(file, reading.property, reading.call).some((held) =>
|
|
869
|
+
holdsValue(held, reading.value),
|
|
870
|
+
),
|
|
871
|
+
),
|
|
872
|
+
)
|
|
873
|
+
.map((reading) => reading.package);
|
|
874
|
+
}
|
|
875
|
+
|
|
811
876
|
/** Two strings ordered bytewise. */
|
|
812
877
|
function compare(a: string, b: string): number {
|
|
813
878
|
if (a === b) {
|
|
@@ -897,6 +962,9 @@ export function readConventions(
|
|
|
897
962
|
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
898
963
|
uses.add(name);
|
|
899
964
|
}
|
|
965
|
+
for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
|
|
966
|
+
uses.add(name);
|
|
967
|
+
}
|
|
900
968
|
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
901
969
|
const excludes = [
|
|
902
970
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
package/src/edges.ts
CHANGED
|
@@ -200,9 +200,10 @@ function compare(a: string, b: string): number {
|
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
202
|
* Evaluates every declared side of this language over the run's findings. A side naming a
|
|
203
|
-
* symbol the run enumerates nothing under is `absent`;
|
|
204
|
-
*
|
|
205
|
-
*
|
|
203
|
+
* symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
|
|
204
|
+
* declaration its reference spells. A declaration the run holds a finding about is `dead`
|
|
205
|
+
* and carries that finding, pending until a merge reads the other side and reported
|
|
206
|
+
* nowhere else, a narrowing finding included; any other is `live`.
|
|
206
207
|
*/
|
|
207
208
|
export function evaluateEdges<F extends Finding>(
|
|
208
209
|
findings: readonly F[],
|
|
@@ -212,10 +213,10 @@ export function evaluateEdges<F extends Finding>(
|
|
|
212
213
|
if (sides.length === 0) {
|
|
213
214
|
return { findings, evaluations: [] };
|
|
214
215
|
}
|
|
215
|
-
const byRef = new Map<string, string>();
|
|
216
|
+
const byRef = new Map<string, string[]>();
|
|
216
217
|
for (const symbol of symbols) {
|
|
217
|
-
if (symbol.kind !== "file"
|
|
218
|
-
byRef.set(symbol.ref, symbol.id);
|
|
218
|
+
if (symbol.kind !== "file") {
|
|
219
|
+
byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
const held = new Map<string, number>();
|
|
@@ -226,18 +227,20 @@ export function evaluateEdges<F extends Finding>(
|
|
|
226
227
|
});
|
|
227
228
|
|
|
228
229
|
const pending = new Set<number>();
|
|
229
|
-
const evaluations = sides.
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
232
|
-
return { ...side, state: "absent" };
|
|
230
|
+
const evaluations = sides.flatMap((side): EdgeEvaluation<F>[] => {
|
|
231
|
+
const ids = byRef.get(side.symbol);
|
|
232
|
+
if (ids === undefined) {
|
|
233
|
+
return [{ ...side, state: "absent" }];
|
|
233
234
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
235
|
+
return ids.map((id) => {
|
|
236
|
+
const at = held.get(id);
|
|
237
|
+
const finding = at === undefined ? undefined : findings[at];
|
|
238
|
+
if (at === undefined || finding === undefined) {
|
|
239
|
+
return { ...side, state: "live" };
|
|
240
|
+
}
|
|
241
|
+
pending.add(at);
|
|
242
|
+
return { ...side, state: "dead", finding };
|
|
243
|
+
});
|
|
241
244
|
});
|
|
242
245
|
evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
|
|
243
246
|
return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
|
package/src/findings/emitters.ts
CHANGED
|
@@ -127,8 +127,7 @@ function dialsOver(
|
|
|
127
127
|
* family's findings completed by one step, so every component number is minted once, the
|
|
128
128
|
* finding each record claims withheld, the rest withheld by the dials, so a root one family
|
|
129
129
|
* reports withholds what another reports in its component, and every declared edge
|
|
130
|
-
* evaluated over what remains, so a pending finding is in its evaluation alone.
|
|
131
|
-
* minimum confidence withheld is what the same decision at the lowest minimum adds.
|
|
130
|
+
* evaluated over what remains, so a pending finding is in its evaluation alone.
|
|
132
131
|
*/
|
|
133
132
|
export function decidedFindings(
|
|
134
133
|
input: EmitterInput,
|
|
@@ -138,20 +137,7 @@ export function decidedFindings(
|
|
|
138
137
|
const unmarked =
|
|
139
138
|
suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
|
|
140
139
|
const would = [...unmarked, ...found];
|
|
141
|
-
|
|
142
|
-
const shown =
|
|
143
|
-
input.config.analysis.minConfidence === "possible"
|
|
144
|
-
? decided
|
|
145
|
-
: dialledOver(
|
|
146
|
-
atPossible(input),
|
|
147
|
-
{
|
|
148
|
-
unmarked: atPossible(suppressed.unmarked),
|
|
149
|
-
records: suppressed.records,
|
|
150
|
-
},
|
|
151
|
-
found,
|
|
152
|
-
unmarked,
|
|
153
|
-
);
|
|
154
|
-
return { ...decided, would, withheld: withheldOf(decided.findings, shown.findings) };
|
|
140
|
+
return { ...dialledOver(input, suppressed, found, unmarked), would };
|
|
155
141
|
}
|
|
156
142
|
|
|
157
143
|
/** The run's input with the minimum confidence at its lowest, which withholds nothing. */
|
|
@@ -173,48 +159,46 @@ function dialledOver(
|
|
|
173
159
|
suppressed: Suppressed,
|
|
174
160
|
found: readonly CompletedFinding[],
|
|
175
161
|
unmarked: readonly CompletedFinding[],
|
|
176
|
-
): Omit<DecidedFindings, "would"
|
|
162
|
+
): Omit<DecidedFindings, "would"> {
|
|
177
163
|
const would = [...unmarked, ...found];
|
|
178
164
|
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
179
165
|
const unanswered = heldKeys(input);
|
|
180
166
|
const markup = markupKeys(input);
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
167
|
+
const candidates = found.filter((finding) => {
|
|
168
|
+
const key = declarationKey(finding.symbol.ref, finding.position.path);
|
|
169
|
+
return (
|
|
170
|
+
!ledger.withheld(finding) &&
|
|
171
|
+
!unanswered.has(key) &&
|
|
172
|
+
!markup.has(key) &&
|
|
173
|
+
!insideSkipped(input.swept.skipped, finding.position)
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
const kept = reportable(input, candidates);
|
|
193
177
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
194
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
findings: evaluated.findings,
|
|
180
|
+
evaluations: evaluated.evaluations,
|
|
181
|
+
ledger,
|
|
182
|
+
withheld: withheldOf(input, candidates),
|
|
183
|
+
};
|
|
195
184
|
}
|
|
196
185
|
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
): Withheld {
|
|
202
|
-
const
|
|
186
|
+
/**
|
|
187
|
+
* The findings the dials withhold only because their confidence is below the minimum, each
|
|
188
|
+
* counted at the confidence this run gives it: the dials at the lowest minimum keep them.
|
|
189
|
+
*/
|
|
190
|
+
function withheldOf(input: EmitterInput, candidates: readonly CompletedFinding[]): Withheld {
|
|
191
|
+
const dials = dialsOf(input, candidates);
|
|
192
|
+
const lowest = dialsOf(atPossible(input), candidates);
|
|
203
193
|
const counts = { certain: 0, probable: 0, possible: 0 };
|
|
204
|
-
for (const finding of
|
|
205
|
-
if (!
|
|
194
|
+
for (const finding of candidates) {
|
|
195
|
+
if (dials.withholds(finding) && !lowest.withholds(finding)) {
|
|
206
196
|
counts[finding.confidence] += 1;
|
|
207
197
|
}
|
|
208
198
|
}
|
|
209
199
|
return counts;
|
|
210
200
|
}
|
|
211
201
|
|
|
212
|
-
/** A finding's identity across two decisions of one run: its position, code and subject. */
|
|
213
|
-
function findingKey(finding: CompletedFinding): string {
|
|
214
|
-
const { path, line, column } = finding.position;
|
|
215
|
-
return JSON.stringify([path, line, column, finding.code, finding.symbol.ref]);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
202
|
/** Every finding the run reports, each family's in table order. */
|
|
219
203
|
export function findingsOf(input: EmitterInput): readonly CompletedFinding[] {
|
|
220
204
|
return decidedFindings(input).findings;
|
package/src/inventory.ts
CHANGED
|
@@ -1110,7 +1110,12 @@ function refOf(record: Building, chain: readonly Component[]): string {
|
|
|
1110
1110
|
return renderRef(record.module, { of: "module" });
|
|
1111
1111
|
}
|
|
1112
1112
|
if (record.kind === "export-alias") {
|
|
1113
|
-
|
|
1113
|
+
const block = record.parentChain.at(-1);
|
|
1114
|
+
return renderRef(record.module, {
|
|
1115
|
+
of: "alias",
|
|
1116
|
+
name: record.component.text,
|
|
1117
|
+
...(block === undefined ? {} : { block: block.text }),
|
|
1118
|
+
});
|
|
1114
1119
|
}
|
|
1115
1120
|
return renderRef(record.module, {
|
|
1116
1121
|
of: "declaration",
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
27
27
|
import { renderPosition } from "./position.ts";
|
|
28
28
|
import { UNANSWERED } from "./query.ts";
|
|
29
|
-
import { nameComponent } from "./ref.ts";
|
|
29
|
+
import { aliasFragment, nameComponent } from "./ref.ts";
|
|
30
30
|
import type { Reference } from "./references.ts";
|
|
31
31
|
import type { ProjectView } from "./session.ts";
|
|
32
32
|
|
|
@@ -110,7 +110,7 @@ interface Named {
|
|
|
110
110
|
* The module declarations of an inventory a specifier names by name or pattern, each with
|
|
111
111
|
* the members that hold the export names given.
|
|
112
112
|
*/
|
|
113
|
-
|
|
113
|
+
function namedBy(
|
|
114
114
|
held: Inventory,
|
|
115
115
|
): (specifier: string, names: readonly string[]) => readonly Named[] {
|
|
116
116
|
if (held.modules.size === 0) {
|
|
@@ -139,7 +139,7 @@ export function namedBy(
|
|
|
139
139
|
const wanted = new Set(
|
|
140
140
|
names.flatMap((one) => [
|
|
141
141
|
`${ref}.${nameComponent(one)}`,
|
|
142
|
-
`${scope}#${
|
|
142
|
+
`${scope}#${aliasFragment(one, name)}`,
|
|
143
143
|
]),
|
|
144
144
|
);
|
|
145
145
|
const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
|
package/src/outside-tests.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The test files no compiler configuration of a run holds. The runner executes them all
|
|
3
3
|
* the same, so each module one names is a test reference: the own file a relative
|
|
4
|
-
* specifier names, and the declarations that file exports under the names read from it
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* specifier names, and the declarations that file exports under the names read from it.
|
|
5
|
+
* No compiler run reads a module declaration for such a file, so its imports name none.
|
|
6
|
+
* Such a file is read without a program, so what its body references beyond the modules
|
|
7
|
+
* it names is not known.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -23,7 +23,7 @@ import { RESOLVED_EXTENSIONS, type ParseFile } from "./configuration-files.ts";
|
|
|
23
23
|
import { emittedFrom } from "./emit-map.ts";
|
|
24
24
|
import type { Host } from "./host.ts";
|
|
25
25
|
import type { Inventory } from "./inventory.ts";
|
|
26
|
-
import {
|
|
26
|
+
import { namesImported } from "./module-declarations.ts";
|
|
27
27
|
import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.ts";
|
|
28
28
|
import { renderPosition } from "./position.ts";
|
|
29
29
|
import { nameComponent } from "./ref.ts";
|
|
@@ -154,7 +154,6 @@ export function outsideTestReferences(
|
|
|
154
154
|
exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
const declared = namedBy(held);
|
|
158
157
|
const found: Reference[] = [];
|
|
159
158
|
for (const { path, file } of tests) {
|
|
160
159
|
const from = `${path}#`;
|
|
@@ -164,11 +163,6 @@ export function outsideTestReferences(
|
|
|
164
163
|
const read = (to: string, use: Reference["use"]): void => {
|
|
165
164
|
found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
|
|
166
165
|
};
|
|
167
|
-
for (const declaration of declared(named.text, named.names)) {
|
|
168
|
-
for (const to of [declaration.id, ...declaration.members]) {
|
|
169
|
-
read(to, "read");
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
166
|
const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
|
|
173
167
|
if (written === undefined) {
|
|
174
168
|
continue;
|
package/src/overrides.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The overrides of the target's class members. A call through the base class names the
|
|
3
3
|
* base's member and runs the override of whichever subclass the value is, so the base
|
|
4
|
-
* member's declaration references every override. A method
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* member's declaration references every override. A method, an accessor or a property,
|
|
5
|
+
* static or not, overriding a member of a class declared outside the target, at any depth
|
|
6
|
+
* of its `extends` chain, is used by outside code holding the instance or the class
|
|
7
|
+
* through that class, so its own class references it.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import {
|
|
@@ -19,8 +20,9 @@ import {
|
|
|
19
20
|
import type { AliasChains } from "./alias-chain.ts";
|
|
20
21
|
import { memberComponent } from "./class-members.ts";
|
|
21
22
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
22
|
-
import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
|
|
23
|
+
import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
|
|
23
24
|
import { UNANSWERED } from "./query.ts";
|
|
25
|
+
import { nameComponent } from "./ref.ts";
|
|
24
26
|
import type { Reference } from "./references.ts";
|
|
25
27
|
import type { ProjectView } from "./session.ts";
|
|
26
28
|
|
|
@@ -60,7 +62,7 @@ function extending<Brand>(
|
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
64
|
* The references each base class member makes to its overrides, for the classes of one
|
|
63
|
-
* project that extend another class of the target, and each class makes to its
|
|
65
|
+
* project that extend another class of the target, and each class makes to its members
|
|
64
66
|
* that override a member of a class declared outside the target. A member a nearer base
|
|
65
67
|
* already overrides is reached through that override. A private member is not
|
|
66
68
|
* overridden. Each reference is placed at the override and made by its file.
|
|
@@ -134,7 +136,7 @@ export function overrideReferences<Brand>(
|
|
|
134
136
|
const outsideNames = outsideMemberNames(project, bases, outsideBases);
|
|
135
137
|
for (const [classId, names] of outsideNames) {
|
|
136
138
|
for (const [component, member] of members.get(classId) ?? []) {
|
|
137
|
-
if (
|
|
139
|
+
if (names.has(component)) {
|
|
138
140
|
found.push({
|
|
139
141
|
from: classId,
|
|
140
142
|
to: member.id,
|
|
@@ -166,7 +168,8 @@ export function overrideReferences<Brand>(
|
|
|
166
168
|
|
|
167
169
|
/**
|
|
168
170
|
* Per class of the target with a class declared outside the target in its `extends`
|
|
169
|
-
* chain, the
|
|
171
|
+
* chain, the member components that outside class's instances and constructor carry, a
|
|
172
|
+
* static one spelled as a static member's component is.
|
|
170
173
|
*/
|
|
171
174
|
function outsideMemberNames<Brand>(
|
|
172
175
|
project: ProjectView<Brand>,
|
|
@@ -175,10 +178,16 @@ function outsideMemberNames<Brand>(
|
|
|
175
178
|
): ReadonlyMap<string, ReadonlySet<string>> {
|
|
176
179
|
const direct = new Map<string, ReadonlySet<string>>();
|
|
177
180
|
for (const [classId, base] of outsideBases) {
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
const instance = project.queries.declaredTypeOf(base);
|
|
182
|
+
const statics = project.queries.typeOfSymbol(base);
|
|
183
|
+
const components = [
|
|
184
|
+
...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
|
|
185
|
+
...namesOf(project, statics === UNANSWERED ? undefined : statics).map(
|
|
186
|
+
(name) => `${nameComponent(name)}:static`,
|
|
187
|
+
),
|
|
188
|
+
];
|
|
189
|
+
if (components.length > 0) {
|
|
190
|
+
direct.set(classId, new Set(components));
|
|
182
191
|
}
|
|
183
192
|
}
|
|
184
193
|
const names = new Map<string, ReadonlySet<string>>();
|
|
@@ -200,3 +209,9 @@ function outsideMemberNames<Brand>(
|
|
|
200
209
|
}
|
|
201
210
|
return names;
|
|
202
211
|
}
|
|
212
|
+
|
|
213
|
+
/** The names of a type's properties; none where the type or its properties are unanswered. */
|
|
214
|
+
function namesOf<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly string[] {
|
|
215
|
+
const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
|
|
216
|
+
return properties === UNANSWERED ? [] : properties.map((property) => property.name);
|
|
217
|
+
}
|
package/src/ref.ts
CHANGED
|
@@ -47,7 +47,7 @@ const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/package\\.json`;
|
|
|
47
47
|
export const REF_EXPRESSIONS: readonly string[] = [
|
|
48
48
|
`^${SCOPE}#$`,
|
|
49
49
|
`^${SCOPE}#${TS_HEAD}$`,
|
|
50
|
-
`^${SCOPE}
|
|
50
|
+
`^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
|
|
51
51
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
|
|
52
52
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
|
|
53
53
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
|
|
@@ -107,8 +107,12 @@ export type Fragment =
|
|
|
107
107
|
readonly static: boolean;
|
|
108
108
|
readonly typeParameter?: string;
|
|
109
109
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
110
|
+
/**
|
|
111
|
+
* One export specifier, a symbol of its own beside the declaration it names. One
|
|
112
|
+
* written inside a `declare module` block carries the block's name, which is always
|
|
113
|
+
* quoted, so the aliases two blocks of one file declare under one name stay apart.
|
|
114
|
+
*/
|
|
115
|
+
| { readonly of: "alias"; readonly name: string; readonly block?: string }
|
|
112
116
|
/** One dependency of a manifest, scoped by that manifest. */
|
|
113
117
|
| { readonly of: "dependency"; readonly name: string; readonly section: DependencySection };
|
|
114
118
|
|
|
@@ -124,6 +128,10 @@ export function nameComponent(name: string): string {
|
|
|
124
128
|
if (IDENTIFIER.test(name) || PRIVATE_NAME.test(name)) {
|
|
125
129
|
return name;
|
|
126
130
|
}
|
|
131
|
+
return quoted(name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function quoted(name: string): string {
|
|
127
135
|
const escaped = name
|
|
128
136
|
.replaceAll("\\", "\\\\")
|
|
129
137
|
.replaceAll("'", "\\'")
|
|
@@ -142,6 +150,12 @@ export function computedComponent(text: string): string {
|
|
|
142
150
|
return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
|
|
143
151
|
}
|
|
144
152
|
|
|
153
|
+
/** The fragment of an export alias, inside the `declare module` block named, if one is. */
|
|
154
|
+
export function aliasFragment(name: string, block?: string): string {
|
|
155
|
+
const head = block === undefined ? "" : `${quoted(block)}.`;
|
|
156
|
+
return `${head}${nameComponent(name)}:alias`;
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
function spell(component: Component): string {
|
|
146
160
|
return component.computed ? computedComponent(component.text) : nameComponent(component.text);
|
|
147
161
|
}
|
|
@@ -157,7 +171,7 @@ export function renderRef(module: Module, fragment: Fragment): string {
|
|
|
157
171
|
case "module":
|
|
158
172
|
return `${scope}#`;
|
|
159
173
|
case "alias":
|
|
160
|
-
return `${scope}#${
|
|
174
|
+
return `${scope}#${aliasFragment(fragment.name, fragment.block)}`;
|
|
161
175
|
case "dependency":
|
|
162
176
|
return `${scope}#${fragment.name}:${fragment.section}`;
|
|
163
177
|
case "declaration": {
|