@cplieger/deadset-ts 3.12.1 → 4.0.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.
Files changed (120) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +217 -48
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/components.js +4 -2
  8. package/dist/src/configured-declarations.js +56 -7
  9. package/dist/src/conformance-record.js +2 -2
  10. package/dist/src/dependencies.js +34 -17
  11. package/dist/src/deprecation.js +35 -24
  12. package/dist/src/destructuring.js +37 -29
  13. package/dist/src/discover.js +105 -28
  14. package/dist/src/edges.js +4 -1
  15. package/dist/src/entry-points.js +7 -0
  16. package/dist/src/enum-group.js +46 -40
  17. package/dist/src/exempt.js +42 -1
  18. package/dist/src/findings/completion.js +37 -8
  19. package/dist/src/findings/emitters.js +20 -6
  20. package/dist/src/findings/interfaces.js +7 -1
  21. package/dist/src/findings/intra-function.js +91 -2
  22. package/dist/src/findings/self-check.js +30 -7
  23. package/dist/src/findings/unused-declarations.js +15 -3
  24. package/dist/src/findings-pass.js +18 -11
  25. package/dist/src/framework-lifecycle.js +8 -6
  26. package/dist/src/implementations.js +45 -23
  27. package/dist/src/interface-satisfaction.js +30 -27
  28. package/dist/src/intra-function-parts.js +660 -0
  29. package/dist/src/inventory.js +85 -4
  30. package/dist/src/json-chunks.js +76 -0
  31. package/dist/src/kinds.js +337 -35
  32. package/dist/src/partial.js +23 -0
  33. package/dist/src/query.js +207 -0
  34. package/dist/src/references.js +92 -20
  35. package/dist/src/reflective-lookup.js +46 -4
  36. package/dist/src/report.js +14 -3
  37. package/dist/src/reporters/annotations.js +9 -5
  38. package/dist/src/reporters/json.js +3 -2
  39. package/dist/src/reporters/sarif.js +22 -16
  40. package/dist/src/reporters/template.js +112 -12
  41. package/dist/src/reporters/text.js +8 -6
  42. package/dist/src/roots.js +27 -11
  43. package/dist/src/serialization-contract.js +53 -32
  44. package/dist/src/session.js +79 -39
  45. package/dist/src/signatures.js +32 -0
  46. package/dist/src/suppress-file.js +29 -7
  47. package/dist/src/suppress-inline.js +8 -1
  48. package/dist/src/suppress.js +27 -0
  49. package/dist/src/sweep.js +4 -1
  50. package/dist/src/value-reach.js +44 -25
  51. package/dist/src/verbs/analyze.js +39 -14
  52. package/dist/src/verbs/describe.js +12 -8
  53. package/dist/src/verbs/explain.js +22 -9
  54. package/dist/src/verbs/print-projects.js +40 -7
  55. package/dist/src/verbs/print-retained.js +6 -2
  56. package/dist/src/verbs/print-roots.js +5 -1
  57. package/dist/src/version.js +1 -1
  58. package/jsr.json +1 -1
  59. package/package.json +3 -3
  60. package/src/alias-chain.ts +75 -11
  61. package/src/analysis.ts +385 -104
  62. package/src/calls.ts +10 -9
  63. package/src/class-members.ts +10 -11
  64. package/src/components.ts +4 -2
  65. package/src/configured-declarations.ts +80 -8
  66. package/src/conformance-record.ts +2 -2
  67. package/src/dependencies.ts +42 -20
  68. package/src/deprecation.ts +53 -27
  69. package/src/destructuring.ts +50 -39
  70. package/src/discover.ts +177 -33
  71. package/src/edges.ts +4 -1
  72. package/src/entry-points.ts +7 -0
  73. package/src/enum-group.ts +56 -42
  74. package/src/exempt.ts +50 -2
  75. package/src/finding.ts +5 -1
  76. package/src/findings/completion.ts +46 -10
  77. package/src/findings/emitter.ts +3 -0
  78. package/src/findings/emitters.ts +27 -6
  79. package/src/findings/interfaces.ts +7 -1
  80. package/src/findings/intra-function.ts +106 -3
  81. package/src/findings/self-check.ts +48 -7
  82. package/src/findings/unused-declarations.ts +18 -4
  83. package/src/findings-pass.ts +26 -11
  84. package/src/framework-lifecycle.ts +10 -6
  85. package/src/host.ts +5 -3
  86. package/src/implementations.ts +59 -23
  87. package/src/interface-satisfaction.ts +30 -27
  88. package/src/intra-function-parts.ts +889 -0
  89. package/src/inventory.ts +97 -4
  90. package/src/json-chunks.ts +87 -0
  91. package/src/kinds.ts +355 -41
  92. package/src/partial.ts +40 -0
  93. package/src/query.ts +522 -0
  94. package/src/references.ts +119 -38
  95. package/src/reflective-lookup.ts +56 -6
  96. package/src/report.ts +32 -4
  97. package/src/reporters/annotations.ts +23 -21
  98. package/src/reporters/json.ts +3 -2
  99. package/src/reporters/reporter.ts +2 -2
  100. package/src/reporters/sarif.ts +27 -18
  101. package/src/reporters/template.ts +119 -14
  102. package/src/reporters/text.ts +12 -12
  103. package/src/roots.ts +34 -12
  104. package/src/serialization-contract.ts +59 -40
  105. package/src/session.ts +146 -58
  106. package/src/signatures.ts +55 -0
  107. package/src/suppress-file.ts +40 -8
  108. package/src/suppress-inline.ts +8 -1
  109. package/src/suppress.ts +31 -0
  110. package/src/sweep.ts +9 -4
  111. package/src/value-reach.ts +66 -28
  112. package/src/verbs/analyze.ts +51 -14
  113. package/src/verbs/describe.ts +13 -9
  114. package/src/verbs/explain.ts +20 -9
  115. package/src/verbs/print-projects.ts +53 -7
  116. package/src/verbs/print-retained.ts +6 -2
  117. package/src/verbs/print-roots.ts +5 -1
  118. package/src/version.ts +1 -1
  119. package/dist/src/reporters/rules.js +0 -178
  120. package/src/reporters/rules.ts +0 -195
