|
| 1 | +// Validates that generated app.js is syntactically valid JavaScript. |
| 2 | +// |
| 3 | +// app.js is assembled from string templates and schema-derived action snippets |
| 4 | +// in generators.cc. This uses the webcc binary to generate app.js for a range of |
| 5 | +// feature combinations and syntax-checks each with `new vm.Script`, so a |
| 6 | +// malformed JS action is caught in CI rather than at runtime in the browser. |
| 7 | +// |
| 8 | +// Usage: node tests/js/check_js.mjs <path-to-webcc-binary> <repo-root> |
| 9 | +import { execFileSync } from "node:child_process"; |
| 10 | +import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs"; |
| 11 | +import { tmpdir } from "node:os"; |
| 12 | +import { join } from "node:path"; |
| 13 | +import vm from "node:vm"; |
| 14 | + |
| 15 | +const [, , webccBin, repoRoot] = process.argv; |
| 16 | +if (!webccBin || !repoRoot) { |
| 17 | + console.error("usage: node check_js.mjs <webcc-binary> <repo-root>"); |
| 18 | + process.exit(2); |
| 19 | +} |
| 20 | + |
| 21 | +// Feature programs chosen to exercise different generator paths: |
| 22 | +// void commands, return-value imports, event delegation, string args, floats. |
| 23 | +const cases = { |
| 24 | + canvas: `#include "webcc/canvas.h" |
| 25 | +int main(){ auto c=webcc::canvas::create_canvas("c",640,480); |
| 26 | + auto x=webcc::canvas::get_context_2d(c); |
| 27 | + webcc::canvas::fill_rect(x,0,0,100,100); |
| 28 | + webcc::canvas::fill_text(x,"hi",10,10); }`, |
| 29 | + |
| 30 | + dom_events: `#include "webcc/dom.h" |
| 31 | +int main(){ auto b=webcc::dom::get_body(); |
| 32 | + auto i=webcc::dom::create_element("input"); |
| 33 | + webcc::dom::add_input_listener(i); |
| 34 | + webcc::dom::add_click_listener(i); |
| 35 | + webcc::dom::append_child(b,i); }`, |
| 36 | + |
| 37 | + webgl: `#include "webcc/webgl.h" |
| 38 | +#include "webcc/canvas.h" |
| 39 | +int main(){ auto c=webcc::canvas::create_canvas("c",640,480); |
| 40 | + auto gl=webcc::canvas::get_context_webgl(c); |
| 41 | + webcc::webgl::clear_color(gl,0,0,0,1); |
| 42 | + webcc::webgl::clear(gl,16384); }`, |
| 43 | + |
| 44 | + websocket: `#include "webcc/websocket.h" |
| 45 | +int main(){ auto ws=webcc::websocket::connect("wss://x"); |
| 46 | + webcc::websocket::send(ws,"hello"); }`, |
| 47 | + |
| 48 | + fetch_storage: `#include "webcc/fetch.h" |
| 49 | +#include "webcc/storage.h" |
| 50 | +int main(){ webcc::fetch::get("/api","{}"); |
| 51 | + webcc::storage::set_item("k","v"); }`, |
| 52 | + |
| 53 | + webgpu: `#include "webcc/wgpu.h" |
| 54 | +int main(){ webcc::wgpu::request_adapter(); }`, |
| 55 | +}; |
| 56 | + |
| 57 | +const work = mkdtempSync(join(tmpdir(), "webcc-js-")); |
| 58 | +let failures = 0; |
| 59 | + |
| 60 | +for (const [name, src] of Object.entries(cases)) { |
| 61 | + const srcPath = join(work, `${name}.cc`); |
| 62 | + const outDir = join(work, name); |
| 63 | + writeFileSync(srcPath, src); |
| 64 | + execFileSync("mkdir", ["-p", outDir]); |
| 65 | + |
| 66 | + // We only need JS generation, which happens before WASM compilation. We let |
| 67 | + // webcc run fully; if the toolchain can compile it too, great, but a compile |
| 68 | + // failure shouldn't mask a JS syntax check. So generate, then read app.js if |
| 69 | + // present. |
| 70 | + try { |
| 71 | + execFileSync(webccBin, ["-o", outDir, srcPath], { |
| 72 | + cwd: repoRoot, |
| 73 | + stdio: "pipe", |
| 74 | + }); |
| 75 | + } catch (e) { |
| 76 | + // Compilation may fail in minimal CI (missing wasm libc bits) but app.js is |
| 77 | + // written before compilation. Only treat as fatal if app.js is absent. |
| 78 | + } |
| 79 | + |
| 80 | + let js; |
| 81 | + try { |
| 82 | + js = readFileSync(join(outDir, "app.js"), "utf8"); |
| 83 | + } catch { |
| 84 | + console.error(` FAIL ${name}: app.js was not generated`); |
| 85 | + failures++; |
| 86 | + continue; |
| 87 | + } |
| 88 | + |
| 89 | + try { |
| 90 | + // Parse-only: constructing a Script compiles (syntax-checks) without running. |
| 91 | + new vm.Script(js, { filename: `${name}/app.js` }); |
| 92 | + console.log(` PASS ${name} (app.js parses, ${js.length} bytes)`); |
| 93 | + } catch (e) { |
| 94 | + console.error(` FAIL ${name}: ${e.message}`); |
| 95 | + failures++; |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +rmSync(work, { recursive: true, force: true }); |
| 100 | + |
| 101 | +console.log(""); |
| 102 | +if (failures) { |
| 103 | + console.error(`${failures} JS validation failure(s).`); |
| 104 | + process.exit(1); |
| 105 | +} |
| 106 | +console.log("All generated app.js files are valid JavaScript."); |
0 commit comments