@cplieger/deadset-ts 5.2.2 → 6.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 (75) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +28 -5
  3. package/dist/src/augmentations.js +50 -0
  4. package/dist/src/components.js +15 -15
  5. package/dist/src/conformance-record.js +2 -2
  6. package/dist/src/convention-rows.js +17 -2
  7. package/dist/src/conventions.js +82 -1
  8. package/dist/src/dependencies.js +42 -26
  9. package/dist/src/entry-points.js +28 -14
  10. package/dist/src/exempt.js +1 -0
  11. package/dist/src/findings/dependencies-and-module-machinery.js +2 -2
  12. package/dist/src/findings/emitters.js +47 -12
  13. package/dist/src/findings/interfaces.js +4 -3
  14. package/dist/src/findings/intra-function.js +15 -2
  15. package/dist/src/findings/unused-declarations.js +13 -1
  16. package/dist/src/findings-pass.js +1 -0
  17. package/dist/src/graph.js +6 -6
  18. package/dist/src/implementations.js +1 -1
  19. package/dist/src/indexed-access.js +215 -0
  20. package/dist/src/interface-satisfaction.js +85 -23
  21. package/dist/src/intra-function-parts.js +139 -30
  22. package/dist/src/inventory.js +6 -1
  23. package/dist/src/kinds.js +13 -12
  24. package/dist/src/matrix.js +34 -9
  25. package/dist/src/module-declarations.js +180 -0
  26. package/dist/src/outside-tests.js +17 -15
  27. package/dist/src/overrides.js +70 -9
  28. package/dist/src/query.js +7 -0
  29. package/dist/src/ref.js +1 -0
  30. package/dist/src/references.js +6 -0
  31. package/dist/src/report.js +6 -1
  32. package/dist/src/reporters/sarif.js +3 -1
  33. package/dist/src/reporters/text.js +23 -3
  34. package/dist/src/roots.js +50 -10
  35. package/dist/src/version.js +1 -1
  36. package/dist/src/workflow-steps.js +82 -0
  37. package/jsr.json +1 -1
  38. package/package.json +8 -2
  39. package/src/analysis.ts +40 -4
  40. package/src/augmentations.ts +72 -0
  41. package/src/components.ts +15 -13
  42. package/src/conformance-record.ts +2 -2
  43. package/src/convention-rows.ts +25 -3
  44. package/src/conventions.ts +106 -2
  45. package/src/dependencies.ts +51 -31
  46. package/src/entry-points.ts +32 -19
  47. package/src/exempt.ts +4 -1
  48. package/src/findings/dependencies-and-module-machinery.ts +2 -2
  49. package/src/findings/emitters.ts +71 -12
  50. package/src/findings/interfaces.ts +7 -6
  51. package/src/findings/intra-function.ts +20 -2
  52. package/src/findings/unused-declarations.ts +16 -1
  53. package/src/findings-pass.ts +4 -1
  54. package/src/graph.ts +6 -6
  55. package/src/implementations.ts +1 -1
  56. package/src/indexed-access.ts +295 -0
  57. package/src/interface-satisfaction.ts +118 -27
  58. package/src/intra-function-parts.ts +196 -35
  59. package/src/inventory.ts +8 -0
  60. package/src/kinds.ts +14 -12
  61. package/src/matrix.ts +51 -8
  62. package/src/module-declarations.ts +241 -0
  63. package/src/outside-tests.ts +18 -16
  64. package/src/overrides.ts +80 -14
  65. package/src/query.ts +28 -0
  66. package/src/ref.ts +1 -0
  67. package/src/references.ts +6 -0
  68. package/src/report.ts +11 -1
  69. package/src/reporters/sarif.ts +4 -1
  70. package/src/reporters/text.ts +25 -3
  71. package/src/roots.ts +56 -7
  72. package/src/sweep.ts +12 -0
  73. package/src/version.ts +1 -1
  74. package/src/workflow-steps.ts +104 -0
  75. package/src/workspace-run.ts +3 -4
