@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.
Files changed (113) hide show
  1. package/dist/src/analysis.js +95 -60
  2. package/dist/src/augmentations.js +14 -12
  3. package/dist/src/component-layer.js +1 -1
  4. package/dist/src/components.js +62 -20
  5. package/dist/src/configuration-files.js +230 -30
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +104 -13
  8. package/dist/src/convention-rows.js +10 -0
  9. package/dist/src/conventions.js +35 -1
  10. package/dist/src/dependencies.js +90 -32
  11. package/dist/src/discover.js +1 -1
  12. package/dist/src/edges.js +1 -1
  13. package/dist/src/element-access.js +341 -0
  14. package/dist/src/erased-stores.js +317 -0
  15. package/dist/src/exempt-classes.js +9 -22
  16. package/dist/src/exempt.js +3 -15
  17. package/dist/src/findings/completion.js +78 -16
  18. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  19. package/dist/src/findings/emitters.js +1 -5
  20. package/dist/src/findings/self-check.js +27 -10
  21. package/dist/src/findings-pass.js +6 -5
  22. package/dist/src/in-operands.js +30 -0
  23. package/dist/src/indexed-access.js +264 -119
  24. package/dist/src/intra-function-parts.js +25 -11
  25. package/dist/src/inventory.js +28 -12
  26. package/dist/src/keyof-comparisons.js +170 -0
  27. package/dist/src/kinds.js +17 -7
  28. package/dist/src/manifest.js +6 -16
  29. package/dist/src/matrix.js +20 -2
  30. package/dist/src/query.js +3 -0
  31. package/dist/src/references.js +14 -3
  32. package/dist/src/report.js +4 -1
  33. package/dist/src/reporters/sarif.js +4 -0
  34. package/dist/src/roots.js +0 -1
  35. package/dist/src/run.js +0 -2
  36. package/dist/src/schema.js +1 -1
  37. package/dist/src/scope.js +2 -2
  38. package/dist/src/serialization-contract.js +43 -14
  39. package/dist/src/session.js +12 -8
  40. package/dist/src/suppress-file.js +58 -6
  41. package/dist/src/suppress-inline.js +14 -5
  42. package/dist/src/suppress.js +64 -35
  43. package/dist/src/unimplemented-callees.js +312 -0
  44. package/dist/src/value-reach.js +7 -7
  45. package/dist/src/verbs/analyze.js +11 -9
  46. package/dist/src/workspace-programs.js +0 -3
  47. package/dist/src/workspace-resolution.js +0 -2
  48. package/dist/src/workspace.js +0 -3
  49. package/jsr.json +1 -1
  50. package/package.json +3 -2
  51. package/src/analysis.ts +145 -82
  52. package/src/augmentations.ts +14 -11
  53. package/src/calls.ts +3 -3
  54. package/src/component-files.ts +7 -7
  55. package/src/component-layer.ts +2 -2
  56. package/src/components.ts +104 -22
  57. package/src/config.ts +4 -4
  58. package/src/configuration-files.ts +250 -31
  59. package/src/conformance-record.ts +1 -1
  60. package/src/contextual-uses.ts +135 -17
  61. package/src/convention-rows.ts +23 -0
  62. package/src/conventions.ts +53 -1
  63. package/src/dependencies.ts +119 -38
  64. package/src/discover.ts +3 -3
  65. package/src/edges.ts +3 -3
  66. package/src/element-access.ts +446 -0
  67. package/src/entry-points.ts +1 -1
  68. package/src/erased-stores.ts +413 -0
  69. package/src/exempt-classes.ts +10 -24
  70. package/src/exempt.ts +5 -29
  71. package/src/file-facts.ts +3 -1
  72. package/src/finding.ts +3 -1
  73. package/src/findings/completion.ts +97 -21
  74. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  75. package/src/findings/emitters.ts +4 -9
  76. package/src/findings/self-check.ts +31 -14
  77. package/src/findings-pass.ts +14 -7
  78. package/src/graph.ts +2 -2
  79. package/src/html-entries.ts +1 -1
  80. package/src/in-operands.ts +45 -0
  81. package/src/indexed-access.ts +303 -137
  82. package/src/intra-function-parts.ts +41 -16
  83. package/src/inventory.ts +37 -33
  84. package/src/json-document.ts +1 -1
  85. package/src/keyof-comparisons.ts +211 -0
  86. package/src/kinds.ts +20 -7
  87. package/src/manifest.ts +7 -22
  88. package/src/matrix.ts +34 -2
  89. package/src/namespace-objects.ts +1 -1
  90. package/src/pnpm-workspace.ts +1 -1
  91. package/src/query.ts +10 -0
  92. package/src/references.ts +30 -7
  93. package/src/report.ts +11 -5
  94. package/src/reporters/sarif.ts +4 -0
  95. package/src/roots.ts +0 -3
  96. package/src/run.ts +0 -2
  97. package/src/schema.ts +2 -2
  98. package/src/scope.ts +2 -2
  99. package/src/serialization-contract.ts +53 -14
  100. package/src/session.ts +34 -31
  101. package/src/stores.ts +1 -1
  102. package/src/suppress-file.ts +68 -5
  103. package/src/suppress-inline.ts +17 -7
  104. package/src/suppress.ts +79 -47
  105. package/src/template-field.ts +1 -1
  106. package/src/unimplemented-callees.ts +411 -0
  107. package/src/value-reach.ts +12 -8
  108. package/src/verbs/analyze.ts +15 -9
  109. package/src/verbs/verb.ts +1 -4
  110. package/src/workspace-programs.ts +2 -9
  111. package/src/workspace-resolution.ts +0 -3
  112. package/src/workspace-run.ts +1 -1
  113. package/src/workspace.ts +1 -9
