Skip to content

Commit 66da819

Browse files
committed
fix: correctly handle an unpaired quotes
1 parent f355674 commit 66da819

File tree

2 files changed

+86
-17
lines changed

2 files changed

+86
-17
lines changed

Diff for: lib/parse.js

+22-14
Original file line numberDiff line numberDiff line change
@@ -165,25 +165,33 @@ const parse = (input, options = {}) => {
165165
*/
166166

167167
if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {
168-
const open = value;
169-
let next;
168+
const leftQuote = value;
169+
// Looking for the nearest unescaped quote of the same type.
170+
// @todo Use negative lookbehind after targeting the [email protected]+
171+
const hasRightQuote = input.slice(index).search(new RegExp(`[^\\\\]${leftQuote}|^${leftQuote}`)) !== -1;
170172

171-
if (options.keepQuotes !== true) {
172-
value = '';
173-
}
173+
let next;
174174

175-
while (index < length && (next = advance())) {
176-
if (next === CHAR_BACKSLASH) {
177-
value += next + advance();
178-
continue;
175+
// If there is no right quote, consume an unpaired quote as a regular character.
176+
if (hasRightQuote) {
177+
if (options.keepQuotes !== true) {
178+
value = '';
179179
}
180180

181-
if (next === open) {
182-
if (options.keepQuotes === true) value += next;
183-
break;
184-
}
181+
while (index <= length && (next = advance())) {
182+
// Skip escaped quotes.
183+
if (next === CHAR_BACKSLASH) {
184+
value += next + advance();
185+
continue;
186+
}
185187

186-
value += next;
188+
if (next === leftQuote) {
189+
if (options.keepQuotes === true) value += next;
190+
break;
191+
}
192+
193+
value += next;
194+
}
187195
}
188196

189197
push({ type: 'text', value });

Diff for: test/bash-spec.js

+64-3
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,74 @@ const equal = (input, expected = bash(input), options) => {
2323

2424
describe('bash', () => {
2525
const fixtures = [
26-
['{1\\.2}', {}, ['{1.2}']],
27-
['{1\\.2}', { keepEscaping: true }, ['{1\\.2}']],
26+
// Single paired quotes
27+
["''{x,y}", {}, ["x", "y"]],
28+
["''{x,y}", { keepQuotes: true }, ["''x", "''y"]],
29+
["a'b'c{x,y}", {}, ['abcx', 'abcy']],
30+
["a'b'c{x,y}", { keepQuotes: true }, ["a'b'cx", "a'b'cy"]],
31+
["'{x,x}'", {}, ['{x,x}']],
32+
["'{x,x}'", { keepQuotes: true }, ["'{x,x}'"]],
33+
["{'x,x'}", {}, ['{x,x}']],
34+
["{'x,x'}", { keepQuotes: true }, ["{'x,x'}"]],
35+
["{x','x}", {}, ['{x,x}']],
36+
["{x','x}", { keepQuotes: true }, ["{x','x}"]],
37+
// Single unpaired quotes
38+
["'{x,y}", {}, ["'x", "'y"]],
39+
["'{x,y}", { keepQuotes: true }, ["'x", "'y"]],
40+
["a'bc{x,y}", {}, ["a'bcx", "a'bcy"]],
41+
["a'bc{x,y}", { keepQuotes: true }, ["a'bcx", "a'bcy"]],
42+
// Double paired quotes
43+
['""{x,y}', {}, ['x', 'y']],
44+
['""{x,y}', { keepQuotes: true }, ['""x', '""y']],
45+
['a"b"c{x,y}', {}, ['abcx', 'abcy']],
46+
['a"b"c{x,y}', { keepQuotes: true }, ['a"b"cx', 'a"b"cy']],
47+
['"{x,x}"', {}, ['{x,x}']],
48+
['"{x,x}"', { keepQuotes: true }, ['"{x,x}"']],
2849
['{"x,x"}', {}, ['{x,x}']],
50+
['{"x,x"}', { keepQuotes: true }, ['{"x,x"}']],
2951
['{x","x}', {}, ['{x,x}']],
30-
['\'{x,x}\'', {}, ['{x,x}']],
52+
['{x","x}', { keepQuotes: true }, ['{x","x}']],
53+
// Double unpaired quotes
54+
['"{x,y}', {}, ['"x', '"y']],
55+
['"{x,y}', { keepQuotes: true }, ['"x', '"y']],
56+
['a"bc{x,y}', {}, ['a"bcx', 'a"bcy']],
57+
['a"bc{x,y}', { keepQuotes: true }, ['a"bcx', 'a"bcy']],
58+
// Paired backticks
59+
['``{x,y}', {}, ['x', 'y']],
60+
['``{x,y}', { keepQuotes: true }, ['``x', '``y']],
61+
['a`b`c{x,y}', {}, ['abcx', 'abcy']],
62+
['a`b`c{x,y}', { keepQuotes: true }, ['a`b`cx', 'a`b`cy']],
63+
['`{x,x}`', {}, ['{x,x}']],
64+
['`{x,x}`', { keepQuotes: true }, ['`{x,x}`']],
65+
['{`x,x`}', {}, ['{x,x}']],
66+
['{`x,x`}', { keepQuotes: true }, ['{`x,x`}']],
3167
['{x`,`x}', {}, ['{x,x}']],
3268
['{x`,`x}', { keepQuotes: true }, ['{x`,`x}']],
69+
// Unpaired backticks
70+
['`{x,y}', {}, ['`x', '`y']],
71+
['`{x,y}', { keepQuotes: true }, ['`x', '`y']],
72+
['a`bc{x,y}', {}, ['a`bcx', 'a`bcy']],
73+
['a`bc{x,y}', { keepQuotes: true }, ['a`bcx', 'a`bcy']],
74+
// Mixed unpaired quotes
75+
['a\'b"c`{x,y}', {}, ['a\'b"c`x', 'a\'b"c`y']],
76+
['a\'b"c`{x,y}', { keepQuotes: true }, ['a\'b"c`x', 'a\'b"c`y']],
77+
// Mixed quotes
78+
[`a\\'"'"b{x,y}`, {}, [`a''bx`, `a''by`]],
79+
['a"\'`b`\'"c{x,y}', {}, ['a\'`b`\'cx', 'a\'`b`\'cy']],
80+
['a"\'`b`\'"c{x,y}', { keepQuotes: true }, ['a"\'`b`\'"cx', 'a"\'`b`\'"cy']],
81+
// Escaped quotes
82+
["\\'{x,y}", {}, ["'x", "'y"]],
83+
["\\'{x,y}", { keepEscaping: true }, ["\\'x", "\\'y"]],
84+
["a'b{x,y}\\'", {}, ["a'bx'", "a'by'"]],
85+
["a'b{x,y}\\'", { keepEscaping: true }, ["a'bx\\'", "a'by\\'"]],
86+
["a'b{x,y}\\'", { keepQuotes: true }, ["a'bx'", "a'by'"]],
87+
["a'b{x,y}\\'", { keepEscaping: true, keepQuotes: true }, ["a'bx\\'", "a'by\\'"]],
88+
["a'b{x,y}\\''", {}, ["ab{x,y}\\'"]],
89+
["a'b{x,y}\\''", { keepQuotes: true }, ["a'b{x,y}\\''"]],
90+
["a'\\'b{x,y}", { keepQuotes: true }, ["a''bx", "a''by"]],
91+
// Common
92+
['{1\\.2}', {}, ['{1.2}']],
93+
['{1\\.2}', { keepEscaping: true }, ['{1\\.2}']],
3394
['\'{a,b}{{a,b},a,b}\'', {}, ['{a,b}{{a,b},a,b}']],
3495
['A{b,{d,e},{f,g}}Z', {}, ['AbZ', 'AdZ', 'AeZ', 'AfZ', 'AgZ']],
3596
['PRE-{a,b}{{a,b},a,b}-POST', {}, ['PRE-aa-POST', 'PRE-ab-POST', 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-ba-POST', 'PRE-bb-POST', 'PRE-ba-POST', 'PRE-bb-POST']],

0 commit comments

Comments
 (0)