Skip to content

Commit 2f87708

Browse files
authoredJul 7, 2022
feat: inline comment tag (#514)
* style: remove unnecessary intermediate constant * feat: add inline comment tag * refactor: use readIdentifier when reading tag names * docs: add inline comment tag
1 parent 4a0186d commit 2f87708

File tree

9 files changed

+198
-5
lines changed

9 files changed

+198
-5
lines changed
 

‎docs/source/_data/sidebar.yml

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ filters:
7878

7979
tags:
8080
overview: overview.html
81+
"# (inline comment)": inline_comment.html
8182
assign: assign.html
8283
capture: capture.html
8384
case: case.html

‎docs/source/tags/inline_comment.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: "# (inline comment)"
3+
---
4+
5+
{% since %}v9.38.0{% endsince %}
6+
7+
Add comments to a Liquid template using an inline tag. Text enclosed in an inline comment tag will not be printed.
8+
9+
Input
10+
```liquid
11+
Anything inside an inline comment tag will not be printed.
12+
{% # this is an inline comment %}
13+
But every line must start with a '#'.
14+
{%
15+
# this is a comment
16+
# that spans multiple lines
17+
%}
18+
```
19+
20+
Output
21+
```text
22+
Anything inside an inline comment tag will not be printed.
23+
But every line must start with a '#'.
24+
```
25+
26+
Inline comments are useful inside <a href="./liquid.html">`liquid`</a> tags too.
27+
28+
```liquid
29+
{% liquid
30+
# required args
31+
assign product = collection.products.first
32+
33+
# optional args
34+
assign should_show_border = should_show_border | default: true
35+
assign should_highlight = should_highlight | default: false
36+
%}
37+
```
38+
39+
But they don't work well for commenting out blocks of Liquid code. The <a href="./comment.html">`comment`</a> block tag is the better option when you need to temporarily stop other tags from being executed.
40+
41+
Input
42+
```liquid
43+
{%- # {% echo 'Welcome to LiquidJS!' %} -%}
44+
{% comment %}{% echo 'Welcome to LiquidJS!' %}{% endcomment %}
45+
```
46+
47+
Output
48+
```text
49+
-%}
50+
```

‎src/builtin/tags/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import Break from './break'
1818
import Continue from './continue'
1919
import echo from './echo'
2020
import liquid from './liquid'
21+
import inlineComment from './inline-comment'
2122
import { TagImplOptions } from '../../template/tag/tag-impl-options'
2223

2324
const tags: { [key: string]: TagImplOptions } = {
24-
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid
25+
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid, '#': inlineComment
2526
}
2627

2728
export default tags

‎src/builtin/tags/inline-comment.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { TagToken } from '../../tokens/tag-token'
2+
import { TopLevelToken } from '../../tokens/toplevel-token'
3+
import { TagImplOptions } from '../../template/tag/tag-impl-options'
4+
5+
export default {
6+
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
7+
if (tagToken.args.search(/\n\s*[^#\s]/g) !== -1) {
8+
throw new Error('every line of an inline comment must start with a \'#\' character')
9+
}
10+
}
11+
} as TagImplOptions

‎src/parser/tokenizer.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,7 @@ export class Tokenizer {
207207
const begin = this.p
208208
let end = this.N
209209
if (this.readToDelimiter('\n') !== -1) end = this.p
210-
const token = new LiquidTagToken(input, begin, end, options, file)
211-
return token
210+
return new LiquidTagToken(input, begin, end, options, file)
212211
}
213212

214213
mkError (msg: string, begin: number) {
@@ -234,6 +233,13 @@ export class Tokenizer {
234233
return new IdentifierToken(this.input, begin, this.p, this.file)
235234
}
236235

236+
readTagName (): string {
237+
this.skipBlank()
238+
// Handle inline comment tags
239+
if (this.input[this.p] === '#') return this.input.slice(this.p, ++this.p)
240+
return this.readIdentifier().getText()
241+
}
242+
237243
readHashes (jekyllStyle?: boolean) {
238244
const hashes = []
239245
while (true) {

‎src/tokens/liquid-tag-token.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class LiquidTagToken extends DelimitedToken {
2323
this.args = ''
2424
} else {
2525
const tokenizer = new Tokenizer(this.content, options.operatorsTrie)
26-
this.name = tokenizer.readIdentifier().getText()
26+
this.name = tokenizer.readTagName()
2727
if (!this.name) throw new TokenizationError(`illegal liquid tag syntax`, this)
2828

2929
tokenizer.skipBlank()

‎src/tokens/tag-token.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class TagToken extends DelimitedToken {
1919
super(TokenKind.Tag, value, input, begin, end, trimTagLeft, trimTagRight, file)
2020

2121
const tokenizer = new Tokenizer(this.content, options.operatorsTrie)
22-
this.name = tokenizer.readIdentifier().getText()
22+
this.name = tokenizer.readTagName()
2323
if (!this.name) throw new TokenizationError(`illegal tag syntax`, this)
2424

2525
tokenizer.skipBlank()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Liquid } from '../../../../src/liquid'
2+
import { expect, use } from 'chai'
3+
import * as chaiAsPromised from 'chai-as-promised'
4+
5+
use(chaiAsPromised)
6+
7+
describe('tags/inline-comment', function () {
8+
const liquid = new Liquid()
9+
it('should ignore plain string', async function () {
10+
const src = 'My name is {% # super %} Shopify.'
11+
const html = await liquid.parseAndRender(src)
12+
return expect(html).to.equal('My name is Shopify.')
13+
})
14+
it('should ignore output tokens', async function () {
15+
const src = '{% #\n{{ foo}} \n %}'
16+
const html = await liquid.parseAndRender(src)
17+
return expect(html).to.equal('')
18+
})
19+
it('should support whitespace control', async function () {
20+
const src = '{%- # some comment \n -%}\nfoo'
21+
const html = await liquid.parseAndRender(src)
22+
return expect(html).to.equal('foo')
23+
})
24+
it('should handle hash without trailing whitespace', async function () {
25+
const src = '{% #some comment %}'
26+
const html = await liquid.parseAndRender(src)
27+
return expect(html).to.equal('')
28+
})
29+
it('should handle hash without leading whitespace', async function () {
30+
const src = '{%#some comment %}'
31+
const html = await liquid.parseAndRender(src)
32+
return expect(html).to.equal('')
33+
})
34+
it('should handle empty comment', async function () {
35+
const src = '{%#%}'
36+
const html = await liquid.parseAndRender(src)
37+
return expect(html).to.equal('')
38+
})
39+
it('should support multiple lines', async function () {
40+
const src = [
41+
'{%-',
42+
' # spread inline comments',
43+
' # over multiple lines',
44+
'-%}'
45+
].join('\n')
46+
const html = await liquid.parseAndRender(src)
47+
return expect(html).to.equal('')
48+
})
49+
it('should enforce leading hashes', async function () {
50+
const src = [
51+
'{%-',
52+
' # spread inline comments',
53+
' over multiple lines',
54+
'-%}'
55+
].join('\n')
56+
return expect(liquid.parseAndRender(src))
57+
.to.be.rejectedWith(/every line of an inline comment must start with a '#' character/)
58+
})
59+
describe('sync support', function () {
60+
it('should ignore plain string', function () {
61+
const src = 'My name is {% # super %} Shopify.'
62+
const html = liquid.parseAndRenderSync(src)
63+
return expect(html).to.equal('My name is Shopify.')
64+
})
65+
})
66+
describe('liquid tag', function () {
67+
it('should treat lines starting with a hash as a comment', async function () {
68+
const src = [
69+
'{% liquid ',
70+
' # first comment line',
71+
' # second comment line',
72+
'',
73+
' # another comment line',
74+
' echo \'Hello \'',
75+
'',
76+
' # more comments',
77+
' echo \'goodbye\'',
78+
'-%}'
79+
].join('\n')
80+
const html = await liquid.parseAndRender(src)
81+
return expect(html).to.equal('Hello goodbye')
82+
})
83+
it('should handle lots of hashes', async function () {
84+
const src = [
85+
'{% liquid',
86+
' ##########################',
87+
' # spread inline comments #',
88+
' ##########################',
89+
'-%}'
90+
].join('\n')
91+
const html = await liquid.parseAndRender(src)
92+
return expect(html).to.equal('')
93+
})
94+
})
95+
})

‎test/unit/parser/tokenizer.ts

+29
Original file line numberDiff line numberDiff line change
@@ -525,4 +525,33 @@ describe('Tokenizer', function () {
525525
expect(() => tokenizer.readLiquidTagTokens()).to.throw(/illegal liquid tag syntax/)
526526
})
527527
})
528+
describe('#read inline comment tags', () => {
529+
it('should allow hash characters in tag names', () => {
530+
const tokenizer = new Tokenizer('{% # some comment %}', trie)
531+
const tokens = tokenizer.readTopLevelTokens()
532+
expect(tokens.length).to.equal(1)
533+
const tag = tokens[0] as TagToken
534+
expect(tag).instanceOf(TagToken)
535+
expect(tag.name).to.equal('#')
536+
expect(tag.args).to.equal('some comment')
537+
})
538+
it('should handle leading whitespace', () => {
539+
const tokenizer = new Tokenizer('{%\n # some comment %}', trie)
540+
const tokens = tokenizer.readTopLevelTokens()
541+
expect(tokens.length).to.equal(1)
542+
const tag = tokens[0] as TagToken
543+
expect(tag).instanceOf(TagToken)
544+
expect(tag.name).to.equal('#')
545+
expect(tag.args).to.equal('some comment')
546+
})
547+
it('should handle no trailing whitespace', () => {
548+
const tokenizer = new Tokenizer('{%\n #some comment %}', trie)
549+
const tokens = tokenizer.readTopLevelTokens()
550+
expect(tokens.length).to.equal(1)
551+
const tag = tokens[0] as TagToken
552+
expect(tag).instanceOf(TagToken)
553+
expect(tag.name).to.equal('#')
554+
expect(tag.args).to.equal('some comment')
555+
})
556+
})
528557
})

0 commit comments

Comments
 (0)
Please sign in to comment.