gofront 0.0.1 → 0.0.3

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/src/compiler.js CHANGED
@@ -86,8 +86,9 @@ export function compileFiles(files, options = {}) {
86
86
 
87
87
  // js: prefix — local .d.ts files
88
88
  for (const imp of allImports) {
89
- for (const path of imp.paths) {
89
+ for (const { path, alias } of imp.imports) {
90
90
  if (!path.startsWith("js:")) continue;
91
+ if (alias === "_") continue; // side-effect import — no type definitions
91
92
  const dtsPath = join(fromDir, path.slice(3));
92
93
  try {
93
94
  const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
@@ -98,8 +99,12 @@ export function compileFiles(files, options = {}) {
98
99
  }
99
100
  }
100
101
 
101
- // npm packages
102
- const resolved = resolveAll(allImports, dummyFromFile, parseDts);
102
+ // npm packages — exclude side-effect imports (alias "_") from type resolution
103
+ const allImportsNoSideEffect = allImports.map((imp) => ({
104
+ ...imp,
105
+ imports: imp.imports.filter(({ alias }) => alias !== "_"),
106
+ }));
107
+ const resolved = resolveAll(allImportsNoSideEffect, dummyFromFile, parseDts);
103
108
  for (const [path, info] of resolved) {
104
109
  if (!info) continue;
105
110
  checker.addDefinitions(info.types, info.values);
@@ -108,33 +113,49 @@ export function compileFiles(files, options = {}) {
108
113
 
109
114
  // local GoFront packages (./subdir)
110
115
  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}`,
116
+ for (const p of programs) {
117
+ for (const imp of p.imports) {
118
+ for (const { path, alias, _line } of imp.imports) {
119
+ if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
120
+ seenLocalPaths.add(path);
121
+
122
+ const depDir = resolveGwDir(path, dummyFromFile);
123
+ if (!depDir) {
124
+ console.error(
125
+ `gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
126
+ );
127
+ continue;
128
+ }
129
+
130
+ // Recursively compile dependency
131
+ const dep = compileDir(depDir);
132
+ preambles.push(dep.js);
133
+ if (alias === "_") continue; // side-effect import — bundle code but don't expose namespace
134
+
135
+ // Dot import: flatten all exported symbols into global scope
136
+ if (alias === ".") {
137
+ 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
+ continue;
142
+ }
143
+
144
+ const nameUsed = alias ?? dep.pkgName;
145
+ bundledPackages.add(nameUsed);
146
+ checker.addPackageNamespace(
147
+ nameUsed,
148
+ dep.exportedSymbols,
149
+ dep.exportedTypes,
120
150
  );
121
- continue;
151
+ checker.trackImport(nameUsed, { _line }, p._filename, p._source);
122
152
  }
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
153
  }
134
154
  }
135
155
 
136
156
  // ── 3. Type-check ─────────────────────────────────────────────
137
157
  const errors = checker.checkAll(programs);
