diff --git a/src/parse.ts b/src/parse.ts index 091c22c..62fb5a9 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -19,6 +19,8 @@ const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g; const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g; +const lineTerminatorReg = /(?:\r\n|[\n\r\u2028\u2029])/g; + /** Escape special regular expression characters inside a string */ function escapeRegExp(string: string) { @@ -109,7 +111,9 @@ export function parse(this: Eta, str: string): Array { ); const parseCloseReg = new RegExp( - "'|\"|`|\\/\\*|(\\s*(-|_)?" + escapeRegExp(config.tags[1]) + ")", + "'|\"|`|\\/\\*|(\\s*(-|_)?" + + escapeRegExp(config.tags[1]) + + ")|\\/\\/", "g", ); @@ -161,6 +165,14 @@ export function parse(this: Eta, str: string): Array { ParseErr("unclosed comment", str, closeTag.index); } parseCloseReg.lastIndex = commentCloseInd; + } else if (char === "//") { + lineTerminatorReg.lastIndex = parseCloseReg.lastIndex; + const match = lineTerminatorReg.exec(str); + if (match) { + parseCloseReg.lastIndex = match.index + match[0].length; + } else { + parseCloseReg.lastIndex = str.length; + } } else if (char === "'") { singleQuoteReg.lastIndex = closeTag.index; diff --git a/test/parse.spec.ts b/test/parse.spec.ts index 2b8f757..4de8e01 100644 --- a/test/parse.spec.ts +++ b/test/parse.spec.ts @@ -34,6 +34,22 @@ describe("parse test", () => { ]); }); + it("works with unpaired apostrophe in single-line comment", () => { + const buff = eta.parse("hi <% // comment with unpaired apostrophe' \n %>"); + expect(buff).toEqual([ + "hi ", + { val: "// comment with unpaired apostrophe' \n", t: "e" }, + ]); + }); + + it("works with unpaired apostrophe in multiline comment", () => { + const buff = eta.parse("hi <% /* comment with unpaired apostrophe' */ %>"); + expect(buff).toEqual([ + "hi ", + { val: "/* comment with unpaired apostrophe' */", t: "e" }, + ]); + }); + it("parses with simple template literal", () => { // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional const buff = eta.parse("hi <%= `template %> ${value}` %>"); @@ -114,4 +130,32 @@ describe("parse test", () => { <%= /* %> ^`); }); + + it("handles alternative closing tags properly, rather than confusing them with comments", () => { + const originalEta = new Eta({ tags: ["{{", "//"] }); + const buff = originalEta.parse("{{= it.x//"); + expect(buff).toEqual([{ val: "it.x", t: "i" }]); + const originalEta2 = new Eta({ tags: ["{{", "//}}"] }); + const buff2 = originalEta2.parse("{{= it.x//}}"); + expect(buff2).toEqual([{ val: "it.x", t: "i" }]); + }); + + it("handles various line termination characters in single-line comments", () => { + const cases = ["\r", "\r\n", "\u2028", "\u2029", "\n"]; + for (const terminator of cases) { + const buff = eta.parse(`hi <% // comment ending with terminator${terminator} %>`); + expect(buff).toEqual([ + "hi ", + { val: `// comment ending with terminator${terminator}`, t: "e" }, + ]); + } + }); + + it("handles a sequence looking like close tag inside single line comment", () => { + const buff = eta.parse("hi <% // comment %> with close tag\n %>"); + expect(buff).toEqual([ + "hi ", + { val: "// comment %> with close tag\n", t: "e" }, + ]); + }); });