@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,171 @@
1
+ import type { ParsedBirdDocument } from "@birdcc/parser";
2
+ import type {
3
+ BirdDiagnostic,
4
+ BirdSymbolKind,
5
+ SymbolDefinition,
6
+ SymbolReference,
7
+ SymbolTable,
8
+ } from "./types.js";
9
+ import { createRange } from "./range.js";
10
+
11
+ export const DEFAULT_DOCUMENT_URI = "memory://bird.conf";
12
+
13
+ const declarationToSymbol = (
14
+ declaration: ParsedBirdDocument["program"]["declarations"][number],
15
+ uri: string,
16
+ ): SymbolDefinition | null => {
17
+ const toSymbol = (
18
+ kind: BirdSymbolKind,
19
+ name: string,
20
+ nameRange: {
21
+ line: number;
22
+ column: number;
23
+ endLine: number;
24
+ endColumn: number;
25
+ },
26
+ ): SymbolDefinition => ({
27
+ kind,
28
+ name,
29
+ line: nameRange.line,
30
+ column: nameRange.column,
31
+ endLine: nameRange.endLine,
32
+ endColumn: nameRange.endColumn,
33
+ uri,
34
+ });
35
+
36
+ if (declaration.kind === "protocol") {
37
+ return toSymbol("protocol", declaration.name, declaration.nameRange);
38
+ }
39
+
40
+ if (declaration.kind === "template") {
41
+ return toSymbol("template", declaration.name, declaration.nameRange);
42
+ }
43
+
44
+ if (declaration.kind === "filter") {
45
+ return toSymbol("filter", declaration.name, declaration.nameRange);
46
+ }
47
+
48
+ if (declaration.kind === "function") {
49
+ return toSymbol("function", declaration.name, declaration.nameRange);
50
+ }
51
+
52
+ if (declaration.kind === "table") {
53
+ return toSymbol("table", declaration.name, declaration.nameRange);
54
+ }
55
+
56
+ return null;
57
+ };
58
+
59
+ export const buildSymbolTableFromParsed = (
60
+ parsed: ParsedBirdDocument,
61
+ options: { uri?: string } = {},
62
+ ): SymbolTable => {
63
+ const uri = options.uri ?? DEFAULT_DOCUMENT_URI;
64
+ const definitions: SymbolDefinition[] = [];
65
+ const references: SymbolReference[] = [];
66
+
67
+ for (const declaration of parsed.program.declarations) {
68
+ const symbol = declarationToSymbol(declaration, uri);
69
+ if (symbol) {
70
+ definitions.push(symbol);
71
+ }
72
+
73
+ if (declaration.kind === "protocol" && declaration.fromTemplate) {
74
+ const range = declaration.fromTemplateRange ?? {
75
+ line: declaration.line,
76
+ column: declaration.column,
77
+ endLine: declaration.endLine,
78
+ endColumn: declaration.endColumn,
79
+ };
80
+
81
+ references.push({
82
+ kind: "template",
83
+ name: declaration.fromTemplate,
84
+ line: range.line,
85
+ column: range.column,
86
+ endLine: range.endLine,
87
+ endColumn: range.endColumn,
88
+ uri,
89
+ });
90
+ }
91
+
92
+ if (declaration.kind === "template" && declaration.fromTemplate) {
93
+ const range = declaration.fromTemplateRange ?? {
94
+ line: declaration.line,
95
+ column: declaration.column,
96
+ endLine: declaration.endLine,
97
+ endColumn: declaration.endColumn,
98
+ };
99
+
100
+ references.push({
101
+ kind: "template",
102
+ name: declaration.fromTemplate,
103
+ line: range.line,
104
+ column: range.column,
105
+ endLine: range.endLine,
106
+ endColumn: range.endColumn,
107
+ uri,
108
+ });
109
+ }
110
+ }
111
+
112
+ return { definitions, references };
113
+ };
114
+
115
+ export const mergeSymbolTables = (tables: SymbolTable[]): SymbolTable => {
116
+ const definitions: SymbolDefinition[] = [];
117
+ const references: SymbolReference[] = [];
118
+
119
+ for (const table of tables) {
120
+ definitions.push(...table.definitions);
121
+ references.push(...table.references);
122
+ }
123
+
124
+ return { definitions, references };
125
+ };
126
+
127
+ export const pushSymbolTableDiagnostics = (
128
+ symbolTable: SymbolTable,
129
+ diagnostics: BirdDiagnostic[],
130
+ ): void => {
131
+ const seenDefinitions = new Map<string, SymbolDefinition>();
132
+
133
+ for (const symbol of symbolTable.definitions) {
134
+ const key = `${symbol.kind}:${symbol.name.toLowerCase()}`;
135
+ const previous = seenDefinitions.get(key);
136
+
137
+ if (previous) {
138
+ diagnostics.push({
139
+ code: "semantic/duplicate-definition",
140
+ message: `${symbol.kind} '${symbol.name}' is already defined`,
141
+ severity: "error",
142
+ source: "core",
143
+ uri: symbol.uri,
144
+ range: createRange(symbol.line, symbol.column, symbol.name.length),
145
+ });
146
+ continue;
147
+ }
148
+
149
+ seenDefinitions.set(key, symbol);
150
+ }
151
+
152
+ for (const reference of symbolTable.references) {
153
+ const key = `template:${reference.name.toLowerCase()}`;
154
+ if (seenDefinitions.has(key)) {
155
+ continue;
156
+ }
157
+
158
+ diagnostics.push({
159
+ code: "semantic/undefined-reference",
160
+ message: `Undefined template reference '${reference.name}'`,
161
+ severity: "error",
162
+ source: "core",
163
+ uri: reference.uri,
164
+ range: createRange(
165
+ reference.line,
166
+ reference.column,
167
+ reference.name.length,
168
+ ),
169
+ });
170
+ }
171
+ };
@@ -0,0 +1,142 @@
1
+ import type { ParsedBirdDocument, SourceRange } from "@birdcc/parser";
2
+ import type { BirdDiagnostic } from "./types.js";
3
+
4
+ interface ParsedDocumentWithUri {
5
+ uri: string;
6
+ parsed: ParsedBirdDocument;
7
+ }
8
+
9
+ interface TemplateInheritanceNode {
10
+ uri: string;
11
+ name: string;
12
+ key: string;
13
+ parentName?: string;
14
+ parentKey?: string;
15
+ parentRange: SourceRange;
16
+ }
17
+
18
+ const createNode = (
19
+ uri: string,
20
+ declaration: Extract<
21
+ ParsedBirdDocument["program"]["declarations"][number],
22
+ { kind: "template" }
23
+ >,
24
+ ): TemplateInheritanceNode => ({
25
+ uri,
26
+ name: declaration.name,
27
+ key: declaration.name.toLowerCase(),
28
+ parentName: declaration.fromTemplate,
29
+ parentKey: declaration.fromTemplate?.toLowerCase(),
30
+ parentRange: declaration.fromTemplateRange ?? declaration.nameRange,
31
+ });
32
+
33
+ const collectTemplateInheritanceNodes = (
34
+ parsedDocuments: ParsedDocumentWithUri[],
35
+ ): Map<string, TemplateInheritanceNode> => {
36
+ const nodes = new Map<string, TemplateInheritanceNode>();
37
+
38
+ for (const document of parsedDocuments) {
39
+ const { uri, parsed } = document;
40
+ for (const declaration of parsed.program.declarations) {
41
+ if (declaration.kind !== "template") {
42
+ continue;
43
+ }
44
+
45
+ const node = createNode(uri, declaration);
46
+ if (!nodes.has(node.key)) {
47
+ nodes.set(node.key, node);
48
+ }
49
+ }
50
+ }
51
+
52
+ return nodes;
53
+ };
54
+
55
+ const normalizeParsedDocuments = (
56
+ parsedDocuments: ParsedBirdDocument[] | ParsedDocumentWithUri[],
57
+ ): ParsedDocumentWithUri[] => {
58
+ if (parsedDocuments.length === 0) {
59
+ return [];
60
+ }
61
+
62
+ const first = parsedDocuments[0];
63
+ if ("parsed" in first && "uri" in first) {
64
+ return parsedDocuments as ParsedDocumentWithUri[];
65
+ }
66
+
67
+ return (parsedDocuments as ParsedBirdDocument[]).map((parsed, index) => ({
68
+ uri: `memory://document-${index + 1}.conf`,
69
+ parsed,
70
+ }));
71
+ };
72
+
73
+ export const collectCircularTemplateDiagnostics = (
74
+ parsedDocuments: ParsedBirdDocument[] | ParsedDocumentWithUri[],
75
+ ): BirdDiagnostic[] => {
76
+ const normalizedDocuments = normalizeParsedDocuments(parsedDocuments);
77
+ const nodes = collectTemplateInheritanceNodes(normalizedDocuments);
78
+ const diagnostics: BirdDiagnostic[] = [];
79
+ const state = new Map<string, 0 | 1 | 2>();
80
+ const stack: string[] = [];
81
+ const emitted = new Set<string>();
82
+
83
+ const visit = (key: string): void => {
84
+ state.set(key, 1);
85
+ stack.push(key);
86
+
87
+ const node = nodes.get(key);
88
+ const parentKey = node?.parentKey;
89
+
90
+ if (node && parentKey && nodes.has(parentKey)) {
91
+ const parentState = state.get(parentKey) ?? 0;
92
+
93
+ if (parentState === 0) {
94
+ visit(parentKey);
95
+ } else if (parentState === 1) {
96
+ const startIndex = stack.lastIndexOf(parentKey);
97
+ if (startIndex >= 0) {
98
+ const cycleKeys = [...stack.slice(startIndex), parentKey];
99
+ const cycleSignature = cycleKeys
100
+ .slice(0, -1)
101
+ .map((entry) => entry.toLowerCase())
102
+ .sort()
103
+ .join("->");
104
+
105
+ if (!emitted.has(cycleSignature)) {
106
+ emitted.add(cycleSignature);
107
+ const cycleNames = cycleKeys.map(
108
+ (entry) => nodes.get(entry)?.name ?? entry,
109
+ );
110
+
111
+ diagnostics.push({
112
+ code: "semantic/circular-template",
113
+ message: `Circular template inheritance detected: ${cycleNames.join(" -> ")}`,
114
+ severity: "error",
115
+ source: "core",
116
+ uri: node.uri,
117
+ range: {
118
+ line: node.parentRange.line,
119
+ column: node.parentRange.column,
120
+ endLine: node.parentRange.endLine,
121
+ endColumn: node.parentRange.endColumn,
122
+ },
123
+ });
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ stack.pop();
130
+ state.set(key, 2);
131
+ };
132
+
133
+ for (const key of nodes.keys()) {
134
+ if ((state.get(key) ?? 0) !== 0) {
135
+ continue;
136
+ }
137
+
138
+ visit(key);
139
+ }
140
+
141
+ return diagnostics;
142
+ };