Skip to content

Commit 3b7640c

Browse files
committed
Ignore single-quoted double quotes and vice versa generally
1 parent c1751c8 commit 3b7640c

File tree

2 files changed

+54
-39
lines changed

2 files changed

+54
-39
lines changed

src/tests/validity/command_interpolation_single_apix.ab

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// '
99
// a'b
1010
// a'bABCc'd
11+
// "ab
1112

1213
const str = "ABC"
1314
const space = " "
@@ -21,3 +22,4 @@ echo "a'b{str}c'd"
2122
trust $ echo "'" $
2223
trust $ echo "a'b" $
2324
trust $ echo "a'b{str}c'd" $
25+
trust $ echo '"a'b $

src/translate/fragments/interpolable.rs

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,9 @@ impl InterpolableFragment {
7070
s.insert_str(0, "\"'");
7171
}
7272

73-
let unescaped = count_single_quotes(s, &mut in_double_quotes);
73+
let single_quote_open = scan_quote_state(s, &mut in_single_quotes, &mut in_double_quotes);
7474

75-
// If this chunk has an odd number of unescaped quotes, it toggles the region.
76-
if unescaped % 2 == 1 {
77-
in_single_quotes = !in_single_quotes;
75+
if single_quote_open {
7876
// Close the chunk locally so each piece is balanced.
7977
s.push_str("'\"");
8078
}
@@ -103,30 +101,32 @@ impl InterpolableFragment {
103101
}
104102
}
105103

106-
/// Count single quotes that are NOT escaped by an odd number of preceding backslashes.
107-
fn count_single_quotes(s: &str, in_double_quotes: &mut bool) -> usize {
108-
let mut count = 0usize;
109-
let mut backslashes = 0usize;
104+
/// Scans a string to determine the quoting state, updating the state flags.
105+
/// Returns `true` if the single-quote state was toggled.
106+
fn scan_quote_state(s: &str, in_single_quotes: &mut bool, in_double_quotes: &mut bool) -> bool {
107+
let initial_in_single_quotes = *in_single_quotes;
108+
let mut backslashes = 0;
110109

111110
for b in s.bytes() {
112111
match b {
113112
b'\\' => backslashes += 1,
114113
b'"' => {
115-
if backslashes % 2 == 0 {
114+
if !*in_single_quotes && backslashes % 2 == 0 {
116115
*in_double_quotes = !*in_double_quotes;
117116
}
118117
backslashes = 0;
119118
}
120119
b'\'' => {
121120
if !*in_double_quotes && backslashes % 2 == 0 {
122-
count += 1;
121+
*in_single_quotes = !*in_single_quotes;
123122
}
124123
backslashes = 0;
125124
}
126125
_ => backslashes = 0,
127126
}
128127
}
129-
count
128+
129+
initial_in_single_quotes != *in_single_quotes
130130
}
131131

132132
impl FragmentRenderable for InterpolableFragment {
@@ -186,34 +186,47 @@ mod tests {
186186
}
187187

188188
#[test]
189-
fn test_count_unescaped_single_quotes() {
190-
let mut in_dq;
191-
in_dq = false; assert_eq!(count_single_quotes(r#"foo"#, &mut in_dq), 0);
192-
in_dq = false; assert_eq!(count_single_quotes(r#"foo\'bar"#, &mut in_dq), 0);
193-
in_dq = false; assert_eq!(count_single_quotes(r#"foo'bar"#, &mut in_dq), 1);
189+
fn test_toggles_single_quote_state() {
190+
let mut dq = false;
191+
let mut sq = false;
192+
scan_quote_state(r#"foo"#, &mut sq, &mut dq);
193+
scan_quote_state(r#"foo\'bar"#, &mut sq, &mut dq);
194+
scan_quote_state(r#"foo'bar"#, &mut sq, &mut dq);
194195
// even number of backslashes before quote -> not escaped
195-
in_dq = false; assert_eq!(count_single_quotes(r#"foo\\\\'bar"#, &mut in_dq), 1);
196-
in_dq = false; assert_eq!(count_single_quotes(r#"'"#, &mut in_dq), 1);
197-
in_dq = false; assert_eq!(count_single_quotes(r#"'\"'"#, &mut in_dq), 2);
198-
in_dq = false; assert_eq!(count_single_quotes(r#"'''"#, &mut in_dq), 3);
199-
in_dq = false; assert_eq!(count_single_quotes(r#""'""#, &mut in_dq), 0);
200-
201-
in_dq = false;
202-
assert_eq!(count_single_quotes(r#"'""#, &mut in_dq), 1);
203-
assert!(in_dq);
204-
assert_eq!(count_single_quotes(r#"'""#, &mut in_dq), 0);
205-
assert!(!in_dq);
206-
assert_eq!(count_single_quotes(r#"\"'\""#, &mut in_dq), 1);
207-
assert!(!in_dq);
208-
assert_eq!(count_single_quotes(r#"""#, &mut in_dq), 0);
209-
assert!(in_dq);
210-
assert_eq!(count_single_quotes(r#"'"#, &mut in_dq), 0);
211-
assert!(in_dq);
212-
assert_eq!(count_single_quotes(r#"\'"#, &mut in_dq), 0);
213-
assert!(in_dq);
214-
assert_eq!(count_single_quotes(r#"\""#, &mut in_dq), 0);
215-
assert!(in_dq);
216-
assert_eq!(count_single_quotes(r#"""#, &mut in_dq), 0);
217-
assert!(!in_dq);
196+
scan_quote_state(r#"foo\\\\'bar"#, &mut sq, &mut dq);
197+
scan_quote_state(r#"'"#, &mut sq, &mut dq);
198+
scan_quote_state(r#"'\"'"#, &mut sq, &mut dq);
199+
scan_quote_state(r#"'''"#, &mut sq, &mut dq);
200+
scan_quote_state(r#""'""#, &mut sq, &mut dq);
201+
202+
sq = false;
203+
dq = false;
204+
scan_quote_state(r#" '" "#, &mut sq, &mut dq);
205+
assert!(sq);
206+
assert!(!dq);
207+
scan_quote_state(r#" '" "#, &mut sq, &mut dq);
208+
assert!(!sq);
209+
assert!(dq);
210+
scan_quote_state(r#" \"'\" "#, &mut sq, &mut dq);
211+
assert!(!sq);
212+
assert!(dq);
213+
scan_quote_state(r#" " "#, &mut sq, &mut dq);
214+
assert!(!sq);
215+
assert!(!dq);
216+
scan_quote_state(r#" ' "#, &mut sq, &mut dq);
217+
assert!(sq);
218+
assert!(!dq);
219+
scan_quote_state(r#" \' "#, &mut sq, &mut dq);
220+
assert!(sq);
221+
assert!(!dq);
222+
scan_quote_state(r#" \" "#, &mut sq, &mut dq);
223+
assert!(sq);
224+
assert!(!dq);
225+
scan_quote_state(r#" " "#, &mut sq, &mut dq);
226+
assert!(sq);
227+
assert!(!dq);
228+
scan_quote_state(r#" '"' "#, &mut sq, &mut dq);
229+
assert!(!sq);
230+
assert!(dq);
218231
}
219232
}

0 commit comments

Comments
 (0)