Compare commits

..

22 Commits

Author SHA1 Message Date
Kaelig
be02670a54 Convert array to string interpolation, use 'unless' rather than 'if x !=' 2021-05-14 10:19:15 -07:00
Kaelig
b1cdd31858 Only output media attribute if needed, delete redundant type attribute in stylesheet_tag 2021-05-14 08:02:57 -07:00
Kaelig
8ece160330 Delete redudant type attribute in script_tag 2021-05-14 08:02:57 -07:00
Dylan Thacker-Smith
b9e0d28729 rubocop: Clarify that a config is a rubocop-shopify backport 2021-04-20 09:01:06 -07:00
Dylan Thacker-Smith
020f6b93c5 rubocop: Fix unsupported ruby version in TargetRubyVersion error 2021-04-20 09:00:20 -07:00
Dylan Thacker-Smith
cfe1637bdd Translate RangeError to Liquid::Error for truncatewords with large int (#1431) 2021-04-20 11:48:22 -04:00
Dylan Thacker-Smith
eab13a07d9 Add changelog entry for a recent fix 2021-03-29 13:43:01 -07:00
Dylan Thacker-Smith
ca96ca0fef Fix support for using a String subclass for the liquid source (#1421) 2021-03-29 16:22:05 -04:00
Marc-André Cournoyer
4e7a953e73 Merge pull request #1417 from Shopify/prep-release
Bump to 5.0.1 and add changelog for release
2021-03-24 16:55:10 -04:00
Marc-André Cournoyer
6ac2499f7f Remove internal tokenizer fix from History.md
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-03-24 16:53:45 -04:00
Marc-André Cournoyer
ff70161512 Remove internal fixes from History.md
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-03-24 16:52:57 -04:00
Marc-André Cournoyer
026157e128 Bump to 5.0.1 and add changelog for release 2021-03-24 16:19:19 -04:00
Charles-Philippe Clermont
bf64239ea6 Merge pull request #1414 from Shopify/fix/echo-parse-tree-visitor
Add ParseTreeVisitor to Echo tag
2021-03-24 09:38:48 -04:00
Charles-P. Clermont
c270a6f378 Add ParseTreeVisitor to Echo tag
This fixes theme-check#218, wherein variables used in echo tags are not
considered used by the linter. It is because our visitor doesn't see the
:variable_lookup's in the echo tag since the children array is empty.
But this array is empty because it is swallowed by the @variable.
2021-03-24 09:35:43 -04:00
Dylan Thacker-Smith
4fba61a802 Merge pull request #1402 from Shopify/rubocop-shopify
Use the rubocop-shopify gem and autocorrect
2021-03-16 17:39:09 -04:00
Dylan Thacker-Smith
6b6baece25 Merge pull request #1406 from ADTC/patch-2
Add a line to Sign the CLA as the first step in Workflow
2021-02-26 09:03:40 -05:00
ADTC
15b2d193ec Add a hint to sign CLA only if it's the first time
The CLA isn't signed per repository. They might have done so for another repository. In which case, they won't need to do that again.

Change attributed to @dylanahsmith

Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-02-26 16:31:16 +08:00
ADTC
fd712d134a Give info in the Contributing page about doc updates (#1405)
Please see #1399 for more info and discussion.
2021-02-25 09:31:18 -05:00
ADTC
0c2db998cf Add a line to Sign the CLA as the first step in Workflow
If a pull request is created by someone who didn't sign the CLA yet, the tests will fail. It requires the contributor to submit a new PR or add an insignificant commit (like an empty commit) to force the tests to rerun on the same PR. If we gently nudge a new contributor to sign the CLA in advance, we can help them avoid the hassle.
2021-02-25 09:59:51 +08:00
Dylan Thacker-Smith
9dac68cce1 Use the rubocop-shopify gem 2021-02-21 12:22:03 -05:00
Dylan Thacker-Smith
c50509b741 Commit rubocop remote configuration update 2021-02-21 12:02:55 -05:00
Dylan Thacker-Smith
cd66572514 Rubocop autocorrections to prepare for rubocop config updates 2021-02-21 12:01:59 -05:00
19 changed files with 85 additions and 1132 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
inherit_gem:
rubocop-shopify: rubocop.yml
inherit_from: inherit_from:
- 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
- .rubocop_todo.yml - .rubocop_todo.yml
require: rubocop-performance require: rubocop-performance
@@ -8,7 +10,7 @@ Performance:
Enabled: true Enabled: true
AllCops: AllCops:
TargetRubyVersion: 2.4 TargetRubyVersion: 2.5
NewCops: disable NewCops: disable
Exclude: Exclude:
- 'vendor/bundle/**/*' - 'vendor/bundle/**/*'
@@ -16,3 +18,7 @@ AllCops:
Naming/MethodName: Naming/MethodName:
Exclude: Exclude:
- 'example/server/liquid_servlet.rb' - 'example/server/liquid_servlet.rb'
# Backport https://github.com/Shopify/ruby-style-guide/pull/258
Layout/BeginEndAlignment:
Enabled: true

View File

@@ -5,6 +5,7 @@
* Bugfixes * Bugfixes
* Performance improvements * Performance improvements
* Features that are likely to be useful to the majority of Liquid users * Features that are likely to be useful to the majority of Liquid users
* Documentation updates that are concise and likely to be useful to the majority of Liquid users
## Things we won't merge ## Things we won't merge
@@ -14,12 +15,14 @@
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem) * Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
* Code that does not include tests * Code that does not include tests
* Code that breaks existing tests * Code that breaks existing tests
* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)
## Workflow ## Workflow
* [Sign the CLA](https://cla.shopify.com/) if you haven't already
* Fork the Liquid repository * Fork the Liquid repository
* Create a new branch in your fork * Create a new branch in your fork
* For updating [Liquid documentation](https://shopify.github.io/liquid/), create it from `gh-pages` branch. (You can skip tests.)
* If it makes sense, add tests for your code and/or run a performance benchmark * If it makes sense, add tests for your code and/or run a performance benchmark
* Make sure all tests pass (`bundle exec rake`) * Make sure all tests pass (`bundle exec rake`)
* Create a pull request * Create a pull request

View File

@@ -19,9 +19,10 @@ end
group :test do group :test do
gem 'rubocop', '~> 1.4', require: false gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-shopify', '~> 1.0.7', require: false
gem 'rubocop-performance', require: false gem 'rubocop-performance', require: false
platform :mri, :truffleruby do platform :mri, :truffleruby do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'inline-comment' gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
end end
end end

View File

@@ -1,9 +1,19 @@
# Liquid Change Log # Liquid Change Log
## Unreleased ## 5.0.2 (unreleased)
### Features ### Fixes
* Allow `#` to be used as an inline comment tag (#1401) [Dylan Thacker-Smith] * Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
## 5.0.1 / 2021-03-24
### Fixes
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
* Handle carriage return in newlines_to_br (#1391) [Unending]
### Performance Improvements
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
## 5.0.0 / 2021-01-06 ## 5.0.0 / 2021-01-06

View File

@@ -4,8 +4,8 @@ require 'English'
module Liquid module Liquid
class BlockBody class BlockBody
LiquidTagToken = /\A\s*(\w+|#)\s*(.*?)\z/o LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+|#)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/ WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%" TAGSTART = "{%"

View File

@@ -22,7 +22,6 @@
tag_never_closed: "'%{block_name}' tag was never closed" 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" 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" render: "Syntax error in tag 'render' - Template name must be a quoted string"
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
argument: argument:
include: "Argument error in tag 'include' - Illegal template name" include: "Argument error in tag 'include' - Illegal template name"
disabled: disabled:

View File

@@ -5,6 +5,7 @@ require 'bigdecimal'
module Liquid module Liquid
module StandardFilters module StandardFilters
MAX_INT = (1 << 31) - 1
HTML_ESCAPE = { HTML_ESCAPE = {
'&' => '&amp;', '&' => '&amp;',
'>' => '&gt;', '>' => '&gt;',
@@ -93,7 +94,13 @@ module Liquid
words = Utils.to_integer(words) words = Utils.to_integer(words)
words = 1 if words <= 0 words = 1 if words <= 0
wordlist = input.split(" ", words + 1) wordlist = begin
input.split(" ", words + 1)
rescue RangeError
raise if words + 1 < MAX_INT
# e.g. integer #{words} too big to convert to `int'
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
end
return input if wordlist.length <= words return input if wordlist.length <= words
wordlist.pop wordlist.pop

View File

@@ -12,6 +12,8 @@ module Liquid
# {% echo user | link %} # {% echo user | link %}
# #
class Echo < Tag class Echo < Tag
attr_reader :variable
def initialize(tag_name, markup, parse_context) def initialize(tag_name, markup, parse_context)
super super
@variable = Variable.new(markup, parse_context) @variable = Variable.new(markup, parse_context)
@@ -20,6 +22,12 @@ module Liquid
def render(context) def render(context)
@variable.render_to_output_buffer(context, +'') @variable.render_to_output_buffer(context, +'')
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.variable]
end
end
end end
Template.register_tag('echo', Echo) Template.register_tag('echo', Echo)

View File

@@ -1,25 +0,0 @@
# 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.
if markup.match?(/\n\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

View File

@@ -5,7 +5,7 @@ module Liquid
attr_reader :line_number, :for_liquid_tag attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false) def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source @source = source.to_s.to_str
@line_number = line_number || (line_numbers ? 1 : nil) @line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag @for_liquid_tag = for_liquid_tag
@tokens = tokenize @tokens = tokenize
@@ -24,7 +24,7 @@ module Liquid
private private
def tokenize def tokenize
return [] if @source.to_s.empty? return [] if @source.empty?
return @source.split("\n") if @for_liquid_tag return @source.split("\n") if @for_liquid_tag

View File

@@ -2,5 +2,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module Liquid module Liquid
VERSION = "5.0.0" VERSION = "5.0.2.alpha"
end end

View File

@@ -14,11 +14,11 @@ module ShopFilter
end end
def script_tag(url) def script_tag(url)
%(<script src="#{url}" type="text/javascript"></script>) %(<script src="#{url}"></script>)
end end
def stylesheet_tag(url, media = "all") def stylesheet_tag(url, media = "all")
%(<link href="#{url}" rel="stylesheet" type="text/css" media="#{media}" />) %(<link href="#{url}" rel="stylesheet" #{%(media="#{media}" ) unless media == 'all'}/>)
end end
def link_to(link, url, title = "") def link_to(link, url, title = "")

View File

@@ -171,13 +171,17 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three', @filters.truncatewords('one two three')) assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal( assert_equal(
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', 'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15) @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
) )
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)) assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1)) assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3)) assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
assert_equal('one two...', @filters.truncatewords("one two three four", 2)) assert_equal('one two...', @filters.truncatewords("one two three four", 2))
assert_equal('one...', @filters.truncatewords("one two three four", 0)) assert_equal('one...', @filters.truncatewords("one two three four", 0))
exception = assert_raises(Liquid::ArgumentError) do
@filters.truncatewords("one two three four", 1 << 31)
end
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
end end
def test_strip_html def test_strip_html