@@ -2,6 +2,7 @@ import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMe
2
2
  import { dirnamePath, joinPath, relativePath } from "./paths.js";
3
3
  import { byPosition, positionKey, renderPosition } from "./position.js";
4
4
  import { computedComponent, nameComponent, renderRef } from "./ref.js";
5
+ import { isAnswered, UNANSWERED } from "./query.js";
5
6
  const MANIFEST = "package.json";
6
7
  /**
7
8
  * Resolves each file's package scope: the name of the nearest manifest at or above
@@ -493,12 +494,28 @@ export function inventory(project, host, targetRoot) {
493
494
  const handles = batched.map((node) => project.handle(node));
494
495
  const resolved = project.symbolsAt(handles);
495
496
  const symbolOf = new Map();
497
+ // A scope or a container whose symbol or table went unanswered keeps what its syntax
498
+ // declares, and everything under it is recorded unanswered.
499
+ const unansweredOwners = new Set();
496
500
  batched.forEach((node, index) => {
497
501
  const symbol = resolved[index];
498
- if (symbol !== undefined) {
502
+ if (typeof symbol === "object") {
499
503
  symbolOf.set(node, symbol);
500
504
  }
501
505
  });
506
+ resolved.slice(0, exportScopes.length).forEach((symbol, index) => {
507
+ const scope = exportScopes[index];
508
+ if (symbol === UNANSWERED && scope !== undefined) {
509
+ unansweredOwners.add(scope.owner);
510
+ }
511
+ });
512
+ containers
513
+ .filter((container) => container.nameNode !== undefined)
514
+ .forEach((container, index) => {
515
+ if (resolved[exportScopes.length + index] === UNANSWERED) {
516
+ unansweredOwners.add(container.owner);
517
+ }
518
+ });
502
519
  let exportTables = 0;
503
520
  for (const scope of exportScopes) {
504
521
  const symbol = symbolOf.get(scope.nameNode);
@@ -506,7 +523,11 @@ export function inventory(project, host, targetRoot) {
506
523
  continue;
507
524
  }
508
525
  exportTables += 1;
509
- for (const [, exported] of symbol.getExports()) {
526
+ const table = project.queries.exportsOf(symbol);
527
+ if (!isAnswered(table)) {
528
+ unansweredOwners.add(scope.owner);
529
+ }
530
+ for (const [, exported] of isAnswered(table) ? table : []) {
510
531
  for (const declaration of exported.declarations) {
511
532
  const held = project.declarationAt(declaration);
512
533
  if (held === undefined) {
@@ -527,11 +548,23 @@ export function inventory(project, host, targetRoot) {
527
548
  if (symbol !== undefined) {
528
549
  if (!container.membersAreExports) {
529
550
  memberTables += 1;
530
- outsideOwnFiles += readTable(container, symbol.getMembers(), seen, false);
551
+ const table = project.queries.membersOf(symbol);
552
+ if (isAnswered(table)) {
553
+ outsideOwnFiles += readTable(container, table, seen, false);
554
+ }
555
+ else {
556
+ unansweredOwners.add(container.owner);
557
+ }
531
558
  }
532
559
  if (container.membersAreExports || container.isClass) {
533
560
  memberTables += 1;
534
- outsideOwnFiles += readTable(container, symbol.getExports(), seen, container.isClass);
561
+ const table = project.queries.exportsOf(symbol);
562
+ if (isAnswered(table)) {
563
+ outsideOwnFiles += readTable(container, table, seen, container.isClass);
564
+ }
565
+ else {
566
+ unansweredOwners.add(container.owner);
567
+ }
535
568
  }
536
569
  }
537
570
  completeFromTree(container, seen);
@@ -635,6 +668,7 @@ export function inventory(project, host, targetRoot) {
635
668
  }
636
669
  return undefined;
637
670
  }
671
+ const byId = new Map(building.map((record) => [positionKey(record.position), record]));
638
672
  const symbols = building
639
673
  .map(render)
640
674
  .sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
@@ -645,8 +679,47 @@ export function inventory(project, host, targetRoot) {
645
679
  cost: { batches: 1, exportTables, memberTables },
646
680
  starReExports,
647
681
  outsideOwnFiles,
682
+ unanswered: symbols
683
+ .filter((symbol) => underUnanswered(byId.get(symbol.id), unansweredOwners))
684
+ .map((symbol) => symbol.id),
685
+ byName: namesOf(building),
648
686
  };
649
687
  }
688
+ /** Whether one declaration is, or is declared under, a scope or container left unanswered. */
689
+ function underUnanswered(record, owners) {
690
+ for (let at = record; at !== undefined; at = at.parent) {
691
+ if (owners.has(at)) {
692
+ return true;
693
+ }
694
+ }
695
+ return false;
696
+ }
697
+ /** Every declaration but a file, by each name a use may spell it with. */
698
+ function namesOf(building) {
699
+ const byName = new Map();
700
+ for (const record of building) {
701
+ if (record.kind === "file") {
702
+ continue;
703
+ }
704
+ const id = positionKey(record.position);
705
+ const names = record.typeParameter === undefined
706
+ ? [record.component.text, record.localName]
707
+ : [record.typeParameter];
708
+ for (const name of new Set(names)) {
709
+ if (name === undefined) {
710
+ continue;
711
+ }
712
+ const ids = byName.get(name);
713
+ if (ids === undefined) {
714
+ byName.set(name, [id]);
715
+ }
716
+ else if (!ids.includes(id)) {
717
+ ids.push(id);
718
+ }
719
+ }
720
+ }
721
+ return byName;
722
+ }
650
723
  /** The chain of components that names one declaration, read from the module inward. */
