@birdcc/core 0.0.1-alpha.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 (58) hide show
  1. package/.oxfmtrc.json +16 -0
  2. package/LICENSE +674 -0
  3. package/README.md +343 -0
  4. package/dist/cross-file.d.ts +5 -0
  5. package/dist/cross-file.d.ts.map +1 -0
  6. package/dist/cross-file.js +264 -0
  7. package/dist/cross-file.js.map +1 -0
  8. package/dist/index.d.ts +10 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +12 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/prefix.d.ts +2 -0
  13. package/dist/prefix.d.ts.map +1 -0
  14. package/dist/prefix.js +76 -0
  15. package/dist/prefix.js.map +1 -0
  16. package/dist/range.d.ts +3 -0
  17. package/dist/range.d.ts.map +1 -0
  18. package/dist/range.js +7 -0
  19. package/dist/range.js.map +1 -0
  20. package/dist/semantic-diagnostics.d.ts +4 -0
  21. package/dist/semantic-diagnostics.d.ts.map +1 -0
  22. package/dist/semantic-diagnostics.js +75 -0
  23. package/dist/semantic-diagnostics.js.map +1 -0
  24. package/dist/snapshot.d.ts +7 -0
  25. package/dist/snapshot.d.ts.map +1 -0
  26. package/dist/snapshot.js +22 -0
  27. package/dist/snapshot.js.map +1 -0
  28. package/dist/symbol-table.d.ts +9 -0
  29. package/dist/symbol-table.d.ts.map +1 -0
  30. package/dist/symbol-table.js +118 -0
  31. package/dist/symbol-table.js.map +1 -0
  32. package/dist/template-cycles.d.ts +9 -0
  33. package/dist/template-cycles.d.ts.map +1 -0
  34. package/dist/template-cycles.js +95 -0
  35. package/dist/template-cycles.js.map +1 -0
  36. package/dist/type-checker.d.ts +4 -0
  37. package/dist/type-checker.d.ts.map +1 -0
  38. package/dist/type-checker.js +390 -0
  39. package/dist/type-checker.js.map +1 -0
  40. package/dist/types.d.ts +84 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +2 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +45 -0
  45. package/scripts/benchmark-node-vs-regex.js +86 -0
  46. package/src/cross-file.ts +412 -0
  47. package/src/index.ts +42 -0
  48. package/src/prefix.ts +94 -0
  49. package/src/range.ts +12 -0
  50. package/src/semantic-diagnostics.ts +93 -0
  51. package/src/snapshot.ts +32 -0
  52. package/src/symbol-table.ts +171 -0
  53. package/src/template-cycles.ts +142 -0
  54. package/src/type-checker.ts +595 -0
  55. package/src/types.ts +101 -0
  56. package/test/core.test.ts +503 -0
  57. package/test/prefix.test.ts +40 -0
  58. package/tsconfig.json +8 -0
