@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
@@ -0,0 +1,446 @@
1
+ /**
2
+ * The members an element access reads by the literal type of its index: `o[k]` with `k`
3
+ * typed `"a" | "b"` reads `a` and `b` of the type of `o`, an index typed `keyof T` reads
4
+ * every member of `T`, and an index typed as a type parameter reads what its constraint does. Where the index is a parameter of a function every call of which the
5
+ * target holds, the members are read at each call, by the type of the argument it passes.
6
+ */
7
+
8
+ import {
9
+ isArrayLiteralExpression,
10
+ isArrowFunction,
11
+ isBinaryExpression,
12
+ isCallExpression,
13
+ isElementAccessExpression,
14
+ isForInStatement,
15
+ isForOfStatement,
16
+ isFunctionDeclaration,
17
+ isFunctionExpression,
18
+ isIdentifier,
19
+ isNoSubstitutionTemplateLiteral,
20
+ isNumericLiteral,
21
+ isObjectLiteralExpression,
22
+ isParenthesizedExpression,
23
+ isPostfixUnaryExpression,
24
+ isPrefixUnaryExpression,
25
+ isPropertyAssignment,
26
+ isShorthandPropertyAssignment,
27
+ isSpreadAssignment,
28
+ isSpreadElement,
29
+ isStringLiteral,
30
+ isVariableDeclaration,
31
+ isVariableDeclarationList,
32
+ isVariableStatement,
33
+ NodeFlags,
34
+ SyntaxKind,
35
+ type CallExpression,
36
+ type ElementAccessExpression,
37
+ type Expression,
38
+ type Identifier,
39
+ type Node,
40
+ type ParameterDeclaration,
41
+ type SourceFile,
42
+ } from "@typescript/native/unstable/ast";
43
+ import type { Type } from "@typescript/native/unstable/sync";
44
+ import type { AliasChains } from "./alias-chain.ts";
45
+ import { nodeKey, type Inventory } from "./inventory.ts";
46
+ import { renderPosition } from "./position.ts";
47
+ import { UNANSWERED } from "./query.ts";
48
+ import type { Reference } from "./references.ts";
49
+ import type { ProjectView } from "./session.ts";
50
+
51
+ type Use = "read" | "write" | "both";
52
+
53
+ /** The assignment operators that read their target before they write it. */
54
+ const COMPOUND: ReadonlySet<SyntaxKind> = new Set([
55
+ SyntaxKind.PlusEqualsToken,
56
+ SyntaxKind.MinusEqualsToken,
57
+ SyntaxKind.AsteriskEqualsToken,
58
+ SyntaxKind.AsteriskAsteriskEqualsToken,
59
+ SyntaxKind.SlashEqualsToken,
60
+ SyntaxKind.PercentEqualsToken,
61
+ SyntaxKind.LessThanLessThanEqualsToken,
62
+ SyntaxKind.GreaterThanGreaterThanEqualsToken,
63
+ SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
64
+ SyntaxKind.AmpersandEqualsToken,
65
+ SyntaxKind.BarEqualsToken,
66
+ SyntaxKind.CaretEqualsToken,
67
+ SyntaxKind.AmpersandAmpersandEqualsToken,
68
+ SyntaxKind.BarBarEqualsToken,
69
+ SyntaxKind.QuestionQuestionEqualsToken,
70
+ ]);
71
+
72
+ /** How an expression is used where it is written: read, written, or both. */
73
+ function useOf(node: Node): Use {
74
+ let at = node;
75
+ while (isParenthesizedExpression(at.parent)) {
76
+ at = at.parent;
77
+ }
78
+ const parent = at.parent;
79
+ if (isBinaryExpression(parent) && parent.left === at) {
80
+ if (parent.operatorToken.kind === SyntaxKind.EqualsToken) {
81
+ return "write";
82
+ }
83
+ if (COMPOUND.has(parent.operatorToken.kind)) {
84
+ return "both";
85
+ }
86
+ }
87
+ if (
88
+ (isPrefixUnaryExpression(parent) || isPostfixUnaryExpression(parent)) &&
89
+ (parent.operator === SyntaxKind.PlusPlusToken || parent.operator === SyntaxKind.MinusMinusToken)
90
+ ) {
91
+ return "both";
92
+ }
93
+ return "read";
94
+ }
95
+
96
+ function unwrapped(node: Expression): Expression {
97
+ return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
98
+ }
99
+
100
+ /** Whether an index is a literal the checker resolves to a member by its own text. */
101
+ function isLiteralIndex(node: Expression): boolean {
102
+ const at = unwrapped(node);
103
+ return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at);
104
+ }
105
+
106
+ /** A function whose calls may decide an access, by the name its calls spell. */
107
+ interface Callable {
108
+ readonly name: Identifier;
109
+ readonly parameters: readonly ParameterDeclaration[];
110
+ readonly body: Node | undefined;
111
+ }
112
+
113
+ /**
114
+ * The function `node` is, where every call of it is written in its own file under its own
115
+ * name: a function declaration or a `const` initialized with an arrow function or a function
116
+ * expression, neither exported. A method may be dispatched to through a base class or an
117
+ * interface, so it is none.
118
+ */
119
+ function callableOf(node: Node): Callable | undefined {
120
+ const exported = (holder: Node): boolean =>
121
+ ((holder as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(
122
+ (modifier) =>
123
+ modifier.kind === SyntaxKind.ExportKeyword || modifier.kind === SyntaxKind.DefaultKeyword,
124
+ );
125
+ if (isFunctionDeclaration(node)) {
126
+ return node.name === undefined || exported(node)
127
+ ? undefined
128
+ : { name: node.name, parameters: node.parameters, body: node.body };
129
+ }
130
+ if (!isArrowFunction(node) && !isFunctionExpression(node)) {
131
+ return undefined;
132
+ }
133
+ const declaration = node.parent;
134
+ const list = declaration.parent;
135
+ if (
136
+ !isVariableDeclaration(declaration) ||
137
+ declaration.initializer !== node ||
138
+ !isIdentifier(declaration.name) ||
139
+ !isVariableDeclarationList(list) ||
140
+ (list.flags & NodeFlags.Const) === 0 ||
141
+ !isVariableStatement(list.parent) ||
142
+ exported(list.parent)
143
+ ) {
144
+ return undefined;
145
+ }
146
+ return { name: declaration.name, parameters: node.parameters, body: node.body };
147
+ }
148
+
149
+ /** The innermost function enclosing a node, or none. */
150
+ function enclosingFunction(node: Node): Node | undefined {
151
+ for (let at = node.parent as Node | undefined; at !== undefined; at = at.parent) {
152
+ if (
153
+ isFunctionDeclaration(at) ||
154
+ isArrowFunction(at) ||
155
+ isFunctionExpression(at) ||
156
+ at.kind === SyntaxKind.MethodDeclaration ||
157
+ at.kind === SyntaxKind.Constructor ||
158
+ at.kind === SyntaxKind.GetAccessor ||
159
+ at.kind === SyntaxKind.SetAccessor
160
+ ) {
161
+ return at;
162
+ }
163
+ }
164
+ return undefined;
165
+ }
166
+
167
+ /** Whether an assignment target, a destructuring pattern at any depth included, assigns a name. */
168
+ function targetAssigns(target: Node, name: string): boolean {
169
+ if (isParenthesizedExpression(target)) {
170
+ return targetAssigns(target.expression, name);
171
+ }
172
+ if (isIdentifier(target)) {
173
+ return target.text === name;
174
+ }
175
+ if (isArrayLiteralExpression(target)) {
176
+ return target.elements.some((element) => targetAssigns(element, name));
177
+ }
178
+ if (isObjectLiteralExpression(target)) {
179
+ return target.properties.some((property) =>
180
+ isPropertyAssignment(property)
181
+ ? targetAssigns(property.initializer, name)
182
+ : isShorthandPropertyAssignment(property)
183
+ ? isIdentifier(property.name) && property.name.text === name
184
+ : isSpreadAssignment(property) && targetAssigns(property.expression, name),
185
+ );
186
+ }
187
+ if (isSpreadElement(target)) {
188
+ return targetAssigns(target.expression, name);
189
+ }
190
+ // A pattern element with a default value, `[k = "a"] = …`, assigns its left side.
191
+ return (
192
+ isBinaryExpression(target) &&
193
+ target.operatorToken.kind === SyntaxKind.EqualsToken &&
194
+ targetAssigns(target.left, name)
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Whether a body assigns a name: by an assignment, a destructuring one included, an
200
+ * increment or a decrement, or as the target of a `for…in` or `for…of` loop.
201
+ */
202
+ function assigns(body: Node | undefined, name: string): boolean {
203
+ let found = false;
204
+ const visit = (node: Node): void => {
205
+ if (found) {
206
+ return;
207
+ }
208
+ const target =
209
+ isBinaryExpression(node) &&
210
+ (node.operatorToken.kind === SyntaxKind.EqualsToken || COMPOUND.has(node.operatorToken.kind))
211
+ ? node.left
212
+ : (isPrefixUnaryExpression(node) || isPostfixUnaryExpression(node)) &&
213
+ (node.operator === SyntaxKind.PlusPlusToken ||
214
+ node.operator === SyntaxKind.MinusMinusToken)
215
+ ? node.operand
216
+ : (isForInStatement(node) || isForOfStatement(node)) &&
217
+ node.initializer.kind !== SyntaxKind.VariableDeclarationList
218
+ ? node.initializer
219
+ : undefined;
220
+ found = target !== undefined && targetAssigns(target, name);
221
+ node.forEachChild(visit);
222
+ };
223
+ if (body !== undefined) {
224
+ visit(body);
225
+ }
226
+ return found;
227
+ }
228
+
229
+ /** One lookup: the index whose literal type names members of the object's type, read where and by whom. */
230
+ interface Lookup {
231
+ readonly file: SourceFile;
232
+ readonly from: string;
233
+ readonly at: Node;
234
+ readonly object: Expression;
235
+ readonly index: Expression;
236
+ readonly use: Use;
237
+ }
238
+
239
+ /** One access whose index may be a parameter decided at the function's calls. */
240
+ interface Candidate {
241
+ readonly file: SourceFile;
242
+ readonly from: string;
243
+ readonly access: ElementAccessExpression;
244
+ readonly use: Use;
245
+ }
246
+
247
+ /**
248
+ * The references the element accesses of one project's own files make to the members their
249
+ * index's literal type names, each made by the declaration holding the access or the call.
250
+ */
251
+ export function elementAccessReferences<Brand>(
252
+ project: ProjectView<Brand>,
253
+ held: Inventory,
254
+ chains: AliasChains,
255
+ root: string,
256
+ testFiles: ReadonlySet<string>,
257
+ ): readonly Reference[] {
258
+ const queries = project.queries;
259
+ const candidates: Candidate[] = [];
260
+ const calls = new Map<Node, { readonly file: SourceFile; readonly from: string }>();
261
+ const names: Identifier[] = [];
262
+
263
+ for (const file of project.ownSourceFiles()) {
264
+ const fileId = held.declarations.get(nodeKey(file, file));
265
+ if (fileId === undefined) {
266
+ continue;
267
+ }
268
+ const visit = (node: Node, from: string): void => {
269
+ const holder = held.declarations.get(nodeKey(file, node)) ?? from;
270
+ if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
271
+ candidates.push({ file, from: holder, access: node, use: useOf(node) });
272
+ }
273
+ if (isCallExpression(node)) {
274
+ calls.set(node, { file, from: holder });
275
+ }
276
+ if (isIdentifier(node)) {
277
+ names.push(node);
278
+ }
279
+ node.forEachChild((child) => {
280
+ visit(child, holder);
281
+ });
282
+ };
283
+ file.forEachChild((child) => {
284
+ visit(child, fileId);
285
+ });
286
+ }
287
+ if (candidates.length === 0) {
288
+ return [];
289
+ }
290
+
291
+ // A parameter index is decided at the calls only where the function is a callable every
292
+ // use of which is a call, so the symbols of the parameters and of every name are read.
293
+ const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
294
+ const byCallable = new Map<Identifier, Candidate[]>();
295
+ for (const candidate of candidates) {
296
+ const index = unwrapped(candidate.access.argumentExpression);
297
+ const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
298
+ const callable = fn === undefined ? undefined : callableOf(fn);
299
+ if (callable === undefined || !isIdentifier(index)) {
300
+ continue;
301
+ }
302
+ const at = callable.parameters.findIndex(
303
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text,
304
+ );
305
+ if (at < 0 || assigns(callable.body, index.text)) {
306
+ continue;
307
+ }
308
+ parameterOf.set(candidate, { callable, at });
309
+ byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
310
+ }
311
+ const callableNames = [...byCallable.keys()];
312
+ const spelled = new Set(callableNames.map((name) => name.text));
313
+ const sameName = names.filter((name) => spelled.has(name.text));
314
+ const symbols = queries.symbolsAt([...callableNames, ...sameName]);
315
+ const symbolOf = new Map<Node, unknown>();
316
+ [...callableNames, ...sameName].forEach((node, at) => {
317
+ const symbol = symbols[at];
318
+ if (symbol !== UNANSWERED && symbol !== undefined) {
319
+ symbolOf.set(node, symbol);
320
+ }
321
+ });
322
+ const callsOf = new Map<Node, CallExpression[] | undefined>();
323
+ for (const name of callableNames) {
324
+ const symbol = symbolOf.get(name);
325
+ let found: CallExpression[] | undefined = symbol === undefined ? undefined : [];
326
+ for (const use of sameName) {
327
+ if (found === undefined) {
328
+ break;
329
+ }
330
+ if (use === name || symbolOf.get(use) !== symbol) {
331
+ continue;
332
+ }
333
+ const parent = use.parent;
334
+ found =
335
+ isCallExpression(parent) && parent.expression === use ? [...found, parent] : undefined;
336
+ }
337
+ callsOf.set(name, found);
338
+ }
339
+
340
+ const lookups: Lookup[] = [];
341
+ for (const candidate of candidates) {
342
+ const deferred = parameterOf.get(candidate);
343
+ const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
344
+ if (deferred === undefined || sites === undefined) {
345
+ lookups.push({
346
+ file: candidate.file,
347
+ from: candidate.from,
348
+ at: candidate.access.argumentExpression,
349
+ object: candidate.access.expression,
350
+ index: candidate.access.argumentExpression,
351
+ use: candidate.use,
352
+ });
353
+ continue;
354
+ }
355
+ // An object that is a parameter too is read as the argument each call passes there.
356
+ const object = unwrapped(candidate.access.expression);
357
+ const objectAt = isIdentifier(object)
358
+ ? deferred.callable.parameters.findIndex(
359
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
360
+ )
361
+ : -1;
362
+ for (const call of sites) {
363
+ const parameter = deferred.callable.parameters[deferred.at];
364
+ const index = call.arguments[deferred.at] ?? parameter?.initializer;
365
+ const site = calls.get(call);
366
+ if (index === undefined || site === undefined) {
367
+ continue;
368
+ }
369
+ lookups.push({
370
+ file: site.file,
371
+ from: site.from,
372
+ at: call,
373
+ object:
374
+ (objectAt < 0 ? undefined : call.arguments[objectAt]) ?? candidate.access.expression,
375
+ index,
376
+ use: candidate.use,
377
+ });
378
+ }
379
+ }
380
+
381
+ const nodes = lookups.flatMap((one) => [one.index, one.object]);
382
+ const types = queries.typesAt(nodes);
383
+ const typeOf = (node: Node, at: number): Type | undefined => {
384
+ const type = types[at];
385
+ return nodes[at] === node && type !== UNANSWERED ? type : undefined;
386
+ };
387
+ // A type parameter's body serves every instantiation, so it reads what its constraint admits.
388
+ const keysOf = (type: Type): Type => {
389
+ if (!type.isTypeParameter()) {
390
+ return type;
391
+ }
392
+ const constraint = queries.constraintOf(type);
393
+ return constraint === UNANSWERED || constraint === undefined ? type : constraint;
394
+ };
395
+ const partsOf = (type: Type): readonly Type[] => {
396
+ const parts = queries.constituents(type);
397
+ return parts === UNANSWERED ? [type] : parts;
398
+ };
399
+
400
+ const found: Reference[] = [];
401
+ lookups.forEach((lookup, at) => {
402
+ const indexType = typeOf(lookup.index, 2 * at);
403
+ const objectType = typeOf(lookup.object, 2 * at + 1);
404
+ const literals = indexType === undefined ? undefined : literalNames(partsOf(keysOf(indexType)));
405
+ if (objectType === undefined || literals === undefined) {
406
+ return;
407
+ }
408
+ const position = renderPosition(lookup.file, root, lookup.at.getStart());
409
+ for (const part of partsOf(objectType)) {
410
+ const properties = queries.propertiesOf(part);
411
+ for (const property of properties === UNANSWERED ? [] : properties) {
412
+ if (!literals.has(property.escapedName)) {
413
+ continue;
414
+ }
415
+ for (const to of chains.declarationsOf(property)) {
416
+ for (const use of lookup.use === "both" ? (["read", "write"] as const) : [lookup.use]) {
417
+ found.push({
418
+ from: lookup.from,
419
+ to,
420
+ position,
421
+ use,
422
+ resolution: "keyed",
423
+ test: testFiles.has(position.path),
424
+ });
425
+ }
426
+ }
427
+ }
428
+ }
429
+ });
430
+ return found;
431
+ }
432
+
433
+ /** The names a union of literal types spells, or none where a constituent is no string or number literal. */
434
+ function literalNames(parts: readonly Type[]): ReadonlySet<string> | undefined {
435
+ const names = new Set<string>();
436
+ for (const part of parts) {
437
+ if (part.isStringLiteralType()) {
438
+ names.add(part.value);
439
+ } else if (part.isNumberLiteralType()) {
440
+ names.add(String(part.value));
441
+ } else {
442
+ return undefined;
443
+ }
444
+ }
445
+ return names;
446
+ }
@@ -64,7 +64,7 @@ export type EntryRule =
64
64
  | "workflow-step";
65
65
 
66
66
  /** One file a rule enters. */
67
- export interface EntryPoint {
67
+ interface EntryPoint {
68
68
  readonly file: SourceFile;
69
69
  readonly rule: EntryRule;
70
70
  /**