@cplieger/deadset-ts 7.1.0 → 7.2.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/README.md +1 -1
- package/dist/src/alias-chain.js +6 -2
- package/dist/src/analysis.js +3 -1
- package/dist/src/config.js +4 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/decorator.js +3 -2
- package/dist/src/dependencies.js +0 -4
- package/dist/src/deprecation.js +1 -1
- package/dist/src/findings/completion.js +1 -1
- package/dist/src/findings/interfaces.js +1 -8
- package/dist/src/findings/unused-declarations.js +8 -11
- package/dist/src/html-entries.js +0 -3
- package/dist/src/injection-container.js +28 -25
- package/dist/src/intra-function-parts.js +71 -22
- package/dist/src/inventory.js +19 -6
- package/dist/src/kinds.js +2 -2
- package/dist/src/matrix.js +1 -8
- package/dist/src/module-declarations.js +0 -3
- package/dist/src/ref.js +3 -2
- package/dist/src/references.js +38 -20
- package/dist/src/report.js +1 -1
- package/dist/src/resolve.js +37 -4
- package/dist/src/schema.js +4 -1
- package/dist/src/suppress-file.js +43 -9
- package/dist/src/suppress-inline.js +35 -12
- package/dist/src/suppress.js +3 -2
- package/dist/src/type-errors.js +2 -1
- package/dist/src/verbs/analyze.js +7 -3
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-resolution.js +5 -8
- package/dist/src/workspace.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +7 -2
- package/src/analysis.ts +3 -3
- package/src/config.ts +9 -1
- package/src/conformance-record.ts +2 -2
- package/src/decorator.ts +8 -2
- package/src/dependencies.ts +0 -4
- package/src/deprecation.ts +1 -1
- package/src/findings/completion.ts +1 -4
- package/src/findings/interfaces.ts +1 -9
- package/src/findings/unused-declarations.ts +8 -15
- package/src/html-entries.ts +0 -3
- package/src/injection-container.ts +31 -26
- package/src/intra-function-parts.ts +96 -24
- package/src/inventory.ts +27 -5
- package/src/kinds.ts +2 -2
- package/src/matrix.ts +1 -9
- package/src/module-declarations.ts +0 -3
- package/src/ref.ts +3 -2
- package/src/references.ts +55 -21
- package/src/report.ts +1 -1
- package/src/resolve.ts +56 -6
- package/src/schema.ts +10 -14
- package/src/signatures.ts +6 -1
- package/src/suppress-file.ts +48 -9
- package/src/suppress-inline.ts +57 -15
- package/src/suppress.ts +3 -2
- package/src/type-errors.ts +2 -1
- package/src/verbs/analyze.ts +7 -2
- package/src/version.ts +1 -1
- package/src/workspace-resolution.ts +5 -8
- package/src/workspace.ts +2 -2
package/src/matrix.ts
CHANGED
|
@@ -94,14 +94,6 @@ export interface SweepResult {
|
|
|
94
94
|
readonly components: readonly Component[];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/** Two strings ordered bytewise. */
|
|
98
|
-
function compare(a: string, b: string): number {
|
|
99
|
-
if (a === b) {
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
return a < b ? -1 : 1;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
97
|
/**
|
|
106
98
|
* What makes one reference one reference, whichever configuration made it. A consumer's
|
|
107
99
|
* position is rendered against its own root, so the consumer is part of what it names.
|
|
@@ -163,7 +155,7 @@ export function matrixOf(per: readonly Configured[]): Matrix {
|
|
|
163
155
|
references.push(reference);
|
|
164
156
|
}
|
|
165
157
|
});
|
|
166
|
-
symbols.sort((a, b) => byPosition(a.position, b.position)
|
|
158
|
+
symbols.sort((a, b) => byPosition(a.position, b.position));
|
|
167
159
|
|
|
168
160
|
return {
|
|
169
161
|
configurations: per.map((one) => one.configuration),
|
|
@@ -113,9 +113,6 @@ interface Named {
|
|
|
113
113
|
function namedBy(
|
|
114
114
|
held: Inventory,
|
|
115
115
|
): (specifier: string, names: readonly string[]) => readonly Named[] {
|
|
116
|
-
if (held.modules.size === 0) {
|
|
117
|
-
return () => [];
|
|
118
|
-
}
|
|
119
116
|
const refs = new Map<string, string>();
|
|
120
117
|
const children = new Map<string, InventorySymbol[]>();
|
|
121
118
|
for (const symbol of held.symbols) {
|
package/src/ref.ts
CHANGED
|
@@ -27,10 +27,11 @@ const TS_COMPUTED = String.raw`\[[^\]\r\n\t ]+\]`;
|
|
|
27
27
|
const TS_HEAD = `(?:${TS_IDENT}|${TS_QUOTED})`;
|
|
28
28
|
const TS_MEMBER = `(?:${TS_IDENT}|#${TS_IDENT}|${TS_QUOTED}|${TS_COMPUTED})`;
|
|
29
29
|
const TS_PACKAGE = String.raw`(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+`;
|
|
30
|
-
const
|
|
30
|
+
const TS_DIRS = String.raw`(?:[^/#\r\n]+/)*`;
|
|
31
|
+
const TS_FILE = String.raw`${TS_DIRS}[^/#\r\n]+(?:(?:\.d)?\.[mc]?ts|\.tsx|\.[mc]?js|\.jsx)`;
|
|
31
32
|
|
|
32
33
|
const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
|
|
33
|
-
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}
|
|
34
|
+
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* One expression per form the grammar defines, each anchored and each the whole of
|
package/src/references.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
isVoidExpression,
|
|
33
33
|
SyntaxKind,
|
|
34
34
|
type Node,
|
|
35
|
+
type ParameterDeclaration,
|
|
35
36
|
type SourceFile,
|
|
36
37
|
} from "@typescript/native/ast";
|
|
37
38
|
import type { Symbol as TSSymbol } from "@typescript/native/sync";
|
|
@@ -41,7 +42,13 @@ import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuri
|
|
|
41
42
|
import { specifierCandidates } from "./configuration-files.ts";
|
|
42
43
|
import { namespaceUses } from "./namespace-objects.ts";
|
|
43
44
|
import { globExpression } from "./glob.ts";
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
declarationsByName,
|
|
47
|
+
isParameterProperty,
|
|
48
|
+
nodeKey,
|
|
49
|
+
type Inventory,
|
|
50
|
+
type InventorySymbol,
|
|
51
|
+
} from "./inventory.ts";
|
|
45
52
|
import { augmentationReferences } from "./augmentations.ts";
|
|
46
53
|
import { elementAccessReferences } from "./element-access.ts";
|
|
47
54
|
import { indexedAccessReferences } from "./indexed-access.ts";
|
|
@@ -478,6 +485,13 @@ interface Decorated {
|
|
|
478
485
|
readonly declaration: string;
|
|
479
486
|
}
|
|
480
487
|
|
|
488
|
+
/** One parameter property, which its constructor writes, and the declaration that writes it. */
|
|
489
|
+
interface ParameterWrite {
|
|
490
|
+
readonly name: Node;
|
|
491
|
+
readonly from: string;
|
|
492
|
+
readonly member: string;
|
|
493
|
+
}
|
|
494
|
+
|
|
481
495
|
/** One property a pattern reads, and the declaration the pattern is written inside. */
|
|
482
496
|
interface Read extends PropertyRead {
|
|
483
497
|
readonly from: string;
|
|
@@ -494,6 +508,7 @@ interface FileSites {
|
|
|
494
508
|
readonly wholes: readonly WholeRead[];
|
|
495
509
|
readonly urls: readonly ModuleURL[];
|
|
496
510
|
readonly decorated: readonly Decorated[];
|
|
511
|
+
readonly parameterWrites: readonly ParameterWrite[];
|
|
497
512
|
readonly keyed: readonly KeyedAccess[];
|
|
498
513
|
}
|
|
499
514
|
|
|
@@ -682,6 +697,7 @@ function sitesOf(
|
|
|
682
697
|
const wholes: WholeRead[] = [];
|
|
683
698
|
const urls: ModuleURL[] = [];
|
|
684
699
|
const decorated: Decorated[] = [];
|
|
700
|
+
const parameterWrites: ParameterWrite[] = [];
|
|
685
701
|
const namespaces = namespaceUses(file);
|
|
686
702
|
const literals: LiteralAt[] = [];
|
|
687
703
|
const flows: FlowAt[] = [];
|
|
@@ -766,7 +782,15 @@ function sitesOf(
|
|
|
766
782
|
}
|
|
767
783
|
const held = declarations.get(nodeKey(file, node));
|
|
768
784
|
const outer = enclosing;
|
|
769
|
-
|
|
785
|
+
// A parameter property's annotation, initializer and decorators stay with the
|
|
786
|
+
// parameter when the member goes, so they are written inside the constructor's class.
|
|
787
|
+
if (held !== undefined && isParameterProperty(node)) {
|
|
788
|
+
parameterWrites.push({
|
|
789
|
+
name: (node as ParameterDeclaration).name,
|
|
790
|
+
from: outer,
|
|
791
|
+
member: held,
|
|
792
|
+
});
|
|
793
|
+
} else if (held !== undefined) {
|
|
770
794
|
enclosing = held;
|
|
771
795
|
for (const modifier of (node as { readonly modifiers?: readonly Node[] }).modifiers ?? []) {
|
|
772
796
|
if (isDecorator(modifier)) {
|
|
@@ -789,6 +813,7 @@ function sitesOf(
|
|
|
789
813
|
wholes,
|
|
790
814
|
urls,
|
|
791
815
|
decorated,
|
|
816
|
+
parameterWrites,
|
|
792
817
|
keyed,
|
|
793
818
|
};
|
|
794
819
|
}
|
|
@@ -884,15 +909,12 @@ function importedNames(file: SourceFile): ReadonlyMap<string, readonly (string |
|
|
|
884
909
|
return names;
|
|
885
910
|
}
|
|
886
911
|
|
|
887
|
-
/**
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
*/
|
|
891
|
-
function importBindings(file: SourceFile): readonly { node: Node; whole: boolean }[] {
|
|
892
|
-
const found: { node: Node; whole: boolean }[] = [];
|
|
912
|
+
/** The name node of every import binding one file's top level declares. */
|
|
913
|
+
function importBindings(file: SourceFile): readonly { node: Node }[] {
|
|
914
|
+
const found: { node: Node }[] = [];
|
|
893
915
|
for (const statement of file.statements) {
|
|
894
916
|
if (isImportEqualsDeclaration(statement)) {
|
|
895
|
-
found.push({ node: statement.name
|
|
917
|
+
found.push({ node: statement.name });
|
|
896
918
|
continue;
|
|
897
919
|
}
|
|
898
920
|
const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
|
|
@@ -900,16 +922,16 @@ function importBindings(file: SourceFile): readonly { node: Node; whole: boolean
|
|
|
900
922
|
continue;
|
|
901
923
|
}
|
|
902
924
|
if (clause.name !== undefined) {
|
|
903
|
-
found.push({ node: clause.name
|
|
925
|
+
found.push({ node: clause.name });
|
|
904
926
|
}
|
|
905
927
|
const bindings = clause.namedBindings;
|
|
906
928
|
if (bindings === undefined) {
|
|
907
929
|
continue;
|
|
908
930
|
}
|
|
909
931
|
if (bindings.kind === SyntaxKind.NamespaceImport) {
|
|
910
|
-
found.push({ node: bindings.name
|
|
932
|
+
found.push({ node: bindings.name });
|
|
911
933
|
} else {
|
|
912
|
-
found.push(...bindings.elements.map((element) => ({ node: element.name
|
|
934
|
+
found.push(...bindings.elements.map((element) => ({ node: element.name })));
|
|
913
935
|
}
|
|
914
936
|
}
|
|
915
937
|
return found;
|
|
@@ -1286,6 +1308,7 @@ export function references<Brand>(
|
|
|
1286
1308
|
wholes,
|
|
1287
1309
|
urls,
|
|
1288
1310
|
decorated,
|
|
1311
|
+
parameterWrites,
|
|
1289
1312
|
keyed,
|
|
1290
1313
|
} of walked) {
|
|
1291
1314
|
const test = testFile.get(file) === true;
|
|
@@ -1307,6 +1330,18 @@ export function references<Brand>(
|
|
|
1307
1330
|
});
|
|
1308
1331
|
}
|
|
1309
1332
|
|
|
1333
|
+
// The constructor stores its argument in the member a parameter property declares.
|
|
1334
|
+
for (const { name, from, member } of parameterWrites) {
|
|
1335
|
+
found.push({
|
|
1336
|
+
from,
|
|
1337
|
+
to: member,
|
|
1338
|
+
position: renderPosition(file, root, name.getStart()),
|
|
1339
|
+
use: "write",
|
|
1340
|
+
resolution: "syntax",
|
|
1341
|
+
test,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1310
1345
|
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
1311
1346
|
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
1312
1347
|
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
@@ -1456,15 +1491,14 @@ export function references<Brand>(
|
|
|
1456
1491
|
symbol === UNANSWERED
|
|
1457
1492
|
? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
1458
1493
|
: chains.chainOf(symbol);
|
|
1459
|
-
// The markup may name any export of a module a binding
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
: chained;
|
|
1494
|
+
// The markup may name any export of a module a binding's chain reaches, whether a
|
|
1495
|
+
// namespace import binds it or a named or default import of a namespace re-export.
|
|
1496
|
+
const targets = chained.flatMap((target) => [
|
|
1497
|
+
target,
|
|
1498
|
+
...declaredIn(target.id)
|
|
1499
|
+
.filter((one) => one.exported)
|
|
1500
|
+
.map((one) => ({ ...target, id: one.id })),
|
|
1501
|
+
]);
|
|
1468
1502
|
for (const target of targets) {
|
|
1469
1503
|
found.push({
|
|
1470
1504
|
from: fileId,
|
package/src/report.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { StaleSuppression } from "./findings/self-check.ts";
|
|
|
19
19
|
import type { TestFileRule } from "./references.ts";
|
|
20
20
|
|
|
21
21
|
/** The version of the report schema this analyzer writes a report to. */
|
|
22
|
-
const SCHEMA_VERSION = "8.
|
|
22
|
+
const SCHEMA_VERSION = "8.2.0";
|
|
23
23
|
|
|
24
24
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
25
25
|
export const SCHEMA_VERSIONS_ACCEPTED: readonly string[] = [SCHEMA_VERSION];
|
package/src/resolve.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type Sort,
|
|
20
20
|
type Source,
|
|
21
21
|
type TargetKind,
|
|
22
|
+
type TargetKindSetting,
|
|
22
23
|
type TemplateDelimiters,
|
|
23
24
|
} from "./config.ts";
|
|
24
25
|
import { CONVENTION_ROWS } from "./convention-rows.ts";
|
|
@@ -57,9 +58,9 @@ const PROJECT_MEMBER = "project";
|
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* One segment of a project path: not empty, not `.` or `..`, and holding no
|
|
60
|
-
* solidus
|
|
61
|
+
* solidus or control character.
|
|
61
62
|
*/
|
|
62
|
-
const PROJECT_SEGMENT = String.raw`(?:[
|
|
63
|
+
const PROJECT_SEGMENT = String.raw`(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)`;
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* A project path: segments joined by a solidus, so the path is relative, below
|
|
@@ -85,7 +86,7 @@ const CASCADES: readonly Cascade[] = ["roots", "full"];
|
|
|
85
86
|
*/
|
|
86
87
|
interface Doc {
|
|
87
88
|
readonly contractVersion: string | undefined;
|
|
88
|
-
readonly targetKind:
|
|
89
|
+
readonly targetKind: TargetKindSetting | undefined;
|
|
89
90
|
readonly languages: readonly Language[] | undefined;
|
|
90
91
|
readonly minConfidence: Confidence | undefined;
|
|
91
92
|
readonly generatedFiles: GeneratedFiles | undefined;
|
|
@@ -281,6 +282,55 @@ function readStrings<T extends string>(
|
|
|
281
282
|
return value as readonly T[];
|
|
282
283
|
}
|
|
283
284
|
|
|
285
|
+
/**
|
|
286
|
+
* The target kind, a string or an object naming every language. The walk has
|
|
287
|
+
* already refused an undeclared member, so an object is refused for the first
|
|
288
|
+
* language it leaves out, then for the first language whose value is refused,
|
|
289
|
+
* each in the vocabulary's language order.
|
|
290
|
+
*/
|
|
291
|
+
function readTargetKind(
|
|
292
|
+
target: Record<string, unknown> | undefined,
|
|
293
|
+
label: string,
|
|
294
|
+
): TargetKindSetting | undefined {
|
|
295
|
+
const value = member(target, "kind");
|
|
296
|
+
const perLanguage = `an object naming the kind of each of ${spell(LANGUAGES)}`;
|
|
297
|
+
if (value === undefined) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
if (typeof value === "string") {
|
|
301
|
+
if (!(TARGET_KINDS as readonly string[]).includes(value)) {
|
|
302
|
+
throw malformed(
|
|
303
|
+
label,
|
|
304
|
+
"target.kind",
|
|
305
|
+
`${JSON.stringify(value)} is not one of ${spell(TARGET_KINDS)}, nor ${perLanguage}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return value as TargetKind;
|
|
309
|
+
}
|
|
310
|
+
if (!isRecord(value)) {
|
|
311
|
+
throw malformed(
|
|
312
|
+
label,
|
|
313
|
+
"target.kind",
|
|
314
|
+
`${JSON.stringify(value)} is neither one of ${spell(TARGET_KINDS)} nor ${perLanguage}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const missing = LANGUAGES.find((language) => !Object.hasOwn(value, language));
|
|
318
|
+
if (missing !== undefined) {
|
|
319
|
+
throw malformed(label, `target.kind.${missing}`, `is not set, and ${perLanguage} names each`);
|
|
320
|
+
}
|
|
321
|
+
for (const language of LANGUAGES) {
|
|
322
|
+
const kind = value[language];
|
|
323
|
+
if (typeof kind !== "string" || !(TARGET_KINDS as readonly string[]).includes(kind)) {
|
|
324
|
+
throw malformed(
|
|
325
|
+
label,
|
|
326
|
+
`target.kind.${language}`,
|
|
327
|
+
`${JSON.stringify(kind)} is not one of ${spell(TARGET_KINDS)}`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { go: value["go"] as TargetKind, ts: value["ts"] as TargetKind };
|
|
332
|
+
}
|
|
333
|
+
|
|
284
334
|
/** A component file's extension: a full stop followed by letters and digits. */
|
|
285
335
|
const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
|
|
286
336
|
|
|
@@ -355,7 +405,7 @@ function readProject(
|
|
|
355
405
|
label,
|
|
356
406
|
path,
|
|
357
407
|
`${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
|
|
358
|
-
"none empty, . or .., and no
|
|
408
|
+
"none empty, . or .., and no control character",
|
|
359
409
|
);
|
|
360
410
|
}
|
|
361
411
|
return { shape: "project", id, project };
|
|
@@ -795,7 +845,7 @@ function readDoc(value: unknown, label: string): Doc {
|
|
|
795
845
|
|
|
796
846
|
return {
|
|
797
847
|
contractVersion,
|
|
798
|
-
targetKind:
|
|
848
|
+
targetKind: readTargetKind(target, label),
|
|
799
849
|
languages: readStrings(analysis, "languages", "analysis.languages", label, 0, LANGUAGES),
|
|
800
850
|
minConfidence: readEnum(
|
|
801
851
|
analysis,
|
|
@@ -1052,7 +1102,7 @@ function settleAll(inputs: Inputs): { config: Config; provenance: Map<string, Or
|
|
|
1052
1102
|
|
|
1053
1103
|
const config: Config = {
|
|
1054
1104
|
contractVersion: at("contract_version", defaults.contractVersion, (doc) => doc.contractVersion),
|
|
1055
|
-
targetKind: at<
|
|
1105
|
+
targetKind: at<TargetKindSetting | "">("target.kind", "", (doc) => doc.targetKind),
|
|
1056
1106
|
analysis: {
|
|
1057
1107
|
languages: at("analysis.languages", defaults.analysis.languages, (doc) => doc.languages),
|
|
1058
1108
|
minConfidence: at(
|
package/src/schema.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { ConfigError } from "./config.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* How one node of the closed key list is written
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* scalars: the severity and provenance objects.
|
|
11
|
-
* - `list` is an array whose entries are objects with declared members. Where
|
|
12
|
-
* the key list admits an entry in more than one shape, the members are every
|
|
13
|
-
* shape's together, and which of them one entry may hold is the decoder's
|
|
14
|
-
* refusal, not the walk's.
|
|
15
|
-
* - `object` is one setting written as an object: its member names are declared
|
|
16
|
-
* and a source supplies the object whole.
|
|
4
|
+
* How one node of the closed key list is written: `leaf`, a scalar or an array of
|
|
5
|
+
* scalars; `section`, an object of declared members and no value of its own; `map`, an
|
|
6
|
+
* object of open member names and scalar values (severity, provenance); `list`, an
|
|
7
|
+
* array of objects holding every admitted shape's members; `object`, one setting
|
|
8
|
+
* written as an object of declared members and supplied whole. Which shape an entry or
|
|
9
|
+
* a value takes, where the key list admits several, is the decoder's refusal.
|
|
17
10
|
*/
|
|
18
11
|
type KeyKind = "leaf" | "section" | "map" | "list" | "object";
|
|
19
12
|
|
|
@@ -46,7 +39,10 @@ export const SCHEMA_ROOT: KeyNode = {
|
|
|
46
39
|
kind: "section",
|
|
47
40
|
members: {
|
|
48
41
|
contract_version: leaf,
|
|
49
|
-
target: {
|
|
42
|
+
target: {
|
|
43
|
+
kind: "section",
|
|
44
|
+
members: { kind: { kind: "object", members: { go: leaf, ts: leaf } } },
|
|
45
|
+
},
|
|
50
46
|
analysis: {
|
|
51
47
|
kind: "section",
|
|
52
48
|
members: {
|
package/src/signatures.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isPropertyDeclaration,
|
|
14
14
|
isVariableDeclaration,
|
|
15
15
|
type ArrowFunction,
|
|
16
|
+
type ConstructorDeclaration,
|
|
16
17
|
type FunctionDeclaration,
|
|
17
18
|
type FunctionExpression,
|
|
18
19
|
type MethodDeclaration,
|
|
@@ -21,7 +22,11 @@ import {
|
|
|
21
22
|
|
|
22
23
|
/** A function-like node that carries a body of its own. */
|
|
23
24
|
export type SignatureNode =
|
|
24
|
-
|
|
25
|
+
| FunctionDeclaration
|
|
26
|
+
| MethodDeclaration
|
|
27
|
+
| ArrowFunction
|
|
28
|
+
| FunctionExpression
|
|
29
|
+
| ConstructorDeclaration;
|
|
25
30
|
|
|
26
31
|
/** Whether one node is a function value written in place: an arrow or a function expression. */
|
|
27
32
|
function isFunctionValue(node: Node | undefined): node is ArrowFunction | FunctionExpression {
|
package/src/suppress-file.ts
CHANGED
|
@@ -303,11 +303,12 @@ interface KeptRow {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
|
-
* The rows of
|
|
307
|
-
* document order and as written, which a write keeps. A document that is
|
|
308
|
-
* or a row in a shape no record takes, is refused rather
|
|
306
|
+
* The rows of a scheme other than `own` that the baseline document `text` holds, in
|
|
307
|
+
* document order and as written, which a write of `own`'s rows keeps. A document that is
|
|
308
|
+
* not a baseline, or a row in a shape no record takes, of any scheme, is refused rather
|
|
309
|
+
* than dropped.
|
|
309
310
|
*/
|
|
310
|
-
export function otherSchemeRows(text: string): readonly KeptRow[] {
|
|
311
|
+
export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonly KeptRow[] {
|
|
311
312
|
const refuse = (detail: string): never => {
|
|
312
313
|
throw new Error(
|
|
313
314
|
`${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`,
|
|
@@ -331,7 +332,7 @@ export function otherSchemeRows(text: string): readonly KeptRow[] {
|
|
|
331
332
|
}
|
|
332
333
|
// readRecord has checked every member is a string and the code and symbol are present.
|
|
333
334
|
const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
|
|
334
|
-
if (code === undefined || symbol === undefined || symbol.startsWith(
|
|
335
|
+
if (code === undefined || symbol === undefined || symbol.startsWith(own)) {
|
|
335
336
|
return [];
|
|
336
337
|
}
|
|
337
338
|
return [
|
|
@@ -345,10 +346,37 @@ export function otherSchemeRows(text: string): readonly KeptRow[] {
|
|
|
345
346
|
});
|
|
346
347
|
}
|
|
347
348
|
|
|
349
|
+
/** The reference schemes in the order a baseline holds their rows. */
|
|
350
|
+
const SCHEME_ORDER: readonly string[] = ["go://", "ts://"];
|
|
351
|
+
|
|
352
|
+
function schemeRank(row: { readonly symbol: string }): number {
|
|
353
|
+
return SCHEME_ORDER.findIndex((scheme) => row.symbol.startsWith(scheme));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** A surrogate half no other completes, which Go's encoding/json decodes as U+FFFD. */
|
|
357
|
+
const LONE_SURROGATE = /[\ud800-\udfff]/gu;
|
|
358
|
+
|
|
359
|
+
/** One string with every lone surrogate written as U+FFFD. */
|
|
360
|
+
function wellFormed(text: string): string {
|
|
361
|
+
return text.replace(LONE_SURROGATE, "\ufffd");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The pieces of a JSON document with U+2028 and U+2029 escaped, as Go's encoding/json
|
|
366
|
+
* writes them. Neither is JSON syntax, so each stands inside a string.
|
|
367
|
+
*/
|
|
368
|
+
function* separatorsEscaped(pieces: Iterable<string>): Generator<string, void, undefined> {
|
|
369
|
+
for (const piece of pieces) {
|
|
370
|
+
yield piece.replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
348
374
|
/**
|
|
349
|
-
* A baseline holding one row per finding, in the order given, which is the report's,
|
|
350
|
-
* the other language's rows `kept`,
|
|
351
|
-
*
|
|
375
|
+
* A baseline holding one row per finding, in the order given, which is the report's, and
|
|
376
|
+
* the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
|
|
377
|
+
* the order given, so two analyzers writing one document in either order write the same
|
|
378
|
+
* bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
|
|
379
|
+
* provenance as its reason. A finding
|
|
352
380
|
* missing a value a row needs, and an identity missing its name or version, are refused
|
|
353
381
|
* rather than written, because the grammar requires a reason on every row.
|
|
354
382
|
*/
|
|
@@ -371,5 +399,16 @@ export function writeBaseline(
|
|
|
371
399
|
}
|
|
372
400
|
return { code: found.code, symbol: found.symbol, path: found.path, reason };
|
|
373
401
|
});
|
|
374
|
-
|
|
402
|
+
const rows = [...kept, ...baseline]
|
|
403
|
+
.sort((a, b) => schemeRank(a) - schemeRank(b))
|
|
404
|
+
.map((row: KeptRow): KeptRow => {
|
|
405
|
+
const { code, symbol, path, reason } = row;
|
|
406
|
+
return {
|
|
407
|
+
code: wellFormed(code),
|
|
408
|
+
symbol: wellFormed(symbol),
|
|
409
|
+
...(path === undefined ? {} : { path: wellFormed(path) }),
|
|
410
|
+
...(reason === undefined ? {} : { reason: wellFormed(reason) }),
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
return separatorsEscaped(jsonChunks({ description: BASELINE_DESCRIPTION, baseline: rows }, 2));
|
|
375
414
|
}
|
package/src/suppress-inline.ts
CHANGED
|
@@ -4,12 +4,18 @@
|
|
|
4
4
|
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
isConstructorDeclaration,
|
|
9
|
+
SyntaxKind,
|
|
10
|
+
type Node,
|
|
11
|
+
type SourceFile,
|
|
12
|
+
} from "@typescript/native/ast";
|
|
13
|
+
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
9
14
|
import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
|
|
10
15
|
import type { ProjectView } from "./session.ts";
|
|
11
16
|
import { signatureOf } from "./signatures.ts";
|
|
12
17
|
import {
|
|
18
|
+
isPartKind,
|
|
13
19
|
SUPPRESSION_WITHOUT_REASON,
|
|
14
20
|
SuppressionError,
|
|
15
21
|
siteText,
|
|
@@ -166,35 +172,64 @@ interface Declared {
|
|
|
166
172
|
readonly position: Position;
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
/**
|
|
176
|
+
* What one placement binds: the declaration a record for a code that reports a
|
|
177
|
+
* declaration binds, and the one a record for a part kind binds, which holds the part.
|
|
178
|
+
* A parameter property's field is the first and its class the second; a constructor's
|
|
179
|
+
* plain parameter binds only the second, because a constructor is no declaration.
|
|
180
|
+
*/
|
|
181
|
+
interface Binding {
|
|
182
|
+
readonly declared: Declared | undefined;
|
|
183
|
+
readonly part: Declared;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function bindingKey(one: Binding): string {
|
|
187
|
+
return `${one.declared?.id ?? ""}\u0000${one.part.id}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
169
190
|
/**
|
|
170
191
|
* The declarations of one file by the line each begins on, the line of its first token: a
|
|
171
192
|
* decorator, a modifier or the keyword. A variable's first token is its statement's
|
|
172
193
|
* keyword, and a variable written on a later line of the statement also begins there. A
|
|
173
|
-
* function also begins, for a directive, on the line of each of its parameters
|
|
194
|
+
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
195
|
+
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
174
196
|
*/
|
|
175
197
|
function declarationLines(
|
|
176
198
|
file: SourceFile,
|
|
177
199
|
held: Inventory,
|
|
178
200
|
symbols: ReadonlyMap<string, Declared>,
|
|
179
|
-
): ReadonlyMap<number, readonly
|
|
180
|
-
const below = new Map<number,
|
|
181
|
-
const add = (offset: number, one:
|
|
201
|
+
): ReadonlyMap<number, readonly Binding[]> {
|
|
202
|
+
const below = new Map<number, Binding[]>();
|
|
203
|
+
const add = (offset: number, one: Binding): void => {
|
|
182
204
|
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
183
205
|
const at = below.get(line) ?? [];
|
|
184
|
-
if (!at.some((known) => known
|
|
206
|
+
if (!at.some((known) => bindingKey(known) === bindingKey(one))) {
|
|
185
207
|
at.push(one);
|
|
186
208
|
}
|
|
187
209
|
below.set(line, at);
|
|
188
210
|
};
|
|
189
|
-
const
|
|
211
|
+
const declaredAt = (node: Node): Declared | undefined => {
|
|
190
212
|
const id = held.declarations.get(nodeKey(file, node));
|
|
191
|
-
|
|
213
|
+
return id === undefined ? undefined : symbols.get(id);
|
|
214
|
+
};
|
|
215
|
+
const visit = (node: Node): void => {
|
|
216
|
+
const one = declaredAt(node);
|
|
217
|
+
const owner = isConstructorDeclaration(node) ? declaredAt(node.parent) : undefined;
|
|
218
|
+
if (owner !== undefined && isConstructorDeclaration(node)) {
|
|
219
|
+
for (const at of [node, ...node.parameters.filter((one) => !isParameterProperty(one))]) {
|
|
220
|
+
add(at.pos, { declared: undefined, part: owner });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const holder =
|
|
224
|
+
one !== undefined && isParameterProperty(node) ? declaredAt(node.parent.parent) : one;
|
|
225
|
+
if (one !== undefined && holder !== undefined) {
|
|
226
|
+
add(node.pos, { declared: one, part: holder });
|
|
227
|
+
}
|
|
192
228
|
if (one !== undefined) {
|
|
193
|
-
add(node.pos, one);
|
|
194
229
|
// A parameter on a later line of a wrapped signature takes its directive on the
|
|
195
230
|
// line above it, inside the parameter list, and the directive binds the function.
|
|
196
231
|
for (const parameter of signatureOf(node)?.parameters ?? []) {
|
|
197
|
-
add(parameter.pos, one);
|
|
232
|
+
add(parameter.pos, { declared: one, part: one });
|
|
198
233
|
}
|
|
199
234
|
const list = node.parent;
|
|
200
235
|
if (
|
|
@@ -202,7 +237,7 @@ function declarationLines(
|
|
|
202
237
|
list.kind === SyntaxKind.VariableDeclarationList &&
|
|
203
238
|
list.parent.kind === SyntaxKind.VariableStatement
|
|
204
239
|
) {
|
|
205
|
-
add(list.parent.pos, one);
|
|
240
|
+
add(list.parent.pos, { declared: one, part: one });
|
|
206
241
|
}
|
|
207
242
|
}
|
|
208
243
|
node.forEachChild(visit);
|
|
@@ -225,7 +260,7 @@ export interface InlineDirective {
|
|
|
225
260
|
readonly site: Position;
|
|
226
261
|
readonly text: string;
|
|
227
262
|
readonly directive: Directive;
|
|
228
|
-
readonly bound:
|
|
263
|
+
readonly bound: Binding[];
|
|
229
264
|
}
|
|
230
265
|
|
|
231
266
|
/**
|
|
@@ -282,7 +317,7 @@ export function inlineSuppressions(
|
|
|
282
317
|
continue;
|
|
283
318
|
}
|
|
284
319
|
for (const one of written.bound) {
|
|
285
|
-
if (!known.bound.some((other) => other
|
|
320
|
+
if (!known.bound.some((other) => bindingKey(other) === bindingKey(one))) {
|
|
286
321
|
known.bound.push(one);
|
|
287
322
|
}
|
|
288
323
|
}
|
|
@@ -311,8 +346,15 @@ export function inlineSuppressions(
|
|
|
311
346
|
}
|
|
312
347
|
break;
|
|
313
348
|
case "directive": {
|
|
314
|
-
const declared = [...bound].sort((a, b) => byPosition(a.position, b.position));
|
|
315
349
|
for (const code of directive.codes) {
|
|
350
|
+
const targets = new Map<string, Declared>();
|
|
351
|
+
for (const one of bound) {
|
|
352
|
+
const target = isPartKind(code) ? one.part : one.declared;
|
|
353
|
+
if (target !== undefined) {
|
|
354
|
+
targets.set(target.id, target);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const declared = [...targets.values()].sort((a, b) => byPosition(a.position, b.position));
|
|
316
358
|
const base = {
|
|
317
359
|
code,
|
|
318
360
|
path: site.path,
|
package/src/suppress.ts
CHANGED
|
@@ -88,9 +88,10 @@ export const CODE_FORM = /^DS[0-9]{4}$/u;
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* A path inside the target: relative to the target root, the solidus as separator, no
|
|
91
|
-
* leading `./`, no trailing solidus, no `.` or `..` segment, never absolute
|
|
91
|
+
* leading `./`, no trailing solidus, no `.` or `..` segment, never absolute, and no
|
|
92
|
+
* control character; a backslash is a character of a name.
|
|
92
93
|
*/
|
|
93
|
-
export const PATH_EXPRESSION = String.raw`^(?:[
|
|
94
|
+
export const PATH_EXPRESSION = String.raw`^(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)(?:/(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+))*$`;
|
|
94
95
|
|
|
95
96
|
const PATH_FORM = new RegExp(PATH_EXPRESSION, "u");
|
|
96
97
|
|
package/src/type-errors.ts
CHANGED
|
@@ -151,7 +151,8 @@ function declaringManifest(
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
// The target root is not below itself, so the walk stops after reading its manifest.
|
|
155
|
+
if (relativePath(targetRoot, at) === undefined) {
|
|
155
156
|
return undefined;
|
|
156
157
|
}
|
|
157
158
|
}
|