651
724
  function chainOf(record) {
652
725
  return [...record.parentChain, record.component];
@@ -691,3 +764,11 @@ function refOf(record, chain) {
691
764
  ...(record.typeParameter === undefined ? {} : { typeParameter: record.typeParameter }),
692
765
  });
693
766
  }
767
+ /**
768
+ * Every declaration of an inventory other than a file, by each name a use may spell it
769
+ * with. It is how a reference the checker could not resolve is still counted, as a
770
+ * reference to every declaration that bears the name it spells.
771
+ */
772
+ export function declarationsByName(held) {
773
+ return held.byName;
774
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * A document written in pieces, so no stage of writing it holds the whole document as
3
+ * one string: a string has a length limit far below what a large report reaches.
4
+ */
5
+ /** The length a piece grows to before it is handed on. */
6
+ export const CHUNK_LENGTH = 1 << 16;
7
+ /**
8
+ * The pieces of `pieces` joined into runs of about {@link CHUNK_LENGTH}, the last one
9
+ * shorter. A piece longer than that is handed on whole.
10
+ */
11
+ export function* coalesced(pieces) {
12
+ let held = [];
13
+ let length = 0;
14
+ for (const piece of pieces) {
15
+ held.push(piece);
16
+ length += piece.length;
17
+ if (length >= CHUNK_LENGTH) {
18
+ yield held.join("");
19
+ held = [];
20
+ length = 0;
21
+ }
22
+ }
23
+ if (length > 0) {
24
+ yield held.join("");
25
+ }
26
+ }
27
+ /** Whether `JSON.stringify` writes nothing for a value where an object member holds it. */
28
+ function omitted(value) {
29
+ return value === undefined || typeof value === "function" || typeof value === "symbol";
30
+ }
31
+ function* valueOf(value, prefix, step) {
32
+ // With no indentation the document is compact: no line breaks, and no space after a key.
33
+ const nl = step === "" ? "" : "\n";
34
+ if (Array.isArray(value)) {
35
+ if (value.length === 0) {
36
+ yield "[]";
37
+ return;
38
+ }
39
+ const inner = prefix + step;
40
+ yield "[";
41
+ for (let at = 0; at < value.length; at += 1) {
42
+ yield at === 0 ? `${nl}${inner}` : `,${nl}${inner}`;
43
+ const element = value[at];
44
+ yield* omitted(element) ? ["null"] : valueOf(element, inner, step);
45
+ }
46
+ yield `${nl}${prefix}]`;
47
+ return;
48
+ }
49
+ if (typeof value === "object" && value !== null) {
50
+ const inner = prefix + step;
51
+ let first = true;
52
+ for (const [key, member] of Object.entries(value)) {
53
+ if (omitted(member)) {
54
+ continue;
55
+ }
56
+ yield `${first ? "{" : ","}${nl}${inner}${JSON.stringify(key)}:${step === "" ? "" : " "}`;
57
+ first = false;
58
+ yield* valueOf(member, inner, step);
59
+ }
60
+ yield first ? "{}" : `${nl}${prefix}}`;
61
+ return;
62
+ }
63
+ yield JSON.stringify(value);
64
+ }
65
+ /**
66
+ * One JSON document in pieces whose concatenation is the value as `JSON.stringify`
67
+ * writes it at `indent` spaces, followed by a newline. The value is plain data: objects,
68
+ * arrays, strings, finite numbers, booleans and null, an object member holding nothing
69
+ * being left out as `JSON.stringify` leaves it out.
70
+ */
71
+ export function jsonChunks(value, indent) {
72
+ return coalesced((function* document() {
73
+ yield* valueOf(value, "", " ".repeat(indent));
74
+ yield "\n";
75
+ })());
76
+ }
package/dist/src/kinds.js CHANGED
@@ -5,42 +5,344 @@
5
5
  * Contract release the analyzer is written against.
6
6
  */
7
7
  const ROWS = [
8
- row("DS1001", "unused-exported", "deny", "deletable"),
9
- row("DS1002", "unused-unexported", "deny", "deletable"),
10
- row("DS1003", "unused-member", "deny", "deletable"),
11
- row("DS1004", "test-only-use", "deny", "deletable"),
12
- row("DS1005", "test-of-dead-code", "deny", "deletable"),
13
- row("DS1006", "deprecated-unused", "deny", "deletable"),
14
- row("DS1101", "unnecessary-export", "warn", "narrowable"),
15
- row("DS1102", "unnecessary-exposure", "warn", "narrowable"),
16
- row("DS1103", "unreachable-export", "deny", "deletable"),
17
- row("DS1104", "redundant-export-keyword", "warn", "narrowable"),
18
- row("DS1201", "unused-interface", "deny", "deletable"),
19
- row("DS1203", "uncalled-interface-method", "warn", "manual"),
20
- row("DS1204", "unused-satisfaction-assertion", "deny", "deletable"),
21
- row("DS1301", "write-only-symbol", "deny", "deletable"),
22
- row("DS1302", "unused-enum-member", "deny", "deletable"),
23
- row("DS1303", "unused-type-parameter", "deny", "deletable"),
24
- row("DS1501", "file-never-built", "deny", "deletable"),
25
- row("DS1502", "file-never-imported", "deny", "deletable"),
26
- row("DS1601", "unused-dependency", "deny", "manual"),
27
- row("DS1605", "unused-module-directive", "warn", "deletable"),
28
- row("DS1701", "suppression-without-reason", "deny", "none"),
29
- row("DS1702", "unscoped-ignore-entry", "deny", "none"),
30
- { ...row("DS1703", "stale-suppression", "deny", "none"), fixed: true },
31
- { ...row("DS1704", "unmatched-root", "deny", "none"), fixed: true },
32
- row("DS1705", "stale-cross-language-edge", "deny", "none"),
33
- row("DS1801", "unused-parameter", "warn", "manual"),
34
- row("DS1802", "unused-receiver", "warn", "deletable"),
35
- row("DS1803", "unused-result", "warn", "manual"),
36
- row("DS1805", "unreachable-statement", "deny", "deletable"),
37
- row("DS1807", "dead-store", "deny", "deletable"),
38
- row("DS1809", "unreachable-case", "deny", "deletable"),
8
+ {
9
+ code: "DS1001",
10
+ name: "unused-exported",
11
+ languages: ["go", "ts"],
12
+ rule: "An exported symbol with no reference in the target and no reference from any loaded consumer. A symbol referenced only from its own declaration site counts as unreferenced.",
13
+ defaultSeverity: "deny",
14
+ maxClass: "certain",
15
+ fixability: "deletable",
16
+ fixed: false,
17
+ },
18
+ {
19
+ code: "DS1002",
20
+ name: "unused-unexported",
21
+ languages: ["go", "ts"],
22
+ rule: "An unexported symbol with no reference in the target. A symbol referenced only from its own declaration site counts as unreferenced.",
23
+ defaultSeverity: "deny",
24
+ maxClass: "certain",
25
+ fixability: "deletable",
26
+ fixed: false,
27
+ },
28
+ {
29
+ code: "DS1003",
30
+ name: "unused-member",
31
+ languages: ["go", "ts"],
32
+ rule: "A struct field, class member or type member with no reference, a private member included. A member referenced only from its own declaration site counts as unreferenced.",
33
+ defaultSeverity: "deny",
34
+ maxClass: "certain",
35
+ fixability: "deletable",
36
+ fixed: false,
37
+ },
38
+ {
39
+ code: "DS1004",
40
+ name: "test-only-use",
41
+ languages: ["go", "ts"],
42
+ rule: "A symbol with zero production references and at least one test reference: an unused-exported, unused-unexported or unused-member candidate whose test reference count is not zero, reported once under this code. A reference from a consumer's test files is a test reference unless the configuration counts consumer tests as production.",
43
+ defaultSeverity: "deny",
44
+ maxClass: "certain",
45
+ fixability: "deletable",
46
+ fixed: false,
47
+ },
48
+ {
49
+ code: "DS1005",
50
+ name: "test-of-dead-code",
51
+ languages: ["go", "ts"],
52
+ rule: "A test symbol whose set of referenced target symbols is non-empty and every member of that set is reported dead. A test that references at least one live target symbol is never reported, no notion of a test's subject and no name matching enters the rule, and the message states the rule. The test joins the dead component of the symbols it references, and it and each of them count as referencing each other, so the test is a member of their cycle and never a dead symbol outside it.",
53
+ defaultSeverity: "deny",
54
+ maxClass: "certain",
55
+ fixability: "deletable",
56
+ fixed: false,
57
+ },
58
+ {
59
+ code: "DS1006",
60
+ name: "deprecated-unused",
61
+ languages: ["go", "ts"],
62
+ rule: "A symbol carrying a deprecation marker and no production reference: an unused-exported, unused-unexported or unused-member candidate whose symbol is deprecated, reported once under this code. Go identifies the marker by the convention the standard tools recognize, TypeScript by the documentation tag its tools recognize.",
63
+ defaultSeverity: "deny",
64
+ maxClass: "certain",
65
+ fixability: "deletable",
66
+ fixed: false,
67
+ },
68
+ {
69
+ code: "DS1101",
70
+ name: "unnecessary-export",
71
+ languages: ["go", "ts"],
72
+ rule: "An exported symbol whose every reference is inside the symbol's own package or module, reported as a candidate for unexporting. The subject is a package-level declaration or a method, and three subjects are excluded: an interface method, whose exportedness is the contract of the interface that declares it; a struct field, which an encoder reads by name; and a method that satisfies an interface some symbol uses as a type, which cannot be unexported without its type ceasing to satisfy that interface. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference; where the edge's other side is unknown to the analyzer the finding is emitted pending.",
73
+ precondition: "Closed world only. Reported always for a main package and an internal/ directory tree, and for a published package only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first.",
74
+ defaultSeverity: "warn",
75
+ maxClass: "certain",
76
+ fixability: "narrowable",
77
+ fixed: false,
78
+ },
79
+ {
80
+ code: "DS1102",
81
+ name: "unnecessary-exposure",
82
+ languages: ["go"],
83
+ rule: "An exported symbol of a non-internal package whose every reference is inside the target module, reported as a candidate for relocation behind an internal boundary. The subject is a package-level declaration or a method, and three subjects are excluded: an interface method, whose exportedness is the contract of the interface that declares it; a struct field, which an encoder reads by name; and a method that satisfies an interface some symbol uses as a type, which cannot be unexported without its type ceasing to satisfy that interface. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference; where the edge's other side is unknown to the analyzer the finding is emitted pending.",
84
+ precondition: "Closed world only. Reported always for a main package and an internal/ directory tree, and for a published package only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first.",
85
+ defaultSeverity: "warn",
86
+ maxClass: "certain",
87
+ fixability: "narrowable",
88
+ fixed: false,
89
+ },
90
+ {
91
+ code: "DS1103",
92
+ name: "unreachable-export",
93
+ languages: ["go", "ts"],
94
+ rule: "An unused exported symbol in a package or file no external code can import: an unused-exported candidate whose enclosing package or file is unimportable from outside, reported once under this code at the certain class, because unimportability is a property of the package graph rather than of the consumer set.",
95
+ defaultSeverity: "deny",
96
+ maxClass: "certain",
97
+ fixability: "deletable",
98
+ fixed: false,
99
+ },
100
+ {
101
+ code: "DS1104",
102
+ name: "redundant-export-keyword",
103
+ languages: ["ts"],
104
+ rule: "An exported declaration used only inside its own file, reported as a candidate for removing the export keyword. The finding names the narrower visibility the references support.",
105
+ precondition: "Closed world only. Reported in a file that is not an entry file and that either belongs to a project whose consumer set the configuration declares complete or is reached by no manifest export. A declared cross-language edge counts as a reference from outside the file; where the edge's other side is unknown to the analyzer the finding is emitted pending. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first.",
106
+ defaultSeverity: "warn",
107
+ maxClass: "certain",
108
+ fixability: "narrowable",
109
+ fixed: false,
110
+ },
111
+ {
112
+ code: "DS1201",
113
+ name: "unused-interface",
114
+ languages: ["go", "ts"],
115
+ rule: "An interface no symbol uses as a type, counting uses from every loaded module. The finding names the concrete implementations and their positions, and the interface's members join its dead component rather than being reported as independent unused declarations.",
116
+ defaultSeverity: "deny",
117
+ maxClass: "certain",
118
+ fixability: "deletable",
119
+ fixed: false,
120
+ },
121
+ {
122
+ code: "DS1203",
123
+ name: "uncalled-interface-method",
124
+ languages: ["go", "ts"],
125
+ rule: "An interface method that no call site invokes or selects through the interface, whatever the number of implementations. The finding names the concrete implementations and their positions.",
126
+ precondition: "Exempt: in Go, every method of an interface that declares an unexported method, the sum-type shape whose method set exists to restrict the implementors, a shape a TypeScript interface cannot take because it declares no member less visible than itself; and, in both languages, every marker method, an interface method whose every implementation carries an empty body.",
127
+ defaultSeverity: "warn",
128
+ maxClass: "certain",
129
+ fixability: "manual",
130
+ fixed: false,
131
+ },
132
+ {
133
+ code: "DS1204",
134
+ name: "unused-satisfaction-assertion",
135
+ languages: ["go"],
136
+ rule: "A compile-time satisfaction assertion whose interface no symbol uses as a type.",
137
+ defaultSeverity: "deny",
138
+ maxClass: "certain",
139
+ fixability: "deletable",
140
+ fixed: false,
141
+ },
142
+ {
143
+ code: "DS1301",
144
+ name: "write-only-symbol",
145
+ languages: ["go", "ts"],
146
+ rule: "A package-level or module-level variable, struct field, class member or collection that production code writes and never reads. In production mode a read from a test file counts as no read. A comparison of struct values reads every field of the type: in Go, a value of a struct type used as an operand of `==` or `!=`, as a map key, or as a `switch` tag or `case` expression is a read of every field of that type, transitively through the fields of every struct type it holds, recorded at the comparison, because equality reads every field and a field that decides equality carries information. In TypeScript a comparison of two object references reads no member, because `===` compares identity and never a member. The finding names each write position, so the deletion set is visible.",
147
+ defaultSeverity: "deny",
148
+ maxClass: "certain",
149
+ fixability: "deletable",
150
+ fixed: false,
151
+ },
152
+ {
153
+ code: "DS1302",
154
+ name: "unused-enum-member",
155
+ languages: ["go", "ts"],
156
+ rule: "An enumerated member that no symbol names, at the reachability class derived for the member and with no confidence ceiling below it. Every member of a type that carries a string, text or binary conversion method, or that a conversion from an integer or a wire value produces, is retained by the enum-group exemption first, so a member reached only by value is never a candidate.",
157
+ defaultSeverity: "deny",
158
+ maxClass: "certain",
159
+ fixability: "deletable",
160
+ fixed: false,
161
+ },
162
+ {
163
+ code: "DS1303",
164
+ name: "unused-type-parameter",
165
+ languages: ["go", "ts"],
166
+ rule: "A type parameter of a function or method that no part of the declaration's signature and no part of the declaration's body names, at the certain class. The deletion is local to the declaration and the explicit instantiations the reference set already lists. A type parameter of a function or method that is itself dead falls with it and is reported under no code of its own.",
167
+ precondition: "Function and method type parameters only. A type parameter of a type declaration is never reported, because a phantom type parameter such as `type ID[T any] int` makes two instantiations distinct types while naming the parameter nowhere, so deleting it changes the program. A method signature an interface or an object type declares belongs to that type declaration, so its type parameters are never reported either.",
168
+ defaultSeverity: "deny",
169
+ maxClass: "certain",
170
+ fixability: "deletable",
171
+ fixed: false,
172
+ },
173
+ {
174
+ code: "DS1501",
175
+ name: "file-never-built",
176
+ languages: ["go", "ts"],
177
+ rule: "A source file no configuration in the build matrix builds. On the Go side the finding names the build constraint that excluded the file. A file whose build constraint is the ignore tag is never reported: the toolchain applies no build constraint to a file named on its own command line, so that tag is the toolchain's convention for a file built by hand. A file any other custom tag excludes is reported under a matrix the configuration declares complete, because completeness is the maintainer's assertion that the listed configurations are every one the target builds, and a configuration the maintainer builds by hand belongs in that list.",
178
+ precondition: 'Reported only when the configuration declares the build matrix complete, and never for a file the toolchain ignored solely because it imports "C" under a build with cgo disabled; such a file is recorded as excluded by cgo rather than as never built.',
179
+ defaultSeverity: "deny",
180
+ maxClass: "certain",
181
+ fixability: "deletable",
182
+ fixed: false,
183
+ },
184
+ {
185
+ code: "DS1502",
186
+ name: "file-never-imported",
187
+ languages: ["go", "ts"],
188
+ rule: "A source file that no import reaches, that no root names, and in which no declared cross-language edge names a declaration. An import written in a loaded consumer reaches a file of the target as an import written in the target does. An edge's side names a declaration as a root names one, so a file holding a declaration an edge names is evaluated with the edge rather than reported: whether that declaration is dead is the edge evaluation's to say, and the merge resolves it against the paired side.",
189
+ defaultSeverity: "deny",
190
+ maxClass: "certain",
191
+ fixability: "deletable",
192
+ fixed: false,
193
+ },
194
+ {
195
+ code: "DS1601",
196
+ name: "unused-dependency",
197
+ languages: ["go", "ts"],
198
+ rule: "A directly declared dependency that no import in the target needs. On the Go side, a direct require whose module provides no package any target package or test variant imports. On the TypeScript side, a manifest dependency, development dependency or peer dependency the project's import closure does not need. A deletion finding whose fix would remove the last use of a dependency names that dependency.",
199
+ precondition: "On the Go side the rule is the semantics of `go mod tidy -diff` exactly: a requirement marked indirect is never reported, because it exists to pin a transitive version and removing it changes the build list. On the TypeScript side the import closure is the files of the run's projects, and three more dependencies are needed: one whose installed manifest declares a command, because a command is run by name rather than imported; one that the installed manifest of a needed dependency declares as a peer and does not mark optional; and, to a fixpoint, the required peers of every dependency so held. An installed manifest is the package's manifest in the nearest node_modules directory at or above the target, and a dependency with none is decided by the import closure alone.",
200
+ defaultSeverity: "deny",
201
+ maxClass: "certain",
202
+ fixability: "manual",
203
+ fixed: false,
204
+ },
205
+ {
206
+ code: "DS1605",
207
+ name: "unused-module-directive",
208
+ languages: ["go"],
209
+ rule: "A replace directive whose target module is absent from the build list, the one case in which the directive is exactly a no-op.",
210
+ precondition: "Only a replace whose target is absent from the build list. No other module-file directive is reported: not an exclude directive, because not selected is a counterfactual about a resolution that did not happen; not a workspace use entry and not a tool directive, because each is an external entry point whose invocation lives outside anything the analysis reads.",
211
+ defaultSeverity: "warn",
212
+ maxClass: "certain",
213
+ fixability: "deletable",
214
+ fixed: false,
215
+ },
216
+ {
217
+ code: "DS1701",
218
+ name: "suppression-without-reason",
219
+ languages: ["go", "ts"],
220
+ rule: "A suppression that carries no reason, in any of the three documents: an inline directive, an ignore-file entry or a baseline row. For the ignore file and the baseline, an entry or a row whose reason field is absent, empty or whitespace only is refused and reported rather than matched.",
221
+ defaultSeverity: "deny",
222
+ maxClass: "certain",
223
+ fixability: "none",
224
+ fixed: false,
225
+ },
226
+ {
227
+ code: "DS1702",
228
+ name: "unscoped-ignore-entry",
229
+ languages: ["go", "ts"],
230
+ rule: "An ignore-file entry or a baseline row that names a symbol and no file path. The entry is reported rather than matched, so a bare name cannot mask a match anywhere else in the project; the inline directive is scoped by its position and cannot be unscoped.",
231
+ defaultSeverity: "deny",
232
+ maxClass: "certain",
233
+ fixability: "none",
234
+ fixed: false,
235
+ },
236
+ {
237
+ code: "DS1703",
238
+ name: "stale-suppression",
239
+ languages: ["go", "ts"],
240
+ rule: "A suppression that matches no current finding, at either mechanism and in the baseline alike. The run exits with the findings code when at least one is reported. The kind is fixed on at deny: no flag, no severity setting and no per-mechanism exception reduces it below a finding, and a configuration naming this code under a severity key is an unimplemented key.",
241
+ defaultSeverity: "deny",
242
+ maxClass: "certain",
243
+ fixability: "none",
244
+ fixed: true,
245
+ },
246
+ {
247
+ code: "DS1704",
248
+ name: "unmatched-root",
249
+ languages: ["go", "ts"],
250
+ rule: "A configured root that matches no symbol, or a configured root pattern that matches no symbol. A root set nobody checks silently changes every result, so a stale one is a finding. The kind is fixed on at deny: no flag, no severity setting and no exception reduces it below a finding, and a configuration naming this code under a severity key is an unimplemented key.",
251
+ defaultSeverity: "deny",
252
+ maxClass: "certain",
253
+ fixability: "none",
254
+ fixed: true,
255
+ },
256
+ {
257
+ code: "DS1705",
258
+ name: "stale-cross-language-edge",
259
+ languages: ["go", "ts"],
260
+ rule: "A declared cross-language edge with a side that at least one analyzer evaluated and every evaluation of that side reports absent, so no analyzer that ran enumerates that side's symbol. Emitted by the merge, once per edge, never by an analyzer, so a stale edge is visible rather than silently inert. A dead side paired with an absent side drops its pending finding, and the edge is the reported defect: a misspelled reference must not turn a live pairing into a deletion.",
261
+ defaultSeverity: "deny",
262
+ maxClass: "certain",
263
+ fixability: "none",
264
+ fixed: false,
265
+ },
266
+ {
267
+ code: "DS1706",
268
+ name: "unmatched-configured-declaration",
269
+ languages: ["ts"],
270
+ rule: "An entry of a configuration key that names a declaration, being an entry of ts.injection_registrations, of the components or the bases of an entry of ts.lifecycle_contracts, or of ts.serializers, that names no declaration in any project of the run, by the resolution the ts section of contract/config.schema.json states for its shape. An entry that names a declaration no call reaches matches. An exemption whose configured declarations nobody checks silently retains nothing, so a stale entry is a finding. The kind is fixed on at deny: no flag, no severity setting and no exception reduces it below a finding, and a configuration naming this code under a severity key is an unimplemented key.",
271
+ defaultSeverity: "deny",
272
+ maxClass: "certain",
273
+ fixability: "none",
274
+ fixed: true,
275
+ },
276
+ {
277
+ code: "DS1801",
278
+ name: "unused-parameter",
279
+ languages: ["go", "ts"],
280
+ rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
281
+ precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not used as a value, is not a go:linkname or cgo target, and is not a stub whose body is empty or only panics. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change.",
282
+ defaultSeverity: "warn",
283
+ maxClass: "certain",
284
+ fixability: "manual",
285
+ fixed: false,
286
+ overlap: ["tsc --noUnusedParameters", "@typescript-eslint/no-unused-vars"],
287
+ },
288
+ {
289
+ code: "DS1802",
290
+ name: "unused-receiver",
291
+ languages: ["go"],
292
+ rule: "A named method receiver with no reference inside the method body, on a method whose signature is free to change. Go permits a method with no receiver name, so the fix deletes an identifier and changes no signature.",
293
+ precondition: "The same free-signature rule as unused-parameter, applied to the receiver.",
294
+ defaultSeverity: "warn",
295
+ maxClass: "certain",
296
+ fixability: "deletable",
297
+ fixed: false,
298
+ overlap: ["not applicable"],
299
+ },
300
+ {
301
+ code: "DS1803",
302
+ name: "unused-result",
303
+ languages: ["go", "ts"],
304
+ rule: "A result value that no call site of the function uses, on a function whose signature is free to change.",
305
+ precondition: "The same free-signature rule as unused-parameter, plus a closed-world condition: every call site of the function must be in the loaded graph, because an unknown caller may consume the result, so the kind reports only for a function whose callers are all visible.",
306
+ defaultSeverity: "warn",
307
+ maxClass: "certain",
308
+ fixability: "manual",
309
+ fixed: false,
310
+ overlap: ["none known"],
311
+ },
312
+ {
313
+ code: "DS1805",
314
+ name: "unreachable-statement",
315
+ languages: ["go", "ts"],
316
+ rule: "A statement that control flow cannot reach, at the precision the language's compiler applies to the same construct.",
317
+ defaultSeverity: "deny",
318
+ maxClass: "certain",
319
+ fixability: "deletable",
320
+ fixed: false,
321
+ overlap: ["tsc allowUnreachableCode", "eslint no-unreachable"],
322
+ },
323
+ {
324
+ code: "DS1807",
325
+ name: "dead-store",
326
+ languages: ["go", "ts"],
327
+ rule: "A write to a local variable with no read before the next write to it or the end of its scope, computed on the same read-and-write classification the write-only-symbol kind uses. The finding names the write position.",
328
+ defaultSeverity: "deny",
329
+ maxClass: "certain",
330
+ fixability: "deletable",
331
+ fixed: false,
332
+ overlap: ["eslint no-useless-assignment"],
333
+ },
334
+ {
335
+ code: "DS1809",
336
+ name: "unreachable-case",
337
+ languages: ["go", "ts"],
338
+ rule: "A switch case whose type or value an earlier case in the same switch already covers.",
339
+ defaultSeverity: "deny",
340
+ maxClass: "certain",
341
+ fixability: "deletable",
342
+ fixed: false,
343
+ overlap: ["none known"],
344
+ },
39
345
  ];
40
- /** A row at the `certain` ceiling whose severity a configuration may set. */
41
- function row(code, name, defaultSeverity, fixability) {
42
- return { code, name, defaultSeverity, maxClass: "certain", fixability, fixed: false };
43
- }
44
346
  /** Every live row of the issue-kind vocabulary, by its code, in ascending code order. */
45
347
  export const KINDS = new Map(ROWS.map((one) => [one.code, one]));
46
348
  /**
@@ -0,0 +1,23 @@
1
+ /** A count with its noun, so a line reads for one as well as for several. */
2
+ function counted(n, one, many) {
3
+ return `${String(n)} ${n === 1 ? one : many}`;
4
+ }
5
+ /**
6
+ * One line per configuration the run dropped, and one counting the questions the checker
7
+ * left unanswered by project, so a reader of the error stream sees where the analysis was
8
+ * partial. The lines carry no prefix; the caller writes them as its own diagnostics.
9
+ */
10
+ export function partialNotes(partial) {
11
+ const notes = [...partial.notBuilt]
12
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
13
+ .map((one) => `the derived configuration ${one.id} was not built and is not analyzed: ${one.error}`);
14
+ if (partial.unanswered.length > 0) {
15
+ const byProject = new Map();
16
+ for (const question of partial.unanswered) {
17
+ byProject.set(question.configFile, (byProject.get(question.configFile) ?? 0) + 1);
18
+ }
19
+ const where = [...byProject].map(([configFile, n]) => `${configFile}: ${String(n)}`).join(", ");
20
+ notes.push(`the checker answered ${counted(partial.unanswered.length, "question", "questions")} with a failure (${where}); every declaration an answer could have kept live is kept live and reported by nothing`);
21
+ }
22
+ return notes;
23
+ }