Repository: harttle/liquidjs (ts)

Issue:
Single or double quote breaks comments
Including a single or double quote (straight versions `'"`) inside a comment causes errors. Minimal examples:

```liquid
{% # that's %}
{% # "quoted" %}
```

Error:

```plain
tag "{% # that's %}\r\n" not closed, line:1, col:1
>> 1| {% # that's %}
      ^
   2| 
TokenizationError: tag "{% # that's %}\r\n" not closed, line:1, col:1
```

Removing or replacing them with curly equivalents (`‘’“”`) fixes the error.

[Example from Shopify Liquid docs](https://shopify.dev/docs/api/liquid/tags/liquid) that also breaks due to apostrophe inside the comment:

> ```liquid
> {% liquid
>   # Show a message that's customized to the product type
> 
>   assign product_type = product.type | downcase
>   assign message = ''
> 
>   case product_type
>     when 'health'
>       assign message = 'This is a health potion!'
>     when 'love'
>       assign message = 'This is a love potion!'
>     else
>       assign message = 'This is a potion!'
>   endcase
> 
>   echo message
> %}
> ```

Pull request:
fix: allow quotes in inline comment tag, fixes #628

Patch:
diff --git a/docs/themes/navy/layout/partial/all-contributors.swig b/docs/themes/navy/layout/partial/all-contributors.swig
index 64804020f..152416349 100644
--- a/docs/themes/navy/layout/partial/all-contributors.swig
+++ b/docs/themes/navy/layout/partial/all-contributors.swig
@@ -67,6 +67,7 @@
       <td align="center" valign="top" width="0%"><a href="https://github.com/bangank36"><img src="https://avatars.githubusercontent.com/u/10071857?v=4?s=100" width="100px;" alt="BaNgan"/></a></td>
       <td align="center" valign="top" width="0%"><a href="https://github.com/mahyar-pasarzangene"><img src="https://avatars.githubusercontent.com/u/16485039?v=4?s=100" width="100px;" alt="Mahyar Pasarzangene"/></a></td>
       <td align="center" valign="top" width="0%"><a href="https://hubelbauer.net/"><img src="https://avatars.githubusercontent.com/u/6831144?v=4?s=100" width="100px;" alt="Tomáš Hübelbauer"/></a></td>
+      <td align="center" valign="top" width="0%"><a href="https://sixtwothree.org"><img src="https://avatars.githubusercontent.com/u/73866?v=4?s=100" width="100px;" alt="Jason Garber"/></a></td>
     </tr>
   </tbody>
 </table>
diff --git a/src/parser/tokenizer.spec.ts b/src/parser/tokenizer.spec.ts
index f66f80356..4719ad5d7 100644
--- a/src/parser/tokenizer.spec.ts
+++ b/src/parser/tokenizer.spec.ts
@@ -199,15 +199,6 @@ describe('Tokenizer', function () {
     })
   })
   describe('#readTagToken()', () => {
-    it('should skip quoted delimiters', function () {
-      const html = '{% assign a = "%} {% }} {{" %}'
-      const tokenizer = new Tokenizer(html)
-      const token = tokenizer.readTagToken()
-
-      expect(token).toBeInstanceOf(TagToken)
-      expect(token.name).toBe('assign')
-      expect(token.args).toBe('a = "%} {% }} {{"')
-    })
   })
   describe('#readOutputToken()', () => {
     it('should skip quoted delimiters', function () {
diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts
index 5e69b11d7..87d3ca0a5 100644
--- a/src/parser/tokenizer.ts
+++ b/src/parser/tokenizer.ts
@@ -140,9 +140,10 @@ export class Tokenizer {
     return token
   }
 
-  readToDelimiter (delimiter: string) {
+  readToDelimiter (delimiter: string, respectQuoted = false) {
+    this.skipBlank()
     while (this.p < this.N) {
-      if ((this.peekType() & QUOTE)) {
+      if (respectQuoted && (this.peekType() & QUOTE)) {
         this.readQuoted()
         continue
       }
@@ -156,7 +157,7 @@ export class Tokenizer {
     const { file, input } = this
     const { outputDelimiterRight } = options
     const begin = this.p
-    if (this.readToDelimiter(outputDelimiterRight) === -1) {
+    if (this.readToDelimiter(outputDelimiterRight, true) === -1) {
       throw this.error(`output ${this.snapshot(begin)} not closed`, begin)
     }
     return new OutputToken(input, begin, this.p, options, file)


Test patch:
diff --git a/test/e2e/issues.spec.ts b/test/e2e/issues.spec.ts
index 268911c27..24895a5f6 100644
--- a/test/e2e/issues.spec.ts
+++ b/test/e2e/issues.spec.ts
@@ -431,4 +431,27 @@ describe('Issues', function () {
     const fn = () => engine.parseAndRenderSync("{%- assign module = '' | split '' -%}")
     expect(fn).toThrow(/expected ":" after filter name/)
   })
+  it('#628 Single or double quote breaks comments', () => {
+    const template = `{%- liquid
+  # Show a message that's customized to the product type
+
+  assign product_type = product.type | downcase
+  assign message = ''
+
+  case product_type
+    when 'health'
+      assign message = 'This is a health potion!'
+    when 'love'
+      assign message = 'This is a love potion!'
+    else
+      assign message = 'This is a potion!'
+  endcase
+
+  echo message
+-%}`
+    const engine = new Liquid()
+    const product = { type: 'love' }
+    const result = engine.parseAndRenderSync(template, { product })
+    expect(result).toEqual('This is a love potion!')
+  })
 })
diff --git a/test/integration/tags/inline-comment.spec.ts b/test/integration/tags/inline-comment.spec.ts
index cb1d272f7..5f81e6040 100644
--- a/test/integration/tags/inline-comment.spec.ts
+++ b/test/integration/tags/inline-comment.spec.ts
@@ -17,6 +17,16 @@ describe('tags/inline-comment', function () {
     const html = await liquid.parseAndRender(src)
     return expect(html).toBe('foo')
   })
+  it('should allow single quotes', async function () {
+    const src = "B{% # that's %}A"
+    const html = await liquid.parseAndRender(src)
+    return expect(html).toBe('BA')
+  })
+  it('should allow double quotes', async function () {
+    const src = 'B{% # that"s %}A'
+    const html = await liquid.parseAndRender(src)
+    return expect(html).toBe('BA')
+  })
   it('should handle hash without trailing whitespace', async function () {
     const src = '{% #some comment %}'
     const html = await liquid.parseAndRender(src)