gofront 0.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/LICENSE +9 -0
- package/README.md +202 -0
- package/package.json +41 -0
- package/src/codegen.js +1192 -0
- package/src/compiler.js +156 -0
- package/src/dts-parser.js +762 -0
- package/src/index.js +338 -0
- package/src/lexer.js +465 -0
- package/src/parser.js +1120 -0
- package/src/resolver.js +114 -0
- package/src/typechecker.js +1252 -0
package/src/compiler.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Multi-file GoFront package compiler.
|
|
2
|
+
//
|
|
3
|
+
// compileDir(dir, options) — compile all *.go in a directory as one package
|
|
4
|
+
// compileFiles(files, options) — compile an explicit list of .go files
|
|
5
|
+
//
|
|
6
|
+
// Both return:
|
|
7
|
+
// {
|
|
8
|
+
// pkgName: string,
|
|
9
|
+
// js: string, // generated JS bundle (may include dep preamble)
|
|
10
|
+
// exportedSymbols: Map<name, type>, // for importers to build a namespace
|
|
11
|
+
// exportedTypes: Map<name, type>,
|
|
12
|
+
// }
|
|
13
|
+
//
|
|
14
|
+
// Local imports (`import "./subpkg"`) are compiled recursively and bundled inline.
|
|
15
|
+
// Cross-package access uses the qualified form: `pkg.Foo`. The codegen
|
|
16
|
+
// de-qualifies it because the dependency is inlined.
|
|
17
|
+
|
|
18
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
20
|
+
import { CodeGen } from "./codegen.js";
|
|
21
|
+
import { parseDts } from "./dts-parser.js";
|
|
22
|
+
import { Lexer } from "./lexer.js";
|
|
23
|
+
import { Parser } from "./parser.js";
|
|
24
|
+
import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
|
|
25
|
+
import { TypeChecker } from "./typechecker.js";
|
|
26
|
+
|
|
27
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function parseFile(filePath) {
|
|
30
|
+
const source = readFileSync(filePath, "utf8");
|
|
31
|
+
const filename = basename(filePath);
|
|
32
|
+
const tokens = new Lexer(source, filename).tokenize();
|
|
33
|
+
const ast = new Parser(tokens, filename, source).parse();
|
|
34
|
+
ast._source = source;
|
|
35
|
+
return ast;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function gwFilesIn(dir) {
|
|
39
|
+
return readdirSync(dir)
|
|
40
|
+
.filter((f) => f.endsWith(".go"))
|
|
41
|
+
.sort() // deterministic order
|
|
42
|
+
.map((f) => join(dir, f));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Main entry points ─────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export function compileDir(dir, options = {}) {
|
|
48
|
+
const files = gwFilesIn(dir);
|
|
49
|
+
if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
|
|
50
|
+
return compileFiles(files, { ...options, fromDir: dir });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function compileFiles(files, options = {}) {
|
|
54
|
+
const fromDir = options.fromDir ?? dirname(resolve(files[0]));
|
|
55
|
+
|
|
56
|
+
// ── 1. Parse ─────────────────────────────────────────────────
|
|
57
|
+
const parseErrors = [];
|
|
58
|
+
const programs = [];
|
|
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
|
+
|
|
84
|
+
const allImports = programs.flatMap((p) => p.imports);
|
|
85
|
+
const dummyFromFile = join(fromDir, "_dummy.go");
|
|
86
|
+
|
|
87
|
+
// js: prefix — local .d.ts files
|
|
88
|
+
for (const imp of allImports) {
|
|
89
|
+
for (const path of imp.paths) {
|
|
90
|
+
if (!path.startsWith("js:")) continue;
|
|
91
|
+
const dtsPath = join(fromDir, path.slice(3));
|
|
92
|
+
try {
|
|
93
|
+
const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
|
|
94
|
+
checker.addDefinitions(types, values);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
throw new Error(`Cannot read '${dtsPath}': ${e.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// npm packages
|
|
102
|
+
const resolved = resolveAll(allImports, dummyFromFile, parseDts);
|
|
103
|
+
for (const [path, info] of resolved) {
|
|
104
|
+
if (!info) continue;
|
|
105
|
+
checker.addDefinitions(info.types, info.values);
|
|
106
|
+
jsImports.set(path, [...info.values.keys()]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// local GoFront packages (./subdir)
|
|
110
|
+
const seenLocalPaths = new Set();
|
|
111
|
+
for (const imp of allImports) {
|
|
112
|
+
for (const path of imp.paths) {
|
|
113
|
+
if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
|
|
114
|
+
seenLocalPaths.add(path);
|
|
115
|
+
|
|
116
|
+
const depDir = resolveGwDir(path, dummyFromFile);
|
|
117
|
+
if (!depDir) {
|
|
118
|
+
console.error(
|
|
119
|
+
`gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
|
|
120
|
+
);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Recursively compile dependency
|
|
125
|
+
const dep = compileDir(depDir);
|
|
126
|
+
preambles.push(dep.js);
|
|
127
|
+
bundledPackages.add(dep.pkgName);
|
|
128
|
+
checker.addPackageNamespace(
|
|
129
|
+
dep.pkgName,
|
|
130
|
+
dep.exportedSymbols,
|
|
131
|
+
dep.exportedTypes,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── 3. Type-check ─────────────────────────────────────────────
|
|
137
|
+
const errors = checker.checkAll(programs);
|
|
138
|
+
if (errors.length > 0) {
|
|
139
|
+
const msgs = errors.map((e) => e.message).join("\n");
|
|
140
|
+
throw new Error(msgs);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── 4. Code generation ────────────────────────────────────────
|
|
144
|
+
const codegen = new CodeGen(checker, jsImports, bundledPackages);
|
|
145
|
+
const mainJs = codegen.generateAll(programs);
|
|
146
|
+
|
|
147
|
+
const js =
|
|
148
|
+
preambles.length > 0 ? `${preambles.join("\n")}\n${mainJs}` : mainJs;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
pkgName,
|
|
152
|
+
js,
|
|
153
|
+
exportedSymbols: checker.getExportedSymbols(),
|
|
154
|
+
exportedTypes: checker.getExportedTypes(),
|
|
155
|
+
};
|
|
156
|
+
}
|