@@ -12,8 +12,127 @@ import {
1212 CommandValidator ,
1313 createCommandValidator ,
1414 containsDangerousSubstitution ,
15+ protectNewlinesInQuotes ,
1516} from "../command-validation"
1617
18+ describe ( "protectNewlinesInQuotes" , ( ) => {
19+ const placeholder = "___NEWLINE___"
20+
21+ describe ( "basic quote handling" , ( ) => {
22+ it ( "protects newlines in double quotes" , ( ) => {
23+ const input = 'echo "hello\nworld"'
24+ const expected = `echo "hello${ placeholder } world"`
25+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
26+ } )
27+
28+ it ( "protects newlines in single quotes" , ( ) => {
29+ const input = "echo 'hello\nworld'"
30+ const expected = `echo 'hello${ placeholder } world'`
31+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
32+ } )
33+
34+ it ( "does not protect newlines outside quotes" , ( ) => {
35+ const input = "echo hello\necho world"
36+ const expected = "echo hello\necho world"
37+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
38+ } )
39+ } )
40+
41+ describe ( "quote concatenation" , ( ) => {
42+ it ( "handles quote concatenation where content between quotes is NOT quoted" , ( ) => {
43+ // In bash: echo '"'A'"' prints "A" (A is not quoted)
44+ const input = `echo '"'A\n'"'`
45+ // The newline after A is NOT inside quotes, so it should NOT be protected
46+ const expected = `echo '"'A\n'"'`
47+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
48+ } )
49+
50+ it ( "handles alternating quotes correctly" , ( ) => {
51+ // echo "hello"world"test" -> hello is quoted, world is not, test is quoted
52+ const input = `echo "hello\n"world\n"test\n"`
53+ const expected = `echo "hello${ placeholder } "world\n"test${ placeholder } "`
54+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
55+ } )
56+
57+ it ( "handles single quote after double quote" , ( ) => {
58+ const input = `echo "hello"'world\n'`
59+ const expected = `echo "hello"'world${ placeholder } '`
60+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
61+ } )
62+
63+ it ( "handles double quote after single quote" , ( ) => {
64+ const input = `echo 'hello'"world\n"`
65+ const expected = `echo 'hello'"world${ placeholder } "`
66+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
67+ } )
68+ } )
69+
70+ describe ( "escaped quotes" , ( ) => {
71+ it ( "handles escaped double quotes in double-quoted strings" , ( ) => {
72+ const input = 'echo "hello\\"world\n"'
73+ const expected = `echo "hello\\"world${ placeholder } "`
74+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
75+ } )
76+
77+ it ( "does not treat backslash as escape in single quotes" , ( ) => {
78+ // In single quotes, backslash is literal (except for \' in some shells)
79+ const input = "echo 'hello\\'world\n'"
80+ // The \\ is literal, the ' ends the quote, so world\n is outside quotes
81+ const expected = "echo 'hello\\'world\n'"
82+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
83+ } )
84+ } )
85+
86+ describe ( "edge cases" , ( ) => {
87+ it ( "handles unclosed quotes" , ( ) => {
88+ const input = 'echo "unclosed\n'
89+ const expected = `echo "unclosed${ placeholder } `
90+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
91+ } )
92+
93+ it ( "handles empty string" , ( ) => {
94+ expect ( protectNewlinesInQuotes ( "" , placeholder ) ) . toBe ( "" )
95+ } )
96+
97+ it ( "handles string with no quotes" , ( ) => {
98+ const input = "echo hello\nworld"
99+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( input )
100+ } )
101+
102+ it ( "handles multiple newlines in quotes" , ( ) => {
103+ const input = 'echo "line1\nline2\nline3"'
104+ const expected = `echo "line1${ placeholder } line2${ placeholder } line3"`
105+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
106+ } )
107+
108+ it ( "handles carriage returns" , ( ) => {
109+ const input = 'echo "hello\rworld"'
110+ const expected = `echo "hello${ placeholder } world"`
111+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
112+ } )
113+
114+ it ( "handles CRLF" , ( ) => {
115+ const input = 'echo "hello\r\nworld"'
116+ const expected = `echo "hello${ placeholder } ${ placeholder } world"`
117+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
118+ } )
119+ } )
120+
121+ describe ( "real-world git commit examples" , ( ) => {
122+ it ( "protects newlines in git commit message" , ( ) => {
123+ const input = `git commit -m "feat: title\n\n- point a\n- point b"`
124+ const expected = `git commit -m "feat: title${ placeholder } ${ placeholder } - point a${ placeholder } - point b"`
125+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
126+ } )
127+
128+ it ( "handles complex git command with multiple quoted sections" , ( ) => {
129+ const input = `git add . && git commit -m "feat: title\n\n- point a" && echo "done\n"`
130+ const expected = `git add . && git commit -m "feat: title${ placeholder } ${ placeholder } - point a" && echo "done${ placeholder } "`
131+ expect ( protectNewlinesInQuotes ( input , placeholder ) ) . toBe ( expected )
132+ } )
133+ } )
134+ } )
135+
17136describe ( "Command Validation" , ( ) => {
18137 describe ( "parseCommand" , ( ) => {
19138 it ( "splits commands by chain operators" , ( ) => {
0 commit comments