From a8683e720bc5b3b6895af4a17b72f12f6f1404bb Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Mon, 12 Jan 2026 17:44:19 +0800 Subject: [PATCH] fix(codegen): string escape --- nix-js/src/codegen.rs | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 7eaab53..e3c306c 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -11,21 +11,27 @@ pub(crate) trait CodegenContext { fn get_sym(&self, id: SymId) -> &str; } -fn escape_quote_string(s: &str) -> String { - let mut escaped = String::with_capacity(s.len() + 2); - escaped.push('"'); - for c in s.chars() { - match c { - '\\' => escaped.push_str("\\\\"), - '\"' => escaped.push_str("\\\""), - '\n' => escaped.push_str("\\n"), - '\r' => escaped.push_str("\\r"), - '\t' => escaped.push_str("\\t"), - _ => escaped.push(c), +trait EscapeQuote { + fn escape_quote(&self) -> String; +} + +impl EscapeQuote for str { + fn escape_quote(&self) -> String { + let mut escaped = String::with_capacity(self.len() + 2); + escaped.push('"'); + for c in self.chars() { + match c { + '\\' => escaped.push_str("\\\\"), + '\"' => escaped.push_str("\\\""), + '\n' => escaped.push_str("\\n"), + '\r' => escaped.push_str("\\r"), + '\t' => escaped.push_str("\\t"), + _ => escaped.push(c), + } } + escaped.push('"'); + escaped } - escaped.push('"'); - escaped } impl Compile for Ir { @@ -33,7 +39,7 @@ impl Compile for Ir { match self { Ir::Int(int) => format!("{int}n"), // Generate BigInt literal Ir::Float(float) => float.to_string(), - Ir::Str(s) => escape_quote_string(&s.val), + Ir::Str(s) => s.val.escape_quote(), Ir::Path(p) => { // Path needs runtime resolution for interpolated paths let path_expr = ctx.get_ir(p.expr).compile(ctx); @@ -62,7 +68,9 @@ impl Compile for Ir { format!("expr{}", expr_id.0) } Ir::Builtins(_) => "Nix.builtins".to_string(), - &Ir::Builtin(Builtin(name)) => format!("Nix.builtins[\"{}\"]", ctx.get_sym(name)), + &Ir::Builtin(Builtin(name)) => { + format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote()) + } Ir::ConcatStrings(x) => x.compile(ctx), Ir::HasAttr(x) => x.compile(ctx), &Ir::Assert(Assert { assertion, expr }) => { @@ -145,7 +153,7 @@ impl Func { let required = if let Some(req) = &self.param.required { let keys: Vec<_> = req .iter() - .map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) + .map(|&sym| ctx.get_sym(sym).escape_quote()) .collect(); format!("[{}]", keys.join(",")) } else { @@ -156,7 +164,7 @@ impl Func { let allowed = if let Some(allow) = &self.param.allowed { let keys: Vec<_> = allow .iter() - .map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) + .map(|&sym| ctx.get_sym(sym).escape_quote()) .collect(); format!("[{}]", keys.join(",")) } else { @@ -226,17 +234,17 @@ impl Compile for Select { let is_last = i == attr_count - 1; result = match attr { Attr::Str(sym) => { - let key = ctx.get_sym(*sym); + let key = ctx.get_sym(*sym).escape_quote(); if let Some(default) = self.default && is_last { let default_val = ctx.get_ir(default).compile(ctx); format!( - "Nix.selectWithDefault({}, \"{}\", {})", + "Nix.selectWithDefault({}, {}, {})", result, key, default_val ) } else { - format!("Nix.select({}, \"{}\")", result, key) + format!("Nix.select({}, {})", result, key) } } Attr::Dynamic(expr_id) => { @@ -267,7 +275,7 @@ impl Compile for AttrSet { for (&sym, &expr) in &self.stcs { let key = ctx.get_sym(sym); let value = ctx.get_ir(expr).compile(ctx); - attrs.push(format!("{}: {}", escape_quote_string(key), value)); + attrs.push(format!("{}: {}", key.escape_quote(), value)); } for (key_expr, value_expr) in &self.dyns { @@ -310,9 +318,7 @@ impl Compile for HasAttr { .rhs .iter() .map(|attr| match attr { - Attr::Str(sym) => { - format!("\"{}\"", ctx.get_sym(*sym)) - } + Attr::Str(sym) => ctx.get_sym(*sym).escape_quote(), Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx), }) .join(",");