@@ -0,0 +1,595 @@
1
+ import { isIP } from "node:net";
2
+ import type { ParsedBirdDocument } from "@birdcc/parser";
3
+ import type {
4
+ BirdDiagnostic,
5
+ SymbolTable,
6
+ TypeCheckOptions,
7
+ TypeValue,
8
+ } from "./types.js";
9
+ import { isValidPrefixLiteral } from "./prefix.js";
10
+
11
+ const VARIABLE_DECLARE_PATTERN =
12
+ /^\s*(?:var\s+)?(int|bool|string|ip|prefix)\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s*=\s*(.+?))?\s*;?\s*$/i;
13
+ const VARIABLE_ASSIGN_PATTERN =
14
+ /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?)\s*;?\s*$/;
15
+ const MAX_TYPE_INFER_DEPTH = 64;
16
+ const MAX_TYPE_EXPRESSION_LENGTH = 4096;
17
+ const SET_LITERAL_MAX_ITEMS = 256;
18
+
19
+ const trimSingleEnclosingParentheses = (value: string): string => {
20
+ const current = value.trim();
21
+ if (!current.startsWith("(") || !current.endsWith(")")) {
22
+ return current;
23
+ }
24
+
25
+ let depth = 0;
26
+ for (let index = 0; index < current.length; index += 1) {
27
+ const char = current[index];
28
+ if (char === "(") {
29
+ depth += 1;
30
+ } else if (char === ")") {
31
+ depth -= 1;
32
+ if (depth < 0) {
33
+ return current;
34
+ }
35
+
36
+ if (depth === 0 && index < current.length - 1) {
37
+ return current;
38
+ }
39
+ }
40
+ }
41
+
42
+ return depth === 0 ? current.slice(1, -1).trim() : current;
43
+ };
44
+
45
+ const isQuotedLiteral = (value: string, quote: "'" | '"'): boolean => {
46
+ if (!value.startsWith(quote) || !value.endsWith(quote) || value.length < 2) {
47
+ return false;
48
+ }
49
+
50
+ let escaping = false;
51
+ for (let index = 1; index < value.length - 1; index += 1) {
52
+ const char = value[index];
53
+
54
+ if (escaping) {
55
+ escaping = false;
56
+ continue;
57
+ }
58
+
59
+ if (char === "\\") {
60
+ escaping = true;
61
+ continue;
62
+ }
63
+
64
+ if (char === quote) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ return true;
70
+ };
71
+
72
+ const splitTopLevelBinary = (
73
+ value: string,
74
+ operators: string[],
75
+ ): { left: string; operator: string; right: string } | null => {
76
+ let depth = 0;
77
+ let inSingleQuote = false;
78
+ let inDoubleQuote = false;
79
+ let escaping = false;
80
+
81
+ let lastMatch: { left: string; operator: string; right: string } | null =
82
+ null;
83
+
84
+ for (let index = 0; index < value.length; index += 1) {
85
+ const char = value[index];
86
+
87
+ if (escaping) {
88
+ escaping = false;
89
+ continue;
90
+ }
91
+
92
+ if (char === "\\") {
93
+ escaping = true;
94
+ continue;
95
+ }
96
+
97
+ if (!inDoubleQuote && char === "'") {
98
+ inSingleQuote = !inSingleQuote;
99
+ continue;
100
+ }
101
+
102
+ if (!inSingleQuote && char === '"') {
103
+ inDoubleQuote = !inDoubleQuote;
104
+ continue;
105
+ }
106
+
107
+ if (inSingleQuote || inDoubleQuote) {
108
+ continue;
109
+ }
110
+
111
+ if (char === "(") {
112
+ depth += 1;
113
+ continue;
114
+ }
115
+
116
+ if (char === ")") {
117
+ if (depth > 0) {
118
+ depth -= 1;
119
+ }
120
+ continue;
121
+ }
122
+
123
+ if (depth !== 0) {
124
+ continue;
125
+ }
126
+
127
+ for (const operator of operators) {
128
+ if (!value.startsWith(operator, index)) {
129
+ continue;
130
+ }
131
+
132
+ const left = value.slice(0, index).trim();
133
+ const right = value.slice(index + operator.length).trim();
134
+
135
+ if (left.length === 0 || right.length === 0) {
136
+ continue;
137
+ }
138
+
139
+ lastMatch = { left, operator, right };
140
+ }
141
+ }
142
+
143
+ return lastMatch;
144
+ };
145
+
146
+ const splitTopLevelList = (value: string): string[] => {
147
+ const items: string[] = [];
148
+ let depthParen = 0;
149
+ let depthBracket = 0;
150
+ let depthBrace = 0;
151
+ let inSingleQuote = false;
152
+ let inDoubleQuote = false;
153
+ let escaping = false;
154
+ let segmentStart = 0;
155
+
156
+ for (let index = 0; index < value.length; index += 1) {
157
+ const char = value[index];
158
+
159
+ if (escaping) {
160
+ escaping = false;
161
+ continue;
162
+ }
163
+
164
+ if (char === "\\") {
165
+ escaping = true;
166
+ continue;
167
+ }
168
+
169
+ if (!inDoubleQuote && char === "'") {
170
+ inSingleQuote = !inSingleQuote;
171
+ continue;
172
+ }
173
+
174
+ if (!inSingleQuote && char === '"') {
175
+ inDoubleQuote = !inDoubleQuote;
176
+ continue;
177
+ }
178
+
179
+ if (inSingleQuote || inDoubleQuote) {
180
+ continue;
181
+ }
182
+
183
+ switch (char) {
184
+ case "(":
185
+ depthParen += 1;
186
+ continue;
187
+ case ")":
188
+ if (depthParen > 0) {
189
+ depthParen -= 1;
190
+ }
191
+ continue;
192
+ case "[":
193
+ depthBracket += 1;
194
+ continue;
195
+ case "]":
196
+ if (depthBracket > 0) {
197
+ depthBracket -= 1;
198
+ }
199
+ continue;
200
+ case "{":
201
+ depthBrace += 1;
202
+ continue;
203
+ case "}":
204
+ if (depthBrace > 0) {
205
+ depthBrace -= 1;
206
+ }
207
+ continue;
208
+ case ",":
209
+ if (depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
210
+ items.push(value.slice(segmentStart, index).trim());
211
+ segmentStart = index + 1;
212
+ }
213
+ continue;
214
+ default:
215
+ break;
216
+ }
217
+ }
218
+
219
+ items.push(value.slice(segmentStart).trim());
220
+ return items;
221
+ };
222
+
223
+ const inferSetLiteralElementType = (
224
+ rawValue: string,
225
+ variableTypes: Map<string, TypeValue>,
226
+ depth: number,
227
+ ): TypeValue => {
228
+ if (!rawValue.startsWith("[") || !rawValue.endsWith("]")) {
229
+ return "unknown";
230
+ }
231
+
232
+ const inner = rawValue.slice(1, -1).trim();
233
+ if (inner.length === 0) {
234
+ return "unknown";
235
+ }
236
+
237
+ const items = splitTopLevelList(inner);
238
+ if (items.length > SET_LITERAL_MAX_ITEMS) {
239
+ return "unknown";
240
+ }
241
+
242
+ let elementType: TypeValue | null = null;
243
+
244
+ for (const item of items) {
245
+ if (item.length === 0) {
246
+ return "unknown";
247
+ }
248
+
249
+ const inferredItemType = inferValueType(item, variableTypes, depth + 1);
250
+ if (inferredItemType === "unknown" || inferredItemType === "bool") {
251
+ return "unknown";
252
+ }
253
+
254
+ if (elementType && inferredItemType !== elementType) {
255
+ return "unknown";
256
+ }
257
+
258
+ elementType = inferredItemType;
259
+ }
260
+
261
+ return elementType ?? "unknown";
262
+ };
263
+
264
+ const inferValueType = (
265
+ rawValue: string,
266
+ variableTypes: Map<string, TypeValue>,
267
+ depth = 0,
268
+ ): TypeValue => {
269
+ if (
270
+ depth > MAX_TYPE_INFER_DEPTH ||
271
+ rawValue.length > MAX_TYPE_EXPRESSION_LENGTH
272
+ ) {
273
+ return "unknown";
274
+ }
275
+
276
+ const value = rawValue.trim();
277
+ if (value.length === 0) {
278
+ return "unknown";
279
+ }
280
+
281
+ const stripped = trimSingleEnclosingParentheses(value);
282
+ if (stripped !== value) {
283
+ return inferValueType(stripped, variableTypes, depth + 1);
284
+ }
285
+
286
+ if (value.startsWith("!")) {
287
+ const operandType = inferValueType(
288
+ value.slice(1),
289
+ variableTypes,
290
+ depth + 1,
291
+ );
292
+ return operandType === "bool" ? "bool" : "unknown";
293
+ }
294
+
295
+ if (value.startsWith("-") || value.startsWith("+")) {
296
+ const operandType = inferValueType(
297
+ value.slice(1),
298
+ variableTypes,
299
+ depth + 1,
300
+ );
301
+ if (operandType === "int") {
302
+ return "int";
303
+ }
304
+ }
305
+
306
+ const lowered = value.toLowerCase();
307
+
308
+ if (lowered === "true" || lowered === "false") {
309
+ return "bool";
310
+ }
311
+
312
+ if (/^\d+$/.test(value)) {
313
+ return "int";
314
+ }
315
+
316
+ if (isQuotedLiteral(value, '"') || isQuotedLiteral(value, "'")) {
317
+ return "string";
318
+ }
319
+
320
+ if (isIP(value) !== 0) {
321
+ return "ip";
322
+ }
323
+
324
+ if (isValidPrefixLiteral(value)) {
325
+ return "prefix";
326
+ }
327
+
328
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
329
+ return variableTypes.get(value) ?? "unknown";
330
+ }
331
+
332
+ const logicalOrExpression = splitTopLevelBinary(value, ["||"]);
333
+ if (logicalOrExpression) {
334
+ const leftType = inferValueType(
335
+ logicalOrExpression.left,
336
+ variableTypes,
337
+ depth + 1,
338
+ );
339
+ const rightType = inferValueType(
340
+ logicalOrExpression.right,
341
+ variableTypes,
342
+ depth + 1,
343
+ );
344
+ return leftType === "bool" && rightType === "bool" ? "bool" : "unknown";
345
+ }
346
+
347
+ const logicalAndExpression = splitTopLevelBinary(value, ["&&"]);
348
+ if (logicalAndExpression) {
349
+ const leftType = inferValueType(
350
+ logicalAndExpression.left,
351
+ variableTypes,
352
+ depth + 1,
353
+ );
354
+ const rightType = inferValueType(
355
+ logicalAndExpression.right,
356
+ variableTypes,
357
+ depth + 1,
358
+ );
359
+ return leftType === "bool" && rightType === "bool" ? "bool" : "unknown";
360
+ }
361
+
362
+ const equalityExpression = splitTopLevelBinary(value, ["==", "!="]);
363
+ if (equalityExpression) {
364
+ const leftType = inferValueType(
365
+ equalityExpression.left,
366
+ variableTypes,
367
+ depth + 1,
368
+ );
369
+ const rightType = inferValueType(
370
+ equalityExpression.right,
371
+ variableTypes,
372
+ depth + 1,
373
+ );
374
+ return leftType !== "unknown" &&
375
+ rightType !== "unknown" &&
376
+ leftType === rightType
377
+ ? "bool"
378
+ : "unknown";
379
+ }
380
+
381
+ const relationalExpression = splitTopLevelBinary(value, [
382
+ "<=",
383
+ ">=",
384
+ "<",
385
+ ">",
386
+ ]);
387
+ if (relationalExpression) {
388
+ const leftType = inferValueType(
389
+ relationalExpression.left,
390
+ variableTypes,
391
+ depth + 1,
392
+ );
393
+ const rightType = inferValueType(
394
+ relationalExpression.right,
395
+ variableTypes,
396
+ depth + 1,
397
+ );
398
+ return leftType === "int" && rightType === "int" ? "bool" : "unknown";
399
+ }
400
+
401
+ const matchExpression = splitTopLevelBinary(value, ["!~", "~"]);
402
+ if (matchExpression) {
403
+ const leftType = inferValueType(
404
+ matchExpression.left,
405
+ variableTypes,
406
+ depth + 1,
407
+ );
408
+ if (leftType === "unknown") {
409
+ return "unknown";
410
+ }
411
+
412
+ const rightSetElementType = inferSetLiteralElementType(
413
+ matchExpression.right,
414
+ variableTypes,
415
+ depth + 1,
416
+ );
417
+ if (rightSetElementType !== "unknown") {
418
+ return leftType === rightSetElementType ? "bool" : "unknown";
419
+ }
420
+
421
+ return "unknown";
422
+ }
423
+
424
+ const additiveExpression = splitTopLevelBinary(value, ["+", "-"]);
425
+ if (additiveExpression) {
426
+ const leftType = inferValueType(
427
+ additiveExpression.left,
428
+ variableTypes,
429
+ depth + 1,
430
+ );
431
+ const rightType = inferValueType(
432
+ additiveExpression.right,
433
+ variableTypes,
434
+ depth + 1,
435
+ );
436
+ return leftType === "int" && rightType === "int" ? "int" : "unknown";
437
+ }
438
+
439
+ const multiplicativeExpression = splitTopLevelBinary(value, ["*", "/"]);
440
+ if (multiplicativeExpression) {
441
+ const leftType = inferValueType(
442
+ multiplicativeExpression.left,
443
+ variableTypes,
444
+ depth + 1,
445
+ );
446
+ const rightType = inferValueType(
447
+ multiplicativeExpression.right,
448
+ variableTypes,
449
+ depth + 1,
450
+ );
451
+ return leftType === "int" && rightType === "int" ? "int" : "unknown";
452
+ }
453
+
454
+ return "unknown";
455
+ };
456
+
457
+ const createTypeMismatchDiagnostic = (
458
+ expectedType: TypeValue,
459
+ actualType: TypeValue,
460
+ line: number,
461
+ column: number,
462
+ endLine: number,
463
+ endColumn: number,
464
+ variableName: string,
465
+ ): BirdDiagnostic => ({
466
+ code: "type/mismatch",
467
+ message: `Type mismatch for '${variableName}': expected ${expectedType}, got ${actualType}`,
468
+ severity: "error",
469
+ source: "core",
470
+ range: { line, column, endLine, endColumn },
471
+ });
472
+
473
+ export const checkTypes = (
474
+ program: ParsedBirdDocument["program"],
475
+ // Reserved for upcoming phases (cross-declaration and cross-file type rules).
476
+ _symbolTable: SymbolTable,
477
+ options: TypeCheckOptions = {},
478
+ ): BirdDiagnostic[] => {
479
+ const diagnostics: BirdDiagnostic[] = [];
480
+ const strictUnknownExpression = options.strictUnknownExpression ?? false;
481
+
482
+ for (const declaration of program.declarations) {
483
+ if (declaration.kind !== "filter" && declaration.kind !== "function") {
484
+ continue;
485
+ }
486
+
487
+ const variableTypes = new Map<string, TypeValue>();
488
+
489
+ for (const statement of declaration.statements) {
490
+ if (statement.kind !== "expression") {
491
+ if (
492
+ strictUnknownExpression &&
493
+ statement.kind === "return" &&
494
+ statement.valueText
495
+ ) {
496
+ const inferredType = inferValueType(
497
+ statement.valueText,
498
+ variableTypes,
499
+ );
500
+ if (inferredType === "unknown") {
501
+ diagnostics.push({
502
+ code: "type/unknown-expression",
503
+ message: `Cannot infer type for return expression '${statement.valueText}'`,
504
+ severity: "warning",
505
+ source: "core",
506
+ range: {
507
+ line: statement.line,
508
+ column: statement.column,
509
+ endLine: statement.endLine,
510
+ endColumn: statement.endColumn,
511
+ },
512
+ });
513
+ }
514
+ }
515
+
516
+ continue;
517
+ }
518
+
519
+ const expression = statement.expressionText.trim();
520
+ const declarationMatch = expression.match(VARIABLE_DECLARE_PATTERN);
521
+ if (declarationMatch) {
522
+ const declaredType = (
523
+ declarationMatch[1] ?? "unknown"
524
+ ).toLowerCase() as TypeValue;
525
+ const variableName = declarationMatch[2] ?? "";
526
+ const initializer = declarationMatch[3]?.trim();
527
+
528
+ if (variableName.length > 0) {
529
+ variableTypes.set(variableName, declaredType);
530
+ }
531
+
532
+ if (initializer) {
533
+ const inferredType = inferValueType(initializer, variableTypes);
534
+ if (inferredType !== "unknown" && inferredType !== declaredType) {
535
+ diagnostics.push(
536
+ createTypeMismatchDiagnostic(
537
+ declaredType,
538
+ inferredType,
539
+ statement.line,
540
+ statement.column,
541
+ statement.endLine,
542
+ statement.endColumn,
543
+ variableName,
544
+ ),
545
+ );
546
+ }
547
+ }
548
+
549
+ continue;
550
+ }
551
+
552
+ const assignMatch = expression.match(VARIABLE_ASSIGN_PATTERN);
553
+ if (!assignMatch) {
554
+ continue;
555
+ }
556
+
557
+ const variableName = assignMatch[1] ?? "";
558
+ const assignedValue = assignMatch[2] ?? "";
559
+ const expectedType = variableTypes.get(variableName);
560
+
561
+ if (!expectedType) {
562
+ diagnostics.push({
563
+ code: "type/undefined-variable",
564
+ message: `Assignment to undefined variable '${variableName}'`,
565
+ severity: "error",
566
+ source: "core",
567
+ range: {
568
+ line: statement.line,
569
+ column: statement.column,
570
+ endLine: statement.endLine,
571
+ endColumn: statement.endColumn,
572
+ },
573
+ });
574
+ continue;
575
+ }
576
+
577
+ const inferredType = inferValueType(assignedValue, variableTypes);
578
+ if (inferredType !== "unknown" && inferredType !== expectedType) {
579
+ diagnostics.push(
580
+ createTypeMismatchDiagnostic(
581
+ expectedType,
582
+ inferredType,
583
+ statement.line,
584
+ statement.column,
585
+ statement.endLine,
586
+ statement.endColumn,
587
+ variableName,
588
+ ),
589
+ );
590
+ }
591
+ }
592
+ }
593
+
594
+ return diagnostics;
595
+ };
package/src/types.ts ADDED
@@ -0,0 +1,101 @@
1
+ export type BirdDiagnosticSeverity = "error" | "warning" | "info";
2
+
3
+ export interface BirdRange {
4
+ line: number;
5
+ column: number;
6
+ endLine: number;
7
+ endColumn: number;
8
+ }
9
+
10
+ export interface BirdDiagnostic {
11
+ code: string;
12
+ message: string;
13
+ severity: BirdDiagnosticSeverity;
14
+ source: "parser" | "core" | "linter" | "bird";
15
+ range: BirdRange;
16
+ uri?: string;
17
+ }
18
+
19
+ export type BirdSymbolKind =
20
+ | "protocol"
21
+ | "template"
22
+ | "filter"
23
+ | "function"
24
+ | "table";
25
+
26
+ export interface SymbolDefinition {
27
+ kind: BirdSymbolKind;
28
+ name: string;
29
+ line: number;
30
+ column: number;
31
+ endLine: number;
32
+ endColumn: number;
33
+ uri: string;
34
+ }
35
+
36
+ export interface SymbolReference {
37
+ kind: "template";
38
+ name: string;
39
+ line: number;
40
+ column: number;
41
+ endLine: number;
42
+ endColumn: number;
43
+ uri: string;
44
+ }
45
+
46
+ export interface SymbolTable {
47
+ definitions: SymbolDefinition[];
48
+ references: SymbolReference[];
49
+ }
50
+
51
+ export type TypeValue = "int" | "bool" | "string" | "ip" | "prefix" | "unknown";
52
+
53
+ export interface TypeCheckOptions {
54
+ uri?: string;
55
+ strictUnknownExpression?: boolean;
56
+ }
57
+
58
+ export interface CoreSnapshot {
59
+ symbols: SymbolDefinition[];
60
+ references: SymbolReference[];
61
+ symbolTable: SymbolTable;
62
+ typeDiagnostics: BirdDiagnostic[];
63
+ diagnostics: BirdDiagnostic[];
64
+ }
65
+
66
+ export interface CrossFileDocumentInput {
67
+ uri: string;
68
+ text: string;
69
+ }
70
+
71
+ export interface CrossFileResolutionStats {
72
+ loadedFromMemory: number;
73
+ loadedFromFileSystem: number;
74
+ skippedByDepth: number;
75
+ skippedByFileLimit: number;
76
+ missingIncludes: number;
77
+ parsedCacheHits: number;
78
+ parsedCacheMisses: number;
79
+ }
80
+
81
+ export interface CrossFileResolveOptions {
82
+ entryUri: string;
83
+ documents?: CrossFileDocumentInput[];
84
+ maxDepth?: number;
85
+ maxFiles?: number;
86
+ loadFromFileSystem?: boolean;
87
+ workspaceRootUri?: string;
88
+ allowIncludeOutsideWorkspace?: boolean;
89
+ typeCheck?: TypeCheckOptions;
90
+ readFileText?: (uri: string) => Promise<string>;
91
+ }
92
+
93
+ export interface CrossFileResolutionResult {
94
+ entryUri: string;
95
+ visitedUris: string[];
96
+ symbolTable: SymbolTable;
97
+ snapshots: Record<string, CoreSnapshot>;
98
+ documents: Record<string, string>;
99
+ diagnostics: BirdDiagnostic[];
100
+ stats: CrossFileResolutionStats;
101
+ }