@@ -0,0 +1,241 @@
1
+ /**
2
+ * The imports that name a `declare module` declaration whose name is a string literal: an
3
+ * augmentation or an ambient module, a wildcard one included. An import names it when its
4
+ * specifier equals the name, matches it with the one `*` standing for any run, or resolves
5
+ * to the same module. One that names it by name or pattern also reads the members it
6
+ * imports, whatever file the specifier resolves to, as a compiler reading no such file does.
7
+ */
8
+
9
+ import {
10
+ isCallExpression,
11
+ isExportDeclaration,
12
+ isExternalModuleReference,
13
+ isImportDeclaration,
14
+ isImportEqualsDeclaration,
15
+ isImportTypeNode,
16
+ isLiteralTypeNode,
17
+ isModuleDeclaration,
18
+ isNamespaceExport,
19
+ isNamespaceImport,
20
+ isNoSubstitutionTemplateLiteral,
21
+ isStringLiteral,
22
+ SyntaxKind,
23
+ type Node,
24
+ type SourceFile,
25
+ } from "@typescript/native/unstable/ast";
26
+ import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
27
+ import { renderPosition } from "./position.ts";
28
+ import { UNANSWERED } from "./query.ts";
29
+ import { nameComponent } from "./ref.ts";
30
+ import type { Reference } from "./references.ts";
31
+ import type { ProjectView } from "./session.ts";
32
+
33
+ /** Whether a node is a specifier's text: a string literal or a template with no substitution. */
34
+ function literalText(node: Node | undefined): node is Node & { readonly text: string } {
35
+ return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
36
+ }
37
+
38
+ /** The specifier one node writes, where it is an import of any form. */
39
+ function specifierOf(node: Node): (Node & { readonly text: string }) | undefined {
40
+ if (isImportDeclaration(node) || isExportDeclaration(node)) {
41
+ const specifier = node.moduleSpecifier;
42
+ return literalText(specifier) ? specifier : undefined;
43
+ }
44
+ if (isImportEqualsDeclaration(node)) {
45
+ const reference = node.moduleReference;
46
+ return isExternalModuleReference(reference) && literalText(reference.expression)
47
+ ? reference.expression
48
+ : undefined;
49
+ }
50
+ if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
51
+ const [argument] = node.arguments;
52
+ return literalText(argument) ? argument : undefined;
53
+ }
54
+ if (isImportTypeNode(node) && isLiteralTypeNode(node.argument)) {
55
+ const literal = node.argument.literal;
56
+ return literalText(literal) ? literal : undefined;
57
+ }
58
+ return undefined;
59
+ }
60
+
61
+ /**
62
+ * The export names an import or a re-export reads from the module it names: none for a
63
+ * namespace, a star or a bare form.
64
+ */
65
+ export function namesImported(node: Node): readonly string[] {
66
+ if (isImportDeclaration(node)) {
67
+ const clause = node.importClause;
68
+ const bindings = clause?.namedBindings;
69
+ return [
70
+ ...(clause?.name === undefined ? [] : ["default"]),
71
+ ...(bindings === undefined || isNamespaceImport(bindings)
72
+ ? []
73
+ : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
74
+ ];
75
+ }
76
+ const clause = isExportDeclaration(node) ? node.exportClause : undefined;
77
+ return clause === undefined || isNamespaceExport(clause)
78
+ ? []
79
+ : clause.elements.map((one) => (one.propertyName ?? one.name).text);
80
+ }
81
+
82
+ /**
83
+ * Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
84
+ * A relative name is resolved against its own file, so no specifier names it by its text.
85
+ */
86
+ function namesModule(specifier: string, name: string): boolean {
87
+ if (/^(?:\.{1,2}(?:\/|$)|\/)/u.test(name)) {
88
+ return false;
89
+ }
90
+ const star = name.indexOf("*");
91
+ if (star < 0 || name.slice(star + 1).includes("*")) {
92
+ return specifier === name;
93
+ }
94
+ const prefix = name.slice(0, star);
95
+ const suffix = name.slice(star + 1);
96
+ return (
97
+ specifier.length >= prefix.length + suffix.length &&
98
+ specifier.startsWith(prefix) &&
99
+ specifier.endsWith(suffix)
100
+ );
101
+ }
102
+
103
+ /** One module declaration a specifier names, and the members of it the import reads. */
104
+ interface Named {
105
+ readonly id: string;
106
+ readonly members: readonly string[];
107
+ }
108
+
109
+ /**
110
+ * The module declarations of an inventory a specifier names by name or pattern, each with
111
+ * the members that hold the export names given.
112
+ */
113
+ export function namedBy(
114
+ held: Inventory,
115
+ ): (specifier: string, names: readonly string[]) => readonly Named[] {
116
+ if (held.modules.size === 0) {
117
+ return () => [];
118
+ }
119
+ const refs = new Map<string, string>();
120
+ const children = new Map<string, InventorySymbol[]>();
121
+ for (const symbol of held.symbols) {
122
+ if (held.modules.has(symbol.id)) {
123
+ refs.set(symbol.id, symbol.ref);
124
+ }
125
+ if (held.modules.has(symbol.parent)) {
126
+ const siblings = children.get(symbol.parent) ?? [];
127
+ siblings.push(symbol);
128
+ children.set(symbol.parent, siblings);
129
+ }
130
+ }
131
+ return (specifier, names) =>
132
+ [...held.modules].flatMap(([id, name]) => {
133
+ if (!namesModule(specifier, name)) {
134
+ return [];
135
+ }
136
+ // A member is named by its declaration's reference, or an export alias's.
137
+ const ref = refs.get(id) ?? "";
138
+ const scope = ref.slice(0, ref.indexOf("#"));
139
+ const wanted = new Set(
140
+ names.flatMap((one) => [
141
+ `${ref}.${nameComponent(one)}`,
142
+ `${scope}#${nameComponent(one)}:alias`,
143
+ ]),
144
+ );
145
+ const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
146
+ return [{ id, members: members.map((child) => child.id) }];
147
+ });
148
+ }
149
+
150
+ /** One import of a project's own files, and the declaration or file that holds it. */
151
+ interface Import {
152
+ readonly file: SourceFile;
153
+ readonly from: string;
154
+ readonly specifier: Node & { readonly text: string };
155
+ readonly names: readonly string[];
156
+ }
157
+
158
+ /** One module declaration of the inventory, and the name node it declares. */
159
+ interface Declared {
160
+ readonly id: string;
161
+ readonly name: Node & { readonly text: string };
162
+ }
163
+
164
+ /**
165
+ * The references the imports of one project's own files make to the module declarations
166
+ * they name, each at the import's specifier.
167
+ */
168
+ export function moduleDeclarationReferences<Brand>(
169
+ project: ProjectView<Brand>,
170
+ held: Inventory,
171
+ root: string,
172
+ testFiles: ReadonlySet<string>,
173
+ ): readonly Reference[] {
174
+ const imports: Import[] = [];
175
+ const declared: Declared[] = [];
176
+ for (const file of project.ownSourceFiles()) {
177
+ const fileId = held.declarations.get(nodeKey(file, file));
178
+ if (fileId === undefined) {
179
+ continue;
180
+ }
181
+ const visit = (node: Node, from: string): void => {
182
+ const own = held.declarations.get(nodeKey(file, node));
183
+ if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
184
+ declared.push({ id: own, name: node.name });
185
+ }
186
+ const specifier = specifierOf(node);
187
+ if (specifier !== undefined) {
188
+ imports.push({ file, from, specifier, names: namesImported(node) });
189
+ }
190
+ node.forEachChild((child) => {
191
+ visit(child, own ?? from);
192
+ });
193
+ };
194
+ file.forEachChild((child) => {
195
+ visit(child, fileId);
196
+ });
197
+ }
198
+ if (declared.length === 0 || imports.length === 0) {
199
+ return [];
200
+ }
201
+
202
+ const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
203
+ const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
204
+ const moduleOf = (at: number): number | undefined => {
205
+ const symbol = answers[at];
206
+ return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
207
+ };
208
+
209
+ const named = namedBy(held);
210
+ const found: Reference[] = [];
211
+ imports.forEach((one, index) => {
212
+ const resolved = moduleOf(declared.length + index);
213
+ const position = renderPosition(one.file, root, one.specifier.getStart());
214
+ const read = (to: string, resolution: Reference["resolution"]): void => {
215
+ found.push({
216
+ from: one.from,
217
+ to,
218
+ position,
219
+ use: "read",
220
+ resolution,
221
+ test: testFiles.has(position.path),
222
+ });
223
+ };
224
+ const same = new Set(
225
+ declared
226
+ .filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
227
+ .map((declaration) => declaration.id),
228
+ );
229
+ for (const id of same) {
230
+ read(id, "batch");
231
+ }
232
+ for (const declaration of named(one.specifier.text, one.names)) {
233
+ if (!same.has(declaration.id)) {
234
+ for (const to of [declaration.id, ...declaration.members]) {
235
+ read(to, "syntax");
236
+ }
237
+ }
238
+ }
239
+ });
240
+ return found;
241
+ }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * The test files no compiler configuration of a run holds. The runner executes them all
3
3
  * the same, so each module one names is a test reference: the own file a relative
