From 1af70f9f6fe922be7a4f6af1758cd5b80112ca55 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 16 May 2026 09:40:53 +0100 Subject: [PATCH] fix(faces): strip trailing `;` from module name in JaffaScript imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `transform_import` extracted the module name by stripping surrounding quote characters with `String.sub mod_part 1 (len - 2)`. When the import ends with a semicolon (the normal JS style: `import { x } from \"m\";`), `mod_part` is `\"m\";` — the last character is `;`, not `\"`, so the slice kept the trailing `\"` in the module name and produced `use m\"::{x};` instead of `use m::{x};`. Fix: in all three import branches (`import { x }`, `import *`, and `import name`), strip a trailing `;` from `mod_part` / `quoted` before the quote-stripping slice. Updated `examples/faces/hello-jaffa.affine` to include `import { println } from \"io\";` so the import path is exercised by the regression test. Updated snapshot. --- examples/faces/hello-jaffa.affine | 4 +++- lib/js_face.ml | 23 ++++++++++++++++++----- tests/faces/hello-jaffa.expected.txt | 4 +++- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/examples/faces/hello-jaffa.affine b/examples/faces/hello-jaffa.affine index 3e4aedb7..6ec686e3 100644 --- a/examples/faces/hello-jaffa.affine +++ b/examples/faces/hello-jaffa.affine @@ -3,9 +3,11 @@ // // JaffaScript face. Distinctive features exercised: `function` keyword, // `const`/`let` bindings (let lowers to let mut), brace-delimited -// blocks, `===` equality. +// blocks, `===` equality, `import { x } from "m";` imports. // face: jaffascript +import { println } from "io"; + effect IO { fn println(s: String) -> (); } diff --git a/lib/js_face.ml b/lib/js_face.ml index 9a81e760..1905f005 100644 --- a/lib/js_face.ml +++ b/lib/js_face.ml @@ -39,7 +39,7 @@ [{ expr }]. Full multi-line arrow bodies are a follow-up task. *) -(* ─── Character helpers ────────────────────────────────────────────────── *) +(* ─── Character helpers ────────────────────────────────────────────── *) let is_id_char c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') @@ -113,7 +113,7 @@ let ends_with s suffix = let sl = String.length s and tl = String.length suffix in sl >= tl && String.sub s (sl - tl) tl = suffix -(* ─── Import / export handling ─────────────────────────────────────────── *) +(* ─── Import / export handling ────────────────────────────────────────────── *) (** Transform JavaScript import declarations to AffineScript use declarations. Examples: @@ -136,6 +136,11 @@ let transform_import line = let rest = String.trim rest in if starts_with rest "from " then begin let mod_part = String.trim (String.sub rest 5 (String.length rest - 5)) in + (* Strip a trailing `;` before stripping the surrounding quotes. *) + let mod_part = + if ends_with mod_part ";" then String.sub mod_part 0 (String.length mod_part - 1) + else mod_part + in let mod_name = String.sub mod_part 1 (String.length mod_part - 2) in (* strip quotes *) let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in let names = String.trim names_raw in @@ -151,6 +156,10 @@ let transform_import line = if starts_with rest "from " then (* import * from "module" — just bring in module *) let mod_part = String.trim (String.sub rest 5 (String.length rest - 5)) in + let mod_part = + if ends_with mod_part ";" then String.sub mod_part 0 (String.length mod_part - 1) + else mod_part + in let mod_name = String.sub mod_part 1 (String.length mod_part - 2) in let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in Printf.sprintf "use %s;" mod_name @@ -158,6 +167,10 @@ let transform_import line = (* import name from "module" *) match String.split_on_char ' ' rest with | name :: "from" :: quoted :: _ -> + let quoted = + if ends_with quoted ";" then String.sub quoted 0 (String.length quoted - 1) + else quoted + in let mod_name = String.sub quoted 1 (String.length quoted - 2) in let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in Printf.sprintf "use %s::%s;" mod_name name @@ -176,7 +189,7 @@ let strip_export line = String.sub line 7 (String.length line - 7) else line -(* ─── Function / variable declarations ────────────────────────────────── *) +(* ─── Function / variable declarations ──────────────────────────────────────────── *) (** Transform a [const]/[let]/[var] binding line. *) let transform_var_decl line = @@ -251,7 +264,7 @@ let transform_arrow_body line = before ^ "{ " ^ after ^ " }" end else line -(* ─── Line-by-line transform ────────────────────────────────────────────── *) +(* ─── Line-by-line transform ────────────────────────────────────────────────── *) (** Transform a single source line from JS-face to canonical AffineScript. Order matters: export stripping before function detection, variable @@ -287,7 +300,7 @@ let transform_line line = end end -(* ─── File-level entry points ─────────────────────────────────────────── *) +(* ─── File-level entry points ────────────────────────────────────────────── *) (** Transform a full JS-face source string to canonical AffineScript. *) let transform_source source = diff --git a/tests/faces/hello-jaffa.expected.txt b/tests/faces/hello-jaffa.expected.txt index 709f5319..29752d9e 100644 --- a/tests/faces/hello-jaffa.expected.txt +++ b/tests/faces/hello-jaffa.expected.txt @@ -3,9 +3,11 @@ // // JaffaScript face. Distinctive features exercised: `function` keyword, // `const`/`let` bindings (let lowers to let mut), brace-delimited -// blocks, `==` equality. +// blocks, `==` equality, `import { x } from "m";` imports. // face: jaffascript +use io::{println}; + effect IO { fn println(s: String) -> (); }