158
+ checker.reportUnusedImports();
138
159
  if (errors.length > 0) {
139
160
  const msgs = errors.map((e) => e.message).join("\n");
140
161
  throw new Error(msgs);
package/src/dts-parser.js CHANGED
@@ -404,13 +404,11 @@ export class DtsParser {
404
404
  if (this.eof() || this.ch() === "}") return;
405
405
 
406
406
  // Skip modifiers (export, declare, static, readonly, etc.)
407
- let _exported = false;
408
407
  while (true) {
409
408
  this.skip();
410
409
  let found = false;
411
410
  for (const mod of SKIP_MODIFIERS) {
412
411
  if (this.matchKw(mod)) {
413
- if (mod === "export") _exported = true;
414
412
  found = true;
415
413
  break;
416
414
  }
@@ -604,13 +602,11 @@ export class DtsParser {
604
602
  if (this.eof() || this.ch() === "}") break;
605
603
 
606
604
  // Collect modifiers
607
- let _isExport = false;
608
605
  while (true) {
609
606
  this.skip();
610
607
  let found = false;
611
608
  for (const mod of SKIP_MODIFIERS) {
612
609
  if (this.matchKw(mod)) {
613
- if (mod === "export") _isExport = true;
614
610
  found = true;
615
611
  break;
616
612
  }
package/src/index.js CHANGED
@@ -148,16 +148,14 @@ function runCompile() {
148
148
  throw new Error(`cannot read '${inputArg}': ${e.message}`);
149
149
  }
150
150
 
151
- let tokens;
152
- tokens = new Lexer(source, basename(inputPath)).tokenize();
151
+ const tokens = new Lexer(source, basename(inputPath)).tokenize();
153
152
 
154
153
  if (dumpTokens) {
155
154
  for (const tok of tokens) console.log(tok.toString());
156
155
  process.exit(0);
157
156
  }
158
157
 
159
- let ast;
160
- ast = new Parser(tokens, basename(inputPath), source).parse();
158
+ const ast = new Parser(tokens, basename(inputPath), source).parse();
161
159
  ast._source = source;
162
160
 
163
161
  if (dumpAst) {
@@ -170,7 +168,7 @@ function runCompile() {
170
168
  const jsImports = new Map();
171
169
 
172
170
  for (const imp of ast.imports) {
173
- for (const path of imp.paths) {
171
+ for (const { path } of imp.imports) {
174
172
  if (!path.startsWith("js:")) continue;
175
173
  const dtsPath = join(fromDir, path.slice(3));
176
174
  try {
@@ -193,7 +191,7 @@ function runCompile() {
193
191
  const preambles = [];
194
192
 
195
193
  for (const imp of ast.imports) {
196
- for (const path of imp.paths) {
194
+ for (const { path, alias } of imp.imports) {
197
195
  if (!isLocalPath(path)) continue;
198
196
  const depDir = resolveGwDir(path, inputPath);
199
197
  if (!depDir) {
@@ -204,9 +202,10 @@ function runCompile() {
204
202
  }
205
203
  const dep = compileDir(depDir);
206
204
  preambles.push(dep.js);
207
- bundledPackages.add(dep.pkgName);
205
+ const nameUsed = alias ?? dep.pkgName;
206
+ bundledPackages.add(nameUsed);
208
207
  checker.addPackageNamespace(
209
- dep.pkgName,
208
+ nameUsed,
210
209
  dep.exportedSymbols,
211
210
  dep.exportedTypes,
212
211
  );
@@ -228,15 +227,7 @@ function runCompile() {
228
227
  }
229
228
 
230
229
  const output = preambles.length > 0 ? `${preambles.join("\n")}\n${js}` : js;
231
- return { js: output, _cg: cg };
232
- }
233
-
234
- function _writeOutput(js) {
235
- if (outputFile) {
236
- writeFileSync(outputFile, `${js}\n`);
237
- } else {
238
- console.log(js);
239
- }
230
+ return { js: output };
240
231
  }
241
232
 
242
233
  // ── Minify helper ────────────────────────────────────────────
@@ -299,7 +290,7 @@ function timestamp() {
299
290
  return new Date().toLocaleTimeString();
300
291
  }
301
292
 
302
- async function buildOnce(_label) {
293
+ async function buildOnce() {
303
294
  try {
304
295
  const startMs = performance.now();
305
296
  const result = runCompile();
@@ -323,7 +314,7 @@ async function buildOnce(_label) {
323
314
  }
324
315
 
325
316
  // Initial build
326
- buildOnce("initial");
317
+ buildOnce();
327
318
 
328
319
  // Determine what to watch
329
320
  const watchTarget = isDir ? inputPath : dirname(inputPath);
@@ -332,7 +323,7 @@ let debounce = null;
332
323
  watch(watchTarget, { recursive: true }, (_event, filename) => {
333
324
  if (filename && !filename.endsWith(".go")) return;
334
325
  clearTimeout(debounce);
335
- debounce = setTimeout(() => buildOnce(filename ?? "change"), 80);
326
+ debounce = setTimeout(() => buildOnce(), 80);
336
327
  });
337
328
 
338
329
  console.error(`[${timestamp()}] gofront: watching ${inputArg} ...`);
package/src/lexer.js CHANGED
@@ -35,6 +35,9 @@ export const T = {
35
35
  DEFER: "defer",
36
36
  ASYNC: "async",
37
37
  AWAIT: "await",
38
+ GO: "go",
39
+ CHAN: "chan",
40
+ SELECT: "select",
38
41
 
39
42
  // Operators
40
43
  PLUS: "+",
@@ -58,11 +61,17 @@ export const T = {
58
61
  STAR_ASSIGN: "*=",
59
62
  SLASH_ASSIGN: "/=",
60
63
  PERCENT_ASSIGN: "%=",
64
+ AMP_ASSIGN: "&=",
65
+ PIPE_ASSIGN: "|=",
66
+ CARET_ASSIGN: "^=",
67
+ LSHIFT_ASSIGN: "<<=",
68
+ RSHIFT_ASSIGN: ">>=",
61
69
  INC: "++",
62
70
  DEC: "--",
63
71
  AMP: "&",
64
72
  PIPE: "|",
65
73
  CARET: "^",
74
+ AND_NOT: "&^",
66
75
  LSHIFT: "<<",
67
76
  RSHIFT: ">>",
68
77
  ELLIPSIS: "...",
@@ -131,6 +140,9 @@ const KEYWORDS = new Set([
131
140
  "defer",
132
141
  "async",
133
142
  "await",
143
+ "go",
144
+ "chan",
145
+ "select",
134
146
  ]);
135
147
 
136
148
  export class Token {
@@ -212,16 +224,28 @@ export class Lexer {
212
224
  while (this.pos < this.src.length && this.peek() !== "\n")
213
225
  this.advance();
214
226
  } else if (ch === "/" && this.peek(1) === "*") {
227
+ const startLine = this.line,
228
+ startCol = this.col;
215
229
  this.advance();
216
230
  this.advance();
231
+ let closed = false;
217
232
  while (this.pos < this.src.length) {
218
233
  if (this.peek() === "*" && this.peek(1) === "/") {
219
234
  this.advance();
220
235
  this.advance();
236
+ closed = true;
221
237
  break;
222
238
  }
223
239
  this.advance();
224
240
  }
241
+ if (!closed)
242
+ throw new LexError(
243
+ "Unterminated block comment",
244
+ startLine,
245
+ startCol,
246
+ this.filename,
247
+ this.src,
248
+ );
225
249
  } else {
226
250
  break;
227
251
  }
@@ -267,7 +291,7 @@ export class Lexer {
267
291
  }
268
292
  }
269
293
  if (this.pos >= this.src.length)
270
- throw new LexError("Unterminated string", l, c, this.filename);
294
+ throw new LexError("Unterminated string", l, c, this.filename, this.src);
271
295
  this.advance(); // closing "
272
296
  return s;
273
297
  }
@@ -281,23 +305,145 @@ export class Lexer {
281
305
  return s;
282
306
  }
283
307
 
308
+ readRuneLiteral() {
309
+ const l = this.line,
310
+ c = this.col;
311
+ this.advance(); // opening '
312
+ let code;
313
+ if (this.peek() === "\\") {
314
+ this.advance();
315
+ const esc = this.advance();
316
+ switch (esc) {
317
+ case "n":
318
+ code = 10;
319
+ break;
320
+ case "t":
321
+ code = 9;
322
+ break;
323
+ case "r":
324
+ code = 13;
325
+ break;
326
+ case "'":
327
+ code = 39;
328
+ break;
329
+ case "\\":
330
+ code = 92;
331
+ break;
332
+ case "0":
333
+ code = 0;
334
+ break;
335
+ default:
336
+ throw new LexError(
337
+ `Unknown escape in rune literal: \\${esc}`,
338
+ l,
339
+ c,
340
+ this.filename,
341
+ this.src,
342
+ );
343
+ }
344
+ } else if (this.peek() !== "'") {
345
+ code = this.src.codePointAt(this.pos);
346
+ this.advance();
347
+ } else {
348
+ throw new LexError("Empty rune literal", l, c, this.filename, this.src);
349
+ }
350
+ if (this.peek() !== "'")
351
+ throw new LexError(
352
+ "Rune literal must contain exactly one character",
353
+ l,
354
+ c,
355
+ this.filename,
356
+ this.src,
357
+ );
358
+ this.advance(); // closing '
359
+ return String(code);
360
+ }
361
+
284
362
  readNumber() {
285
363
  let n = "";
286
364
  let isFloat = false;
287
- while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
288
- n += this.advance();
365
+
366
+ // Check for prefix: 0b, 0o, 0x
367
+ const first = this.advance();
368
+ n += first;
369
+ if (first === "0") {
370
+ const p = this.peek();
371
+ if (p === "b" || p === "B") {
372
+ n += this.advance();
373
+ while (this.pos < this.src.length && /[01_]/.test(this.peek())) {
374
+ const ch = this.advance();
375
+ if (ch !== "_") n += ch;
376
+ }
377
+ return { n, isFloat: false };
378
+ }
379
+ if (p === "o" || p === "O") {
380
+ n += this.advance();
381
+ while (this.pos < this.src.length && /[0-7_]/.test(this.peek())) {
382
+ const ch = this.advance();
383
+ if (ch !== "_") n += ch;
384
+ }
385
+ return { n, isFloat: false };
386
+ }
387
+ if (p === "x" || p === "X") {
388
+ n += this.advance();
389
+ while (this.pos < this.src.length && /[0-9a-fA-F_]/.test(this.peek())) {
390
+ const ch = this.advance();
391
+ if (ch !== "_") n += ch;
392
+ }
393
+ // Hex float: 0x1.Fp10 or 0xAp-2
394
+ if (this.peek() === "." || this.peek() === "p" || this.peek() === "P") {
395
+ let frac = "";
396
+ if (this.peek() === ".") {
397
+ this.advance(); // skip the dot (don't add to n)
398
+ while (
399
+ this.pos < this.src.length &&
400
+ /[0-9a-fA-F_]/.test(this.peek())
401
+ ) {
402
+ const ch = this.advance();
403
+ if (ch !== "_") frac += ch;
404
+ }
405
+ }
406
+ if (this.peek() === "p" || this.peek() === "P") {
407
+ this.advance(); // skip p/P
408
+ let exp = "";
409
+ if (this.peek() === "+" || this.peek() === "-")
410
+ exp += this.advance();
411
+ while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
412
+ exp += this.advance();
413
+ // Evaluate: JS can't parse hex floats, so convert manually
414
+ // n is "0x1" (integer hex part), frac is hex fractional digits
415
+ let mantissa = Number(n);
416
+ if (frac) {
417
+ mantissa += Number.parseInt(frac, 16) / 16 ** frac.length;
418
+ }
419
+ const value = mantissa * 2 ** Number(exp);
420
+ return { n: String(value), isFloat: true };
421
+ }
422
+ }
423
+ return { n, isFloat: false };
424
+ }
425
+ }
426
+
427
+ while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
428
+ const ch = this.advance();
429
+ if (ch !== "_") n += ch;
430
+ }
289
431
  if (this.peek() === "." && /[0-9]/.test(this.peek(1))) {
290
432
  isFloat = true;
291
433
  n += this.advance();
292
- while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
293
- n += this.advance();
434
+ while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
435
+ const ch = this.advance();
436
+ if (ch !== "_") n += ch;
437
+ }
294
438
  }
295
439
  if (this.peek() === "e" || this.peek() === "E") {
296
440
  isFloat = true;
297
441
  n += this.advance();
298
442
  if (this.peek() === "+" || this.peek() === "-") n += this.advance();
299
- while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
300
- n += this.advance();
443
+ while (this.pos < this.src.length && /[0-9_]/.test(this.peek())) {
444
+ const ch = this.advance();
445
+ if (ch !== "_") n += ch;
446
+ }
301
447
  }
302
448
  return { n, isFloat };
303
449
  }
@@ -347,6 +493,10 @@ export class Lexer {
347
493
  this.push(T.STRING, this.readRawString(), l, c);
348
494
  continue;
349
495
  }
496
+ if (ch === "'") {
497
+ this.push(T.INT, this.readRuneLiteral(), l, c);
498
+ continue;
499
+ }
350
500
 
351
501
  // Numbers
352
502
  if (/[0-9]/.test(ch)) {
@@ -396,25 +546,33 @@ export class Lexer {
396
546
  else this.push(T.NOT, "!", l, c);
397
547
  break;
398
548
  case "<":
399
- if (this.match("<")) this.push(T.LSHIFT, "<<", l, c);
400
- else if (this.match("=")) this.push(T.LTE, "<=", l, c);
549
+ if (this.match("<")) {
550
+ if (this.match("=")) this.push(T.LSHIFT_ASSIGN, "<<=", l, c);
551
+ else this.push(T.LSHIFT, "<<", l, c);
552
+ } else if (this.match("=")) this.push(T.LTE, "<=", l, c);
401
553
  else this.push(T.LT, "<", l, c);
402
554
  break;
403
555
  case ">":
404
- if (this.match(">")) this.push(T.RSHIFT, ">>", l, c);
405
- else if (this.match("=")) this.push(T.GTE, ">=", l, c);
556
+ if (this.match(">")) {
557
+ if (this.match("=")) this.push(T.RSHIFT_ASSIGN, ">>=", l, c);
558
+ else this.push(T.RSHIFT, ">>", l, c);
559
+ } else if (this.match("=")) this.push(T.GTE, ">=", l, c);
406
560
  else this.push(T.GT, ">", l, c);
407
561
  break;
408
562
  case "&":
409
563
  if (this.match("&")) this.push(T.AND, "&&", l, c);
564
+ else if (this.match("^")) this.push(T.AND_NOT, "&^", l, c);
565
+ else if (this.match("=")) this.push(T.AMP_ASSIGN, "&=", l, c);
410
566
  else this.push(T.AMP, "&", l, c);
411
567
  break;
412
568
  case "|":
413
569
  if (this.match("|")) this.push(T.OR, "||", l, c);
570
+ else if (this.match("=")) this.push(T.PIPE_ASSIGN, "|=", l, c);
414
571
  else this.push(T.PIPE, "|", l, c);
415
572
  break;
416
573
  case "^":
417
- this.push(T.CARET, "^", l, c);
574
+ if (this.match("=")) this.push(T.CARET_ASSIGN, "^=", l, c);
575
+ else this.push(T.CARET, "^", l, c);
418
576
  break;
419
577
  case ":":
420
578
  if (this.match("=")) this.push(T.DEFINE, ":=", l, c);