View File

@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
def test_include_tag_with_alias def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ", assert_template_result("Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_include_tag_for_alias def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ", assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_include_tag_with_default_name def test_include_tag_with_default_name

View File

@@ -1,59 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class InlineCommentTest < Minitest::Test
include Liquid
def test_tag_in_different_styles
assert_template_result('', '{% # This text gets ignored %}')
assert_template_result('', '{%# This text gets ignored #%}')
assert_template_result('', '{%# This text gets ignored %}')
assert_template_result('', '{%#- This text gets ignored -#%}')
end
def test_test_syntax_error
assert_template_result('fail', '{% #This doesnt work %}')
assert false
rescue
# ok good
end
def test_tag_ws_stripping
assert_template_result('', ' {%#- This text gets ignored -#%} ')
end
def test_comment_inline_tag
assert_template_result('ok', '{% echo "ok" # output something from a tag %}')
end
def test_comment_line_before_tag
assert_template_result('ok', '{% # this sort of comment also
echo "ok" %}')
end
def test_comment_inline_variable
assert_template_result('ok', '{{ "ok" # output something from a variable }}')
assert_template_result('ok', '{{ "OK" | downcase # output something from a variable }}')
end
def test_inside_liquid_tag
source = <<~LIQUID
{%- liquid
echo "before("
# This text gets ignored
echo ")after"
-%}
LIQUID
assert_template_result('before()after', source)
end
def test_multiline
assert_template_result('', '{% # this sort of comment also
# will just work, because it parses
# as a single call to the "#" tag %}')
end
end

View File

@@ -151,7 +151,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("Product: Draft 151cm ", assert_template_result("Product: Draft 151cm ",
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_render_tag_with_alias def test_render_tag_with_alias
@@ -161,7 +161,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("Product: Draft 151cm ", assert_template_result("Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_render_tag_for_alias def test_render_tag_for_alias
@@ -171,7 +171,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("Product: Draft 151cm Product: Element 155cm ", assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_render_tag_for def test_render_tag_for
@@ -181,7 +181,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("Product: Draft 151cm Product: Element 155cm ", assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_render_tag_forloop def test_render_tag_forloop
@@ -190,7 +190,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ", assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]) "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end end
def test_render_tag_for_drop def test_render_tag_for_drop
@@ -199,7 +199,7 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("123", assert_template_result("123",
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new) "{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
end end
def test_render_tag_with_drop def test_render_tag_with_drop
@@ -208,6 +208,6 @@ class RenderTagTest < Minitest::Test
) )
assert_template_result("TestEnumerable", assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new) "{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
end end
end end

View File

@@ -323,4 +323,18 @@ class TemplateTest < Minitest::Test
result = t.render('x' => 1, 'y' => 5) result = t.render('x' => 1, 'y' => 5)
assert_equal('12345', result) assert_equal('12345', result)
end end
def test_source_string_subclass
string_subclass = Class.new(String) do
# E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
def to_s
self
end
end
source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
assert_instance_of(string_subclass, source)
output = Template.parse(source).render!
assert_equal("x=2", output)
assert_instance_of(String, output)
end
end end

View File

@@ -26,6 +26,13 @@ class ParseTreeVisitorTest < Minitest::Test
) )
end end
def test_echo
assert_equal(
["test"],
visit(%({% echo test %}))
)
end
def test_if_condition def test_if_condition
assert_equal( assert_equal(
["test"], ["test"],