package/src/kinds.ts CHANGED
@@ -19,6 +19,8 @@ export interface KindRow {
19
19
  /** The highest confidence a finding of the kind carries. */
20
20
  readonly maxClass: Confidence;
21
21
  readonly fixability: Fixability;
22
+ /** Each fixability a finding of the kind may carry in place of `fixability`. */
23
+ readonly findingFixabilities?: readonly Fixability[];
22
24
  /**
23
25
  * Whether the Contract fixes the kind on at its default severity, so a severity key
24
26
  * naming the code, or a family prefix whose range holds it, is an unimplemented key.
@@ -43,6 +45,7 @@ const ROWS: readonly KindRow[] = [
43
45
  defaultSeverity: "deny",
44
46
  maxClass: "certain",
45
47
  fixability: "deletable",
48
+ findingFixabilities: ["manual"],
46
49
  fixed: false,
47
50
  },
48
51
  {
@@ -53,6 +56,7 @@ const ROWS: readonly KindRow[] = [
53
56
  defaultSeverity: "deny",
54
57
  maxClass: "certain",
55
58
  fixability: "deletable",
59
+ findingFixabilities: ["manual"],
56
60
  fixed: false,
57
61
  },
58
62
  {
@@ -65,6 +69,7 @@ const ROWS: readonly KindRow[] = [
65
69
  defaultSeverity: "deny",
66
70
  maxClass: "certain",
67
71
  fixability: "deletable",
72
+ findingFixabilities: ["manual"],
68
73
  fixed: false,
69
74
  },
70
75
  {
@@ -75,6 +80,7 @@ const ROWS: readonly KindRow[] = [
75
80
  defaultSeverity: "deny",
76
81
  maxClass: "certain",
77
82
  fixability: "deletable",
83
+ findingFixabilities: ["manual"],
78
84
  fixed: false,
79
85
  },
80
86
  {
@@ -85,6 +91,7 @@ const ROWS: readonly KindRow[] = [
85
91
  defaultSeverity: "deny",
86
92
  maxClass: "certain",
87
93
  fixability: "deletable",
94
+ findingFixabilities: ["manual"],
88
95
  fixed: false,
89
96
  },
90
97
  {
@@ -95,6 +102,7 @@ const ROWS: readonly KindRow[] = [
95
102
  defaultSeverity: "deny",
96
103
  maxClass: "certain",
97
104
  fixability: "deletable",
105
+ findingFixabilities: ["manual"],
98
106
  fixed: false,
99
107
  },
100
108
  {
@@ -103,7 +111,7 @@ const ROWS: readonly KindRow[] = [
103
111
  languages: ["go", "ts"],
104
112
  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. Three subjects are excluded. An interface method is excluded because its exportedness is the contract of the interface that declares it. A struct field is excluded because an encoder reads it by name. A method that satisfies an interface some symbol uses as a type is excluded because it cannot be unexported without its type ceasing to satisfy that interface, and so is a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, because that interface fixes them whether or not the program converts the method's type to it. 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. A reference from a Go external test package, a test file whose package clause names its directory's package with the suffix _test, is a reference from outside the package, because that test builds as another package and needs the export.",
105
113
  precondition:
106
- "Closed world only. A Go main package, and an internal/ directory tree for importers outside its parent, are closed worlds whatever the configuration declares, because no other program can import them, so this finding is reported there always. A published package is a closed world 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. A type that a parameter or a result of an exported function or method of its own package names is not reported when a reference from outside the package references that function or method, because the caller holds values of the type, and unexporting it would leave an exported signature naming a type its callers cannot name.",
114
+ "Closed world only. A Go main package, and an internal/ directory tree for importers outside its parent, are closed worlds whatever the configuration declares, because no other program can import them, so this finding is reported there always. A published package is a closed world 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. A type that a parameter or a result of an exported function or method of its own package names is not reported when a reference from outside the package references that function or method, because the caller holds values of the type, and unexporting it would leave an exported signature naming a type its callers cannot name. A package-level declaration whose name a string literal spells carries the class grammar/analysis.md gives it.",
107
115
  defaultSeverity: "warn",
108
116
  maxClass: "certain",
109
117
  fixability: "narrowable",
@@ -125,10 +133,11 @@ const ROWS: readonly KindRow[] = [
125
133
  code: "DS1103",
126
134
  name: "unreachable-export",
127
135
  languages: ["go", "ts"],
128
- 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.",
136
+ 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, except where the confidence rule of grammar/analysis.md gives a declaration a string literal spells the class possible.",
129
137
  defaultSeverity: "deny",
130
138
  maxClass: "certain",
131
139
  fixability: "deletable",
140
+ findingFixabilities: ["manual"],
132
141
  fixed: false,
133
142
  },
134
143
  {
@@ -151,6 +160,7 @@ const ROWS: readonly KindRow[] = [
151
160
  defaultSeverity: "deny",
152
161
  maxClass: "certain",
153
162
  fixability: "deletable",
163
+ findingFixabilities: ["manual"],
154
164
  fixed: false,
155
165
  },
156
166
  {
@@ -170,6 +180,8 @@ const ROWS: readonly KindRow[] = [
170
180
  name: "unused-satisfaction-assertion",
171
181
  languages: ["go"],
172
182
  rule: "A compile-time satisfaction assertion whose interface no symbol uses as a type.",
183
+ precondition:
184
+ "In a library, an assertion whose interface is published API, an exported interface of a package outside programs can import, has the reachability class possible, whatever consumers the run declares and loads. The assertion checks a promise the published API makes, that the type satisfies the published interface, and that promise is made to every program that imports the package, not only to the consumers the run declares. A loaded consumer that names the interface as a type uses it, and the assertion is not reported.",
173
185
  defaultSeverity: "deny",
174
186
  maxClass: "certain",
175
187
  fixability: "deletable",
@@ -179,7 +191,7 @@ const ROWS: readonly KindRow[] = [
179
191
  code: "DS1301",
180
192
  name: "write-only-symbol",
181
193
  languages: ["go", "ts"],
182
- rule: "A package-level or module-level variable, struct field, class member, type member or collection that production code writes and never reads. In production mode a read from a test file counts as no read. A write from a loaded consumer's code counts as a read, because the deletion the finding names would break a consumer the target does not own. A write in a consumer's test file counts as a read in the run that counts test references. It counts in a production sweep only where the configuration counts consumer tests as production. 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 Go a type argument the program instantiates a type parameter with, where the parameter's constraint is `comparable` or embeds it, is compared as an operand of `==` of that type is, recorded at the instantiation, so the struct values `slices.Equal`, `slices.Index` or `maps.Equal` compares have every field read. `reflect.DeepEqual` is not a comparison under this rule. In TypeScript a comparison of two object references reads no member, because `===` compares identity and never a member. In TypeScript a property of an object literal writes the member of the same name that the literal's contextual type declares, whether or not the compiler checks the literal for excess properties. Where that contextual type is a union or an intersection, the property writes the member of that name on each constituent that declares one. So a literal that a declaration annotates, a function returns, a call passes or a `satisfies` expression checks writes the members of the type it is given. A store through the value a field or a member holds reads that field or member rather than writing it, because the store lands in storage every other holder of the value reads. In Go that store is an assignment, an increment, a compound assignment or a `delete` whose target is reached through a struct field holding a map, a slice or a pointer. Examples are `s.f[k] = v`, `s.f.x = v` and `delete(s.f, k)`. The same store through a field holding an array or a struct is a store into the field itself. TypeScript holds every object by reference, so an element or property store through a member's value reads the member. The finding names each write position, so the deletion set is visible. Deleting the subject and its writes also deletes every declaration whose only uses they hold. Such a declaration is a type parameter of the subject's declaring type that the subject's type alone names, an import binding the writes alone use, or a declaration whose every use is in the type annotation of the subject or of another subject this rule reports, or in the writes those findings name. The finding's message names each such declaration, and no finding is reported about the declaration itself.",
194
+ rule: "A package-level or module-level variable, struct field, class member, type member or collection that production code writes and never reads. In production mode a read from a test file counts as no read. A write from a loaded consumer's code counts as a read, because the deletion the finding names would break a consumer the target does not own. A write in a consumer's test file counts as a read in the run that counts test references. It counts in a production sweep only where the configuration counts consumer tests as production. 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 Go a type argument the program instantiates a type parameter with, where the parameter's constraint is `comparable` or embeds it, is compared as an operand of `==` of that type is, recorded at the instantiation, so the struct values `slices.Equal`, `slices.Index` or `maps.Equal` compares have every field read. `reflect.DeepEqual` is not a comparison under this rule. In Go, a conversion the compiler accepts between two struct types, or between two pointer types that are not named types and whose base types are struct types, reads every field of the two struct types where their struct type literals are distinct, recorded at the conversion, because the conversion is legal only while their fields are identical, ignoring tags, and so no field of either can be deleted alone. It reads in the same way every field of each pair of distinct struct type literals the identity of the two types compares in turn: through each field's type, and from there through the element, key, base, parameter and result types of the type literals it reaches, through the method signatures of an interface type literal, and through the type arguments of an instantiated generic type. A field reached there has no reference of its own, as grammar/symbol-ref.md states, so it is never a subject and those reads hold no finding back. It stops at a defined type, whose fields the conversion does not read, because a defined type is identical to itself whatever fields it declares. A pair whose two sides are one struct type literal, the two struct types themselves included, as where one type is declared from the other or the conversion is to the operand's own type, reads nothing, because a field deleted from that literal leaves both sides identical. Where the operand's type or the conversion's type is a type parameter, the struct type its constraint's core type is takes that side's place, and a type parameter operand reads in the same way the fields of each struct type argument an instantiation the program holds gives it, an instantiation made through another generic declaration's own type parameters included. A conversion to any other type, an interface type among them, reads no field under this rule. In TypeScript a comparison of two object references reads no member, because `===` compares identity and never a member. In TypeScript a property of an object literal writes the member of the same name that the literal's contextual type declares, whether or not the compiler checks the literal for excess properties. Where that contextual type is a union or an intersection, the property writes the member of that name on each constituent that declares one. So a literal that a declaration annotates, a function returns, a call passes or a `satisfies` expression checks writes the members of the type it is given. An object literal handed to a callee the target does not implement has its members read, as grammar/analysis.md states. A store through the value a field or a member holds reads that field or member rather than writing it, because the store lands in storage every other holder of the value reads. In Go that store is an assignment, an increment, a compound assignment or a `delete` whose target is reached through a struct field holding a map, a slice or a pointer. Examples are `s.f[k] = v`, `s.f.x = v` and `delete(s.f, k)`. The same store through a field holding an array or a struct is a store into the field itself. TypeScript holds every object by reference, so an element or property store through a member's value reads the member. The finding names each write position, so the deletion set is visible. Deleting the subject and its writes also deletes every declaration whose only uses they hold. Such a declaration is a type parameter of the subject's declaring type that the subject's type alone names, an import binding the writes alone use, or a declaration whose every use is in the type annotation of the subject or of another subject this rule reports, or in the writes those findings name. The finding's message names each such declaration, and no finding is reported about the declaration itself.",
183
195
  defaultSeverity: "deny",
184
196
  maxClass: "certain",
185
197
  fixability: "deletable",
@@ -193,6 +205,7 @@ const ROWS: readonly KindRow[] = [
193
205
  defaultSeverity: "deny",
194
206
  maxClass: "certain",
195
207
  fixability: "deletable",
208
+ findingFixabilities: ["manual"],
196
209
  fixed: false,
197
210
  },
198
211
  {
@@ -211,7 +224,7 @@ const ROWS: readonly KindRow[] = [
211
224
  code: "DS1501",
212
225
  name: "file-never-built",
213
226
  languages: ["go", "ts"],
214
- 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.",
227
+ 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 holds only when the ignore tag is set is never reported, because grammar/analysis.md keeps such a file outside the analysis wherever no configuration builds it: the tag is the toolchain's convention for a file kept out of ordinary package builds unless a configuration explicitly satisfies the tag, and built by hand, the toolchain applying no build constraint to a file named on its own command line. 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.",
215
228
  precondition:
216
229
  '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.',
217
230
  defaultSeverity: "deny",
@@ -235,7 +248,7 @@ const ROWS: readonly KindRow[] = [
235
248
  languages: ["go", "ts"],
236
249
  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.",
237
250
  precondition:
238
- "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 a call of `import.meta.resolve` or of `require.resolve` in them, whatever `require` names, whose first argument is a string literal or a template literal with no substitution needs each dependency an import of that specifier needs. Three more dependencies are needed: one whose command a script entry or a workflow step runs, one that a string of a configuration file names, and one that a string names under a manifest key or a package key of an applied convention row, each as grammar/analysis.md states. A command no script entry and no workflow step runs needs nothing, and a peer the installed manifest of a needed dependency requires is needed only as any other dependency is, because the package manager installs a required peer itself. A dependency of a manifest whose directory holds source only in configurations the run lists in configurations_not_built is reported at the reachability class possible, because the source that may import it was not analyzed. 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.",
251
+ "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 a call of `import.meta.resolve` or of `require.resolve` in them, whatever `require` names, whose first argument is a string literal or a template literal with no substitution needs each dependency an import of that specifier needs. Four more dependencies are needed: one whose command a script entry or a workflow step runs, one that a string of a configuration file names, and one that a string names under a manifest key or a package key of an applied convention row, each as grammar/analysis.md states, and one that a needed dependency names as a peer, as stated below. A command no script entry and no workflow step runs needs nothing. A declared dependency that the installed manifest of a needed declared dependency names in its peerDependencies is needed, and so on until no further dependency joins. A peer dependency is a package the needed dependency expects beside it at a version its manifest states, whether or not it imports the package, and the project's declaration is what keeps that package present at a version the project chooses, so removing the declaration can break the needed dependency where nothing the analysis reads imports the peer. A peer that peerDependenciesMeta marks optional is needed in the same way, because the needed dependency's manifest still names it as a package it works with, and a package manager may leave an optional peer out where the project does not declare it, so the project's declaration is what keeps it present for the needed dependency. A declared dependency that only dependencies nothing needs name as a peer is needed by nothing. A dependency of a manifest whose directory holds source only in configurations the run lists in configurations_not_built is reported at the reachability class possible, because the source that may import it was not analyzed. An installed manifest is the package's manifest in the nearest node_modules directory at or above the directory of the manifest that declares the dependency, and a dependency with none is decided by the import closure alone. On the TypeScript side, the dependencies judged are those of every manifest grammar/analysis.md says the analysis reads, a nested package's among them, each read against its own directory: a dependency is needed by a use written in a file at or below the directory of the manifest that declares it, by a use no one file holds, and as a peer of a needed dependency of the same manifest or of a manifest below it. A dependency a nested package's manifest declares whose installed manifest the analysis cannot find is not judged, because no run requires a nested package to be installed and its commands and peers cannot be read, while a workspace member's and the target's own are judged as above.",
239
252
  defaultSeverity: "deny",
240
253
  maxClass: "certain",
241
254
  fixability: "manual",
@@ -277,7 +290,7 @@ const ROWS: readonly KindRow[] = [
277
290
  code: "DS1703",
278
291
  name: "stale-suppression",
279
292
  languages: ["go", "ts"],
280
- 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.",
293
+ rule: "A suppression record that is stale, as grammar/suppression.md defines it, 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.",
281
294
  defaultSeverity: "deny",
282
295
  maxClass: "certain",
283
296
  fixability: "none",
@@ -345,7 +358,7 @@ const ROWS: readonly KindRow[] = [
345
358
  languages: ["go", "ts"],
346
359
  rule: "A result value that no call site of the function uses, on a function whose signature is free to change.",
347
360
  precondition:
348
- "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.",
361
+ "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. A call that is the operand of an await expression written as a statement of its own uses a result whose type has a member named then, a promise or another thenable, because the caller sequences on it, and discards any other result. A call written as a statement of its own, or as the operand of void, discards its result whatever its type.",
349
362
  defaultSeverity: "warn",
350
363
  maxClass: "certain",
351
364
  fixability: "manual",
package/src/manifest.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Host } from "./host.ts";
9
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
9
+ import { isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
10
10
 
11
11
  /** The manifest's own name. */
12
12
  const MANIFEST = "package.json";
@@ -15,7 +15,7 @@ const MANIFEST = "package.json";
15
15
  const LEGACY_MEMBERS = ["main", "module", "types"] as const;
16
16
 
17
17
  /** What reaches one entry point the manifest names. */
18
- export type EntryRole =
18
+ type EntryRole =
19
19
  /** A consumer importing the package, which is a caller the analysis cannot see. */
20
20
  | "import"
21
21
  /** A shell running the package's command, which is a caller that exists. */
@@ -212,31 +212,16 @@ export function readManifest(host: Host, targetRoot: string): Manifest {
212
212
  }
213
213
 
214
214
  /**
215
- * The entry points every package manifest between the target root and each compiler
216
- * configuration names: each directory holding a `package.json` that a configuration sits
217
- * in or below, the target root and `known` excluded, whose manifests the run reads
218
- * otherwise. Each entry is spelled under its manifest's path below the target root, and
219
- * is published by its own manifest's rule, as the target's own entries are.
215
+ * The entry points the manifests of the nested packages in `dirs` name. Each entry is
216
+ * spelled under its manifest's path below the target root, and is published by its own
217
+ * manifest's rule, as the target's own entries are.
220
218
  */
221
219
  export function packageEntries(
222
220
  host: Host,
223
221
  targetRoot: string,
224
- configFiles: readonly string[],
225
- known: ReadonlySet<string>,
222
+ dirs: readonly string[],
226
223
  ): readonly ManifestEntry[] {
227
- const dirs = new Set<string>();
228
- for (const configFile of configFiles) {
229
- for (
230
- let dir = dirnamePath(resolvePath(targetRoot, configFile));
231
- dir !== targetRoot && relativePath(targetRoot, dir) !== undefined;
232
- dir = dirnamePath(dir)
233
- ) {
234
- if (!known.has(dir) && host.kindOf(joinPath(dir, MANIFEST)) === "file") {
235
- dirs.add(dir);
236
- }
237
- }
238
- }
239
- return [...dirs].sort().flatMap((dir) => {
224
+ return dirs.flatMap((dir) => {
240
225
  const manifest = readManifest(host, dir);
241
226
  const where = `${relativePath(targetRoot, dir) ?? dir}/${MANIFEST}`;
242
227
  return manifest.entries.map((entry) => ({
package/src/matrix.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * and intersected, with the dead components computed once over every configuration.
5
5
  */
6
6
 
7
- import { componentsOf, type Component } from "./components.ts";
7
+ import { componentsOf, type Component, type Positional } from "./components.ts";
8
8
  import { graphOf, OUTSIDE, type Graph } from "./graph.ts";
9
9
  import type { InventorySymbol } from "./inventory.ts";
10
10
  import { byPosition } from "./position.ts";
@@ -36,6 +36,8 @@ export interface Configured {
36
36
  * finding.
37
37
  */
38
38
  readonly referenceOnly?: readonly string[] | undefined;
39
+ /** Each declaration's dependents, by id, as the configuration's inventory states them. */
40
+ readonly dependents?: ReadonlyMap<string, readonly string[]> | undefined;
39
41
  }
40
42
 
41
43
  /** The run's configurations, indexed for the sweep. */
@@ -56,6 +58,8 @@ export interface Matrix {
56
58
  * whose project holds it, in the run's order.
57
59
  */
58
60
  readonly heldIn: readonly (readonly string[])[];
61
+ /** Each declaration's dependents, by id, as the first configuration holding it states them. */
62
+ readonly dependents: ReadonlyMap<string, readonly string[]>;
59
63
  }
60
64
 
61
65
  /** One declaration dead in every configuration that holds it. */
@@ -128,7 +132,13 @@ export function matrixOf(per: readonly Configured[]): Matrix {
128
132
  const references: Reference[] = [];
129
133
  const firstSeen = new Map<string, number>();
130
134
  const judged = new Set<string>();
135
+ const dependents = new Map<string, readonly string[]>();
131
136
  per.forEach((one, index) => {
137
+ for (const [id, after] of one.dependents ?? []) {
138
+ if (!dependents.has(id)) {
139
+ dependents.set(id, after);
140
+ }
141
+ }
132
142
  const referenceOnly = new Set(one.referenceOnly);
133
143
  for (const symbol of one.symbols) {
134
144
  const held = holders.get(symbol.id);
@@ -168,6 +178,7 @@ export function matrixOf(per: readonly Configured[]): Matrix {
168
178
  [...new Set(per.flatMap((one) => one.supportFiles ?? []))],
169
179
  ),
170
180
  heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
181
+ dependents,
171
182
  };
172
183
  }
173
184
 
@@ -257,14 +268,16 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
257
268
  });
258
269
  });
259
270
 
271
+ const liveUnder = liveAnywhere(union.symbols, per);
260
272
  return {
261
- liveUnder: liveAnywhere(union.symbols, per),
273
+ liveUnder,
262
274
  candidates,
263
275
  components: componentsOf(
264
276
  union,
265
277
  dead.map((one, at) => one && heldBy[at] === undefined),
266
278
  testOfDeadCode,
267
279
  unreferencedTest,
280
+ positionsOf(union, matrix.dependents, liveUnder),
268
281
  ),
269
282
  };
270
283
  }
@@ -286,6 +299,25 @@ export function liveFilesAt(
286
299
  );
287
300
  }
288
301
 
302
+ /**
303
+ * Each declaration's dependents by its place in the union, and per place whether the
304
+ * declaration is live under reachability in some configuration.
305
+ */
306
+ function positionsOf(
307
+ union: Graph,
308
+ dependents: ReadonlyMap<string, readonly string[]>,
309
+ liveUnder: ReadonlyMap<string, readonly Relation[]>,
310
+ ): Positional {
311
+ return {
312
+ dependents: union.symbols.map((symbol) =>
313
+ (dependents.get(symbol.id) ?? []).map((id) => union.at(id)).filter((at) => at !== OUTSIDE),
314
+ ),
315
+ live: union.symbols.map(
316
+ (symbol) => liveUnder.get(symbol.id)?.includes("reachability") === true,
317
+ ),
318
+ };
319
+ }
320
+
289
321
  /** The relations in the order a declaration's entry lists them. */
290
322
  const RELATIONS: readonly Relation[] = ["reference-counting", "reachability"];
291
323
 
@@ -34,7 +34,7 @@ import {
34
34
  } from "@typescript/native/unstable/ast";
35
35
 
36
36
  /** What one use of a namespace object reads beyond the names the checker resolves. */
37
- export type NamespaceUse =
37
+ type NamespaceUse =
38
38
  /** Every export of the module `specifier` names. */
39
39
  | { readonly kind: "whole"; readonly specifier: Node }
40
40
  /** The export a literal index names, the literal being the node to resolve. */
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  /** What the document says about `packages`. */
9
- export type PnpmPackages =
9
+ type PnpmPackages =
10
10
  | { readonly kind: "listed"; readonly patterns: readonly string[] }
11
11
  /** The document declares no `packages` key. */
12
12
  | { readonly kind: "absent" }
package/src/query.ts CHANGED
@@ -205,6 +205,8 @@ export interface Queries {
205
205
  targetOf(type: Type): Answer<Type>;
206
206
  /** The constraint of a type parameter, where it declares one; none for another type. */
207
207
  constraintOf(type: Type): Answer<Type | undefined>;
208
+ /** The object and the index of an indexed-access type; none for another type. */
209
+ indexedAccessOf(type: Type): Answer<{ readonly object: Type; readonly index: Type } | undefined>;
208
210
  /** The symbol a type is declared by. */
209
211
  symbolOfType(type: Type): Answer<TSSymbol | undefined>;
210
212
  signaturesOf(type: Type, kind: SignatureKind): Answer<readonly Signature[]>;
@@ -516,6 +518,14 @@ export function queriesOf(
516
518
  () => type.getConstraint(),
517
519
  )
518
520
  : undefined,
521
+ indexedAccessOf: (type) =>
522
+ type.isIndexedAccessType()
523
+ ? one(
524
+ "getObjectType",
525
+ () => typeLocation(type),
526
+ () => ({ object: type.getObjectType(), index: type.getIndexType() }),
527
+ )
528
+ : undefined,
519
529
  symbolOfType: (type) =>
520
530
  one(
521
531
  "getSymbol",
package/src/references.ts CHANGED
@@ -43,7 +43,10 @@ import { namespaceUses } from "./namespace-objects.ts";
43
43
  import { globExpression } from "./glob.ts";
44
44
  import { declarationsByName, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
45
45
  import { augmentationReferences } from "./augmentations.ts";
46
+ import { elementAccessReferences } from "./element-access.ts";
46
47
  import { indexedAccessReferences } from "./indexed-access.ts";
48
+ import { keyofComparisonReferences } from "./keyof-comparisons.ts";
49
+ import { unimplementedCalleeReads } from "./unimplemented-callees.ts";
47
50
  import { moduleDeclarationReferences } from "./module-declarations.ts";
48
51
  import { overrideReferences } from "./overrides.ts";
49
52
  import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
@@ -78,7 +81,7 @@ import { handedOn, valuesOf } from "./value-flow.ts";
78
81
  * `decorator` names the declaration a decorator expression is attached to, which the
79
82
  * decorator receives when the class is defined.
80
83
  */
81
- export type Use = "read" | "write" | "evaluation" | "decorator";
84
+ type Use = "read" | "write" | "evaluation" | "decorator";
82
85
 
83
86
  /**
84
87
  * Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`, the
@@ -88,7 +91,7 @@ export type Use = "read" | "write" | "evaluation" | "decorator";
88
91
  * or a value's position reads; `override`, the override a base call dispatches to; `keyed`, the
89
92
  * member a symbol key selects; `by-name`, each declaration so spelled, where the checker failed.
90
93
  */
91
- export type Resolution =
94
+ type Resolution =
92
95
  | "batch"
93
96
  | "resolved-symbol"
94
97
  | "shorthand"
@@ -185,12 +188,17 @@ export interface ReferenceCost {
185
188
  readonly contextualBatches: number;
186
189
  /** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
187
190
  readonly contextualLookups: number;
191
+ /**
192
+ * The requests the passes that read members through the checker's types make: element
193
+ * accesses, key comparisons, instantiated signatures and values handed to unimplemented
194
+ * callees. They are
195
+ * counted where the caller measures the client, through `requestCount`, and are 0 otherwise.
196
+ */
197
+ readonly typedLookups: number;
188
198
  }
189
199
 
190
200
  /** One project's references, in position order, and what reading them cost. */
191
201
  export interface References {
192
- /** The compiler configuration the project was opened from. */
193
- readonly configFile: string;
194
202
  readonly references: readonly Reference[];
195
203
  /**
196
204
  * Every file the rules classified as a test file, by its path below the target root,
@@ -214,6 +222,8 @@ export interface ReferenceOptions {
214
222
  * Each pattern is a rule of its own, which {@link testFileRulesOf} counts.
215
223
  */
216
224
  readonly testFiles: readonly string[];
225
+ /** The files, absolute, that are test files beside those the patterns name. */
226
+ readonly testFileNames?: ReadonlySet<string>;
217
227
  /**
218
228
  * The consumer whose files the pass walks instead of the target's. Its files are the
219
229
  * program's own files below its root and not below the target root, each position is
@@ -221,6 +231,8 @@ export interface ReferenceOptions {
221
231
  * reference to a declaration of `held`, the target's, is recorded.
222
232
  */
223
233
  readonly consumer?: ConsumerSide;
234
+ /** The client's running request count, where the caller measures the client. */
235
+ readonly requestCount?: () => number;
224
236
  }
225
237
 
226
238
  /**
@@ -1225,7 +1237,9 @@ export function references<Brand>(
1225
1237
  const testFile = new Map<SourceFile, boolean>();
1226
1238
  for (const { file } of walked) {
1227
1239
  const path = renderPosition(file, root, 0).path;
1228
- const test = patterns.some((pattern) => pattern.test(path));
1240
+ const test =
1241
+ patterns.some((pattern) => pattern.test(path)) ||
1242
+ options.testFileNames?.has(file.fileName) === true;
1229
1243
  testFile.set(file, test);
1230
1244
  if (test) {
1231
1245
  tests.push(path);
@@ -1554,12 +1568,21 @@ export function references<Brand>(
1554
1568
  }
1555
1569
  }
1556
1570
 
1571
+ let typedLookups = 0;
1557
1572
  if (consumer === undefined) {
1558
1573
  found.push(...overrideReferences(project, held, chains, new Set(tests)));
1574
+ const before = options.requestCount?.() ?? 0;
1559
1575
  found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1576
+ found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1577
+ found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1578
+ found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1579
+ typedLookups = (options.requestCount?.() ?? 0) - before;
1560
1580
  found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1561
1581
  }
1562
- found.push(...augmentationReferences(held, found));
1582
+ const scripts = new Set(
1583
+ walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId),
1584
+ );
1585
+ found.push(...augmentationReferences(held, found, scripts));
1563
1586
  found.sort(
1564
1587
  (a, b) =>
1565
1588
  byPosition(a.position, b.position) ||
@@ -1569,7 +1592,6 @@ export function references<Brand>(
1569
1592
  );
1570
1593
 
1571
1594
  return {
1572
- configFile: project.configFile,
1573
1595
  references:
1574
1596
  consumer === undefined
1575
1597
  ? found
@@ -1585,6 +1607,7 @@ export function references<Brand>(
1585
1607
  patternLookups: destructured.cost.patternLookups,
1586
1608
  contextualBatches: contextual.batches,
1587
1609
  contextualLookups: contextual.lookups,
1610
+ typedLookups,
1588
1611
  },
1589
1612
  };
1590
1613
  }
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.0.0";
22
+ const SCHEMA_VERSION = "8.1.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];
@@ -30,14 +30,14 @@ export const ANALYZER_NAME = "deadset-ts";
30
30
  /** The language this analyzer claims. */
31
31
  export const LANGUAGE = "ts";
32
32
 
33
- export interface WirePosition {
33
+ interface WirePosition {
34
34
  readonly path: string;
35
35
  readonly line: number;
36
36
  readonly column: number;
37
37
  readonly end_line: number;
38
38
  }
39
39
 
40
- export interface WireDetails {
40
+ interface WireDetails {
41
41
  readonly narrower_visibility?: string;
42
42
  readonly implementations?: readonly {
43
43
  readonly ref: string;
@@ -45,6 +45,8 @@ export interface WireDetails {
45
45
  readonly position: WirePosition;
46
46
  }[];
47
47
  readonly write_positions?: readonly WirePosition[];
48
+ /** Set under the name-literal rule, which no finding of this analyzer meets; SARIF still maps it. */
49
+ readonly name_literal?: WirePosition;
48
50
  readonly dependency_class?: string;
49
51
  readonly mechanism?: string;
50
52
  readonly entry?: {
@@ -54,6 +56,7 @@ export interface WireDetails {
54
56
  readonly reason?: string;
55
57
  };
56
58
  readonly overlap?: readonly string[];
59
+ readonly removes_last_use_of?: readonly string[];
57
60
  }
58
61
 
59
62
  /** One finding as the finding schema spells it. */
@@ -93,7 +96,7 @@ export interface WireFinding {
93
96
  readonly details: WireDetails;
94
97
  }
95
98
 
96
- export interface WireEdgeEvaluation {
99
+ interface WireEdgeEvaluation {
97
100
  readonly edge: string;
98
101
  readonly side: string;
99
102
  readonly symbol: string;
@@ -178,7 +181,7 @@ interface ReportConsumer {
178
181
  }
179
182
 
180
183
  /** One consumer the scope declared and the run did not load, with why. */
181
- export interface UnavailableConsumer {
184
+ interface UnavailableConsumer {
182
185
  readonly id: string;
183
186
  readonly reason: string;
184
187
  }
@@ -297,6 +300,9 @@ function wireDetails(details: FindingDetails): WireDetails {
297
300
  },
298
301
  }),
299
302
  ...(details.overlap === undefined ? {} : { overlap: details.overlap }),
303
+ ...(details.removesLastUseOf === undefined
304
+ ? {}
305
+ : { removes_last_use_of: details.removesLastUseOf }),
300
306
  };
301
307
  }
302
308
 
@@ -160,6 +160,10 @@ function relatedOf(found: WireFinding): readonly Related[] {
160
160
  for (const at of found.details.write_positions ?? []) {
161
161
  related.push({ label: "write", at, endLine: at.end_line });
162
162
  }
163
+ if (found.details.name_literal !== undefined) {
164
+ const at = found.details.name_literal;
165
+ related.push({ label: "name", at, endLine: at.end_line });
166
+ }
163
167
  for (const one of found.component.members ?? []) {
164
168
  if (
165
169
  declarationKey(one.ref, one.position.path) !==
package/src/roots.ts CHANGED
@@ -81,8 +81,6 @@ export interface Root {
81
81
 
82
82
  /** One project's roots, and every configured string that named nothing in it. */
83
83
  export interface Roots {
84
- /** The compiler configuration the project was opened from. */
85
- readonly configFile: string;
86
84
  /**
87
85
  * Every root of this project, ordered by the site of the declaration it names,
88
86
  * then by kind, then by the string that named it. A declaration that is a root for
@@ -432,7 +430,6 @@ export function roots<Brand>(
432
430
  });
433
431
 
434
432
  return {
435
- configFile: project.configFile,
436
433
  liveUnderReachability: ordered,
437
434
  unmatched,
438
435
  unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
package/src/run.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ConfigError, REPOSITORY_DOCUMENT, type Inputs } from "./config.ts";
2
2
  import { DiscoveryError, renderDiagnostic } from "./discover.ts";
3
- import { EMITTERS } from "./findings/emitters.ts";
4
3
  import type { Host } from "./host.ts";
5
4
  import { joinPath, resolvePath } from "./paths.ts";
6
5
  import { readScope, ScopeError, scopeForDir, type Scope } from "./scope.ts";
@@ -264,7 +263,6 @@ function invoke(
264
263
  });
265
264
  return opened;
266
265
  },
267
- emitters: EMITTERS,
268
266
  });
269
267
  } catch (error: unknown) {
270
268
  opened?.close();
package/src/schema.ts CHANGED
@@ -15,7 +15,7 @@ import { ConfigError } from "./config.ts";
15
15
  * - `object` is one setting written as an object: its member names are declared
16
16
  * and a source supplies the object whole.
17
17
  */
18
- export type KeyKind = "leaf" | "section" | "map" | "list" | "object";
18
+ type KeyKind = "leaf" | "section" | "map" | "list" | "object";
19
19
 
20
20
  /**
21
21
  * One node of the closed key list. `members` holds a section's declared members,
@@ -259,7 +259,7 @@ export function malformed(label: string, path: string, detail: string): ConfigEr
259
259
  * A refusal of a key the closed key list does not declare, naming the nearest key
260
260
  * it does.
261
261
  */
262
- export function unimplementedKey(label: string, path: string): ConfigError {
262
+ function unimplementedKey(label: string, path: string): ConfigError {
263
263
  const nearest = nearestKey(path);
264
264
  let hint = "";
265
265
  if (nearest === path) {
package/src/scope.ts CHANGED
@@ -4,8 +4,8 @@ import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
4
4
  import { checkDocument, type KeyNode } from "./schema.ts";
5
5
 
6
6
  /** The roles a scope document declares for a module. */
7
- export const ROLE_TARGET = "target";
8
- export const ROLE_CONSUMER = "consumer";
7
+ const ROLE_TARGET = "target";
8
+ const ROLE_CONSUMER = "consumer";
9
9
 
10
10
  /**
11
11
  * A scope document above this length is refused rather than decoded. It names one