@flatkit/compiler 0.35.3 → 0.36.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/bin/flatc.mjs CHANGED
@@ -20,4 +20,11 @@ try {
20
20
 
21
21
  const code = await run(process.argv) // `--render` is async (SVG decode + raster)
22
22
  // In --watch mode, `fs.watch` keeps the process alive: do not exit (otherwise we kill the watcher).
23
- if (!process.argv.includes('--watch')) process.exit(code)
23
+ if (!process.argv.includes('--watch')) {
24
+ // Let both streams DRAIN first. A write to a pipe is asynchronous on macOS and Windows, and
25
+ // `process.exit` does not wait for it: a report longer than the pipe's buffer (64 KB) was cut off
26
+ // mid-line whenever the output was piped into another tool — and whole when sent to a file or a terminal.
27
+ const drained = (stream) => new Promise((done) => stream.write('', () => done()))
28
+ await Promise.all([drained(process.stdout), drained(process.stderr)])
29
+ process.exit(code)
30
+ }
package/dist/analysis.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  scopeRegions,
28
28
  splitScopeProgram,
29
29
  wrapMetrics
30
- } from "./chunk-W3YUC6BC.js";
30
+ } from "./chunk-43MGBTUK.js";
31
31
  import "./chunk-KXKX2RMX.js";