4
- * specifier names, and the declarations that file exports under the names read from it.
5
- * Such a file is read without a program, so what its body references beyond the modules
6
- * it names is not known.
4
+ * specifier names, and the declarations that file exports under the names read from it,
5
+ * or a module declaration the specifier names by name or pattern and its members of
6
+ * those names. Such a file is read without a program, so what its body references
7
+ * beyond the modules it names is not known.
7
8
  */
8
9
 
9
10
  import {
@@ -22,6 +23,7 @@ import { RESOLVED_EXTENSIONS, type ParseFile } from "./configuration-files.ts";
22
23
  import { emittedFrom } from "./emit-map.ts";
23
24
  import type { Host } from "./host.ts";
24
25
  import type { Inventory } from "./inventory.ts";
26
+ import { namedBy, namesImported } from "./module-declarations.ts";
25
27
  import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.ts";
26
28
  import { renderPosition } from "./position.ts";
27
29
  import { nameComponent } from "./ref.ts";
@@ -75,21 +77,15 @@ function modulesNamed(file: SourceFile): readonly NamedModule[] {
75
77
  const bindings = clause?.namedBindings;
76
78
  name(
77
79
  statement.moduleSpecifier,
78
- [
79
- ...(clause?.name === undefined ? [] : ["default"]),
80
- ...(bindings === undefined || isNamespaceImport(bindings)
81
- ? []
82
- : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
83
- ],
80
+ namesImported(statement),
84
81
  bindings === undefined ? clause?.name === undefined : isNamespaceImport(bindings),
85
82
  );
86
83
  } else if (isExportDeclaration(statement) && statement.moduleSpecifier !== undefined) {
87
84
  const clause = statement.exportClause;
88
- const star = clause === undefined || isNamespaceExport(clause);
89
85
  name(
90
86
  statement.moduleSpecifier,
91
- star ? [] : clause.elements.map((one) => (one.propertyName ?? one.name).text),
92
- star,
87
+ namesImported(statement),
88
+ clause === undefined || isNamespaceExport(clause),
93
89
  );
94
90
  }
95
91
  }
@@ -158,11 +154,21 @@ export function outsideTestReferences(
158
154
  exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
159
155
  }
160
156
  }
