From cadb0f101a70ee8ba99f820a9685f899bd698957 Mon Sep 17 00:00:00 2001 From: James Prior Date: Sat, 2 Jul 2022 07:36:50 +0100 Subject: [PATCH 1/4] style: remove unnecessary intermediate constant --- src/parser/tokenizer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 621f9000e6..70328932e9 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -207,8 +207,7 @@ export class Tokenizer { const begin = this.p let end = this.N if (this.readToDelimiter('\n') !== -1) end = this.p - const token = new LiquidTagToken(input, begin, end, options, file) - return token + return new LiquidTagToken(input, begin, end, options, file) } mkError (msg: string, begin: number) { From 0ae7d86848f27dcdcb8d8e6725ea0167d1703801 Mon Sep 17 00:00:00 2001 From: James Prior Date: Sat, 2 Jul 2022 15:20:38 +0100 Subject: [PATCH 2/4] feat: add inline comment tag --- src/builtin/tags/index.ts | 3 +- src/builtin/tags/inline-comment.ts | 11 +++ src/parser/tokenizer.ts | 11 +++ src/tokens/liquid-tag-token.ts | 2 +- src/tokens/tag-token.ts | 2 +- .../builtin/tags/inline-comment.ts | 95 +++++++++++++++++++ test/unit/parser/tokenizer.ts | 29 ++++++ 7 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 src/builtin/tags/inline-comment.ts create mode 100644 test/integration/builtin/tags/inline-comment.ts diff --git a/src/builtin/tags/index.ts b/src/builtin/tags/index.ts index 7951ea8c91..82ec5b4a49 100644 --- a/src/builtin/tags/index.ts +++ b/src/builtin/tags/index.ts @@ -18,10 +18,11 @@ import Break from './break' import Continue from './continue' import echo from './echo' import liquid from './liquid' +import inlineComment from './inline-comment' import { TagImplOptions } from '../../template/tag/tag-impl-options' const tags: { [key: string]: TagImplOptions } = { - 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 + 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 } export default tags diff --git a/src/builtin/tags/inline-comment.ts b/src/builtin/tags/inline-comment.ts new file mode 100644 index 0000000000..755d4ca3f2 --- /dev/null +++ b/src/builtin/tags/inline-comment.ts @@ -0,0 +1,11 @@ +import { TagToken } from '../../tokens/tag-token' +import { TopLevelToken } from '../../tokens/toplevel-token' +import { TagImplOptions } from '../../template/tag/tag-impl-options' + +export default { + parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { + if (tagToken.args.search(/\n\s*[^#\s]/g) !== -1) { + throw new Error('every line of an inline comment must start with a \'#\' character') + } + } +} as TagImplOptions diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 70328932e9..7a1912cf15 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -233,6 +233,17 @@ export class Tokenizer { return new IdentifierToken(this.input, begin, this.p, this.file) } + readTagName (): string { + this.skipBlank() + const begin = this.p + // Handle inline comment tags + if (this.input[this.p] === '#') { + return this.input.slice(begin, ++this.p) + } + while (this.peekType() & IDENTIFIER) ++this.p + return this.input.slice(begin, this.p) + } + readHashes (jekyllStyle?: boolean) { const hashes = [] while (true) { diff --git a/src/tokens/liquid-tag-token.ts b/src/tokens/liquid-tag-token.ts index d3681e1bc6..e8a0f6fa72 100644 --- a/src/tokens/liquid-tag-token.ts +++ b/src/tokens/liquid-tag-token.ts @@ -23,7 +23,7 @@ export class LiquidTagToken extends DelimitedToken { this.args = '' } else { const tokenizer = new Tokenizer(this.content, options.operatorsTrie) - this.name = tokenizer.readIdentifier().getText() + this.name = tokenizer.readTagName() if (!this.name) throw new TokenizationError(`illegal liquid tag syntax`, this) tokenizer.skipBlank() diff --git a/src/tokens/tag-token.ts b/src/tokens/tag-token.ts index 6163a9e1be..b8ebecce0c 100644 --- a/src/tokens/tag-token.ts +++ b/src/tokens/tag-token.ts @@ -19,7 +19,7 @@ export class TagToken extends DelimitedToken { super(TokenKind.Tag, value, input, begin, end, trimTagLeft, trimTagRight, file) const tokenizer = new Tokenizer(this.content, options.operatorsTrie) - this.name = tokenizer.readIdentifier().getText() + this.name = tokenizer.readTagName() if (!this.name) throw new TokenizationError(`illegal tag syntax`, this) tokenizer.skipBlank() diff --git a/test/integration/builtin/tags/inline-comment.ts b/test/integration/builtin/tags/inline-comment.ts new file mode 100644 index 0000000000..08d7ef4478 --- /dev/null +++ b/test/integration/builtin/tags/inline-comment.ts @@ -0,0 +1,95 @@ +import { Liquid } from '../../../../src/liquid' +import { expect, use } from 'chai' +import * as chaiAsPromised from 'chai-as-promised' + +use(chaiAsPromised) + +describe('tags/inline-comment', function () { + const liquid = new Liquid() + it('should ignore plain string', async function () { + const src = 'My name is {% # super %} Shopify.' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('My name is Shopify.') + }) + it('should ignore output tokens', async function () { + const src = '{% #\n{{ foo}} \n %}' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + it('should support whitespace control', async function () { + const src = '{%- # some comment \n -%}\nfoo' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('foo') + }) + it('should handle hash without trailing whitespace', async function () { + const src = '{% #some comment %}' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + it('should handle hash without leading whitespace', async function () { + const src = '{%#some comment %}' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + it('should handle empty comment', async function () { + const src = '{%#%}' + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + it('should support multiple lines', async function () { + const src = [ + '{%-', + ' # spread inline comments', + ' # over multiple lines', + '-%}' + ].join('\n') + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + it('should enforce leading hashes', async function () { + const src = [ + '{%-', + ' # spread inline comments', + ' over multiple lines', + '-%}' + ].join('\n') + return expect(liquid.parseAndRender(src)) + .to.be.rejectedWith(/every line of an inline comment must start with a '#' character/) + }) + describe('sync support', function () { + it('should ignore plain string', function () { + const src = 'My name is {% # super %} Shopify.' + const html = liquid.parseAndRenderSync(src) + return expect(html).to.equal('My name is Shopify.') + }) + }) + describe('liquid tag', function () { + it('should treat lines starting with a hash as a comment', async function () { + const src = [ + '{% liquid ', + ' # first comment line', + ' # second comment line', + '', + ' # another comment line', + ' echo \'Hello \'', + '', + ' # more comments', + ' echo \'goodbye\'', + '-%}' + ].join('\n') + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('Hello goodbye') + }) + it('should handle lots of hashes', async function () { + const src = [ + '{% liquid', + ' ##########################', + ' # spread inline comments #', + ' ##########################', + '-%}' + ].join('\n') + const html = await liquid.parseAndRender(src) + return expect(html).to.equal('') + }) + }) +}) diff --git a/test/unit/parser/tokenizer.ts b/test/unit/parser/tokenizer.ts index 2acf7efbcf..8ec6b0b945 100644 --- a/test/unit/parser/tokenizer.ts +++ b/test/unit/parser/tokenizer.ts @@ -525,4 +525,33 @@ describe('Tokenizer', function () { expect(() => tokenizer.readLiquidTagTokens()).to.throw(/illegal liquid tag syntax/) }) }) + describe('#read inline comment tags', () => { + it('should allow hash characters in tag names', () => { + const tokenizer = new Tokenizer('{% # some comment %}', trie) + const tokens = tokenizer.readTopLevelTokens() + expect(tokens.length).to.equal(1) + const tag = tokens[0] as TagToken + expect(tag).instanceOf(TagToken) + expect(tag.name).to.equal('#') + expect(tag.args).to.equal('some comment') + }) + it('should handle leading whitespace', () => { + const tokenizer = new Tokenizer('{%\n # some comment %}', trie) + const tokens = tokenizer.readTopLevelTokens() + expect(tokens.length).to.equal(1) + const tag = tokens[0] as TagToken + expect(tag).instanceOf(TagToken) + expect(tag.name).to.equal('#') + expect(tag.args).to.equal('some comment') + }) + it('should handle no trailing whitespace', () => { + const tokenizer = new Tokenizer('{%\n #some comment %}', trie) + const tokens = tokenizer.readTopLevelTokens() + expect(tokens.length).to.equal(1) + const tag = tokens[0] as TagToken + expect(tag).instanceOf(TagToken) + expect(tag.name).to.equal('#') + expect(tag.args).to.equal('some comment') + }) + }) }) From 200fb365c8e935241e80aca76f4d49145e6160d5 Mon Sep 17 00:00:00 2001 From: James Prior Date: Sun, 3 Jul 2022 07:36:45 +0100 Subject: [PATCH 3/4] refactor: use readIdentifier when reading tag names --- src/parser/tokenizer.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 7a1912cf15..7f02313ab0 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -235,13 +235,9 @@ export class Tokenizer { readTagName (): string { this.skipBlank() - const begin = this.p // Handle inline comment tags - if (this.input[this.p] === '#') { - return this.input.slice(begin, ++this.p) - } - while (this.peekType() & IDENTIFIER) ++this.p - return this.input.slice(begin, this.p) + if (this.input[this.p] === '#') return this.input.slice(this.p, ++this.p) + return this.readIdentifier().getText() } readHashes (jekyllStyle?: boolean) { From 134a66a6d240836cfd8a3c1f40a952a677256fa0 Mon Sep 17 00:00:00 2001 From: James Prior Date: Sun, 3 Jul 2022 17:02:02 +0100 Subject: [PATCH 4/4] docs: add inline comment tag --- docs/source/_data/sidebar.yml | 1 + docs/source/tags/inline_comment.md | 50 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 docs/source/tags/inline_comment.md diff --git a/docs/source/_data/sidebar.yml b/docs/source/_data/sidebar.yml index f8eaf46c96..d697eaa0c2 100644 --- a/docs/source/_data/sidebar.yml +++ b/docs/source/_data/sidebar.yml @@ -78,6 +78,7 @@ filters: tags: overview: overview.html + "# (inline comment)": inline_comment.html assign: assign.html capture: capture.html case: case.html diff --git a/docs/source/tags/inline_comment.md b/docs/source/tags/inline_comment.md new file mode 100644 index 0000000000..e40f0eb41a --- /dev/null +++ b/docs/source/tags/inline_comment.md @@ -0,0 +1,50 @@ +--- +title: "# (inline comment)" +--- + +{% since %}v9.38.0{% endsince %} + +Add comments to a Liquid template using an inline tag. Text enclosed in an inline comment tag will not be printed. + +Input +```liquid +Anything inside an inline comment tag will not be printed. +{% # this is an inline comment %} +But every line must start with a '#'. +{% + # this is a comment + # that spans multiple lines +%} +``` + +Output +```text +Anything inside an inline comment tag will not be printed. +But every line must start with a '#'. +``` + +Inline comments are useful inside `liquid` tags too. + +```liquid +{% liquid + # required args + assign product = collection.products.first + + # optional args + assign should_show_border = should_show_border | default: true + assign should_highlight = should_highlight | default: false +%} +``` + +But they don't work well for commenting out blocks of Liquid code. The `comment` block tag is the better option when you need to temporarily stop other tags from being executed. + +Input +```liquid +{%- # {% echo 'Welcome to LiquidJS!' %} -%} +{% comment %}{% echo 'Welcome to LiquidJS!' %}{% endcomment %} +``` + +Output +```text + -%} +```