@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3
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 +95 -60
- package/dist/src/augmentations.js +14 -12
- package/dist/src/component-layer.js +1 -1
- package/dist/src/components.js +62 -20
- package/dist/src/configuration-files.js +230 -30
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +104 -13
- package/dist/src/convention-rows.js +10 -0
- package/dist/src/conventions.js +35 -1
- package/dist/src/dependencies.js +90 -32
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/element-access.js +341 -0
- package/dist/src/erased-stores.js +317 -0
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +78 -16
- package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
- package/dist/src/findings/emitters.js +1 -5
- package/dist/src/findings/self-check.js +27 -10
- package/dist/src/findings-pass.js +6 -5
- package/dist/src/in-operands.js +30 -0
- package/dist/src/indexed-access.js +264 -119
- package/dist/src/intra-function-parts.js +25 -11
- package/dist/src/inventory.js +28 -12
- package/dist/src/keyof-comparisons.js +170 -0
- package/dist/src/kinds.js +17 -7
- package/dist/src/manifest.js +6 -16
- package/dist/src/matrix.js +20 -2
- package/dist/src/query.js +3 -0
- package/dist/src/references.js +14 -3
- package/dist/src/report.js +4 -1
- package/dist/src/reporters/sarif.js +4 -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/serialization-contract.js +43 -14
- package/dist/src/session.js +12 -8
- package/dist/src/suppress-file.js +58 -6
- package/dist/src/suppress-inline.js +14 -5
- package/dist/src/suppress.js +64 -35
- package/dist/src/unimplemented-callees.js +312 -0
- package/dist/src/value-reach.js +7 -7
- package/dist/src/verbs/analyze.js +11 -9
- 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 +3 -2
- package/src/analysis.ts +145 -82
- package/src/augmentations.ts +14 -11
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +104 -22
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +250 -31
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +135 -17
- package/src/convention-rows.ts +23 -0
- package/src/conventions.ts +53 -1
- package/src/dependencies.ts +119 -38
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/element-access.ts +446 -0
- package/src/entry-points.ts +1 -1
- package/src/erased-stores.ts +413 -0
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +3 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +97 -21
- package/src/findings/dependencies-and-module-machinery.ts +1 -1
- package/src/findings/emitters.ts +4 -9
- package/src/findings/self-check.ts +31 -14
- package/src/findings-pass.ts +14 -7
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/in-operands.ts +45 -0
- package/src/indexed-access.ts +303 -137
- package/src/intra-function-parts.ts +41 -16
- package/src/inventory.ts +37 -33
- package/src/json-document.ts +1 -1
- package/src/keyof-comparisons.ts +211 -0
- package/src/kinds.ts +20 -7
- package/src/manifest.ts +7 -22
- package/src/matrix.ts +34 -2
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/query.ts +10 -0
- package/src/references.ts +30 -7
- package/src/report.ts +11 -5
- package/src/reporters/sarif.ts +4 -0
- 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/serialization-contract.ts +53 -14
- package/src/session.ts +34 -31
- package/src/stores.ts +1 -1
- package/src/suppress-file.ts +68 -5
- package/src/suppress-inline.ts +17 -7
- package/src/suppress.ts +79 -47
- package/src/template-field.ts +1 -1
- package/src/unimplemented-callees.ts +411 -0
- package/src/value-reach.ts +12 -8
- package/src/verbs/analyze.ts +15 -9
- 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
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The serialization-contract class: the data members and `toJSON` of every
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* destination does, to a fixpoint. A function invoked through `Function.prototype.call`,
|
|
9
|
-
* `apply` or `bind` is the callee, given the arguments those methods pass it.
|
|
2
|
+
* The serialization-contract class: the data members and `toJSON` of every type a value's
|
|
3
|
+
* static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
|
|
4
|
+
* names, and `toString` too where it reaches an `unknown` or `any` parameter of a function
|
|
5
|
+
* outside the analysis or an erased property of a type the program does not declare. A
|
|
6
|
+
* function of the project handing such a parameter to a destination is one, to a fixpoint,
|
|
7
|
+
* and a function invoked through `call`, `apply` or `bind` is the callee.
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
import {
|
|
@@ -59,6 +57,7 @@ import { nodeKey, type Inventory } from "./inventory.ts";
|
|
|
59
57
|
import { renderPosition } from "./position.ts";
|
|
60
58
|
import { must } from "./query.ts";
|
|
61
59
|
import type { ProjectView } from "./session.ts";
|
|
60
|
+
import { erasedStores, type ErasedStores } from "./erased-stores.ts";
|
|
62
61
|
import { valueReach, type Reached } from "./value-reach.ts";
|
|
63
62
|
|
|
64
63
|
/** The serializer every run reads, whatever the configuration names. */
|
|
@@ -527,13 +526,20 @@ function destinations<Brand>(
|
|
|
527
526
|
return { invocation, at, mayReceive };
|
|
528
527
|
}
|
|
529
528
|
|
|
530
|
-
/** One
|
|
529
|
+
/** One value that carries classes and object types of the target into a destination. */
|
|
531
530
|
interface Flow {
|
|
532
531
|
readonly site: Node;
|
|
533
532
|
readonly classes: ReadonlySet<string>;
|
|
534
533
|
/** The members of the interfaces and object types the value carries. */
|
|
535
534
|
readonly members: readonly TSSymbol[];
|
|
536
535
|
readonly to: Destination;
|
|
536
|
+
/** What a retention names the destination by. */
|
|
537
|
+
readonly detail: string;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** What two reaches carry together. */
|
|
541
|
+
function joined(a: Reached, b: Reached): Reached {
|
|
542
|
+
return { classes: new Set([...a.classes, ...b.classes]), members: [...a.members, ...b.members] };
|
|
537
543
|
}
|
|
538
544
|
|
|
539
545
|
/** Each argument of a call that can receive one whose value carries a class of the target. */
|
|
@@ -542,8 +548,9 @@ function flowsOf<Brand>(
|
|
|
542
548
|
held: Inventory,
|
|
543
549
|
sites: readonly Site[],
|
|
544
550
|
to: Destinations,
|
|
551
|
+
stores: ErasedStores,
|
|
545
552
|
): readonly Flow[] {
|
|
546
|
-
const reach = valueReach(project, held);
|
|
553
|
+
const reach = valueReach(project, held, stores.inside);
|
|
547
554
|
const arguments_ = sites.flatMap((site) =>
|
|
548
555
|
to.mayReceive(site.call)
|
|
549
556
|
? to.invocation(site.call).arguments.flatMap((argument, index) => {
|
|
@@ -560,7 +567,7 @@ function flowsOf<Brand>(
|
|
|
560
567
|
const typeOf = new Map<Expression, Type | undefined>(
|
|
561
568
|
asked.map((one, index) => [one.value, types[index]]),
|
|
562
569
|
);
|
|
563
|
-
|
|
570
|
+
const passed = arguments_.flatMap((one) => {
|
|
564
571
|
const type = typeOf.get(one.value);
|
|
565
572
|
let reached: Reached = { classes: new Set<string>(), members: [] };
|
|
566
573
|
if (one.value.kind === SyntaxKind.ThisKeyword) {
|
|
@@ -571,12 +578,38 @@ function flowsOf<Brand>(
|
|
|
571
578
|
} else if (type !== undefined) {
|
|
572
579
|
reached = reach.reachedFrom(type);
|
|
573
580
|
}
|
|
581
|
+
for (const stored of stores.ofVariable(one.value) ?? []) {
|
|
582
|
+
reached = joined(reached, reach.reachedFrom(stored));
|
|
583
|
+
}
|
|
574
584
|
const carries = reached.classes.size > 0 || reached.members.length > 0;
|
|
575
585
|
const destination = carries ? to.at(one.site.call, one.argument) : undefined;
|
|
576
586
|
return destination === undefined
|
|
577
587
|
? []
|
|
578
|
-
: [
|
|
588
|
+
: [
|
|
589
|
+
{
|
|
590
|
+
site: one.value,
|
|
591
|
+
classes: reached.classes,
|
|
592
|
+
members: reached.members,
|
|
593
|
+
to: destination,
|
|
594
|
+
detail: `passed to ${destination.callee}`,
|
|
595
|
+
},
|
|
596
|
+
];
|
|
597
|
+
});
|
|
598
|
+
const left = stores.outside.flatMap((store) => {
|
|
599
|
+
const reached = reach.reachedFrom(store.type);
|
|
600
|
+
return reached.classes.size === 0 && reached.members.length === 0
|
|
601
|
+
? []
|
|
602
|
+
: [
|
|
603
|
+
{
|
|
604
|
+
site: store.value,
|
|
605
|
+
classes: reached.classes,
|
|
606
|
+
members: reached.members,
|
|
607
|
+
to: { reach: "conversions" as const, callee: store.property },
|
|
608
|
+
detail: `stored in the property ${store.property} of a type the program does not declare`,
|
|
609
|
+
},
|
|
610
|
+
];
|
|
579
611
|
});
|
|
612
|
+
return [...passed, ...left];
|
|
580
613
|
}
|
|
581
614
|
|
|
582
615
|
/**
|
|
@@ -589,7 +622,13 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
|
|
|
589
622
|
const { project, held, targetRoot } = input;
|
|
590
623
|
const walked = walkProject(project, held);
|
|
591
624
|
const chains = aliasChains(project, held);
|
|
592
|
-
const flows = flowsOf(
|
|
625
|
+
const flows = flowsOf(
|
|
626
|
+
project,
|
|
627
|
+
held,
|
|
628
|
+
walked.sites,
|
|
629
|
+
destinations(input, chains, walked),
|
|
630
|
+
erasedStores(project),
|
|
631
|
+
);
|
|
593
632
|
if (flows.length === 0) {
|
|
594
633
|
return [];
|
|
595
634
|
}
|
|
@@ -609,7 +648,7 @@ export function serializationContract<Brand>(input: DetectorInput<Brand>): reado
|
|
|
609
648
|
const found: Evidence[] = [];
|
|
610
649
|
for (const flow of flows) {
|
|
611
650
|
const site = renderPosition(flow.site.getSourceFile(), targetRoot, flow.site.getStart());
|
|
612
|
-
const detail =
|
|
651
|
+
const { detail } = flow;
|
|
613
652
|
for (const member of [...flow.classes].flatMap((id) => members.get(id) ?? [])) {
|
|
614
653
|
const symbol = byId.get(member.id);
|
|
615
654
|
const name = symbol === undefined ? "" : (memberComponent(symbol, byId) ?? "");
|
package/src/session.ts
CHANGED
|
@@ -28,13 +28,10 @@ import {
|
|
|
28
28
|
} from "./query.ts";
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* The compiler the analysis reads through
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* It is an interface because the run's own accounting for that lifetime is not on
|
|
36
|
-
* the client's surface: a snapshot reports whether it is disposed and not how many
|
|
37
|
-
* a client opened, so the count is observed at this seam.
|
|
31
|
+
* The compiler the analysis reads through: a client over a protocol to a separate
|
|
32
|
+
* process, so every accessor call is a round trip and a run holds one client and one
|
|
33
|
+
* snapshot. It is an interface because a snapshot reports whether it is disposed and not
|
|
34
|
+
* how many a client opened, so the run's count of them is observed at this seam.
|
|
38
35
|
*/
|
|
39
36
|
export interface Engine {
|
|
40
37
|
/**
|
|
@@ -187,14 +184,10 @@ export function openEngine(options: EngineOptions): Engine {
|
|
|
187
184
|
}
|
|
188
185
|
|
|
189
186
|
/**
|
|
190
|
-
* A node of one project's program, carried with that project's identity.
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* compiler will not exchange and a handle cannot reach another project's checker.
|
|
195
|
-
* Handles are only meaningful inside the program that produced them, and the
|
|
196
|
-
* cross-project step of the analysis compares rendered positions rather than
|
|
197
|
-
* handles.
|
|
187
|
+
* A node of one project's program, carried with that project's identity. `Brand` is a
|
|
188
|
+
* type parameter and never a value: each visitor introduces its own, so two projects'
|
|
189
|
+
* handle types do not exchange and a handle cannot reach another project's checker. The
|
|
190
|
+
* cross-project step compares rendered positions, never handles.
|
|
198
191
|
*/
|
|
199
192
|
export interface Handle<Brand> {
|
|
200
193
|
readonly node: Node;
|
|
@@ -253,14 +246,11 @@ export interface ProjectView<Brand> {
|
|
|
253
246
|
*/
|
|
254
247
|
aliasStepOf(symbol: TSSymbol): Answer<TSSymbol | undefined>;
|
|
255
248
|
/**
|
|
256
|
-
* The node one symbol's declaration handle names, read in this project's program,
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
* would answer out of whichever project reached the symbol first. The project is
|
|
262
|
-
* named here instead, and the answer becomes a handle of this project like any
|
|
263
|
-
* other. The read is local once the file has been fetched.
|
|
249
|
+
* The node one symbol's declaration handle names, read in this project's program, or
|
|
250
|
+
* `undefined` where this program does not hold that file. A symbol is shared by every
|
|
251
|
+
* project of the snapshot and remembers the one it was first seen in, so the project is
|
|
252
|
+
* named here, or the answer would come from whichever project reached the symbol first.
|
|
253
|
+
* The read is local once the file has been fetched.
|
|
264
254
|
*/
|
|
265
255
|
declarationAt(handle: NodeHandle): Handle<Brand> | undefined;
|
|
266
256
|
}
|
|
@@ -272,6 +262,13 @@ export interface ProjectView<Brand> {
|
|
|
272
262
|
*/
|
|
273
263
|
export type ProjectVisitor<Result> = <Brand>(project: ProjectView<Brand>) => Result;
|
|
274
264
|
|
|
265
|
+
/** One project a session opened: its configuration and the files of its own program. */
|
|
266
|
+
export interface OpenedFiles {
|
|
267
|
+
readonly configFile: string;
|
|
268
|
+
/** The program's source files no default library and no external library holds. */
|
|
269
|
+
readonly fileNames: () => readonly string[];
|
|
270
|
+
}
|
|
271
|
+
|
|
275
272
|
/**
|
|
276
273
|
* The source files of one program that `own` admits, in the program's order, none of
|
|
277
274
|
* them one of the compiler's default libraries.
|
|
@@ -417,7 +414,6 @@ export function ownedAs<Brand>(
|
|
|
417
414
|
|
|
418
415
|
/** Every diagnostic set a project is refused for carrying an error in. */
|
|
419
416
|
export interface ProjectDiagnostics {
|
|
420
|
-
readonly configFile: string;
|
|
421
417
|
readonly syntactic: readonly Diagnostic[];
|
|
422
418
|
readonly semantic: readonly Diagnostic[];
|
|
423
419
|
readonly configParsing: readonly Diagnostic[];
|
|
@@ -431,7 +427,6 @@ export interface ProjectDiagnostics {
|
|
|
431
427
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
432
428
|
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
433
429
|
return {
|
|
434
|
-
configFile: project.configFile,
|
|
435
430
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
436
431
|
semantic: project.program.getSemanticDiagnostics(),
|
|
437
432
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
@@ -489,10 +484,10 @@ function projectsOf(
|
|
|
489
484
|
/**
|
|
490
485
|
* Runs `visit` over every project the compiler configurations name, inside one never updated
|
|
491
486
|
* snapshot of one client, and answers in their order. A configuration `programs` describes is
|
|
492
|
-
* built as that program, or opened from the configuration it writes
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
487
|
+
* built as that program, or opened from the configuration it writes. One the snapshot holds
|
|
488
|
+
* nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
|
|
489
|
+
* `first` runs over every opened project before the first visit and `last` after the last,
|
|
490
|
+
* both while the client answers; the snapshot and the client are then released.
|
|
496
491
|
*/
|
|
497
492
|
export function runSession<Result>(
|
|
498
493
|
engine: Engine,
|
|
@@ -501,6 +496,7 @@ export function runSession<Result>(
|
|
|
501
496
|
unopened: Unopened = "refuse",
|
|
502
497
|
programs: ReadonlyMap<string, WorkspaceProgram> = new Map(),
|
|
503
498
|
last: () => void = () => undefined,
|
|
499
|
+
first: (opened: readonly OpenedFiles[]) => void = () => undefined,
|
|
504
500
|
): SessionResult<Result> {
|
|
505
501
|
try {
|
|
506
502
|
const built: string[] = [];
|
|
@@ -525,7 +521,7 @@ export function runSession<Result>(
|
|
|
525
521
|
try {
|
|
526
522
|
const opened = projectsOf(snapshot, built, written);
|
|
527
523
|
const log = new UnansweredLog();
|
|
528
|
-
const
|
|
524
|
+
const views: ProjectView<unknown>[] = [];
|
|
529
525
|
const missing: string[] = [];
|
|
530
526
|
for (const configFile of configFiles) {
|
|
531
527
|
const project = opened.get(configFile);
|
|
@@ -536,8 +532,15 @@ export function runSession<Result>(
|
|
|
536
532
|
missing.push(configFile);
|
|
537
533
|
continue;
|
|
538
534
|
}
|
|
539
|
-
|
|
535
|
+
views.push(viewOf(project, configFile, engine, log));
|
|
540
536
|
}
|
|
537
|
+
first(
|
|
538
|
+
views.map((view) => ({
|
|
539
|
+
configFile: view.configFile,
|
|
540
|
+
fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
|
|
541
|
+
})),
|
|
542
|
+
);
|
|
543
|
+
const projects = views.map((view) => visit(view));
|
|
541
544
|
last();
|
|
542
545
|
return {
|
|
543
546
|
projects,
|
package/src/stores.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface Stores {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** What one configuration contributes to the run's stores. */
|
|
40
|
-
|
|
40
|
+
interface ConfigurationStores {
|
|
41
41
|
readonly references: readonly Reference[];
|
|
42
42
|
readonly accessors: readonly string[];
|
|
43
43
|
}
|
package/src/suppress-file.ts
CHANGED
|
@@ -59,6 +59,13 @@ const BASELINE_SHAPE: Shape = {
|
|
|
59
59
|
recordWant: "a row naming code, symbol, path and reason, each a string",
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The reference scheme of this analyzer's language. A record of the other scheme is
|
|
64
|
+
* form-checked and then left to the analyzer of its language: it binds nothing here, counts
|
|
65
|
+
* in neither total and is reported under no code.
|
|
66
|
+
*/
|
|
67
|
+
const OWN_SCHEME = "ts://";
|
|
68
|
+
|
|
62
69
|
/** The closed key list of one entry or row. */
|
|
63
70
|
const RECORD_KEYS: ReadonlySet<string> = new Set(["code", "symbol", "path", "reason"]);
|
|
64
71
|
|
|
@@ -126,6 +133,9 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
|
|
|
126
133
|
if (path !== undefined && path !== "" && !isEntryPath(path)) {
|
|
127
134
|
return refuse(JSON.stringify(path), PATH_WANT);
|
|
128
135
|
}
|
|
136
|
+
if (!symbol.startsWith(OWN_SCHEME)) {
|
|
137
|
+
return { records: [], refusals: [] };
|
|
138
|
+
}
|
|
129
139
|
|
|
130
140
|
const written = {
|
|
131
141
|
code,
|
|
@@ -284,15 +294,68 @@ export function rowReason(provenance: Provenance): string {
|
|
|
284
294
|
return `recorded by ${provenance.analyzer} ${provenance.version}`;
|
|
285
295
|
}
|
|
286
296
|
|
|
297
|
+
/** A row of the other language's scheme a write keeps: the members the document held, and no other. */
|
|
298
|
+
interface KeptRow {
|
|
299
|
+
readonly code: string;
|
|
300
|
+
readonly symbol: string;
|
|
301
|
+
readonly path?: string;
|
|
302
|
+
readonly reason?: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The rows of the other language's scheme that the baseline document `text` holds, in
|
|
307
|
+
* document order and as written, which a write keeps. A document that is not a baseline,
|
|
308
|
+
* or a row in a shape no record takes, is refused rather than dropped.
|
|
309
|
+
*/
|
|
310
|
+
export function otherSchemeRows(text: string): readonly KeptRow[] {
|
|
311
|
+
const refuse = (detail: string): never => {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`,
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
let parsed: unknown;
|
|
317
|
+
try {
|
|
318
|
+
parsed = JSON.parse(text);
|
|
319
|
+
} catch (error: unknown) {
|
|
320
|
+
return refuse(error instanceof Error ? error.message : String(error));
|
|
321
|
+
}
|
|
322
|
+
const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
|
|
323
|
+
if (!Array.isArray(held)) {
|
|
324
|
+
return refuse(BASELINE_SHAPE.documentWant);
|
|
325
|
+
}
|
|
326
|
+
return (held as readonly unknown[]).flatMap((value): KeptRow[] => {
|
|
327
|
+
try {
|
|
328
|
+
readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
|
|
329
|
+
} catch (error: unknown) {
|
|
330
|
+
return refuse(error instanceof Error ? error.message : String(error));
|
|
331
|
+
}
|
|
332
|
+
// readRecord has checked every member is a string and the code and symbol are present.
|
|
333
|
+
const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
|
|
334
|
+
if (code === undefined || symbol === undefined || symbol.startsWith(OWN_SCHEME)) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
return [
|
|
338
|
+
{
|
|
339
|
+
code,
|
|
340
|
+
symbol,
|
|
341
|
+
...(path === undefined ? {} : { path }),
|
|
342
|
+
...(reason === undefined ? {} : { reason }),
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
287
348
|
/**
|
|
288
|
-
* A baseline holding one row per finding, in the order given, which is the report's
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
349
|
+
* A baseline holding one row per finding, in the order given, which is the report's, after
|
|
350
|
+
* the other language's rows `kept`, so two analyzers writing one document in either order
|
|
351
|
+
* write the same bytes. Every recorded row carries the provenance as its reason. A finding
|
|
352
|
+
* missing a value a row needs, and an identity missing its name or version, are refused
|
|
353
|
+
* rather than written, because the grammar requires a reason on every row.
|
|
292
354
|
*/
|
|
293
355
|
export function writeBaseline(
|
|
294
356
|
findings: readonly Recorded[],
|
|
295
357
|
provenance: Provenance,
|
|
358
|
+
kept: readonly KeptRow[] = [],
|
|
296
359
|
): Iterable<string> {
|
|
297
360
|
if (provenance.analyzer === "" || provenance.version === "") {
|
|
298
361
|
throw new Error(
|
|
@@ -308,5 +371,5 @@ export function writeBaseline(
|
|
|
308
371
|
}
|
|
309
372
|
return { code: found.code, symbol: found.symbol, path: found.path, reason };
|
|
310
373
|
});
|
|
311
|
-
return jsonChunks({ description: BASELINE_DESCRIPTION, baseline }, 2);
|
|
374
|
+
return jsonChunks({ description: BASELINE_DESCRIPTION, baseline: [...kept, ...baseline] }, 2);
|
|
312
375
|
}
|
package/src/suppress-inline.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The inline directive: `deadset:ignore`, the first token of a `//` line comment, naming
|
|
3
3
|
* one or more codes and a reason, and binding to every declaration that begins on the line
|
|
4
|
-
* below it.
|
|
4
|
+
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { SyntaxKind, type Node, type SourceFile } from "@typescript/native/unstable/ast";
|
|
@@ -37,7 +37,7 @@ const NO_REASON = new RegExp(DIRECTIVE_EXPRESSIONS.noReason);
|
|
|
37
37
|
const DIRECTIVE_FORM = "// deadset:ignore DS0000[,DS0000...] -- <reason>";
|
|
38
38
|
|
|
39
39
|
/** What the grammar's decision procedure makes of one comment. */
|
|
40
|
-
|
|
40
|
+
type Directive =
|
|
41
41
|
| { readonly is: "none" }
|
|
42
42
|
| { readonly is: "directive"; readonly codes: readonly string[]; readonly reason: string }
|
|
43
43
|
| { readonly is: "no-reason"; readonly codes: readonly string[] }
|
|
@@ -64,7 +64,7 @@ export function classifyComment(text: string): Directive {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/** One `//` line comment: the offset of its first solidus, and its text to the end of its line. */
|
|
67
|
-
|
|
67
|
+
interface LineComment {
|
|
68
68
|
readonly offset: number;
|
|
69
69
|
readonly text: string;
|
|
70
70
|
}
|
|
@@ -123,7 +123,7 @@ export function tokenStart(text: string, from: number): number {
|
|
|
123
123
|
* syntax tree holds are skipped, so a `//` inside a string, a template or a regular
|
|
124
124
|
* expression starts nothing.
|
|
125
125
|
*/
|
|
126
|
-
|
|
126
|
+
function lineComments(file: SourceFile): readonly LineComment[] {
|
|
127
127
|
const text = file.text;
|
|
128
128
|
const spans: { readonly start: number; readonly end: number }[] = [];
|
|
129
129
|
const visit = (node: Node): void => {
|
|
@@ -211,6 +211,15 @@ function declarationLines(
|
|
|
211
211
|
return below;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Whether the comment at `offset` follows a token on its own line, so it binds the
|
|
216
|
+
* declarations that begin on that line rather than on the next.
|
|
217
|
+
*/
|
|
218
|
+
function trails(file: SourceFile, offset: number): boolean {
|
|
219
|
+
const { line } = file.getLineAndCharacterOfPosition(offset);
|
|
220
|
+
return tokenStart(file.text, file.getPositionOfLineAndCharacter(line, 0)) < offset;
|
|
221
|
+
}
|
|
222
|
+
|
|
214
223
|
/** One comment in the namespace, what the grammar made of it, and what it bound. */
|
|
215
224
|
export interface InlineDirective {
|
|
216
225
|
readonly site: Position;
|
|
@@ -242,11 +251,12 @@ export function inlineDirectives<Brand>(
|
|
|
242
251
|
const below = declarationLines(file, held, symbols);
|
|
243
252
|
for (const comment of comments) {
|
|
244
253
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
254
|
+
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|
|
245
255
|
found.push({
|
|
246
256
|
site,
|
|
247
257
|
text: comment.text,
|
|
248
258
|
directive: classifyComment(comment.text),
|
|
249
|
-
bound: [...(below.get(
|
|
259
|
+
bound: [...(below.get(target) ?? [])],
|
|
250
260
|
});
|
|
251
261
|
}
|
|
252
262
|
}
|
|
@@ -254,8 +264,8 @@ export function inlineDirectives<Brand>(
|
|
|
254
264
|
}
|
|
255
265
|
|
|
256
266
|
/**
|
|
257
|
-
* The inline directives of a run's projects, each bound to the declarations
|
|
258
|
-
*
|
|
267
|
+
* The inline directives of a run's projects, each bound to the declarations the placement
|
|
268
|
+
* rule gives it, one directive binding the union of what each project holding its file
|
|
259
269
|
* declares there. A directive that lacks only its reason is one {@link Refusal} per code;
|
|
260
270
|
* any other malformed comment in the namespace ends the read with a
|
|
261
271
|
* {@link SuppressionError} naming the first one written.
|