32
32
  export {
33
33
  allScopeVariables,
@@ -197,7 +197,7 @@ import { objectNames } from "@flatkit/engine/sceneRefs";
197
197
  import { behaviorRegions } from "@flatkit/engine/flatFormat";
198
198
  import { itemBBox, itemBoundsById, dropZoneBounds, transformBBox, revealGrid } from "@flatkit/engine/groups";
199
199
  import { IDENTITY, apply, compose } from "@flatkit/engine/transform";
200
- import { makePathSampler } from "@flatkit/engine/path";
200
+ import { makePathSampler, softVertexCount, smoothingDeviation } from "@flatkit/engine/path";
201
201
  import { bboxIntersects } from "@flatkit/engine/bbox";
202
202
 
203
203
  // src/docWalk.ts
@@ -262,6 +262,11 @@ function forEachLeafExpression(it, fn) {
262
262
  if (isRegion(it)) {
263
263
  if (it.drawExpr) fn(it.drawExpr);
264
264
  if (it.drawFromExpr) fn(it.drawFromExpr);
265
+ if (it.poly) {
266
+ fn(it.poly.xs);
267
+ fn(it.poly.ys);
268
+ if (it.poly.count !== void 0) fn(it.poly.count);
269
+ }
265
270
  }
266
271
  }
267
272
  function forEachExpression(doc, fn) {
@@ -288,6 +293,7 @@ function forEachExpression(doc, fn) {
288
293
  }
289
294
  for (const i of doc.interactions ?? []) forEachActionExpression(i.actions, fn);
290
295
  for (const f of doc.functions ?? []) if (f.kind === "proc") forEachActionExpression(f.body, fn);
296
+ else fn(f.expr);
291
297
  for (const t of [doc.timeline, ...(doc.symbols ?? []).map((s) => s.timeline)]) {
292
298
  if (!t) continue;
293
299
  forEachActionExpression(t.onLoad, fn);
@@ -298,13 +304,14 @@ function forEachExpression(doc, fn) {
298
304
 
299
305
  // src/programDoc.ts
300
306
  import { parseUnits as parseUnits2 } from "@flatkit/engine/dsl";
307
+ import { MATH_CTX, STD_CONSTANTS as STD_CONSTANTS2, STD_IDS as STD_IDS2 } from "@flatkit/engine/expr";
301
308
  function scopeProgram(doc, editPath = []) {
302
309
  const atRoot = editPath.length === 0;
303
310
  const items = contextLayers(doc, editPath).flatMap((l) => l.items);
304
311
  const nameCount = /* @__PURE__ */ new Map();
305
312
  for (const it of items) if (isContainer(it) && it.name) nameCount.set(it.name, (nameCount.get(it.name) ?? 0) + 1);
306
- const scripted = (it) => !!doc.interactions?.some((i) => i.targetId === it.id) || !!doc.interactors?.some((i) => i.targetId === it.id) || !!(it.expressions && Object.keys(it.expressions).length);
307
- const objects = items.filter((it) => isContainer(it) && !!it.name && nameCount.get(it.name) === 1 && scripted(it)).map((it) => ({ name: it.name, body: printUnits(objectToUnits(it.id, doc.interactions, it.expressions, doc.interactors)) }));
313
+ const scripted = (it) => !!doc.interactions?.some((i) => i.targetId === it.id) || !!doc.interactors?.some((i) => i.targetId === it.id) || !!(it.expressions && Object.keys(it.expressions).length) || !!it.focusable;
314
+ const objects = items.filter((it) => isContainer(it) && !!it.name && nameCount.get(it.name) === 1 && scripted(it)).map((it) => ({ name: it.name, body: printUnits(objectToUnits(it.id, doc.interactions, it.expressions, doc.interactors, void 0, it.focusable)) }));
308
315
  const imports = atRoot ? printUnits(importsToUnits(doc.imports)) : "";
309
316
  const funcs = atRoot ? printUnits(functionsToUnits((doc.functions ?? []).filter((f) => !f.name.includes(".")))) : "";
310
317
  const globals = atRoot ? printUnits(variablesToUnits(doc.variables)) : "";
@@ -408,6 +415,102 @@ function timeCapturedVars(doc) {
408
415
  });
409
416
  return out;
410
417
  }
418
+ function didYouMean(word, names) {
419
+ const dist = (a, b) => {
420
+ const row = Array.from({ length: b.length + 1 }, (_, j) => j);
421
+ for (let i = 1; i <= a.length; i++) {
422
+ let prev = row[0];
423
+ row[0] = i;
424
+ for (let j = 1; j <= b.length; j++) {
425
+ const t = row[j];
426
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
427
+ prev = t;
428
+ }
429
+ }
430
+ return row[b.length];
431
+ };
432
+ const best = names.map((n) => ({ n, d: dist(word, n) })).filter((x) => x.d <= 2).sort((a, b) => a.d - b.d)[0];
433
+ return best ? ` \u2014 did you mean "${best.n}"?` : "";
434
+ }
435
+ var COLOR_LITERAL = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
436
+ function instanceParamWarnings(doc) {
437
+ const out = [];
438
+ const names = (sym) => [...(sym.params ?? []).map((p) => p.name), ...(sym.states ?? []).map((sm) => sm.param)];
439
+ const stateNames = (sym, param) => sym.states?.find((sm) => sm.param === param)?.states.map((st) => st.name);
440
+ const checkLiteral = (who, sym, key, value) => {
441
+ const def = sym.params?.find((p) => p.name === key);
442
+ const states = stateNames(sym, key);
443
+ if (!def && !states) {
444
+ out.push(`${who}: unknown param "${key}" (symbol "${sym.name}")${didYouMean(key, names(sym))}`);
445
+ return;
446
+ }
447
+ if (states) {
448
+ if (!states.includes(value) && !Number.isFinite(Number(value))) out.push(`${who}: "${value}" is not a state of "${key}" (${states.join(", ")})`);
449
+ return;
450
+ }
451
+ if (def.type === "color") {
452
+ if (!COLOR_LITERAL.test(value)) out.push(`${who}: "${key}" is a color param and was given "${value}"`);
453
+ } else if (def.type === "bool") {
454
+ if (!["true", "false", "0", "1"].includes(value)) out.push(`${who}: "${key}" is true or false, and was given "${value}"`);
455
+ } else if (def.type === "number") {
456
+ const v = Number(value);
457
+ if (!Number.isFinite(v)) out.push(`${who}: "${key}" is a number param and was given "${value}"`);
458
+ else if (def.min != null && def.max != null && def.min <= def.max && (v < def.min || v > def.max)) out.push(`${who}: "${key}" = ${value} is outside its range ${def.min}..${def.max} (it is clamped)`);
459
+ }
460
+ };
461
+ const byName = /* @__PURE__ */ new Map();
462
+ const visit = (items, atRoot) => {
463
+ for (const it of items) {
464
+ if (isGroup2(it)) {
465
+ for (const l of it.layers) visit(l.items, atRoot);
466
+ continue;
467
+ }
468
+ if (!isInstance(it)) continue;
469
+ const sym = doc.symbols.find((x) => x.id === it.symbolId);
470
+ if (!sym) continue;
471
+ if (atRoot && it.name && !byName.has(it.name)) byName.set(it.name, sym);
472
+ for (const [k, v] of Object.entries(it.params ?? {})) checkLiteral(`instance "${it.name}"`, sym, k, v);
473
+ }
474
+ };
475
+ for (const l of doc.layers) visit(l.items, true);
476
+ for (const sy of doc.symbols ?? []) for (const l of sy.layers) visit(l.items, false);
477
+ const known = /* @__PURE__ */ new Set([...Object.keys(doc.variables ?? {}), ...(doc.functions ?? []).flatMap((f) => f.params)]);
478
+ const reported = /* @__PURE__ */ new Set();
479
+ const say = (m) => {
480
+ if (!reported.has(m)) {
481
+ reported.add(m);
482
+ out.push(m);
483
+ }
484
+ };
485
+ forEachAction(doc, (a) => {
486
+ if (a.do !== "setParam") return;
487
+ const sym = byName.get(a.target);
488
+ if (!sym) {
489
+ say(`no instance named "${a.target}" in the scene \u2014 \`${a.target}.${a.param} = \u2026\` does nothing${didYouMean(a.target, [...byName.keys()])}`);
490
+ return;
491
+ }
492
+ const who = `${a.target}.${a.param} = \u2026`;
493
+ const value = a.value.trim();
494
+ const def = sym.params?.find((p) => p.name === a.param);
495
+ const states = stateNames(sym, a.param);
496
+ if (!def && !states) {
497
+ say(`${who}: unknown param "${a.param}" (symbol "${sym.name}")${didYouMean(a.param, names(sym))}`);
498
+ return;
499
+ }
500
+ if (states) {
501
+ if (/^[A-Za-z_]\w*$/.test(value) && !states.includes(value) && !known.has(value)) say(`${who}: "${value}" is not a state of "${a.param}" (${states.join(", ")})`);
502
+ } else if (def.type === "color") {
503
+ if (!COLOR_LITERAL.test(value)) say(`${who}: "${a.param}" is a color param \u2014 it takes a color literal (#rrggbb), not an expression`);
504
+ } else if (def.type === "text") {
505
+ if (!/^".*"$/.test(value)) say(`${who}: "${a.param}" is a text param \u2014 it takes a quoted text`);
506
+ } else if (/^-?[\d.]+$/.test(value)) {
507
+ const before = out.length;
508
+ checkLiteral(who, sym, a.param, value);
509
+ for (const m of out.splice(before)) say(m);
510
+ }
511
+ });
512
+ return out;
513
+ }
411
514
  function docStructureWarnings(doc) {
412
515
  const out = [];
413
516
  const names = new Set(objectNames(doc.layers));
@@ -443,6 +546,70 @@ function docStructureWarnings(doc) {
443
546
  out.push({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message: `global variable "${name}" never used (declared, but neither read nor written)` } });
444
547
  }
445
548
  }
549
+ const hiddenBy = (name) => STD_CONSTANTS2.includes(name) ? `the constant ${name}` : Object.hasOwn(MATH_CTX, name) ? `the function ${name}()` : STD_IDS2.includes(name) ? `the reserved name ${name}` : void 0;
550
+ const valueFns = new Set([...doc.functions ?? [], ...importedFunctions(doc.imports)].filter((f) => f.kind === "value").map((f) => f.name));
551
+ for (const name of names0) {
552
+ const by = hiddenBy(name) ?? (valueFns.has(name) ? `the function ${name}()` : void 0);
553
+ if (by) out.push({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message: `variable "${name}" is hidden by ${by} \u2014 every read returns that, never the variable. Rename it` } });
554
+ }
555
+ for (const f of doc.functions ?? []) {
556
+ if (f.name.includes(".")) continue;
557
+ for (const p of f.params) {
558
+ const by = hiddenBy(p);
559
+ if (by) out.push({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message: `parameter "${p}" of fn ${f.name} is hidden by ${by} \u2014 the body reads that, never the argument. Rename it` } });
560
+ }
561
+ }
562
+ const looksLikeMaterial = (r) => {
563
+ for (const sp of r.path.subpaths) {
564
+ const n = sp.segments.length;
565
+ if (n < 12) continue;
566
+ const handles = sp.segments.flatMap((sg) => [[sg.inHandle, sg.anchor], [sg.outHandle, sg.anchor]]).filter(([h]) => h !== void 0);
567
+ if (!handles.length || handles.some(([h, a]) => h.x !== a.x || h.y !== a.y)) continue;
568
+ const xs = sp.segments.map((sg) => sg.anchor.x), ys = sp.segments.map((sg) => sg.anchor.y);
569
+ const size = Math.hypot(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
570
+ if (softVertexCount(sp) >= n * 0.75 && smoothingDeviation(sp) >= Math.max(1, size * 4e-3)) return n;
571
+ }
572
+ return 0;
573
+ };
574
+ const scanMaterial = (items) => {
575
+ for (const it of items) {
576
+ if (isRegion2(it) && !it.poly) {
577
+ const n = looksLikeMaterial(it);
578
+ if (n) out.push({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message: `a path of ${n} points with no curve is drawn with straight segments \u2014 if it is free-hand material or a sampled curve, write \`smooth\` after its path data (\`path "\u2026" smooth\`) to have it rounded` } });
579
+ }
580
+ if (isGroup2(it)) for (const l of it.layers) {
581
+ scanMaterial(l.items);
582
+ for (const c of l.cels ?? []) scanMaterial(c.matter ?? []);
583
+ }
584
+ }
585
+ };
586
+ for (const l of [...doc.layers, ...(doc.symbols ?? []).flatMap((sy) => sy.layers)]) {
587
+ scanMaterial(l.items);
588
+ for (const c of l.cels ?? []) scanMaterial(c.matter ?? []);
589
+ }
590
+ const polyNames = /* @__PURE__ */ new Set();
591
+ const scanPoly = (items) => {
592
+ for (const it of items) {
593
+ if (isRegion2(it) && it.poly) {
594
+ polyNames.add(it.poly.xs);
595
+ polyNames.add(it.poly.ys);
596
+ }
597
+ if (isGroup2(it)) for (const l of it.layers) {
598
+ scanPoly(l.items);
599
+ for (const c of l.cels ?? []) scanPoly(c.matter ?? []);
600
+ }
601
+ }
602
+ };
603
+ for (const l of [...doc.layers, ...(doc.symbols ?? []).flatMap((sy) => sy.layers)]) {
604
+ scanPoly(l.items);
605
+ for (const c of l.cels ?? []) scanPoly(c.matter ?? []);
606
+ }
607
+ for (const name of polyNames) {
608
+ const v = doc.variables?.[name];
609
+ if (Array.isArray(v)) continue;
610
+ out.push({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message: v === void 0 ? `polyline: "${name}" is not declared \u2014 it takes two array variables (\`var ${name} = fill(<n>, 0)\`); nothing is drawn` : `polyline: "${name}" is a number, not an array (\`var ${name} = fill(<n>, 0)\`); nothing is drawn` } });
611
+ }
612
+ out.push(...instanceParamWarnings(doc).map((message) => ({ scope: "scene", diag: { line: 1, col: 1, severity: "warning", message } })));
446
613
  const missing = /* @__PURE__ */ new Set();