157
+ const declared = namedBy(held);
161
158
  const found: Reference[] = [];
162
159
  for (const { path, file } of tests) {
163
160
  const from = `${path}#`;
164
161
  const dir = dirnamePath(path) === "." ? "" : dirnamePath(path);
165
162
  for (const named of modulesNamed(file)) {
163
+ const position = renderPosition(file, targetRoot, named.specifier.getStart());
164
+ const read = (to: string, use: Reference["use"]): void => {
165
+ found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
166
+ };
167
+ for (const declaration of declared(named.text, named.names)) {
168
+ for (const to of [declaration.id, ...declaration.members]) {
169
+ read(to, "read");
170
+ }
171
+ }
166
172
  const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
167
173
  if (written === undefined) {
168
174
  continue;
@@ -173,10 +179,6 @@ export function outsideTestReferences(
173
179
  if (fileId === undefined) {
174
180
  continue;
175
181
  }
176
- const position = renderPosition(file, targetRoot, named.specifier.getStart());
177
- const read = (to: string, use: Reference["use"]): void => {
178
- found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
179
- };
180
182
  if (named.evaluates) {
181
183
  read(fileId, "evaluation");
182
184
  }
package/src/overrides.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
- * The overrides of the target's class members. A call through the base class, `this`
3
- * in the base's own body or a value typed as the base, names the base's member and
4
- * runs the override of whichever subclass the value is, so the base member's
5
- * declaration references every override as a use of it.
2
+ * The overrides of the target's class members. A call through the base class names the
3
+ * base's member and runs the override of whichever subclass the value is, so the base
4
+ * member's declaration references every override. A method overriding a member of a class
5
+ * declared outside the target, at any depth of its `extends` chain, is called by outside
6
+ * code holding the instance through that class, so its own class references it.
6
7
  */
7
8
 
8
9
  import {
@@ -18,6 +19,7 @@ import {
18
19
  import type { AliasChains } from "./alias-chain.ts";
19
20
  import { memberComponent } from "./class-members.ts";
20
21
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
22
+ import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
21
23
  import { UNANSWERED } from "./query.ts";
22
24
  import type { Reference } from "./references.ts";
23
25
  import type { ProjectView } from "./session.ts";
@@ -58,9 +60,10 @@ function extending<Brand>(
58
60
 
59
61
  /**
60
62
  * The references each base class member makes to its overrides, for the classes of one
61
- * project that extend another class of the target. A member a nearer base already
62
- * overrides is reached through that override. A private member is not overridden.
63
- * Each reference is placed at the override and made by its file.
63
+ * project that extend another class of the target, and each class makes to its methods
64
+ * that override a member of a class declared outside the target. A member a nearer base
65
+ * already overrides is reached through that override. A private member is not
66
+ * overridden. Each reference is placed at the override and made by its file.
64
67
  */
65
68
  export function overrideReferences<Brand>(
66
69
  project: ProjectView<Brand>,
@@ -89,17 +92,28 @@ export function overrideReferences<Brand>(
89
92
  members.set(symbol.parent, own);
90
93
  }
91
94
  const bases = new Map<string, readonly string[]>();
95
+ const outsideBases = new Map<string, TSSymbol>();
96
+ const own = project.ownPaths();
92
97
  const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
93
98
  classes.forEach(({ id }, index) => {
94
99
  const symbol = symbols[index];
95
100
  if (symbol !== undefined && symbol !== UNANSWERED) {
96
- bases.set(
97
- id,
98
- chains
99
- .chainOf(symbol)
100
- .map((target) => target.id)
101
- .filter((target) => byId.get(target)?.kind === "class" && target !== id),
102
- );
101
+ const inTarget = chains
102
+ .chainOf(symbol)
103
+ .map((target) => target.id)
104
+ .filter((target) => byId.get(target)?.kind === "class" && target !== id);
105
+ bases.set(id, inTarget);
106
+ const declared =
107
+ (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
108
+ if (
109
+ inTarget.length === 0 &&
110
+ declared !== UNANSWERED &&
111
+ (declared.flags & SymbolFlags.Class) !== 0 &&
112
+ declared.declarations.length > 0 &&
113
+ declared.declarations.every((handle) => !own.has(handle.path))
114
+ ) {
115
+ outsideBases.set(id, declared);
116
+ }
103
117
  }
104
118
  });
105
119
 
@@ -117,6 +131,21 @@ export function overrideReferences<Brand>(
117
131
  };
118
132
 
119
133
  const found: Reference[] = [];
134
+ const outsideNames = outsideMemberNames(project, bases, outsideBases);
135
+ for (const [classId, names] of outsideNames) {
136
+ for (const [component, member] of members.get(classId) ?? []) {
137
+ if (member.kind === "method" && names.has(component)) {
138
+ found.push({
139
+ from: classId,
140
+ to: member.id,
141
+ position: member.position,
142
+ use: "read",
143
+ resolution: "override",
144
+ test: testFiles.has(member.position.path),
145
+ });
146
+ }
147
+ }
148
+ }
120
149
  for (const [classId, baseIds] of bases) {
121
150
  for (const [component, member] of members.get(classId) ?? []) {
122
151
  const seen = new Set([classId]);
@@ -134,3 +163,40 @@ export function overrideReferences<Brand>(
134
163
  }
135
164
  return found;
136
165
  }
166
+
167
+ /**
168
+ * Per class of the target with a class declared outside the target in its `extends`
169
+ * chain, the names of the members that outside class's instances carry.
170
+ */
171
+ function outsideMemberNames<Brand>(
172
+ project: ProjectView<Brand>,
173
+ bases: ReadonlyMap<string, readonly string[]>,
174
+ outsideBases: ReadonlyMap<string, TSSymbol>,
175
+ ): ReadonlyMap<string, ReadonlySet<string>> {
176
+ const direct = new Map<string, ReadonlySet<string>>();
177
+ for (const [classId, base] of outsideBases) {
178
+ const type = project.queries.declaredTypeOf(base);
179
+ const properties = type === UNANSWERED ? UNANSWERED : project.queries.propertiesOf(type);
180
+ if (properties !== UNANSWERED) {
181
+ direct.set(classId, new Set(properties.map((property) => property.escapedName)));
182
+ }
183
+ }
184
+ const names = new Map<string, ReadonlySet<string>>();
185
+ const above = (classId: string, seen: Set<string>): ReadonlySet<string> => {
186
+ if (seen.has(classId)) {
187
+ return new Set();
188
+ }
189
+ seen.add(classId);
190
+ return new Set([
191
+ ...(direct.get(classId) ?? []),
192
+ ...(bases.get(classId) ?? []).flatMap((base) => [...above(base, seen)]),
193
+ ]);
194
+ };
195
+ for (const classId of bases.keys()) {
196
+ const found = above(classId, new Set());
197
+ if (found.size > 0) {
198
+ names.set(classId, found);
199
+ }
200
+ }
201
+ return names;
202
+ }
package/src/query.ts CHANGED
@@ -203,11 +203,17 @@ export interface Queries {
203
203
  constituents(type: Type): Answer<readonly Type[]>;
204
204
  /** The generic type a type reference instantiates; the type itself for another type. */
205
205
  targetOf(type: Type): Answer<Type>;
206
+ /** The constraint of a type parameter, where it declares one; none for another type. */
207
+ constraintOf(type: Type): Answer<Type | undefined>;
206
208
  /** The symbol a type is declared by. */
207
209
  symbolOfType(type: Type): Answer<TSSymbol | undefined>;
208
210
  signaturesOf(type: Type, kind: SignatureKind): Answer<readonly Signature[]>;
209
211
  resolvedSignature(node: Node): Answer<Signature | undefined>;
210
212
  parameterType(signature: Signature, index: number): Answer<Type | undefined>;
213
+ /** The generic signature an instantiated one instantiates; none for another signature. */
214
+ genericOf(signature: Signature): Answer<Signature | undefined>;
215
+ /** The type of the parameter an argument at one position binds, a rest element's included. */
216
+ typeAtPosition(signature: Signature, index: number): Answer<Type>;
211
217
  signatureOf(declaration: Node): Answer<Signature | undefined>;
212
218
  returnTypeOf(signature: Signature): Answer<Type | undefined>;
213
219
  typeToString(type: Type): Answer<string>;
@@ -502,6 +508,14 @@ export function queriesOf(
502
508
  () => type.getTarget(),
503
509
  )
504
510
  : type,
511
+ constraintOf: (type) =>
512
+ type.isTypeParameter()
513
+ ? one(
514
+ "getConstraint",
515
+ () => typeLocation(type),
516
+ () => type.getConstraint(),
517
+ )
518
+ : undefined,
505
519
  symbolOfType: (type) =>
506
520
  one(
507
521
  "getSymbol",
@@ -526,6 +540,20 @@ export function queriesOf(
526
540
  () => `signature ${String(signature.id)} parameter ${String(index)}`,
527
541
  () => checker.getParameterType(signature, index),
528
542
  ),
543
+ genericOf: (signature) =>
544
+ signature.target === undefined
545
+ ? undefined
546
+ : one(
547
+ "getTarget",
548
+ () => `signature ${String(signature.id)}`,
549
+ () => signature.getTarget(),
550
+ ),
551
+ typeAtPosition: (signature, index) =>
552
+ one(
553
+ "getTypeParameterAtPosition",
554
+ () => `signature ${String(signature.id)} position ${String(index)}`,
555
+ () => signature.getTypeParameterAtPosition(index),
556
+ ),
529
557
  signatureOf: (declaration) =>
530
558
  one(
531
559
  "getSignatureFromDeclaration",
package/src/ref.ts CHANGED
@@ -60,6 +60,7 @@ const REF_FORMS: readonly RegExp[] = REF_EXPRESSIONS.map(
60
60
 
61
61
  const IDENTIFIER = new RegExp(`^${TS_IDENT}$`, "u");
62
62
  const PRIVATE_NAME = new RegExp(`^#${TS_IDENT}$`, "u");
63
+ // Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
63
64
  const ASCII_WHITESPACE = /[\t\n\v\f\r ]/gu;
64
65
 
65
66
  /** The section of a manifest that declares a dependency. */
package/src/references.ts CHANGED
@@ -42,6 +42,9 @@ import { specifierCandidates } from "./configuration-files.ts";
42
42
  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
+ import { augmentationReferences } from "./augmentations.ts";
46
+ import { indexedAccessReferences } from "./indexed-access.ts";
47
+ import { moduleDeclarationReferences } from "./module-declarations.ts";
45
48
  import { overrideReferences } from "./overrides.ts";
46
49
  import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
47
50
  import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
@@ -1494,7 +1497,10 @@ export function references<Brand>(
1494
1497
 
1495
1498
  if (consumer === undefined) {
1496
1499
  found.push(...overrideReferences(project, held, chains, new Set(tests)));
1500
+ found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1501
+ found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1497
1502
  }
1503
+ found.push(...augmentationReferences(held, found));
1498
1504
  found.sort(
1499
1505
  (a, b) =>
1500
1506
  byPosition(a.position, b.position) ||
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 = "7.0.0";
22
+ const SCHEMA_VERSION = "8.0.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];
@@ -131,6 +131,11 @@ export interface WireTotals {
131
131
  readonly stale_suppressions: number;
132
132
  readonly pending: number;
133
133
  readonly omitted: number;
134
+ readonly withheld: {
135
+ readonly certain: number;
136
+ readonly probable: number;
137
+ readonly possible: number;
138
+ };
134
139
  }
135
140
 
136
141
  /** One project configuration of the matrix a run analyzed. */
@@ -438,6 +443,11 @@ function totalsOf(findings: readonly WireFinding[], result: PassResult): WireTot
438
443
  stale_suppressions: result.staleSuppressions.length,
439
444
  pending: result.totals.pending,
440
445
  omitted: 0,
446
+ withheld: {
447
+ certain: result.totals.withheld.certain,
448
+ probable: result.totals.withheld.probable,
449
+ possible: result.totals.withheld.possible,
450
+ },
441
451
  };
442
452
  }
443
453
 
@@ -5,6 +5,7 @@ import type { Report, WireFinding, WireStaleSuppression } from "../report.ts";
5
5
  import { lineHashes, symbolFingerprint } from "./fingerprint.ts";
6
6
  import { RenderError, type RenderOptions } from "./reporter.ts";
7
7
  import { utf8Bytes } from "./sha256.ts";
8
+ import { withheldLine } from "./text.ts";
8
9
 
9
10
  const SCHEMA =
10
11
  "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
@@ -271,6 +272,7 @@ export function sarif(report: Report, options: RenderOptions): Iterable<string>
271
272
  ...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
272
273
  ];
273
274
  const languages = [...report.analyzer.languages].sort().join("+");
275
+ const withheld = withheldLine(report.totals);
274
276
  const log = {
275
277
  $schema: SCHEMA,
276
278
  version: VERSION,
@@ -288,7 +290,8 @@ export function sarif(report: Report, options: RenderOptions): Iterable<string>
288
290
  columnKind: COLUMN_KIND,
289
291
  originalUriBaseIds: { [URI_BASE_ID]: { description: { text: ROOT_DESCRIPTION } } },
290
292
  results,
291
- properties: { totals: report.totals },
293
+ properties:
294
+ withheld === undefined ? { totals: report.totals } : { totals: report.totals, withheld },
292
295
  },
293
296
  ],
294
297
  };
@@ -24,11 +24,29 @@ function summary(totals: WireTotals): string {
24
24
  ].join(", ");
25
25
  }
26
26
 
27
+ /** The confidences a withheld line names, from the highest the minimum can withhold. */
28
+ const WITHHELD_ORDER = ["probable", "possible"] as const;
29
+
30
+ /**
31
+ * The line naming what the minimum confidence withheld and the setting that shows it all,
32
+ * or undefined where it withheld nothing.
33
+ */
34
+ export function withheldLine(totals: WireTotals): string | undefined {
35
+ const named = WITHHELD_ORDER.filter((confidence) => totals.withheld[confidence] > 0);
36
+ const lowest = named.at(-1);
37
+ if (lowest === undefined) {
38
+ return undefined;
39
+ }
40
+ const counts = named.map((confidence) => `${String(totals.withheld[confidence])} ${confidence}`);
41
+ return `withheld by analysis.min_confidence: ${counts.join(", ")}, shown with analysis.min_confidence set to ${lowest}`;
42
+ }
43
+
27
44
  /**
28
45
  * One line per finding in the report's order, then one per stale suppression, then the
29
- * summary. A finding line is `path:line:col: kind name: message [confidence] (CODE)`; the
30
- * summary is deliberately not in that shape, so a filter for finding lines yields exactly
31
- * them. Nothing else is written: no timestamp, duration or host detail.
46
+ * withheld line where the minimum withheld a finding, then the summary. A finding line is
47
+ * `path:line:col: kind name: message [confidence] (CODE)`; the summary is deliberately not
48
+ * in that shape, so a filter for finding lines yields exactly them. Nothing else is
49
+ * written: no timestamp, duration or host detail.
32
50
  */
33
51
  export const text = (report: Report): Iterable<string> =>
34
52
  coalesced(
@@ -39,6 +57,10 @@ export const text = (report: Report): Iterable<string> =>
39
57
  for (const stale of report.stale_suppressions) {
40
58
  yield `${stale.position.path}:${String(stale.position.line)}:${String(stale.position.column)}: ${STALE_KIND} ${stale.symbol}: ${stale.message} [${STALE_CONFIDENCE}] (${stale.code})\n`;
41
59
  }
60
+ const withheld = withheldLine(report.totals);
61
+ if (withheld !== undefined) {
62
+ yield `${withheld}\n`;
63
+ }
42
64
  yield `${summary(report.totals)}\n`;
43
65
  })(),
44
66
  );