Skip to main content

alef_e2e/
escape.rs

1//! Language-specific string escaping for e2e test code generation.
2
3/// Escape a string for embedding in a Python string literal.
4pub fn escape_python(s: &str) -> String {
5    s.replace('\\', "\\\\")
6        .replace('"', "\\\"")
7        .replace('\n', "\\n")
8        .replace('\r', "\\r")
9        .replace('\t', "\\t")
10}
11
12/// Escape a string for embedding in a Rust string literal.
13pub fn escape_rust(s: &str) -> String {
14    s.replace('\\', "\\\\")
15        .replace('"', "\\\"")
16        .replace('\n', "\\n")
17        .replace('\r', "\\r")
18        .replace('\t', "\\t")
19}
20
21/// Compute the number of # needed for a Rust raw string literal.
22pub fn raw_string_hashes(s: &str) -> usize {
23    let mut max_hashes = 0;
24    let mut current = 0;
25    let mut after_quote = false;
26    for ch in s.chars() {
27        if ch == '"' {
28            after_quote = true;
29            current = 0;
30        } else if ch == '#' && after_quote {
31            current += 1;
32            max_hashes = max_hashes.max(current);
33        } else {
34            after_quote = false;
35            current = 0;
36        }
37    }
38    max_hashes + 1
39}
40
41/// Format a string as a Rust raw string literal (r#"..."#).
42pub fn rust_raw_string(s: &str) -> String {
43    let hashes = raw_string_hashes(s);
44    let h: String = "#".repeat(hashes);
45    format!("r{h}\"{s}\"{h}")
46}
47
48/// Escape a string for embedding in a JavaScript/TypeScript string literal.
49pub fn escape_js(s: &str) -> String {
50    s.replace('\\', "\\\\")
51        .replace('"', "\\\"")
52        .replace('\n', "\\n")
53        .replace('\r', "\\r")
54        .replace('\t', "\\t")
55        .replace('`', "\\`")
56        .replace('$', "\\$")
57}
58
59/// Format a string as a Go string literal (backtick or quoted).
60pub fn go_string_literal(s: &str) -> String {
61    if !s.contains('`') {
62        format!("`{s}`")
63    } else {
64        format!("\"{}\"", escape_go(s))
65    }
66}
67
68/// Escape a string for embedding in a Go double-quoted string.
69pub fn escape_go(s: &str) -> String {
70    s.replace('\\', "\\\\")
71        .replace('"', "\\\"")
72        .replace('\n', "\\n")
73        .replace('\r', "\\r")
74        .replace('\t', "\\t")
75}
76
77/// Escape a string for embedding in a Java string literal.
78pub fn escape_java(s: &str) -> String {
79    s.replace('\\', "\\\\")
80        .replace('"', "\\\"")
81        .replace('\n', "\\n")
82        .replace('\r', "\\r")
83        .replace('\t', "\\t")
84}
85
86/// Escape a string for embedding in a C# string literal.
87pub fn escape_csharp(s: &str) -> String {
88    s.replace('\\', "\\\\")
89        .replace('"', "\\\"")
90        .replace('\n', "\\n")
91        .replace('\r', "\\r")
92        .replace('\t', "\\t")
93}
94
95/// Escape a string for embedding in a PHP string literal.
96pub fn escape_php(s: &str) -> String {
97    s.replace('\\', "\\\\")
98        .replace('"', "\\\"")
99        .replace('$', "\\$")
100        .replace('\n', "\\n")
101        .replace('\r', "\\r")
102        .replace('\t', "\\t")
103}
104
105/// Escape a string for embedding in a Ruby string literal.
106pub fn escape_ruby(s: &str) -> String {
107    s.replace('\\', "\\\\")
108        .replace('"', "\\\"")
109        .replace('#', "\\#")
110        .replace('\n', "\\n")
111        .replace('\r', "\\r")
112        .replace('\t', "\\t")
113}
114
115/// Escape a string for embedding in an Elixir string literal.
116pub fn escape_elixir(s: &str) -> String {
117    s.replace('\\', "\\\\")
118        .replace('"', "\\\"")
119        .replace('#', "\\#")
120        .replace('\n', "\\n")
121        .replace('\r', "\\r")
122        .replace('\t', "\\t")
123}
124
125/// Escape a string for embedding in an R string literal.
126pub fn escape_r(s: &str) -> String {
127    s.replace('\\', "\\\\")
128        .replace('"', "\\\"")
129        .replace('\n', "\\n")
130        .replace('\r', "\\r")
131        .replace('\t', "\\t")
132}
133
134/// Escape a string for embedding in a C string literal.
135pub fn escape_c(s: &str) -> String {
136    s.replace('\\', "\\\\")
137        .replace('"', "\\\"")
138        .replace('\n', "\\n")
139        .replace('\r', "\\r")
140        .replace('\t', "\\t")
141}
142
143/// Sanitize an identifier for use as a test function name.
144/// Replaces non-alphanumeric characters with underscores, strips leading digits.
145pub fn sanitize_ident(s: &str) -> String {
146    let mut result = String::with_capacity(s.len());
147    for ch in s.chars() {
148        if ch.is_ascii_alphanumeric() || ch == '_' {
149            result.push(ch);
150        } else {
151            result.push('_');
152        }
153    }
154    // Strip leading digits
155    let trimmed = result.trim_start_matches(|c: char| c.is_ascii_digit());
156    if trimmed.is_empty() {
157        "_".to_string()
158    } else {
159        trimmed.to_string()
160    }
161}
162
163/// Convert a category name to a sanitized filename component.
164pub fn sanitize_filename(s: &str) -> String {
165    s.chars()
166        .map(|c| if c.is_ascii_alphanumeric() || c == '_' { c } else { '_' })
167        .collect::<String>()
168        .to_lowercase()
169}