@cplieger/deadset-ts 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/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/completion.js +9 -4
- package/dist/src/findings/emitters.js +35 -34
- package/dist/src/findings/reads-and-writes.js +3 -2
- package/dist/src/findings/visibility-narrowing.js +2 -1
- 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 +18 -2
- package/dist/src/reporters/sarif.js +3 -1
- package/dist/src/suppress-file.js +3 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-programs.js +6 -31
- package/jsr.json +1 -1
- package/package.json +2 -2
- 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/completion.ts +11 -4
- package/src/findings/emitters.ts +38 -51
- package/src/findings/reads-and-writes.ts +7 -2
- package/src/findings/visibility-narrowing.ts +2 -1
- 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 +27 -4
- package/src/reporters/sarif.ts +5 -1
- package/src/suppress-file.ts +3 -6
- package/src/version.ts +1 -1
- package/src/workspace-programs.ts +6 -35
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 };
|
|
@@ -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);
|
package/src/findings/emitters.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Confidence } from "../config.ts";
|
|
|
2
2
|
import { evaluateEdges, type EdgeEvaluation } from "../edges.ts";
|
|
3
3
|
import type { CompletedFinding } from "../finding.ts";
|
|
4
4
|
import { liveFilesAt } from "../matrix.ts";
|
|
5
|
+
import { declarationKey } from "../ref.ts";
|
|
5
6
|
import { ledgerOf, type Dials, type Ledger, type SuppressionRecord } from "../suppress.ts";
|
|
6
7
|
import { insideSkipped } from "../type-errors.ts";
|
|
7
8
|
import { completed, dialsOf, reportable } from "./completion.ts";
|
|
@@ -28,10 +29,10 @@ export const EMITTERS: Emitters = new Map<string, Emitter>([
|
|
|
28
29
|
]);
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
* The
|
|
32
|
-
*
|
|
32
|
+
* The keys of every declaration an unanswered question could have kept live, which the run
|
|
33
|
+
* reports nothing about: a finding about one would be computed from the gap.
|
|
33
34
|
*/
|
|
34
|
-
function
|
|
35
|
+
function heldKeys(input: EmitterInput): ReadonlySet<string> {
|
|
35
36
|
const held = new Set(input.swept.heldByUnanswered);
|
|
36
37
|
if (held.size === 0) {
|
|
37
38
|
return held;
|
|
@@ -39,16 +40,16 @@ function heldRefs(input: EmitterInput): ReadonlySet<string> {
|
|
|
39
40
|
return new Set(
|
|
40
41
|
input.swept.matrix.union.symbols
|
|
41
42
|
.filter((symbol) => held.has(symbol.id))
|
|
42
|
-
.map((symbol) => symbol.ref),
|
|
43
|
+
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)),
|
|
43
44
|
);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
* The
|
|
48
|
+
* The keys of every top-level declaration of a live component file. The file's
|
|
48
49
|
* markup may use each of them, which no analysis of the markup can rule out, so the run
|
|
49
50
|
* reports nothing about one while the file is live.
|
|
50
51
|
*/
|
|
51
|
-
function
|
|
52
|
+
function markupKeys(input: EmitterInput): ReadonlySet<string> {
|
|
52
53
|
const { swept } = input;
|
|
53
54
|
if (swept.componentFiles.length === 0) {
|
|
54
55
|
return new Set();
|
|
@@ -61,7 +62,7 @@ function markupRefs(input: EmitterInput): ReadonlySet<string> {
|
|
|
61
62
|
return new Set(
|
|
62
63
|
swept.matrix.union.symbols
|
|
63
64
|
.filter((symbol) => live.has(symbol.parent))
|
|
64
|
-
.map((symbol) => symbol.ref),
|
|
65
|
+
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)),
|
|
65
66
|
);
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -126,8 +127,7 @@ function dialsOver(
|
|
|
126
127
|
* family's findings completed by one step, so every component number is minted once, the
|
|
127
128
|
* finding each record claims withheld, the rest withheld by the dials, so a root one family
|
|
128
129
|
* reports withholds what another reports in its component, and every declared edge
|
|
129
|
-
* evaluated over what remains, so a pending finding is in its evaluation alone.
|
|
130
|
-
* 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.
|
|
131
131
|
*/
|
|
132
132
|
export function decidedFindings(
|
|
133
133
|
input: EmitterInput,
|
|
@@ -137,20 +137,7 @@ export function decidedFindings(
|
|
|
137
137
|
const unmarked =
|
|
138
138
|
suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
|
|
139
139
|
const would = [...unmarked, ...found];
|
|
140
|
-
|
|
141
|
-
const shown =
|
|
142
|
-
input.config.analysis.minConfidence === "possible"
|
|
143
|
-
? decided
|
|
144
|
-
: dialledOver(
|
|
145
|
-
atPossible(input),
|
|
146
|
-
{
|
|
147
|
-
unmarked: atPossible(suppressed.unmarked),
|
|
148
|
-
records: suppressed.records,
|
|
149
|
-
},
|
|
150
|
-
found,
|
|
151
|
-
unmarked,
|
|
152
|
-
);
|
|
153
|
-
return { ...decided, would, withheld: withheldOf(decided.findings, shown.findings) };
|
|
140
|
+
return { ...dialledOver(input, suppressed, found, unmarked), would };
|
|
154
141
|
}
|
|
155
142
|
|
|
156
143
|
/** The run's input with the minimum confidence at its lowest, which withholds nothing. */
|
|
@@ -172,46 +159,46 @@ function dialledOver(
|
|
|
172
159
|
suppressed: Suppressed,
|
|
173
160
|
found: readonly CompletedFinding[],
|
|
174
161
|
unmarked: readonly CompletedFinding[],
|
|
175
|
-
): Omit<DecidedFindings, "would"
|
|
162
|
+
): Omit<DecidedFindings, "would"> {
|
|
176
163
|
const would = [...unmarked, ...found];
|
|
177
164
|
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
178
|
-
const unanswered =
|
|
179
|
-
const markup =
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
(finding)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
);
|
|
165
|
+
const unanswered = heldKeys(input);
|
|
166
|
+
const markup = markupKeys(input);
|
|
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);
|
|
190
177
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
191
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
findings: evaluated.findings,
|
|
180
|
+
evaluations: evaluated.evaluations,
|
|
181
|
+
ledger,
|
|
182
|
+
withheld: withheldOf(input, candidates),
|
|
183
|
+
};
|
|
192
184
|
}
|
|
193
185
|
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
): Withheld {
|
|
199
|
-
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);
|
|
200
193
|
const counts = { certain: 0, probable: 0, possible: 0 };
|
|
201
|
-
for (const finding of
|
|
202
|
-
if (!
|
|
194
|
+
for (const finding of candidates) {
|
|
195
|
+
if (dials.withholds(finding) && !lowest.withholds(finding)) {
|
|
203
196
|
counts[finding.confidence] += 1;
|
|
204
197
|
}
|
|
205
198
|
}
|
|
206
199
|
return counts;
|
|
207
200
|
}
|
|
208
201
|
|
|
209
|
-
/** A finding's identity across two decisions of one run: its position, code and subject. */
|
|
210
|
-
function findingKey(finding: CompletedFinding): string {
|
|
211
|
-
const { path, line, column } = finding.position;
|
|
212
|
-
return JSON.stringify([path, line, column, finding.code, finding.symbol.ref]);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
202
|
/** Every finding the run reports, each family's in table order. */
|
|
216
203
|
export function findingsOf(input: EmitterInput): readonly CompletedFinding[] {
|
|
217
204
|
return decidedFindings(input).findings;
|
|
@@ -2,6 +2,7 @@ import type { Finding, FindingDetails, FindingPosition } from "../finding.ts";
|
|
|
2
2
|
import type { InventorySymbol } from "../inventory.ts";
|
|
3
3
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
4
4
|
import { positionKey } from "../position.ts";
|
|
5
|
+
import { declarationKey } from "../ref.ts";
|
|
5
6
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
6
7
|
|
|
7
8
|
/** The codes of this family. */
|
|
@@ -242,12 +243,16 @@ function unusedTypeParameters(run: Run): readonly Finding[] {
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
/**
|
|
245
|
-
* The
|
|
246
|
+
* The keys of the declarations the write-only kind reports, whatever the
|
|
246
247
|
* severity: a narrowing kind yields each to it, since deleting the declaration
|
|
247
248
|
* supersedes narrowing it.
|
|
248
249
|
*/
|
|
249
250
|
export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string> {
|
|
250
|
-
return new Set(
|
|
251
|
+
return new Set(
|
|
252
|
+
writeOnlySymbols(indexed(input)).map((finding) =>
|
|
253
|
+
declarationKey(finding.symbol.ref, finding.position.path),
|
|
254
|
+
),
|
|
255
|
+
);
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
/**
|
|
@@ -2,6 +2,7 @@ import type { Finding } from "../finding.ts";
|
|
|
2
2
|
import { OUTSIDE, type Graph } from "../graph.ts";
|
|
3
3
|
import type { InventorySymbol, SymbolKind } from "../inventory.ts";
|
|
4
4
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
5
|
+
import { declarationKey } from "../ref.ts";
|
|
5
6
|
import type { RootKind } from "../roots.ts";
|
|
6
7
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
7
8
|
import { writeOnlyDeclarations } from "./reads-and-writes.ts";
|
|
@@ -159,7 +160,7 @@ function narrowing(
|
|
|
159
160
|
const file = facts.fileOf[at] ?? OUTSIDE;
|
|
160
161
|
if (
|
|
161
162
|
!topLevelExport(facts, at, symbol) ||
|
|
162
|
-
world.writeOnly.has(symbol.ref) ||
|
|
163
|
+
world.writeOnly.has(declarationKey(symbol.ref, symbol.position.path)) ||
|
|
163
164
|
symbol.kind === "export-alias" ||
|
|
164
165
|
entersByName(facts.rootedBy[at]) ||
|
|
165
166
|
entersByName(facts.rootedBy[file]) ||
|
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": {
|
|
@@ -169,6 +183,15 @@ export function renderRef(module: Module, fragment: Fragment): string {
|
|
|
169
183
|
}
|
|
170
184
|
}
|
|
171
185
|
|
|
186
|
+
/**
|
|
187
|
+
* One declaration's key among a run's: its reference and the file that declares it. Two
|
|
188
|
+
* packages of one name spell one reference for two declarations, so the reference alone
|
|
189
|
+
* keys neither.
|
|
190
|
+
*/
|
|
191
|
+
export function declarationKey(ref: string, path: string): string {
|
|
192
|
+
return `${ref}\u0000${path}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
172
195
|
/**
|
|
173
196
|
* Whether one string is a reference of this language's grammar, in its canonical
|
|
174
197
|
* spelling. A string that is not, whether misspelled or spelled in a form the
|
package/src/reporters/sarif.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Confidence, Severity } from "../config.ts";
|
|
2
2
|
import { jsonChunks } from "../json-chunks.ts";
|
|
3
3
|
import { KINDS, type KindRow } from "../kinds.ts";
|
|
4
|
+
import { declarationKey } from "../ref.ts";
|
|
4
5
|
import type { Report, WireFinding, WireStaleSuppression } from "../report.ts";
|
|
5
6
|
import { lineHashes, symbolFingerprint } from "./fingerprint.ts";
|
|
6
7
|
import { RenderError, type RenderOptions } from "./reporter.ts";
|
|
@@ -160,7 +161,10 @@ function relatedOf(found: WireFinding): readonly Related[] {
|
|
|
160
161
|
related.push({ label: "write", at, endLine: at.end_line });
|
|
161
162
|
}
|
|
162
163
|
for (const one of found.component.members ?? []) {
|
|
163
|
-
if (
|
|
164
|
+
if (
|
|
165
|
+
declarationKey(one.ref, one.position.path) !==
|
|
166
|
+
declarationKey(found.symbol.ref, found.position.path)
|
|
167
|
+
) {
|
|
164
168
|
related.push({ label: "member", at: one.position, endLine: one.position.end_line });
|
|
165
169
|
}
|
|
166
170
|
}
|
package/src/suppress-file.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { jsonChunks } from "./json-chunks.ts";
|
|
|
11
11
|
import { joinPath } from "./paths.ts";
|
|
12
12
|
import { positionAt, walkDocument } from "./json-document.ts";
|
|
13
13
|
import type { Position } from "./position.ts";
|
|
14
|
+
import { declarationKey } from "./ref.ts";
|
|
14
15
|
import {
|
|
15
16
|
CODE_FORM,
|
|
16
17
|
SUPPRESSION_WITHOUT_REASON,
|
|
@@ -84,10 +85,6 @@ interface Read {
|
|
|
84
85
|
/** The declarations of the run by reference and file, which is what an entry names. */
|
|
85
86
|
type ByReference = ReadonlyMap<string, readonly InventorySymbol[]>;
|
|
86
87
|
|
|
87
|
-
function referenceKey(ref: string, path: string): string {
|
|
88
|
-
return `${ref}\u0000${path}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
88
|
/**
|
|
92
89
|
* Reads one entry or row: the records it binds, the refusals it carries, or the
|
|
93
90
|
* malformed value that ends the run.
|
|
@@ -148,7 +145,7 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
|
|
|
148
145
|
if (refusals.length > 0) {
|
|
149
146
|
return { records: [], refusals };
|
|
150
147
|
}
|
|
151
|
-
const named = byReference.get(
|
|
148
|
+
const named = byReference.get(declarationKey(symbol, written.path)) ?? [];
|
|
152
149
|
if (named.length === 0) {
|
|
153
150
|
return { records: [{ ...written, bound: "" }], refusals: [] };
|
|
154
151
|
}
|
|
@@ -158,7 +155,7 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
|
|
|
158
155
|
function byReferenceOf(symbols: readonly InventorySymbol[]): ByReference {
|
|
159
156
|
const byReference = new Map<string, InventorySymbol[]>();
|
|
160
157
|
for (const symbol of symbols) {
|
|
161
|
-
const key =
|
|
158
|
+
const key = declarationKey(symbol.ref, symbol.position.path);
|
|
162
159
|
byReference.set(key, [...(byReference.get(key) ?? []), symbol]);
|
|
163
160
|
}
|
|
164
161
|
return byReference;
|
package/src/version.ts
CHANGED