gofront 0.0.9 → 1.0.1
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.
- package/README.md +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +86 -0
- package/src/typechecker/types.js +1 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// Templ (.templ file) code-generation mixin for CodeGen.
|
|
2
|
+
// Mounts an HTML/expression tree onto a parent DOM node by emitting plain
|
|
3
|
+
// document.createElement / appendChild / setAttribute calls.
|
|
4
|
+
|
|
5
|
+
import { Parser } from "../parser/index.js";
|
|
6
|
+
import { T, Token } from "../tokens.js";
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
9
|
+
|
|
10
|
+
/** @type {ThisType<CodeGen>} */
|
|
11
|
+
export const templGenMethods = {
|
|
12
|
+
genTemplDecl(decl) {
|
|
13
|
+
const params = decl.params
|
|
14
|
+
.map((p, i) =>
|
|
15
|
+
p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
|
|
16
|
+
)
|
|
17
|
+
.join(", ");
|
|
18
|
+
this.line(`function ${decl.name}(${params}) {`);
|
|
19
|
+
this.indented(() => {
|
|
20
|
+
this.line(`return {Mount(___p) {`);
|
|
21
|
+
this.indented(() => {
|
|
22
|
+
this._genTemplNodes(decl.body, "___p");
|
|
23
|
+
});
|
|
24
|
+
this.line(`}};`);
|
|
25
|
+
});
|
|
26
|
+
this.line("}");
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Emit mount statements for an array of TemplNodes into parent variable `p`.
|
|
30
|
+
_genTemplNodes(nodes, p) {
|
|
31
|
+
if (!nodes || nodes.length === 0) return;
|
|
32
|
+
if (nodes.length === 1) {
|
|
33
|
+
this._genTemplNodeMount(nodes[0], p);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Wrap multiple root nodes in a fragment div? No — emit each directly.
|
|
37
|
+
for (const node of nodes) {
|
|
38
|
+
this._genTemplNodeMount(node, p);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Emit JS to mount a single TemplNode onto parent `p`.
|
|
43
|
+
_genTemplNodeMount(node, p) {
|
|
44
|
+
switch (node.kind) {
|
|
45
|
+
case "TemplElement":
|
|
46
|
+
return this._genTemplElement(node, p);
|
|
47
|
+
case "TemplText":
|
|
48
|
+
return this.line(
|
|
49
|
+
`${p}.appendChild(document.createTextNode(${JSON.stringify(node.value)}));`,
|
|
50
|
+
);
|
|
51
|
+
case "TemplExpr":
|
|
52
|
+
return this.line(
|
|
53
|
+
`${p}.appendChild(document.createTextNode(String(${this._genTemplTokenExpr(node.tokens)})));`,
|
|
54
|
+
);
|
|
55
|
+
case "TemplComponent":
|
|
56
|
+
return this._genTemplComponentMount(node, p);
|
|
57
|
+
case "TemplChildren":
|
|
58
|
+
// children is a variadic gom.Node param — mount each child
|
|
59
|
+
return this.line(
|
|
60
|
+
`if(typeof children!=="undefined")(Array.isArray(children)?children:[...children]).forEach(___c=>___c?.Mount?.(${p}));`,
|
|
61
|
+
);
|
|
62
|
+
case "TemplIf":
|
|
63
|
+
return this._genTemplIf(node, p);
|
|
64
|
+
case "TemplFor":
|
|
65
|
+
return this._genTemplFor(node, p);
|
|
66
|
+
case "TemplSwitch":
|
|
67
|
+
return this._genTemplSwitch(node, p);
|
|
68
|
+
default:
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
_genTemplComponentMount(node, p) {
|
|
74
|
+
// Special case: @templ.Raw(expr) → insertAdjacentHTML
|
|
75
|
+
const toks = node.tokens;
|
|
76
|
+
if (
|
|
77
|
+
toks.length >= 4 &&
|
|
78
|
+
toks[0]?.value === "templ" &&
|
|
79
|
+
toks[1]?.type === T.DOT &&
|
|
80
|
+
toks[2]?.value === "Raw" &&
|
|
81
|
+
toks[3]?.type === T.LPAREN
|
|
82
|
+
) {
|
|
83
|
+
const argTokens = toks.slice(4, toks.length - 1);
|
|
84
|
+
const argJs = this._genTemplTokenExpr(argTokens);
|
|
85
|
+
return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
|
|
86
|
+
}
|
|
87
|
+
return this.line(
|
|
88
|
+
`(${this._genTemplTokenCallExpr(node.tokens)}).Mount(${p});`,
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
_genTemplElement(node, p) {
|
|
93
|
+
const { tag, attrs, children } = node;
|
|
94
|
+
const elVar = this._freshVar("e");
|
|
95
|
+
this.line(
|
|
96
|
+
`const ${elVar} = document.createElement(${JSON.stringify(tag)});`,
|
|
97
|
+
);
|
|
98
|
+
for (const attr of attrs) {
|
|
99
|
+
this._genTemplAttr(attr, elVar);
|
|
100
|
+
}
|
|
101
|
+
this._genTemplNodes(children, elVar);
|
|
102
|
+
this.line(`${p}.appendChild(${elVar});`);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
_genTemplAttr(attr, el) {
|
|
106
|
+
const { name } = attr;
|
|
107
|
+
if (attr.kind === "static") {
|
|
108
|
+
if (name === "class") {
|
|
109
|
+
this.line(`${el}.className = ${JSON.stringify(attr.value)};`);
|
|
110
|
+
} else {
|
|
111
|
+
this.line(
|
|
112
|
+
`${el}.setAttribute(${JSON.stringify(name)}, ${JSON.stringify(attr.value)});`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
} else if (attr.kind === "expr") {
|
|
116
|
+
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
117
|
+
if (name === "class") {
|
|
118
|
+
this.line(`${el}.className = ${exprJs};`);
|
|
119
|
+
} else {
|
|
120
|
+
this.line(
|
|
121
|
+
`${el}.setAttribute(${JSON.stringify(name)}, String(${exprJs}));`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} else if (attr.kind === "bool") {
|
|
125
|
+
this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
|
|
126
|
+
} else if (attr.kind === "cond-bool") {
|
|
127
|
+
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
128
|
+
this.line(
|
|
129
|
+
`if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
_genTemplIf(node, p) {
|
|
135
|
+
const condJs = this._genTemplCondTokens(node.condTokens);
|
|
136
|
+
this.line(`if (${condJs}) {`);
|
|
137
|
+
this.indented(() => this._genTemplNodes(node.then, p));
|
|
138
|
+
this._genTemplIfElse(node.else_, p);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
_genTemplIfElse(else_, p) {
|
|
142
|
+
if (!else_ || else_.length === 0) {
|
|
143
|
+
this.line(`}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (else_.length === 1 && else_[0].kind === "TemplIf") {
|
|
147
|
+
const elseIf = else_[0];
|
|
148
|
+
this.line(`} else if (${this._genTemplCondTokens(elseIf.condTokens)}) {`);
|
|
149
|
+
this.indented(() => this._genTemplNodes(elseIf.then, p));
|
|
150
|
+
this._genTemplIfElse(elseIf.else_, p);
|
|
151
|
+
} else {
|
|
152
|
+
this.line(`} else {`);
|
|
153
|
+
this.indented(() => this._genTemplNodes(else_, p));
|
|
154
|
+
this.line(`}`);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
_genTemplSwitch(node, p) {
|
|
159
|
+
const { exprTokens, cases } = node;
|
|
160
|
+
if (cases.length === 0) return;
|
|
161
|
+
const exprJs = this._genTemplTokenExpr(exprTokens);
|
|
162
|
+
this.line(`switch (${exprJs}) {`);
|
|
163
|
+
this.indented(() => {
|
|
164
|
+
for (const c of cases) {
|
|
165
|
+
const label =
|
|
166
|
+
c.caseTokens === null
|
|
167
|
+
? "default"
|
|
168
|
+
: `case ${this._genTemplTokenExpr(c.caseTokens)}`;
|
|
169
|
+
this.line(`${label}: {`);
|
|
170
|
+
this.indented(() => this._genTemplNodes(c.body, p));
|
|
171
|
+
this.line(`break; }`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
this.line(`}`);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
_genTemplFor(node, p) {
|
|
178
|
+
// stmtTokens include the `for` keyword plus the range clause (no body).
|
|
179
|
+
// Strip any trailing semicolons (inserted by the Go lexer on newlines),
|
|
180
|
+
// then add a fake empty body `{ }` so the parser can complete the ForStmt.
|
|
181
|
+
let tokens = node.stmtTokens;
|
|
182
|
+
while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
|
|
183
|
+
tokens = tokens.slice(0, -1);
|
|
184
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
185
|
+
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
186
|
+
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
187
|
+
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
188
|
+
const withFor =
|
|
189
|
+
tokens[0]?.type === T.FOR
|
|
190
|
+
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
191
|
+
: [
|
|
192
|
+
new Token(T.FOR, "for", 1, 1),
|
|
193
|
+
...tokens,
|
|
194
|
+
lbrace,
|
|
195
|
+
rbrace,
|
|
196
|
+
semi,
|
|
197
|
+
eofTok,
|
|
198
|
+
];
|
|
199
|
+
try {
|
|
200
|
+
const p2 = new Parser(withFor);
|
|
201
|
+
const forStmt = p2.parseFor();
|
|
202
|
+
this._genTemplRangeFor(forStmt, node.body, p);
|
|
203
|
+
} catch {
|
|
204
|
+
// Fallback for non-range loops: emit a comment + skip
|
|
205
|
+
this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
_genTemplRangeFor(forStmt, templBody, mountParent) {
|
|
210
|
+
const init = forStmt.init;
|
|
211
|
+
if (!init?.rhs?.[0] || init.rhs[0].kind !== "RangeExpr") {
|
|
212
|
+
// Plain for loop (cond-only or three-clause)
|
|
213
|
+
const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
|
|
214
|
+
this.line(`while (${condJs}) {`);
|
|
215
|
+
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
216
|
+
this.line("}");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Range for: for key, val := range iter
|
|
220
|
+
const range = init.rhs[0];
|
|
221
|
+
const iterJs = this.genExpr(range.expr);
|
|
222
|
+
const lhs = init.lhs.map((e) => e.name ?? "_");
|
|
223
|
+
const keyName = lhs[0] ?? "_";
|
|
224
|
+
const valName = lhs[1] ?? null;
|
|
225
|
+
|
|
226
|
+
const loopHeader = this._genTemplRangeHeader(keyName, valName, iterJs);
|
|
227
|
+
this.line(loopHeader);
|
|
228
|
+
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
229
|
+
this.line("}");
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
_genTemplRangeHeader(keyName, valName, iterJs) {
|
|
233
|
+
if (valName === null || valName === undefined) {
|
|
234
|
+
return `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
|
|
235
|
+
}
|
|
236
|
+
if (keyName === "_" && valName === "_")
|
|
237
|
+
return `for (const _$ of ${iterJs}) {`;
|
|
238
|
+
if (keyName === "_") return `for (const ${valName} of ${iterJs}) {`;
|
|
239
|
+
if (valName === "_")
|
|
240
|
+
return `for (const [${keyName}] of Object.entries(${iterJs})) {`;
|
|
241
|
+
// Default to .entries() for arrays, Object.entries for objects
|
|
242
|
+
this._usesSliceGuard = true;
|
|
243
|
+
return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Parse a token array as a Go expression and emit JS.
|
|
247
|
+
_genTemplTokenExpr(tokens) {
|
|
248
|
+
if (!tokens || tokens.length === 0) return '""';
|
|
249
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
250
|
+
const p = new Parser([...tokens, eofTok]);
|
|
251
|
+
const ast = p.parseExpr();
|
|
252
|
+
return this.genExpr(ast);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Parse a token array as a Go call expression and emit JS.
|
|
256
|
+
_genTemplTokenCallExpr(tokens) {
|
|
257
|
+
if (!tokens || tokens.length === 0) return "null";
|
|
258
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
259
|
+
const p = new Parser([...tokens, eofTok]);
|
|
260
|
+
const ast = p.parseExpr();
|
|
261
|
+
return this.genExpr(ast);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// Extract condition JS from condTokens (which may start with `if`).
|
|
265
|
+
_genTemplCondTokens(tokens) {
|
|
266
|
+
if (!tokens || tokens.length === 0) return "false";
|
|
267
|
+
let toks = tokens;
|
|
268
|
+
if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
|
|
269
|
+
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
270
|
+
const p = new Parser([...toks, eofTok]);
|
|
271
|
+
const ast = p.parseExpr();
|
|
272
|
+
return this.genExpr(ast);
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
_tokensToSource(tokens) {
|
|
276
|
+
return tokens.map((t) => t.value).join(" ");
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Fresh variable name counter
|
|
280
|
+
_freshVar(prefix = "v") {
|
|
281
|
+
this._templVarCount = (this._templVarCount ?? 0) + 1;
|
|
282
|
+
return `___${prefix}${this._templVarCount}`;
|
|
283
|
+
},
|
|
284
|
+
};
|
package/src/compiler.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
//
|
|
1
|
+
// GoFront package compiler.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// compileSingleFile(path, options) — compile one .go file
|
|
4
|
+
// compileDir(dir, options) — compile all *.go in a directory as one package
|
|
5
|
+
// compileFiles(files, options) — compile an explicit list of .go files
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
+
// compileSingleFile returns: { js } (or { tokens } / { ast } for debug modes)
|
|
8
|
+
// compileDir / compileFiles return:
|
|
7
9
|
// {
|
|
8
10
|
// pkgName: string,
|
|
9
11
|
// js: string, // generated JS bundle (may include dep preamble)
|
|
@@ -17,14 +19,14 @@
|
|
|
17
19
|
|
|
18
20
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
19
21
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
20
|
-
import { CodeGen } from "./codegen.js";
|
|
22
|
+
import { CodeGen } from "./codegen/index.js";
|
|
21
23
|
import { parseDts } from "./dts-parser.js";
|
|
22
24
|
import { Lexer } from "./lexer.js";
|
|
23
|
-
import { Parser } from "./parser.js";
|
|
25
|
+
import { Parser } from "./parser/index.js";
|
|
24
26
|
import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
|
|
25
27
|
import { TemplLexer } from "./templ-lexer.js";
|
|
26
28
|
import { TemplParser } from "./templ-parser.js";
|
|
27
|
-
import { TypeChecker } from "./typechecker.js";
|
|
29
|
+
import { TypeChecker } from "./typechecker/index.js";
|
|
28
30
|
|
|
29
31
|
// ── Parse cache ──────────────────────────────────────────────
|
|
30
32
|
|
|
@@ -41,29 +43,20 @@ export function parseCacheSize() {
|
|
|
41
43
|
|
|
42
44
|
// ── Helpers ──────────────────────────────────────────────────
|
|
43
45
|
|
|
44
|
-
function
|
|
46
|
+
function parseGoFrontFile(filePath) {
|
|
45
47
|
const mtime = statSync(filePath).mtimeMs;
|
|
46
48
|
const cached = _parseCache.get(filePath);
|
|
47
49
|
if (cached && cached.mtime === mtime) return cached.ast;
|
|
48
50
|
|
|
49
51
|
const source = readFileSync(filePath, "utf8");
|
|
50
52
|
const filename = basename(filePath);
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
function parseTemplFile(filePath) {
|
|
59
|
-
const mtime = statSync(filePath).mtimeMs;
|
|
60
|
-
const cached = _parseCache.get(filePath);
|
|
61
|
-
if (cached && cached.mtime === mtime) return cached.ast;
|
|
62
|
-
|
|
63
|
-
const source = readFileSync(filePath, "utf8");
|
|
64
|
-
const filename = basename(filePath);
|
|
65
|
-
const tokens = new TemplLexer(source, filename).tokenize();
|
|
66
|
-
const ast = new TemplParser(tokens, filename, source).parse();
|
|
53
|
+
const isTempl = filePath.endsWith(".templ");
|
|
54
|
+
const tokens = isTempl
|
|
55
|
+
? new TemplLexer(source, filename).tokenize()
|
|
56
|
+
: new Lexer(source, filename).tokenize();
|
|
57
|
+
const ast = isTempl
|
|
58
|
+
? new TemplParser(tokens, filename, source).parse()
|
|
59
|
+
: new Parser(tokens, filename, source).parse();
|
|
67
60
|
ast._source = source;
|
|
68
61
|
_parseCache.set(filePath, { mtime, ast });
|
|
69
62
|
return ast;
|
|
@@ -76,53 +69,27 @@ function gwFilesIn(dir) {
|
|
|
76
69
|
.map((f) => join(dir, f));
|
|
77
70
|
}
|
|
78
71
|
|
|
79
|
-
// ──
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
|
|
84
|
-
return compileFiles(files, { ...options, fromDir: dir });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function compileFiles(files, options = {}) {
|
|
88
|
-
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
89
|
-
|
|
90
|
-
// ── 1. Parse ─────────────────────────────────────────────────
|
|
91
|
-
const parseErrors = [];
|
|
92
|
-
const programs = [];
|
|
93
|
-
for (const f of files) {
|
|
94
|
-
try {
|
|
95
|
-
programs.push(f.endsWith(".templ") ? parseTemplFile(f) : parseFile(f));
|
|
96
|
-
} catch (e) {
|
|
97
|
-
parseErrors.push(e.message);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (parseErrors.length > 0) {
|
|
101
|
-
throw new Error(parseErrors.join("\n"));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Validate consistent package name
|
|
105
|
-
const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
|
|
106
|
-
if (pkgNames.length > 1)
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
|
|
109
|
-
);
|
|
110
|
-
const pkgName = pkgNames[0];
|
|
111
|
-
|
|
112
|
-
// ── 2. Resolve imports ────────────────────────────────────────
|
|
113
|
-
const checker = new TypeChecker();
|
|
114
|
-
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
115
|
-
const bundledPackages = new Set(); // package names whose code is inlined
|
|
116
|
-
const preambles = []; // JS code from compiled sub-packages
|
|
72
|
+
// ── Import resolution ─────────────────────────────────────────
|
|
73
|
+
//
|
|
74
|
+
// Shared by compileFiles (multi-file) and the single-file path in index.js.
|
|
75
|
+
// Mutates checker, jsImports, bundledPackages, and preambles in place.
|
|
117
76
|
|
|
77
|
+
export function resolveImports(
|
|
78
|
+
programs,
|
|
79
|
+
fromFile,
|
|
80
|
+
checker,
|
|
81
|
+
jsImports,
|
|
82
|
+
bundledPackages,
|
|
83
|
+
preambles,
|
|
84
|
+
) {
|
|
85
|
+
const fromDir = dirname(resolve(fromFile));
|
|
118
86
|
const allImports = programs.flatMap((p) => p.imports);
|
|
119
|
-
const dummyFromFile = join(fromDir, "_dummy.go");
|
|
120
87
|
|
|
121
88
|
// js: prefix — local .d.ts files
|
|
122
89
|
for (const imp of allImports) {
|
|
123
90
|
for (const { path, alias } of imp.imports) {
|
|
124
91
|
if (!path.startsWith("js:")) continue;
|
|
125
|
-
if (alias === "_") continue;
|
|
92
|
+
if (alias === "_") continue;
|
|
126
93
|
const dtsPath = join(fromDir, path.slice(3));
|
|
127
94
|
try {
|
|
128
95
|
const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
|
|
@@ -133,12 +100,12 @@ export function compileFiles(files, options = {}) {
|
|
|
133
100
|
}
|
|
134
101
|
}
|
|
135
102
|
|
|
136
|
-
// npm packages — exclude side-effect imports
|
|
103
|
+
// npm packages — exclude side-effect imports from type resolution
|
|
137
104
|
const allImportsNoSideEffect = allImports.map((imp) => ({
|
|
138
105
|
...imp,
|
|
139
106
|
imports: imp.imports.filter(({ alias }) => alias !== "_"),
|
|
140
107
|
}));
|
|
141
|
-
const resolved = resolveAll(allImportsNoSideEffect,
|
|
108
|
+
const resolved = resolveAll(allImportsNoSideEffect, fromFile, parseDts);
|
|
142
109
|
for (const [path, info] of resolved) {
|
|
143
110
|
if (!info) continue;
|
|
144
111
|
checker.addDefinitions(info.types, info.values);
|
|
@@ -152,29 +119,20 @@ export function compileFiles(files, options = {}) {
|
|
|
152
119
|
for (const { path, alias, _line } of imp.imports) {
|
|
153
120
|
if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
|
|
154
121
|
seenLocalPaths.add(path);
|
|
155
|
-
|
|
156
|
-
const depDir = resolveGwDir(path, dummyFromFile);
|
|
122
|
+
const depDir = resolveGwDir(path, fromFile);
|
|
157
123
|
if (!depDir) {
|
|
158
124
|
console.error(
|
|
159
125
|
`gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
|
|
160
126
|
);
|
|
161
127
|
continue;
|
|
162
128
|
}
|
|
163
|
-
|
|
164
|
-
// Recursively compile dependency
|
|
165
129
|
const dep = compileDir(depDir);
|
|
166
130
|
preambles.push(dep.js);
|
|
167
|
-
if (alias === "_") continue;
|
|
168
|
-
|
|
169
|
-
// Dot import: flatten all exported symbols into global scope
|
|
131
|
+
if (alias === "_") continue;
|
|
170
132
|
if (alias === ".") {
|
|
171
133
|
checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
|
|
172
|
-
// All dep symbols must be treated as bundled so codegen
|
|
173
|
-
// can emit them without a qualifier. We DON'T add a
|
|
174
|
-
// namespace, so SelectorExpr never appears for these.
|
|
175
134
|
continue;
|
|
176
135
|
}
|
|
177
|
-
|
|
178
136
|
const nameUsed = alias ?? dep.pkgName;
|
|
179
137
|
bundledPackages.add(nameUsed);
|
|
180
138
|
checker.addPackageNamespace(
|
|
@@ -186,6 +144,113 @@ export function compileFiles(files, options = {}) {
|
|
|
186
144
|
}
|
|
187
145
|
}
|
|
188
146
|
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Main entry points ─────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
export function compileSingleFile(inputPath, options = {}) {
|
|
152
|
+
const {
|
|
153
|
+
sourceMap = false,
|
|
154
|
+
outputFile = null,
|
|
155
|
+
dumpTokens = false,
|
|
156
|
+
dumpAst = false,
|
|
157
|
+
} = options;
|
|
158
|
+
|
|
159
|
+
let source;
|
|
160
|
+
try {
|
|
161
|
+
source = readFileSync(inputPath, "utf8");
|
|
162
|
+
} catch (e) {
|
|
163
|
+
throw new Error(`cannot read '${inputPath}': ${e.message}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const tokens = new Lexer(source, basename(inputPath)).tokenize();
|
|
167
|
+
if (dumpTokens) return { tokens };
|
|
168
|
+
|
|
169
|
+
const ast = new Parser(tokens, basename(inputPath), source).parse();
|
|
170
|
+
ast._source = source;
|
|
171
|
+
if (dumpAst) return { ast };
|
|
172
|
+
|
|
173
|
+
const checker = new TypeChecker();
|
|
174
|
+
const jsImports = new Map();
|
|
175
|
+
const bundledPackages = new Set();
|
|
176
|
+
const preambles = [];
|
|
177
|
+
|
|
178
|
+
resolveImports(
|
|
179
|
+
[ast],
|
|
180
|
+
inputPath,
|
|
181
|
+
checker,
|
|
182
|
+
jsImports,
|
|
183
|
+
bundledPackages,
|
|
184
|
+
preambles,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const errors = checker.check(ast);
|
|
188
|
+
checker.reportUnusedImports();
|
|
189
|
+
if (errors.length > 0) {
|
|
190
|
+
throw new Error(errors.map((e) => e.message).join("\n"));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cg = new CodeGen(checker, jsImports, bundledPackages);
|
|
194
|
+
let js = cg.generate(ast);
|
|
195
|
+
|
|
196
|
+
if (sourceMap) {
|
|
197
|
+
const outputDir = outputFile ? dirname(resolve(outputFile)) : resolve(".");
|
|
198
|
+
const srcPath = relative(outputDir, inputPath);
|
|
199
|
+
const map = cg.getSourceMap(srcPath, [source]);
|
|
200
|
+
const b64 = Buffer.from(map).toString("base64");
|
|
201
|
+
js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const output = preambles.length > 0 ? `${preambles.join("\n")}\n${js}` : js;
|
|
205
|
+
return { js: output };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function compileDir(dir, options = {}) {
|
|
209
|
+
const files = gwFilesIn(dir);
|
|
210
|
+
if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
|
|
211
|
+
return compileFiles(files, { ...options, fromDir: dir });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function compileFiles(files, options = {}) {
|
|
215
|
+
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
216
|
+
|
|
217
|
+
// ── 1. Parse ─────────────────────────────────────────────────
|
|
218
|
+
const parseErrors = [];
|
|
219
|
+
const programs = [];
|
|
220
|
+
for (const f of files) {
|
|
221
|
+
try {
|
|
222
|
+
programs.push(parseGoFrontFile(f));
|
|
223
|
+
} catch (e) {
|
|
224
|
+
parseErrors.push(e.message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (parseErrors.length > 0) {
|
|
228
|
+
throw new Error(parseErrors.join("\n"));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Validate consistent package name
|
|
232
|
+
const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
|
|
233
|
+
if (pkgNames.length > 1)
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
|
|
236
|
+
);
|
|
237
|
+
const pkgName = pkgNames[0];
|
|
238
|
+
|
|
239
|
+
// ── 2. Resolve imports ────────────────────────────────────────
|
|
240
|
+
const checker = new TypeChecker();
|
|
241
|
+
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
242
|
+
const bundledPackages = new Set(); // package names whose code is inlined
|
|
243
|
+
const preambles = []; // JS code from compiled sub-packages
|
|
244
|
+
|
|
245
|
+
const dummyFromFile = join(fromDir, "_dummy.go");
|
|
246
|
+
resolveImports(
|
|
247
|
+
programs,
|
|
248
|
+
dummyFromFile,
|
|
249
|
+
checker,
|
|
250
|
+
jsImports,
|
|
251
|
+
bundledPackages,
|
|
252
|
+
preambles,
|
|
253
|
+
);
|
|
189
254
|
|
|
190
255
|
// ── 3. Type-check ─────────────────────────────────────────────
|
|
191
256
|
const errors = checker.checkAll(programs);
|