447
614
  const scanRefs = (items) => {
448
615
  for (const it of items) {
@@ -808,7 +975,7 @@ function docLayoutWarnings(doc) {
808
975
  const zones = [];
809
976
  const collectZones = (layers) => {
810
977
  for (const l of layers) for (const it of l.items) {
811
- if (isGroup2(it) && it.hitbox && it.name) {
978
+ if ((isGroup2(it) || isInstance(it)) && it.hitbox && it.name) {
812
979
  const b = dropZoneBounds(doc, it.name);
813
980
  if (b) zones.push({ name: it.name, b });
814
981
  }
@@ -816,6 +983,8 @@ function docLayoutWarnings(doc) {
816
983
  }
817
984
  };
818
985
  collectZones(doc.layers);
986
+ const dropTargets = new Set((doc.interactions ?? []).filter((it) => it.event === "drop" && it.over).map((it) => it.over));
987
+ for (let k = zones.length - 1; k >= 0; k--) if (!dropTargets.has(zones[k].name)) zones.splice(k, 1);
819
988
  for (let i = 0; i < zones.length; i++) for (let j = i + 1; j < zones.length; j++) {
820
989
  if (bboxIntersects(zones[i].b, zones[j].b)) out.push(warn(`overlapping hitboxes: "${zones[i].name}" and "${zones[j].name}" -> ambiguous drop`));
821
990
  }
@@ -1012,4 +1181,4 @@ export {
1012
1181
  applyFixes,
1013
1182
  repairLoop
1014
1183
  };
1015
- //# sourceMappingURL=chunk-W3YUC6BC.js.map
1184
+ //# sourceMappingURL=chunk-43MGBTUK.js.map