gofront 0.0.8 → 1.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.
- package/README.md +88 -12
- package/package.json +8 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/{codegen.js → codegen/index.js} +155 -184
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +102 -62
- package/src/dts-parser.js +355 -371
- package/src/index.js +22 -54
- package/src/lexer.js +200 -286
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +54 -36
- package/src/{parser.js → parser/index.js} +12 -5
- package/src/parser/statements.js +29 -35
- package/src/parser/types.js +31 -29
- package/src/templ-lexer.js +737 -0
- package/src/templ-parser.js +235 -0
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -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 +83 -0
- package/src/typechecker/types.js +6 -3
- package/src/typechecker.js +0 -1271
package/src/compiler.js
CHANGED
|
@@ -15,80 +15,79 @@
|
|
|
15
15
|
// Cross-package access uses the qualified form: `pkg.Foo`. The codegen
|
|
16
16
|
// de-qualifies it because the dependency is inlined.
|
|
17
17
|
|
|
18
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
18
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
19
19
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
20
|
-
import { CodeGen } from "./codegen.js";
|
|
20
|
+
import { CodeGen } from "./codegen/index.js";
|
|
21
21
|
import { parseDts } from "./dts-parser.js";
|
|
22
22
|
import { Lexer } from "./lexer.js";
|
|
23
|
-
import { Parser } from "./parser.js";
|
|
23
|
+
import { Parser } from "./parser/index.js";
|
|
24
24
|
import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
|
|
25
|
-
import {
|
|
25
|
+
import { TemplLexer } from "./templ-lexer.js";
|
|
26
|
+
import { TemplParser } from "./templ-parser.js";
|
|
27
|
+
import { TypeChecker } from "./typechecker/index.js";
|
|
28
|
+
|
|
29
|
+
// ── Parse cache ──────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
// Map<filePath, { mtime: number, ast: object }>
|
|
32
|
+
const _parseCache = new Map();
|
|
33
|
+
|
|
34
|
+
export function clearParseCache() {
|
|
35
|
+
_parseCache.clear();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseCacheSize() {
|
|
39
|
+
return _parseCache.size;
|
|
40
|
+
}
|
|
26
41
|
|
|
27
42
|
// ── Helpers ──────────────────────────────────────────────────
|
|
28
43
|
|
|
29
|
-
function
|
|
44
|
+
function parseGoFrontFile(filePath) {
|
|
45
|
+
const mtime = statSync(filePath).mtimeMs;
|
|
46
|
+
const cached = _parseCache.get(filePath);
|
|
47
|
+
if (cached && cached.mtime === mtime) return cached.ast;
|
|
48
|
+
|
|
30
49
|
const source = readFileSync(filePath, "utf8");
|
|
31
50
|
const filename = basename(filePath);
|
|
32
|
-
const
|
|
33
|
-
const
|
|
51
|
+
const isTempl = filePath.endsWith(".templ");
|
|
52
|
+
const tokens = isTempl
|
|
53
|
+
? new TemplLexer(source, filename).tokenize()
|
|
54
|
+
: new Lexer(source, filename).tokenize();
|
|
55
|
+
const ast = isTempl
|
|
56
|
+
? new TemplParser(tokens, filename, source).parse()
|
|
57
|
+
: new Parser(tokens, filename, source).parse();
|
|
34
58
|
ast._source = source;
|
|
59
|
+
_parseCache.set(filePath, { mtime, ast });
|
|
35
60
|
return ast;
|
|
36
61
|
}
|
|
37
62
|
|
|
38
63
|
function gwFilesIn(dir) {
|
|
39
64
|
return readdirSync(dir)
|
|
40
|
-
.filter((f) => f.endsWith(".go"))
|
|
65
|
+
.filter((f) => f.endsWith(".go") || f.endsWith(".templ"))
|
|
41
66
|
.sort() // deterministic order
|
|
42
67
|
.map((f) => join(dir, f));
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
// ──
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
for (const f of files) {
|
|
60
|
-
try {
|
|
61
|
-
programs.push(parseFile(f));
|
|
62
|
-
} catch (e) {
|
|
63
|
-
parseErrors.push(e.message);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (parseErrors.length > 0) {
|
|
67
|
-
throw new Error(parseErrors.join("\n"));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Validate consistent package name
|
|
71
|
-
const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
|
|
72
|
-
if (pkgNames.length > 1)
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
|
|
75
|
-
);
|
|
76
|
-
const pkgName = pkgNames[0];
|
|
77
|
-
|
|
78
|
-
// ── 2. Resolve imports ────────────────────────────────────────
|
|
79
|
-
const checker = new TypeChecker();
|
|
80
|
-
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
81
|
-
const bundledPackages = new Set(); // package names whose code is inlined
|
|
82
|
-
const preambles = []; // JS code from compiled sub-packages
|
|
83
|
-
|
|
70
|
+
// ── Import resolution ─────────────────────────────────────────
|
|
71
|
+
//
|
|
72
|
+
// Shared by compileFiles (multi-file) and the single-file path in index.js.
|
|
73
|
+
// Mutates checker, jsImports, bundledPackages, and preambles in place.
|
|
74
|
+
|
|
75
|
+
export function resolveImports(
|
|
76
|
+
programs,
|
|
77
|
+
fromFile,
|
|
78
|
+
checker,
|
|
79
|
+
jsImports,
|
|
80
|
+
bundledPackages,
|
|
81
|
+
preambles,
|
|
82
|
+
) {
|
|
83
|
+
const fromDir = dirname(resolve(fromFile));
|
|
84
84
|
const allImports = programs.flatMap((p) => p.imports);
|
|
85
|
-
const dummyFromFile = join(fromDir, "_dummy.go");
|
|
86
85
|
|
|
87
86
|
// js: prefix — local .d.ts files
|
|
88
87
|
for (const imp of allImports) {
|
|
89
88
|
for (const { path, alias } of imp.imports) {
|
|
90
89
|
if (!path.startsWith("js:")) continue;
|
|
91
|
-
if (alias === "_") continue;
|
|
90
|
+
if (alias === "_") continue;
|
|
92
91
|
const dtsPath = join(fromDir, path.slice(3));
|
|
93
92
|
try {
|
|
94
93
|
const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
|
|
@@ -99,12 +98,12 @@ export function compileFiles(files, options = {}) {
|
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
// npm packages — exclude side-effect imports
|
|
101
|
+
// npm packages — exclude side-effect imports from type resolution
|
|
103
102
|
const allImportsNoSideEffect = allImports.map((imp) => ({
|
|
104
103
|
...imp,
|
|
105
104
|
imports: imp.imports.filter(({ alias }) => alias !== "_"),
|
|
106
105
|
}));
|
|
107
|
-
const resolved = resolveAll(allImportsNoSideEffect,
|
|
106
|
+
const resolved = resolveAll(allImportsNoSideEffect, fromFile, parseDts);
|
|
108
107
|
for (const [path, info] of resolved) {
|
|
109
108
|
if (!info) continue;
|
|
110
109
|
checker.addDefinitions(info.types, info.values);
|
|
@@ -118,29 +117,20 @@ export function compileFiles(files, options = {}) {
|
|
|
118
117
|
for (const { path, alias, _line } of imp.imports) {
|
|
119
118
|
if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
|
|
120
119
|
seenLocalPaths.add(path);
|
|
121
|
-
|
|
122
|
-
const depDir = resolveGwDir(path, dummyFromFile);
|
|
120
|
+
const depDir = resolveGwDir(path, fromFile);
|
|
123
121
|
if (!depDir) {
|
|
124
122
|
console.error(
|
|
125
123
|
`gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
|
|
126
124
|
);
|
|
127
125
|
continue;
|
|
128
126
|
}
|
|
129
|
-
|
|
130
|
-
// Recursively compile dependency
|
|
131
127
|
const dep = compileDir(depDir);
|
|
132
128
|
preambles.push(dep.js);
|
|
133
|
-
if (alias === "_") continue;
|
|
134
|
-
|
|
135
|
-
// Dot import: flatten all exported symbols into global scope
|
|
129
|
+
if (alias === "_") continue;
|
|
136
130
|
if (alias === ".") {
|
|
137
131
|
checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
|
|
138
|
-
// All dep symbols must be treated as bundled so codegen
|
|
139
|
-
// can emit them without a qualifier. We DON'T add a
|
|
140
|
-
// namespace, so SelectorExpr never appears for these.
|
|
141
132
|
continue;
|
|
142
133
|
}
|
|
143
|
-
|
|
144
134
|
const nameUsed = alias ?? dep.pkgName;
|
|
145
135
|
bundledPackages.add(nameUsed);
|
|
146
136
|
checker.addPackageNamespace(
|
|
@@ -152,6 +142,56 @@ export function compileFiles(files, options = {}) {
|
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Main entry points ─────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
export function compileDir(dir, options = {}) {
|
|
150
|
+
const files = gwFilesIn(dir);
|
|
151
|
+
if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
|
|
152
|
+
return compileFiles(files, { ...options, fromDir: dir });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function compileFiles(files, options = {}) {
|
|
156
|
+
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
157
|
+
|
|
158
|
+
// ── 1. Parse ─────────────────────────────────────────────────
|
|
159
|
+
const parseErrors = [];
|
|
160
|
+
const programs = [];
|
|
161
|
+
for (const f of files) {
|
|
162
|
+
try {
|
|
163
|
+
programs.push(parseGoFrontFile(f));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
parseErrors.push(e.message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (parseErrors.length > 0) {
|
|
169
|
+
throw new Error(parseErrors.join("\n"));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Validate consistent package name
|
|
173
|
+
const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
|
|
174
|
+
if (pkgNames.length > 1)
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
|
|
177
|
+
);
|
|
178
|
+
const pkgName = pkgNames[0];
|
|
179
|
+
|
|
180
|
+
// ── 2. Resolve imports ────────────────────────────────────────
|
|
181
|
+
const checker = new TypeChecker();
|
|
182
|
+
const jsImports = new Map(); // npm imports → exported names (for ESM emit)
|
|
183
|
+
const bundledPackages = new Set(); // package names whose code is inlined
|
|
184
|
+
const preambles = []; // JS code from compiled sub-packages
|
|
185
|
+
|
|
186
|
+
const dummyFromFile = join(fromDir, "_dummy.go");
|
|
187
|
+
resolveImports(
|
|
188
|
+
programs,
|
|
189
|
+
dummyFromFile,
|
|
190
|
+
checker,
|
|
191
|
+
jsImports,
|
|
192
|
+
bundledPackages,
|
|
193
|
+
preambles,
|
|
194
|
+
);
|
|
155
195
|
|
|
156
196
|
// ── 3. Type-check ─────────────────────────────────────────────
|
|
157
197
|
const errors = checker.checkAll(programs);
|