From 05dc7d7de439415de50f3442c7bc0979affa6a29 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Thu, 16 Dec 2021 10:15:12 -0500 Subject: [PATCH] Add `#` inline comment tag. This commit adds a new tag named `#` that behaves like a comment. Therefore it behaves as you'd expect any tag would work. The difference with the comment tag is that the comment is in the tag markup and that there is no block delimiter. What it looks like in practice: ```liquid {%- # this is an inline comment -%} {% # this too is an inline comment %} {% liquid # required args: assign product = product # optional args: assign should_show_border = should_show_border | default: true assign should_show_cursor = should_show_cursor | default: true %} {% liquid # This is a very long comment that spans multiple lines. # It looks very similar to what it would look like if you wrote # ruby code instead of liquid. But it doesn't have all the clunk # of having an open tag and a close tag with so many characters. %} ``` Co-authored-by: Dylan Thacker-Smith --- Gemfile | 2 +- lib/liquid.rb | 1 + lib/liquid/block_body.rb | 4 +- lib/liquid/locales/en.yml | 11 ++-- lib/liquid/tags/inline_comment.rb | 30 +++++++++ test/integration/tags/inline_comment_test.rb | 68 ++++++++++++++++++++ 6 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 lib/liquid/tags/inline_comment.rb create mode 100644 test/integration/tags/inline_comment_test.rb diff --git a/Gemfile b/Gemfile index daff580cf..e4c1adab7 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,6 @@ group :test do gem 'rubocop-performance', require: false platform :mri, :truffleruby do - gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master' + gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'inline-comment-fresh' end end diff --git a/lib/liquid.rb b/lib/liquid.rb index 28337943b..75c2f405f 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -29,6 +29,7 @@ module Liquid WhitespaceControl = '-' TagStart = /\{\%/ TagEnd = /\%\}/ + TagName = /#|\w+/ VariableSignature = /\(?[\w\-\.\[\]]\)?/ VariableSegment = /[\w\-]/ VariableStart = /\{\{/ diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 76ab0a857..37eb24c58 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -4,8 +4,8 @@ module Liquid class BlockBody - LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o - FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om + LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o + FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om WhitespaceOrNothing = /\A\s*\z/ TAGSTART = "{%" diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index eb35d8689..7e232de42 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -13,15 +13,16 @@ for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset" if: "Syntax Error in tag 'if' - Valid syntax: if [expression]" include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]" - unknown_tag: "Unknown tag '%{tag}'" + inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character" invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}" + render: "Syntax error in tag 'render' - Template name must be a quoted string" + table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3" + tag_never_closed: "'%{block_name}' tag was never closed" + tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}" unexpected_else: "%{block_name} tag does not expect 'else' tag" unexpected_outer_tag: "Unexpected outer '%{tag}' tag" - tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}" + unknown_tag: "Unknown tag '%{tag}'" variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}" - tag_never_closed: "'%{block_name}' tag was never closed" - table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3" - render: "Syntax error in tag 'render' - Template name must be a quoted string" argument: include: "Argument error in tag 'include' - Illegal template name" disabled: diff --git a/lib/liquid/tags/inline_comment.rb b/lib/liquid/tags/inline_comment.rb new file mode 100644 index 000000000..493cfddc7 --- /dev/null +++ b/lib/liquid/tags/inline_comment.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Liquid + class InlineComment < Tag + def initialize(tag_name, markup, options) + super + + # Semantically, a comment should only ignore everything after it on the line. + # Currently, this implementation doesn't support mixing a comment with another tag + # but we need to reserve future support for this and prevent the introduction + # of inline comments from being backward incompatible change. + # + # As such, we're forcing users to put a # symbol on every line otherwise this + # tag will throw an error. + if markup.match?(/\n\s*[^#\s]/) + raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid") + end + end + + def render_to_output_buffer(_context, output) + output + end + + def blank? + true + end + end + + Template.register_tag('#', InlineComment) +end diff --git a/test/integration/tags/inline_comment_test.rb b/test/integration/tags/inline_comment_test.rb new file mode 100644 index 000000000..66fbc48db --- /dev/null +++ b/test/integration/tags/inline_comment_test.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'test_helper' + +class InlineCommentTest < Minitest::Test + include Liquid + + def test_inline_comment_returns_nothing + assert_template_result('', '{%- # this is an inline comment -%}') + assert_template_result('', '{%-# this is an inline comment -%}') + assert_template_result('', '{% # this is an inline comment %}') + assert_template_result('', '{%# this is an inline comment %}') + end + + def test_inline_comment_does_not_require_a_space_after_the_pound_sign + assert_template_result('', '{%#this is an inline comment%}') + end + + def test_liquid_inline_comment_returns_nothing + assert_template_result('Hey there, how are you doing today?', <<~LIQUID) + {%- liquid + # This is how you'd write a block comment in a liquid tag. + # It looks a lot like what you'd have in ruby. + + # You can use it as inline documentation in your + # liquid blocks to explain why you're doing something. + echo "Hey there, " + + # It won't affect the output. + echo "how are you doing today?" + -%} + LIQUID + end + + def test_inline_comment_can_be_written_on_multiple_lines + assert_template_result('', <<~LIQUID) + {%- + # That kind of block comment is also allowed. + # It would only be a stylistic difference. + # Much like JavaScript's /* */ comments and their + # leading * on new lines. + -%} + LIQUID + end + + def test_inline_comment_multiple_pound_signs + assert_template_result('', <<~LIQUID) + {%- liquid + ###################################### + # We support comments like this too. # + ###################################### + -%} + LIQUID + end + + def test_inline_comments_require_the_pound_sign_on_every_new_line + assert_match_syntax_error("Each line of comments must be prefixed by the '#' character", <<~LIQUID) + {%- + # some comment + echo 'hello world' + -%} + LIQUID + end + + def test_inline_comment_does_not_support_nested_tags + assert_template_result(' -%}', "{%- # {% echo 'hello world' %} -%}") + end +end