From 75046375e15b3387773be32bdbad293d475d145a Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Fri, 13 Mar 2026 00:51:30 +0100 Subject: [PATCH] fix: new line in heredoc --- src/parser/scalar.js | 18 +- .../__snapshots__/heredoc.test.js.snap | 54 ++++++ .../__snapshots__/location.test.js.snap | 3 +- test/snapshot/__snapshots__/loop.test.js.snap | 12 +- .../__snapshots__/string.test.js.snap | 166 ++++++++++++++---- test/snapshot/heredoc.test.js | 12 ++ test/snapshot/string.test.js | 13 +- 7 files changed, 231 insertions(+), 47 deletions(-) diff --git a/src/parser/scalar.js b/src/parser/scalar.js index eca32bd2c..57b17e597 100644 --- a/src/parser/scalar.js +++ b/src/parser/scalar.js @@ -26,7 +26,7 @@ module.exports = { return text.replace(/\\\\/g, "\\").replace(/\\'/g, "'"); } return text - .replace(/\\"/, '"') + .replace(/\\"/g, '"') .replace( /\\([\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u{([0-9a-fA-F]+)})/g, ($match, p1, p2) => { @@ -342,13 +342,16 @@ module.exports = { "string", false, this.version >= 703 && !this.lexer.heredoc_label.finished - ? this.remove_heredoc_leading_whitespace_chars( - this.resolve_special_chars(text, isDoubleQuote), - this.lexer.heredoc_label.indentation, - this.lexer.heredoc_label.indentation_uses_spaces, - this.lexer.heredoc_label.first_encaps_node, + ? this.resolve_special_chars( + this.remove_heredoc_leading_whitespace_chars( + text, + this.lexer.heredoc_label.indentation, + this.lexer.heredoc_label.indentation_uses_spaces, + this.lexer.heredoc_label.first_encaps_node, + ), + isDoubleQuote, ) - : text, + : this.resolve_special_chars(text, isDoubleQuote), false, text, ); @@ -447,6 +450,7 @@ module.exports = { value.push(this.read_encapsed_string_item(true)); } if ( + type === this.ast.encapsed.TYPE_HEREDOC && value.length > 0 && value[value.length - 1].kind === "encapsedpart" && value[value.length - 1].expression.kind === "string" diff --git a/test/snapshot/__snapshots__/heredoc.test.js.snap b/test/snapshot/__snapshots__/heredoc.test.js.snap index fc91ddcb1..4fe837e11 100644 --- a/test/snapshot/__snapshots__/heredoc.test.js.snap +++ b/test/snapshot/__snapshots__/heredoc.test.js.snap @@ -1,5 +1,59 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`heredoc Can parse HEREDOC with escaped characters #1130 1`] = ` +Program { + "children": [ + If { + "alternate": null, + "body": Block { + "children": [ + Echo { + "expressions": [ + Encapsed { + "kind": "encapsed", + "label": "STR", + "raw": "<<$colors[1]<\\\\n\\"", + "raw": ""\\colors[1] contains >$colors[1]<\\n"", "type": "string", - "value": Array [ - String { - "isDoubleQuote": false, - "kind": "string", - "raw": "\\\\colors[1] contains >", - "value": "\\\\colors[1] contains >", - }, - OffsetLookup { - "kind": "offsetlookup", - "offset": Number { - "kind": "number", - "value": "1", + "value": [ + EncapsedPart { + "curly": false, + "expression": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "\\colors[1] contains >", + "unicode": false, + "value": "\\colors[1] contains >", }, - "what": Variable { - "byref": false, - "curly": false, - "kind": "variable", - "name": "colors", + "kind": "encapsedpart", + "syntax": null, + }, + EncapsedPart { + "curly": false, + "expression": OffsetLookup { + "kind": "offsetlookup", + "offset": Number { + "kind": "number", + "value": "1", + }, + "what": Variable { + "curly": false, + "kind": "variable", + "name": "colors", + }, }, + "kind": "encapsedpart", + "syntax": "simple", }, - String { - "isDoubleQuote": false, - "kind": "string", - "raw": "<\\\\n", - "value": "< + EncapsedPart { + "curly": false, + "expression": String { + "isDoubleQuote": false, + "kind": "string", + "raw": "<\\n", + "unicode": false, + "value": "< ", + }, + "kind": "encapsedpart", + "syntax": null, }, ], }, + "kind": "cast", + "raw": "(binary)", + "type": "binary", }, ], "kind": "echo", "shortForm": false, }, ], - "errors": Array [], + "errors": [], "kind": "program", } `; @@ -1314,7 +1331,8 @@ Program { "kind": "string", "raw": "\\n", "unicode": false, - "value": "", + "value": " +", }, "kind": "encapsedpart", "syntax": null, @@ -1702,6 +1720,63 @@ Program { } `; +exports[`Test strings heredoc ... 1`] = ` +Program { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "fallbackContent", + }, + "operator": ".=", + "right": Call { + "arguments": [ + Encapsed { + "kind": "encapsed", + "label": "EOF2", + "raw": "<<addFallbackCatalogue(\\$catalogue%s); + EOF2", + "type": "heredoc", + "value": [ + EncapsedPart { + "curly": false, + "expression": String { + "isDoubleQuote": false, + "kind": "string", + "raw": " \\$catalogue%s = new MessageCatalogue('%s', %s); + \\$catalogue%s->addFallbackCatalogue(\\$catalogue%s); + ", + "unicode": false, + "value": "$catalogue%s = new MessageCatalogue('%s', %s); +$catalogue%s->addFallbackCatalogue($catalogue%s);", + }, + "kind": "encapsedpart", + "syntax": null, + }, + ], + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "sprintf", + "resolution": "uqn", + }, + }, + }, + "kind": "expressionstatement", + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`Test strings implement #116 1`] = ` Program { "children": [ @@ -1731,6 +1806,34 @@ bar", } `; +exports[`Test strings multiple escaped double quotes 1`] = ` +Program { + "children": [ + ExpressionStatement { + "expression": Assign { + "kind": "assign", + "left": Variable { + "curly": false, + "kind": "variable", + "name": "var", + }, + "operator": "=", + "right": String { + "isDoubleQuote": true, + "kind": "string", + "raw": ""say \\"hello\\" and \\"bye\\""", + "unicode": false, + "value": "say "hello" and "bye"", + }, + }, + "kind": "expressionstatement", + }, + ], + "errors": [], + "kind": "program", +} +`; + exports[`Test strings single (2) 1`] = ` Program { "children": [ @@ -2346,7 +2449,8 @@ Program { "kind": "string", "raw": "<\\n", "unicode": false, - "value": "<", + "value": "< +", }, "kind": "encapsedpart", "syntax": null, diff --git a/test/snapshot/heredoc.test.js b/test/snapshot/heredoc.test.js index 801949d37..5c43ed655 100644 --- a/test/snapshot/heredoc.test.js +++ b/test/snapshot/heredoc.test.js @@ -505,4 +505,16 @@ $b = <<<'EOT' ), ).toMatchSnapshot(); }); + + it("Can parse HEREDOC with escaped characters #1130", () => { + expect( + parser.parseEval(` + if (true) { + echo <<$colors[1]<\\n";`), ).toMatchSnapshot(); @@ -275,14 +275,13 @@ describe("Test strings", function () { ).toMatchSnapshot(); }); - it.skip("heredoc ...", function () { + it("heredoc ...", function () { expect( parser.parseEval(` $fallbackContent .= sprintf(<<addFallbackCatalogue(\\$catalogue%s); - EOF2 - ) + EOF2) `), ).toMatchSnapshot(); }); @@ -399,4 +398,10 @@ $var = "\\n | \\r | \\t | \\v | \\e | \\f | \\\\ | \\$ | \\" | \\141 | \\x61 | \ `), ).toMatchSnapshot(); }); + + it("multiple escaped double quotes", function () { + expect( + parser.parseEval(`$var = "say \\"hello\\" and \\"bye\\"";`), + ).toMatchSnapshot(); + }); });