From 59950bff8739d34d1b005822924bd91fb29a8b3f Mon Sep 17 00:00:00 2001 From: Eric Chan Date: Wed, 13 Sep 2017 01:37:40 -0400 Subject: [PATCH 01/74] Fix sort_natural on sorting with non-string values --- lib/liquid/standardfilters.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 7c18c0d7..9ebb763e 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -143,11 +143,19 @@ module Liquid ary = InputIterator.new(input) if property.nil? - ary.sort { |a, b| a.casecmp(b) } + ary.sort { |a, b| a.to_s.casecmp(b.to_s) } elsif ary.empty? # The next two cases assume a non-empty array. [] elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? - ary.sort { |a, b| a[property].casecmp(b[property]) } + ary.sort do |a, b| + a = a[property] + b = b[property] + if a && b + a[property].to_s.casecmp(b[property].to_s) + else + a ? -1 : 1 + end + end end end From cfe1844de920970ceb91db600e987b18bb2c8462 Mon Sep 17 00:00:00 2001 From: Eric Chan Date: Wed, 13 Sep 2017 22:17:59 -0400 Subject: [PATCH 02/74] Added test coverage for sort_natural --- lib/liquid/standardfilters.rb | 2 +- test/integration/standard_filter_test.rb | 41 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 9ebb763e..f14ba1db 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -151,7 +151,7 @@ module Liquid a = a[property] b = b[property] if a && b - a[property].to_s.casecmp(b[property].to_s) + a.to_s.casecmp(b.to_s) else a ? -1 : 1 end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 9de2106d..c53aae1a 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -208,6 +208,47 @@ class StandardFiltersTest < Minitest::Test assert_equal expectation, @filters.sort(input, "price") end + def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last + input = [ + { "price" => "4", "handle" => "alpha" }, + { "handle" => "beta" }, + { "price" => "1", "handle" => "gamma" }, + { "handle" => "delta" }, + { "price" => 2, "handle" => "epsilon" } + ] + expectation = [ + { "price" => "1", "handle" => "gamma" }, + { "price" => 2, "handle" => "epsilon" }, + { "price" => "4", "handle" => "alpha" }, + { "handle" => "delta" }, + { "handle" => "beta" } + ] + assert_equal expectation, @filters.sort_natural(input, "price") + end + + def test_sort_natural_case_check + input = [ + { "key" => "X" }, + { "key" => "Y" }, + { "key" => "Z" }, + { "fake" => "t" }, + { "key" => "a" }, + { "key" => "b" }, + { "key" => "c" } + ] + expectation = [ + { "key" => "a" }, + { "key" => "b" }, + { "key" => "c" }, + { "key" => "X" }, + { "key" => "Y" }, + { "key" => "Z" }, + { "fake" => "t" } + ] + assert_equal expectation, @filters.sort_natural(input, "key") + assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"]) + end + def test_sort_empty_array assert_equal [], @filters.sort([], "a") end From deb10ebc7a8b1979b7c5daec83f2e69d9b68591d Mon Sep 17 00:00:00 2001 From: Eric Chan Date: Thu, 14 Sep 2017 02:00:43 -0400 Subject: [PATCH 03/74] Sorting support for data with undefined values --- lib/liquid/standardfilters.rb | 28 +++++++++++++++++------- test/integration/standard_filter_test.rb | 15 +++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index f14ba1db..34b4a491 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -121,17 +121,23 @@ module Liquid def sort(input, property = nil) ary = InputIterator.new(input) if property.nil? - ary.sort + ary.sort do |a, b| + if !a.nil? && !b.nil? + a <=> b + else + a.nil? ? 1 : -1 + end + end elsif ary.empty? # The next two cases assume a non-empty array. [] - elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? + elsif ary.all? { |el| el.respond_to?(:[]) } ary.sort do |a, b| a = a[property] b = b[property] - if a && b + if !a.nil? && !b.nil? a <=> b else - a ? -1 : 1 + a.nil? ? 1 : -1 end end end @@ -143,17 +149,23 @@ module Liquid ary = InputIterator.new(input) if property.nil? - ary.sort { |a, b| a.to_s.casecmp(b.to_s) } + ary.sort do |a, b| + if !a.nil? && !b.nil? + a.to_s.casecmp(b.to_s) + else + a.nil? ? 1 : -1 + end + end elsif ary.empty? # The next two cases assume a non-empty array. [] - elsif ary.first.respond_to?(:[]) && !ary.first[property].nil? + elsif ary.all? { |el| el.respond_to?(:[]) } ary.sort do |a, b| a = a[property] b = b[property] - if a && b + if !a.nil? && !b.nil? a.to_s.casecmp(b.to_s) else - a ? -1 : 1 + a.nil? ? 1 : -1 end end end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index c53aae1a..6c91f160 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -190,6 +190,11 @@ class StandardFiltersTest < Minitest::Test assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a") end + def test_sort_with_nils + assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]) + assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a") + end + def test_sort_when_property_is_sometimes_missing_puts_nils_last input = [ { "price" => 4, "handle" => "alpha" }, @@ -208,6 +213,16 @@ class StandardFiltersTest < Minitest::Test assert_equal expectation, @filters.sort(input, "price") end + def test_sort_natural + assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"]) + assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a") + end + + def test_sort_natural_with_nils + assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"]) + assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a") + end + def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last input = [ { "price" => "4", "handle" => "alpha" }, From 479d8fb4a40db6947670fd6b1386cb3e2d612883 Mon Sep 17 00:00:00 2001 From: printercu Date: Thu, 27 Sep 2018 17:13:35 +0300 Subject: [PATCH 04/74] Single regexp for strip_html --- lib/liquid/standardfilters.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index c5dbcb81..3d939aed 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -11,6 +11,12 @@ module Liquid "'".freeze => '''.freeze } HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ + STRIP_HTML = Regexp.union( + //m, + //m, + //m, + /<.*?>/m, + ) # Return the size of an array or of an string def size(input) @@ -102,8 +108,7 @@ module Liquid end def strip_html(input) - empty = ''.freeze - input.to_s.gsub(//m, empty).gsub(//m, empty).gsub(//m, empty).gsub(/<.*?>/m, empty) + input.to_s.gsub(STRIP_HTML, ''.freeze) end # Remove all newlines from the string From 89c1ba2b0e3f55b3cb1b1b5759c5db58e87effa6 Mon Sep 17 00:00:00 2001 From: printercu Date: Thu, 27 Sep 2018 17:24:01 +0300 Subject: [PATCH 05/74] Fix rubocop warning --- lib/liquid/standardfilters.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 3d939aed..d7e2dbb8 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -15,7 +15,7 @@ module Liquid //m, //m, //m, - /<.*?>/m, + /<.*?>/m ) # Return the size of an array or of an string From 4661700a979d419521c3a5264071cc48d24cb918 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Tue, 9 Oct 2018 11:13:19 +0200 Subject: [PATCH 06/74] bump to v4.0.1 --- lib/liquid/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index af15e071..64abb7ee 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,4 +1,4 @@ # encoding: utf-8 module Liquid - VERSION = "4.0.0" + VERSION = "4.0.1" end From 842986a9721de11e71387732be51951285225977 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 13 Sep 2018 16:37:38 -0400 Subject: [PATCH 07/74] Add `where` filter to standard filters Users of Liquid will often wish to filter an array to only those items that match a certain criteria. For example, showing "pinned" messages at the top of a list. Example usage: `{{ comments | where: "pinned" | first }}` or `{{ products | where: "category", "kitchen" }}` * Add where filter to standard filters * Add tests for new where functionality --- lib/liquid/standardfilters.rb | 32 +++++++++++ test/integration/standard_filter_test.rb | 72 ++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index c5dbcb81..29c2cad5 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -151,6 +151,20 @@ module Liquid end end + # Filter the elements of an array to those with a certain property value. + # By default the target is any truthy value. + def where(input, property, target_value = nil) + ary = InputIterator.new(input) + + if ary.empty? + [] + elsif ary.first.respond_to?(:[]) && target_value.nil? + ary.where_present(property) + elsif ary.first.respond_to?(:[]) + ary.where(property, target_value) + end + end + # Remove duplicate elements from an array # provide optional property with which to determine uniqueness def uniq(input, property = nil) @@ -429,6 +443,24 @@ module Liquid yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) end end + + def where(property, target_value) + select do |item| + item[property] == target_value + end + rescue TypeError + # Cannot index with the given property type (eg. indexing integers with strings + # which are only allowed to be indexed by other integers). + raise ArgumentError.new("cannot select the property `#{property}`") + end + + def where_present(property) + select { |item| item[property] } + rescue TypeError + # Cannot index with the given property type (eg. indexing integers with strings + # which are only allowed to be indexed by other integers). + raise ArgumentError.new("cannot select the property `#{property}`") + end end end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index d39472e0..75e6e8d2 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -558,6 +558,78 @@ class StandardFiltersTest < Minitest::Test assert_template_result('abc', "{{ 'abc' | date: '%D' }}") end + def test_where + input = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "beta", "ok" => false }, + { "handle" => "gamma", "ok" => false }, + { "handle" => "delta", "ok" => true } + ] + + expectation = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "delta", "ok" => true } + ] + + assert_equal expectation, @filters.where(input, "ok", true) + assert_equal expectation, @filters.where(input, "ok") + end + + def test_where_no_key_set + input = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "beta" }, + { "handle" => "gamma" }, + { "handle" => "delta", "ok" => true } + ] + + expectation = [ + { "handle" => "alpha", "ok" => true }, + { "handle" => "delta", "ok" => true } + ] + + assert_equal expectation, @filters.where(input, "ok", true) + assert_equal expectation, @filters.where(input, "ok") + end + + def test_where_non_array_map_input + assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok") + assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok") + end + + def test_where_indexable_but_non_map_value + assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) } + assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") } + end + + def test_where_non_boolean_value + input = [ + { "message" => "Bonjour!", "language" => "French" }, + { "message" => "Hello!", "language" => "English" }, + { "message" => "Hallo!", "language" => "German" } + ] + + assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French") + assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German") + assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English") + end + + def test_where_array_of_only_unindexable_values + assert_nil @filters.where([nil], "ok", true) + assert_nil @filters.where([nil], "ok") + end + + def test_where_no_target_value + input = [ + { "foo" => false }, + { "foo" => true }, + { "foo" => "for sure" }, + { "bar" => true } + ] + + assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo") + end + private def with_timezone(tz) From d789ec4175176127ca50e0e39e77540c5d7222d6 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 10 Sep 2018 13:36:27 -0400 Subject: [PATCH 08/74] Liquid::Traversal This enables traversal over whole document tree. --- lib/liquid/condition.rb | 4 +- lib/liquid/tags/assign.rb | 2 + lib/liquid/tags/case.rb | 2 + lib/liquid/tags/cycle.rb | 2 + lib/liquid/tags/for.rb | 3 +- lib/liquid/tags/if.rb | 10 +- lib/liquid/tags/include.rb | 2 + lib/liquid/tags/table_row.rb | 2 + lib/liquid/traversal.rb | 118 +++++++++++++++ test/integration/traversal_test.rb | 234 +++++++++++++++++++++++++++++ 10 files changed, 371 insertions(+), 8 deletions(-) create mode 100644 lib/liquid/traversal.rb create mode 100644 test/integration/traversal_test.rb diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index 3e798498..72bd2ee7 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -29,7 +29,7 @@ module Liquid @@operators end - attr_reader :attachment + attr_reader :attachment, :child_condition attr_accessor :left, :operator, :right def initialize(left = nil, operator = nil, right = nil) @@ -83,7 +83,7 @@ module Liquid protected - attr_reader :child_relation, :child_condition + attr_reader :child_relation private diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index f6cd5fad..ee6fa76a 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -10,6 +10,8 @@ module Liquid class Assign < Tag Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om + attr_reader :to, :from + def initialize(tag_name, markup, options) super if markup =~ Syntax diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 453b4d67..f55aa613 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -3,6 +3,8 @@ module Liquid Syntax = /(#{QuotedFragment})/o WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om + attr_reader :blocks, :left + def initialize(tag_name, markup, options) super @blocks = [] diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index ad116a61..6cf77a22 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -15,6 +15,8 @@ module Liquid SimpleSyntax = /\A#{QuotedFragment}+/o NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om + attr_reader :variables + def initialize(tag_name, markup, options) super case markup diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 6c95624d..c529aaec 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -46,8 +46,7 @@ module Liquid class For < Block Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o - attr_reader :collection_name - attr_reader :variable_name + attr_reader :collection_name, :variable_name, :limit, :from def initialize(tag_name, markup, options) super diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 904369dd..2a917410 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -14,21 +14,23 @@ module Liquid ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o BOOLEAN_OPERATORS = %w(and or) + attr_reader :blocks + def initialize(tag_name, markup, options) super @blocks = [] push_block('if'.freeze, markup) end + def nodelist + @blocks.map(&:attachment) + end + def parse(tokens) while parse_body(@blocks.last.attachment, tokens) end end - def nodelist - @blocks.map(&:attachment) - end - def unknown_tag(tag, markup, tokens) if ['elsif'.freeze, 'else'.freeze].include?(tag) push_block(tag, markup) diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index a8007036..a334d83f 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -16,6 +16,8 @@ module Liquid class Include < Tag Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o + attr_reader :template_name_expr, :variable_name_expr, :attributes + def initialize(tag_name, markup, options) super diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index cfdef33f..99d12ec7 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -2,6 +2,8 @@ module Liquid class TableRow < Block Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o + attr_reader :variable_name, :collection_name, :attributes + def initialize(tag_name, markup, options) super if markup =~ Syntax diff --git a/lib/liquid/traversal.rb b/lib/liquid/traversal.rb new file mode 100644 index 00000000..339fc2c0 --- /dev/null +++ b/lib/liquid/traversal.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Liquid + class Traversal + def self.for(node, callbacks = Hash.new(proc {})) + kase = CASES.find { |(klass, _)| node.is_a?(klass) }&.last + (kase || self).new(node, callbacks) + end + + def initialize(node, callbacks) + @node = node + @callbacks = callbacks + end + + def callback_for(*classes, &block) + callback = block + callback = ->(node, _) { block.call(node) } if block.arity.abs == 1 + callback = ->(_, _) { block.call } if block.arity.zero? + classes.each { |klass| @callbacks[klass] = callback } + self + end + + def traverse(context = nil) + children.map do |node| + item, new_context = @callbacks[node.class].call(node, context) + [ + item, + Traversal.for(node, @callbacks).traverse(new_context || context) + ] + end + end + + protected + + def children + @node.respond_to?(:nodelist) ? Array(@node.nodelist) : [] + end + + class Assign < Traversal + def children + [@node.from] + end + end + + class Case < Traversal + def children + [@node.left] + @node.blocks + end + end + + class Condition < Traversal + def children + [ + @node.left, @node.right, + @node.child_condition, @node.attachment + ].compact + end + end + + class Cycle < Traversal + def children + Array(@node.variables) + end + end + + class For < Traversal + def children + (super + [@node.limit, @node.from, @node.collection_name]).compact + end + end + + class If < Traversal + def children + @node.blocks + end + end + + class Include < Traversal + def children + [ + @node.template_name_expr, + @node.variable_name_expr + ] + @node.attributes.values + end + end + + class TableRow < Traversal + def children + super + @node.attributes.values + [@node.collection_name] + end + end + + class Variable < Traversal + def children + [@node.name] + @node.filters.flatten + end + end + + class VariableLookup < Traversal + def children + @node.lookups + end + end + + CASES = { + Liquid::Assign => Assign, + Liquid::Case => Case, + Liquid::Condition => Condition, + Liquid::Cycle => Cycle, + Liquid::For => For, + Liquid::If => If, + Liquid::Include => Include, + Liquid::TableRow => TableRow, + Liquid::Variable => Variable, + Liquid::VariableLookup => VariableLookup + }.freeze + end +end diff --git a/test/integration/traversal_test.rb b/test/integration/traversal_test.rb new file mode 100644 index 00000000..6254cb9b --- /dev/null +++ b/test/integration/traversal_test.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'liquid/traversal' + +class TraversalTest < Minitest::Test + include Liquid + + def test_variable + assert_equal( + ["test"], + traversal(%({{ test }})) + ) + end + + def test_varible_with_filter + assert_equal( + ["test", "infilter"], + traversal(%({{ test | split: infilter }})) + ) + end + + def test_dynamic_variable + assert_equal( + ["test", "inlookup"], + traversal(%({{ test[inlookup] }})) + ) + end + + def test_if_condition + assert_equal( + ["test"], + traversal(%({% if test %}{% endif %})) + ) + end + + def test_complex_if_condition + assert_equal( + ["test"], + traversal(%({% if 1 == 1 and 2 == test %}{% endif %})) + ) + end + + def test_if_body + assert_equal( + ["test"], + traversal(%({% if 1 == 1 %}{{ test }}{% endif %})) + ) + end + + def test_unless_condition + assert_equal( + ["test"], + traversal(%({% unless test %}{% endunless %})) + ) + end + + def test_complex_unless_condition + assert_equal( + ["test"], + traversal(%({% unless 1 == 1 and 2 == test %}{% endunless %})) + ) + end + + def test_unless_body + assert_equal( + ["test"], + traversal(%({% unless 1 == 1 %}{{ test }}{% endunless %})) + ) + end + + def test_elsif_condition + assert_equal( + ["test"], + traversal(%({% if 1 == 1 %}{% elsif test %}{% endif %})) + ) + end + + def test_complex_elsif_condition + assert_equal( + ["test"], + traversal(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})) + ) + end + + def test_elsif_body + assert_equal( + ["test"], + traversal(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})) + ) + end + + def test_else_body + assert_equal( + ["test"], + traversal(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})) + ) + end + + def test_case_left + assert_equal( + ["test"], + traversal(%({% case test %}{% endcase %})) + ) + end + + def test_case_condition + assert_equal( + ["test"], + traversal(%({% case 1 %}{% when test %}{% endcase %})) + ) + end + + def test_case_when_body + assert_equal( + ["test"], + traversal(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})) + ) + end + + def test_case_else_body + assert_equal( + ["test"], + traversal(%({% case 1 %}{% else %}{{ test }}{% endcase %})) + ) + end + + def test_for_in + assert_equal( + ["test"], + traversal(%({% for x in test %}{% endfor %})) + ) + end + + def test_for_limit + assert_equal( + ["test"], + traversal(%({% for x in (1..5) limit: test %}{% endfor %})) + ) + end + + def test_for_offset + assert_equal( + ["test"], + traversal(%({% for x in (1..5) offset: test %}{% endfor %})) + ) + end + + def test_for_body + assert_equal( + ["test"], + traversal(%({% for x in (1..5) %}{{ test }}{% endfor %})) + ) + end + + def test_tablerow_in + assert_equal( + ["test"], + traversal(%({% tablerow x in test %}{% endtablerow %})) + ) + end + + def test_tablerow_limit + assert_equal( + ["test"], + traversal(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})) + ) + end + + def test_tablerow_offset + assert_equal( + ["test"], + traversal(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})) + ) + end + + def test_tablerow_body + assert_equal( + ["test"], + traversal(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})) + ) + end + + def test_cycle + assert_equal( + ["test"], + traversal(%({% cycle test %})) + ) + end + + def test_assign + assert_equal( + ["test"], + traversal(%({% assign x = test %})) + ) + end + + def test_capture + assert_equal( + ["test"], + traversal(%({% capture x %}{{ test }}{% endcapture %})) + ) + end + + def test_include + assert_equal( + ["test"], + traversal(%({% include test %})) + ) + end + + def test_include_with + assert_equal( + ["test"], + traversal(%({% include "hai" with test %})) + ) + end + + def test_include_for + assert_equal( + ["test"], + traversal(%({% include "hai" for test %})) + ) + end + + private + + def traversal(template) + ParseTreeVisitor + .for(Template.parse(template).root) + .add_callback_for(VariableLookup, &:name) + .visit.flatten.compact + end +end From c11fc656cf67365fefac7d1f71a7c172cefd6a5e Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 10 Oct 2018 09:49:14 -0400 Subject: [PATCH 09/74] Colocate Traversal classes with classes they traverse This puts all knowledge of the traversal in the same file, and removes the need for a CASES registry. --- lib/liquid.rb | 1 + lib/liquid/condition.rb | 9 ++++ lib/liquid/tags/assign.rb | 6 +++ lib/liquid/tags/case.rb | 6 +++ lib/liquid/tags/cycle.rb | 6 +++ lib/liquid/tags/for.rb | 6 +++ lib/liquid/tags/if.rb | 6 +++ lib/liquid/tags/include.rb | 9 ++++ lib/liquid/tags/table_row.rb | 6 +++ lib/liquid/traversal.rb | 86 ++---------------------------- lib/liquid/variable.rb | 6 +++ lib/liquid/variable_lookup.rb | 6 +++ test/integration/traversal_test.rb | 1 - 13 files changed, 72 insertions(+), 82 deletions(-) diff --git a/lib/liquid.rb b/lib/liquid.rb index 7d9da269..eef582f2 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -45,6 +45,7 @@ module Liquid end require "liquid/version" +require 'liquid/traversal' require 'liquid/lexer' require 'liquid/parser' require 'liquid/i18n' diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index 72bd2ee7..aca9420d 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -128,6 +128,15 @@ module Liquid end end end + + class Traversal < Liquid::Traversal + def children + [ + @node.left, @node.right, + @node.child_condition, @node.attachment + ].compact + end + end end class ElseCondition < Condition diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index ee6fa76a..53c9eb2f 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -47,6 +47,12 @@ module Liquid 1 end end + + class Traversal < Liquid::Traversal + def children + [@node.from] + end + end end Template.register_tag('assign'.freeze, Assign) diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index f55aa613..433d4048 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -82,6 +82,12 @@ module Liquid block.attach(BlockBody.new) @blocks << block end + + class Traversal < Liquid::Traversal + def children + [@node.left] + @node.blocks + end + end end Template.register_tag('case'.freeze, Case) diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index 6cf77a22..aaac6869 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -53,6 +53,12 @@ module Liquid $1 ? Expression.parse($1) : nil end.compact end + + class Traversal < Liquid::Traversal + def children + Array(@node.variables) + end + end end Template.register_tag('cycle', Cycle) diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index c529aaec..68c1f926 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -191,6 +191,12 @@ module Liquid def render_else(context) @else_block ? @else_block.render(context) : ''.freeze end + + class Traversal < Liquid::Traversal + def children + (super + [@node.limit, @node.from, @node.collection_name]).compact + end + end end Template.register_tag('for'.freeze, For) diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 2a917410..54560c2f 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -110,6 +110,12 @@ module Liquid Condition.new(a) end end + + class Traversal < Liquid::Traversal + def children + @node.blocks + end + end end Template.register_tag('if'.freeze, If) diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index a334d83f..23cc5915 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -109,6 +109,15 @@ module Liquid file_system.read_template_file(context.evaluate(@template_name_expr)) end + + class Traversal < Liquid::Traversal + def children + [ + @node.template_name_expr, + @node.variable_name_expr + ] + @node.attributes.values + end + end end Template.register_tag('include'.freeze, Include) diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 99d12ec7..39945539 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -50,6 +50,12 @@ module Liquid result << "\n" result end + + class Traversal < Liquid::Traversal + def children + super + @node.attributes.values + [@node.collection_name] + end + end end Template.register_tag('tablerow'.freeze, TableRow) diff --git a/lib/liquid/traversal.rb b/lib/liquid/traversal.rb index 339fc2c0..0cebd96e 100644 --- a/lib/liquid/traversal.rb +++ b/lib/liquid/traversal.rb @@ -3,8 +3,11 @@ module Liquid class Traversal def self.for(node, callbacks = Hash.new(proc {})) - kase = CASES.find { |(klass, _)| node.is_a?(klass) }&.last - (kase || self).new(node, callbacks) + if defined?(node.class::Traversal) + node.class::Traversal + else + self + end.new(node, callbacks) end def initialize(node, callbacks) @@ -35,84 +38,5 @@ module Liquid def children @node.respond_to?(:nodelist) ? Array(@node.nodelist) : [] end - - class Assign < Traversal - def children - [@node.from] - end - end - - class Case < Traversal - def children - [@node.left] + @node.blocks - end - end - - class Condition < Traversal - def children - [ - @node.left, @node.right, - @node.child_condition, @node.attachment - ].compact - end - end - - class Cycle < Traversal - def children - Array(@node.variables) - end - end - - class For < Traversal - def children - (super + [@node.limit, @node.from, @node.collection_name]).compact - end - end - - class If < Traversal - def children - @node.blocks - end - end - - class Include < Traversal - def children - [ - @node.template_name_expr, - @node.variable_name_expr - ] + @node.attributes.values - end - end - - class TableRow < Traversal - def children - super + @node.attributes.values + [@node.collection_name] - end - end - - class Variable < Traversal - def children - [@node.name] + @node.filters.flatten - end - end - - class VariableLookup < Traversal - def children - @node.lookups - end - end - - CASES = { - Liquid::Assign => Assign, - Liquid::Case => Case, - Liquid::Condition => Condition, - Liquid::Cycle => Cycle, - Liquid::For => For, - Liquid::If => If, - Liquid::Include => Include, - Liquid::TableRow => TableRow, - Liquid::Variable => Variable, - Liquid::VariableLookup => VariableLookup - }.freeze end end diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 5f88eb38..1258b2da 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -138,5 +138,11 @@ module Liquid raise error end end + + class Traversal < Liquid::Traversal + def children + [@node.name] + @node.filters.flatten + end + end end end diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index 3ed4e4a4..f1b78343 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -78,5 +78,11 @@ module Liquid def state [@name, @lookups, @command_flags] end + + class Traversal < Liquid::Traversal + def children + @node.lookups + end + end end end diff --git a/test/integration/traversal_test.rb b/test/integration/traversal_test.rb index 6254cb9b..7bcfac8b 100644 --- a/test/integration/traversal_test.rb +++ b/test/integration/traversal_test.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'test_helper' -require 'liquid/traversal' class TraversalTest < Minitest::Test include Liquid From ff727016ef946bccceadf4c98384f45889081ef2 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 10 Oct 2018 09:51:37 -0400 Subject: [PATCH 10/74] s/callback_for/add_callback_for --- lib/liquid/traversal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/liquid/traversal.rb b/lib/liquid/traversal.rb index 0cebd96e..d9d6fa8b 100644 --- a/lib/liquid/traversal.rb +++ b/lib/liquid/traversal.rb @@ -15,7 +15,7 @@ module Liquid @callbacks = callbacks end - def callback_for(*classes, &block) + def add_callback_for(*classes, &block) callback = block callback = ->(node, _) { block.call(node) } if block.arity.abs == 1 callback = ->(_, _) { block.call } if block.arity.zero? From 7d13d882583bae1794a40be048a03662f35d5dde Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 11 Oct 2018 10:45:31 -0400 Subject: [PATCH 11/74] s/Traversal/ParseTreeVisitor --- lib/liquid.rb | 2 +- lib/liquid/condition.rb | 2 +- lib/liquid/{traversal.rb => parse_tree_visitor.rb} | 10 +++++----- lib/liquid/tags/assign.rb | 2 +- lib/liquid/tags/case.rb | 2 +- lib/liquid/tags/cycle.rb | 2 +- lib/liquid/tags/for.rb | 2 +- lib/liquid/tags/if.rb | 2 +- lib/liquid/tags/include.rb | 2 +- lib/liquid/tags/table_row.rb | 2 +- lib/liquid/variable.rb | 2 +- lib/liquid/variable_lookup.rb | 2 +- .../{traversal_test.rb => parse_tree_visitor_test.rb} | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) rename lib/liquid/{traversal.rb => parse_tree_visitor.rb} (78%) rename test/integration/{traversal_test.rb => parse_tree_visitor_test.rb} (98%) diff --git a/lib/liquid.rb b/lib/liquid.rb index eef582f2..770d2f91 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -45,7 +45,7 @@ module Liquid end require "liquid/version" -require 'liquid/traversal' +require 'liquid/parse_tree_visitor' require 'liquid/lexer' require 'liquid/parser' require 'liquid/i18n' diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index aca9420d..3b51682e 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -129,7 +129,7 @@ module Liquid end end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ @node.left, @node.right, diff --git a/lib/liquid/traversal.rb b/lib/liquid/parse_tree_visitor.rb similarity index 78% rename from lib/liquid/traversal.rb rename to lib/liquid/parse_tree_visitor.rb index d9d6fa8b..62f6c7b4 100644 --- a/lib/liquid/traversal.rb +++ b/lib/liquid/parse_tree_visitor.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module Liquid - class Traversal + class ParseTreeVisitor def self.for(node, callbacks = Hash.new(proc {})) - if defined?(node.class::Traversal) - node.class::Traversal + if defined?(node.class::ParseTreeVisitor) + node.class::ParseTreeVisitor else self end.new(node, callbacks) @@ -23,12 +23,12 @@ module Liquid self end - def traverse(context = nil) + def visit(context = nil) children.map do |node| item, new_context = @callbacks[node.class].call(node, context) [ item, - Traversal.for(node, @callbacks).traverse(new_context || context) + ParseTreeVisitor.for(node, @callbacks).visit(new_context || context) ] end end diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 53c9eb2f..c8d0574a 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -48,7 +48,7 @@ module Liquid end end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [@node.from] end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 433d4048..5036b279 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -83,7 +83,7 @@ module Liquid @blocks << block end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [@node.left] + @node.blocks end diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index aaac6869..17aa860d 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -54,7 +54,7 @@ module Liquid end.compact end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children Array(@node.variables) end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 68c1f926..b69aa78c 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -192,7 +192,7 @@ module Liquid @else_block ? @else_block.render(context) : ''.freeze end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children (super + [@node.limit, @node.from, @node.collection_name]).compact end diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 54560c2f..1451c25f 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -111,7 +111,7 @@ module Liquid end end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children @node.blocks end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 23cc5915..c9f2a288 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -110,7 +110,7 @@ module Liquid file_system.read_template_file(context.evaluate(@template_name_expr)) end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ @node.template_name_expr, diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 39945539..7f391cf0 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -51,7 +51,7 @@ module Liquid result end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children super + @node.attributes.values + [@node.collection_name] end diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 1258b2da..c31bffe5 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -139,7 +139,7 @@ module Liquid end end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [@node.name] + @node.filters.flatten end diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index f1b78343..8f7ad463 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -79,7 +79,7 @@ module Liquid [@name, @lookups, @command_flags] end - class Traversal < Liquid::Traversal + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children @node.lookups end diff --git a/test/integration/traversal_test.rb b/test/integration/parse_tree_visitor_test.rb similarity index 98% rename from test/integration/traversal_test.rb rename to test/integration/parse_tree_visitor_test.rb index 7bcfac8b..4ffef089 100644 --- a/test/integration/traversal_test.rb +++ b/test/integration/parse_tree_visitor_test.rb @@ -2,7 +2,7 @@ require 'test_helper' -class TraversalTest < Minitest::Test +class ParseTreeVisitorTest < Minitest::Test include Liquid def test_variable From 8217a8d86cbc4f5d0ea1c3f6e02dc271cbe3e73f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 15 Oct 2018 10:22:09 -0400 Subject: [PATCH 12/74] Add test for the full array structure --- test/integration/parse_tree_visitor_test.rb | 78 ++++++++++++--------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/test/integration/parse_tree_visitor_test.rb b/test/integration/parse_tree_visitor_test.rb index 4ffef089..6ad6a2d0 100644 --- a/test/integration/parse_tree_visitor_test.rb +++ b/test/integration/parse_tree_visitor_test.rb @@ -8,217 +8,228 @@ class ParseTreeVisitorTest < Minitest::Test def test_variable assert_equal( ["test"], - traversal(%({{ test }})) + visit(%({{ test }})) ) end def test_varible_with_filter assert_equal( ["test", "infilter"], - traversal(%({{ test | split: infilter }})) + visit(%({{ test | split: infilter }})) ) end def test_dynamic_variable assert_equal( ["test", "inlookup"], - traversal(%({{ test[inlookup] }})) + visit(%({{ test[inlookup] }})) ) end def test_if_condition assert_equal( ["test"], - traversal(%({% if test %}{% endif %})) + visit(%({% if test %}{% endif %})) ) end def test_complex_if_condition assert_equal( ["test"], - traversal(%({% if 1 == 1 and 2 == test %}{% endif %})) + visit(%({% if 1 == 1 and 2 == test %}{% endif %})) ) end def test_if_body assert_equal( ["test"], - traversal(%({% if 1 == 1 %}{{ test }}{% endif %})) + visit(%({% if 1 == 1 %}{{ test }}{% endif %})) ) end def test_unless_condition assert_equal( ["test"], - traversal(%({% unless test %}{% endunless %})) + visit(%({% unless test %}{% endunless %})) ) end def test_complex_unless_condition assert_equal( ["test"], - traversal(%({% unless 1 == 1 and 2 == test %}{% endunless %})) + visit(%({% unless 1 == 1 and 2 == test %}{% endunless %})) ) end def test_unless_body assert_equal( ["test"], - traversal(%({% unless 1 == 1 %}{{ test }}{% endunless %})) + visit(%({% unless 1 == 1 %}{{ test }}{% endunless %})) ) end def test_elsif_condition assert_equal( ["test"], - traversal(%({% if 1 == 1 %}{% elsif test %}{% endif %})) + visit(%({% if 1 == 1 %}{% elsif test %}{% endif %})) ) end def test_complex_elsif_condition assert_equal( ["test"], - traversal(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})) + visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %})) ) end def test_elsif_body assert_equal( ["test"], - traversal(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})) + visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %})) ) end def test_else_body assert_equal( ["test"], - traversal(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})) + visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %})) ) end def test_case_left assert_equal( ["test"], - traversal(%({% case test %}{% endcase %})) + visit(%({% case test %}{% endcase %})) ) end def test_case_condition assert_equal( ["test"], - traversal(%({% case 1 %}{% when test %}{% endcase %})) + visit(%({% case 1 %}{% when test %}{% endcase %})) ) end def test_case_when_body assert_equal( ["test"], - traversal(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})) + visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %})) ) end def test_case_else_body assert_equal( ["test"], - traversal(%({% case 1 %}{% else %}{{ test }}{% endcase %})) + visit(%({% case 1 %}{% else %}{{ test }}{% endcase %})) ) end def test_for_in assert_equal( ["test"], - traversal(%({% for x in test %}{% endfor %})) + visit(%({% for x in test %}{% endfor %})) ) end def test_for_limit assert_equal( ["test"], - traversal(%({% for x in (1..5) limit: test %}{% endfor %})) + visit(%({% for x in (1..5) limit: test %}{% endfor %})) ) end def test_for_offset assert_equal( ["test"], - traversal(%({% for x in (1..5) offset: test %}{% endfor %})) + visit(%({% for x in (1..5) offset: test %}{% endfor %})) ) end def test_for_body assert_equal( ["test"], - traversal(%({% for x in (1..5) %}{{ test }}{% endfor %})) + visit(%({% for x in (1..5) %}{{ test }}{% endfor %})) ) end def test_tablerow_in assert_equal( ["test"], - traversal(%({% tablerow x in test %}{% endtablerow %})) + visit(%({% tablerow x in test %}{% endtablerow %})) ) end def test_tablerow_limit assert_equal( ["test"], - traversal(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})) + visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %})) ) end def test_tablerow_offset assert_equal( ["test"], - traversal(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})) + visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %})) ) end def test_tablerow_body assert_equal( ["test"], - traversal(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})) + visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %})) ) end def test_cycle assert_equal( ["test"], - traversal(%({% cycle test %})) + visit(%({% cycle test %})) ) end def test_assign assert_equal( ["test"], - traversal(%({% assign x = test %})) + visit(%({% assign x = test %})) ) end def test_capture assert_equal( ["test"], - traversal(%({% capture x %}{{ test }}{% endcapture %})) + visit(%({% capture x %}{{ test }}{% endcapture %})) ) end def test_include assert_equal( ["test"], - traversal(%({% include test %})) + visit(%({% include test %})) ) end def test_include_with assert_equal( ["test"], - traversal(%({% include "hai" with test %})) + visit(%({% include "hai" with test %})) ) end def test_include_for assert_equal( ["test"], - traversal(%({% include "hai" for test %})) + visit(%({% include "hai" for test %})) + ) + end + + def test_preserve_tree_structure + assert_equal( + [[nil, [ + [nil, [[nil, [["other", []]]]]], + ["test", []], + ["xs", []] + ]]], + traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit ) end @@ -228,6 +239,9 @@ class ParseTreeVisitorTest < Minitest::Test ParseTreeVisitor .for(Template.parse(template).root) .add_callback_for(VariableLookup, &:name) - .visit.flatten.compact + end + + def visit(template) + traversal(template).visit.flatten.compact end end From 52ee303a3656e798f8a5a47a97a80640c9eb15ba Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 18 Oct 2018 09:41:53 -0400 Subject: [PATCH 13/74] s/block.call/yield --- lib/liquid/parse_tree_visitor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/liquid/parse_tree_visitor.rb b/lib/liquid/parse_tree_visitor.rb index 62f6c7b4..74f5563a 100644 --- a/lib/liquid/parse_tree_visitor.rb +++ b/lib/liquid/parse_tree_visitor.rb @@ -17,8 +17,8 @@ module Liquid def add_callback_for(*classes, &block) callback = block - callback = ->(node, _) { block.call(node) } if block.arity.abs == 1 - callback = ->(_, _) { block.call } if block.arity.zero? + callback = ->(node, _) { yield node } if block.arity.abs == 1 + callback = ->(_, _) { yield } if block.arity.zero? classes.each { |klass| @callbacks[klass] = callback } self end From 7c613e87cb1d2bc7247a60d9b438398d6af611a1 Mon Sep 17 00:00:00 2001 From: Tim Layton Date: Thu, 18 Oct 2018 23:10:56 -0700 Subject: [PATCH 14/74] Enable CLA bot --- .github/probots.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/probots.yml diff --git a/.github/probots.yml b/.github/probots.yml new file mode 100644 index 00000000..1491d275 --- /dev/null +++ b/.github/probots.yml @@ -0,0 +1,2 @@ +enabled: + - cla From 407c8abf301acd2ee8c4e4d0d35942dbc2ee04da Mon Sep 17 00:00:00 2001 From: David Cornu Date: Fri, 19 Oct 2018 14:52:16 -0400 Subject: [PATCH 15/74] Use TrailingCommaInLiteral TrailingCommaInArrayLiteral and TrailingCommaInHashLiteral were introduced in v0.53.0 and we're running v0.49.0. https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md#0530-2018-03-05 --- .rubocop.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4e180788..a622ef16 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -71,10 +71,7 @@ Style/Documentation: Style/ClassAndModuleChildren: Enabled: false -Style/TrailingCommaInArrayLiteral: - Enabled: false - -Style/TrailingCommaInHashLiteral: +Style/TrailingCommaInLiteral: Enabled: false Layout/IndentHash: @@ -125,6 +122,6 @@ Style/TrivialAccessors: Style/WordArray: Enabled: false -Naming/MethodName: +Style/MethodName: Exclude: - 'example/server/liquid_servlet.rb' From ca9e75db5329b864982218f70b9f3bab9a8ddc05 Mon Sep 17 00:00:00 2001 From: David Cornu Date: Fri, 19 Oct 2018 14:57:33 -0400 Subject: [PATCH 16/74] Reduce perceived complexity for #sort and #sort_natural --- lib/liquid/standardfilters.rb | 53 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index b675ba28..ee14f5cb 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -120,25 +120,16 @@ module Liquid # provide optional property with which to sort an array of hashes or drops def sort(input, property = nil) ary = InputIterator.new(input) + + return [] if ary.empty? + if property.nil? ary.sort do |a, b| - if !a.nil? && !b.nil? - a <=> b - else - a.nil? ? 1 : -1 - end + nil_safe_compare(a, b) end - elsif ary.empty? # The next two cases assume a non-empty array. - [] elsif ary.all? { |el| el.respond_to?(:[]) } ary.sort do |a, b| - a = a[property] - b = b[property] - if !a.nil? && !b.nil? - a <=> b - else - a.nil? ? 1 : -1 - end + nil_safe_compare(a[property], b[property]) end end end @@ -148,25 +139,15 @@ module Liquid def sort_natural(input, property = nil) ary = InputIterator.new(input) + return [] if ary.empty? + if property.nil? ary.sort do |a, b| - if !a.nil? && !b.nil? - a.to_s.casecmp(b.to_s) - else - a.nil? ? 1 : -1 - end + nil_safe_casecmp(a, b) end - elsif ary.empty? # The next two cases assume a non-empty array. - [] elsif ary.all? { |el| el.respond_to?(:[]) } ary.sort do |a, b| - a = a[property] - b = b[property] - if !a.nil? && !b.nil? - a.to_s.casecmp(b.to_s) - else - a.nil? ? 1 : -1 - end + nil_safe_casecmp(a[property], b[property]) end end end @@ -418,6 +399,22 @@ module Liquid result.is_a?(BigDecimal) ? result.to_f : result end + def nil_safe_compare(a, b) + if !a.nil? && !b.nil? + a <=> b + else + a.nil? ? 1 : -1 + end + end + + def nil_safe_casecmp(a, b) + if !a.nil? && !b.nil? + a.to_s.casecmp(b.to_s) + else + a.nil? ? 1 : -1 + end + end + class InputIterator include Enumerable From 2a2376bfd97f8a813934f832c1945af4ae60f46a Mon Sep 17 00:00:00 2001 From: David Cornu Date: Fri, 19 Oct 2018 15:06:36 -0400 Subject: [PATCH 17/74] Run :test before :rubocop in the default Rake task --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e3633f59..3dba4cf8 100755 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,7 @@ require 'rake/testtask' $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require "liquid/version" -task default: [:rubocop, :test] +task default: [:test, :rubocop] desc 'run test suite with default parser' Rake::TestTask.new(:base_test) do |t| From b41fc10d8e22ae4182b90ce3a069c88da14db16c Mon Sep 17 00:00:00 2001 From: Eric Chan Date: Mon, 3 Dec 2018 23:27:14 -0500 Subject: [PATCH 18/74] Updated changelog for v4.0.1 --- History.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/History.md b/History.md index 2dc8f3d0..620f7a12 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,42 @@ # Liquid Change Log +## 4.0.1 / 2018-10-09 + +### Changed +* Add benchmark group in Gemfile (#855) [Jerry Liu] +* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu] +* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith] +* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith] +* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield] +* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith] +* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith] +* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith] +* Remove Spy Gem (#896) [Dylan Thacker-Smith] +* Add `collection_name` and `variable_name` reader to `For` block (#909) +* Symbols render as strings (#920) [Justin Li] +* Remove default value from Hash objects (#932) [Maxime Bedard] +* Remove one level of nesting (#944) [Dylan Thacker-Smith] +* Update Rubocop version (#952) [Justin Li] +* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal] +* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith] +* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith] +* Add tests against Ruby 2.4 (#963) and 2.5 (#981) +* Replace RegExp literals with constants (#988) [Ashwin Maroli] +* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli] +* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith] +* Refactor and optimize rendering (#1005) [Christopher Aue] +* Add installation instruction (#1006) [Ben Gift] +* Remove Circle CI (#1010) +* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO] +* Rename deprecated Rubocop name (#1027) [Justin Li] + +### Fixed +* Handle `join` filter on non String joiners (#857) [Richard Monette] +* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861) +* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal] +* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz] +* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan] + ## 4.0.0 / 2016-12-14 / branch "4-0-stable" ### Changed From cec27ea326f3bdf09622363bf5df75f41e71f8bc Mon Sep 17 00:00:00 2001 From: Garland Zhang Date: Fri, 15 Feb 2019 15:55:43 -0500 Subject: [PATCH 19/74] Extract raise error line and some filters with begin/rescue blocks --- lib/liquid/standardfilters.rb | 60 +++++++++++--------- test/integration/standard_filter_test.rb | 71 ++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 26 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index ee14f5cb..60472588 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -128,8 +128,10 @@ module Liquid nil_safe_compare(a, b) end elsif ary.all? { |el| el.respond_to?(:[]) } - ary.sort do |a, b| - nil_safe_compare(a[property], b[property]) + begin + ary.sort { |a, b| nil_safe_compare(a[property], b[property]) } + rescue TypeError + raise_property_error(property) end end end @@ -146,8 +148,10 @@ module Liquid nil_safe_casecmp(a, b) end elsif ary.all? { |el| el.respond_to?(:[]) } - ary.sort do |a, b| - nil_safe_casecmp(a[property], b[property]) + begin + ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) } + rescue TypeError + raise_property_error(property) end end end @@ -160,9 +164,17 @@ module Liquid if ary.empty? [] elsif ary.first.respond_to?(:[]) && target_value.nil? - ary.where_present(property) + begin + ary.select { |item| item[property] } + rescue TypeError + raise_property_error(property) + end elsif ary.first.respond_to?(:[]) - ary.where(property, target_value) + begin + ary.select { |item| item[property] == target_value } + rescue TypeError + raise_property_error(property) + end end end @@ -176,7 +188,11 @@ module Liquid elsif ary.empty? # The next two cases assume a non-empty array. [] elsif ary.first.respond_to?(:[]) - ary.uniq{ |a| a[property] } + begin + ary.uniq { |a| a[property] } + rescue TypeError + raise_property_error(property) + end end end @@ -198,6 +214,8 @@ module Liquid r.is_a?(Proc) ? r.call : r end end + rescue TypeError + raise_property_error(property) end # Remove nils within an array @@ -210,7 +228,11 @@ module Liquid elsif ary.empty? # The next two cases assume a non-empty array. [] elsif ary.first.respond_to?(:[]) - ary.reject{ |a| a[property].nil? } + begin + ary.reject { |a| a[property].nil? } + rescue TypeError + raise_property_error(property) + end end end @@ -394,6 +416,10 @@ module Liquid private + def raise_property_error(property) + raise Liquid::ArgumentError.new("cannot select the property '#{property}'") + end + def apply_operation(input, operand, operation) result = Utils.to_number(input).send(operation, Utils.to_number(operand)) result.is_a?(BigDecimal) ? result.to_f : result @@ -460,24 +486,6 @@ module Liquid yield(e.respond_to?(:to_liquid) ? e.to_liquid : e) end end - - def where(property, target_value) - select do |item| - item[property] == target_value - end - rescue TypeError - # Cannot index with the given property type (eg. indexing integers with strings - # which are only allowed to be indexed by other integers). - raise ArgumentError.new("cannot select the property `#{property}`") - end - - def where_present(property) - select { |item| item[property] } - rescue TypeError - # Cannot index with the given property type (eg. indexing integers with strings - # which are only allowed to be indexed by other integers). - raise ArgumentError.new("cannot select the property `#{property}`") - end end end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index c32f4bcd..2709a6c0 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -268,10 +268,34 @@ class StandardFiltersTest < Minitest::Test assert_equal [], @filters.sort([], "a") end + def test_sort_invalid_property + foo = [ + [1], + [2], + [3] + ] + + assert_raises Liquid::ArgumentError do + @filters.sort(foo, "bar") + end + end + def test_sort_natural_empty_array assert_equal [], @filters.sort_natural([], "a") end + def test_sort_natural_invalid_property + foo = [ + [1], + [2], + [3] + ] + + assert_raises Liquid::ArgumentError do + @filters.sort_natural(foo, "bar") + end + end + def test_legacy_sort_hash assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 }) end @@ -295,10 +319,34 @@ class StandardFiltersTest < Minitest::Test assert_equal [], @filters.uniq([], "a") end + def test_uniq_invalid_property + foo = [ + [1], + [2], + [3] + ] + + assert_raises Liquid::ArgumentError do + @filters.uniq(foo, "bar") + end + end + def test_compact_empty_array assert_equal [], @filters.compact([], "a") end + def test_compact_invalid_property + foo = [ + [1], + [2], + [3] + ] + + assert_raises Liquid::ArgumentError do + @filters.compact(foo, "bar") + end + end + def test_reverse assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]) end @@ -364,6 +412,29 @@ class StandardFiltersTest < Minitest::Test assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new end + def test_map_returns_empty_on_2d_input_array + foo = [ + [1], + [2], + [3] + ] + + assert_raises Liquid::ArgumentError do + @filters.map(foo, "bar") + end + end + + def test_map_returns_empty_with_no_property + foo = [ + [1], + [2], + [3] + ] + assert_raises Liquid::ArgumentError do + @filters.map(foo, nil) + end + end + def test_sort_works_on_enumerables assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new end From f59f6dea83234b71a7be994078ee8e9dddf60dc3 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Fri, 22 Feb 2019 23:02:56 +0530 Subject: [PATCH 20/74] Fix simple RuboCop offenses and update TODO file (#1062) * Fix Layout/EmptyLineAfterMagicComment offense * Fix Layout/ExtraSpacing offense * Fix Layout/ClosingParenthesisIndentation offenses * Fix Style/MutableConstant offense * Fix Style/UnneededInterpolation offenses * Fix Style/RedundantParentheses offenses * Update TODO config for RuboCop * Add executable bit to test/test_helper.rb ref: https://travis-ci.org/Shopify/liquid/jobs/488169512#L578 --- .rubocop_todo.yml | 66 +++--------------------- lib/liquid/expression.rb | 2 +- lib/liquid/i18n.rb | 2 +- lib/liquid/lexer.rb | 2 +- lib/liquid/standardfilters.rb | 2 +- lib/liquid/tags/if.rb | 2 +- lib/liquid/variable_lookup.rb | 2 +- lib/liquid/version.rb | 3 +- test/integration/error_handling_test.rb | 6 +-- test/integration/parsing_quirks_test.rb | 2 +- test/integration/standard_filter_test.rb | 6 +-- test/test_helper.rb | 0 test/unit/condition_unit_test.rb | 4 +- 13 files changed, 23 insertions(+), 76 deletions(-) mode change 100644 => 100755 test/test_helper.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8860c199..bd80b385 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,30 +1,11 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-11-22 11:35:55 -0500 using RuboCop version 0.49.1. +# on 2019-02-03 21:12:39 +0530 using RuboCop version 0.49.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# Cop supports --auto-correct. -Layout/ClosingParenthesisIndentation: - Exclude: - - 'test/integration/error_handling_test.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Layout/EmptyLineAfterMagicComment: - Exclude: - - 'lib/liquid/version.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'test/integration/parsing_quirks_test.rb' - # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. @@ -51,31 +32,26 @@ Lint/InheritException: Exclude: - 'lib/liquid/interrupts.rb' -# Offense count: 1 -Lint/ScriptPermission: - Exclude: - - 'test/test_helper.rb' - -# Offense count: 52 +# Offense count: 51 Metrics/AbcSize: Max: 56 -# Offense count: 13 +# Offense count: 11 Metrics/CyclomaticComplexity: Max: 12 -# Offense count: 620 +# Offense count: 639 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 294 -# Offense count: 102 +# Offense count: 108 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 37 -# Offense count: 9 +# Offense count: 7 Metrics/PerceivedComplexity: Max: 11 @@ -143,23 +119,6 @@ Style/GuardClause: - 'lib/liquid/variable.rb' - 'test/unit/tokenizer_unit_test.rb' -# Offense count: 4 -# Configuration parameters: SupportedStyles. -# SupportedStyles: snake_case, camelCase -Style/MethodName: - EnforcedStyle: snake_case - -# Offense count: 6 -# Cop supports --auto-correct. -Style/MutableConstant: - Exclude: - - 'lib/liquid/expression.rb' - - 'lib/liquid/lexer.rb' - - 'lib/liquid/standardfilters.rb' - - 'lib/liquid/tags/if.rb' - - 'lib/liquid/variable_lookup.rb' - - 'lib/liquid/version.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. @@ -190,12 +149,6 @@ Style/PercentLiteralDelimiters: - 'test/integration/assign_test.rb' - 'test/integration/standard_filter_test.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Style/RedundantParentheses: - Exclude: - - 'test/unit/condition_unit_test.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/RedundantSelf: @@ -227,13 +180,6 @@ Style/TernaryParentheses: - 'lib/liquid/context.rb' - 'lib/liquid/utils.rb' -# Offense count: 4 -# Cop supports --auto-correct. -Style/UnneededInterpolation: - Exclude: - - 'lib/liquid/i18n.rb' - - 'test/integration/standard_filter_test.rb' - # Offense count: 2 # Cop supports --auto-correct. Style/UnneededPercentQ: diff --git a/lib/liquid/expression.rb b/lib/liquid/expression.rb index 1d01cddf..98be6db6 100644 --- a/lib/liquid/expression.rb +++ b/lib/liquid/expression.rb @@ -19,7 +19,7 @@ module Liquid 'false'.freeze => false, 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze, 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze - } + }.freeze SINGLE_QUOTED_STRING = /\A'(.*)'\z/m DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m diff --git a/lib/liquid/i18n.rb b/lib/liquid/i18n.rb index e998d581..2671507e 100644 --- a/lib/liquid/i18n.rb +++ b/lib/liquid/i18n.rb @@ -26,7 +26,7 @@ module Liquid def interpolate(name, vars) name.gsub(/%\{(\w+)\}/) do # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym] - "#{vars[$1.to_sym]}" + (vars[$1.to_sym]).to_s end end diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb index e9114df1..f290744d 100644 --- a/lib/liquid/lexer.rb +++ b/lib/liquid/lexer.rb @@ -12,7 +12,7 @@ module Liquid ')'.freeze => :close_round, '?'.freeze => :question, '-'.freeze => :dash - } + }.freeze IDENTIFIER = /[a-zA-Z_][\w-]*\??/ SINGLE_STRING_LITERAL = /'[^\']*'/ DOUBLE_STRING_LITERAL = /"[^\"]*"/ diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 60472588..e63d388c 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -9,7 +9,7 @@ module Liquid '<'.freeze => '<'.freeze, '"'.freeze => '"'.freeze, "'".freeze => '''.freeze - } + }.freeze HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ # Return the size of an array or of an string diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 1451c25f..02da42b7 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -12,7 +12,7 @@ module Liquid class If < Block Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o - BOOLEAN_OPERATORS = %w(and or) + BOOLEAN_OPERATORS = %w(and or).freeze attr_reader :blocks diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index 8f7ad463..62f4877c 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -1,7 +1,7 @@ module Liquid class VariableLookup SQUARE_BRACKETED = /\A\[(.*)\]\z/m - COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze] + COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze attr_reader :name, :lookups diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index 64abb7ee..388a0c07 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,4 +1,5 @@ # encoding: utf-8 + module Liquid - VERSION = "4.0.1" + VERSION = "4.0.1".freeze end diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index ba818612..b2d186cf 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -123,7 +123,7 @@ class ErrorHandlingTest < Minitest::Test ', error_mode: :warn, line_numbers: true - ) + ) assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'], template.warnings.map(&:message) @@ -140,7 +140,7 @@ class ErrorHandlingTest < Minitest::Test ', error_mode: :strict, line_numbers: true - ) + ) end assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message @@ -158,7 +158,7 @@ class ErrorHandlingTest < Minitest::Test bla ', line_numbers: true - ) + ) end assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message diff --git a/test/integration/parsing_quirks_test.rb b/test/integration/parsing_quirks_test.rb index 23742dc7..29cb6d6d 100644 --- a/test/integration/parsing_quirks_test.rb +++ b/test/integration/parsing_quirks_test.rb @@ -99,7 +99,7 @@ class ParsingQuirksTest < Minitest::Test # After the messed up quotes a filter without parameters (reverse) should work # but one with parameters (remove) shouldn't be detected. assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}") - assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}") + assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}") end end diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 2709a6c0..0aa8bbf3 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -465,9 +465,9 @@ class StandardFiltersTest < Minitest::Test assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y") assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y") - assert_equal "#{Date.today.year}", @filters.date('now', '%Y') - assert_equal "#{Date.today.year}", @filters.date('today', '%Y') - assert_equal "#{Date.today.year}", @filters.date('Today', '%Y') + assert_equal Date.today.year.to_s, @filters.date('now', '%Y') + assert_equal Date.today.year.to_s, @filters.date('today', '%Y') + assert_equal Date.today.year.to_s, @filters.date('Today', '%Y') assert_nil @filters.date(nil, "%B") diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100644 new mode 100755 diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index 5afa2b7f..b3b90e88 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -24,9 +24,9 @@ class ConditionUnitTest < Minitest::Test assert_evaluates_true 1, '<=', 1 # negative numbers assert_evaluates_true 1, '>', -1 - assert_evaluates_true (-1), '<', 1 + assert_evaluates_true -1, '<', 1 assert_evaluates_true 1.0, '>', -1.0 - assert_evaluates_true (-1.0), '<', 1.0 + assert_evaluates_true -1.0, '<', 1.0 end def test_default_operators_evalute_false From ed73794f82b6a7fc529c7bd85761c225e7bc5a09 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Fri, 22 Feb 2019 12:53:54 -0500 Subject: [PATCH 21/74] Preserve existing strip_html behaviour for weird inputs --- lib/liquid/standardfilters.rb | 11 +++++++---- test/integration/standard_filter_test.rb | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index d7e2dbb8..af9b30b8 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -11,12 +11,12 @@ module Liquid "'".freeze => '''.freeze } HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ - STRIP_HTML = Regexp.union( + STRIP_HTML_BLOCKS = Regexp.union( //m, //m, - //m, - /<.*?>/m + //m ) + STRIP_HTML_TAGS = /<.*?>/m # Return the size of an array or of an string def size(input) @@ -108,7 +108,10 @@ module Liquid end def strip_html(input) - input.to_s.gsub(STRIP_HTML, ''.freeze) + empty = ''.freeze + result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty) + result.gsub!(STRIP_HTML_TAGS, empty) + result end # Remove all newlines from the string diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index d39472e0..d7a575e8 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -177,6 +177,9 @@ class StandardFiltersTest < Minitest::Test assert_equal 'test', @filters.strip_html("test") assert_equal 'test', @filters.strip_html("test") assert_equal '', @filters.strip_html(nil) + + # Quirk of the existing implementation + assert_equal 'foo;', @filters.strip_html("<<") end def test_join From e2d9907df280ee4099757bd57d240de44690d695 Mon Sep 17 00:00:00 2001 From: Clayton Smith Date: Thu, 7 Mar 2019 14:01:10 -0500 Subject: [PATCH 22/74] Validate the character encoding in url_decode. --- lib/liquid/standardfilters.rb | 7 ++++++- test/integration/standard_filter_test.rb | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 68ff2c51..0bddfa97 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -52,7 +52,12 @@ module Liquid end def url_decode(input) - CGI.unescape(input.to_s) unless input.nil? + return if input.nil? + + result = CGI.unescape(input.to_s) + raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding? + + result end def slice(input, offset, length = nil) diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 98818eb9..60909510 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -158,6 +158,10 @@ class StandardFiltersTest < Minitest::Test assert_equal '1', @filters.url_decode(1) assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)) assert_nil @filters.url_decode(nil) + exception = assert_raises Liquid::ArgumentError do + @filters.url_decode('%ff') + end + assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message end def test_truncatewords From 8013df8ca2e25b72260d945f0d6bfb1484a7d04c Mon Sep 17 00:00:00 2001 From: Justin Li Date: Fri, 8 Mar 2019 15:43:46 -0500 Subject: [PATCH 23/74] v4.0.2 --- History.md | 11 +++++++++++ lib/liquid/version.rb | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 620f7a12..91f62578 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,16 @@ # Liquid Change Log +## 4.0.2 / 2019-03-08 + +### Changed +* Add `where` filter (#1026) [Samuel Doiron] +* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber] +* Improve `strip_html` performance (#1032) [printercu] + +### Fixed +* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang] +* Validate the character encoding in url_decode (#1070) [Clayton Smith] + ## 4.0.1 / 2018-10-09 ### Changed diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index 388a0c07..07bd0f15 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,5 +1,5 @@ # encoding: utf-8 module Liquid - VERSION = "4.0.1".freeze + VERSION = "4.0.2".freeze end From 39fecd06db1424ea8b7e222141b3f5686feb6270 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Tue, 12 Mar 2019 12:18:22 -0400 Subject: [PATCH 24/74] Fix interrupts through includes --- lib/liquid/block_body.rb | 1 + test/integration/tags/include_tag_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 266d8ed7..ba294155 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -89,6 +89,7 @@ module Liquid break else # Other non-Block tags render_node_to_output(node, output, context) + break if context.interrupt? # might have happened through an include end idx += 1 end diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 1d854b0c..9c188d57 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -30,6 +30,9 @@ class TestFileSystem when 'assignments' "{% assign foo = 'bar' %}" + when 'break' + "{% break %}" + else template_path end @@ -242,4 +245,9 @@ class IncludeTagTest < Minitest::Test assert_equal [], template.errors end + + def test_break_through_include + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}" + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}" + end end # IncludeTagTest From f2f467bdbc248e7bf26b4f5e552cfbce6052f811 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Tue, 12 Mar 2019 12:43:48 -0400 Subject: [PATCH 25/74] v4.0.3 --- History.md | 5 +++++ lib/liquid/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 91f62578..9a82faab 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,10 @@ # Liquid Change Log +## 4.0.3 / 2019-03-12 + +### Fixed +* Fix break and continue tags inside included templates in loops (#1072) [Justin Li] + ## 4.0.2 / 2019-03-08 ### Changed diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index 07bd0f15..da01c476 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,5 +1,5 @@ # encoding: utf-8 module Liquid - VERSION = "4.0.2".freeze + VERSION = "4.0.3".freeze end From 06c4789dc58b6719620ab1a344cee17618526591 Mon Sep 17 00:00:00 2001 From: Richard Monette Date: Mon, 18 Mar 2019 14:51:26 -0400 Subject: [PATCH 26/74] update Rubocop for trailing comma styles --- .rubocop.yml | 17 ++++--- .rubocop_todo.yml | 116 ++++++++++++++++++++++++++++++++++++---------- Gemfile | 2 +- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a622ef16..6a306a1e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,6 @@ -inherit_from: ./.rubocop_todo.yml +inherit_from: + - .rubocop_todo.yml + - ./.rubocop_todo.yml AllCops: Exclude: @@ -29,7 +31,7 @@ Lint/ParenthesesAsGroupedExpression: Lint/UnusedBlockArgument: Enabled: false -Lint/EndAlignment: +Layout/EndAlignment: EnforcedStyleAlignWith: variable Lint/UnusedMethodArgument: @@ -59,7 +61,7 @@ Style/BracesAroundHashParameters: Style/NumericLiterals: Enabled: false -Layout/SpaceInsideBrackets: +Layout/SpaceInsideArrayLiteralBrackets: Enabled: false Layout/SpaceBeforeBlockBraces: @@ -71,7 +73,10 @@ Style/Documentation: Style/ClassAndModuleChildren: Enabled: false -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: + Enabled: false + +Style/TrailingCommaInHashLiteral: Enabled: false Layout/IndentHash: @@ -104,7 +109,7 @@ Style/SymbolLiteral: Performance/Count: Enabled: false -Style/ConstantName: +Naming/ConstantName: Enabled: false Layout/CaseIndentation: @@ -122,6 +127,6 @@ Style/TrivialAccessors: Style/WordArray: Enabled: false -Style/MethodName: +Naming/MethodName: Exclude: - 'example/server/liquid_servlet.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bd80b385..6868a7a2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,14 +1,22 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-02-03 21:12:39 +0530 using RuboCop version 0.49.1. +# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: Include, TreatCommentsAsGroupSeparators. +# Include: **/*.gemspec +Gemspec/OrderedDependencies: + Exclude: + - 'liquid.gemspec' + # Offense count: 5 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle. # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent Layout/IndentHeredoc: Exclude: @@ -17,7 +25,7 @@ Layout/IndentHeredoc: # Offense count: 6 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line Layout/MultilineMethodCallBraceLayout: Exclude: @@ -26,38 +34,65 @@ Layout/MultilineMethodCallBraceLayout: # Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle. # SupportedStyles: runtime_error, standard_error Lint/InheritException: Exclude: - 'lib/liquid/interrupts.rb' -# Offense count: 51 +# Offense count: 1 +# Configuration parameters: CheckForMethodsWithNoSideEffects. +Lint/Void: + Exclude: + - 'lib/liquid/parse_context.rb' + +# Offense count: 54 Metrics/AbcSize: Max: 56 -# Offense count: 11 +# Offense count: 12 Metrics/CyclomaticComplexity: Max: 12 -# Offense count: 639 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 294 - -# Offense count: 108 +# Offense count: 112 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 37 -# Offense count: 7 +# Offense count: 8 Metrics/PerceivedComplexity: Max: 11 +# Offense count: 52 +# Configuration parameters: Blacklist. +# Blacklist: END, (?-mix:EO[A-Z]{1}) +Naming/HeredocDelimiterNaming: + Exclude: + - 'test/integration/assign_test.rb' + - 'test/integration/capture_test.rb' + - 'test/integration/trim_mode_test.rb' + +# Offense count: 23 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id +Naming/UncommunicativeMethodParamName: + Exclude: + - 'example/server/example_servlet.rb' + - 'lib/liquid/condition.rb' + - 'lib/liquid/context.rb' + - 'lib/liquid/standardfilters.rb' + - 'lib/liquid/tags/if.rb' + - 'lib/liquid/utils.rb' + - 'lib/liquid/variable.rb' + - 'test/integration/filter_test.rb' + - 'test/integration/standard_filter_test.rb' + - 'test/integration/tags/for_tag_test.rb' + - 'test/integration/template_test.rb' + - 'test/unit/condition_unit_test.rb' + # Offense count: 10 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle. # SupportedStyles: prefer_alias, prefer_alias_method Style/Alias: Exclude: @@ -69,14 +104,23 @@ Style/Alias: - 'lib/liquid/tags/include.rb' - 'lib/liquid/variable.rb' +# Offense count: 22 +Style/CommentedKeyword: + Enabled: false + # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'lib/liquid/errors.rb' +# Offense count: 1 +Style/DateTime: + Exclude: + - 'test/unit/context_unit_test.rb' + # Offense count: 2 # Cop supports --auto-correct. Style/EmptyCaseCondition: @@ -86,7 +130,7 @@ Style/EmptyCaseCondition: # Offense count: 5 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: @@ -96,11 +140,28 @@ Style/EmptyMethod: - 'test/integration/tags/include_tag_test.rb' - 'test/unit/context_unit_test.rb' +# Offense count: 3 +# Cop supports --auto-correct. +Style/Encoding: + Exclude: + - 'lib/liquid/version.rb' + - 'liquid.gemspec' + - 'test/integration/standard_filter_test.rb' + # Offense count: 2 -# Configuration parameters: SupportedStyles. -# SupportedStyles: annotated, template +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Exclude: + - 'Rakefile' + - 'liquid.gemspec' + +# Offense count: 7 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: annotated, template, unannotated Style/FormatStringToken: - EnforcedStyle: template + Exclude: + - 'test/integration/filter_test.rb' + - 'test/integration/hash_ordering_test.rb' # Offense count: 14 # Configuration parameters: MinBodyLength. @@ -121,7 +182,7 @@ Style/GuardClause: # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. +# Configuration parameters: EnforcedStyle, MinBodyLength. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: @@ -129,7 +190,7 @@ Style/Next: # Offense count: 4 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# Configuration parameters: AutoCorrect, EnforcedStyle. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: @@ -166,14 +227,14 @@ Style/Semicolon: # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: MinSize, SupportedStyles. +# Configuration parameters: MinSize. # SupportedStyles: percent, brackets Style/SymbolArray: EnforcedStyle: brackets # Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. +# Configuration parameters: EnforcedStyle, AllowSafeAssignment. # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex Style/TernaryParentheses: Exclude: @@ -188,7 +249,12 @@ Style/UnneededPercentQ: # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: MaxLineLength. Style/WhileUntilModifier: Exclude: - 'lib/liquid/tags/case.rb' + +# Offense count: 640 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 294 diff --git a/Gemfile b/Gemfile index bdeefac7..6e3136d9 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ group :benchmark, :test do end group :test do - gem 'rubocop', '~> 0.49.0' + gem 'rubocop', '~> 0.53.0' platform :mri do gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6' From 274f07880604576c745e431a3e41d6fe97bede43 Mon Sep 17 00:00:00 2001 From: Richard Monette Date: Tue, 12 Mar 2019 17:28:16 -0400 Subject: [PATCH 27/74] defer hash allocation in parse_filter_expressions add exploration of GC object allocation remove performance test can actually remove one more if branch use named locals to improve readability --- lib/liquid/variable.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index c31bffe5..717b1a2a 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -95,16 +95,17 @@ module Liquid def parse_filter_expressions(filter_name, unparsed_args) filter_args = [] - keyword_args = {} + keyword_args = nil unparsed_args.each do |a| if matches = a.match(JustTagAttributes) + keyword_args ||= {} keyword_args[matches[1]] = Expression.parse(matches[2]) else filter_args << Expression.parse(a) end end result = [filter_name, filter_args] - result << keyword_args unless keyword_args.empty? + result << keyword_args if keyword_args result end From 8d1cd4145335d9ee0c7cc0b99da0143c8334bba2 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Fri, 15 Mar 2019 14:49:31 -0400 Subject: [PATCH 28/74] Add {% liquid %}, {% echo %}, and {% local %} tags --- Gemfile | 2 +- lib/liquid/block_body.rb | 60 +++++++++++++-- lib/liquid/locales/en.yml | 1 + lib/liquid/tag.rb | 4 +- lib/liquid/tags/assign.rb | 6 +- lib/liquid/tags/echo.rb | 24 ++++++ lib/liquid/tags/local.rb | 30 ++++++++ lib/liquid/tokenizer.rb | 18 +++-- test/integration/tags/echo_test.rb | 11 +++ test/integration/tags/liquid_tag_test.rb | 98 ++++++++++++++++++++++++ test/integration/tags/local_test.rb | 15 ++++ test/test_helper.rb | 6 +- 12 files changed, 257 insertions(+), 18 deletions(-) create mode 100644 lib/liquid/tags/echo.rb create mode 100644 lib/liquid/tags/local.rb create mode 100644 test/integration/tags/echo_test.rb create mode 100644 test/integration/tags/liquid_tag_test.rb create mode 100644 test/integration/tags/local_test.rb diff --git a/Gemfile b/Gemfile index bdeefac7..60f1a7a2 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,6 @@ group :test do gem 'rubocop', '~> 0.49.0' platform :mri do - gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6' + gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'liquid-tag' end end diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 266d8ed7..5f865450 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -1,6 +1,7 @@ module Liquid class BlockBody - FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om + LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o + FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om WhitespaceOrNothing = /\A\s*\z/ TAGSTART = "{%".freeze @@ -13,8 +14,46 @@ module Liquid @blank = true end - def parse(tokenizer, parse_context) + def parse(tokenizer, parse_context, &block) parse_context.line_number = tokenizer.line_number + + if tokenizer.for_liquid_tag + parse_for_liquid_tag(tokenizer, parse_context, &block) + else + parse_for_document(tokenizer, parse_context, &block) + end + end + + private def parse_for_liquid_tag(tokenizer, parse_context) + while token = tokenizer.shift + case + when token.empty? + # pass + else + unless token =~ LiquidTagToken + next if token =~ WhitespaceOrNothing + # line isn't empty but didn't match tag syntax, yield and let the + # caller raise a syntax error + return yield token, token + end + tag_name = $1 + markup = $2 + unless tag = registered_tags[tag_name] + # end parsing if we reach an unknown tag and let the caller decide + # determine how to proceed + return yield tag_name, markup + end + new_tag = tag.parse(tag_name, markup, tokenizer, parse_context) + @blank &&= new_tag.blank? + @nodelist << new_tag + end + parse_context.line_number = tokenizer.line_number + end + + yield nil, nil + end + + private def parse_for_document(tokenizer, parse_context, &block) while token = tokenizer.shift next if token.empty? case @@ -23,9 +62,20 @@ module Liquid unless token =~ FullToken raise_missing_tag_terminator(token, parse_context) end - tag_name = $1 - markup = $2 - # fetch the tag from registered blocks + tag_name = $2 + markup = $4 + + if parse_context.line_number + # newlines inside the tag should increase the line number, + # particularly important for multiline {% liquid %} tags + parse_context.line_number += $1.count("\n".freeze) + $3.count("\n".freeze) + end + + if tag_name == 'liquid'.freeze + liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true) + next parse(liquid_tag_tokenizer, parse_context, &block) + end + unless tag = registered_tags[tag_name] # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index 48b3b1d8..56c068f3 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -3,6 +3,7 @@ syntax: tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}" assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]" + local: "Syntax Error in 'local' - Valid syntax: local [var] = [source]" capture: "Syntax Error in 'capture' - Valid syntax: capture [var]" case: "Syntax Error in 'case' - Valid syntax: case [condition]" case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}" diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index 06970c18..ac99ecda 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -5,8 +5,8 @@ module Liquid include ParserSwitching class << self - def parse(tag_name, markup, tokenizer, options) - tag = new(tag_name, markup, options) + def parse(tag_name, markup, tokenizer, parse_context) + tag = new(tag_name, markup, parse_context) tag.parse(tokenizer) tag end diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index c8d0574a..55753d8b 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -10,6 +10,10 @@ module Liquid class Assign < Tag Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om + def self.syntax_error_translation_key + "errors.syntax.assign".freeze + end + attr_reader :to, :from def initialize(tag_name, markup, options) @@ -18,7 +22,7 @@ module Liquid @to = $1 @from = Variable.new($2, options) else - raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze) + raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key)) end end diff --git a/lib/liquid/tags/echo.rb b/lib/liquid/tags/echo.rb new file mode 100644 index 00000000..acb9ab45 --- /dev/null +++ b/lib/liquid/tags/echo.rb @@ -0,0 +1,24 @@ +module Liquid + # Echo outputs an expression + # + # {% echo monkey %} + # {% echo user.name %} + # + # This is identical to variable output syntax, like {{ foo }}, but works + # inside {% liquid %} tags. The full syntax is supported, including filters: + # + # {% echo user | link %} + # + class Echo < Tag + def initialize(tag_name, markup, parse_context) + super + @variable = Variable.new(markup, parse_context) + end + + def render(context) + @variable.render(context) + end + end + + Template.register_tag('echo'.freeze, Echo) +end diff --git a/lib/liquid/tags/local.rb b/lib/liquid/tags/local.rb new file mode 100644 index 00000000..572920ea --- /dev/null +++ b/lib/liquid/tags/local.rb @@ -0,0 +1,30 @@ +require_relative 'assign' + +module Liquid + # Local sets a variable in the current scope. + # + # {% local foo = 'monkey' %} + # + # You can then use the variable later in the scope. + # + # {% if true %} + # {% local foo = 'monkey' %} + # {{ foo }} outputs monkey + # {% endif %} + # {{ foo }} outputs nothing + # + class Local < Assign + def self.syntax_error_translation_key + "errors.syntax.local".freeze + end + + def render(context) + val = @from.render(context) + context[@to] = val + context.resource_limits.assign_score += assign_score_of(val) + ''.freeze + end + end + + Template.register_tag('local'.freeze, Local) +end diff --git a/lib/liquid/tokenizer.rb b/lib/liquid/tokenizer.rb index d03657e8..d3fd6766 100644 --- a/lib/liquid/tokenizer.rb +++ b/lib/liquid/tokenizer.rb @@ -1,25 +1,31 @@ module Liquid class Tokenizer - attr_reader :line_number + attr_reader :line_number, :for_liquid_tag - def initialize(source, line_numbers = false) + def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false) @source = source - @line_number = line_numbers ? 1 : nil + @line_number = line_number || (line_numbers ? 1 : nil) + @for_liquid_tag = for_liquid_tag @tokens = tokenize end def shift - token = @tokens.shift - @line_number += token.count("\n") if @line_number && token + token = @tokens.shift or return + + if @line_number + @line_number += @for_liquid_tag ? 1 : token.count("\n") + end + token end private def tokenize - @source = @source.source if @source.respond_to?(:source) return [] if @source.to_s.empty? + return @source.split("\n") if @for_liquid_tag + tokens = @source.split(TemplateParser) # removes the rogue empty element at the beginning of the array diff --git a/test/integration/tags/echo_test.rb b/test/integration/tags/echo_test.rb new file mode 100644 index 00000000..ed5b821d --- /dev/null +++ b/test/integration/tags/echo_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class EchoTest < Minitest::Test + include Liquid + + def test_echo_outputs_its_input + assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' }) + {%- echo variable-name | upcase -%} + LIQUID + end +end diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb new file mode 100644 index 00000000..2cfd13a4 --- /dev/null +++ b/test/integration/tags/liquid_tag_test.rb @@ -0,0 +1,98 @@ +require 'test_helper' + +class LiquidTagTest < Minitest::Test + include Liquid + + def test_liquid_tag + assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3]) + {%- liquid + echo array | join: " " + -%} + LIQUID + + assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3]) + {%- liquid + for value in array + echo value + unless forloop.last + echo " " + endunless + endfor + -%} + LIQUID + + assert_template_result('4 8 12', <<~LIQUID, 'array' => [1, 2, 3]) + {%- liquid + for value in array + local double_value = value | times: 2 + echo double_value | times: 2 + unless forloop.last + echo " " + endunless + endfor + + echo double_value + -%} + LIQUID + + assert_template_result('abc', <<~LIQUID) + {%- liquid echo "a" -%} + b + {%- liquid echo "c" -%} + LIQUID + end + + def test_liquid_tag_errors + assert_match_syntax_error("syntax error (line 1): Unknown tag 'error'", <<~LIQUID) + {%- liquid error no such tag -%} + LIQUID + + assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID) + {{ test }} + + {%- + liquid + for value in array + + error no such tag + endfor + -%} + LIQUID + + assert_match_syntax_error("syntax error (line 2): Unknown tag '!!! the guards are vigilant'", <<~LIQUID) + {%- liquid + !!! the guards are vigilant + -%} + LIQUID + + assert_match_syntax_error("syntax error (line 4): 'for' tag was never closed", <<~LIQUID) + {%- liquid + for value in array + echo 'forgot to close the for tag' + -%} + LIQUID + end + + def test_cannot_open_blocks_living_past_a_liquid_tag + assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID) + {%- liquid + if true + -%} + {%- endif -%} + LIQUID + end + + def test_quirk_can_close_blocks_created_before_a_liquid_tag + assert_template_result("42", <<~LIQUID) + {%- if true -%} + 42 + {%- liquid endif -%} + LIQUID + end + + def test_liquid_tag_in_raw + assert_template_result("{% liquid echo 'test' %}\n", <<~LIQUID) + {% raw %}{% liquid echo 'test' %}{% endraw %} + LIQUID + end +end diff --git a/test/integration/tags/local_test.rb b/test/integration/tags/local_test.rb new file mode 100644 index 00000000..412ee26a --- /dev/null +++ b/test/integration/tags/local_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +class LocalTest < Minitest::Test + include Liquid + + def test_local_is_scope_aware + assert_template_result('value', <<~LIQUID) + {%- if true -%} + {%- local variable-name = 'value' -%} + {{- variable-name -}} + {%- endif -%} + {{- variable-name -}} + LIQUID + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index ac5ab53d..c55328d2 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -37,18 +37,18 @@ module Minitest include Liquid def assert_template_result(expected, template, assigns = {}, message = nil) - assert_equal expected, Template.parse(template).render!(assigns), message + assert_equal expected, Template.parse(template, line_numbers: true).render!(assigns), message end def assert_template_result_matches(expected, template, assigns = {}, message = nil) return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp - assert_match expected, Template.parse(template).render!(assigns), message + assert_match expected, Template.parse(template, line_numbers: true).render!(assigns), message end def assert_match_syntax_error(match, template, assigns = {}) exception = assert_raises(Liquid::SyntaxError) do - Template.parse(template).render(assigns) + Template.parse(template, line_numbers: true).render(assigns) end assert_match match, exception.message end From 951abb67ee29af54378960815eb7118c90414653 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Mon, 8 Apr 2019 18:34:39 -0400 Subject: [PATCH 29/74] Remove {% local %} tag --- lib/liquid/tags/local.rb | 30 ------------------------ test/integration/tags/liquid_tag_test.rb | 5 ++-- test/integration/tags/local_test.rb | 15 ------------ 3 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 lib/liquid/tags/local.rb delete mode 100644 test/integration/tags/local_test.rb diff --git a/lib/liquid/tags/local.rb b/lib/liquid/tags/local.rb deleted file mode 100644 index 572920ea..00000000 --- a/lib/liquid/tags/local.rb +++ /dev/null @@ -1,30 +0,0 @@ -require_relative 'assign' - -module Liquid - # Local sets a variable in the current scope. - # - # {% local foo = 'monkey' %} - # - # You can then use the variable later in the scope. - # - # {% if true %} - # {% local foo = 'monkey' %} - # {{ foo }} outputs monkey - # {% endif %} - # {{ foo }} outputs nothing - # - class Local < Assign - def self.syntax_error_translation_key - "errors.syntax.local".freeze - end - - def render(context) - val = @from.render(context) - context[@to] = val - context.resource_limits.assign_score += assign_score_of(val) - ''.freeze - end - end - - Template.register_tag('local'.freeze, Local) -end diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb index 2cfd13a4..56ca712b 100644 --- a/test/integration/tags/liquid_tag_test.rb +++ b/test/integration/tags/liquid_tag_test.rb @@ -21,16 +21,17 @@ class LiquidTagTest < Minitest::Test -%} LIQUID - assert_template_result('4 8 12', <<~LIQUID, 'array' => [1, 2, 3]) + assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3]) {%- liquid for value in array - local double_value = value | times: 2 + assign double_value = value | times: 2 echo double_value | times: 2 unless forloop.last echo " " endunless endfor + echo " " echo double_value -%} LIQUID diff --git a/test/integration/tags/local_test.rb b/test/integration/tags/local_test.rb deleted file mode 100644 index 412ee26a..00000000 --- a/test/integration/tags/local_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'test_helper' - -class LocalTest < Minitest::Test - include Liquid - - def test_local_is_scope_aware - assert_template_result('value', <<~LIQUID) - {%- if true -%} - {%- local variable-name = 'value' -%} - {{- variable-name -}} - {%- endif -%} - {{- variable-name -}} - LIQUID - end -end From e6ed804ca56d6bcc818e439bee372cd5d8a379c3 Mon Sep 17 00:00:00 2001 From: Justin Li Date: Mon, 8 Apr 2019 18:43:09 -0400 Subject: [PATCH 30/74] Fix line number tracking after a non-empty blank token --- lib/liquid/block_body.rb | 6 +++--- test/integration/tags/liquid_tag_test.rb | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 5f865450..4861bca8 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -27,11 +27,11 @@ module Liquid private def parse_for_liquid_tag(tokenizer, parse_context) while token = tokenizer.shift case - when token.empty? - # pass + when token.empty? || token =~ WhitespaceOrNothing + # pass, but assign line_number below, since it could change even when + # the token is empty else unless token =~ LiquidTagToken - next if token =~ WhitespaceOrNothing # line isn't empty but didn't match tag syntax, yield and let the # caller raise a syntax error return yield token, token diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb index 56ca712b..d4be1282 100644 --- a/test/integration/tags/liquid_tag_test.rb +++ b/test/integration/tags/liquid_tag_test.rb @@ -74,6 +74,11 @@ class LiquidTagTest < Minitest::Test LIQUID end + def test_line_number_is_correct_after_a_blank_token + assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n\n error %}") + assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}") + end + def test_cannot_open_blocks_living_past_a_liquid_tag assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID) {%- liquid From 7dc488a73b8bbe8717f318ed510bacc090abe3be Mon Sep 17 00:00:00 2001 From: Justin Li Date: Tue, 9 Apr 2019 15:19:47 -0400 Subject: [PATCH 31/74] Simplifications from review --- lib/liquid/block_body.rb | 8 ++------ lib/liquid/locales/en.yml | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 4861bca8..cffb15bb 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -26,11 +26,7 @@ module Liquid private def parse_for_liquid_tag(tokenizer, parse_context) while token = tokenizer.shift - case - when token.empty? || token =~ WhitespaceOrNothing - # pass, but assign line_number below, since it could change even when - # the token is empty - else + unless token.empty? || token =~ WhitespaceOrNothing unless token =~ LiquidTagToken # line isn't empty but didn't match tag syntax, yield and let the # caller raise a syntax error @@ -73,7 +69,7 @@ module Liquid if tag_name == 'liquid'.freeze liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true) - next parse(liquid_tag_tokenizer, parse_context, &block) + next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block) end unless tag = registered_tags[tag_name] diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index 56c068f3..48b3b1d8 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -3,7 +3,6 @@ syntax: tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}" assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]" - local: "Syntax Error in 'local' - Valid syntax: local [var] = [source]" capture: "Syntax Error in 'capture' - Valid syntax: capture [var]" case: "Syntax Error in 'case' - Valid syntax: case [condition]" case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}" From c89ce9c2ed523f05152dfb0dfc2ecb32afff6b76 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Wed, 17 Apr 2019 18:48:36 +0100 Subject: [PATCH 32/74] use bytesize, not length --- lib/liquid/block_body.rb | 2 +- lib/liquid/tags/assign.rb | 2 +- lib/liquid/tags/capture.rb | 2 +- test/integration/template_test.rb | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index ba294155..d4fab30f 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -115,7 +115,7 @@ module Liquid end def check_resources(context, node_output) - context.resource_limits.render_length += node_output.length + context.resource_limits.render_length += node_output.bytesize return unless context.resource_limits.reached? raise MemoryError.new("Memory limits exceeded".freeze) end diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index c8d0574a..3b696dd9 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -37,7 +37,7 @@ module Liquid def assign_score_of(val) if val.instance_of?(String) - val.length + val.bytesize elsif val.instance_of?(Array) || val.instance_of?(Hash) sum = 1 # Uses #each to avoid extra allocations. diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index 86743567..d5b8e297 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -25,7 +25,7 @@ module Liquid def render(context) output = super context.scopes.last[@to] = output - context.resource_limits.assign_score += output.length + context.resource_limits.assign_score += output.bytesize ''.freeze end diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index d10e1c5e..0dc0ae5f 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -139,6 +139,16 @@ class TemplateTest < Minitest::Test refute_nil t.resource_limits.assign_score end + def test_resource_limits_assign_score_counts_bytes_not_characters + t = Template.parse("{% assign foo = 'すごい' %}") + t.render + assert_equal 9, t.resource_limits.assign_score + + t = Template.parse("{% capture foo %}すごい{% endcapture %}") + t.render + assert_equal 9, t.resource_limits.assign_score + end + def test_resource_limits_assign_score_nested t = Template.parse("{% assign foo = 'aaaa' | reverse %}") @@ -187,6 +197,14 @@ class TemplateTest < Minitest::Test assert_equal "ababab", t.render end + def test_render_length_uses_number_of_bytes_not_characters + t = Template.parse("{% if true %}すごい{% endif %}") + t.resource_limits.render_length_limit = 10 + assert_equal "Liquid error: Memory limits exceeded", t.render + t.resource_limits.render_length_limit = 18 + assert_equal "すごい", t.render + end + def test_default_resource_limits_unaffected_by_render_with_context context = Context.new t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}") From c67b77709d45d663de62d200c1870d5487d1509b Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Wed, 17 Apr 2019 18:33:59 +0100 Subject: [PATCH 33/74] rake memory_profile:run --- Gemfile | 7 +++++-- Rakefile | 7 +++++++ performance/memory_profile.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 performance/memory_profile.rb diff --git a/Gemfile b/Gemfile index 6e3136d9..c7cde24f 100644 --- a/Gemfile +++ b/Gemfile @@ -5,10 +5,13 @@ end gemspec -gem 'stackprof', platforms: :mri - group :benchmark, :test do gem 'benchmark-ips' + gem 'memory_profiler' + + install_if -> { RUBY_PLATFORM !~ /mingw|mswin/ } do + gem 'stackprof' + end end group :test do diff --git a/Rakefile b/Rakefile index 3dba4cf8..9650abb4 100755 --- a/Rakefile +++ b/Rakefile @@ -85,6 +85,13 @@ namespace :profile do end end +namespace :memory_profile do + desc "Run memory profiler" + task :run do + ruby "./performance/memory_profile.rb" + end +end + desc "Run example" task :example do ruby "-w -d -Ilib example/server/server.rb" diff --git a/performance/memory_profile.rb b/performance/memory_profile.rb new file mode 100644 index 00000000..bfacde89 --- /dev/null +++ b/performance/memory_profile.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'benchmark/ips' +require 'memory_profiler' +require_relative 'theme_runner' + +def profile(phase, &block) + puts + puts "#{phase}:" + puts + + report = MemoryProfiler.report(&block) + + report.pretty_print( + color_output: true, + scale_bytes: true, + detailed_report: true + ) +end + +Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first + +profiler = ThemeRunner.new + +profile("Parsing") { profiler.compile } +profile("Rendering") { profiler.render } From 2a1ca3152d1e5726e05ac27c0a6b33a61577cbe1 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Wed, 17 Apr 2019 14:07:10 +0100 Subject: [PATCH 34/74] liquid without the garbage --- lib/liquid/block.rb | 4 ++-- lib/liquid/block_body.rb | 24 ++++++++++------------- lib/liquid/profiler/hooks.rb | 12 ++++++------ lib/liquid/tag.rb | 4 ++-- lib/liquid/tags/assign.rb | 6 +++--- lib/liquid/tags/capture.rb | 8 ++++---- lib/liquid/tags/case.rb | 10 +++++----- lib/liquid/tags/comment.rb | 4 ++-- lib/liquid/tags/cycle.rb | 17 +++++++++++++--- lib/liquid/tags/decrement.rb | 5 +++-- lib/liquid/tags/for.rb | 22 +++++++++++---------- lib/liquid/tags/if.rb | 7 ++++--- lib/liquid/tags/ifchanged.rb | 14 ++++++------- lib/liquid/tags/include.rb | 10 ++++++---- lib/liquid/tags/increment.rb | 5 +++-- lib/liquid/tags/raw.rb | 5 +++-- lib/liquid/tags/table_row.rb | 15 ++++++++------ lib/liquid/tags/unless.rb | 10 +++++----- lib/liquid/template.rb | 10 +++++++++- lib/liquid/variable.rb | 17 ++++++++++++++-- performance/shopify/comment_form.rb | 6 ++++-- performance/shopify/paginate.rb | 2 +- test/integration/blank_test.rb | 5 +++-- test/integration/tags/include_tag_test.rb | 5 +++-- test/integration/template_test.rb | 18 ++++++++--------- 25 files changed, 144 insertions(+), 101 deletions(-) diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 00c59b2d..843b6b75 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -13,8 +13,8 @@ module Liquid end end - def render(context) - @body.render(context) + def render(context, output = '') + @body.render(context, output) end def blank? diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index d4fab30f..f404d205 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -66,8 +66,7 @@ module Liquid @blank end - def render(context) - output = [] + def render(context, output = '') context.resource_limits.render_score += @nodelist.length idx = 0 @@ -77,9 +76,9 @@ module Liquid check_resources(context, node) output << node when Variable - render_node_to_output(node, output, context) + render_node(context, output, node) when Block - render_node_to_output(node, output, context, node.blank?) + render_node(context, node.blank? ? '' : output, node) break if context.interrupt? # might have happened in a for-block when Continue, Break # If we get an Interrupt that means the block must stop processing. An @@ -88,34 +87,31 @@ module Liquid context.push_interrupt(node.interrupt) break else # Other non-Block tags - render_node_to_output(node, output, context) + render_node(context, output, node) break if context.interrupt? # might have happened through an include end idx += 1 end - output.join + output end private - def render_node_to_output(node, output, context, skip_output = false) - node_output = node.render(context) - node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s - check_resources(context, node_output) - output << node_output unless skip_output + def render_node(context, output, node) + node.render(context, output) + check_resources(context, output) rescue MemoryError => e raise e rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e context.handle_error(e, node.line_number) - output << nil rescue ::StandardError => e line_number = node.is_a?(String) ? nil : node.line_number output << context.handle_error(e, line_number) end - def check_resources(context, node_output) - context.resource_limits.render_length += node_output.bytesize + def check_resources(context, output) + context.resource_limits.render_length = output.bytesize return unless context.resource_limits.reached? raise MemoryError.new("Memory limits exceeded".freeze) end diff --git a/lib/liquid/profiler/hooks.rb b/lib/liquid/profiler/hooks.rb index cb11cd7e..112a7d32 100644 --- a/lib/liquid/profiler/hooks.rb +++ b/lib/liquid/profiler/hooks.rb @@ -1,19 +1,19 @@ module Liquid class BlockBody - def render_node_with_profiling(node, output, context, skip_output = false) + def render_node_with_profiling(context, output, node) Profiler.profile_node_render(node) do - render_node_without_profiling(node, output, context, skip_output) + render_node_without_profiling(context, output, node) end end - alias_method :render_node_without_profiling, :render_node_to_output - alias_method :render_node_to_output, :render_node_with_profiling + alias_method :render_node_without_profiling, :render_node + alias_method :render_node, :render_node_with_profiling end class Include < Tag - def render_with_profiling(context) + def render_with_profiling(context, output) Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do - render_without_profiling(context) + render_without_profiling(context, output) end end diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index 06970c18..d9d4ff72 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -32,8 +32,8 @@ module Liquid self.class.name.downcase end - def render(_context) - ''.freeze + def render(_context, output = '') + output end def blank? diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 3b696dd9..62a4747c 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -22,11 +22,11 @@ module Liquid end end - def render(context) - val = @from.render(context) + def render(context, output = '') + val = @from.render(context, nil) context.scopes.last[@to] = val context.resource_limits.assign_score += assign_score_of(val) - ''.freeze + output end def blank? diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index d5b8e297..b4d418b5 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -22,11 +22,11 @@ module Liquid end end - def render(context) - output = super + def render(context, output = '') + super context.scopes.last[@to] = output - context.resource_limits.assign_score += output.bytesize - ''.freeze + context.resource_limits.assign_score = output.bytesize + output end def blank? diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 5036b279..385c8788 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -38,21 +38,21 @@ module Liquid end end - def render(context) + def render(context, output = '') context.stack do execute_else_block = true - output = '' @blocks.each do |block| if block.else? - return block.attachment.render(context) if execute_else_block + block.attachment.render(context, output) if execute_else_block elsif block.evaluate(context) execute_else_block = false - output << block.attachment.render(context) + block.attachment.render(context, output) end end - output end + + output end private diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb index c57c9cd6..cb7739fd 100644 --- a/lib/liquid/tags/comment.rb +++ b/lib/liquid/tags/comment.rb @@ -1,7 +1,7 @@ module Liquid class Comment < Block - def render(_context) - ''.freeze + def render(_context, output = '') + output end def unknown_tag(_tag, _markup, _tokens) diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index 17aa860d..cbcff790 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -31,18 +31,29 @@ module Liquid end end - def render(context) + def render(context, output = '') context.registers[:cycle] ||= {} context.stack do key = context.evaluate(@name) iteration = context.registers[:cycle][key].to_i - result = context.evaluate(@variables[iteration]) + + val = context.evaluate(@variables[iteration]) + + if val.is_a?(Array) + val = val.join + elsif !val.is_a?(String) + val = val.to_s + end + + output << val + iteration += 1 iteration = 0 if iteration >= @variables.size context.registers[:cycle][key] = iteration - result end + + output end private diff --git a/lib/liquid/tags/decrement.rb b/lib/liquid/tags/decrement.rb index b5cdaaa6..9cf30733 100644 --- a/lib/liquid/tags/decrement.rb +++ b/lib/liquid/tags/decrement.rb @@ -23,11 +23,12 @@ module Liquid @variable = markup.strip end - def render(context) + def render(context, output = '') value = context.environments.first[@variable] ||= 0 value -= 1 context.environments.first[@variable] = value - value.to_s + output << value.to_s + output end end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index b69aa78c..01df4847 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -70,13 +70,13 @@ module Liquid @else_block = BlockBody.new end - def render(context) + def render(context, output = '') segment = collection_segment(context) if segment.empty? - render_else(context) + render_else(context, output) else - render_segment(context, segment) + render_segment(context, output, segment) end end @@ -141,12 +141,10 @@ module Liquid segment end - def render_segment(context, segment) + def render_segment(context, output, segment) for_stack = context.registers[:for_stack] ||= [] length = segment.length - result = '' - context.stack do loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) @@ -157,7 +155,7 @@ module Liquid segment.each do |item| context[@variable_name] = item - result << @for_block.render(context) + @for_block.render(context, output) loop_vars.send(:increment!) # Handle any interrupts if they exist. @@ -172,7 +170,7 @@ module Liquid end end - result + output end def set_attribute(key, expr) @@ -188,8 +186,12 @@ module Liquid end end - def render_else(context) - @else_block ? @else_block.render(context) : ''.freeze + def render_else(context, output) + if @else_block + @else_block.render(context, output) + else + output + end end class ParseTreeVisitor < Liquid::ParseTreeVisitor diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 02da42b7..a1e4eec7 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -39,15 +39,16 @@ module Liquid end end - def render(context) + def render(context, output) context.stack do @blocks.each do |block| if block.evaluate(context) - return block.attachment.render(context) + return block.attachment.render(context, output) end end - ''.freeze end + + output end private diff --git a/lib/liquid/tags/ifchanged.rb b/lib/liquid/tags/ifchanged.rb index d70cbe1f..2c026f17 100644 --- a/lib/liquid/tags/ifchanged.rb +++ b/lib/liquid/tags/ifchanged.rb @@ -1,16 +1,16 @@ module Liquid class Ifchanged < Block - def render(context) + def render(context, output) context.stack do - output = super + block_output = super(context, '') - if output != context.registers[:ifchanged] - context.registers[:ifchanged] = output - output - else - ''.freeze + if block_output != context.registers[:ifchanged] + context.registers[:ifchanged] = block_output + output << block_output end end + + output end end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index c9f2a288..28a74814 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -42,7 +42,7 @@ module Liquid def parse(_tokens) end - def render(context) + def render(context, output) template_name = context.evaluate(@template_name_expr) raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name @@ -66,19 +66,21 @@ module Liquid end if variable.is_a?(Array) - variable.collect do |var| + variable.each do |var| context[context_variable_name] = var - partial.render(context) + partial.render(context, output: output) end else context[context_variable_name] = variable - partial.render(context) + partial.render(context, output: output) end end ensure context.template_name = old_template_name context.partial = old_partial end + + output end private diff --git a/lib/liquid/tags/increment.rb b/lib/liquid/tags/increment.rb index baa0cbb6..4182c46e 100644 --- a/lib/liquid/tags/increment.rb +++ b/lib/liquid/tags/increment.rb @@ -20,10 +20,11 @@ module Liquid @variable = markup.strip end - def render(context) + def render(context, output = '') value = context.environments.first[@variable] ||= 0 context.environments.first[@variable] = value + 1 - value.to_s + output << value.to_s + output end end diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index 6b461bdb..4f6aa150 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -22,8 +22,9 @@ module Liquid raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) end - def render(_context) - @body + def render(_context, output = '') + output << @body + output end def nodelist diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 7f391cf0..09b69e92 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -18,7 +18,7 @@ module Liquid end end - def render(context) + def render(context, output = '') collection = context.evaluate(@collection_name) or return ''.freeze from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 @@ -30,7 +30,7 @@ module Liquid cols = context.evaluate(@attributes['cols'.freeze]).to_i - result = "\n" + output << "\n" context.stack do tablerowloop = Liquid::TablerowloopDrop.new(length, cols) context['tablerowloop'.freeze] = tablerowloop @@ -38,17 +38,20 @@ module Liquid collection.each do |item| context[@variable_name] = item - result << "" << super << '' + output << "" + super + output << '' if tablerowloop.col_last && !tablerowloop.last - result << "\n" + output << "\n" end tablerowloop.send(:increment!) end end - result << "\n" - result + + output << "\n" + output end class ParseTreeVisitor < Liquid::ParseTreeVisitor diff --git a/lib/liquid/tags/unless.rb b/lib/liquid/tags/unless.rb index 1d4280db..8d4c32e8 100644 --- a/lib/liquid/tags/unless.rb +++ b/lib/liquid/tags/unless.rb @@ -6,23 +6,23 @@ module Liquid # {% unless x < 0 %} x is greater than zero {% endunless %} # class Unless < If - def render(context) + def render(context, output = '') context.stack do # First condition is interpreted backwards ( if not ) first_block = @blocks.first unless first_block.evaluate(context) - return first_block.attachment.render(context) + return first_block.attachment.render(context, output) end # After the first condition unless works just like if @blocks[1..-1].each do |block| if block.evaluate(context) - return block.attachment.render(context) + return block.attachment.render(context, output) end end - - ''.freeze end + + output end end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 31a67e49..c22f1646 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -187,9 +187,12 @@ module Liquid raise ArgumentError, "Expected Hash or Liquid::Context as parameter" end + output = nil + case args.last when Hash options = args.pop + output = options[:output] if options[:output] registers.merge!(options[:registers]) if options[:registers].is_a?(Hash) @@ -205,7 +208,8 @@ module Liquid # render the nodelist. # for performance reasons we get an array back here. join will make a string out of it. result = with_profiling(context) do - @root.render(context) + output ||= self.class.output_buffer.clear + @root.render(context, output) end result.respond_to?(:join) ? result.join : result rescue Liquid::MemoryError => e @@ -215,6 +219,10 @@ module Liquid end end + def self.output_buffer + @output_buffer ||= String.new(capacity: 1024) + end + def render!(*args) @rethrow_errors = true render(*args) diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 717b1a2a..5e03706d 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -78,7 +78,7 @@ module Liquid filterargs end - def render(context) + def render(context, output = '') obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)| filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) context.invoke(filter_name, output, *filter_args) @@ -88,7 +88,20 @@ module Liquid taint_check(context, obj) - obj + if output + if obj.is_a?(Array) + output << obj.join + elsif obj.nil? + elsif !obj.is_a?(String) + output << obj.to_s + else + output << obj + end + + output + else + obj + end end private diff --git a/performance/shopify/comment_form.rb b/performance/shopify/comment_form.rb index d661c31d..ce33a2c5 100644 --- a/performance/shopify/comment_form.rb +++ b/performance/shopify/comment_form.rb @@ -12,7 +12,7 @@ class CommentForm < Liquid::Block end end - def render(context) + def render(context, output = '') article = context[@variable_name] context.stack do @@ -23,7 +23,9 @@ class CommentForm < Liquid::Block 'email' => context['comment.email'], 'body' => context['comment.body'] } - wrap_in_form(article, render_all(@nodelist, context)) + + output << wrap_in_form(article, render_all(@nodelist, context, output)) + output end end diff --git a/performance/shopify/paginate.rb b/performance/shopify/paginate.rb index 38a9a1ae..875e58be 100644 --- a/performance/shopify/paginate.rb +++ b/performance/shopify/paginate.rb @@ -21,7 +21,7 @@ class Paginate < Liquid::Block end end - def render(context) + def render(context, output = '') @context = context context.stack do diff --git a/test/integration/blank_test.rb b/test/integration/blank_test.rb index e9b56df1..cc097819 100644 --- a/test/integration/blank_test.rb +++ b/test/integration/blank_test.rb @@ -1,8 +1,9 @@ require 'test_helper' class FoobarTag < Liquid::Tag - def render(*args) - " " + def render(context, output = '') + output << ' ' + output end Liquid::Template.register_tag('foobar', FoobarTag) diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 9c188d57..cf83f599 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -66,8 +66,9 @@ class CustomInclude < Liquid::Tag def parse(tokens) end - def render(context) - @template_name[1..-2] + def render(context, output = '') + output << @template_name[1..-2] + output end end diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index 0dc0ae5f..c8b77704 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -177,31 +177,31 @@ class TemplateTest < Minitest::Test def test_render_length_persists_between_blocks t = Template.parse("{% if true %}aaaa{% endif %}") - t.resource_limits.render_length_limit = 7 + t.resource_limits.render_length_limit = 3 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 8 + t.resource_limits.render_length_limit = 4 assert_equal "aaaa", t.render t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}") - t.resource_limits.render_length_limit = 13 + t.resource_limits.render_length_limit = 6 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 14 + t.resource_limits.render_length_limit = 7 assert_equal "aaaabbb", t.render t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}") + t.resource_limits.render_length_limit = 4 + assert_equal "Liquid error: Memory limits exceeded", t.render t.resource_limits.render_length_limit = 5 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 11 - assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 12 + t.resource_limits.render_length_limit = 6 assert_equal "ababab", t.render end def test_render_length_uses_number_of_bytes_not_characters t = Template.parse("{% if true %}すごい{% endif %}") - t.resource_limits.render_length_limit = 10 + t.resource_limits.render_length_limit = 8 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 18 + t.resource_limits.render_length_limit = 9 assert_equal "すごい", t.render end From 70ed1fc86d22440bfeff657a10ceb8783f8510ee Mon Sep 17 00:00:00 2001 From: David Cornu Date: Tue, 23 Apr 2019 15:32:59 -0400 Subject: [PATCH 35/74] Make sure the limit and offset values are integers --- lib/liquid/tags/for.rb | 15 +++++++++++--- test/integration/tags/for_tag_test.rb | 28 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index b69aa78c..f18fb713 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -124,14 +124,23 @@ module Liquid from = if @from == :continue offsets[@name].to_i else - context.evaluate(@from).to_i + from_value = context.evaluate(@from) + if from_value.nil? + 0 + else + Utils.to_integer(from_value) + end end collection = context.evaluate(@collection_name) collection = collection.to_a if collection.is_a?(Range) - limit = context.evaluate(@limit) - to = limit ? limit.to_i + from : nil + limit_value = context.evaluate(@limit) + to = if limit_value.nil? + nil + else + Utils.to_integer(limit_value) + from + end segment = Utils.slice_collection(collection, from, to) segment.reverse! if @reversed diff --git a/test/integration/tags/for_tag_test.rb b/test/integration/tags/for_tag_test.rb index cb7a822d..9980e258 100644 --- a/test/integration/tags/for_tag_test.rb +++ b/test/integration/tags/for_tag_test.rb @@ -103,6 +103,34 @@ HERE assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns) end + def test_limiting_with_invalid_limit + assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } + template = <<-MKUP + {% for i in array limit: true offset: 1 %} + {{ i }} + {% endfor %} + MKUP + + exception = assert_raises(Liquid::ArgumentError) do + Template.parse(template).render!(assigns) + end + assert_equal("Liquid error: invalid integer", exception.message) + end + + def test_limiting_with_invalid_offset + assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } + template = <<-MKUP + {% for i in array limit: 1 offset: true %} + {{ i }} + {% endfor %} + MKUP + + exception = assert_raises(Liquid::ArgumentError) do + Template.parse(template).render!(assigns) + end + assert_equal("Liquid error: invalid integer", exception.message) + end + def test_dynamic_variable_limiting assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } assigns['limit'] = 2 From 453f6348c27652bf47b965bbeaad8958e5fa5e38 Mon Sep 17 00:00:00 2001 From: David Cornu Date: Tue, 23 Apr 2019 16:55:37 -0400 Subject: [PATCH 36/74] Stop installing the rainbow gem on Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0e3e476d..3a01754c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,6 @@ matrix: - rvm: jruby-head install: - - gem install rainbow -v 2.2.1 - bundle install script: bundle exec rake From 9640e7780537772585fe470912830dbba02f37b1 Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Mon, 22 Apr 2019 17:23:44 -0400 Subject: [PATCH 37/74] render_to_output_buffer --- .gitignore | 1 + .rubocop_todo.yml | 12 +++--- lib/liquid/block.rb | 5 ++- lib/liquid/block_body.rb | 20 ++++++---- lib/liquid/profiler/hooks.rb | 8 ++-- lib/liquid/tag.rb | 10 ++++- lib/liquid/tags/assign.rb | 4 +- lib/liquid/tags/capture.rb | 5 ++- lib/liquid/tags/case.rb | 6 +-- lib/liquid/tags/comment.rb | 2 +- lib/liquid/tags/cycle.rb | 2 +- lib/liquid/tags/decrement.rb | 2 +- lib/liquid/tags/for.rb | 8 ++-- lib/liquid/tags/if.rb | 4 +- lib/liquid/tags/ifchanged.rb | 5 ++- lib/liquid/tags/include.rb | 6 +-- lib/liquid/tags/increment.rb | 2 +- lib/liquid/tags/raw.rb | 2 +- lib/liquid/tags/table_row.rb | 2 +- lib/liquid/tags/unless.rb | 6 +-- lib/liquid/template.rb | 14 +++---- lib/liquid/variable.rb | 24 ++++++------ performance/shopify/comment_form.rb | 2 +- performance/shopify/paginate.rb | 2 +- test/integration/blank_test.rb | 8 ++-- test/integration/tags/include_tag_test.rb | 2 +- test/integration/template_test.rb | 18 ++++----- test/test_helper.rb | 7 ++++ test/unit/block_unit_test.rb | 45 +++++++++++++++++++++-- test/unit/tag_unit_test.rb | 38 +++++++++++++++++++ 30 files changed, 184 insertions(+), 88 deletions(-) diff --git a/.gitignore b/.gitignore index 7ac01c1f..90bf6dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ pkg .ruby-version Gemfile.lock .bundle +.byebug_history diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6868a7a2..4af3202c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.0. +# on 2019-04-22 19:11:24 -0400 using RuboCop version 0.53.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -46,18 +46,18 @@ Lint/Void: Exclude: - 'lib/liquid/parse_context.rb' -# Offense count: 54 +# Offense count: 53 Metrics/AbcSize: Max: 56 # Offense count: 12 Metrics/CyclomaticComplexity: - Max: 12 + Max: 13 # Offense count: 112 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 37 + Max: 38 # Offense count: 8 Metrics/PerceivedComplexity: @@ -90,7 +90,7 @@ Naming/UncommunicativeMethodParamName: - 'test/integration/template_test.rb' - 'test/unit/condition_unit_test.rb' -# Offense count: 10 +# Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: prefer_alias, prefer_alias_method @@ -253,7 +253,7 @@ Style/WhileUntilModifier: Exclude: - 'lib/liquid/tags/case.rb' -# Offense count: 640 +# Offense count: 648 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 843b6b75..549a3a36 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -13,8 +13,9 @@ module Liquid end end - def render(context, output = '') - @body.render(context, output) + # For backwards compatibility + def render(context) + @body.render(context) end def blank? diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index f404d205..c2478ce1 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -66,14 +66,19 @@ module Liquid @blank end - def render(context, output = '') + def render(context) + render_to_output_buffer(context, '') + end + + def render_to_output_buffer(context, output) context.resource_limits.render_score += @nodelist.length idx = 0 while node = @nodelist[idx] + previous_output_size = output.bytesize + case node when String - check_resources(context, node) output << node when Variable render_node(context, output, node) @@ -91,6 +96,8 @@ module Liquid break if context.interrupt? # might have happened through an include end idx += 1 + + raise_if_resource_limits_reached(context, output.bytesize - previous_output_size) end output @@ -99,10 +106,7 @@ module Liquid private def render_node(context, output, node) - node.render(context, output) - check_resources(context, output) - rescue MemoryError => e - raise e + node.render_to_output_buffer(context, output) rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e context.handle_error(e, node.line_number) rescue ::StandardError => e @@ -110,8 +114,8 @@ module Liquid output << context.handle_error(e, line_number) end - def check_resources(context, output) - context.resource_limits.render_length = output.bytesize + def raise_if_resource_limits_reached(context, length) + context.resource_limits.render_length += length return unless context.resource_limits.reached? raise MemoryError.new("Memory limits exceeded".freeze) end diff --git a/lib/liquid/profiler/hooks.rb b/lib/liquid/profiler/hooks.rb index 112a7d32..cda166b7 100644 --- a/lib/liquid/profiler/hooks.rb +++ b/lib/liquid/profiler/hooks.rb @@ -11,13 +11,13 @@ module Liquid end class Include < Tag - def render_with_profiling(context, output) + def render_to_output_buffer_with_profiling(context, output) Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do - render_without_profiling(context, output) + render_to_output_buffer_without_profiling(context, output) end end - alias_method :render_without_profiling, :render - alias_method :render, :render_with_profiling + alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer + alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling end end diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index d9d4ff72..5099ccb2 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -32,7 +32,15 @@ module Liquid self.class.name.downcase end - def render(_context, output = '') + def render(_context) + ''.freeze + end + + # For backwards compatibility with custom tags. In a future release, the semantics + # of the `render_to_output_buffer` method will become the default and the `render` + # method will be removed. + def render_to_output_buffer(context, output) + output << render(context) output end diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 62a4747c..767d874c 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -22,8 +22,8 @@ module Liquid end end - def render(context, output = '') - val = @from.render(context, nil) + def render_to_output_buffer(context, output) + val = @from.render(context) context.scopes.last[@to] = val context.resource_limits.assign_score += assign_score_of(val) output diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index b4d418b5..d717b769 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -22,10 +22,11 @@ module Liquid end end - def render(context, output = '') + def render_to_output_buffer(context, output) + previous_output_size = output.bytesize super context.scopes.last[@to] = output - context.resource_limits.assign_score = output.bytesize + context.resource_limits.assign_score += (output.bytesize - previous_output_size) output end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 385c8788..92b2ed0d 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -38,16 +38,16 @@ module Liquid end end - def render(context, output = '') + def render_to_output_buffer(context, output) context.stack do execute_else_block = true @blocks.each do |block| if block.else? - block.attachment.render(context, output) if execute_else_block + block.attachment.render_to_output_buffer(context, output) if execute_else_block elsif block.evaluate(context) execute_else_block = false - block.attachment.render(context, output) + block.attachment.render_to_output_buffer(context, output) end end end diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb index cb7739fd..cad3931e 100644 --- a/lib/liquid/tags/comment.rb +++ b/lib/liquid/tags/comment.rb @@ -1,6 +1,6 @@ module Liquid class Comment < Block - def render(_context, output = '') + def render_to_output_buffer(_context, output) output end diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index cbcff790..e42244d4 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -31,7 +31,7 @@ module Liquid end end - def render(context, output = '') + def render_to_output_buffer(context, output) context.registers[:cycle] ||= {} context.stack do diff --git a/lib/liquid/tags/decrement.rb b/lib/liquid/tags/decrement.rb index 9cf30733..08ddd4d6 100644 --- a/lib/liquid/tags/decrement.rb +++ b/lib/liquid/tags/decrement.rb @@ -23,7 +23,7 @@ module Liquid @variable = markup.strip end - def render(context, output = '') + def render_to_output_buffer(context, output) value = context.environments.first[@variable] ||= 0 value -= 1 context.environments.first[@variable] = value diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 01df4847..8dceab06 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -70,7 +70,7 @@ module Liquid @else_block = BlockBody.new end - def render(context, output = '') + def render_to_output_buffer(context, output) segment = collection_segment(context) if segment.empty? @@ -78,6 +78,8 @@ module Liquid else render_segment(context, output, segment) end + + output end protected @@ -155,7 +157,7 @@ module Liquid segment.each do |item| context[@variable_name] = item - @for_block.render(context, output) + @for_block.render_to_output_buffer(context, output) loop_vars.send(:increment!) # Handle any interrupts if they exist. @@ -188,7 +190,7 @@ module Liquid def render_else(context, output) if @else_block - @else_block.render(context, output) + @else_block.render_to_output_buffer(context, output) else output end diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index a1e4eec7..25534a98 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -39,11 +39,11 @@ module Liquid end end - def render(context, output) + def render_to_output_buffer(context, output) context.stack do @blocks.each do |block| if block.evaluate(context) - return block.attachment.render(context, output) + return block.attachment.render_to_output_buffer(context, output) end end end diff --git a/lib/liquid/tags/ifchanged.rb b/lib/liquid/tags/ifchanged.rb index 2c026f17..e3040cec 100644 --- a/lib/liquid/tags/ifchanged.rb +++ b/lib/liquid/tags/ifchanged.rb @@ -1,8 +1,9 @@ module Liquid class Ifchanged < Block - def render(context, output) + def render_to_output_buffer(context, output) context.stack do - block_output = super(context, '') + block_output = '' + super(context, block_output) if block_output != context.registers[:ifchanged] context.registers[:ifchanged] = block_output diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 28a74814..24acf9d9 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -42,7 +42,7 @@ module Liquid def parse(_tokens) end - def render(context, output) + def render_to_output_buffer(context, output) template_name = context.evaluate(@template_name_expr) raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name @@ -68,11 +68,11 @@ module Liquid if variable.is_a?(Array) variable.each do |var| context[context_variable_name] = var - partial.render(context, output: output) + partial.render_to_output_buffer(context, output) end else context[context_variable_name] = variable - partial.render(context, output: output) + partial.render_to_output_buffer(context, output) end end ensure diff --git a/lib/liquid/tags/increment.rb b/lib/liquid/tags/increment.rb index 4182c46e..5af12422 100644 --- a/lib/liquid/tags/increment.rb +++ b/lib/liquid/tags/increment.rb @@ -20,7 +20,7 @@ module Liquid @variable = markup.strip end - def render(context, output = '') + def render_to_output_buffer(context, output) value = context.environments.first[@variable] ||= 0 context.environments.first[@variable] = value + 1 output << value.to_s diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index 4f6aa150..4fa75d99 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -22,7 +22,7 @@ module Liquid raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) end - def render(_context, output = '') + def render_to_output_buffer(_context, output) output << @body output end diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 09b69e92..95321020 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -18,7 +18,7 @@ module Liquid end end - def render(context, output = '') + def render_to_output_buffer(context, output) collection = context.evaluate(@collection_name) or return ''.freeze from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 diff --git a/lib/liquid/tags/unless.rb b/lib/liquid/tags/unless.rb index 8d4c32e8..18856c3b 100644 --- a/lib/liquid/tags/unless.rb +++ b/lib/liquid/tags/unless.rb @@ -6,18 +6,18 @@ module Liquid # {% unless x < 0 %} x is greater than zero {% endunless %} # class Unless < If - def render(context, output = '') + def render_to_output_buffer(context, output) context.stack do # First condition is interpreted backwards ( if not ) first_block = @blocks.first unless first_block.evaluate(context) - return first_block.attachment.render(context, output) + return first_block.attachment.render_to_output_buffer(context, output) end # After the first condition unless works just like if @blocks[1..-1].each do |block| if block.evaluate(context) - return block.attachment.render(context, output) + return block.attachment.render_to_output_buffer(context, output) end end end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index c22f1646..35db6747 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -207,11 +207,9 @@ module Liquid begin # render the nodelist. # for performance reasons we get an array back here. join will make a string out of it. - result = with_profiling(context) do - output ||= self.class.output_buffer.clear - @root.render(context, output) + with_profiling(context) do + @root.render_to_output_buffer(context, output || '') end - result.respond_to?(:join) ? result.join : result rescue Liquid::MemoryError => e context.handle_error(e) ensure @@ -219,15 +217,15 @@ module Liquid end end - def self.output_buffer - @output_buffer ||= String.new(capacity: 1024) - end - def render!(*args) @rethrow_errors = true render(*args) end + def render_to_output_buffer(context, output) + render(context, output: output) + end + private def tokenize(source) diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 5e03706d..cbf99864 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -78,30 +78,28 @@ module Liquid filterargs end - def render(context, output = '') + def render(context) obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)| filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) context.invoke(filter_name, output, *filter_args) end obj = context.apply_global_filter(obj) - taint_check(context, obj) + obj + end - if output - if obj.is_a?(Array) - output << obj.join - elsif obj.nil? - elsif !obj.is_a?(String) - output << obj.to_s - else - output << obj - end + def render_to_output_buffer(context, output) + obj = render(context) - output + if obj.is_a?(Array) + output << obj.join + elsif obj.nil? else - obj + output << obj.to_s end + + output end private diff --git a/performance/shopify/comment_form.rb b/performance/shopify/comment_form.rb index ce33a2c5..7b5bd53b 100644 --- a/performance/shopify/comment_form.rb +++ b/performance/shopify/comment_form.rb @@ -12,7 +12,7 @@ class CommentForm < Liquid::Block end end - def render(context, output = '') + def render_to_output_buffer(context, output) article = context[@variable_name] context.stack do diff --git a/performance/shopify/paginate.rb b/performance/shopify/paginate.rb index 875e58be..0abd11ff 100644 --- a/performance/shopify/paginate.rb +++ b/performance/shopify/paginate.rb @@ -21,7 +21,7 @@ class Paginate < Liquid::Block end end - def render(context, output = '') + def render_to_output_buffer(context, output) @context = context context.stack do diff --git a/test/integration/blank_test.rb b/test/integration/blank_test.rb index cc097819..2b46ad71 100644 --- a/test/integration/blank_test.rb +++ b/test/integration/blank_test.rb @@ -1,12 +1,10 @@ require 'test_helper' class FoobarTag < Liquid::Tag - def render(context, output = '') + def render_to_output_buffer(context, output) output << ' ' output end - - Liquid::Template.register_tag('foobar', FoobarTag) end class BlankTestFileSystem @@ -32,7 +30,9 @@ class BlankTest < Minitest::Test end def test_new_tags_are_not_blank_by_default - assert_template_result(" " * N, wrap_in_for("{% foobar %}")) + with_custom_tag('foobar', FoobarTag) do + assert_template_result(" " * N, wrap_in_for("{% foobar %}")) + end end def test_loops_are_blank diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index cf83f599..14bb8c3a 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -66,7 +66,7 @@ class CustomInclude < Liquid::Tag def parse(tokens) end - def render(context, output = '') + def render_to_output_buffer(context, output) output << @template_name[1..-2] output end diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index c8b77704..0dc0ae5f 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -177,31 +177,31 @@ class TemplateTest < Minitest::Test def test_render_length_persists_between_blocks t = Template.parse("{% if true %}aaaa{% endif %}") - t.resource_limits.render_length_limit = 3 + t.resource_limits.render_length_limit = 7 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 4 + t.resource_limits.render_length_limit = 8 assert_equal "aaaa", t.render t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}") - t.resource_limits.render_length_limit = 6 + t.resource_limits.render_length_limit = 13 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 7 + t.resource_limits.render_length_limit = 14 assert_equal "aaaabbb", t.render t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}") - t.resource_limits.render_length_limit = 4 - assert_equal "Liquid error: Memory limits exceeded", t.render t.resource_limits.render_length_limit = 5 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 6 + t.resource_limits.render_length_limit = 11 + assert_equal "Liquid error: Memory limits exceeded", t.render + t.resource_limits.render_length_limit = 12 assert_equal "ababab", t.render end def test_render_length_uses_number_of_bytes_not_characters t = Template.parse("{% if true %}すごい{% endif %}") - t.resource_limits.render_length_limit = 8 + t.resource_limits.render_length_limit = 10 assert_equal "Liquid error: Memory limits exceeded", t.render - t.resource_limits.render_length_limit = 9 + t.resource_limits.render_length_limit = 18 assert_equal "すごい", t.render end diff --git a/test/test_helper.rb b/test/test_helper.rb index ac5ab53d..34e7553c 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -84,6 +84,13 @@ module Minitest ensure Liquid::Template.error_mode = old_mode end + + def with_custom_tag(tag_name, tag_class) + Liquid::Template.register_tag(tag_name, tag_class) + yield + ensure + Liquid::Template.tags.delete(tag_name) + end end end diff --git a/test/unit/block_unit_test.rb b/test/unit/block_unit_test.rb index 6a27a7d4..9f7b94f8 100644 --- a/test/unit/block_unit_test.rb +++ b/test/unit/block_unit_test.rb @@ -44,10 +44,47 @@ class BlockUnitTest < Minitest::Test end def test_with_custom_tag - Liquid::Template.register_tag("testtag", Block) - assert Liquid::Template.parse("{% testtag %} {% endtesttag %}") - ensure - Liquid::Template.tags.delete('testtag') + with_custom_tag('testtag', Block) do + assert Liquid::Template.parse("{% testtag %} {% endtesttag %}") + end + end + + def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility + klass1 = Class.new(Block) do + def render(*) + 'hello' + end + end + + with_custom_tag('blabla', klass1) do + template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}") + + assert_equal 'hello', template.render + + buf = '' + output = template.render({}, output: buf) + assert_equal 'hello', output + assert_equal 'hello', buf + assert_equal buf.object_id, output.object_id + end + + klass2 = Class.new(klass1) do + def render(*) + 'foo' + super + 'bar' + end + end + + with_custom_tag('blabla', klass2) do + template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}") + + assert_equal 'foohellobar', template.render + + buf = '' + output = template.render({}, output: buf) + assert_equal 'foohellobar', output + assert_equal 'foohellobar', buf + assert_equal buf.object_id, output.object_id + end end private diff --git a/test/unit/tag_unit_test.rb b/test/unit/tag_unit_test.rb index c4b901b1..a3fb40e3 100644 --- a/test/unit/tag_unit_test.rb +++ b/test/unit/tag_unit_test.rb @@ -18,4 +18,42 @@ class TagUnitTest < Minitest::Test tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new) assert_equal 'some_tag', tag.tag_name end + + def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility + klass1 = Class.new(Tag) do + def render(*) + 'hello' + end + end + + with_custom_tag('blabla', klass1) do + template = Liquid::Template.parse("{% blabla %}") + + assert_equal 'hello', template.render + + buf = '' + output = template.render({}, output: buf) + assert_equal 'hello', output + assert_equal 'hello', buf + assert_equal buf.object_id, output.object_id + end + + klass2 = Class.new(klass1) do + def render(*) + 'foo' + super + 'bar' + end + end + + with_custom_tag('blabla', klass2) do + template = Liquid::Template.parse("{% blabla %}") + + assert_equal 'foohellobar', template.render + + buf = '' + output = template.render({}, output: buf) + assert_equal 'foohellobar', output + assert_equal 'foohellobar', buf + assert_equal buf.object_id, output.object_id + end + end end From 1c577c5b62423662b9edf7cf93596942d1819592 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 24 Apr 2019 11:31:20 +0530 Subject: [PATCH 38/74] Don't attempt to install stackprof gem on JRuby --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c7cde24f..37ffe1d9 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ group :benchmark, :test do gem 'benchmark-ips' gem 'memory_profiler' - install_if -> { RUBY_PLATFORM !~ /mingw|mswin/ } do + install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do gem 'stackprof' end end From 4684478e94f92726bbd90b099b3c2f5321d1a7d4 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Mon, 29 Apr 2019 23:45:45 +0530 Subject: [PATCH 39/74] Use a private constant to stash token-types --- lib/liquid/parser.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index 69543434..152e04cd 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -44,11 +44,14 @@ module Liquid tok[0] == type end + CONSUME_TOKENS = [:string, :number] + private_constant :CONSUME_TOKENS + def expression token = @tokens[@p] if token[0] == :id variable_signature - elsif [:string, :number].include? token[0] + elsif CONSUME_TOKENS.include? token[0] consume elsif token.first == :open_round consume From 9ef6f9b642dfc0769bdb058bacf0e57f882839e6 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Mon, 29 Apr 2019 23:50:49 +0530 Subject: [PATCH 40/74] Freeze mutable object assigned to constant --- lib/liquid/parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index 152e04cd..2bc4f954 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -44,7 +44,7 @@ module Liquid tok[0] == type end - CONSUME_TOKENS = [:string, :number] + CONSUME_TOKENS = [:string, :number].freeze private_constant :CONSUME_TOKENS def expression From ab698191b9681228f8a50946fbd79e8779af09f2 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Thu, 16 May 2019 19:14:27 +0530 Subject: [PATCH 41/74] Add a CI job to profile memory usage of commit --- .travis.yml | 7 ++-- Gemfile | 1 + performance/memory_profile.rb | 64 +++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a01754c..24e755bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,18 +6,21 @@ rvm: - 2.3 - 2.4 - 2.5 + - &latest_ruby 2.6 - ruby-head - jruby-head # - rbx-2 -sudo: false - addons: apt: packages: - libgmp3-dev matrix: + include: + - rvm: *latest_ruby + script: bundle exec rake memory_profile:run + name: Profiling Memory Usage allow_failures: - rvm: ruby-head - rvm: jruby-head diff --git a/Gemfile b/Gemfile index 37ffe1d9..ddff94a1 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gemspec group :benchmark, :test do gem 'benchmark-ips' gem 'memory_profiler' + gem 'terminal-table' install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do gem 'stackprof' diff --git a/performance/memory_profile.rb b/performance/memory_profile.rb index bfacde89..9a153755 100644 --- a/performance/memory_profile.rb +++ b/performance/memory_profile.rb @@ -2,25 +2,61 @@ require 'benchmark/ips' require 'memory_profiler' +require 'terminal-table' require_relative 'theme_runner' -def profile(phase, &block) - puts - puts "#{phase}:" - puts +class Profiler + LOG_LABEL = "Profiling: ".rjust(14).freeze + REPORTS_DIR = File.expand_path('.memprof', __dir__).freeze - report = MemoryProfiler.report(&block) + def self.run + puts + yield new + end - report.pretty_print( - color_output: true, - scale_bytes: true, - detailed_report: true - ) + def initialize + @allocated = [] + @retained = [] + @headings = [] + end + + def profile(phase, &block) + print LOG_LABEL + print "#{phase}.. ".ljust(10) + report = MemoryProfiler.report(&block) + puts 'Done.' + @headings << phase.capitalize + @allocated << "#{report.scale_bytes(report.total_allocated_memsize)} (#{report.total_allocated} objects)" + @retained << "#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)" + + return if ENV['CI'] + require 'fileutils' + report_file = File.join(REPORTS_DIR, "#{sanitize(phase)}.txt") + FileUtils.mkdir_p(REPORTS_DIR) + report.pretty_print(to_file: report_file, scale_bytes: true) + end + + def tabulate + table = Terminal::Table.new(headings: @headings.unshift('Phase')) do |t| + t << @allocated.unshift('Total allocated') + t << @retained.unshift('Total retained') + end + + puts + puts table + puts "\nDetailed report(s) saved to #{REPORTS_DIR}/" unless ENV['CI'] + end + + def sanitize(string) + string.downcase.gsub(/[\W]/, '-').squeeze('-') + end end Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first -profiler = ThemeRunner.new - -profile("Parsing") { profiler.compile } -profile("Rendering") { profiler.render } +runner = ThemeRunner.new +Profiler.run do |x| + x.profile('parse') { runner.compile } + x.profile('render') { runner.render } + x.tabulate +end From 2c424476597b03d65e87cef272d7505e5184aad4 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Fri, 17 May 2019 23:30:24 +0530 Subject: [PATCH 42/74] Rename constant to SINGLE_TOKEN_EXPRESSION_TYPES --- lib/liquid/parser.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index 2bc4f954..16df6e93 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -44,14 +44,14 @@ module Liquid tok[0] == type end - CONSUME_TOKENS = [:string, :number].freeze - private_constant :CONSUME_TOKENS + SINGLE_TOKEN_EXPRESSION_TYPES = [:string, :number].freeze + private_constant :SINGLE_TOKEN_EXPRESSION_TYPES def expression token = @tokens[@p] if token[0] == :id variable_signature - elsif CONSUME_TOKENS.include? token[0] + elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0] consume elsif token.first == :open_round consume From d19967a79dc1263230d0d9a7ea94c277542d34c2 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Mon, 22 Jul 2019 17:35:45 +0530 Subject: [PATCH 43/74] Reduce string allocations from truncate filters --- lib/liquid/standardfilters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 0bddfa97..afcf479e 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -79,7 +79,7 @@ module Liquid truncate_string_str = truncate_string.to_s l = length - truncate_string_str.length l = 0 if l < 0 - input_str.length > length ? input_str[0...l] + truncate_string_str : input_str + input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str end def truncatewords(input, words = 15, truncate_string = "...".freeze) @@ -88,7 +88,7 @@ module Liquid words = Utils.to_integer(words) l = words - 1 l = 0 if l < 0 - wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input + wordlist.length > l ? wordlist[0..l].join(" ".freeze).concat(truncate_string.to_s) : input end # Split input string into an array of substrings separated by given pattern. From c2c1497ca87db47e300fdfa431320ff7f059aacd Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Mon, 22 Jul 2019 20:42:37 +0530 Subject: [PATCH 44/74] Reduce allocations while registering Liquid tags --- lib/liquid/template.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 35db6747..91369677 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -50,7 +50,9 @@ module Liquid private def lookup_class(name) - name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) } + names = name.split("::".freeze) + names.reject!(&:empty?) + names.reduce(Object) { |scope, const| scope.const_get(const) } end end From 00702d8e6308759b9a6748c79714e41e2493f12a Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 7 Aug 2019 11:44:53 +0530 Subject: [PATCH 45/74] Use `Object.const_get` directly --- lib/liquid/template.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 91369677..91e30fbc 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -50,9 +50,7 @@ module Liquid private def lookup_class(name) - names = name.split("::".freeze) - names.reject!(&:empty?) - names.reduce(Object) { |scope, const| scope.const_get(const) } + Object.const_get(name) end end From b16b109a80194190f0b6b4e824dd1398e34085f7 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Tue, 27 Aug 2019 22:35:17 +1000 Subject: [PATCH 46/74] Bump Minimum version to 2.4 and bump Rubocop --- .rubocop.yml | 12 ++-- .rubocop_todo.yml | 142 +++++++++++++++++++++++++++++++++++--------- .travis.yml | 18 ++---- Gemfile | 9 +-- Rakefile | 10 ++-- liquid.gemspec | 2 +- test/test_helper.rb | 2 +- 7 files changed, 137 insertions(+), 58 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6a306a1e..671f0768 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,9 +2,15 @@ inherit_from: - .rubocop_todo.yml - ./.rubocop_todo.yml +require: rubocop-performance + +Performance: + Enabled: true + AllCops: Exclude: - 'performance/shopify/*' + - 'vendor/bundle/**/*' - 'pkg/**' Metrics/BlockNesting: @@ -79,9 +85,6 @@ Style/TrailingCommaInArrayLiteral: Style/TrailingCommaInHashLiteral: Enabled: false -Layout/IndentHash: - EnforcedStyle: consistent - Style/FormatString: Enabled: false @@ -106,9 +109,6 @@ Style/RegexpLiteral: Style/SymbolLiteral: Enabled: false -Performance/Count: - Enabled: false - Naming/ConstantName: Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4af3202c..19c27e85 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-04-22 19:11:24 -0400 using RuboCop version 0.53.0. +# on 2019-08-27 22:42:50 +1000 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -8,16 +8,67 @@ # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: Include, TreatCommentsAsGroupSeparators. +# Configuration parameters: TreatCommentsAsGroupSeparators, Include. # Include: **/*.gemspec Gemspec/OrderedDependencies: Exclude: - 'liquid.gemspec' +# Offense count: 1 +# Configuration parameters: Include. +# Include: **/*.gemspec, +Gemspec/RequiredRubyVersion: + Exclude: + - 'liquid.gemspec' + +# Offense count: 124 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_argument, with_fixed_indentation +Layout/AlignArguments: + Enabled: false + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/AlignHash: + Exclude: + - 'lib/liquid/condition.rb' + - 'lib/liquid/expression.rb' + - 'test/unit/context_unit_test.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Layout/ClosingHeredocIndentation: + Exclude: + - 'test/integration/tags/for_tag_test.rb' + +# Offense count: 25 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/liquid/block.rb' + - 'lib/liquid/block_body.rb' + - 'lib/liquid/context.rb' + - 'lib/liquid/drop.rb' + - 'lib/liquid/lexer.rb' + - 'lib/liquid/parser.rb' + - 'lib/liquid/standardfilters.rb' + - 'lib/liquid/strainer.rb' + - 'lib/liquid/tags/for.rb' + - 'lib/liquid/tags/if.rb' + - 'lib/liquid/tags/include.rb' + - 'lib/liquid/utils.rb' + - 'lib/liquid/variable.rb' + - 'lib/liquid/variable_lookup.rb' + # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +# SupportedStyles: squiggly, active_support, powerpack, unindent Layout/IndentHeredoc: Exclude: - 'test/integration/tags/for_tag_test.rb' @@ -32,6 +83,13 @@ Layout/MultilineMethodCallBraceLayout: - 'test/integration/error_handling_test.rb' - 'test/unit/strainer_unit_test.rb' +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Layout/SpaceAroundOperators: + Exclude: + - 'lib/liquid/condition.rb' + # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -46,7 +104,7 @@ Lint/Void: Exclude: - 'lib/liquid/parse_context.rb' -# Offense count: 53 +# Offense count: 52 Metrics/AbcSize: Max: 56 @@ -54,27 +112,25 @@ Metrics/AbcSize: Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 112 -# Configuration parameters: CountComments. +# Offense count: 114 +# Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 38 -# Offense count: 8 +# Offense count: 9 Metrics/PerceivedComplexity: Max: 11 -# Offense count: 52 -# Configuration parameters: Blacklist. -# Blacklist: END, (?-mix:EO[A-Z]{1}) -Naming/HeredocDelimiterNaming: +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: Exclude: - - 'test/integration/assign_test.rb' - - 'test/integration/capture_test.rb' - - 'test/integration/trim_mode_test.rb' + - 'lib/liquid/context.rb' -# Offense count: 23 +# Offense count: 20 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id +# AllowedNames: io, id, to, by, on, in, at, ip, db Naming/UncommunicativeMethodParamName: Exclude: - 'example/server/example_servlet.rb' @@ -82,15 +138,22 @@ Naming/UncommunicativeMethodParamName: - 'lib/liquid/context.rb' - 'lib/liquid/standardfilters.rb' - 'lib/liquid/tags/if.rb' - - 'lib/liquid/utils.rb' - 'lib/liquid/variable.rb' - 'test/integration/filter_test.rb' - 'test/integration/standard_filter_test.rb' - - 'test/integration/tags/for_tag_test.rb' - 'test/integration/template_test.rb' - 'test/unit/condition_unit_test.rb' -# Offense count: 12 +# Offense count: 3 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: inline, group +Style/AccessModifierDeclarations: + Exclude: + - 'lib/liquid/tag.rb' + - 'lib/liquid/tags/include.rb' + - 'test/unit/strainer_unit_test.rb' + +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: prefer_alias, prefer_alias_method @@ -117,15 +180,9 @@ Style/ConditionalAssignment: - 'lib/liquid/errors.rb' # Offense count: 1 -Style/DateTime: - Exclude: - - 'test/unit/context_unit_test.rb' - -# Offense count: 2 # Cop supports --auto-correct. Style/EmptyCaseCondition: Exclude: - - 'lib/liquid/block_body.rb' - 'lib/liquid/lexer.rb' # Offense count: 5 @@ -163,6 +220,13 @@ Style/FormatStringToken: - 'test/integration/filter_test.rb' - 'test/integration/hash_ordering_test.rb' +# Offense count: 103 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, never +Style/FrozenStringLiteralComment: + Enabled: false + # Offense count: 14 # Configuration parameters: MinBodyLength. Style/GuardClause: @@ -180,6 +244,13 @@ Style/GuardClause: - 'lib/liquid/variable.rb' - 'test/unit/tokenizer_unit_test.rb' +# Offense count: 52 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Enabled: false + # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength. @@ -188,9 +259,9 @@ Style/Next: Exclude: - 'lib/liquid/tags/for.rb' -# Offense count: 4 +# Offense count: 13 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle. +# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: @@ -199,6 +270,8 @@ Style/NumericPredicate: - 'lib/liquid/forloop_drop.rb' - 'lib/liquid/standardfilters.rb' - 'lib/liquid/tablerowloop_drop.rb' + - 'test/integration/standard_filter_test.rb' + - 'test/integration/template_test.rb' # Offense count: 14 # Cop supports --auto-correct. @@ -216,6 +289,16 @@ Style/RedundantSelf: Exclude: - 'lib/liquid/strainer.rb' +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. +# Whitelist: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/liquid/drop.rb' + - 'lib/liquid/strainer.rb' + - 'lib/liquid/tokenizer.rb' + # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. @@ -253,8 +336,9 @@ Style/WhileUntilModifier: Exclude: - 'lib/liquid/tags/case.rb' -# Offense count: 648 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# Offense count: 650 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 294 diff --git a/.travis.yml b/.travis.yml index 3a01754c..082cb572 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,21 @@ language: ruby rvm: - - 2.1 - - 2.2 - - 2.3 - 2.4 - 2.5 + - 2.6 + - 2.7 - ruby-head - jruby-head -# - rbx-2 - -sudo: false - -addons: - apt: - packages: - - libgmp3-dev + - truffleruby matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head + - rvm: truffleruby -install: - - bundle install +cache: bundler script: bundle exec rake diff --git a/Gemfile b/Gemfile index 37ffe1d9..370fa03b 100644 --- a/Gemfile +++ b/Gemfile @@ -9,15 +9,16 @@ group :benchmark, :test do gem 'benchmark-ips' gem 'memory_profiler' - install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do + install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do gem 'stackprof' end end group :test do - gem 'rubocop', '~> 0.53.0' + gem 'rubocop', '~> 0.74.0', require: false + gem 'rubocop-performance', require: false - platform :mri do - gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6' + platform :mri, :truffleruby do + gem 'liquid-c', github: 'Shopify/liquid-c', ref: '7ba926791ef8411984d0f3e41c6353fd716041c6' end end diff --git a/Rakefile b/Rakefile index 9650abb4..f7186eb7 100755 --- a/Rakefile +++ b/Rakefile @@ -19,8 +19,10 @@ task :warn_test do end task :rubocop do - require 'rubocop/rake_task' - RuboCop::RakeTask.new + if RUBY_ENGINE == 'ruby' + require 'rubocop/rake_task' + RuboCop::RakeTask.new + end end desc 'runs test suite with both strict and lax parsers' @@ -32,8 +34,8 @@ task :test do Rake::Task['base_test'].reenable Rake::Task['base_test'].invoke - if RUBY_ENGINE == 'ruby' - ENV['LIQUID-C'] = '1' + if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby' + ENV['LIQUID_C'] = '1' ENV['LIQUID_PARSER_MODE'] = 'lax' Rake::Task['base_test'].reenable diff --git a/liquid.gemspec b/liquid.gemspec index e0e4ddbf..89df19a0 100644 --- a/liquid.gemspec +++ b/liquid.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.license = "MIT" # s.description = "A secure, non-evaling end user template engine with aesthetic markup." - s.required_ruby_version = ">= 2.1.0" + s.required_ruby_version = ">= 2.4.0" s.required_rubygems_version = ">= 1.3.7" s.test_files = Dir.glob("{test}/**/*") diff --git a/test/test_helper.rb b/test/test_helper.rb index 34e7553c..affa2e45 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,7 +14,7 @@ if env_mode = ENV['LIQUID_PARSER_MODE'] end Liquid::Template.error_mode = mode -if ENV['LIQUID-C'] == '1' +if ENV['LIQUID_C'] == '1' puts "-- LIQUID C" require 'liquid/c' end From 7bae55dd3943907716166cef5bd8f6757e7664e5 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 28 Aug 2019 19:22:40 +1000 Subject: [PATCH 47/74] Bugfix for new Liquid tag --- lib/liquid/tags/echo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/liquid/tags/echo.rb b/lib/liquid/tags/echo.rb index acb9ab45..d3d30e3b 100644 --- a/lib/liquid/tags/echo.rb +++ b/lib/liquid/tags/echo.rb @@ -16,7 +16,7 @@ module Liquid end def render(context) - @variable.render(context) + @variable.render_to_output_buffer(context, '') end end From 8f68cffdf139ac37b40b9ac8e6aaf2ee295310e1 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Thu, 29 Aug 2019 00:45:38 +1000 Subject: [PATCH 48/74] Resolve failing rubocop issues --- .rubocop_todo.yml | 17 +++-- performance/memory_profile.rb | 1 + test/integration/tags/liquid_tag_test.rb | 96 ++++++++++++------------ 3 files changed, 58 insertions(+), 56 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 19c27e85..992d135a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-08-27 22:42:50 +1000 using RuboCop version 0.74.0. +# on 2019-08-29 00:43:36 +1000 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -46,7 +46,7 @@ Layout/ClosingHeredocIndentation: Exclude: - 'test/integration/tags/for_tag_test.rb' -# Offense count: 25 +# Offense count: 27 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: Exclude: @@ -104,7 +104,7 @@ Lint/Void: Exclude: - 'lib/liquid/parse_context.rb' -# Offense count: 52 +# Offense count: 53 Metrics/AbcSize: Max: 56 @@ -112,7 +112,7 @@ Metrics/AbcSize: Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 114 +# Offense count: 118 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 38 @@ -144,11 +144,12 @@ Naming/UncommunicativeMethodParamName: - 'test/integration/template_test.rb' - 'test/unit/condition_unit_test.rb' -# Offense count: 3 +# Offense count: 5 # Configuration parameters: EnforcedStyle. # SupportedStyles: inline, group Style/AccessModifierDeclarations: Exclude: + - 'lib/liquid/block_body.rb' - 'lib/liquid/tag.rb' - 'lib/liquid/tags/include.rb' - 'test/unit/strainer_unit_test.rb' @@ -220,7 +221,7 @@ Style/FormatStringToken: - 'test/integration/filter_test.rb' - 'test/integration/hash_ordering_test.rb' -# Offense count: 103 +# Offense count: 106 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, never @@ -244,7 +245,7 @@ Style/GuardClause: - 'lib/liquid/variable.rb' - 'test/unit/tokenizer_unit_test.rb' -# Offense count: 52 +# Offense count: 53 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: literals, strict @@ -336,7 +337,7 @@ Style/WhileUntilModifier: Exclude: - 'lib/liquid/tags/case.rb' -# Offense count: 650 +# Offense count: 665 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/performance/memory_profile.rb b/performance/memory_profile.rb index 9a153755..14b3770b 100644 --- a/performance/memory_profile.rb +++ b/performance/memory_profile.rb @@ -30,6 +30,7 @@ class Profiler @retained << "#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)" return if ENV['CI'] + require 'fileutils' report_file = File.join(REPORTS_DIR, "#{sanitize(phase)}.txt") FileUtils.mkdir_p(REPORTS_DIR) diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb index d4be1282..628eb85e 100644 --- a/test/integration/tags/liquid_tag_test.rb +++ b/test/integration/tags/liquid_tag_test.rb @@ -5,72 +5,72 @@ class LiquidTagTest < Minitest::Test def test_liquid_tag assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3]) - {%- liquid - echo array | join: " " - -%} + {%- liquid + echo array | join: " " + -%} LIQUID assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3]) - {%- liquid - for value in array - echo value - unless forloop.last - echo " " - endunless - endfor - -%} + {%- liquid + for value in array + echo value + unless forloop.last + echo " " + endunless + endfor + -%} LIQUID assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3]) - {%- liquid - for value in array - assign double_value = value | times: 2 - echo double_value | times: 2 - unless forloop.last - echo " " - endunless - endfor + {%- liquid + for value in array + assign double_value = value | times: 2 + echo double_value | times: 2 + unless forloop.last + echo " " + endunless + endfor - echo " " - echo double_value - -%} + echo " " + echo double_value + -%} LIQUID assert_template_result('abc', <<~LIQUID) - {%- liquid echo "a" -%} - b - {%- liquid echo "c" -%} + {%- liquid echo "a" -%} + b + {%- liquid echo "c" -%} LIQUID end def test_liquid_tag_errors assert_match_syntax_error("syntax error (line 1): Unknown tag 'error'", <<~LIQUID) - {%- liquid error no such tag -%} + {%- liquid error no such tag -%} LIQUID assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID) - {{ test }} + {{ test }} - {%- - liquid - for value in array + {%- + liquid + for value in array - error no such tag - endfor - -%} + error no such tag + endfor + -%} LIQUID assert_match_syntax_error("syntax error (line 2): Unknown tag '!!! the guards are vigilant'", <<~LIQUID) - {%- liquid - !!! the guards are vigilant - -%} + {%- liquid + !!! the guards are vigilant + -%} LIQUID assert_match_syntax_error("syntax error (line 4): 'for' tag was never closed", <<~LIQUID) - {%- liquid - for value in array - echo 'forgot to close the for tag' - -%} + {%- liquid + for value in array + echo 'forgot to close the for tag' + -%} LIQUID end @@ -81,24 +81,24 @@ class LiquidTagTest < Minitest::Test def test_cannot_open_blocks_living_past_a_liquid_tag assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID) - {%- liquid - if true - -%} - {%- endif -%} + {%- liquid + if true + -%} + {%- endif -%} LIQUID end def test_quirk_can_close_blocks_created_before_a_liquid_tag assert_template_result("42", <<~LIQUID) - {%- if true -%} - 42 - {%- liquid endif -%} + {%- if true -%} + 42 + {%- liquid endif -%} LIQUID end def test_liquid_tag_in_raw assert_template_result("{% liquid echo 'test' %}\n", <<~LIQUID) - {% raw %}{% liquid echo 'test' %}{% endraw %} + {% raw %}{% liquid echo 'test' %}{% endraw %} LIQUID end end From b3097f143cfb3bbec4f010c5a0a86e368be70464 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Wed, 28 Aug 2019 21:28:49 +0530 Subject: [PATCH 49/74] Build only pushes to certain branches on Travis CI --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6fb9aa5..b6a7db0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: ruby +cache: bundler rvm: - 2.4 @@ -9,7 +10,6 @@ rvm: - jruby-head - truffleruby - matrix: include: - rvm: *latest_ruby @@ -20,9 +20,11 @@ matrix: - rvm: jruby-head - rvm: truffleruby -cache: bundler - -script: bundle exec rake +branches: + only: + - master + - gh-pages + - /.*-stable/ notifications: disable: true From 2324564743f005a3366ab3cecc3e5c2532433f81 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Thu, 29 Aug 2019 08:50:36 +1000 Subject: [PATCH 50/74] Remove lazy load stacks Remove lazy load stacks and instead only create a new scope when a tag is known to need one --- lib/liquid/context.rb | 35 ++++++++++++++++++----------------- lib/liquid/tags/case.rb | 16 +++++++--------- lib/liquid/tags/cycle.rb | 28 +++++++++++++--------------- lib/liquid/tags/if.rb | 8 +++----- lib/liquid/tags/ifchanged.rb | 12 +++++------- lib/liquid/tags/unless.rb | 20 +++++++++----------- 6 files changed, 55 insertions(+), 64 deletions(-) diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 2dcc6af8..b341a31b 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -25,8 +25,6 @@ module Liquid @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) squash_instance_assigns_with_environments - @this_stack_used = false - self.exception_renderer = Template.default_exception_renderer if rethrow_errors self.exception_renderer = ->(e) { raise } @@ -111,19 +109,26 @@ module Liquid # end # # context['var] #=> nil - def stack(new_scope = nil) - old_stack_used = @this_stack_used - if new_scope - push(new_scope) - @this_stack_used = true - else - @this_stack_used = false - end - + # + # false or {} can be used to control if a new scope is needed + # + # Example: + # new_scope = false + # context.stack(new_scope) do + # # no scope created + # end + # + # Example: + # new_scope = {} + # context.stack(new_scope) do + # # scope created + # end + # + def stack(new_scope = {}) + push(new_scope) unless new_scope == false yield ensure - pop if @this_stack_used - @this_stack_used = old_stack_used + pop unless new_scope == false end def clear_instance_assigns @@ -132,10 +137,6 @@ module Liquid # Only allow String, Numeric, Hash, Array, Proc, Boolean or Liquid::Drop def []=(key, value) - unless @this_stack_used - @this_stack_used = true - push({}) - end @scopes[0][key] = value end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 92b2ed0d..e167b9df 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -39,16 +39,14 @@ module Liquid end def render_to_output_buffer(context, output) - context.stack do - execute_else_block = true + execute_else_block = true - @blocks.each do |block| - if block.else? - block.attachment.render_to_output_buffer(context, output) if execute_else_block - elsif block.evaluate(context) - execute_else_block = false - block.attachment.render_to_output_buffer(context, output) - end + @blocks.each do |block| + if block.else? + block.attachment.render_to_output_buffer(context, output) if execute_else_block + elsif block.evaluate(context) + execute_else_block = false + block.attachment.render_to_output_buffer(context, output) end end diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index e42244d4..8c11d37b 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -34,25 +34,23 @@ module Liquid def render_to_output_buffer(context, output) context.registers[:cycle] ||= {} - context.stack do - key = context.evaluate(@name) - iteration = context.registers[:cycle][key].to_i + key = context.evaluate(@name) + iteration = context.registers[:cycle][key].to_i - val = context.evaluate(@variables[iteration]) + val = context.evaluate(@variables[iteration]) - if val.is_a?(Array) - val = val.join - elsif !val.is_a?(String) - val = val.to_s - end - - output << val - - iteration += 1 - iteration = 0 if iteration >= @variables.size - context.registers[:cycle][key] = iteration + if val.is_a?(Array) + val = val.join + elsif !val.is_a?(String) + val = val.to_s end + output << val + + iteration += 1 + iteration = 0 if iteration >= @variables.size + context.registers[:cycle][key] = iteration + output end diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 25534a98..709cf7f8 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -40,11 +40,9 @@ module Liquid end def render_to_output_buffer(context, output) - context.stack do - @blocks.each do |block| - if block.evaluate(context) - return block.attachment.render_to_output_buffer(context, output) - end + @blocks.each do |block| + if block.evaluate(context) + return block.attachment.render_to_output_buffer(context, output) end end diff --git a/lib/liquid/tags/ifchanged.rb b/lib/liquid/tags/ifchanged.rb index e3040cec..ddd276cf 100644 --- a/lib/liquid/tags/ifchanged.rb +++ b/lib/liquid/tags/ifchanged.rb @@ -1,14 +1,12 @@ module Liquid class Ifchanged < Block def render_to_output_buffer(context, output) - context.stack do - block_output = '' - super(context, block_output) + block_output = '' + super(context, block_output) - if block_output != context.registers[:ifchanged] - context.registers[:ifchanged] = block_output - output << block_output - end + if block_output != context.registers[:ifchanged] + context.registers[:ifchanged] = block_output + output << block_output end output diff --git a/lib/liquid/tags/unless.rb b/lib/liquid/tags/unless.rb index 18856c3b..32aa3a41 100644 --- a/lib/liquid/tags/unless.rb +++ b/lib/liquid/tags/unless.rb @@ -7,18 +7,16 @@ module Liquid # class Unless < If def render_to_output_buffer(context, output) - context.stack do - # First condition is interpreted backwards ( if not ) - first_block = @blocks.first - unless first_block.evaluate(context) - return first_block.attachment.render_to_output_buffer(context, output) - end + # First condition is interpreted backwards ( if not ) + first_block = @blocks.first + unless first_block.evaluate(context) + return first_block.attachment.render_to_output_buffer(context, output) + end - # After the first condition unless works just like if - @blocks[1..-1].each do |block| - if block.evaluate(context) - return block.attachment.render_to_output_buffer(context, output) - end + # After the first condition unless works just like if + @blocks[1..-1].each do |block| + if block.evaluate(context) + return block.attachment.render_to_output_buffer(context, output) end end From d67de1c9b28c986d93e861e8d56dc0156876e9bd Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Thu, 29 Aug 2019 12:21:49 +1000 Subject: [PATCH 51/74] Follow Shopify ruby style This is the first step in bringing Liquid style inline with Shopify ruby style --- ...ify-github-io-ruby-style-guide-rubocop-yml | 1027 +++++++++++++++++ .rubocop.yml | 122 +- .rubocop_todo.yml | 434 ++++--- 3 files changed, 1280 insertions(+), 303 deletions(-) create mode 100644 .rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml diff --git a/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml b/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml new file mode 100644 index 00000000..5f5212ca --- /dev/null +++ b/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml @@ -0,0 +1,1027 @@ +AllCops: + Exclude: + - 'db/schema.rb' + DisabledByDefault: true + StyleGuideBaseURL: https://shopify.github.io/ruby-style-guide/ + +Lint/AssignmentInCondition: + Enabled: true + +Layout/AccessModifierIndentation: + EnforcedStyle: indent + SupportedStyles: + - outdent + - indent + IndentationWidth: + +Style/Alias: + EnforcedStyle: prefer_alias_method + SupportedStyles: + - prefer_alias + - prefer_alias_method + +Layout/AlignHash: + EnforcedHashRocketStyle: key + EnforcedColonStyle: key + EnforcedLastArgumentHashStyle: ignore_implicit + SupportedLastArgumentHashStyles: + - always_inspect + - always_ignore + - ignore_implicit + - ignore_explicit + +Layout/AlignParameters: + EnforcedStyle: with_fixed_indentation + SupportedStyles: + - with_first_parameter + - with_fixed_indentation + IndentationWidth: + +Style/AndOr: + EnforcedStyle: always + SupportedStyles: + - always + - conditionals + +Style/BarePercentLiterals: + EnforcedStyle: bare_percent + SupportedStyles: + - percent_q + - bare_percent + +Style/BlockDelimiters: + EnforcedStyle: line_count_based + SupportedStyles: + - line_count_based + - semantic + - braces_for_chaining + ProceduralMethods: + - benchmark + - bm + - bmbm + - create + - each_with_object + - measure + - new + - realtime + - tap + - with_object + FunctionalMethods: + - let + - let! + - subject + - watch + IgnoredMethods: + - lambda + - proc + - it + +Style/BracesAroundHashParameters: + EnforcedStyle: no_braces + SupportedStyles: + - braces + - no_braces + - context_dependent + +Layout/CaseIndentation: + EnforcedStyle: end + SupportedStyles: + - case + - end + IndentOneStep: false + IndentationWidth: + +Style/ClassAndModuleChildren: + EnforcedStyle: nested + SupportedStyles: + - nested + - compact + +Style/ClassCheck: + EnforcedStyle: is_a? + SupportedStyles: + - is_a? + - kind_of? + +Style/CommandLiteral: + EnforcedStyle: percent_x + SupportedStyles: + - backticks + - percent_x + - mixed + AllowInnerBackticks: false + +Style/CommentAnnotation: + Keywords: + - TODO + - FIXME + - OPTIMIZE + - HACK + - REVIEW + +Style/ConditionalAssignment: + EnforcedStyle: assign_to_condition + SupportedStyles: + - assign_to_condition + - assign_inside_condition + SingleLineConditionsOnly: true + +Layout/DotPosition: + EnforcedStyle: leading + SupportedStyles: + - leading + - trailing + +Style/EmptyElse: + EnforcedStyle: both + SupportedStyles: + - empty + - nil + - both + +Layout/EmptyLineBetweenDefs: + AllowAdjacentOneLineDefs: false + +Layout/EmptyLinesAroundBlockBody: + EnforcedStyle: no_empty_lines + SupportedStyles: + - empty_lines + - no_empty_lines + +Layout/EmptyLinesAroundClassBody: + EnforcedStyle: no_empty_lines + SupportedStyles: + - empty_lines + - empty_lines_except_namespace + - no_empty_lines + +Layout/EmptyLinesAroundModuleBody: + EnforcedStyle: no_empty_lines + SupportedStyles: + - empty_lines + - empty_lines_except_namespace + - no_empty_lines + +Layout/ExtraSpacing: + AllowForAlignment: true + ForceEqualSignAlignment: false + +Naming/FileName: + Exclude: [] + ExpectMatchingDefinition: false + Regex: + IgnoreExecutableScripts: true + +Layout/IndentFirstArgument: + EnforcedStyle: consistent + SupportedStyles: + - consistent + - special_for_inner_method_call + - special_for_inner_method_call_in_parentheses + IndentationWidth: + +Style/For: + EnforcedStyle: each + SupportedStyles: + - for + - each + +Style/FormatString: + EnforcedStyle: format + SupportedStyles: + - format + - sprintf + - percent + +Style/FrozenStringLiteralComment: + Details: >- + Add `# frozen_string_literal: true` to the top of the file. Frozen string + literals will become the default in a future Ruby version, and we want to + make sure we're ready. + EnforcedStyle: always + SupportedStyles: + - always + - never + +Style/GlobalVars: + AllowedVariables: [] + +Style/HashSyntax: + EnforcedStyle: ruby19 + SupportedStyles: + - ruby19 + - hash_rockets + - no_mixed_keys + - ruby19_no_mixed_keys + UseHashRocketsWithSymbolValues: false + PreferHashRocketsForNonAlnumEndingSymbols: false + +Layout/IndentationConsistency: + EnforcedStyle: normal + SupportedStyles: + - normal + - rails + +Layout/IndentationWidth: + Width: 2 + +Layout/IndentFirstArrayElement: + EnforcedStyle: consistent + SupportedStyles: + - special_inside_parentheses + - consistent + - align_brackets + IndentationWidth: + +Layout/IndentAssignment: + IndentationWidth: + +Layout/IndentFirstHashElement: + EnforcedStyle: consistent + SupportedStyles: + - special_inside_parentheses + - consistent + - align_braces + IndentationWidth: + +Style/LambdaCall: + EnforcedStyle: call + SupportedStyles: + - call + - braces + +Style/Next: + EnforcedStyle: skip_modifier_ifs + MinBodyLength: 3 + SupportedStyles: + - skip_modifier_ifs + - always + +Style/NonNilCheck: + IncludeSemanticChanges: false + +Style/MethodCallWithArgsParentheses: + Enabled: true + IgnoreMacros: true + IgnoredMethods: + - require + - require_relative + - require_dependency + - yield + - raise + - puts + Exclude: + - Gemfile + +Style/MethodDefParentheses: + EnforcedStyle: require_parentheses + SupportedStyles: + - require_parentheses + - require_no_parentheses + - require_no_parentheses_except_multiline + +Naming/MethodName: + EnforcedStyle: snake_case + SupportedStyles: + - snake_case + - camelCase + +Layout/MultilineArrayBraceLayout: + EnforcedStyle: symmetrical + SupportedStyles: + - symmetrical + - new_line + - same_line + +Layout/MultilineHashBraceLayout: + EnforcedStyle: symmetrical + SupportedStyles: + - symmetrical + - new_line + - same_line + +Layout/MultilineMethodCallBraceLayout: + EnforcedStyle: symmetrical + SupportedStyles: + - symmetrical + - new_line + - same_line + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + SupportedStyles: + - aligned + - indented + - indented_relative_to_receiver + IndentationWidth: 2 + +Layout/MultilineMethodDefinitionBraceLayout: + EnforcedStyle: symmetrical + SupportedStyles: + - symmetrical + - new_line + - same_line + +Style/NumericLiteralPrefix: + EnforcedOctalStyle: zero_only + SupportedOctalStyles: + - zero_with_o + - zero_only + +Style/ParenthesesAroundCondition: + AllowSafeAssignment: true + +Style/PercentQLiterals: + EnforcedStyle: lower_case_q + SupportedStyles: + - lower_case_q + - upper_case_q + +Naming/PredicateName: + NamePrefix: + - is_ + NamePrefixBlacklist: + - is_ + NameWhitelist: + - is_a? + Exclude: + - 'spec/**/*' + +Style/PreferredHashMethods: + EnforcedStyle: short + SupportedStyles: + - short + - verbose + +Style/RaiseArgs: + EnforcedStyle: exploded + SupportedStyles: + - compact + - exploded + +Style/RedundantReturn: + AllowMultipleReturnValues: false + +Style/RegexpLiteral: + EnforcedStyle: mixed + SupportedStyles: + - slashes + - percent_r + - mixed + AllowInnerSlashes: false + +Style/SafeNavigation: + ConvertCodeThatCanStartToReturnNil: false + Enabled: true + +Lint/SafeNavigationChain: + Enabled: true + +Style/Semicolon: + AllowAsExpressionSeparator: false + +Style/SignalException: + EnforcedStyle: only_raise + SupportedStyles: + - only_raise + - only_fail + - semantic + +Style/SingleLineMethods: + AllowIfMethodIsEmpty: true + +Layout/SpaceBeforeFirstArg: + AllowForAlignment: true + +Style/SpecialGlobalVars: + EnforcedStyle: use_english_names + SupportedStyles: + - use_perl_names + - use_english_names + +Style/StabbyLambdaParentheses: + EnforcedStyle: require_parentheses + SupportedStyles: + - require_parentheses + - require_no_parentheses + +Style/StringLiteralsInInterpolation: + EnforcedStyle: single_quotes + SupportedStyles: + - single_quotes + - double_quotes + +Layout/SpaceAroundBlockParameters: + EnforcedStyleInsidePipes: no_space + SupportedStylesInsidePipes: + - space + - no_space + +Layout/SpaceAroundEqualsInParameterDefault: + EnforcedStyle: space + SupportedStyles: + - space + - no_space + +Layout/SpaceAroundOperators: + AllowForAlignment: true + +Layout/SpaceBeforeBlockBraces: + EnforcedStyle: space + EnforcedStyleForEmptyBraces: space + SupportedStyles: + - space + - no_space + +Layout/SpaceInsideBlockBraces: + EnforcedStyle: space + SupportedStyles: + - space + - no_space + EnforcedStyleForEmptyBraces: no_space + SpaceBeforeBlockParameters: true + +Layout/SpaceInsideHashLiteralBraces: + EnforcedStyle: space + EnforcedStyleForEmptyBraces: no_space + SupportedStyles: + - space + - no_space + - compact + +Layout/SpaceInsideStringInterpolation: + EnforcedStyle: no_space + SupportedStyles: + - space + - no_space + +Style/SymbolProc: + IgnoredMethods: + - respond_to + - define_method + +Style/TernaryParentheses: + EnforcedStyle: require_no_parentheses + SupportedStyles: + - require_parentheses + - require_no_parentheses + AllowSafeAssignment: true + +Layout/TrailingBlankLines: + EnforcedStyle: final_newline + SupportedStyles: + - final_newline + - final_blank_line + +Style/TrivialAccessors: + ExactNameMatch: true + AllowPredicates: true + AllowDSLWriters: false + IgnoreClassMethods: false + Whitelist: + - to_ary + - to_a + - to_c + - to_enum + - to_h + - to_hash + - to_i + - to_int + - to_io + - to_open + - to_path + - to_proc + - to_r + - to_regexp + - to_str + - to_s + - to_sym + +Naming/VariableName: + EnforcedStyle: snake_case + SupportedStyles: + - snake_case + - camelCase + +Style/WhileUntilModifier: + Enabled: true + +Metrics/BlockNesting: + Max: 3 + +Metrics/LineLength: + Max: 120 + AllowHeredoc: true + AllowURI: true + URISchemes: + - http + - https + IgnoreCopDirectives: false + IgnoredPatterns: + - '\A\s*(remote_)?test(_\w+)?\s.*(do|->)(\s|\Z)' + +Metrics/ParameterLists: + Max: 5 + CountKeywordArgs: false + +Layout/BlockAlignment: + EnforcedStyleAlignWith: either + SupportedStylesAlignWith: + - either + - start_of_block + - start_of_line + +Layout/EndAlignment: + EnforcedStyleAlignWith: variable + SupportedStylesAlignWith: + - keyword + - variable + - start_of_line + +Layout/DefEndAlignment: + EnforcedStyleAlignWith: start_of_line + SupportedStylesAlignWith: + - start_of_line + - def + +Lint/InheritException: + EnforcedStyle: runtime_error + SupportedStyles: + - runtime_error + - standard_error + +Lint/UnusedBlockArgument: + IgnoreEmptyBlocks: true + AllowUnusedKeywordArguments: false + +Lint/UnusedMethodArgument: + AllowUnusedKeywordArguments: false + IgnoreEmptyMethods: true + +Naming/AccessorMethodName: + Enabled: true + +Layout/AlignArray: + Enabled: true + +Style/ArrayJoin: + Enabled: true + +Naming/AsciiIdentifiers: + Enabled: true + +Style/Attr: + Enabled: true + +Style/BeginBlock: + Enabled: true + +Style/BlockComments: + Enabled: true + +Layout/BlockEndNewline: + Enabled: true + +Style/CaseEquality: + Enabled: true + +Style/CharacterLiteral: + Enabled: true + +Naming/ClassAndModuleCamelCase: + Enabled: true + +Style/ClassMethods: + Enabled: true + +Style/ClassVars: + Enabled: true + +Layout/ClosingParenthesisIndentation: + Enabled: true + +Style/ColonMethodCall: + Enabled: true + +Layout/CommentIndentation: + Enabled: true + +Naming/ConstantName: + Enabled: true + +Style/DateTime: + Enabled: true + +Style/DefWithParentheses: + Enabled: true + +Style/EachForSimpleLoop: + Enabled: true + +Style/EachWithObject: + Enabled: true + +Layout/ElseAlignment: + Enabled: true + +Style/EmptyCaseCondition: + Enabled: true + +Layout/EmptyLines: + Enabled: true + +Layout/EmptyLinesAroundAccessModifier: + Enabled: true + +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +Style/EmptyLiteral: + Enabled: true + +Style/EndBlock: + Enabled: true + +Layout/EndOfLine: + Enabled: true + +Style/EvenOdd: + Enabled: true + +Layout/InitialIndentation: + Enabled: true + +Lint/FlipFlop: + Enabled: true + +Style/IfInsideElse: + Enabled: true + +Style/IfUnlessModifierOfIfUnless: + Enabled: true + +Style/IfWithSemicolon: + Enabled: true + +Style/IdenticalConditionalBranches: + Enabled: true + +Style/InfiniteLoop: + Enabled: true + +Layout/LeadingCommentSpace: + Enabled: true + +Style/LineEndConcatenation: + Enabled: true + +Style/MethodCallWithoutArgsParentheses: + Enabled: true + +Style/MethodMissingSuper: + Enabled: true + +Style/MissingRespondToMissing: + Enabled: true + +Layout/MultilineBlockLayout: + Enabled: true + +Style/MultilineIfThen: + Enabled: true + +Style/MultilineMemoization: + Enabled: true + +Style/MultilineTernaryOperator: + Enabled: true + +Style/NegatedIf: + Enabled: true + +Style/NegatedWhile: + Enabled: true + +Style/NestedModifier: + Enabled: true + +Style/NestedParenthesizedCalls: + Enabled: true + +Style/NestedTernaryOperator: + Enabled: true + +Style/NilComparison: + Enabled: true + +Style/Not: + Enabled: true + +Style/OneLineConditional: + Enabled: true + +Naming/BinaryOperatorParameterName: + Enabled: true + +Style/OptionalArguments: + Enabled: true + +Style/ParallelAssignment: + Enabled: true + +Style/PerlBackrefs: + Enabled: true + +Style/Proc: + Enabled: true + +Style/RedundantBegin: + Enabled: true + +Style/RedundantException: + Enabled: true + +Style/RedundantFreeze: + Enabled: true + +Style/RedundantParentheses: + Enabled: true + +Style/RedundantSelf: + Enabled: true + +Style/RedundantSortBy: + Enabled: true + +Layout/RescueEnsureAlignment: + Enabled: true + +Style/RescueModifier: + Enabled: true + +Style/Sample: + Enabled: true + +Style/SelfAssignment: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAfterMethodName: + Enabled: true + +Layout/SpaceAfterNot: + Enabled: true + +Layout/SpaceAfterSemicolon: + Enabled: true + +Layout/SpaceBeforeComma: + Enabled: true + +Layout/SpaceBeforeComment: + Enabled: true + +Layout/SpaceBeforeSemicolon: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceInsideArrayPercentLiteral: + Enabled: true + +Layout/SpaceInsidePercentLiteralDelimiters: + Enabled: true + +Layout/SpaceInsideArrayLiteralBrackets: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +Layout/SpaceInsideRangeLiteral: + Enabled: true + +Style/SymbolLiteral: + Enabled: true + +Layout/Tab: + Enabled: true + +Layout/TrailingWhitespace: + Enabled: true + +Style/UnlessElse: + Enabled: true + +Style/UnneededCapitalW: + Enabled: true + +Style/UnneededInterpolation: + Enabled: true + +Style/UnneededPercentQ: + Enabled: true + +Style/VariableInterpolation: + Enabled: true + +Style/WhenThen: + Enabled: true + +Style/WhileUntilDo: + Enabled: true + +Style/ZeroLengthPredicate: + Enabled: true + +Layout/IndentHeredoc: + EnforcedStyle: squiggly + +Lint/AmbiguousOperator: + Enabled: true + +Lint/AmbiguousRegexpLiteral: + Enabled: true + +Lint/CircularArgumentReference: + Enabled: true + +Layout/ConditionPosition: + Enabled: true + +Lint/Debugger: + Enabled: true + +Lint/DeprecatedClassMethods: + Enabled: true + +Lint/DuplicateMethods: + Enabled: true + +Lint/DuplicatedKey: + Enabled: true + +Lint/EachWithObjectArgument: + Enabled: true + +Lint/ElseLayout: + Enabled: true + +Lint/EmptyEnsure: + Enabled: true + +Lint/EmptyInterpolation: + Enabled: true + +Lint/EndInMethod: + Enabled: true + +Lint/EnsureReturn: + Enabled: true + +Lint/FloatOutOfRange: + Enabled: true + +Lint/FormatParameterMismatch: + Enabled: true + +Lint/HandleExceptions: + Enabled: true + +Lint/ImplicitStringConcatenation: + Description: Checks for adjacent string literals on the same line, which could + better be represented as a single string literal. + +Lint/IneffectiveAccessModifier: + Description: Checks for attempts to use `private` or `protected` to set the visibility + of a class method, which does not work. + +Lint/LiteralAsCondition: + Enabled: true + +Lint/LiteralInInterpolation: + Enabled: true + +Lint/Loop: + Description: Use Kernel#loop with break rather than begin/end/until or begin/end/while + for post-loop tests. + +Lint/NestedMethodDefinition: + Enabled: true + +Lint/NextWithoutAccumulator: + Description: Do not omit the accumulator when calling `next` in a `reduce`/`inject` + block. + +Lint/NonLocalExitFromIterator: + Enabled: true + +Lint/ParenthesesAsGroupedExpression: + Enabled: true + +Lint/PercentStringArray: + Enabled: true + +Lint/PercentSymbolArray: + Enabled: true + +Lint/RandOne: + Description: Checks for `rand(1)` calls. Such calls always return `0` and most + likely a mistake. + +Lint/RequireParentheses: + Enabled: true + +Lint/RescueException: + Enabled: true + +Lint/ShadowedException: + Enabled: true + +Lint/ShadowingOuterLocalVariable: + Enabled: true + +Lint/StringConversionInInterpolation: + Enabled: true + +Lint/UnderscorePrefixedVariableName: + Enabled: true + +Lint/UnifiedInteger: + Enabled: true + +Lint/UnneededCopDisableDirective: + Enabled: true + +Lint/UnneededCopEnableDirective: + Enabled: true + +Lint/UnneededSplatExpansion: + Enabled: true + +Lint/UnreachableCode: + Enabled: true + +Lint/UselessAccessModifier: + ContextCreatingMethods: [] + +Lint/UselessAssignment: + Enabled: true + +Lint/UselessComparison: + Enabled: true + +Lint/UselessElseWithoutRescue: + Enabled: true + +Lint/UselessSetterCall: + Enabled: true + +Lint/Void: + Enabled: true + +Security/Eval: + Enabled: true + +Security/JSONLoad: + Enabled: true + +Security/Open: + Enabled: true + +Lint/BigDecimalNew: + Enabled: true + +Style/Strip: + Enabled: true + +Style/TrailingBodyOnClass: + Enabled: true + +Style/TrailingBodyOnModule: + Enabled: true + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + Enabled: true + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: comma + Enabled: true + +Layout/SpaceInsideReferenceBrackets: + EnforcedStyle: no_space + EnforcedStyleForEmptyBrackets: no_space + Enabled: true + +Style/ModuleFunction: + EnforcedStyle: extend_self + +Lint/OrderedMagicComments: + Enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index 671f0768..6b9aa9fe 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,6 @@ inherit_from: + - https://shopify.github.io/ruby-style-guide/rubocop.yml - .rubocop_todo.yml - - ./.rubocop_todo.yml require: rubocop-performance @@ -9,124 +9,8 @@ Performance: AllCops: Exclude: - - 'performance/shopify/*' - 'vendor/bundle/**/*' - - 'pkg/**' - -Metrics/BlockNesting: - Max: 3 - -Metrics/ModuleLength: - Enabled: false - -Metrics/ClassLength: - Enabled: false - -Lint/AssignmentInCondition: - Enabled: false - -Lint/AmbiguousOperator: - Enabled: false - -Lint/AmbiguousRegexpLiteral: - Enabled: false - -Lint/ParenthesesAsGroupedExpression: - Enabled: false - -Lint/UnusedBlockArgument: - Enabled: false - -Layout/EndAlignment: - EnforcedStyleAlignWith: variable - -Lint/UnusedMethodArgument: - Enabled: false - -Style/SingleLineBlockParams: - Enabled: false - -Style/DoubleNegation: - Enabled: false - -Style/StringLiteralsInInterpolation: - Enabled: false - -Style/AndOr: - Enabled: false - -Style/SignalException: - Enabled: false - -Style/StringLiterals: - Enabled: false - -Style/BracesAroundHashParameters: - Enabled: false - -Style/NumericLiterals: - Enabled: false - -Layout/SpaceInsideArrayLiteralBrackets: - Enabled: false - -Layout/SpaceBeforeBlockBraces: - Enabled: false - -Style/Documentation: - Enabled: false - -Style/ClassAndModuleChildren: - Enabled: false - -Style/TrailingCommaInArrayLiteral: - Enabled: false - -Style/TrailingCommaInHashLiteral: - Enabled: false - -Style/FormatString: - Enabled: false - -Layout/AlignParameters: - EnforcedStyle: with_fixed_indentation - -Layout/MultilineOperationIndentation: - EnforcedStyle: indented - -Style/IfUnlessModifier: - Enabled: false - -Style/RaiseArgs: - Enabled: false - -Style/PreferredHashMethods: - Enabled: false - -Style/RegexpLiteral: - Enabled: false - -Style/SymbolLiteral: - Enabled: false - -Naming/ConstantName: - Enabled: false - -Layout/CaseIndentation: - Enabled: false - -Style/ClassVars: - Enabled: false - -Style/PerlBackrefs: - Enabled: false - -Style/TrivialAccessors: - AllowPredicates: true - -Style/WordArray: - Enabled: false - + Naming/MethodName: Exclude: - - 'example/server/liquid_servlet.rb' + - 'example/server/liquid_servlet.rb' \ No newline at end of file diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 992d135a..22330ae6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,34 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-08-29 00:43:36 +1000 using RuboCop version 0.74.0. +# on 2019-08-29 12:16:25 +1000 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: TreatCommentsAsGroupSeparators, Include. -# Include: **/*.gemspec -Gemspec/OrderedDependencies: - Exclude: - - 'liquid.gemspec' - -# Offense count: 1 -# Configuration parameters: Include. -# Include: **/*.gemspec, -Gemspec/RequiredRubyVersion: - Exclude: - - 'liquid.gemspec' - -# Offense count: 124 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: - Enabled: false - -# Offense count: 7 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. # SupportedHashRocketStyles: key, separator, table @@ -38,32 +16,17 @@ Layout/AlignHash: Exclude: - 'lib/liquid/condition.rb' - 'lib/liquid/expression.rb' + - 'performance/shopify/comment_form.rb' + - 'performance/shopify/database.rb' + - 'performance/shopify/paginate.rb' - 'test/unit/context_unit_test.rb' -# Offense count: 6 +# Offense count: 3 # Cop supports --auto-correct. -Layout/ClosingHeredocIndentation: +# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. +Layout/ExtraSpacing: Exclude: - - 'test/integration/tags/for_tag_test.rb' - -# Offense count: 27 -# Cop supports --auto-correct. -Layout/EmptyLineAfterGuardClause: - Exclude: - - 'lib/liquid/block.rb' - - 'lib/liquid/block_body.rb' - - 'lib/liquid/context.rb' - - 'lib/liquid/drop.rb' - - 'lib/liquid/lexer.rb' - - 'lib/liquid/parser.rb' - - 'lib/liquid/standardfilters.rb' - - 'lib/liquid/strainer.rb' - - 'lib/liquid/tags/for.rb' - - 'lib/liquid/tags/if.rb' - - 'lib/liquid/tags/include.rb' - - 'lib/liquid/utils.rb' - - 'lib/liquid/variable.rb' - - 'lib/liquid/variable_lookup.rb' + - 'performance/shopify/paginate.rb' # Offense count: 5 # Cop supports --auto-correct. @@ -83,12 +46,61 @@ Layout/MultilineMethodCallBraceLayout: - 'test/integration/error_handling_test.rb' - 'test/unit/strainer_unit_test.rb' -# Offense count: 1 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment. Layout/SpaceAroundOperators: Exclude: - 'lib/liquid/condition.rb' + - 'performance/shopify/paginate.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceBeforeBlockBraces: + Exclude: + - 'example/server/server.rb' + - 'lib/liquid/variable.rb' + - 'test/integration/drop_test.rb' + - 'test/integration/standard_filter_test.rb' + - 'test/integration/tags/if_else_tag_test.rb' + +# Offense count: 19 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBrackets: space, no_space +Layout/SpaceInsideArrayLiteralBrackets: + Exclude: + - 'test/integration/drop_test.rb' + - 'test/integration/standard_filter_test.rb' + - 'test/integration/tags/for_tag_test.rb' + - 'test/integration/tags/include_tag_test.rb' + - 'test/integration/tags/standard_tag_test.rb' + - 'test/unit/context_unit_test.rb' + +# Offense count: 2 +Lint/AmbiguousOperator: + Exclude: + - 'test/unit/condition_unit_test.rb' + +# Offense count: 16 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Exclude: + - 'lib/liquid/block_body.rb' + - 'lib/liquid/lexer.rb' + - 'lib/liquid/standardfilters.rb' + - 'lib/liquid/tags/for.rb' + - 'lib/liquid/tags/if.rb' + - 'lib/liquid/tags/include.rb' + - 'lib/liquid/tags/raw.rb' + - 'lib/liquid/variable.rb' + - 'performance/profile.rb' + - 'test/test_helper.rb' + - 'test/unit/tokenizer_unit_test.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -98,87 +110,133 @@ Lint/InheritException: Exclude: - 'lib/liquid/interrupts.rb' +# Offense count: 10 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'lib/liquid/condition.rb' + - 'lib/liquid/context.rb' + - 'lib/liquid/document.rb' + - 'lib/liquid/parse_context.rb' + - 'lib/liquid/template.rb' + - 'performance/shopify/json_filter.rb' + - 'test/integration/filter_test.rb' + - 'test/integration/render_profiling_test.rb' + - 'test/integration/variable_test.rb' + - 'test/unit/condition_unit_test.rb' + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Exclude: + - 'example/server/liquid_servlet.rb' + - 'test/integration/blank_test.rb' + - 'test/integration/error_handling_test.rb' + - 'test/integration/filter_test.rb' + - 'test/integration/output_test.rb' + - 'test/integration/standard_filter_test.rb' + - 'test/integration/tags/include_tag_test.rb' + - 'test/unit/strainer_unit_test.rb' + +# Offense count: 2 +Lint/UselessAssignment: + Exclude: + - 'performance/shopify/database.rb' + # Offense count: 1 # Configuration parameters: CheckForMethodsWithNoSideEffects. Lint/Void: Exclude: - 'lib/liquid/parse_context.rb' -# Offense count: 53 -Metrics/AbcSize: - Max: 56 - -# Offense count: 12 -Metrics/CyclomaticComplexity: - Max: 13 - -# Offense count: 118 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/MethodLength: - Max: 38 - -# Offense count: 9 -Metrics/PerceivedComplexity: - Max: 11 - -# Offense count: 1 +# Offense count: 95 # Cop supports --auto-correct. -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Exclude: - - 'lib/liquid/context.rb' +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 294 -# Offense count: 20 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db -Naming/UncommunicativeMethodParamName: +# Offense count: 44 +Naming/ConstantName: Exclude: - - 'example/server/example_servlet.rb' - - 'lib/liquid/condition.rb' - - 'lib/liquid/context.rb' - - 'lib/liquid/standardfilters.rb' + - 'lib/liquid.rb' + - 'lib/liquid/block_body.rb' + - 'lib/liquid/tags/assign.rb' + - 'lib/liquid/tags/capture.rb' + - 'lib/liquid/tags/case.rb' + - 'lib/liquid/tags/cycle.rb' + - 'lib/liquid/tags/for.rb' - 'lib/liquid/tags/if.rb' + - 'lib/liquid/tags/include.rb' + - 'lib/liquid/tags/raw.rb' + - 'lib/liquid/tags/table_row.rb' - 'lib/liquid/variable.rb' + - 'performance/shopify/comment_form.rb' + - 'performance/shopify/paginate.rb' + - 'test/integration/tags/include_tag_test.rb' + +# Offense count: 2 +# Configuration parameters: . +# SupportedStyles: snake_case, camelCase +Naming/MethodName: + EnforcedStyle: snake_case + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, conditionals +Style/AndOr: + Exclude: + - 'lib/liquid/i18n.rb' + - 'lib/liquid/tags/table_row.rb' + - 'lib/liquid/tokenizer.rb' + +# Offense count: 40 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Exclude: + - 'test/integration/error_handling_test.rb' - 'test/integration/filter_test.rb' + - 'test/integration/render_profiling_test.rb' - 'test/integration/standard_filter_test.rb' + - 'test/integration/tags/echo_test.rb' + - 'test/integration/tags/increment_tag_test.rb' + - 'test/integration/tags/standard_tag_test.rb' - 'test/integration/template_test.rb' - 'test/unit/condition_unit_test.rb' + - 'test/unit/context_unit_test.rb' # Offense count: 5 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: inline, group -Style/AccessModifierDeclarations: +Style/ClassVars: Exclude: - - 'lib/liquid/block_body.rb' - - 'lib/liquid/tag.rb' - - 'lib/liquid/tags/include.rb' - - 'test/unit/strainer_unit_test.rb' + - 'lib/liquid/condition.rb' + - 'lib/liquid/strainer.rb' + - 'lib/liquid/template.rb' -# Offense count: 10 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: prefer_alias, prefer_alias_method -Style/Alias: - Exclude: - - 'lib/liquid/drop.rb' - - 'lib/liquid/i18n.rb' - - 'lib/liquid/profiler/hooks.rb' - - 'lib/liquid/standardfilters.rb' - - 'lib/liquid/tag.rb' - - 'lib/liquid/tags/include.rb' - - 'lib/liquid/variable.rb' - -# Offense count: 22 -Style/CommentedKeyword: - Enabled: false - -# Offense count: 1 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'lib/liquid/errors.rb' + - 'performance/shopify/shop_filter.rb' + +# Offense count: 1 +# Configuration parameters: AllowCoercion. +Style/DateTime: + Exclude: + - 'test/unit/context_unit_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/EachWithObject: + Exclude: + - 'performance/shopify/database.rb' # Offense count: 1 # Cop supports --auto-correct. @@ -186,71 +244,50 @@ Style/EmptyCaseCondition: Exclude: - 'lib/liquid/lexer.rb' -# Offense count: 5 +# Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. -# SupportedStyles: compact, expanded -Style/EmptyMethod: +# SupportedStyles: each, for +Style/For: Exclude: - - 'lib/liquid/tag.rb' - - 'lib/liquid/tags/comment.rb' - - 'lib/liquid/tags/include.rb' - - 'test/integration/tags/include_tag_test.rb' - - 'test/unit/context_unit_test.rb' + - 'performance/shopify/shop_filter.rb' -# Offense count: 3 +# Offense count: 9 # Cop supports --auto-correct. -Style/Encoding: - Exclude: - - 'lib/liquid/version.rb' - - 'liquid.gemspec' - - 'test/integration/standard_filter_test.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/ExpandPathArguments: - Exclude: - - 'Rakefile' - - 'liquid.gemspec' - -# Offense count: 7 # Configuration parameters: EnforcedStyle. -# SupportedStyles: annotated, template, unannotated -Style/FormatStringToken: +# SupportedStyles: format, sprintf, percent +Style/FormatString: Exclude: + - 'example/server/example_servlet.rb' + - 'performance/shopify/money_filter.rb' + - 'performance/shopify/weight_filter.rb' - 'test/integration/filter_test.rb' - 'test/integration/hash_ordering_test.rb' -# Offense count: 106 +# Offense count: 115 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, never Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 14 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Exclude: - - 'lib/liquid/condition.rb' - - 'lib/liquid/lexer.rb' - - 'lib/liquid/strainer.rb' - - 'lib/liquid/tags/assign.rb' - - 'lib/liquid/tags/capture.rb' - - 'lib/liquid/tags/case.rb' - - 'lib/liquid/tags/for.rb' - - 'lib/liquid/tags/include.rb' - - 'lib/liquid/tags/raw.rb' - - 'lib/liquid/tags/table_row.rb' - - 'lib/liquid/variable.rb' - - 'test/unit/tokenizer_unit_test.rb' - -# Offense count: 53 +# Offense count: 30 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Enabled: false +# Configuration parameters: IgnoreMacros, IgnoredMethods, IncludedMacros, AllowParenthesesInMultilineCall, AllowParenthesesInChaining, AllowParenthesesInCamelCaseMethod, EnforcedStyle. +# SupportedStyles: require_parentheses, omit_parentheses +Style/MethodCallWithArgsParentheses: + Exclude: + - 'Gemfile' + - 'Rakefile' + - 'lib/liquid/block_body.rb' + - 'lib/liquid/parser.rb' + - 'lib/liquid/tags/for.rb' + - 'liquid.gemspec' + - 'performance/shopify/database.rb' + - 'performance/shopify/liquid.rb' + - 'test/test_helper.rb' + - 'test/unit/condition_unit_test.rb' + - 'test/unit/tags/if_tag_unit_test.rb' # Offense count: 1 # Cop supports --auto-correct. @@ -260,29 +297,17 @@ Style/Next: Exclude: - 'lib/liquid/tags/for.rb' -# Offense count: 13 +# Offense count: 52 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. -# SupportedStyles: predicate, comparison -Style/NumericPredicate: - Exclude: - - 'spec/**/*' - - 'lib/liquid/context.rb' - - 'lib/liquid/forloop_drop.rb' - - 'lib/liquid/standardfilters.rb' - - 'lib/liquid/tablerowloop_drop.rb' - - 'test/integration/standard_filter_test.rb' - - 'test/integration/template_test.rb' +Style/PerlBackrefs: + Enabled: false -# Offense count: 14 +# Offense count: 33 # Cop supports --auto-correct. -# Configuration parameters: PreferredDelimiters. -Style/PercentLiteralDelimiters: - Exclude: - - 'lib/liquid/tags/if.rb' - - 'liquid.gemspec' - - 'test/integration/assign_test.rb' - - 'test/integration/standard_filter_test.rb' +# Configuration parameters: EnforcedStyle. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Enabled: false # Offense count: 1 # Cop supports --auto-correct. @@ -290,6 +315,17 @@ Style/RedundantSelf: Exclude: - 'lib/liquid/strainer.rb' +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Exclude: + - 'lib/liquid/file_system.rb' + - 'lib/liquid/standardfilters.rb' + - 'performance/shopify/shop_filter.rb' + - 'test/unit/condition_unit_test.rb' + # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. @@ -300,21 +336,31 @@ Style/SafeNavigation: - 'lib/liquid/strainer.rb' - 'lib/liquid/tokenizer.rb' -# Offense count: 9 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: + - 'performance/shopify/database.rb' - 'test/integration/error_handling_test.rb' - 'test/integration/template_test.rb' - 'test/unit/context_unit_test.rb' -# Offense count: 7 +# Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - EnforcedStyle: brackets +# Configuration parameters: EnforcedStyle. +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + Exclude: + - 'performance/shopify/liquid.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Exclude: + - 'performance/shopify/tag_filter.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -325,6 +371,33 @@ Style/TernaryParentheses: - 'lib/liquid/context.rb' - 'lib/liquid/utils.rb' +# Offense count: 21 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'lib/liquid/parse_tree_visitor.rb' + - 'lib/liquid/tags/include.rb' + - 'test/integration/parse_tree_visitor_test.rb' + - 'test/integration/standard_filter_test.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'lib/liquid/condition.rb' + - 'lib/liquid/lexer.rb' + - 'lib/liquid/standardfilters.rb' + - 'performance/shopify/comment_form.rb' + - 'performance/shopify/database.rb' + - 'performance/shopify/paginate.rb' + - 'performance/theme_runner.rb' + - 'test/integration/output_test.rb' + - 'test/unit/context_unit_test.rb' + # Offense count: 2 # Cop supports --auto-correct. Style/UnneededPercentQ: @@ -336,10 +409,3 @@ Style/UnneededPercentQ: Style/WhileUntilModifier: Exclude: - 'lib/liquid/tags/case.rb' - -# Offense count: 665 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 294 From d338ccb9a631a4f021de91272e3972d586061584 Mon Sep 17 00:00:00 2001 From: Samuel Date: Mon, 22 Jul 2019 11:38:23 -0400 Subject: [PATCH 52/74] Add isolated subcontexts An isolated subcontext inherits the environment, filters, and static registers of its supercontext, but with a fresh (isolated) scope. This will pave the way for adding the `render` tag, which renders templates in such a subcontext. --- lib/liquid/context.rb | 22 +- lib/liquid/tags/render.rb | 89 ++++++++ test/integration/tags/render_rag_test.rb | 255 +++++++++++++++++++++++ test/integration/tags/render_tag_test.rb | 230 ++++++++++++++++++++ test/unit/context_unit_test.rb | 58 ++++++ 5 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 lib/liquid/tags/render.rb create mode 100644 test/integration/tags/render_rag_test.rb create mode 100644 test/integration/tags/render_tag_test.rb diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 2dcc6af8..a05cdaa4 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -12,13 +12,18 @@ module Liquid # # context['bob'] #=> nil class Context class Context - attr_reader :scopes, :errors, :registers, :environments, :resource_limits + attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters - def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) + def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}) + new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers) + end + + def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}) @environments = [environments].flatten @scopes = [(outer_scope || {})] @registers = registers + @static_registers = static_registers.tap(&:freeze) @errors = [] @partial = false @strict_variables = false @@ -126,6 +131,19 @@ module Liquid @this_stack_used = old_stack_used end + # Creates a new context inheriting resource limits, filters, environment etc., + # but with an isolated scope. + def new_isolated_subcontext + Context.build( + environments: environments, + resource_limits: resource_limits, + static_registers: static_registers + ).tap do |subcontext| + subcontext.exception_renderer = exception_renderer + subcontext.add_filters(@filters) + end + end + def clear_instance_assigns @scopes[0] = {} end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb new file mode 100644 index 00000000..f0f6f107 --- /dev/null +++ b/lib/liquid/tags/render.rb @@ -0,0 +1,89 @@ +module Liquid + # + # TODO: docs + # + class Render < Tag + Syntax = /(#{QuotedFragment}+)/o + + attr_reader :template_name_expr, :attributes + + def initialize(tag_name, markup, options) + super + + if markup =~ Syntax + template_name = $1 + + @template_name_expr = Expression.parse(template_name) + + @attributes = {} + markup.scan(TagAttributes) do |key, value| + @attributes[key] = Expression.parse(value) + end + + else + raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) + end + end + + def parse(_tokens) + end + + def render_to_output_buffer(context, output) + template_name = context.evaluate(@template_name_expr) + raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name + + partial = load_cached_partial(template_name, context) + + inner_context = Context.new + inner_context.template_name = template_name + inner_context.partial = true + @attributes.each do |key, value| + inner_context[key] = context.evaluate(value) + end + partial.render_to_output_buffer(inner_context, output) + + # TODO: Put into a new #isolated_stack method in Context? + inner_context.errors.each { |e| context.errors << e } + + output + end + + private + + alias_method :parse_context, :options + private :parse_context + + def load_cached_partial(template_name, context) + cached_partials = context.registers[:cached_partials] || {} + + if cached = cached_partials[template_name] + return cached + end + source = read_template_from_file_system(context) + begin + parse_context.partial = true + partial = Liquid::Template.parse(source, parse_context) + ensure + parse_context.partial = false + end + cached_partials[template_name] = partial + context.registers[:cached_partials] = cached_partials + partial + end + + def read_template_from_file_system(context) + file_system = context.registers[:file_system] || Liquid::Template.file_system + file_system.read_template_file(context.evaluate(@template_name_expr)) + end + + class ParseTreeVisitor < Liquid::ParseTreeVisitor + def children + [ + @node.template_name_expr, + ] + @node.attributes.values + end + end + end + + Template.register_tag('render'.freeze, Render) +end diff --git a/test/integration/tags/render_rag_test.rb b/test/integration/tags/render_rag_test.rb new file mode 100644 index 00000000..2352bc12 --- /dev/null +++ b/test/integration/tags/render_rag_test.rb @@ -0,0 +1,255 @@ +require 'test_helper' + +class TestFileSystem + def read_template_file(template_path) + case template_path + when "product" + "Product: {{ product.title }} " + + when "locale_variables" + "Locale: {{echo1}} {{echo2}}" + + when "variant" + "Variant: {{ variant.title }}" + + when "nested_template" + "{% include 'header' %} {% include 'body' %} {% include 'footer' %}" + + when "body" + "body {% include 'body_detail' %}" + + when "nested_product_template" + "Product: {{ nested_product_template.title }} {%include 'details'%} " + + when "recursively_nested_template" + "-{% include 'recursively_nested_template' %}" + + when "pick_a_source" + "from TestFileSystem" + + when 'assignments' + "{% assign foo = 'bar' %}" + + when 'break' + "{% break %}" + + else + template_path + end + end +end + +class OtherFileSystem + def read_template_file(template_path) + 'from OtherFileSystem' + end +end + +class CountingFileSystem + attr_reader :count + def read_template_file(template_path) + @count ||= 0 + @count += 1 + 'from CountingFileSystem' + end +end + +class CustomInclude < Liquid::Tag + Syntax = /(#{Liquid::QuotedFragment}+)(\s+(?:with|for)\s+(#{Liquid::QuotedFragment}+))?/o + + def initialize(tag_name, markup, tokens) + markup =~ Syntax + @template_name = $1 + super + end + + def parse(tokens) + end + + def render_to_output_buffer(context, output) + output << @template_name[1..-2] + output + end +end + +class IncludeTagTest < Minitest::Test + include Liquid + + def setup + Liquid::Template.file_system = TestFileSystem.new + end + + def test_include_tag_looks_for_file_system_in_registers_first + assert_equal 'from OtherFileSystem', + Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }) + end + + def test_include_tag_with + assert_template_result "Product: Draft 151cm ", + "{% include 'product' with products[0] %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] + end + + def test_include_tag_with_default_name + assert_template_result "Product: Draft 151cm ", + "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' } + end + + def test_include_tag_for + assert_template_result "Product: Draft 151cm Product: Element 155cm ", + "{% include 'product' for products %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] + end + + def test_include_tag_with_local_variables + assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}" + end + + def test_include_tag_with_multiple_local_variables + assert_template_result "Locale: test123 test321", + "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}" + end + + def test_include_tag_with_multiple_local_variables_from_context + assert_template_result "Locale: test123 test321", + "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}", + 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } + end + + def test_included_templates_assigns_variables + assert_template_result "bar", "{% include 'assignments' %}{{ foo }}" + end + + def test_nested_include_tag + assert_template_result "body body_detail", "{% include 'body' %}" + + assert_template_result "header body body_detail footer", "{% include 'nested_template' %}" + end + + def test_nested_include_with_variable + assert_template_result "Product: Draft 151cm details ", + "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' } + + assert_template_result "Product: Draft 151cm details Product: Element 155cm details ", + "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] + end + + def test_recursively_included_template_does_not_produce_endless_loop + infinite_file_system = Class.new do + def read_template_file(template_path) + "-{% include 'loop' %}" + end + end + + Liquid::Template.file_system = infinite_file_system.new + + assert_raises(Liquid::StackLevelError) do + Template.parse("{% include 'loop' %}").render! + end + end + + def test_dynamically_choosen_template + assert_template_result "Test123", "{% include template %}", "template" => 'Test123' + assert_template_result "Test321", "{% include template %}", "template" => 'Test321' + + assert_template_result "Product: Draft 151cm ", "{% include template for product %}", + "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } + end + + def test_include_tag_caches_second_read_of_same_partial + file_system = CountingFileSystem.new + assert_equal 'from CountingFileSystemfrom CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.count + end + + def test_include_tag_doesnt_cache_partials_across_renders + file_system = CountingFileSystem.new + assert_equal 'from CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.count + + assert_equal 'from CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 2, file_system.count + end + + def test_include_tag_within_if_statement + assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}" + end + + def test_custom_include_tag + original_tag = Liquid::Template.tags['include'] + Liquid::Template.tags['include'] = CustomInclude + begin + assert_equal "custom_foo", + Template.parse("{% include 'custom_foo' %}").render! + ensure + Liquid::Template.tags['include'] = original_tag + end + end + + def test_custom_include_tag_within_if_statement + original_tag = Liquid::Template.tags['include'] + Liquid::Template.tags['include'] = CustomInclude + begin + assert_equal "custom_foo_if_true", + Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render! + ensure + Liquid::Template.tags['include'] = original_tag + end + end + + def test_does_not_add_error_in_strict_mode_for_missing_variable + Liquid::Template.file_system = TestFileSystem.new + + a = Liquid::Template.parse(' {% include "nested_template" %}') + a.render! + assert_empty a.errors + end + + def test_passing_options_to_included_templates + assert_raises(Liquid::SyntaxError) do + Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}') + end + with_error_mode(:lax) do + assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}') + end + assert_raises(Liquid::SyntaxError) do + Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}') + end + with_error_mode(:lax) do + assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}') + end + end + + def test_render_raise_argument_error_when_template_is_undefined + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include undefined_variable %}') + template.render! + end + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include nil %}') + template.render! + end + end + + def test_including_via_variable_value + assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}" + + assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' } + + assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } + end + + def test_including_with_strict_variables + template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) + template.render(nil, strict_variables: true) + + assert_equal [], template.errors + end + + def test_break_through_include + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}" + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}" + end +end # IncludeTagTest + diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb new file mode 100644 index 00000000..928dc385 --- /dev/null +++ b/test/integration/tags/render_tag_test.rb @@ -0,0 +1,230 @@ +require 'test_helper' + +class StubFileSystem + def initialize(values) + @values = values + end + + def read_template_file(template_path) + @values.fetch(template_path) + end +end + +class RenderTagTest < Minitest::Test + include Liquid + + def test_render_with_no_arguments + Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content') + assert_template_result 'rendered content', "{% render 'source' %}" + end + + def test_render_tag_looks_for_file_system_in_registers_first + file_system = StubFileSystem.new('pick_a_source' => 'from register file system') + assert_equal 'from register file system', + Template.parse("{% render 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + end + + def test_render_passes_named_arguments_into_inner_scope + Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}') + assert_template_result 'My Product', "{% render 'product', inner_product: outer_product %}", + 'outer_product' => { 'title' => 'My Product' } + end + + def test_render_accepts_literals_as_arguments + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}') + assert_template_result '123', "{% render 'snippet', price: 123 %}" + end + + def test_render_accepts_multiple_named_arguments + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}') + assert_template_result '1 2', "{% render 'snippet', one: 1, two: 2 %}" + end + + def test_render_does_inherit_parent_scope_variables + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}') + assert_template_result '', "{% render 'snippet' %}", 'outer_variable' => 'should not be visible' + end + + def test_render_does_not_inherit_variable_with_same_name_as_snippet + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}') + assert_template_result '', "{% render 'snippet' %}", 'snippet' => 'should not be visible' + end + + def test_render_sets_the_correct_template_name_for_errors + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}') + Liquid::Template.taint_mode = :error + + template = Liquid::Template.parse("{% render 'snippet', unsafe: unsafe %}") + template.render('unsafe' => String.new('unsafe').tap(&:taint)) + refute_empty template.errors + + assert_equal 'snippet', template.errors.first.template_name + end + + def test_render_sets_the_correct_template_name_for_warnings + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}') + Liquid::Template.taint_mode = :warn + + template = Liquid::Template.parse("{% render 'snippet', unsafe: unsafe %}") + template.render('unsafe' => String.new('unsafe').tap(&:taint)) + refute_empty template.warnings + + assert_equal 'snippet', template.errors.first.template_name + end + + def test_render_does_not_mutate_parent_scope + Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}') + assert_template_result '', "{% render 'snippet' %}{{ inner }}" + end + + def test_nested_render_tag + Liquid::Template.file_system = StubFileSystem.new( + 'one' => "one {{ render 'two' }}", + 'two' => 'two' + ) + assert_template_result 'one two', "{% include 'one' %}" + end + + def test_nested_include_with_variable + skip 'To be implemented' + assert_template_result "Product: Draft 151cm details ", + "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' } + + assert_template_result "Product: Draft 151cm details Product: Element 155cm details ", + "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] + end + + def test_recursively_included_template_does_not_produce_endless_loop + skip 'To be implemented' + infinite_file_system = Class.new do + def read_template_file(template_path) + "-{% include 'loop' %}" + end + end + + Liquid::Template.file_system = infinite_file_system.new + + assert_raises(Liquid::StackLevelError) do + Template.parse("{% include 'loop' %}").render! + end + end + + def test_dynamically_choosen_template + skip 'To be implemented' + assert_template_result "Test123", "{% include template %}", "template" => 'Test123' + assert_template_result "Test321", "{% include template %}", "template" => 'Test321' + + assert_template_result "Product: Draft 151cm ", "{% include template for product %}", + "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } + end + + def test_include_tag_caches_second_read_of_same_partial + skip 'To be implemented' + file_system = CountingFileSystem.new + assert_equal 'from CountingFileSystemfrom CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.count + end + + def test_include_tag_doesnt_cache_partials_across_renders + skip 'To be implemented' + file_system = CountingFileSystem.new + assert_equal 'from CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.count + + assert_equal 'from CountingFileSystem', + Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + assert_equal 2, file_system.count + end + + def test_include_tag_within_if_statement + skip 'To be implemented' + assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}" + end + + def test_custom_include_tag + skip 'To be implemented' + original_tag = Liquid::Template.tags['include'] + Liquid::Template.tags['include'] = CustomInclude + begin + assert_equal "custom_foo", + Template.parse("{% include 'custom_foo' %}").render! + ensure + Liquid::Template.tags['include'] = original_tag + end + end + + def test_custom_include_tag_within_if_statement + skip 'To be implemented' + original_tag = Liquid::Template.tags['include'] + Liquid::Template.tags['include'] = CustomInclude + begin + assert_equal "custom_foo_if_true", + Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render! + ensure + Liquid::Template.tags['include'] = original_tag + end + end + + def test_does_not_add_error_in_strict_mode_for_missing_variable + skip 'To be implemented' + Liquid::Template.file_system = TestFileSystem.new + + a = Liquid::Template.parse(' {% include "nested_template" %}') + a.render! + assert_empty a.errors + end + + def test_passing_options_to_included_templates + skip 'To be implemented' + assert_raises(Liquid::SyntaxError) do + Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}') + end + with_error_mode(:lax) do + assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}') + end + assert_raises(Liquid::SyntaxError) do + Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}') + end + with_error_mode(:lax) do + assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}') + end + end + + def test_render_raise_argument_error_when_template_is_undefined + skip 'To be implemented' + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include undefined_variable %}') + template.render! + end + assert_raises(Liquid::ArgumentError) do + template = Liquid::Template.parse('{% include nil %}') + template.render! + end + end + + def test_including_via_variable_value + skip 'To be implemented' + assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}" + + assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' } + + assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } + end + + def test_including_with_strict_variables + skip 'To be implemented' + template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) + template.render(nil, strict_variables: true) + + assert_equal [], template.errors + end + + def test_break_through_include + skip 'To be implemented' + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}" + assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}" + end +end # IncludeTagTest + diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index fab19b84..15457633 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -473,6 +473,64 @@ class ContextUnitTest < Minitest::Test assert_equal 'hi', context.apply_global_filter('hi') end + def test_new_isolated_subcontext_does_not_inherit_variables + super_context = Context.new + super_context['my_variable'] = 'some value' + subcontext = super_context.new_isolated_subcontext + + assert_nil subcontext['my_variable'] + end + + def test_new_isolated_subcontext_inherits_environment + super_context = Context.new('my_environment_value' => 'my value') + subcontext = super_context.new_isolated_subcontext + + assert_equal 'my value',subcontext['my_environment_value'] + end + + def test_new_isolated_subcontext_inherits_resource_limits + resource_limits = ResourceLimits.new({}) + super_context = Context.new({}, {}, {}, false, resource_limits) + subcontext = super_context.new_isolated_subcontext + assert_equal resource_limits, subcontext.resource_limits + end + + def test_new_isolated_subcontext_inherits_exception_renderer + super_context = Context.new + super_context.exception_renderer = -> (_e) { 'my exception message' } + subcontext = super_context.new_isolated_subcontext + assert_equal 'my exception message', subcontext.handle_error(Liquid::Error.new) + end + + def test_new_isolated_subcontext_does_not_inherit_non_static_registers + registers = { + my_register: :my_value + } + super_context = Context.new({}, {}, registers) + subcontext = super_context.new_isolated_subcontext + assert_nil subcontext.registers[:my_register] + end + + def test_new_isolated_subcontext_inherits_static_registers + super_context = Context.build(static_registers: { my_register: :my_value }) + subcontext = super_context.new_isolated_subcontext + assert_equal :my_value, subcontext.static_registers[:my_register] + end + + def test_new_isolated_subcontext_inherits_filters + my_filter = Module.new do + def my_filter(*) + 'my filter result' + end + end + + super_context = Context.new + super_context.add_filters([my_filter]) + subcontext = super_context.new_isolated_subcontext + template = Template.parse('{{ 123 | my_filter }}') + assert_equal 'my filter result', template.render(subcontext) + end + private def assert_no_object_allocations From 9672ed52850913bdafdc66d1f5ab09e4d8ed6a5c Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 31 Jul 2019 11:20:49 -0400 Subject: [PATCH 53/74] Add a new `{% render %}` tag Example: ``` // the_count.liquid {{ number }}! Ah ah ah. // my_template.liquid {% for number in range (1..3) %} {% render "the_count", number: number %} {% endfor %} Output: 1! Ah ah ah. 2! Ah ah ah. 3! Ah ah ah. ``` The `render` tag is a more strict version of the `include` tag. It is designed to isolate itself from the parent rendering context both by creating a new scope (which does not inherit the parent scope) and by only inheriting "static" registers. Static registers are those that do not hold mutable state which could affect rendering. This again helps `render`ed templates remain entirely separate from their calling context. Unlike `include`, `render` does not permit specifying the target template using a variable, only a string literal. For example, this means that `{% render my_dynamic_template %}` is invalid syntax. This will make it possible to statically analyze the dependencies between templates without making Turing angry. Note that the `static_environment` of a rendered template is inherited, unlike the scope and regular environment. This environment is immutable from within the template. An alternate syntax, which mimics the `{% include ... for %}` tag is currently in design discussion. --- lib/liquid.rb | 1 + lib/liquid/context.rb | 90 +++++--- lib/liquid/locales/en.yml | 1 + lib/liquid/partial_cache.rb | 18 ++ lib/liquid/tags/include.rb | 33 +-- lib/liquid/tags/increment.rb | 1 + lib/liquid/tags/render.rb | 63 ++---- test/integration/tags/render_rag_test.rb | 255 ----------------------- test/integration/tags/render_tag_test.rb | 225 +++++++------------- test/test_helper.rb | 14 ++ test/unit/context_unit_test.rb | 18 +- test/unit/partial_cache_unit_test.rb | 91 ++++++++ 12 files changed, 291 insertions(+), 519 deletions(-) create mode 100644 lib/liquid/partial_cache.rb delete mode 100644 test/integration/tags/render_rag_test.rb create mode 100644 test/unit/partial_cache_unit_test.rb diff --git a/lib/liquid.rb b/lib/liquid.rb index 770d2f91..0e198bb4 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -74,6 +74,7 @@ require 'liquid/condition' require 'liquid/utils' require 'liquid/tokenizer' require 'liquid/parse_context' +require 'liquid/partial_cache' # Load all the tags of the standard library # diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index a05cdaa4..1b15ca7e 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -12,22 +12,25 @@ module Liquid # # context['bob'] #=> nil class Context class Context - attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers + attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters - def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}) - new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers) + # rubocop:disable Metrics/ParameterLists + def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}, static_environments: {}) + new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers, static_environments) end - def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}) - @environments = [environments].flatten - @scopes = [(outer_scope || {})] - @registers = registers - @static_registers = static_registers.tap(&:freeze) - @errors = [] - @partial = false - @strict_variables = false - @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) + def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {}) + @environments = [environments].flatten + @static_environments = [static_environments].flatten.map(&:freeze).freeze + @scopes = [(outer_scope || {})] + @registers = registers + @static_registers = static_registers.freeze + @errors = [] + @partial = false + @strict_variables = false + @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) + @base_scope_depth = 0 squash_instance_assigns_with_environments @this_stack_used = false @@ -41,6 +44,7 @@ module Liquid @filters = [] @global_filter = nil end + # rubocop:enable Metrics/ParameterLists def warnings @warnings ||= [] @@ -94,7 +98,7 @@ module Liquid # Push new local scope on the stack. use Context#stack instead def push(new_scope = {}) @scopes.unshift(new_scope) - raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH + check_overflow end # Merge a hash of variables in the current local scope @@ -134,13 +138,19 @@ module Liquid # Creates a new context inheriting resource limits, filters, environment etc., # but with an isolated scope. def new_isolated_subcontext + check_overflow + Context.build( - environments: environments, resource_limits: resource_limits, + static_environments: static_environments, static_registers: static_registers ).tap do |subcontext| + subcontext.base_scope_depth = base_scope_depth + 1 subcontext.exception_renderer = exception_renderer - subcontext.add_filters(@filters) + subcontext.filters = @filters + subcontext.strainer = nil + subcontext.errors = errors + subcontext.warnings = warnings end end @@ -182,25 +192,13 @@ module Liquid # This was changed from find() to find_index() because this is a very hot # path and find_index() is optimized in MRI to reduce object allocation index = @scopes.find_index { |s| s.key?(key) } - scope = @scopes[index] if index - variable = nil - - if scope.nil? - @environments.each do |e| - variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found) - # When lookup returned a value OR there is no value but the lookup also did not raise - # then it is the value we are looking for. - if !variable.nil? || @strict_variables && raise_on_not_found - scope = e - break - end - end + variable = if index + lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found) + else + try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found) end - scope ||= @environments.last || @scopes.last - variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found) - variable = variable.to_liquid variable.context = self if variable.respond_to?(:context=) @@ -221,8 +219,38 @@ module Liquid end end + protected + + attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters + private + attr_reader :base_scope_depth + + def try_variable_find_in_environments(key, raise_on_not_found:) + @environments.each do |environment| + found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found) + if !found_variable.nil? || @strict_variables && raise_on_not_found + return found_variable + end + end + @static_environments.each do |environment| + found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found) + if !found_variable.nil? || @strict_variables && raise_on_not_found + return found_variable + end + end + nil + end + + def check_overflow + raise StackLevelError, "Nesting too deep".freeze if overflow? + end + + def overflow? + base_scope_depth + @scopes.length > Block::MAX_DEPTH + end + def internal_error # raise and catch to set backtrace and cause on exception raise Liquid::InternalError, 'internal' diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index 48b3b1d8..c0a9aff7 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -22,5 +22,6 @@ tag_never_closed: "'%{block_name}' tag was never closed" meta_syntax_error: "Liquid syntax error: #{e.message}" 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" diff --git a/lib/liquid/partial_cache.rb b/lib/liquid/partial_cache.rb new file mode 100644 index 00000000..d0b88457 --- /dev/null +++ b/lib/liquid/partial_cache.rb @@ -0,0 +1,18 @@ +module Liquid + class PartialCache + def self.load(template_name, context:, parse_context:) + cached_partials = (context.registers[:cached_partials] ||= {}) + cached = cached_partials[template_name] + return cached if cached + + file_system = (context.registers[:file_system] ||= Liquid::Template.file_system) + source = file_system.read_template_file(template_name) + parse_context.partial = true + + partial = Liquid::Template.parse(source, parse_context) + cached_partials[template_name] = partial + ensure + parse_context.partial = false + end + end +end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 24acf9d9..fd86ee4b 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -46,7 +46,12 @@ module Liquid template_name = context.evaluate(@template_name_expr) raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name - partial = load_cached_partial(template_name, context) + partial = PartialCache.load( + template_name, + context: context, + parse_context: parse_context + ) + context_variable_name = template_name.split('/'.freeze).last variable = if @variable_name_expr @@ -83,35 +88,9 @@ module Liquid output end - private - alias_method :parse_context, :options private :parse_context - def load_cached_partial(template_name, context) - cached_partials = context.registers[:cached_partials] || {} - - if cached = cached_partials[template_name] - return cached - end - source = read_template_from_file_system(context) - begin - parse_context.partial = true - partial = Liquid::Template.parse(source, parse_context) - ensure - parse_context.partial = false - end - cached_partials[template_name] = partial - context.registers[:cached_partials] = cached_partials - partial - end - - def read_template_from_file_system(context) - file_system = context.registers[:file_system] || Liquid::Template.file_system - - file_system.read_template_file(context.evaluate(@template_name_expr)) - end - class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ diff --git a/lib/liquid/tags/increment.rb b/lib/liquid/tags/increment.rb index 5af12422..95875aa6 100644 --- a/lib/liquid/tags/increment.rb +++ b/lib/liquid/tags/increment.rb @@ -23,6 +23,7 @@ module Liquid def render_to_output_buffer(context, output) value = context.environments.first[@variable] ||= 0 context.environments.first[@variable] = value + 1 + output << value.to_s output end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index f0f6f107..2e5310b4 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -1,81 +1,46 @@ module Liquid - # - # TODO: docs - # class Render < Tag - Syntax = /(#{QuotedFragment}+)/o + Syntax = /(#{QuotedString})#{QuotedFragment}*/o attr_reader :template_name_expr, :attributes def initialize(tag_name, markup, options) super - if markup =~ Syntax - template_name = $1 + raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ Syntax - @template_name_expr = Expression.parse(template_name) + template_name = $1 - @attributes = {} - markup.scan(TagAttributes) do |key, value| - @attributes[key] = Expression.parse(value) - end + @template_name_expr = Expression.parse(template_name) - else - raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) + @attributes = {} + markup.scan(TagAttributes) do |key, value| + @attributes[key] = Expression.parse(value) end end - def parse(_tokens) - end - def render_to_output_buffer(context, output) + # Though we evaluate this here we will only ever parse it as a string literal. template_name = context.evaluate(@template_name_expr) raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name - partial = load_cached_partial(template_name, context) + partial = PartialCache.load( + template_name, + context: context, + parse_context: parse_context + ) - inner_context = Context.new + inner_context = context.new_isolated_subcontext inner_context.template_name = template_name inner_context.partial = true @attributes.each do |key, value| inner_context[key] = context.evaluate(value) end partial.render_to_output_buffer(inner_context, output) - - # TODO: Put into a new #isolated_stack method in Context? - inner_context.errors.each { |e| context.errors << e } output end - private - - alias_method :parse_context, :options - private :parse_context - - def load_cached_partial(template_name, context) - cached_partials = context.registers[:cached_partials] || {} - - if cached = cached_partials[template_name] - return cached - end - source = read_template_from_file_system(context) - begin - parse_context.partial = true - partial = Liquid::Template.parse(source, parse_context) - ensure - parse_context.partial = false - end - cached_partials[template_name] = partial - context.registers[:cached_partials] = cached_partials - partial - end - - def read_template_from_file_system(context) - file_system = context.registers[:file_system] || Liquid::Template.file_system - file_system.read_template_file(context.evaluate(@template_name_expr)) - end - class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ diff --git a/test/integration/tags/render_rag_test.rb b/test/integration/tags/render_rag_test.rb deleted file mode 100644 index 2352bc12..00000000 --- a/test/integration/tags/render_rag_test.rb +++ /dev/null @@ -1,255 +0,0 @@ -require 'test_helper' - -class TestFileSystem - def read_template_file(template_path) - case template_path - when "product" - "Product: {{ product.title }} " - - when "locale_variables" - "Locale: {{echo1}} {{echo2}}" - - when "variant" - "Variant: {{ variant.title }}" - - when "nested_template" - "{% include 'header' %} {% include 'body' %} {% include 'footer' %}" - - when "body" - "body {% include 'body_detail' %}" - - when "nested_product_template" - "Product: {{ nested_product_template.title }} {%include 'details'%} " - - when "recursively_nested_template" - "-{% include 'recursively_nested_template' %}" - - when "pick_a_source" - "from TestFileSystem" - - when 'assignments' - "{% assign foo = 'bar' %}" - - when 'break' - "{% break %}" - - else - template_path - end - end -end - -class OtherFileSystem - def read_template_file(template_path) - 'from OtherFileSystem' - end -end - -class CountingFileSystem - attr_reader :count - def read_template_file(template_path) - @count ||= 0 - @count += 1 - 'from CountingFileSystem' - end -end - -class CustomInclude < Liquid::Tag - Syntax = /(#{Liquid::QuotedFragment}+)(\s+(?:with|for)\s+(#{Liquid::QuotedFragment}+))?/o - - def initialize(tag_name, markup, tokens) - markup =~ Syntax - @template_name = $1 - super - end - - def parse(tokens) - end - - def render_to_output_buffer(context, output) - output << @template_name[1..-2] - output - end -end - -class IncludeTagTest < Minitest::Test - include Liquid - - def setup - Liquid::Template.file_system = TestFileSystem.new - end - - def test_include_tag_looks_for_file_system_in_registers_first - assert_equal 'from OtherFileSystem', - Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }) - end - - def test_include_tag_with - assert_template_result "Product: Draft 151cm ", - "{% include 'product' with products[0] %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] - end - - def test_include_tag_with_default_name - assert_template_result "Product: Draft 151cm ", - "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' } - end - - def test_include_tag_for - assert_template_result "Product: Draft 151cm Product: Element 155cm ", - "{% include 'product' for products %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] - end - - def test_include_tag_with_local_variables - assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}" - end - - def test_include_tag_with_multiple_local_variables - assert_template_result "Locale: test123 test321", - "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}" - end - - def test_include_tag_with_multiple_local_variables_from_context - assert_template_result "Locale: test123 test321", - "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}", - 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } - end - - def test_included_templates_assigns_variables - assert_template_result "bar", "{% include 'assignments' %}{{ foo }}" - end - - def test_nested_include_tag - assert_template_result "body body_detail", "{% include 'body' %}" - - assert_template_result "header body body_detail footer", "{% include 'nested_template' %}" - end - - def test_nested_include_with_variable - assert_template_result "Product: Draft 151cm details ", - "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' } - - assert_template_result "Product: Draft 151cm details Product: Element 155cm details ", - "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] - end - - def test_recursively_included_template_does_not_produce_endless_loop - infinite_file_system = Class.new do - def read_template_file(template_path) - "-{% include 'loop' %}" - end - end - - Liquid::Template.file_system = infinite_file_system.new - - assert_raises(Liquid::StackLevelError) do - Template.parse("{% include 'loop' %}").render! - end - end - - def test_dynamically_choosen_template - assert_template_result "Test123", "{% include template %}", "template" => 'Test123' - assert_template_result "Test321", "{% include template %}", "template" => 'Test321' - - assert_template_result "Product: Draft 151cm ", "{% include template for product %}", - "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } - end - - def test_include_tag_caches_second_read_of_same_partial - file_system = CountingFileSystem.new - assert_equal 'from CountingFileSystemfrom CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 1, file_system.count - end - - def test_include_tag_doesnt_cache_partials_across_renders - file_system = CountingFileSystem.new - assert_equal 'from CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 1, file_system.count - - assert_equal 'from CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 2, file_system.count - end - - def test_include_tag_within_if_statement - assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}" - end - - def test_custom_include_tag - original_tag = Liquid::Template.tags['include'] - Liquid::Template.tags['include'] = CustomInclude - begin - assert_equal "custom_foo", - Template.parse("{% include 'custom_foo' %}").render! - ensure - Liquid::Template.tags['include'] = original_tag - end - end - - def test_custom_include_tag_within_if_statement - original_tag = Liquid::Template.tags['include'] - Liquid::Template.tags['include'] = CustomInclude - begin - assert_equal "custom_foo_if_true", - Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render! - ensure - Liquid::Template.tags['include'] = original_tag - end - end - - def test_does_not_add_error_in_strict_mode_for_missing_variable - Liquid::Template.file_system = TestFileSystem.new - - a = Liquid::Template.parse(' {% include "nested_template" %}') - a.render! - assert_empty a.errors - end - - def test_passing_options_to_included_templates - assert_raises(Liquid::SyntaxError) do - Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}') - end - with_error_mode(:lax) do - assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}') - end - assert_raises(Liquid::SyntaxError) do - Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}') - end - with_error_mode(:lax) do - assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}') - end - end - - def test_render_raise_argument_error_when_template_is_undefined - assert_raises(Liquid::ArgumentError) do - template = Liquid::Template.parse('{% include undefined_variable %}') - template.render! - end - assert_raises(Liquid::ArgumentError) do - template = Liquid::Template.parse('{% include nil %}') - template.render! - end - end - - def test_including_via_variable_value - assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}" - - assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' } - - assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } - end - - def test_including_with_strict_variables - template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) - template.render(nil, strict_variables: true) - - assert_equal [], template.errors - end - - def test_break_through_include - assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}" - assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}" - end -end # IncludeTagTest - diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index 928dc385..a31d0182 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -1,75 +1,69 @@ require 'test_helper' -class StubFileSystem - def initialize(values) - @values = values - end - - def read_template_file(template_path) - @values.fetch(template_path) - end -end - class RenderTagTest < Minitest::Test include Liquid def test_render_with_no_arguments Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content') - assert_template_result 'rendered content', "{% render 'source' %}" + assert_template_result 'rendered content', '{% render "source" %}' end def test_render_tag_looks_for_file_system_in_registers_first file_system = StubFileSystem.new('pick_a_source' => 'from register file system') assert_equal 'from register file system', - Template.parse("{% render 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) + Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system }) end def test_render_passes_named_arguments_into_inner_scope Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}') - assert_template_result 'My Product', "{% render 'product', inner_product: outer_product %}", + assert_template_result 'My Product', '{% render "product", inner_product: outer_product %}', 'outer_product' => { 'title' => 'My Product' } end def test_render_accepts_literals_as_arguments Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}') - assert_template_result '123', "{% render 'snippet', price: 123 %}" + assert_template_result '123', '{% render "snippet", price: 123 %}' end def test_render_accepts_multiple_named_arguments Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}') - assert_template_result '1 2', "{% render 'snippet', one: 1, two: 2 %}" + assert_template_result '1 2', '{% render "snippet", one: 1, two: 2 %}' end - def test_render_does_inherit_parent_scope_variables + def test_render_does_not_inherit_parent_scope_variables Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}') - assert_template_result '', "{% render 'snippet' %}", 'outer_variable' => 'should not be visible' + assert_template_result '', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}' end def test_render_does_not_inherit_variable_with_same_name_as_snippet Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}') - assert_template_result '', "{% render 'snippet' %}", 'snippet' => 'should not be visible' + assert_template_result '', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}" end def test_render_sets_the_correct_template_name_for_errors Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}') - Liquid::Template.taint_mode = :error - template = Liquid::Template.parse("{% render 'snippet', unsafe: unsafe %}") - template.render('unsafe' => String.new('unsafe').tap(&:taint)) - refute_empty template.errors + with_taint_mode :error do + template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}') + context = Context.new('unsafe' => (+'unsafe').tap(&:taint)) + template.render(context) - assert_equal 'snippet', template.errors.first.template_name + assert_equal [Liquid::TaintedError], template.errors.map(&:class) + assert_equal 'snippet', template.errors.first.template_name + end end def test_render_sets_the_correct_template_name_for_warnings Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}') - Liquid::Template.taint_mode = :warn - template = Liquid::Template.parse("{% render 'snippet', unsafe: unsafe %}") - template.render('unsafe' => String.new('unsafe').tap(&:taint)) - refute_empty template.warnings + with_taint_mode :warn do + template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}') + context = Context.new('unsafe' => (+'unsafe').tap(&:taint)) + template.render(context) - assert_equal 'snippet', template.errors.first.template_name + assert_equal [Liquid::TaintedError], context.warnings.map(&:class) + assert_equal 'snippet', context.warnings.first.template_name + end end def test_render_does_not_mutate_parent_scope @@ -79,152 +73,77 @@ class RenderTagTest < Minitest::Test def test_nested_render_tag Liquid::Template.file_system = StubFileSystem.new( - 'one' => "one {{ render 'two' }}", + 'one' => "one {% render 'two' %}", 'two' => 'two' ) - assert_template_result 'one two', "{% include 'one' %}" + assert_template_result 'one two', "{% render 'one' %}" end - def test_nested_include_with_variable - skip 'To be implemented' - assert_template_result "Product: Draft 151cm details ", - "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' } + def test_recursively_rendered_template_does_not_produce_endless_loop + Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}') - assert_template_result "Product: Draft 151cm details Product: Element 155cm details ", - "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] - end - - def test_recursively_included_template_does_not_produce_endless_loop - skip 'To be implemented' - infinite_file_system = Class.new do - def read_template_file(template_path) - "-{% include 'loop' %}" - end - end - - Liquid::Template.file_system = infinite_file_system.new - - assert_raises(Liquid::StackLevelError) do - Template.parse("{% include 'loop' %}").render! + assert_raises Liquid::StackLevelError do + Template.parse('{% render "loop" %}').render! end end - def test_dynamically_choosen_template - skip 'To be implemented' - assert_template_result "Test123", "{% include template %}", "template" => 'Test123' - assert_template_result "Test321", "{% include template %}", "template" => 'Test321' + def test_includes_and_renders_count_towards_the_same_recursion_limit + Liquid::Template.file_system = StubFileSystem.new( + 'loop_render' => '{% render "loop_include" %}', + 'loop_include' => '{% include "loop_render" %}' + ) - assert_template_result "Product: Draft 151cm ", "{% include template for product %}", - "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } + assert_raises Liquid::StackLevelError do + Template.parse('{% render "loop_include" %}').render! + end + end + + def test_dynamically_choosen_templates_are_not_allowed + Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered') + + assert_raises Liquid::SyntaxError do + Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}") + end end def test_include_tag_caches_second_read_of_same_partial - skip 'To be implemented' - file_system = CountingFileSystem.new - assert_equal 'from CountingFileSystemfrom CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 1, file_system.count + file_system = StubFileSystem.new('snippet' => 'echo') + assert_equal 'echoecho', + Template.parse('{% render "snippet" %}{% render "snippet" %}') + .render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.file_read_count end - def test_include_tag_doesnt_cache_partials_across_renders - skip 'To be implemented' - file_system = CountingFileSystem.new - assert_equal 'from CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 1, file_system.count + def test_render_tag_doesnt_cache_partials_across_renders + file_system = StubFileSystem.new('snippet' => 'my message') - assert_equal 'from CountingFileSystem', - Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }) - assert_equal 2, file_system.count + assert_equal 'my message', + Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }) + assert_equal 1, file_system.file_read_count + + assert_equal 'my message', + Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }) + assert_equal 2, file_system.file_read_count end - def test_include_tag_within_if_statement - skip 'To be implemented' - assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}" + def test_render_tag_within_if_statement + Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message') + assert_template_result 'my message', '{% if true %}{% render "snippet" %}{% endif %}' end - def test_custom_include_tag - skip 'To be implemented' - original_tag = Liquid::Template.tags['include'] - Liquid::Template.tags['include'] = CustomInclude - begin - assert_equal "custom_foo", - Template.parse("{% include 'custom_foo' %}").render! - ensure - Liquid::Template.tags['include'] = original_tag - end + def test_break_through_render + Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}') + assert_template_result '1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}' + assert_template_result '112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}' end - def test_custom_include_tag_within_if_statement - skip 'To be implemented' - original_tag = Liquid::Template.tags['include'] - Liquid::Template.tags['include'] = CustomInclude - begin - assert_equal "custom_foo_if_true", - Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render! - ensure - Liquid::Template.tags['include'] = original_tag - end + def test_increment_is_isolated_between_renders + Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}') + assert_template_result '010', '{% increment %}{% increment %}{% render "incr" %}' end - def test_does_not_add_error_in_strict_mode_for_missing_variable - skip 'To be implemented' - Liquid::Template.file_system = TestFileSystem.new - - a = Liquid::Template.parse(' {% include "nested_template" %}') - a.render! - assert_empty a.errors + def test_decrement_is_isolated_between_renders + Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}') + assert_template_result '-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}' end - - def test_passing_options_to_included_templates - skip 'To be implemented' - assert_raises(Liquid::SyntaxError) do - Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}') - end - with_error_mode(:lax) do - assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}') - end - assert_raises(Liquid::SyntaxError) do - Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}') - end - with_error_mode(:lax) do - assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}') - end - end - - def test_render_raise_argument_error_when_template_is_undefined - skip 'To be implemented' - assert_raises(Liquid::ArgumentError) do - template = Liquid::Template.parse('{% include undefined_variable %}') - template.render! - end - assert_raises(Liquid::ArgumentError) do - template = Liquid::Template.parse('{% include nil %}') - template.render! - end - end - - def test_including_via_variable_value - skip 'To be implemented' - assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}" - - assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' } - - assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' } - end - - def test_including_with_strict_variables - skip 'To be implemented' - template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn) - template.render(nil, strict_variables: true) - - assert_equal [], template.errors - end - - def test_break_through_include - skip 'To be implemented' - assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}" - assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}" - end -end # IncludeTagTest - +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 210333d6..27a24342 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -121,3 +121,17 @@ class ErrorDrop < Liquid::Drop raise Exception, 'exception' end end + +class StubFileSystem + attr_reader :file_read_count + + def initialize(values) + @file_read_count = 0 + @values = values + end + + def read_template_file(template_path) + @file_read_count += 1 + @values.fetch(template_path) + end +end diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index 15457633..9252eb52 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -468,6 +468,16 @@ class ContextUnitTest < Minitest::Test assert_equal 'hi filtered', context.apply_global_filter('hi') end + def test_static_environments_are_read_with_lower_priority_than_environments + context = Context.build( + static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' }, + environments: { 'shadowed' => 'dynamic' } + ) + + assert_equal 'dynamic', context['shadowed'] + assert_equal 'static', context['unshadowed'] + end + def test_apply_global_filter_when_no_global_filter_exist context = Context.new assert_equal 'hi', context.apply_global_filter('hi') @@ -481,11 +491,11 @@ class ContextUnitTest < Minitest::Test assert_nil subcontext['my_variable'] end - def test_new_isolated_subcontext_inherits_environment - super_context = Context.new('my_environment_value' => 'my value') + def test_new_isolated_subcontext_inherits_static_environment + super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' }) subcontext = super_context.new_isolated_subcontext - assert_equal 'my value',subcontext['my_environment_value'] + assert_equal 'my value', subcontext['my_environment_value'] end def test_new_isolated_subcontext_inherits_resource_limits @@ -497,7 +507,7 @@ class ContextUnitTest < Minitest::Test def test_new_isolated_subcontext_inherits_exception_renderer super_context = Context.new - super_context.exception_renderer = -> (_e) { 'my exception message' } + super_context.exception_renderer = ->(_e) { 'my exception message' } subcontext = super_context.new_isolated_subcontext assert_equal 'my exception message', subcontext.handle_error(Liquid::Error.new) end diff --git a/test/unit/partial_cache_unit_test.rb b/test/unit/partial_cache_unit_test.rb new file mode 100644 index 00000000..29f11449 --- /dev/null +++ b/test/unit/partial_cache_unit_test.rb @@ -0,0 +1,91 @@ +require 'test_helper' + +class PartialCacheUnitTest < Minitest::Test + def test_uses_the_file_system_register_if_present + context = Liquid::Context.build( + registers: { + file_system: StubFileSystem.new('my_partial' => 'my partial body') + } + ) + + partial = Liquid::PartialCache.load( + 'my_partial', + context: context, + parse_context: Liquid::ParseContext.new + ) + + assert_equal 'my partial body', partial.render + end + + def test_reads_from_the_file_system_only_once_per_file + file_system = StubFileSystem.new('my_partial' => 'some partial body') + context = Liquid::Context.build( + registers: { file_system: file_system } + ) + + 2.times do + Liquid::PartialCache.load( + 'my_partial', + context: context, + parse_context: Liquid::ParseContext.new + ) + end + + assert_equal 1, file_system.file_read_count + end + + def test_cache_state_is_stored_per_context + parse_context = Liquid::ParseContext.new + shared_file_system = StubFileSystem.new( + 'my_partial' => 'my shared value' + ) + context_one = Liquid::Context.build( + registers: { + file_system: shared_file_system + } + ) + context_two = Liquid::Context.build( + registers: { + file_system: shared_file_system + } + ) + + 2.times do + Liquid::PartialCache.load( + 'my_partial', + context: context_one, + parse_context: parse_context + ) + end + + Liquid::PartialCache.load( + 'my_partial', + context: context_two, + parse_context: parse_context + ) + + assert_equal 2, shared_file_system.file_read_count + end + + def test_cache_is_not_broken_when_a_different_parse_context_is_used + file_system = StubFileSystem.new('my_partial' => 'some partial body') + context = Liquid::Context.build( + registers: { file_system: file_system } + ) + + Liquid::PartialCache.load( + 'my_partial', + context: context, + parse_context: Liquid::ParseContext.new(my_key: 'value one') + ) + Liquid::PartialCache.load( + 'my_partial', + context: context, + parse_context: Liquid::ParseContext.new(my_key: 'value two') + ) + + # Technically what we care about is that the file was parsed twice, + # but measuring file reads is an OK proxy for this. + assert_equal 1, file_system.file_read_count + end +end From 8750b4b006fb56e90154a12b2cb0442260be8caf Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Fri, 30 Aug 2019 09:01:47 +0530 Subject: [PATCH 54/74] Reduce allocations from `Liquid::Context.new` --- lib/liquid/context.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 1b15ca7e..90e6b789 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -21,8 +21,10 @@ module Liquid end def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {}) - @environments = [environments].flatten - @static_environments = [static_environments].flatten.map(&:freeze).freeze + @environments = [environments] + @environments.flatten! + + @static_environments = [static_environments].flat_map(&:freeze).freeze @scopes = [(outer_scope || {})] @registers = registers @static_registers = static_registers.freeze From dafbb4ae904b93de93bd80a555bcf4fcc2a31f4f Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Sat, 31 Aug 2019 20:03:54 +1000 Subject: [PATCH 55/74] Remove hasnling false scopes --- lib/liquid/context.rb | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index b341a31b..bf12dc17 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -109,26 +109,11 @@ module Liquid # end # # context['var] #=> nil - # - # false or {} can be used to control if a new scope is needed - # - # Example: - # new_scope = false - # context.stack(new_scope) do - # # no scope created - # end - # - # Example: - # new_scope = {} - # context.stack(new_scope) do - # # scope created - # end - # def stack(new_scope = {}) - push(new_scope) unless new_scope == false + push(new_scope) yield ensure - pop unless new_scope == false + pop end def clear_instance_assigns From 799da202dfab5cf1066ce45471d1dad46287b9f8 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Sat, 31 Aug 2019 21:58:33 +1000 Subject: [PATCH 56/74] Apply simple rubocop fixes --- .rubocop_todo.yml | 347 +------------------- Rakefile | 14 +- example/server/example_servlet.rb | 2 +- example/server/liquid_servlet.rb | 4 +- example/server/server.rb | 2 +- lib/liquid/block.rb | 12 +- lib/liquid/block_body.rb | 18 +- lib/liquid/condition.rb | 14 +- lib/liquid/context.rb | 4 +- lib/liquid/document.rb | 6 +- lib/liquid/drop.rb | 2 +- lib/liquid/errors.rb | 6 +- lib/liquid/expression.rb | 10 +- lib/liquid/file_system.rb | 2 +- lib/liquid/i18n.rb | 4 +- lib/liquid/interrupts.rb | 4 +- lib/liquid/lexer.rb | 15 +- lib/liquid/parse_context.rb | 2 +- lib/liquid/parse_tree_visitor.rb | 2 +- lib/liquid/parser.rb | 2 +- lib/liquid/standardfilters.rb | 10 +- lib/liquid/strainer.rb | 4 +- lib/liquid/tags/assign.rb | 6 +- lib/liquid/tags/capture.rb | 4 +- lib/liquid/tags/case.rb | 16 +- lib/liquid/tags/cycle.rb | 8 +- lib/liquid/tags/for.rb | 21 +- lib/liquid/tags/if.rb | 10 +- lib/liquid/tags/include.rb | 10 +- lib/liquid/tags/raw.rb | 8 +- lib/liquid/tags/render.rb | 6 +- lib/liquid/tags/table_row.rb | 8 +- lib/liquid/template.rb | 2 +- lib/liquid/tokenizer.rb | 4 +- lib/liquid/utils.rb | 2 +- lib/liquid/variable.rb | 8 +- lib/liquid/variable_lookup.rb | 4 +- liquid.gemspec | 6 +- performance/shopify/comment_form.rb | 8 +- performance/shopify/database.rb | 13 +- performance/shopify/json_filter.rb | 2 +- performance/shopify/liquid.rb | 16 +- performance/shopify/money_filter.rb | 4 +- performance/shopify/paginate.rb | 22 +- performance/shopify/shop_filter.rb | 16 +- performance/shopify/tag_filter.rb | 4 +- performance/shopify/weight_filter.rb | 2 +- performance/theme_runner.rb | 2 +- test/integration/blank_test.rb | 2 +- test/integration/drop_test.rb | 10 +- test/integration/error_handling_test.rb | 29 +- test/integration/filter_test.rb | 12 +- test/integration/hash_ordering_test.rb | 4 +- test/integration/output_test.rb | 4 +- test/integration/parse_tree_visitor_test.rb | 2 +- test/integration/render_profiling_test.rb | 4 +- test/integration/standard_filter_test.rb | 50 +-- test/integration/tags/echo_test.rb | 2 +- test/integration/tags/for_tag_test.rb | 12 +- test/integration/tags/if_else_tag_test.rb | 2 +- test/integration/tags/include_tag_test.rb | 14 +- test/integration/tags/increment_tag_test.rb | 4 +- test/integration/tags/render_tag_test.rb | 2 +- test/integration/tags/standard_tag_test.rb | 42 +-- test/integration/template_test.rb | 18 +- test/integration/trim_mode_test.rb | 60 ++-- test/integration/variable_test.rb | 2 +- test/test_helper.rb | 2 +- test/unit/condition_unit_test.rb | 14 +- test/unit/context_unit_test.rb | 20 +- test/unit/partial_cache_unit_test.rb | 6 +- test/unit/strainer_unit_test.rb | 7 +- test/unit/tags/if_tag_unit_test.rb | 2 +- 73 files changed, 341 insertions(+), 683 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 22330ae6..0bd77b10 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,92 +1,17 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-08-29 12:16:25 +1000 using RuboCop version 0.74.0. +# on 2019-08-31 21:54:20 +1000 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 13 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/AlignHash: - Exclude: - - 'lib/liquid/condition.rb' - - 'lib/liquid/expression.rb' - - 'performance/shopify/comment_form.rb' - - 'performance/shopify/database.rb' - - 'performance/shopify/paginate.rb' - - 'test/unit/context_unit_test.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'performance/shopify/paginate.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: squiggly, active_support, powerpack, unindent -Layout/IndentHeredoc: - Exclude: - - 'test/integration/tags/for_tag_test.rb' - - 'test/integration/trim_mode_test.rb' - -# Offense count: 6 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineMethodCallBraceLayout: - Exclude: - - 'test/integration/error_handling_test.rb' - - 'test/unit/strainer_unit_test.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceAroundOperators: - Exclude: - - 'lib/liquid/condition.rb' - - 'performance/shopify/paginate.rb' - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceBeforeBlockBraces: - Exclude: - - 'example/server/server.rb' - - 'lib/liquid/variable.rb' - - 'test/integration/drop_test.rb' - - 'test/integration/standard_filter_test.rb' - - 'test/integration/tags/if_else_tag_test.rb' - -# Offense count: 19 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideArrayLiteralBrackets: - Exclude: - - 'test/integration/drop_test.rb' - - 'test/integration/standard_filter_test.rb' - - 'test/integration/tags/for_tag_test.rb' - - 'test/integration/tags/include_tag_test.rb' - - 'test/integration/tags/standard_tag_test.rb' - - 'test/unit/context_unit_test.rb' - # Offense count: 2 Lint/AmbiguousOperator: Exclude: - 'test/unit/condition_unit_test.rb' -# Offense count: 16 +# Offense count: 21 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Exclude: @@ -95,51 +20,12 @@ Lint/AssignmentInCondition: - 'lib/liquid/standardfilters.rb' - 'lib/liquid/tags/for.rb' - 'lib/liquid/tags/if.rb' - - 'lib/liquid/tags/include.rb' - 'lib/liquid/tags/raw.rb' - 'lib/liquid/variable.rb' - 'performance/profile.rb' - 'test/test_helper.rb' - 'test/unit/tokenizer_unit_test.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: runtime_error, standard_error -Lint/InheritException: - Exclude: - - 'lib/liquid/interrupts.rb' - -# Offense count: 10 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'lib/liquid/condition.rb' - - 'lib/liquid/context.rb' - - 'lib/liquid/document.rb' - - 'lib/liquid/parse_context.rb' - - 'lib/liquid/template.rb' - - 'performance/shopify/json_filter.rb' - - 'test/integration/filter_test.rb' - - 'test/integration/render_profiling_test.rb' - - 'test/integration/variable_test.rb' - - 'test/unit/condition_unit_test.rb' - -# Offense count: 12 -# Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. -Lint/UnusedMethodArgument: - Exclude: - - 'example/server/liquid_servlet.rb' - - 'test/integration/blank_test.rb' - - 'test/integration/error_handling_test.rb' - - 'test/integration/filter_test.rb' - - 'test/integration/output_test.rb' - - 'test/integration/standard_filter_test.rb' - - 'test/integration/tags/include_tag_test.rb' - - 'test/unit/strainer_unit_test.rb' - # Offense count: 2 Lint/UselessAssignment: Exclude: @@ -151,64 +37,16 @@ Lint/Void: Exclude: - 'lib/liquid/parse_context.rb' -# Offense count: 95 +# Offense count: 98 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 294 -# Offense count: 44 +# Offense count: 45 Naming/ConstantName: - Exclude: - - 'lib/liquid.rb' - - 'lib/liquid/block_body.rb' - - 'lib/liquid/tags/assign.rb' - - 'lib/liquid/tags/capture.rb' - - 'lib/liquid/tags/case.rb' - - 'lib/liquid/tags/cycle.rb' - - 'lib/liquid/tags/for.rb' - - 'lib/liquid/tags/if.rb' - - 'lib/liquid/tags/include.rb' - - 'lib/liquid/tags/raw.rb' - - 'lib/liquid/tags/table_row.rb' - - 'lib/liquid/variable.rb' - - 'performance/shopify/comment_form.rb' - - 'performance/shopify/paginate.rb' - - 'test/integration/tags/include_tag_test.rb' - -# Offense count: 2 -# Configuration parameters: . -# SupportedStyles: snake_case, camelCase -Naming/MethodName: - EnforcedStyle: snake_case - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, conditionals -Style/AndOr: - Exclude: - - 'lib/liquid/i18n.rb' - - 'lib/liquid/tags/table_row.rb' - - 'lib/liquid/tokenizer.rb' - -# Offense count: 40 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces, context_dependent -Style/BracesAroundHashParameters: - Exclude: - - 'test/integration/error_handling_test.rb' - - 'test/integration/filter_test.rb' - - 'test/integration/render_profiling_test.rb' - - 'test/integration/standard_filter_test.rb' - - 'test/integration/tags/echo_test.rb' - - 'test/integration/tags/increment_tag_test.rb' - - 'test/integration/tags/standard_tag_test.rb' - - 'test/integration/template_test.rb' - - 'test/unit/condition_unit_test.rb' - - 'test/unit/context_unit_test.rb' + Enabled: false # Offense count: 5 Style/ClassVars: @@ -217,195 +55,24 @@ Style/ClassVars: - 'lib/liquid/strainer.rb' - 'lib/liquid/template.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'lib/liquid/errors.rb' - - 'performance/shopify/shop_filter.rb' - # Offense count: 1 # Configuration parameters: AllowCoercion. Style/DateTime: Exclude: - 'test/unit/context_unit_test.rb' -# Offense count: 2 -# Cop supports --auto-correct. -Style/EachWithObject: - Exclude: - - 'performance/shopify/database.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/EmptyCaseCondition: - Exclude: - - 'lib/liquid/lexer.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: each, for -Style/For: - Exclude: - - 'performance/shopify/shop_filter.rb' - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: format, sprintf, percent -Style/FormatString: - Exclude: - - 'example/server/example_servlet.rb' - - 'performance/shopify/money_filter.rb' - - 'performance/shopify/weight_filter.rb' - - 'test/integration/filter_test.rb' - - 'test/integration/hash_ordering_test.rb' - -# Offense count: 115 +# Offense count: 119 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, never Style/FrozenStringLiteralComment: Enabled: false -# Offense count: 30 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreMacros, IgnoredMethods, IncludedMacros, AllowParenthesesInMultilineCall, AllowParenthesesInChaining, AllowParenthesesInCamelCaseMethod, EnforcedStyle. -# SupportedStyles: require_parentheses, omit_parentheses -Style/MethodCallWithArgsParentheses: - Exclude: - - 'Gemfile' - - 'Rakefile' - - 'lib/liquid/block_body.rb' - - 'lib/liquid/parser.rb' - - 'lib/liquid/tags/for.rb' - - 'liquid.gemspec' - - 'performance/shopify/database.rb' - - 'performance/shopify/liquid.rb' - - 'test/test_helper.rb' - - 'test/unit/condition_unit_test.rb' - - 'test/unit/tags/if_tag_unit_test.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, MinBodyLength. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Exclude: - - 'lib/liquid/tags/for.rb' - -# Offense count: 52 -# Cop supports --auto-correct. -Style/PerlBackrefs: - Enabled: false - -# Offense count: 33 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: compact, exploded -Style/RaiseArgs: - Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/RedundantSelf: - Exclude: - - 'lib/liquid/strainer.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'lib/liquid/file_system.rb' - - 'lib/liquid/standardfilters.rb' - - 'performance/shopify/shop_filter.rb' - - 'test/unit/condition_unit_test.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. -# Whitelist: present?, blank?, presence, try, try! -Style/SafeNavigation: - Exclude: - - 'lib/liquid/drop.rb' - - 'lib/liquid/strainer.rb' - - 'lib/liquid/tokenizer.rb' - -# Offense count: 10 +# Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: - - 'performance/shopify/database.rb' - 'test/integration/error_handling_test.rb' - 'test/integration/template_test.rb' - 'test/unit/context_unit_test.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: use_perl_names, use_english_names -Style/SpecialGlobalVars: - Exclude: - - 'performance/shopify/liquid.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiteralsInInterpolation: - Exclude: - - 'performance/shopify/tag_filter.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: - Exclude: - - 'lib/liquid/context.rb' - - 'lib/liquid/utils.rb' - -# Offense count: 21 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArrayLiteral: - Exclude: - - 'lib/liquid/parse_tree_visitor.rb' - - 'lib/liquid/tags/include.rb' - - 'test/integration/parse_tree_visitor_test.rb' - - 'test/integration/standard_filter_test.rb' - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'lib/liquid/condition.rb' - - 'lib/liquid/lexer.rb' - - 'lib/liquid/standardfilters.rb' - - 'performance/shopify/comment_form.rb' - - 'performance/shopify/database.rb' - - 'performance/shopify/paginate.rb' - - 'performance/theme_runner.rb' - - 'test/integration/output_test.rb' - - 'test/unit/context_unit_test.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/UnneededPercentQ: - Exclude: - - 'test/integration/error_handling_test.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/WhileUntilModifier: - Exclude: - - 'lib/liquid/tags/case.rb' diff --git a/Rakefile b/Rakefile index f7186eb7..f2a0d07b 100755 --- a/Rakefile +++ b/Rakefile @@ -1,18 +1,18 @@ require 'rake' require 'rake/testtask' -$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) +$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) require "liquid/version" -task default: [:test, :rubocop] +task(default: [:test, :rubocop]) -desc 'run test suite with default parser' +desc('run test suite with default parser') Rake::TestTask.new(:base_test) do |t| t.libs << '.' << 'lib' << 'test' t.test_files = FileList['test/{integration,unit}/**/*_test.rb'] t.verbose = false end -desc 'run test suite with warn error mode' +desc('run test suite with warn error mode') task :warn_test do ENV['LIQUID_PARSER_MODE'] = 'warn' Rake::Task['base_test'].invoke @@ -25,7 +25,7 @@ task :rubocop do end end -desc 'runs test suite with both strict and lax parsers' +desc('runs test suite with both strict and lax parsers') task :test do ENV['LIQUID_PARSER_MODE'] = 'lax' Rake::Task['base_test'].invoke @@ -47,7 +47,7 @@ task :test do end end -task gem: :build +task(gem: :build) task :build do system "gem build liquid.gemspec" end @@ -94,7 +94,7 @@ namespace :memory_profile do end end -desc "Run example" +desc("Run example") task :example do ruby "-w -d -Ilib example/server/server.rb" end diff --git a/example/server/example_servlet.rb b/example/server/example_servlet.rb index dbc7a4b7..9f8c58a8 100644 --- a/example/server/example_servlet.rb +++ b/example/server/example_servlet.rb @@ -1,6 +1,6 @@ module ProductsFilter def price(integer) - sprintf("$%.2d USD", integer / 100.0) + format("$%.2d USD", integer / 100.0) end def prettyprint(text) diff --git a/example/server/liquid_servlet.rb b/example/server/liquid_servlet.rb index b2bf515f..895f274a 100644 --- a/example/server/liquid_servlet.rb +++ b/example/server/liquid_servlet.rb @@ -9,12 +9,12 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet private - def handle(type, req, res) + def handle(_type, req, res) @request = req @response = res @request.path_info =~ /(\w+)\z/ - @action = $1 || 'index' + @action = Regexp.last_match(1) || 'index' @assigns = send(@action) if respond_to?(@action) @response['Content-Type'] = "text/html" diff --git a/example/server/server.rb b/example/server/server.rb index 703b361c..f2f89a40 100644 --- a/example/server/server.rb +++ b/example/server/server.rb @@ -8,5 +8,5 @@ require_relative 'example_servlet' # Setup webrick server = WEBrick::HTTPServer.new(Port: ARGV[1] || 3000) server.mount('/', Servlet) -trap("INT"){ server.shutdown } +trap("INT") { server.shutdown } server.start diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 549a3a36..0036d7bf 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -28,15 +28,15 @@ module Liquid def unknown_tag(tag, _params, _tokens) if tag == 'else'.freeze - raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze, - block_name: block_name)) + raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else".freeze, + block_name: block_name) elsif tag.start_with?('end'.freeze) - raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze, + raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter".freeze, tag: tag, block_name: block_name, - block_delimiter: block_delimiter)) + block_delimiter: block_delimiter) else - raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag)) + raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag) end end @@ -61,7 +61,7 @@ module Liquid return false if end_tag_name == block_delimiter unless end_tag_name - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name) end # this tag is not registered with the system diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 27b4eef0..a52eb620 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -32,8 +32,8 @@ module Liquid # caller raise a syntax error return yield token, token end - tag_name = $1 - markup = $2 + tag_name = Regexp.last_match(1) + markup = Regexp.last_match(2) unless tag = registered_tags[tag_name] # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed @@ -58,13 +58,13 @@ module Liquid unless token =~ FullToken raise_missing_tag_terminator(token, parse_context) end - tag_name = $2 - markup = $4 + tag_name = Regexp.last_match(2) + markup = Regexp.last_match(4) if parse_context.line_number # newlines inside the tag should increase the line number, # particularly important for multiline {% liquid %} tags - parse_context.line_number += $1.count("\n".freeze) + $3.count("\n".freeze) + parse_context.line_number += Regexp.last_match(1).count("\n".freeze) + Regexp.last_match(3).count("\n".freeze) end if tag_name == 'liquid'.freeze @@ -101,7 +101,7 @@ module Liquid def whitespace_handler(token, parse_context) if token[2] == WhitespaceControl previous_token = @nodelist.last - if previous_token.is_a? String + if previous_token.is_a?(String) previous_token.rstrip! end end @@ -163,7 +163,7 @@ module Liquid def raise_if_resource_limits_reached(context, length) context.resource_limits.render_length += length return unless context.resource_limits.reached? - raise MemoryError.new("Memory limits exceeded".freeze) + raise MemoryError, "Memory limits exceeded".freeze end def create_variable(token, parse_context) @@ -175,11 +175,11 @@ module Liquid end def raise_missing_tag_terminator(token, parse_context) - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect)) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect) end def raise_missing_variable_terminator(token, parse_context) - raise SyntaxError.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect)) + raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect) end def registered_tags diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index 3b51682e..c6e29ae0 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -11,18 +11,18 @@ module Liquid '=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) }, '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, - '<'.freeze => :<, - '>'.freeze => :>, + '<'.freeze => :<, + '>'.freeze => :>, '>='.freeze => :>=, '<='.freeze => :<=, - 'contains'.freeze => lambda do |cond, left, right| + 'contains'.freeze => lambda do |_cond, left, right| if left && right && left.respond_to?(:include?) right = right.to_s if left.is_a?(String) left.include?(right) else false end - end + end, } def self.operators @@ -36,7 +36,7 @@ module Liquid @left = left @operator = operator @right = right - @child_relation = nil + @child_relation = nil @child_condition = nil end @@ -116,7 +116,7 @@ module Liquid left = context.evaluate(left) right = context.evaluate(right) - operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}")) + operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}") if operation.respond_to?(:call) operation.call(self, left, right) @@ -124,7 +124,7 @@ module Liquid begin left.send(operation, right) rescue ::ArgumentError => e - raise Liquid::ArgumentError.new(e.message) + raise Liquid::ArgumentError, e.message end end end diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 90e6b789..b5d56385 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -39,7 +39,7 @@ module Liquid self.exception_renderer = Template.default_exception_renderer if rethrow_errors - self.exception_renderer = ->(e) { raise } + self.exception_renderer = ->(_e) { raise } end @interrupts = [] @@ -215,7 +215,7 @@ module Liquid value = obj[key] if value.is_a?(Proc) && obj.respond_to?(:[]=) - obj[key] = (value.arity == 0) ? value.call : value.call(self) + obj[key] = value.arity == 0 ? value.call : value.call(self) else value end diff --git a/lib/liquid/document.rb b/lib/liquid/document.rb index d035dd46..afd4e998 100644 --- a/lib/liquid/document.rb +++ b/lib/liquid/document.rb @@ -7,7 +7,7 @@ module Liquid end def parse(tokens, parse_context) - super do |end_tag_name, end_tag_params| + super do |end_tag_name, _end_tag_params| unknown_tag(end_tag_name, parse_context) if end_tag_name end rescue SyntaxError => e @@ -18,9 +18,9 @@ module Liquid def unknown_tag(tag, parse_context) case tag when 'else'.freeze, 'end'.freeze - raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag)) + raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag) else - raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag)) + raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag) end end end diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb index 6b5aa993..14d6eac9 100644 --- a/lib/liquid/drop.rb +++ b/lib/liquid/drop.rb @@ -25,7 +25,7 @@ module Liquid # Catch all for the method def liquid_method_missing(method) - return nil unless @context && @context.strict_variables + return nil unless @context&.strict_variables raise Liquid::UndefinedDropMethod, "undefined method #{method}" end diff --git a/lib/liquid/errors.rb b/lib/liquid/errors.rb index defa5ea9..4239746f 100644 --- a/lib/liquid/errors.rb +++ b/lib/liquid/errors.rb @@ -21,10 +21,10 @@ module Liquid def message_prefix str = "" - if is_a?(SyntaxError) - str << "Liquid syntax error" + str << if is_a?(SyntaxError) + "Liquid syntax error" else - str << "Liquid error" + "Liquid error" end if line_number diff --git a/lib/liquid/expression.rb b/lib/liquid/expression.rb index 98be6db6..55681992 100644 --- a/lib/liquid/expression.rb +++ b/lib/liquid/expression.rb @@ -15,7 +15,7 @@ module Liquid LITERALS = { nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil, - 'true'.freeze => true, + 'true'.freeze => true, 'false'.freeze => false, 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze, 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze @@ -33,13 +33,13 @@ module Liquid else case markup when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING - $1 + Regexp.last_match(1) when INTEGERS_REGEX - $1.to_i + Regexp.last_match(1).to_i when RANGES_REGEX - RangeLookup.parse($1, $2) + RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2)) when FLOATS_REGEX - $1.to_f + Regexp.last_match(1).to_f else VariableLookup.parse(markup) end diff --git a/lib/liquid/file_system.rb b/lib/liquid/file_system.rb index 13f1f460..a2aa9b75 100644 --- a/lib/liquid/file_system.rb +++ b/lib/liquid/file_system.rb @@ -57,7 +57,7 @@ module Liquid end def full_path(template_path) - raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/ + raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ %r{\A[^./][a-zA-Z0-9_/]+\z} full_path = if template_path.include?('/'.freeze) File.join(root, File.dirname(template_path), @pattern % File.basename(template_path)) diff --git a/lib/liquid/i18n.rb b/lib/liquid/i18n.rb index 2671507e..b2bb51ba 100644 --- a/lib/liquid/i18n.rb +++ b/lib/liquid/i18n.rb @@ -26,13 +26,13 @@ module Liquid def interpolate(name, vars) name.gsub(/%\{(\w+)\}/) do # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym] - (vars[$1.to_sym]).to_s + (vars[Regexp.last_match(1).to_sym]).to_s end end def deep_fetch_translation(name) name.split('.'.freeze).reduce(locale) do |level, cur| - level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}" + level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}") end end end diff --git a/lib/liquid/interrupts.rb b/lib/liquid/interrupts.rb index 41359d78..f3005e4c 100644 --- a/lib/liquid/interrupts.rb +++ b/lib/liquid/interrupts.rb @@ -9,8 +9,8 @@ module Liquid end # Interrupt that is thrown whenever a {% break %} is called. - class BreakInterrupt < Interrupt; end + class BreakInterrupt < RuntimeError; end # Interrupt that is thrown whenever a {% continue %} is called. - class ContinueInterrupt < Interrupt; end + class ContinueInterrupt < RuntimeError; end end diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb index f290744d..367f99ee 100644 --- a/lib/liquid/lexer.rb +++ b/lib/liquid/lexer.rb @@ -11,7 +11,7 @@ module Liquid '('.freeze => :open_round, ')'.freeze => :close_round, '?'.freeze => :question, - '-'.freeze => :dash + '-'.freeze => :dash, }.freeze IDENTIFIER = /[a-zA-Z_][\w-]*\??/ SINGLE_STRING_LITERAL = /'[^\']*'/ @@ -31,13 +31,12 @@ module Liquid until @ss.eos? @ss.skip(WHITESPACE_OR_NOTHING) break if @ss.eos? - tok = case - when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] - when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] - when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t] - when t = @ss.scan(NUMBER_LITERAL) then [:number, t] - when t = @ss.scan(IDENTIFIER) then [:id, t] - when t = @ss.scan(DOTDOT) then [:dotdot, t] + tok = if t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] + elsif t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] + elsif t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t] + elsif t = @ss.scan(NUMBER_LITERAL) then [:number, t] + elsif t = @ss.scan(IDENTIFIER) then [:id, t] + elsif t = @ss.scan(DOTDOT) then [:dotdot, t] else c = @ss.getch if s = SPECIALS[c] diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb index abcdaeba..58437f4d 100644 --- a/lib/liquid/parse_context.rb +++ b/lib/liquid/parse_context.rb @@ -28,7 +28,7 @@ module Liquid if dont_pass == true { locale: locale } elsif dont_pass.is_a?(Array) - @template_options.reject { |k, v| dont_pass.include?(k) } + @template_options.reject { |k, _v| dont_pass.include?(k) } else @template_options end diff --git a/lib/liquid/parse_tree_visitor.rb b/lib/liquid/parse_tree_visitor.rb index 74f5563a..d50943f3 100644 --- a/lib/liquid/parse_tree_visitor.rb +++ b/lib/liquid/parse_tree_visitor.rb @@ -28,7 +28,7 @@ module Liquid item, new_context = @callbacks[node.class].call(node, context) [ item, - ParseTreeVisitor.for(node, @callbacks).visit(new_context || context) + ParseTreeVisitor.for(node, @callbacks).visit(new_context || context), ] end end diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index 16df6e93..c36de86f 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -51,7 +51,7 @@ module Liquid token = @tokens[@p] if token[0] == :id variable_signature - elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0] + elsif SINGLE_TOKEN_EXPRESSION_TYPES.include?(token[0]) consume elsif token.first == :open_round consume diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index afcf479e..b13d089c 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -8,13 +8,13 @@ module Liquid '>'.freeze => '>'.freeze, '<'.freeze => '<'.freeze, '"'.freeze => '"'.freeze, - "'".freeze => '''.freeze + "'".freeze => '''.freeze, }.freeze HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ STRIP_HTML_BLOCKS = Regexp.union( - //m, + %r{}m, //m, - //m + %r{}m ) STRIP_HTML_TAGS = /<.*?>/m @@ -276,7 +276,7 @@ module Liquid def concat(input, array) unless array.respond_to?(:to_ary) - raise ArgumentError.new("concat filter requires an array argument") + raise ArgumentError, "concat filter requires an array argument" end InputIterator.new(input).concat(array) end @@ -430,7 +430,7 @@ module Liquid private def raise_property_error(property) - raise Liquid::ArgumentError.new("cannot select the property '#{property}'") + raise Liquid::ArgumentError, "cannot select the property '#{property}'" end def apply_operation(input, operand, operation) diff --git a/lib/liquid/strainer.rb b/lib/liquid/strainer.rb index 76d56d23..d885ae41 100644 --- a/lib/liquid/strainer.rb +++ b/lib/liquid/strainer.rb @@ -27,7 +27,7 @@ module Liquid def self.add_filter(filter) raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module) - unless self.include?(filter) + unless include?(filter) invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) } if invokable_non_public_methods.any? raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}" @@ -54,7 +54,7 @@ module Liquid def invoke(method, *args) if self.class.invokable?(method) send(method, *args) - elsif @context && @context.strict_filters + elsif @context&.strict_filters raise Liquid::UndefinedFilter, "undefined filter #{method}" else args.first diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 50a95536..6ff65d57 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -19,10 +19,10 @@ module Liquid def initialize(tag_name, markup, options) super if markup =~ Syntax - @to = $1 - @from = Variable.new($2, options) + @to = Regexp.last_match(1) + @from = Variable.new(Regexp.last_match(2), options) else - raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key)) + raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key) end end diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index d717b769..a97e42a1 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -16,9 +16,9 @@ module Liquid def initialize(tag_name, markup, options) super if markup =~ Syntax - @to = $1 + @to = Regexp.last_match(1) else - raise SyntaxError.new(options[:locale].t("errors.syntax.capture")) + raise SyntaxError, options[:locale].t("errors.syntax.capture") end end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 92b2ed0d..7d430193 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -10,17 +10,15 @@ module Liquid @blocks = [] if markup =~ Syntax - @left = Expression.parse($1) + @left = Expression.parse(Regexp.last_match(1)) else - raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.case".freeze) end end def parse(tokens) body = BlockBody.new - while parse_body(body, tokens) - body = @blocks.last.attachment - end + body = @blocks.last.attachment while parse_body(body, tokens) end def nodelist @@ -62,12 +60,12 @@ module Liquid while markup unless markup =~ WhenSyntax - raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when".freeze) end - markup = $2 + markup = Regexp.last_match(2) - block = Condition.new(@left, '=='.freeze, Expression.parse($1)) + block = Condition.new(@left, '=='.freeze, Expression.parse(Regexp.last_match(1))) block.attach(body) @blocks << block end @@ -75,7 +73,7 @@ module Liquid def record_else_condition(markup) unless markup.strip.empty? - raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else".freeze) end block = ElseCondition.new diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index e42244d4..33999028 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -21,13 +21,13 @@ module Liquid super case markup when NamedSyntax - @variables = variables_from_string($2) - @name = Expression.parse($1) + @variables = variables_from_string(Regexp.last_match(2)) + @name = Expression.parse(Regexp.last_match(1)) when SimpleSyntax @variables = variables_from_string(markup) @name = @variables.to_s else - raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.cycle".freeze) end end @@ -61,7 +61,7 @@ module Liquid def variables_from_string(markup) markup.split(',').collect do |var| var =~ /\s*(#{QuotedFragment})\s*/o - $1 ? Expression.parse($1) : nil + Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil end.compact end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 71c2f910..f20953f8 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -86,23 +86,23 @@ module Liquid def lax_parse(markup) if markup =~ Syntax - @variable_name = $1 - collection_name = $2 - @reversed = !!$3 + @variable_name = Regexp.last_match(1) + collection_name = Regexp.last_match(2) + @reversed = !!Regexp.last_match(3) @name = "#{@variable_name}-#{collection_name}" @collection_name = Expression.parse(collection_name) markup.scan(TagAttributes) do |key, value| set_attribute(key, value) end else - raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.for".freeze) end end def strict_parse(markup) p = Parser.new(markup) @variable_name = p.consume(:id) - raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze) + raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in".freeze) unless p.id?('in'.freeze) collection_name = p.expression @name = "#{@variable_name}-#{collection_name}" @collection_name = Expression.parse(collection_name) @@ -110,7 +110,7 @@ module Liquid while p.look(:id) && p.look(:colon, 1) unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze) - raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute".freeze) end p.consume set_attribute(attribute, p.expression) @@ -170,11 +170,10 @@ module Liquid loop_vars.send(:increment!) # Handle any interrupts if they exist. - if context.interrupt? - interrupt = context.pop_interrupt - break if interrupt.is_a? BreakInterrupt - next if interrupt.is_a? ContinueInterrupt - end + next unless context.interrupt? + interrupt = context.pop_interrupt + break if interrupt.is_a?(BreakInterrupt) + next if interrupt.is_a?(ContinueInterrupt) end ensure for_stack.pop diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 25534a98..5c9b8bb1 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -66,17 +66,17 @@ module Liquid def lax_parse(markup) expressions = markup.scan(ExpressionsAndOperators) - raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax + raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless expressions.pop =~ Syntax - condition = Condition.new(Expression.parse($1), $2, Expression.parse($3)) + condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3))) until expressions.empty? operator = expressions.pop.to_s.strip - raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax + raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless expressions.pop.to_s =~ Syntax - new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3)) - raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator) + new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3))) + raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless BOOLEAN_OPERATORS.include?(operator) new_condition.send(operator, condition) condition = new_condition end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index fd86ee4b..d2c6cd26 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -23,8 +23,8 @@ module Liquid if markup =~ Syntax - template_name = $1 - variable_name = $3 + template_name = Regexp.last_match(1) + variable_name = Regexp.last_match(3) @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil @template_name_expr = Expression.parse(template_name) @@ -35,7 +35,7 @@ module Liquid end else - raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.include".freeze) end end @@ -44,7 +44,7 @@ module Liquid def render_to_output_buffer(context, output) template_name = context.evaluate(@template_name_expr) - raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name + raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name partial = PartialCache.load( template_name, @@ -95,7 +95,7 @@ module Liquid def children [ @node.template_name_expr, - @node.variable_name_expr + @node.variable_name_expr, ] + @node.attributes.values end end diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index 4fa75d99..69910021 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -13,13 +13,13 @@ module Liquid @body = '' while token = tokens.shift if token =~ FullTokenPossiblyInvalid - @body << $1 if $1 != "".freeze - return if block_delimiter == $2 + @body << Regexp.last_match(1) if Regexp.last_match(1) != "".freeze + return if block_delimiter == Regexp.last_match(2) end @body << token unless token.empty? end - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name) end def render_to_output_buffer(_context, output) @@ -39,7 +39,7 @@ module Liquid def ensure_valid_markup(tag_name, markup, parse_context) unless markup =~ Syntax - raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name)) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name) end end end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index 2e5310b4..d7288983 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -7,9 +7,9 @@ module Liquid def initialize(tag_name, markup, options) super - raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ Syntax + raise SyntaxError, options[:locale].t("errors.syntax.render".freeze) unless markup =~ Syntax - template_name = $1 + template_name = Regexp.last_match(1) @template_name_expr = Expression.parse(template_name) @@ -22,7 +22,7 @@ module Liquid def render_to_output_buffer(context, output) # Though we evaluate this here we will only ever parse it as a string literal. template_name = context.evaluate(@template_name_expr) - raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name + raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name partial = PartialCache.load( template_name, diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 95321020..93935276 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -7,19 +7,19 @@ module Liquid def initialize(tag_name, markup, options) super if markup =~ Syntax - @variable_name = $1 - @collection_name = Expression.parse($2) + @variable_name = Regexp.last_match(1) + @collection_name = Expression.parse(Regexp.last_match(2)) @attributes = {} markup.scan(TagAttributes) do |key, value| @attributes[key] = Expression.parse(value) end else - raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze)) + raise SyntaxError, options[:locale].t("errors.syntax.table_row".freeze) end end def render_to_output_buffer(context, output) - collection = context.evaluate(@collection_name) or return ''.freeze + (collection = context.evaluate(@collection_name)) || (return ''.freeze) from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 91e30fbc..62250b28 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -172,7 +172,7 @@ module Liquid c = args.shift if @rethrow_errors - c.exception_renderer = ->(e) { raise } + c.exception_renderer = ->(_e) { raise } end c diff --git a/lib/liquid/tokenizer.rb b/lib/liquid/tokenizer.rb index d3fd6766..95114200 100644 --- a/lib/liquid/tokenizer.rb +++ b/lib/liquid/tokenizer.rb @@ -10,7 +10,7 @@ module Liquid end def shift - token = @tokens.shift or return + (token = @tokens.shift) || return if @line_number @line_number += @for_liquid_tag ? 1 : token.count("\n") @@ -29,7 +29,7 @@ module Liquid tokens = @source.split(TemplateParser) # removes the rogue empty element at the beginning of the array - tokens.shift if tokens[0] && tokens[0].empty? + tokens.shift if tokens[0]&.empty? tokens end diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb index 516ac0cf..ada4f396 100644 --- a/lib/liquid/utils.rb +++ b/lib/liquid/utils.rb @@ -50,7 +50,7 @@ module Liquid when Numeric obj when String - (obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i + obj.strip =~ /\A-?\d+\.\d+\z/ ? BigDecimal(obj) : obj.to_i else if obj.respond_to?(:to_number) obj.to_number diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index cbf99864..6efcf705 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -43,11 +43,11 @@ module Liquid @filters = [] return unless markup =~ MarkupWithQuotedFragment - name_markup = $1 - filter_markup = $2 + name_markup = Regexp.last_match(1) + filter_markup = Regexp.last_match(2) @name = Expression.parse(name_markup) if filter_markup =~ FilterMarkupRegex - filters = $1.scan(FilterParser) + filters = Regexp.last_match(1).scan(FilterParser) filters.each do |f| next unless f =~ /\w+/ filtername = Regexp.last_match(0) @@ -121,7 +121,7 @@ module Liquid end def evaluate_filter_expressions(context, filter_args, filter_kwargs) - parsed_args = filter_args.map{ |expr| context.evaluate(expr) } + parsed_args = filter_args.map { |expr| context.evaluate(expr) } if filter_kwargs parsed_kwargs = {} filter_kwargs.each do |key, expr| diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index 62f4877c..ab06bb62 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -14,7 +14,7 @@ module Liquid name = lookups.shift if name =~ SQUARE_BRACKETED - name = Expression.parse($1) + name = Expression.parse(Regexp.last_match(1)) end @name = name @@ -24,7 +24,7 @@ module Liquid @lookups.each_index do |i| lookup = lookups[i] if lookup =~ SQUARE_BRACKETED - lookups[i] = Expression.parse($1) + lookups[i] = Expression.parse(Regexp.last_match(1)) elsif COMMAND_METHODS.include?(lookup) @command_flags |= 1 << i end diff --git a/liquid.gemspec b/liquid.gemspec index 89df19a0..27b24aae 100644 --- a/liquid.gemspec +++ b/liquid.gemspec @@ -1,7 +1,7 @@ # encoding: utf-8 lib = File.expand_path('../lib/', __FILE__) -$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "liquid/version" @@ -26,6 +26,6 @@ Gem::Specification.new do |s| s.require_path = "lib" - s.add_development_dependency 'rake', '~> 11.3' - s.add_development_dependency 'minitest' + s.add_development_dependency('rake', '~> 11.3') + s.add_development_dependency('minitest') end diff --git a/performance/shopify/comment_form.rb b/performance/shopify/comment_form.rb index 7b5bd53b..65af1b51 100644 --- a/performance/shopify/comment_form.rb +++ b/performance/shopify/comment_form.rb @@ -5,10 +5,10 @@ class CommentForm < Liquid::Block super if markup =~ Syntax - @variable_name = $1 + @variable_name = Regexp.last_match(1) @attributes = {} else - raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]") + raise SyntaxError, "Syntax Error in 'comment_form' - Valid syntax: comment_form [article]" end end @@ -20,8 +20,8 @@ class CommentForm < Liquid::Block 'posted_successfully?' => context.registers[:posted_successfully], 'errors' => context['comment.errors'], 'author' => context['comment.author'], - 'email' => context['comment.email'], - 'body' => context['comment.body'] + 'email' => context['comment.email'], + 'body' => context['comment.body'], } output << wrap_in_form(article, render_all(@nodelist, context, output)) diff --git a/performance/shopify/database.rb b/performance/shopify/database.rb index 2b5bca49..c9f18c30 100644 --- a/performance/shopify/database.rb +++ b/performance/shopify/database.rb @@ -16,9 +16,10 @@ module Database end # key the tables by handles, as this is how liquid expects it. - db = db.inject({}) do |assigns, (key, values)| - assigns[key] = values.inject({}) { |h, v| h[v['handle']] = v; h; } - assigns + db = db.each_with_object({}) do |(key, values), assigns| + assigns[key] = values.each_with_object({}) do |v, h| + h[v['handle']] = v + end end # Some standard direct accessors so that the specialized templates @@ -30,8 +31,8 @@ module Database db['cart'] = { 'total_price' => db['line_items'].values.inject(0) { |sum, item| sum += item['line_price'] * item['quantity'] }, - 'item_count' => db['line_items'].values.inject(0) { |sum, item| sum += item['quantity'] }, - 'items' => db['line_items'].values + 'item_count' => db['line_items'].values.inject(0) { |sum, item| sum += item['quantity'] }, + 'items' => db['line_items'].values, } db @@ -40,6 +41,6 @@ module Database end if __FILE__ == $PROGRAM_NAME - p Database.tables['collections']['frontpage'].keys + p(Database.tables['collections']['frontpage'].keys) # p Database.tables['blog']['articles'] end diff --git a/performance/shopify/json_filter.rb b/performance/shopify/json_filter.rb index 8fbb5b66..32583167 100644 --- a/performance/shopify/json_filter.rb +++ b/performance/shopify/json_filter.rb @@ -2,6 +2,6 @@ require 'json' module JsonFilter def json(object) - JSON.dump(object.reject { |k, v| k == "collections" }) + JSON.dump(object.reject { |k, _v| k == "collections" }) end end diff --git a/performance/shopify/liquid.rb b/performance/shopify/liquid.rb index 7716debc..f9d52007 100644 --- a/performance/shopify/liquid.rb +++ b/performance/shopify/liquid.rb @@ -1,4 +1,4 @@ -$:.unshift __dir__ + '/../../lib' +$LOAD_PATH.unshift(__dir__ + '/../../lib') require_relative '../../lib/liquid' require_relative 'comment_form' @@ -9,11 +9,11 @@ require_relative 'shop_filter' require_relative 'tag_filter' require_relative 'weight_filter' -Liquid::Template.register_tag 'paginate', Paginate -Liquid::Template.register_tag 'form', CommentForm +Liquid::Template.register_tag('paginate', Paginate) +Liquid::Template.register_tag('form', CommentForm) -Liquid::Template.register_filter JsonFilter -Liquid::Template.register_filter MoneyFilter -Liquid::Template.register_filter WeightFilter -Liquid::Template.register_filter ShopFilter -Liquid::Template.register_filter TagFilter +Liquid::Template.register_filter(JsonFilter) +Liquid::Template.register_filter(MoneyFilter) +Liquid::Template.register_filter(WeightFilter) +Liquid::Template.register_filter(ShopFilter) +Liquid::Template.register_filter(TagFilter) diff --git a/performance/shopify/money_filter.rb b/performance/shopify/money_filter.rb index 8dad7890..4cc7280d 100644 --- a/performance/shopify/money_filter.rb +++ b/performance/shopify/money_filter.rb @@ -1,12 +1,12 @@ module MoneyFilter def money_with_currency(money) return '' if money.nil? - sprintf("$ %.2f USD", money / 100.0) + format("$ %.2f USD", money / 100.0) end def money(money) return '' if money.nil? - sprintf("$ %.2f", money / 100.0) + format("$ %.2f", money / 100.0) end private diff --git a/performance/shopify/paginate.rb b/performance/shopify/paginate.rb index 0abd11ff..29e7c9ee 100644 --- a/performance/shopify/paginate.rb +++ b/performance/shopify/paginate.rb @@ -1,13 +1,13 @@ class Paginate < Liquid::Block - Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/ + Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/ def initialize(tag_name, markup, options) super if markup =~ Syntax - @collection_name = $1 - @page_size = if $2 - $3.to_i + @collection_name = Regexp.last_match(1) + @page_size = if Regexp.last_match(2) + Regexp.last_match(3).to_i else 20 end @@ -17,7 +17,7 @@ class Paginate < Liquid::Block @attributes[key] = value end else - raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number") + raise SyntaxError, "Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number" end end @@ -25,19 +25,19 @@ class Paginate < Liquid::Block @context = context context.stack do - current_page = context['current_page'].to_i + current_page = context['current_page'].to_i pagination = { - 'page_size' => @page_size, - 'current_page' => 5, - 'current_offset' => @page_size * 5 + 'page_size' => @page_size, + 'current_page' => 5, + 'current_offset' => @page_size * 5, } context['paginate'] = pagination - collection_size = context[@collection_name].size + collection_size = context[@collection_name].size - raise ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection_size.nil? + raise ArgumentError, "Cannot paginate array '#{@collection_name}'. Not found." if collection_size.nil? page_count = (collection_size.to_f / @page_size.to_f).to_f.ceil + 1 diff --git a/performance/shopify/shop_filter.rb b/performance/shopify/shop_filter.rb index 89c9083b..b2a0a9d0 100644 --- a/performance/shopify/shop_filter.rb +++ b/performance/shopify/shop_filter.rb @@ -52,7 +52,7 @@ module ShopFilter end def product_img_url(url, style = 'small') - unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/ + unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})} raise ArgumentError, 'filter "size" can only be called on product images' end @@ -60,7 +60,7 @@ module ShopFilter when 'original' return '/files/shops/random_number/' + url when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon' - "/files/shops/random_number/products/#{$1}_#{style}.#{$2}" + "/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}" else raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon ' end @@ -70,16 +70,14 @@ module ShopFilter html = [] html << %(#{link_to(paginate['previous']['title'], paginate['previous']['url'])}) if paginate['previous'] - for part in paginate['parts'] - - if part['is_link'] - html << %(#{link_to(part['title'], part['url'])}) + paginate['parts'].each do |part| + html << if part['is_link'] + %(#{link_to(part['title'], part['url'])}) elsif part['title'].to_i == paginate['current_page'].to_i - html << %(#{part['title']}) + %(#{part['title']}) else - html << %(#{part['title']}) + %(#{part['title']}) end - end html << %(#{link_to(paginate['next']['title'], paginate['next']['url'])}) if paginate['next'] diff --git a/performance/shopify/tag_filter.rb b/performance/shopify/tag_filter.rb index ab5aef62..34b426f2 100644 --- a/performance/shopify/tag_filter.rb +++ b/performance/shopify/tag_filter.rb @@ -13,11 +13,11 @@ module TagFilter def link_to_add_tag(label, tag) tags = (@context['current_tags'] + [tag]).uniq - "#{label}" + "#{label}" end def link_to_remove_tag(label, tag) tags = (@context['current_tags'] - [tag]).uniq - "#{label}" + "#{label}" end end diff --git a/performance/shopify/weight_filter.rb b/performance/shopify/weight_filter.rb index a0a15fca..b05bcce1 100644 --- a/performance/shopify/weight_filter.rb +++ b/performance/shopify/weight_filter.rb @@ -1,6 +1,6 @@ module WeightFilter def weight(grams) - sprintf("%.2f", grams / 1000) + format("%.2f", grams / 1000) end def weight_with_unit(grams) diff --git a/performance/theme_runner.rb b/performance/theme_runner.rb index 9f6a1fc4..9268558c 100644 --- a/performance/theme_runner.rb +++ b/performance/theme_runner.rb @@ -31,7 +31,7 @@ class ThemeRunner { liquid: File.read(test), layout: (File.file?(theme_path) ? File.read(theme_path) : nil), - template_name: test + template_name: test, } end.compact diff --git a/test/integration/blank_test.rb b/test/integration/blank_test.rb index 2b46ad71..654ee987 100644 --- a/test/integration/blank_test.rb +++ b/test/integration/blank_test.rb @@ -1,7 +1,7 @@ require 'test_helper' class FoobarTag < Liquid::Tag - def render_to_output_buffer(context, output) + def render_to_output_buffer(_context, output) output << ' ' output end diff --git a/test/integration/drop_test.rb b/test/integration/drop_test.rb index 2de4a5a1..9294b2ff 100644 --- a/test/integration/drop_test.rb +++ b/test/integration/drop_test.rb @@ -201,9 +201,9 @@ class DropsTest < Minitest::Test end def test_scope_though_proc - assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }) - assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1]) - assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1]) + assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }) + assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]) + assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]) end def test_scope_with_assigns @@ -241,7 +241,7 @@ class DropsTest < Minitest::Test end def test_some_enumerable_methods_still_get_invoked - [ :count, :max ].each do |method| + [:count, :max].each do |method| assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new) @@ -250,7 +250,7 @@ class DropsTest < Minitest::Test assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new) - [ :min, :first ].each do |method| + [:min, :first].each do |method| assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new) diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index b2d186cf..875f4267 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -83,15 +83,14 @@ class ErrorHandlingTest < Minitest::Test def test_with_line_numbers_adds_numbers_to_parser_errors err = assert_raises(SyntaxError) do - Liquid::Template.parse(%q( + Liquid::Template.parse(' foobar {% "cat" | foobar %} bla - ), - line_numbers: true - ) + ', + line_numbers: true) end assert_match(/Liquid syntax error \(line 4\)/, err.message) @@ -99,15 +98,14 @@ class ErrorHandlingTest < Minitest::Test def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim err = assert_raises(SyntaxError) do - Liquid::Template.parse(%q( + Liquid::Template.parse(' foobar {%- "cat" | foobar -%} bla - ), - line_numbers: true - ) + ', + line_numbers: true) end assert_match(/Liquid syntax error \(line 4\)/, err.message) @@ -122,8 +120,7 @@ class ErrorHandlingTest < Minitest::Test bla ', error_mode: :warn, - line_numbers: true - ) + line_numbers: true) assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'], template.warnings.map(&:message) @@ -139,8 +136,7 @@ class ErrorHandlingTest < Minitest::Test bla ', error_mode: :strict, - line_numbers: true - ) + line_numbers: true) end assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message @@ -157,8 +153,7 @@ class ErrorHandlingTest < Minitest::Test bla ', - line_numbers: true - ) + line_numbers: true) end assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message @@ -205,7 +200,7 @@ class ErrorHandlingTest < Minitest::Test def test_default_exception_renderer_with_internal_error template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) - output = template.render({ 'errors' => ErrorDrop.new }) + output = template.render('errors' => ErrorDrop.new) assert_equal 'This is a runtime error: Liquid error (line 1): internal', output assert_equal [Liquid::InternalError], template.errors.map(&:class) @@ -217,7 +212,7 @@ class ErrorHandlingTest < Minitest::Test Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' } template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}') - output = template.render({ 'errors' => ErrorDrop.new }) + output = template.render('errors' => ErrorDrop.new) assert_equal 'This is a runtime error: ', output assert_equal [Liquid::ArgumentError], template.errors.map(&:class) @@ -239,7 +234,7 @@ class ErrorHandlingTest < Minitest::Test end class TestFileSystem - def read_template_file(template_path) + def read_template_file(_template_path) "{{ errors.argument_error }}" end end diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb index d3c880e8..0af29efb 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -2,23 +2,23 @@ require 'test_helper' module MoneyFilter def money(input) - sprintf(' %d$ ', input) + format(' %d$ ', input) end def money_with_underscore(input) - sprintf(' %d$ ', input) + format(' %d$ ', input) end end module CanadianMoneyFilter def money(input) - sprintf(' %d$ CAD ', input) + format(' %d$ CAD ', input) end end module SubstituteFilter def substitute(input, params = {}) - input.gsub(/%\{(\w+)\}/) { |match| params[$1] } + input.gsub(/%\{(\w+)\}/) { |_match| params[Regexp.last_match(1)] } end end @@ -26,7 +26,7 @@ class FiltersTest < Minitest::Test include Liquid module OverrideObjectMethodFilter - def tap(input) + def tap(_input) "tap overridden" end end @@ -149,7 +149,7 @@ class FiltersTest < Minitest::Test assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter]) # tap still treated as a non-existent filter - assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }) + assert_equal "1000", Template.parse("{{var | tap}}").render!('var' => 1000) end end diff --git a/test/integration/hash_ordering_test.rb b/test/integration/hash_ordering_test.rb index dfc1c29f..8592395a 100644 --- a/test/integration/hash_ordering_test.rb +++ b/test/integration/hash_ordering_test.rb @@ -3,13 +3,13 @@ require 'test_helper' class HashOrderingTest < Minitest::Test module MoneyFilter def money(input) - sprintf(' %d$ ', input) + format(' %d$ ', input) end end module CanadianMoneyFilter def money(input) - sprintf(' %d$ CAD ', input) + format(' %d$ CAD ', input) end end diff --git a/test/integration/output_test.rb b/test/integration/output_test.rb index b4cf9d70..d94b0f89 100644 --- a/test/integration/output_test.rb +++ b/test/integration/output_test.rb @@ -1,7 +1,7 @@ require 'test_helper' module FunnyFilter - def make_funny(input) + def make_funny(_input) 'LOL' end @@ -32,7 +32,7 @@ class OutputTest < Minitest::Test def setup @assigns = { 'best_cars' => 'bmw', - 'car' => { 'bmw' => 'good', 'gm' => 'bad' } + 'car' => { 'bmw' => 'good', 'gm' => 'bad' }, } end diff --git a/test/integration/parse_tree_visitor_test.rb b/test/integration/parse_tree_visitor_test.rb index 6ad6a2d0..d1af123d 100644 --- a/test/integration/parse_tree_visitor_test.rb +++ b/test/integration/parse_tree_visitor_test.rb @@ -227,7 +227,7 @@ class ParseTreeVisitorTest < Minitest::Test [[nil, [ [nil, [[nil, [["other", []]]]]], ["test", []], - ["xs", []] + ["xs", []], ]]], traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit ) diff --git a/test/integration/render_profiling_test.rb b/test/integration/render_profiling_test.rb index d0111e77..283b8bdd 100644 --- a/test/integration/render_profiling_test.rb +++ b/test/integration/render_profiling_test.rb @@ -128,7 +128,7 @@ class RenderProfilingTest < Minitest::Test t.render! timing_count = 0 - t.profiler.each do |timing| + t.profiler.each do |_timing| timing_count += 1 end @@ -145,7 +145,7 @@ class RenderProfilingTest < Minitest::Test def test_profiling_marks_children_of_for_blocks t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true) - t.render!({ "collection" => ["one", "two"] }) + t.render!("collection" => ["one", "two"]) assert_equal 1, t.profiler.length # Will profile each invocation of the for block diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 60909510..7863cfeb 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -17,7 +17,7 @@ class TestThing "woot: #{@foo}" end - def [](whatever) + def [](_whatever) to_s end @@ -37,7 +37,7 @@ class TestEnumerable < Liquid::Drop include Enumerable def each(&block) - [ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block) + [{ "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 }].each(&block) end end @@ -208,14 +208,14 @@ class StandardFiltersTest < Minitest::Test { "handle" => "beta" }, { "price" => 1, "handle" => "gamma" }, { "handle" => "delta" }, - { "price" => 2, "handle" => "epsilon" } + { "price" => 2, "handle" => "epsilon" }, ] expectation = [ { "price" => 1, "handle" => "gamma" }, { "price" => 2, "handle" => "epsilon" }, { "price" => 4, "handle" => "alpha" }, { "handle" => "delta" }, - { "handle" => "beta" } + { "handle" => "beta" }, ] assert_equal expectation, @filters.sort(input, "price") end @@ -236,14 +236,14 @@ class StandardFiltersTest < Minitest::Test { "handle" => "beta" }, { "price" => "1", "handle" => "gamma" }, { "handle" => "delta" }, - { "price" => 2, "handle" => "epsilon" } + { "price" => 2, "handle" => "epsilon" }, ] expectation = [ { "price" => "1", "handle" => "gamma" }, { "price" => 2, "handle" => "epsilon" }, { "price" => "4", "handle" => "alpha" }, { "handle" => "delta" }, - { "handle" => "beta" } + { "handle" => "beta" }, ] assert_equal expectation, @filters.sort_natural(input, "price") end @@ -256,7 +256,7 @@ class StandardFiltersTest < Minitest::Test { "fake" => "t" }, { "key" => "a" }, { "key" => "b" }, - { "key" => "c" } + { "key" => "c" }, ] expectation = [ { "key" => "a" }, @@ -265,7 +265,7 @@ class StandardFiltersTest < Minitest::Test { "key" => "X" }, { "key" => "Y" }, { "key" => "Z" }, - { "fake" => "t" } + { "fake" => "t" }, ] assert_equal expectation, @filters.sort_natural(input, "key") assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"]) @@ -279,7 +279,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @@ -295,7 +295,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @@ -304,7 +304,7 @@ class StandardFiltersTest < Minitest::Test end def test_legacy_sort_hash - assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 }) + assert_equal [{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2) end def test_numerical_vs_lexicographical_sort @@ -330,7 +330,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @@ -346,7 +346,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @@ -380,7 +380,7 @@ class StandardFiltersTest < Minitest::Test def test_map_on_hashes assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}', - "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] } + "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } end def test_legacy_map_on_hashes_with_dynamic_key @@ -397,7 +397,7 @@ class StandardFiltersTest < Minitest::Test def test_map_over_proc drop = TestDrop.new - p = proc{ drop } + p = proc { drop } templ = '{{ procs | map: "test" }}' assert_template_result "testfoo", templ, "procs" => [p] end @@ -405,10 +405,10 @@ class StandardFiltersTest < Minitest::Test def test_map_over_drops_returning_procs drops = [ { - "proc" => ->{ "foo" }, + "proc" => -> { "foo" }, }, { - "proc" => ->{ "bar" }, + "proc" => -> { "bar" }, }, ] templ = '{{ drops | map: "proc" }}' @@ -423,7 +423,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @@ -435,7 +435,7 @@ class StandardFiltersTest < Minitest::Test foo = [ [1], [2], - [3] + [3], ] assert_raises Liquid::ArgumentError do @filters.map(foo, nil) @@ -697,12 +697,12 @@ class StandardFiltersTest < Minitest::Test { "handle" => "alpha", "ok" => true }, { "handle" => "beta", "ok" => false }, { "handle" => "gamma", "ok" => false }, - { "handle" => "delta", "ok" => true } + { "handle" => "delta", "ok" => true }, ] expectation = [ { "handle" => "alpha", "ok" => true }, - { "handle" => "delta", "ok" => true } + { "handle" => "delta", "ok" => true }, ] assert_equal expectation, @filters.where(input, "ok", true) @@ -714,12 +714,12 @@ class StandardFiltersTest < Minitest::Test { "handle" => "alpha", "ok" => true }, { "handle" => "beta" }, { "handle" => "gamma" }, - { "handle" => "delta", "ok" => true } + { "handle" => "delta", "ok" => true }, ] expectation = [ { "handle" => "alpha", "ok" => true }, - { "handle" => "delta", "ok" => true } + { "handle" => "delta", "ok" => true }, ] assert_equal expectation, @filters.where(input, "ok", true) @@ -740,7 +740,7 @@ class StandardFiltersTest < Minitest::Test input = [ { "message" => "Bonjour!", "language" => "French" }, { "message" => "Hello!", "language" => "English" }, - { "message" => "Hallo!", "language" => "German" } + { "message" => "Hallo!", "language" => "German" }, ] assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French") @@ -758,7 +758,7 @@ class StandardFiltersTest < Minitest::Test { "foo" => false }, { "foo" => true }, { "foo" => "for sure" }, - { "bar" => true } + { "bar" => true }, ] assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo") diff --git a/test/integration/tags/echo_test.rb b/test/integration/tags/echo_test.rb index ed5b821d..eab329db 100644 --- a/test/integration/tags/echo_test.rb +++ b/test/integration/tags/echo_test.rb @@ -4,7 +4,7 @@ class EchoTest < Minitest::Test include Liquid def test_echo_outputs_its_input - assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' }) + assert_template_result('BAR', <<~LIQUID, 'variable-name' => 'bar') {%- echo variable-name | upcase -%} LIQUID end diff --git a/test/integration/tags/for_tag_test.rb b/test/integration/tags/for_tag_test.rb index 9980e258..47e3e5f7 100644 --- a/test/integration/tags/for_tag_test.rb +++ b/test/integration/tags/for_tag_test.rb @@ -23,16 +23,16 @@ class ForTagTest < Minitest::Test yo HERE - template = < [1, 2, 3]) end def test_for_reversed - assigns = { 'array' => [ 1, 2, 3] } + assigns = { 'array' => [1, 2, 3] } assert_template_result('321', '{%for item in array reversed %}{{item}}{%endfor%}', assigns) end diff --git a/test/integration/tags/if_else_tag_test.rb b/test/integration/tags/if_else_tag_test.rb index 45a5d3ac..276b15b7 100644 --- a/test/integration/tags/if_else_tag_test.rb +++ b/test/integration/tags/if_else_tag_test.rb @@ -132,7 +132,7 @@ class IfElseTagTest < Minitest::Test end def test_syntax_error_no_variable - assert_raises(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}') } + assert_raises(SyntaxError) { assert_template_result('', '{% if jerry == 1 %}') } end def test_syntax_error_no_expression diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 14bb8c3a..dea932a0 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -40,14 +40,14 @@ class TestFileSystem end class OtherFileSystem - def read_template_file(template_path) + def read_template_file(_template_path) 'from OtherFileSystem' end end class CountingFileSystem attr_reader :count - def read_template_file(template_path) + def read_template_file(_template_path) @count ||= 0 @count += 1 'from CountingFileSystem' @@ -59,14 +59,14 @@ class CustomInclude < Liquid::Tag def initialize(tag_name, markup, tokens) markup =~ Syntax - @template_name = $1 + @template_name = Regexp.last_match(1) super end def parse(tokens) end - def render_to_output_buffer(context, output) + def render_to_output_buffer(_context, output) output << @template_name[1..-2] output end @@ -86,7 +86,7 @@ class IncludeTagTest < Minitest::Test def test_include_tag_with assert_template_result "Product: Draft 151cm ", - "{% include 'product' with products[0] %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] + "{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] end def test_include_tag_with_default_name @@ -96,7 +96,7 @@ class IncludeTagTest < Minitest::Test def test_include_tag_for assert_template_result "Product: Draft 151cm Product: Element 155cm ", - "{% include 'product' for products %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ] + "{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] end def test_include_tag_with_local_variables @@ -134,7 +134,7 @@ class IncludeTagTest < Minitest::Test def test_recursively_included_template_does_not_produce_endless_loop infinite_file_system = Class.new do - def read_template_file(template_path) + def read_template_file(_template_path) "-{% include 'loop' %}" end end diff --git a/test/integration/tags/increment_tag_test.rb b/test/integration/tags/increment_tag_test.rb index 97c51ac5..a793b6d2 100644 --- a/test/integration/tags/increment_tag_test.rb +++ b/test/integration/tags/increment_tag_test.rb @@ -13,11 +13,11 @@ class IncrementTagTest < Minitest::Test end def test_dec - assert_template_result('9', '{%decrement port %}', { 'port' => 10 }) + assert_template_result('9', '{%decrement port %}', 'port' => 10) assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {}) assert_template_result('1 5 2 2 5', '{%increment port %} {%increment starboard%} ' \ '{%increment port %} {%decrement port%} ' \ - '{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 }) + '{%decrement starboard %}', 'port' => 1, 'starboard' => 5) end end diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index a31d0182..ea144771 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -110,7 +110,7 @@ class RenderTagTest < Minitest::Test file_system = StubFileSystem.new('snippet' => 'echo') assert_equal 'echoecho', Template.parse('{% render "snippet" %}{% render "snippet" %}') - .render!({}, registers: { file_system: file_system }) + .render!({}, registers: { file_system: file_system }) assert_equal 1, file_system.file_read_count end diff --git a/test/integration/tags/standard_tag_test.rb b/test/integration/tags/standard_tag_test.rb index 4b4703a6..cee4caca 100644 --- a/test/integration/tags/standard_tag_test.rb +++ b/test/integration/tags/standard_tag_test.rb @@ -69,7 +69,7 @@ class StandardTagTest < Minitest::Test assert_raises(SyntaxError) do assert_template_result('content foo content foo ', '{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', - { 'var' => 'content' }) + 'var' => 'content') end end @@ -183,32 +183,32 @@ class StandardTagTest < Minitest::Test def test_case_when_or code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 }) - assert_template_result(' its 4 ', code, { 'condition' => 4 }) - assert_template_result('', code, { 'condition' => 5 }) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3) + assert_template_result(' its 4 ', code, 'condition' => 4) + assert_template_result('', code, 'condition' => 5) code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil }) - assert_template_result('', code, { 'condition' => 'something else' }) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string') + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil) + assert_template_result('', code, 'condition' => 'something else') end def test_case_when_comma code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 }) - assert_template_result(' its 4 ', code, { 'condition' => 4 }) - assert_template_result('', code, { 'condition' => 5 }) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3) + assert_template_result(' its 4 ', code, 'condition' => 4) + assert_template_result('', code, 'condition' => 5) code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}' - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' }) - assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil }) - assert_template_result('', code, { 'condition' => 'something else' }) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1) + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string') + assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil) + assert_template_result('', code, 'condition' => 'something else') end def test_assign @@ -283,10 +283,10 @@ class StandardTagTest < Minitest::Test end def test_ifchanged - assigns = { 'array' => [ 1, 1, 2, 2, 3, 3] } + assigns = { 'array' => [1, 1, 2, 2, 3, 3] } assert_template_result('123', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns) - assigns = { 'array' => [ 1, 1, 1, 1] } + assigns = { 'array' => [1, 1, 1, 1] } assert_template_result('1', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns) end diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index 0dc0ae5f..e2b54c79 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -224,7 +224,7 @@ class TemplateTest < Minitest::Test end def test_render_bang_force_rethrow_errors_on_passed_context - context = Context.new({ 'drop' => ErroneousDrop.new }) + context = Context.new('drop' => ErroneousDrop.new) t = Template.new.parse('{{ drop.bad_method }}') e = assert_raises RuntimeError do @@ -267,7 +267,7 @@ class TemplateTest < Minitest::Test def test_undefined_variables t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}") - result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true }) + result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true) assert_equal '33 32 ', result assert_equal 3, t.errors.count @@ -292,14 +292,14 @@ class TemplateTest < Minitest::Test t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}") assert_raises UndefinedVariable do - t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true }) + t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true) end end def test_undefined_drop_methods d = DropWithUndefinedMethod.new t = Template.new.parse('{{ foo }} {{ woot }}') - result = t.render(d, { strict_variables: true }) + result = t.render(d, strict_variables: true) assert_equal 'foo ', result assert_equal 1, t.errors.count @@ -311,7 +311,7 @@ class TemplateTest < Minitest::Test t = Template.new.parse('{{ foo }} {{ woot }}') assert_raises UndefinedDropMethod do - t.render!(d, { strict_variables: true }) + t.render!(d, strict_variables: true) end end @@ -322,7 +322,7 @@ class TemplateTest < Minitest::Test "-#{v}-" end end - result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true }) + result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true) assert_equal '123 ', result assert_equal 1, t.errors.count @@ -334,17 +334,17 @@ class TemplateTest < Minitest::Test t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}") assert_raises UndefinedFilter do - t.render!({ 'x' => 'foo' }, { strict_filters: true }) + t.render!({ 'x' => 'foo' }, strict_filters: true) end end def test_using_range_literal_works_as_expected t = Template.parse("{% assign foo = (x..y) %}{{ foo }}") - result = t.render({ 'x' => 1, 'y' => 5 }) + result = t.render('x' => 1, 'y' => 5) assert_equal '1..5', result t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}") - result = t.render({ 'x' => 1, 'y' => 5 }) + result = t.render('x' => 1, 'y' => 5) assert_equal '12345', result end end diff --git a/test/integration/trim_mode_test.rb b/test/integration/trim_mode_test.rb index 52248cff..4e35deee 100644 --- a/test/integration/trim_mode_test.rb +++ b/test/integration/trim_mode_test.rb @@ -76,14 +76,14 @@ class TrimModeTest < Minitest::Test

END_TEMPLATE - expected = <<-END_EXPECTED -
-

-#{whitespace} - yes -#{whitespace} -

-
+ expected = <<~END_EXPECTED +
+

+ #{whitespace} + yes + #{whitespace} +

+
END_EXPECTED assert_template_result(expected, text) @@ -96,12 +96,12 @@ class TrimModeTest < Minitest::Test

END_TEMPLATE - expected = <<-END_EXPECTED -
-

-#{whitespace} -

-
+ expected = <<~END_EXPECTED +
+

+ #{whitespace} +

+
END_EXPECTED assert_template_result(expected, text) end @@ -337,12 +337,12 @@ class TrimModeTest < Minitest::Test

END_TEMPLATE - expected = <<-END_EXPECTED -
-

-#{whitespace} -

-
+ expected = <<~END_EXPECTED +
+

+ #{whitespace} +

+
END_EXPECTED assert_template_result(expected, text) end @@ -513,16 +513,16 @@ class TrimModeTest < Minitest::Test {% endraw %} END_TEMPLATE - expected = <<-END_EXPECTED -
-#{whitespace} - {%- if true -%} -

- {{- 'John' -}} -

- {%- endif -%} -#{whitespace} -
+ expected = <<~END_EXPECTED +
+ #{whitespace} + {%- if true -%} +

+ {{- 'John' -}} +

+ {%- endif -%} + #{whitespace} +
END_EXPECTED assert_template_result(expected, text) end diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb index abd6e70c..244ba95e 100644 --- a/test/integration/variable_test.rb +++ b/test/integration/variable_test.rb @@ -76,7 +76,7 @@ class VariableTest < Minitest::Test def test_hash_with_default_proc template = Template.parse(%(Hello {{ test }})) - assigns = Hash.new { |h, k| raise "Unknown variable '#{k}'" } + assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" } assigns['test'] = 'Tobi' assert_equal 'Hello Tobi', template.render!(assigns) assigns.delete('test') diff --git a/test/test_helper.rb b/test/test_helper.rb index 27a24342..defc67ec 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -41,7 +41,7 @@ module Minitest end def assert_template_result_matches(expected, template, assigns = {}, message = nil) - return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp + return assert_template_result(expected, template, assigns, message) unless expected.is_a?(Regexp) assert_match expected, Template.parse(template, line_numbers: true).render!(assigns), message end diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index b3b90e88..165a7cf9 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -68,7 +68,7 @@ class ConditionUnitTest < Minitest::Test assert_nil Condition.new({}, '>', 2).evaluate assert_nil Condition.new(2, '>', {}).evaluate assert_equal false, Condition.new({}, '==', 2).evaluate - assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate + assert_equal true, Condition.new({ 'a' => 1 }, '==', 'a' => 1).evaluate assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate end @@ -107,11 +107,11 @@ class ConditionUnitTest < Minitest::Test assert_equal false, condition.evaluate - condition.or Condition.new(2, '==', 1) + condition.or(Condition.new(2, '==', 1)) assert_equal false, condition.evaluate - condition.or Condition.new(1, '==', 1) + condition.or(Condition.new(1, '==', 1)) assert_equal true, condition.evaluate end @@ -121,22 +121,22 @@ class ConditionUnitTest < Minitest::Test assert_equal true, condition.evaluate - condition.and Condition.new(2, '==', 2) + condition.and(Condition.new(2, '==', 2)) assert_equal true, condition.evaluate - condition.and Condition.new(2, '==', 1) + condition.and(Condition.new(2, '==', 1)) assert_equal false, condition.evaluate end def test_should_allow_custom_proc_operator - Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} } + Condition.operators['starts_with'] = proc { |_cond, left, right| left =~ /^#{right}/ } assert_evaluates_true 'bob', 'starts_with', 'b' assert_evaluates_false 'bob', 'starts_with', 'o' ensure - Condition.operators.delete 'starts_with' + Condition.operators.delete('starts_with') end def test_left_or_right_may_contain_operators diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index 9252eb52..6d7042c8 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -206,9 +206,9 @@ class ContextUnitTest < Minitest::Test end def test_merge - @context.merge({ "test" => "test" }) + @context.merge("test" => "test") assert_equal 'test', @context['test'] - @context.merge({ "test" => "newvalue", "foo" => "bar" }) + @context.merge("test" => "newvalue", "foo" => "bar") assert_equal 'newvalue', @context['test'] assert_equal 'bar', @context['foo'] end @@ -235,10 +235,10 @@ class ContextUnitTest < Minitest::Test def test_hash_to_array_transition @context['colors'] = { - 'Blue' => ['003366', '336699', '6699CC', '99CCFF'], - 'Green' => ['003300', '336633', '669966', '99CC99'], - 'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'], - 'Red' => ['660000', '993333', 'CC6666', 'FF9999'] + 'Blue' => ['003366', '336699', '6699CC', '99CCFF'], + 'Green' => ['003300', '336633', '669966', '99CC99'], + 'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'], + 'Red' => ['660000', '993333', 'CC6666', 'FF9999'], } assert_equal '003366', @context['colors.Blue[0]'] @@ -263,7 +263,7 @@ class ContextUnitTest < Minitest::Test def test_access_hashes_with_hash_notation @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } - @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] } + @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } assert_equal 5, @context['products["count"]'] assert_equal 'deepsnow', @context['products["tags"][0]'] @@ -301,7 +301,7 @@ class ContextUnitTest < Minitest::Test end def test_first_can_appear_in_middle_of_callchain - @context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] } + @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } assert_equal 'draft151cm', @context['product.variants[0].title'] assert_equal 'element151cm', @context['product.variants[1].title'] @@ -453,7 +453,7 @@ class ContextUnitTest < Minitest::Test end def test_context_initialization_with_a_proc_in_environment - contx = Context.new([test: ->(c) { c['poutine'] }], { test: :foo }) + contx = Context.new([test: ->(c) { c['poutine'] }], test: :foo) assert contx assert_nil contx['poutine'] @@ -514,7 +514,7 @@ class ContextUnitTest < Minitest::Test def test_new_isolated_subcontext_does_not_inherit_non_static_registers registers = { - my_register: :my_value + my_register: :my_value, } super_context = Context.new({}, {}, registers) subcontext = super_context.new_isolated_subcontext diff --git a/test/unit/partial_cache_unit_test.rb b/test/unit/partial_cache_unit_test.rb index 29f11449..5778efb5 100644 --- a/test/unit/partial_cache_unit_test.rb +++ b/test/unit/partial_cache_unit_test.rb @@ -4,7 +4,7 @@ class PartialCacheUnitTest < Minitest::Test def test_uses_the_file_system_register_if_present context = Liquid::Context.build( registers: { - file_system: StubFileSystem.new('my_partial' => 'my partial body') + file_system: StubFileSystem.new('my_partial' => 'my partial body'), } ) @@ -41,12 +41,12 @@ class PartialCacheUnitTest < Minitest::Test ) context_one = Liquid::Context.build( registers: { - file_system: shared_file_system + file_system: shared_file_system, } ) context_two = Liquid::Context.build( registers: { - file_system: shared_file_system + file_system: shared_file_system, } ) diff --git a/test/unit/strainer_unit_test.rb b/test/unit/strainer_unit_test.rb index 5ce21004..5ae2660f 100644 --- a/test/unit/strainer_unit_test.rb +++ b/test/unit/strainer_unit_test.rb @@ -36,7 +36,8 @@ class StrainerUnitTest < Minitest::Test rescue Liquid::ArgumentError => e assert_match( /\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/, - e.message) + e.message + ) assert_equal e.backtrace[0].split(':')[0], __FILE__ end end @@ -135,7 +136,7 @@ class StrainerUnitTest < Minitest::Test end module LateAddedFilter - def late_added_filter(input) + def late_added_filter(_input) "filtered" end end @@ -150,7 +151,7 @@ class StrainerUnitTest < Minitest::Test mod = Module.new do class << self attr_accessor :include_count - def included(mod) + def included(_mod) self.include_count += 1 end end diff --git a/test/unit/tags/if_tag_unit_test.rb b/test/unit/tags/if_tag_unit_test.rb index 7ecfc409..71408b3e 100644 --- a/test/unit/tags/if_tag_unit_test.rb +++ b/test/unit/tags/if_tag_unit_test.rb @@ -3,6 +3,6 @@ require 'test_helper' class IfTagUnitTest < Minitest::Test def test_if_nodelist template = Liquid::Template.parse('{% if true %}IF{% else %}ELSE{% endif %}') - assert_equal ['IF', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten + assert_equal(['IF', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten) end end From 604d899496a0c11ebf54465c68ef64ad5f2ac874 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Sat, 31 Aug 2019 22:48:25 +1000 Subject: [PATCH 57/74] Render tag styling fixes --- lib/liquid/tags/render.rb | 4 ++-- test/integration/tags/render_tag_test.rb | 2 +- test/unit/partial_cache_unit_test.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index 2e5310b4..8f9a91c7 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -1,13 +1,13 @@ module Liquid class Render < Tag - Syntax = /(#{QuotedString})#{QuotedFragment}*/o + SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o attr_reader :template_name_expr, :attributes def initialize(tag_name, markup, options) super - raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ Syntax + raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ SYNTAX template_name = $1 diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index a31d0182..ea144771 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -110,7 +110,7 @@ class RenderTagTest < Minitest::Test file_system = StubFileSystem.new('snippet' => 'echo') assert_equal 'echoecho', Template.parse('{% render "snippet" %}{% render "snippet" %}') - .render!({}, registers: { file_system: file_system }) + .render!({}, registers: { file_system: file_system }) assert_equal 1, file_system.file_read_count end diff --git a/test/unit/partial_cache_unit_test.rb b/test/unit/partial_cache_unit_test.rb index 29f11449..5778efb5 100644 --- a/test/unit/partial_cache_unit_test.rb +++ b/test/unit/partial_cache_unit_test.rb @@ -4,7 +4,7 @@ class PartialCacheUnitTest < Minitest::Test def test_uses_the_file_system_register_if_present context = Liquid::Context.build( registers: { - file_system: StubFileSystem.new('my_partial' => 'my partial body') + file_system: StubFileSystem.new('my_partial' => 'my partial body'), } ) @@ -41,12 +41,12 @@ class PartialCacheUnitTest < Minitest::Test ) context_one = Liquid::Context.build( registers: { - file_system: shared_file_system + file_system: shared_file_system, } ) context_two = Liquid::Context.build( registers: { - file_system: shared_file_system + file_system: shared_file_system, } ) From 806b2622da6acdfd236478a2e90bedf999868f8d Mon Sep 17 00:00:00 2001 From: Justin Li Date: Wed, 4 Sep 2019 15:12:51 -0400 Subject: [PATCH 58/74] Switch back to Liquid-C master, since https://github.com/Shopify/liquid-c/pull/50 is merged --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index d77a7387..00c13263 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,6 @@ group :test do gem 'rubocop-performance', require: false platform :mri, :truffleruby do - gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'liquid-tag' + gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master' end end From b316ff8413a4d69b8a9443aa7430478e6b4c165f Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 11 Sep 2019 04:20:34 +1000 Subject: [PATCH 59/74] Add usage tracking --- README.md | 6 ++++++ lib/liquid/standardfilters.rb | 1 + lib/liquid/usage.rb | 6 ++++++ 3 files changed, 13 insertions(+) create mode 100644 lib/liquid/usage.rb diff --git a/README.md b/README.md index 77e9ff43..75b1d5f6 100644 --- a/README.md +++ b/README.md @@ -106,3 +106,9 @@ template = Liquid::Template.parse("{{x}} {{y}}") template.render!({ 'x' => 1}, { strict_variables: true }) #=> Liquid::UndefinedVariable: Liquid error: undefined variable y ``` + +### Usage tracking + +To help determine if a feature or code path is used in production we have included opt-in usage tracking. To achieve this we provide an empty `Liquid::Usage.increment` method that can be implemented. This was designed to be paired with https://github.com/Shopify/statsd-instrument , however it's implementation is up to you. + +Once you have enabled usage tracking we recommend reporting any logged events through Github Issues that your system may be reporting. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns. diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index afcf479e..f0c98686 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -421,6 +421,7 @@ module Liquid def default(input, default_value = ''.freeze) if !input || input.respond_to?(:empty?) && input.empty? + Usage.increment("liquid.default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127 default_value else input diff --git a/lib/liquid/usage.rb b/lib/liquid/usage.rb new file mode 100644 index 00000000..4876ce93 --- /dev/null +++ b/lib/liquid/usage.rb @@ -0,0 +1,6 @@ +module Liquid + module Usage + def self.increment(name, sample_rate: 0.1, tags: {}) + end + end +end From b6547f322eb9d3b83908bc463dc3dd79e37cb3e4 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 11 Sep 2019 04:56:25 +1000 Subject: [PATCH 60/74] Simplify usage --- lib/liquid.rb | 1 + lib/liquid/standardfilters.rb | 2 +- lib/liquid/usage.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/liquid.rb b/lib/liquid.rb index 0e198bb4..b98d4d96 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -75,6 +75,7 @@ require 'liquid/utils' require 'liquid/tokenizer' require 'liquid/parse_context' require 'liquid/partial_cache' +require 'liquid/usage' # Load all the tags of the standard library # diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index f0c98686..cf72dba2 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -421,7 +421,7 @@ module Liquid def default(input, default_value = ''.freeze) if !input || input.respond_to?(:empty?) && input.empty? - Usage.increment("liquid.default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127 + Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127 default_value else input diff --git a/lib/liquid/usage.rb b/lib/liquid/usage.rb index 4876ce93..e3267ebe 100644 --- a/lib/liquid/usage.rb +++ b/lib/liquid/usage.rb @@ -1,6 +1,6 @@ module Liquid module Usage - def self.increment(name, sample_rate: 0.1, tags: {}) + def self.increment(name) end end end From 8318be2edc0c4a2aaf1a47085a7aa65950eb6c81 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 11 Sep 2019 05:20:05 +1000 Subject: [PATCH 61/74] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75b1d5f6..6802a717 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,6 @@ template.render!({ 'x' => 1}, { strict_variables: true }) ### Usage tracking -To help determine if a feature or code path is used in production we have included opt-in usage tracking. To achieve this we provide an empty `Liquid::Usage.increment` method that can be implemented. This was designed to be paired with https://github.com/Shopify/statsd-instrument , however it's implementation is up to you. +To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you. -Once you have enabled usage tracking we recommend reporting any logged events through Github Issues that your system may be reporting. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns. +Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns. \ No newline at end of file From a5b387cdd4baaa04d205da4a218d1ce8ec637d34 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 11 Sep 2019 06:32:31 +1000 Subject: [PATCH 62/74] Remove reserved word Interrupt to avoid confusion Also resolves rubocop conflicts --- lib/liquid/interrupts.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/liquid/interrupts.rb b/lib/liquid/interrupts.rb index f3005e4c..48a820c7 100644 --- a/lib/liquid/interrupts.rb +++ b/lib/liquid/interrupts.rb @@ -1,6 +1,6 @@ module Liquid - # An interrupt is any command that breaks processing of a block (ex: a for loop). - class Interrupt + # A block interrupt is any command that breaks processing of a block (ex: a for loop). + class BlockInterrupt attr_reader :message def initialize(message = nil) @@ -9,8 +9,8 @@ module Liquid end # Interrupt that is thrown whenever a {% break %} is called. - class BreakInterrupt < RuntimeError; end + class BreakInterrupt < BlockInterrupt; end # Interrupt that is thrown whenever a {% continue %} is called. - class ContinueInterrupt < RuntimeError; end + class ContinueInterrupt < BlockInterrupt; end end From 724d02e9b3f94169252094cad962e56012329726 Mon Sep 17 00:00:00 2001 From: Mike Angell Date: Wed, 11 Sep 2019 06:35:08 +1000 Subject: [PATCH 63/74] Disable interrupt fix in this round --- .rubocop_todo.yml | 29 ++++++++++++++++++++++++++--- lib/liquid/interrupts.rb | 8 ++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0bd77b10..83ced97a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-08-31 21:54:20 +1000 using RuboCop version 0.74.0. +# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -26,6 +26,14 @@ Lint/AssignmentInCondition: - 'test/test_helper.rb' - 'test/unit/tokenizer_unit_test.rb' +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: runtime_error, standard_error +Lint/InheritException: + Exclude: + - 'lib/liquid/interrupts.rb' + # Offense count: 2 Lint/UselessAssignment: Exclude: @@ -44,9 +52,24 @@ Lint/Void: Metrics/LineLength: Max: 294 -# Offense count: 45 +# Offense count: 44 Naming/ConstantName: - Enabled: false + Exclude: + - 'lib/liquid.rb' + - 'lib/liquid/block_body.rb' + - 'lib/liquid/tags/assign.rb' + - 'lib/liquid/tags/capture.rb' + - 'lib/liquid/tags/case.rb' + - 'lib/liquid/tags/cycle.rb' + - 'lib/liquid/tags/for.rb' + - 'lib/liquid/tags/if.rb' + - 'lib/liquid/tags/include.rb' + - 'lib/liquid/tags/raw.rb' + - 'lib/liquid/tags/table_row.rb' + - 'lib/liquid/variable.rb' + - 'performance/shopify/comment_form.rb' + - 'performance/shopify/paginate.rb' + - 'test/integration/tags/include_tag_test.rb' # Offense count: 5 Style/ClassVars: diff --git a/lib/liquid/interrupts.rb b/lib/liquid/interrupts.rb index 48a820c7..41359d78 100644 --- a/lib/liquid/interrupts.rb +++ b/lib/liquid/interrupts.rb @@ -1,6 +1,6 @@ module Liquid - # A block interrupt is any command that breaks processing of a block (ex: a for loop). - class BlockInterrupt + # An interrupt is any command that breaks processing of a block (ex: a for loop). + class Interrupt attr_reader :message def initialize(message = nil) @@ -9,8 +9,8 @@ module Liquid end # Interrupt that is thrown whenever a {% break %} is called. - class BreakInterrupt < BlockInterrupt; end + class BreakInterrupt < Interrupt; end # Interrupt that is thrown whenever a {% continue %} is called. - class ContinueInterrupt < BlockInterrupt; end + class ContinueInterrupt < Interrupt; end end From c0ffee59197cb3ab1609cbe61f6758269b6ad6ca Mon Sep 17 00:00:00 2001 From: Thierry Joyal Date: Thu, 12 Sep 2019 12:29:56 +0000 Subject: [PATCH 64/74] Invokable methods for enumerable reject include? --- lib/liquid/drop.rb | 2 +- test/integration/drop_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb index 6b5aa993..1586c93e 100644 --- a/lib/liquid/drop.rb +++ b/lib/liquid/drop.rb @@ -67,7 +67,7 @@ module Liquid if include?(Enumerable) blacklist += Enumerable.public_instance_methods - blacklist -= [:sort, :count, :first, :min, :max, :include?] + blacklist -= [:sort, :count, :first, :min, :max] end whitelist = [:to_liquid] + (public_instance_methods - blacklist) diff --git a/test/integration/drop_test.rb b/test/integration/drop_test.rb index 2de4a5a1..5a2253b2 100644 --- a/test/integration/drop_test.rb +++ b/test/integration/drop_test.rb @@ -270,4 +270,11 @@ class DropsTest < Minitest::Test assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new) assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new) end + + def test_invokable_methods + assert_equal %w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods + assert_equal %w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods + assert_equal %w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods + assert_equal %w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods + end end # DropsTest From 0d26f05bb83a72e9b737ebbd81008e5631c4e85f Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Wed, 18 Sep 2019 13:19:45 +1000 Subject: [PATCH 65/74] Enabled frozen string literals (#1154) * Enabled frozen string literals * Update rubocop config * Prefer string interpolation in simple cases Co-Authored-By: Dylan Thacker-Smith --- .rubocop_todo.yml | 7 ---- Gemfile | 2 + Rakefile | 2 + example/server/example_servlet.rb | 2 + example/server/liquid_servlet.rb | 2 + example/server/server.rb | 2 + lib/liquid.rb | 10 +++-- lib/liquid/block.rb | 16 ++++---- lib/liquid/block_body.rb | 20 +++++----- lib/liquid/condition.rb | 20 +++++----- lib/liquid/context.rb | 4 +- lib/liquid/document.rb | 8 ++-- lib/liquid/drop.rb | 2 + lib/liquid/errors.rb | 6 ++- lib/liquid/expression.rb | 12 +++--- lib/liquid/extensions.rb | 2 + lib/liquid/file_system.rb | 6 ++- lib/liquid/forloop_drop.rb | 2 + lib/liquid/i18n.rb | 4 +- lib/liquid/interrupts.rb | 4 +- lib/liquid/lexer.rb | 22 ++++++----- lib/liquid/parse_context.rb | 2 + lib/liquid/parser.rb | 6 ++- lib/liquid/parser_switching.rb | 2 + lib/liquid/partial_cache.rb | 2 + lib/liquid/profiler.rb | 2 + lib/liquid/profiler/hooks.rb | 2 + lib/liquid/range_lookup.rb | 2 + lib/liquid/resource_limits.rb | 2 + lib/liquid/standardfilters.rb | 38 ++++++++++--------- lib/liquid/strainer.rb | 2 + lib/liquid/tablerowloop_drop.rb | 2 + lib/liquid/tag.rb | 4 +- lib/liquid/tags/assign.rb | 6 ++- lib/liquid/tags/break.rb | 4 +- lib/liquid/tags/capture.rb | 4 +- lib/liquid/tags/case.rb | 16 ++++---- lib/liquid/tags/comment.rb | 4 +- lib/liquid/tags/continue.rb | 4 +- lib/liquid/tags/cycle.rb | 4 +- lib/liquid/tags/decrement.rb | 4 +- lib/liquid/tags/echo.rb | 6 ++- lib/liquid/tags/for.rb | 24 ++++++------ lib/liquid/tags/if.rb | 18 +++++---- lib/liquid/tags/ifchanged.rb | 6 ++- lib/liquid/tags/include.rb | 8 ++-- lib/liquid/tags/increment.rb | 4 +- lib/liquid/tags/raw.rb | 12 +++--- lib/liquid/tags/render.rb | 6 ++- lib/liquid/tags/table_row.rb | 16 ++++---- lib/liquid/tags/unless.rb | 4 +- lib/liquid/template.rb | 6 ++- lib/liquid/tokenizer.rb | 2 + lib/liquid/usage.rb | 2 + lib/liquid/utils.rb | 4 +- lib/liquid/variable.rb | 2 + lib/liquid/variable_lookup.rb | 4 +- lib/liquid/version.rb | 3 +- liquid.gemspec | 1 + performance/benchmark.rb | 2 + performance/profile.rb | 2 + performance/shopify/comment_form.rb | 2 + performance/shopify/database.rb | 2 + performance/shopify/json_filter.rb | 2 + performance/shopify/liquid.rb | 2 + performance/shopify/money_filter.rb | 2 + performance/shopify/paginate.rb | 2 + performance/shopify/shop_filter.rb | 2 + performance/shopify/tag_filter.rb | 2 + performance/shopify/weight_filter.rb | 2 + performance/theme_runner.rb | 2 + test/integration/assign_test.rb | 2 + test/integration/blank_test.rb | 2 + test/integration/block_test.rb | 2 + test/integration/capture_test.rb | 2 + test/integration/context_test.rb | 2 + test/integration/document_test.rb | 2 + test/integration/drop_test.rb | 6 ++- test/integration/error_handling_test.rb | 2 + test/integration/filter_test.rb | 2 + test/integration/hash_ordering_test.rb | 2 + test/integration/output_test.rb | 2 + test/integration/parsing_quirks_test.rb | 2 + test/integration/render_profiling_test.rb | 2 + test/integration/security_test.rb | 2 + test/integration/standard_filter_test.rb | 1 + test/integration/tags/break_tag_test.rb | 2 + test/integration/tags/continue_tag_test.rb | 2 + test/integration/tags/echo_test.rb | 2 + test/integration/tags/for_tag_test.rb | 2 + test/integration/tags/if_else_tag_test.rb | 2 + test/integration/tags/include_tag_test.rb | 2 + test/integration/tags/increment_tag_test.rb | 2 + test/integration/tags/liquid_tag_test.rb | 2 + test/integration/tags/raw_tag_test.rb | 2 + test/integration/tags/render_tag_test.rb | 2 + test/integration/tags/standard_tag_test.rb | 2 + test/integration/tags/statements_test.rb | 2 + test/integration/tags/table_row_test.rb | 2 + test/integration/tags/unless_else_tag_test.rb | 2 + test/integration/template_test.rb | 2 + test/integration/trim_mode_test.rb | 2 + test/integration/variable_test.rb | 2 + test/test_helper.rb | 1 + test/unit/block_unit_test.rb | 6 ++- test/unit/condition_unit_test.rb | 2 + test/unit/context_unit_test.rb | 2 + test/unit/file_system_unit_test.rb | 2 + test/unit/i18n_unit_test.rb | 2 + test/unit/lexer_unit_test.rb | 2 + test/unit/parser_unit_test.rb | 2 + test/unit/partial_cache_unit_test.rb | 2 + test/unit/regexp_unit_test.rb | 2 + test/unit/strainer_unit_test.rb | 2 + test/unit/tag_unit_test.rb | 6 ++- test/unit/tags/case_tag_unit_test.rb | 2 + test/unit/tags/for_tag_unit_test.rb | 2 + test/unit/tags/if_tag_unit_test.rb | 2 + test/unit/template_unit_test.rb | 2 + test/unit/tokenizer_unit_test.rb | 2 + test/unit/variable_unit_test.rb | 2 + 121 files changed, 379 insertions(+), 150 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 83ced97a..12716289 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -84,13 +84,6 @@ Style/DateTime: Exclude: - 'test/unit/context_unit_test.rb' -# Offense count: 119 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, never -Style/FrozenStringLiteralComment: - Enabled: false - # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. diff --git a/Gemfile b/Gemfile index 00c13263..f5209346 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' git_source(:github) do |repo_name| "https://github.com/#{repo_name}.git" diff --git a/Rakefile b/Rakefile index f2a0d07b..66ae0fae 100755 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rake' require 'rake/testtask' $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) diff --git a/example/server/example_servlet.rb b/example/server/example_servlet.rb index 9f8c58a8..b09e3bb7 100644 --- a/example/server/example_servlet.rb +++ b/example/server/example_servlet.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ProductsFilter def price(integer) format("$%.2d USD", integer / 100.0) diff --git a/example/server/liquid_servlet.rb b/example/server/liquid_servlet.rb index 895f274a..55e21d21 100644 --- a/example/server/liquid_servlet.rb +++ b/example/server/liquid_servlet.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet def do_GET(req, res) handle(:get, req, res) diff --git a/example/server/server.rb b/example/server/server.rb index f2f89a40..bb7a4bc9 100644 --- a/example/server/server.rb +++ b/example/server/server.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'webrick' require 'rexml/document' diff --git a/lib/liquid.rb b/lib/liquid.rb index b98d4d96..66895663 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) 2005 Tobias Luetke # # Permission is hereby granted, free of charge, to any person obtaining @@ -21,10 +23,10 @@ module Liquid FilterSeparator = /\|/ - ArgumentSeparator = ','.freeze - FilterArgumentSeparator = ':'.freeze - VariableAttributeSeparator = '.'.freeze - WhitespaceControl = '-'.freeze + ArgumentSeparator = ',' + FilterArgumentSeparator = ':' + VariableAttributeSeparator = '.' + WhitespaceControl = '-' TagStart = /\{\%/ TagEnd = /\%\}/ VariableSignature = /\(?[\w\-\.\[\]]\)?/ diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 0036d7bf..f6698442 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Block < Tag MAX_DEPTH = 100 @@ -27,16 +29,16 @@ module Liquid end def unknown_tag(tag, _params, _tokens) - if tag == 'else'.freeze - raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else".freeze, + if tag == 'else' + raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else", block_name: block_name) - elsif tag.start_with?('end'.freeze) - raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter".freeze, + elsif tag.start_with?('end') + raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter", tag: tag, block_name: block_name, block_delimiter: block_delimiter) else - raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag) + raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag) end end @@ -52,7 +54,7 @@ module Liquid def parse_body(body, tokens) if parse_context.depth >= MAX_DEPTH - raise StackLevelError, "Nesting too deep".freeze + raise StackLevelError, "Nesting too deep" end parse_context.depth += 1 begin @@ -61,7 +63,7 @@ module Liquid return false if end_tag_name == block_delimiter unless end_tag_name - raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name) end # this tag is not registered with the system diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index a52eb620..c4ce2671 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + module Liquid class BlockBody LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om WhitespaceOrNothing = /\A\s*\z/ - TAGSTART = "{%".freeze - VARSTART = "{{".freeze + TAGSTART = "{%" + VARSTART = "{{" attr_reader :nodelist @@ -64,10 +66,10 @@ module Liquid if parse_context.line_number # newlines inside the tag should increase the line number, # particularly important for multiline {% liquid %} tags - parse_context.line_number += Regexp.last_match(1).count("\n".freeze) + Regexp.last_match(3).count("\n".freeze) + parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n") end - if tag_name == 'liquid'.freeze + if tag_name == 'liquid' liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true) next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block) end @@ -113,7 +115,7 @@ module Liquid end def render(context) - render_to_output_buffer(context, '') + render_to_output_buffer(context, +'') end def render_to_output_buffer(context, output) @@ -129,7 +131,7 @@ module Liquid when Variable render_node(context, output, node) when Block - render_node(context, node.blank? ? '' : output, node) + render_node(context, node.blank? ? +'' : output, node) break if context.interrupt? # might have happened in a for-block when Continue, Break # If we get an Interrupt that means the block must stop processing. An @@ -163,7 +165,7 @@ module Liquid def raise_if_resource_limits_reached(context, length) context.resource_limits.render_length += length return unless context.resource_limits.reached? - raise MemoryError, "Memory limits exceeded".freeze + raise MemoryError, "Memory limits exceeded" end def create_variable(token, parse_context) @@ -175,11 +177,11 @@ module Liquid end def raise_missing_tag_terminator(token, parse_context) - raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect) end def raise_missing_variable_terminator(token, parse_context) - raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect) + raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect) end def registered_tags diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb index c6e29ae0..93ec68b7 100644 --- a/lib/liquid/condition.rb +++ b/lib/liquid/condition.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Container for liquid nodes which conveniently wraps decision making logic # @@ -8,14 +10,14 @@ module Liquid # class Condition #:nodoc: @@operators = { - '=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) }, - '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, - '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, - '<'.freeze => :<, - '>'.freeze => :>, - '>='.freeze => :>=, - '<='.freeze => :<=, - 'contains'.freeze => lambda do |_cond, left, right| + '==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) }, + '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, + '<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) }, + '<' => :<, + '>' => :>, + '>=' => :>=, + '<=' => :<=, + 'contains' => lambda do |_cond, left, right| if left && right && left.respond_to?(:include?) right = right.to_s if left.is_a?(String) left.include?(right) @@ -78,7 +80,7 @@ module Liquid end def inspect - "#" + "#" end protected diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index daae5deb..7e4350aa 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Context keeps the variable stack and resolves variables, as well as keywords # @@ -232,7 +234,7 @@ module Liquid end def check_overflow - raise StackLevelError, "Nesting too deep".freeze if overflow? + raise StackLevelError, "Nesting too deep" if overflow? end def overflow? diff --git a/lib/liquid/document.rb b/lib/liquid/document.rb index afd4e998..e160886d 100644 --- a/lib/liquid/document.rb +++ b/lib/liquid/document.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Document < BlockBody def self.parse(tokens, parse_context) @@ -17,10 +19,10 @@ module Liquid def unknown_tag(tag, parse_context) case tag - when 'else'.freeze, 'end'.freeze - raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag) + when 'else', 'end' + raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag) else - raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag) + raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag) end end end diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb index 669eae6a..d4d8950f 100644 --- a/lib/liquid/drop.rb +++ b/lib/liquid/drop.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module Liquid diff --git a/lib/liquid/errors.rb b/lib/liquid/errors.rb index 4239746f..eda0bd2a 100644 --- a/lib/liquid/errors.rb +++ b/lib/liquid/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Error < ::StandardError attr_accessor :line_number @@ -5,7 +7,7 @@ module Liquid attr_accessor :markup_context def to_s(with_prefix = true) - str = "" + str = +"" str << message_prefix if with_prefix str << super() @@ -20,7 +22,7 @@ module Liquid private def message_prefix - str = "" + str = +"" str << if is_a?(SyntaxError) "Liquid syntax error" else diff --git a/lib/liquid/expression.rb b/lib/liquid/expression.rb index 55681992..9670906b 100644 --- a/lib/liquid/expression.rb +++ b/lib/liquid/expression.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Expression class MethodLiteral @@ -14,11 +16,11 @@ module Liquid end LITERALS = { - nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil, - 'true'.freeze => true, - 'false'.freeze => false, - 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze, - 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze + nil => nil, 'nil' => nil, 'null' => nil, '' => nil, + 'true' => true, + 'false' => false, + 'blank' => MethodLiteral.new(:blank?, '').freeze, + 'empty' => MethodLiteral.new(:empty?, '').freeze }.freeze SINGLE_QUOTED_STRING = /\A'(.*)'\z/m diff --git a/lib/liquid/extensions.rb b/lib/liquid/extensions.rb index 09078193..d1854983 100644 --- a/lib/liquid/extensions.rb +++ b/lib/liquid/extensions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'time' require 'date' diff --git a/lib/liquid/file_system.rb b/lib/liquid/file_system.rb index a2aa9b75..b2093aee 100644 --- a/lib/liquid/file_system.rb +++ b/lib/liquid/file_system.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag. # @@ -44,7 +46,7 @@ module Liquid class LocalFileSystem attr_accessor :root - def initialize(root, pattern = "_%s.liquid".freeze) + def initialize(root, pattern = "_%s.liquid") @root = root @pattern = pattern end @@ -59,7 +61,7 @@ module Liquid def full_path(template_path) raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ %r{\A[^./][a-zA-Z0-9_/]+\z} - full_path = if template_path.include?('/'.freeze) + full_path = if template_path.include?('/') File.join(root, File.dirname(template_path), @pattern % File.basename(template_path)) else File.join(root, @pattern % template_path) diff --git a/lib/liquid/forloop_drop.rb b/lib/liquid/forloop_drop.rb index 81b2d1a2..0ffa2559 100644 --- a/lib/liquid/forloop_drop.rb +++ b/lib/liquid/forloop_drop.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class ForloopDrop < Drop def initialize(name, length, parentloop) diff --git a/lib/liquid/i18n.rb b/lib/liquid/i18n.rb index b2bb51ba..4a2885e8 100644 --- a/lib/liquid/i18n.rb +++ b/lib/liquid/i18n.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' module Liquid @@ -31,7 +33,7 @@ module Liquid end def deep_fetch_translation(name) - name.split('.'.freeze).reduce(locale) do |level, cur| + name.split('.').reduce(locale) do |level, cur| level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}") end end diff --git a/lib/liquid/interrupts.rb b/lib/liquid/interrupts.rb index 41359d78..28355b84 100644 --- a/lib/liquid/interrupts.rb +++ b/lib/liquid/interrupts.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + module Liquid # An interrupt is any command that breaks processing of a block (ex: a for loop). class Interrupt attr_reader :message def initialize(message = nil) - @message = message || "interrupt".freeze + @message = message || "interrupt" end end diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb index 367f99ee..04e0c116 100644 --- a/lib/liquid/lexer.rb +++ b/lib/liquid/lexer.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + require "strscan" module Liquid class Lexer SPECIALS = { - '|'.freeze => :pipe, - '.'.freeze => :dot, - ':'.freeze => :colon, - ','.freeze => :comma, - '['.freeze => :open_square, - ']'.freeze => :close_square, - '('.freeze => :open_round, - ')'.freeze => :close_round, - '?'.freeze => :question, - '-'.freeze => :dash, + '|' => :pipe, + '.' => :dot, + ':' => :colon, + ',' => :comma, + '[' => :open_square, + ']' => :close_square, + '(' => :open_round, + ')' => :close_round, + '?' => :question, + '-' => :dash, }.freeze IDENTIFIER = /[a-zA-Z_][\w-]*\??/ SINGLE_STRING_LITERAL = /'[^\']*'/ diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb index 58437f4d..2da3ad75 100644 --- a/lib/liquid/parse_context.rb +++ b/lib/liquid/parse_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class ParseContext attr_accessor :locale, :line_number, :trim_whitespace, :depth diff --git a/lib/liquid/parser.rb b/lib/liquid/parser.rb index c36de86f..6b9e8374 100644 --- a/lib/liquid/parser.rb +++ b/lib/liquid/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Parser def initialize(input) @@ -66,10 +68,10 @@ module Liquid end def argument - str = "" + str = +"" # might be a keyword argument (identifier: expression) if look(:id) && look(:colon, 1) - str << consume << consume << ' '.freeze + str << consume << consume << ' ' end str << expression diff --git a/lib/liquid/parser_switching.rb b/lib/liquid/parser_switching.rb index 3aa664ab..402b0566 100644 --- a/lib/liquid/parser_switching.rb +++ b/lib/liquid/parser_switching.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid module ParserSwitching def parse_with_selected_parser(markup) diff --git a/lib/liquid/partial_cache.rb b/lib/liquid/partial_cache.rb index d0b88457..43c2e39b 100644 --- a/lib/liquid/partial_cache.rb +++ b/lib/liquid/partial_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class PartialCache def self.load(template_name, context:, parse_context:) diff --git a/lib/liquid/profiler.rb b/lib/liquid/profiler.rb index dc9db60a..dc3f1db6 100644 --- a/lib/liquid/profiler.rb +++ b/lib/liquid/profiler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'liquid/profiler/hooks' module Liquid diff --git a/lib/liquid/profiler/hooks.rb b/lib/liquid/profiler/hooks.rb index cda166b7..e7086533 100644 --- a/lib/liquid/profiler/hooks.rb +++ b/lib/liquid/profiler/hooks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class BlockBody def render_node_with_profiling(context, output, node) diff --git a/lib/liquid/range_lookup.rb b/lib/liquid/range_lookup.rb index 93bb420c..8e4d7652 100644 --- a/lib/liquid/range_lookup.rb +++ b/lib/liquid/range_lookup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class RangeLookup def self.parse(start_markup, end_markup) diff --git a/lib/liquid/resource_limits.rb b/lib/liquid/resource_limits.rb index 08b359ba..5b7e8e46 100644 --- a/lib/liquid/resource_limits.rb +++ b/lib/liquid/resource_limits.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class ResourceLimits attr_accessor :render_length, :render_score, :assign_score, diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 45cf3a05..517857ac 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + require 'cgi' require 'bigdecimal' module Liquid module StandardFilters HTML_ESCAPE = { - '&'.freeze => '&'.freeze, - '>'.freeze => '>'.freeze, - '<'.freeze => '<'.freeze, - '"'.freeze => '"'.freeze, - "'".freeze => '''.freeze, + '&' => '&', + '>' => '>', + '<' => '<', + '"' => '"', + "'" => ''', }.freeze HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ STRIP_HTML_BLOCKS = Regexp.union( @@ -72,7 +74,7 @@ module Liquid end # Truncate a string down to x characters - def truncate(input, length = 50, truncate_string = "...".freeze) + def truncate(input, length = 50, truncate_string = "...") return if input.nil? input_str = input.to_s length = Utils.to_integer(length) @@ -82,13 +84,13 @@ module Liquid input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str end - def truncatewords(input, words = 15, truncate_string = "...".freeze) + def truncatewords(input, words = 15, truncate_string = "...") return if input.nil? wordlist = input.to_s.split words = Utils.to_integer(words) l = words - 1 l = 0 if l < 0 - wordlist.length > l ? wordlist[0..l].join(" ".freeze).concat(truncate_string.to_s) : input + wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input end # Split input string into an array of substrings separated by given pattern. @@ -113,7 +115,7 @@ module Liquid end def strip_html(input) - empty = ''.freeze + empty = '' result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty) result.gsub!(STRIP_HTML_TAGS, empty) result @@ -121,11 +123,11 @@ module Liquid # Remove all newlines from the string def strip_newlines(input) - input.to_s.gsub(/\r?\n/, ''.freeze) + input.to_s.gsub(/\r?\n/, '') end # Join elements of the array with certain character between them - def join(input, glue = ' '.freeze) + def join(input, glue = ' ') InputIterator.new(input).join(glue) end @@ -220,7 +222,7 @@ module Liquid InputIterator.new(input).map do |e| e = e.call if e.is_a?(Proc) - if property == "to_liquid".freeze + if property == "to_liquid" e elsif e.respond_to?(:[]) r = e[property] @@ -250,23 +252,23 @@ module Liquid end # Replace occurrences of a string with another - def replace(input, string, replacement = ''.freeze) + def replace(input, string, replacement = '') input.to_s.gsub(string.to_s, replacement.to_s) end # Replace the first occurrences of a string with another - def replace_first(input, string, replacement = ''.freeze) + def replace_first(input, string, replacement = '') input.to_s.sub(string.to_s, replacement.to_s) end # remove a substring def remove(input, string) - input.to_s.gsub(string.to_s, ''.freeze) + input.to_s.gsub(string.to_s, '') end # remove the first occurrences of a substring def remove_first(input, string) - input.to_s.sub(string.to_s, ''.freeze) + input.to_s.sub(string.to_s, '') end # add one string to another @@ -288,7 +290,7 @@ module Liquid # Add
tags in front of all newlines in input string def newline_to_br(input) - input.to_s.gsub(/\n/, "
\n".freeze) + input.to_s.gsub(/\n/, "
\n") end # Reformat a date using Ruby's core Time#strftime( string ) -> string @@ -419,7 +421,7 @@ module Liquid result.is_a?(BigDecimal) ? result.to_f : result end - def default(input, default_value = ''.freeze) + def default(input, default_value = '') if !input || input.respond_to?(:empty?) && input.empty? Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127 default_value diff --git a/lib/liquid/strainer.rb b/lib/liquid/strainer.rb index d885ae41..3f3417e3 100644 --- a/lib/liquid/strainer.rb +++ b/lib/liquid/strainer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module Liquid diff --git a/lib/liquid/tablerowloop_drop.rb b/lib/liquid/tablerowloop_drop.rb index cda4a1ed..0d00b6f1 100644 --- a/lib/liquid/tablerowloop_drop.rb +++ b/lib/liquid/tablerowloop_drop.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class TablerowloopDrop < Drop def initialize(length, cols) diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index 13b7e4bb..14606391 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Tag attr_reader :nodelist, :tag_name, :line_number, :parse_context @@ -33,7 +35,7 @@ module Liquid end def render(_context) - ''.freeze + '' end # For backwards compatibility with custom tags. In a future release, the semantics diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 6ff65d57..aaad14cf 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Assign sets a variable in your template. # @@ -11,7 +13,7 @@ module Liquid Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om def self.syntax_error_translation_key - "errors.syntax.assign".freeze + "errors.syntax.assign" end attr_reader :to, :from @@ -59,5 +61,5 @@ module Liquid end end - Template.register_tag('assign'.freeze, Assign) + Template.register_tag('assign', Assign) end diff --git a/lib/liquid/tags/break.rb b/lib/liquid/tags/break.rb index 6fe0969b..80f46278 100644 --- a/lib/liquid/tags/break.rb +++ b/lib/liquid/tags/break.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Break tag to be used to break out of a for loop. # @@ -14,5 +16,5 @@ module Liquid end end - Template.register_tag('break'.freeze, Break) + Template.register_tag('break', Break) end diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index a97e42a1..1cace9c1 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Capture stores the result of a block into a variable without rendering it inplace. # @@ -35,5 +37,5 @@ module Liquid end end - Template.register_tag('capture'.freeze, Capture) + Template.register_tag('capture', Capture) end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 2a72a185..30484c6d 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Case < Block Syntax = /(#{QuotedFragment})/o @@ -12,7 +14,7 @@ module Liquid if markup =~ Syntax @left = Expression.parse(Regexp.last_match(1)) else - raise SyntaxError, options[:locale].t("errors.syntax.case".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.case") end end @@ -27,9 +29,9 @@ module Liquid def unknown_tag(tag, markup, tokens) case tag - when 'when'.freeze + when 'when' record_when_condition(markup) - when 'else'.freeze + when 'else' record_else_condition(markup) else super @@ -58,12 +60,12 @@ module Liquid while markup unless markup =~ WhenSyntax - raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when") end markup = Regexp.last_match(2) - block = Condition.new(@left, '=='.freeze, Expression.parse(Regexp.last_match(1))) + block = Condition.new(@left, '==', Expression.parse(Regexp.last_match(1))) block.attach(body) @blocks << block end @@ -71,7 +73,7 @@ module Liquid def record_else_condition(markup) unless markup.strip.empty? - raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else") end block = ElseCondition.new @@ -86,5 +88,5 @@ module Liquid end end - Template.register_tag('case'.freeze, Case) + Template.register_tag('case', Case) end diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb index cad3931e..a5460f99 100644 --- a/lib/liquid/tags/comment.rb +++ b/lib/liquid/tags/comment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Comment < Block def render_to_output_buffer(_context, output) @@ -12,5 +14,5 @@ module Liquid end end - Template.register_tag('comment'.freeze, Comment) + Template.register_tag('comment', Comment) end diff --git a/lib/liquid/tags/continue.rb b/lib/liquid/tags/continue.rb index 9c81ec2c..fb1f371e 100644 --- a/lib/liquid/tags/continue.rb +++ b/lib/liquid/tags/continue.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Continue tag to be used to break out of a for loop. # @@ -14,5 +16,5 @@ module Liquid end end - Template.register_tag('continue'.freeze, Continue) + Template.register_tag('continue', Continue) end diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index adc6e3de..b203c785 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Cycle is usually used within a loop to alternate between values, like colors or DOM classes. # @@ -27,7 +29,7 @@ module Liquid @variables = variables_from_string(markup) @name = @variables.to_s else - raise SyntaxError, options[:locale].t("errors.syntax.cycle".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.cycle") end end diff --git a/lib/liquid/tags/decrement.rb b/lib/liquid/tags/decrement.rb index 08ddd4d6..d761a0c3 100644 --- a/lib/liquid/tags/decrement.rb +++ b/lib/liquid/tags/decrement.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # decrement is used in a place where one needs to insert a counter # into a template, and needs the counter to survive across @@ -32,5 +34,5 @@ module Liquid end end - Template.register_tag('decrement'.freeze, Decrement) + Template.register_tag('decrement', Decrement) end diff --git a/lib/liquid/tags/echo.rb b/lib/liquid/tags/echo.rb index d3d30e3b..1f789376 100644 --- a/lib/liquid/tags/echo.rb +++ b/lib/liquid/tags/echo.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Echo outputs an expression # @@ -16,9 +18,9 @@ module Liquid end def render(context) - @variable.render_to_output_buffer(context, '') + @variable.render_to_output_buffer(context, +'') end end - Template.register_tag('echo'.freeze, Echo) + Template.register_tag('echo', Echo) end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index f20953f8..d9613697 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # "For" iterates over an array or collection. # Several useful variables are available to you within the loop. @@ -66,7 +68,7 @@ module Liquid end def unknown_tag(tag, markup, tokens) - return super unless tag == 'else'.freeze + return super unless tag == 'else' @else_block = BlockBody.new end @@ -95,22 +97,22 @@ module Liquid set_attribute(key, value) end else - raise SyntaxError, options[:locale].t("errors.syntax.for".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.for") end end def strict_parse(markup) p = Parser.new(markup) @variable_name = p.consume(:id) - raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in".freeze) unless p.id?('in'.freeze) + raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in') collection_name = p.expression @name = "#{@variable_name}-#{collection_name}" @collection_name = Expression.parse(collection_name) - @reversed = p.id?('reversed'.freeze) + @reversed = p.id?('reversed') while p.look(:id) && p.look(:colon, 1) - unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze) - raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute".freeze) + unless attribute = p.id?('limit') || p.id?('offset') + raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute") end p.consume set_attribute(attribute, p.expression) @@ -162,7 +164,7 @@ module Liquid for_stack.push(loop_vars) begin - context['forloop'.freeze] = loop_vars + context['forloop'] = loop_vars segment.each do |item| context[@variable_name] = item @@ -185,13 +187,13 @@ module Liquid def set_attribute(key, expr) case key - when 'offset'.freeze - @from = if expr == 'continue'.freeze + when 'offset' + @from = if expr == 'continue' :continue else Expression.parse(expr) end - when 'limit'.freeze + when 'limit' @limit = Expression.parse(expr) end end @@ -211,5 +213,5 @@ module Liquid end end - Template.register_tag('for'.freeze, For) + Template.register_tag('for', For) end diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 1a313952..c3d1a777 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # If is the conditional block # @@ -19,7 +21,7 @@ module Liquid def initialize(tag_name, markup, options) super @blocks = [] - push_block('if'.freeze, markup) + push_block('if', markup) end def nodelist @@ -32,7 +34,7 @@ module Liquid end def unknown_tag(tag, markup, tokens) - if ['elsif'.freeze, 'else'.freeze].include?(tag) + if ['elsif', 'else'].include?(tag) push_block(tag, markup) else super @@ -52,7 +54,7 @@ module Liquid private def push_block(tag, markup) - block = if tag == 'else'.freeze + block = if tag == 'else' ElseCondition.new else parse_with_selected_parser(markup) @@ -64,17 +66,17 @@ module Liquid def lax_parse(markup) expressions = markup.scan(ExpressionsAndOperators) - raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless expressions.pop =~ Syntax + raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3))) until expressions.empty? operator = expressions.pop.to_s.strip - raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless expressions.pop.to_s =~ Syntax + raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3))) - raise SyntaxError, options[:locale].t("errors.syntax.if".freeze) unless BOOLEAN_OPERATORS.include?(operator) + raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator) new_condition.send(operator, condition) condition = new_condition end @@ -92,7 +94,7 @@ module Liquid def parse_binary_comparisons(p) condition = parse_comparison(p) first_condition = condition - while op = (p.id?('and'.freeze) || p.id?('or'.freeze)) + while op = (p.id?('and') || p.id?('or')) child_condition = parse_comparison(p) condition.send(op, child_condition) condition = child_condition @@ -117,5 +119,5 @@ module Liquid end end - Template.register_tag('if'.freeze, If) + Template.register_tag('if', If) end diff --git a/lib/liquid/tags/ifchanged.rb b/lib/liquid/tags/ifchanged.rb index ddd276cf..dd3be532 100644 --- a/lib/liquid/tags/ifchanged.rb +++ b/lib/liquid/tags/ifchanged.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module Liquid class Ifchanged < Block def render_to_output_buffer(context, output) - block_output = '' + block_output = +'' super(context, block_output) if block_output != context.registers[:ifchanged] @@ -13,5 +15,5 @@ module Liquid end end - Template.register_tag('ifchanged'.freeze, Ifchanged) + Template.register_tag('ifchanged', Ifchanged) end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index d2c6cd26..bbcfb1cc 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Include allows templates to relate with other templates # @@ -35,7 +37,7 @@ module Liquid end else - raise SyntaxError, options[:locale].t("errors.syntax.include".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.include") end end @@ -52,7 +54,7 @@ module Liquid parse_context: parse_context ) - context_variable_name = template_name.split('/'.freeze).last + context_variable_name = template_name.split('/').last variable = if @variable_name_expr context.evaluate(@variable_name_expr) @@ -101,5 +103,5 @@ module Liquid end end - Template.register_tag('include'.freeze, Include) + Template.register_tag('include', Include) end diff --git a/lib/liquid/tags/increment.rb b/lib/liquid/tags/increment.rb index 95875aa6..241b316b 100644 --- a/lib/liquid/tags/increment.rb +++ b/lib/liquid/tags/increment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # increment is used in a place where one needs to insert a counter # into a template, and needs the counter to survive across @@ -29,5 +31,5 @@ module Liquid end end - Template.register_tag('increment'.freeze, Increment) + Template.register_tag('increment', Increment) end diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index 69910021..093a37e1 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Raw < Block Syntax = /\A\s*\z/ @@ -10,16 +12,16 @@ module Liquid end def parse(tokens) - @body = '' + @body = +'' while token = tokens.shift if token =~ FullTokenPossiblyInvalid - @body << Regexp.last_match(1) if Regexp.last_match(1) != "".freeze + @body << Regexp.last_match(1) if Regexp.last_match(1) != "" return if block_delimiter == Regexp.last_match(2) end @body << token unless token.empty? end - raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name) end def render_to_output_buffer(_context, output) @@ -39,10 +41,10 @@ module Liquid def ensure_valid_markup(tag_name, markup, parse_context) unless markup =~ Syntax - raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name) + raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name) end end end - Template.register_tag('raw'.freeze, Raw) + Template.register_tag('raw', Raw) end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index d9b80028..e6c62232 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Render < Tag SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o @@ -7,7 +9,7 @@ module Liquid def initialize(tag_name, markup, options) super - raise SyntaxError, options[:locale].t("errors.syntax.render".freeze) unless markup =~ SYNTAX + raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX template_name = Regexp.last_match(1) @@ -50,5 +52,5 @@ module Liquid end end - Template.register_tag('render'.freeze, Render) + Template.register_tag('render', Render) end diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 93935276..7c59bd32 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class TableRow < Block Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o @@ -14,26 +16,26 @@ module Liquid @attributes[key] = Expression.parse(value) end else - raise SyntaxError, options[:locale].t("errors.syntax.table_row".freeze) + raise SyntaxError, options[:locale].t("errors.syntax.table_row") end end def render_to_output_buffer(context, output) - (collection = context.evaluate(@collection_name)) || (return ''.freeze) + (collection = context.evaluate(@collection_name)) || (return '') - from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 - to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil + from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0 + to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil collection = Utils.slice_collection(collection, from, to) length = collection.length - cols = context.evaluate(@attributes['cols'.freeze]).to_i + cols = context.evaluate(@attributes['cols']).to_i output << "\n" context.stack do tablerowloop = Liquid::TablerowloopDrop.new(length, cols) - context['tablerowloop'.freeze] = tablerowloop + context['tablerowloop'] = tablerowloop collection.each do |item| context[@variable_name] = item @@ -61,5 +63,5 @@ module Liquid end end - Template.register_tag('tablerow'.freeze, TableRow) + Template.register_tag('tablerow', TableRow) end diff --git a/lib/liquid/tags/unless.rb b/lib/liquid/tags/unless.rb index 32aa3a41..f67f57a9 100644 --- a/lib/liquid/tags/unless.rb +++ b/lib/liquid/tags/unless.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'if' module Liquid @@ -24,5 +26,5 @@ module Liquid end end - Template.register_tag('unless'.freeze, Unless) + Template.register_tag('unless', Unless) end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 62250b28..e77ba8aa 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Templates are central to liquid. # Interpretating templates is a two step process. First you compile the @@ -165,7 +167,7 @@ module Liquid # filters and tags and might be useful to integrate liquid more with its host application # def render(*args) - return ''.freeze if @root.nil? + return '' if @root.nil? context = case args.first when Liquid::Context @@ -208,7 +210,7 @@ module Liquid # render the nodelist. # for performance reasons we get an array back here. join will make a string out of it. with_profiling(context) do - @root.render_to_output_buffer(context, output || '') + @root.render_to_output_buffer(context, output || +'') end rescue Liquid::MemoryError => e context.handle_error(e) diff --git a/lib/liquid/tokenizer.rb b/lib/liquid/tokenizer.rb index 95114200..a89c7899 100644 --- a/lib/liquid/tokenizer.rb +++ b/lib/liquid/tokenizer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class Tokenizer attr_reader :line_number, :for_liquid_tag diff --git a/lib/liquid/usage.rb b/lib/liquid/usage.rb index e3267ebe..141eccb9 100644 --- a/lib/liquid/usage.rb +++ b/lib/liquid/usage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid module Usage def self.increment(name) diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb index ada4f396..406d667b 100644 --- a/lib/liquid/utils.rb +++ b/lib/liquid/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid module Utils def self.slice_collection(collection, from, to) @@ -69,7 +71,7 @@ module Liquid end case obj - when 'now'.freeze, 'today'.freeze + when 'now', 'today' Time.now when /\A\d+\z/, Integer Time.at(obj.to_i) diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 6efcf705..2fc2ea85 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid # Holds variables. Variables are only loaded "just in time" # and are not evaluated as part of the render stage diff --git a/lib/liquid/variable_lookup.rb b/lib/liquid/variable_lookup.rb index ab06bb62..112373d5 100644 --- a/lib/liquid/variable_lookup.rb +++ b/lib/liquid/variable_lookup.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module Liquid class VariableLookup SQUARE_BRACKETED = /\A\[(.*)\]\z/m - COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze + COMMAND_METHODS = ['size', 'first', 'last'].freeze attr_reader :name, :lookups diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index da01c476..9af29732 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -1,5 +1,6 @@ # encoding: utf-8 +# frozen_string_literal: true module Liquid - VERSION = "4.0.3".freeze + VERSION = "4.0.3" end diff --git a/liquid.gemspec b/liquid.gemspec index 27b24aae..54a11fb9 100644 --- a/liquid.gemspec +++ b/liquid.gemspec @@ -1,4 +1,5 @@ # encoding: utf-8 +# frozen_string_literal: true lib = File.expand_path('../lib/', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) diff --git a/performance/benchmark.rb b/performance/benchmark.rb index 68c568c1..4d28b9ac 100644 --- a/performance/benchmark.rb +++ b/performance/benchmark.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'benchmark/ips' require_relative 'theme_runner' diff --git a/performance/profile.rb b/performance/profile.rb index c6fb1932..101f6e5d 100644 --- a/performance/profile.rb +++ b/performance/profile.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'stackprof' require_relative 'theme_runner' diff --git a/performance/shopify/comment_form.rb b/performance/shopify/comment_form.rb index 65af1b51..7648e1a4 100644 --- a/performance/shopify/comment_form.rb +++ b/performance/shopify/comment_form.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CommentForm < Liquid::Block Syntax = /(#{Liquid::VariableSignature}+)/ diff --git a/performance/shopify/database.rb b/performance/shopify/database.rb index c9f18c30..9836cd46 100644 --- a/performance/shopify/database.rb +++ b/performance/shopify/database.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' module Database diff --git a/performance/shopify/json_filter.rb b/performance/shopify/json_filter.rb index 32583167..c7c25d8a 100644 --- a/performance/shopify/json_filter.rb +++ b/performance/shopify/json_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' module JsonFilter diff --git a/performance/shopify/liquid.rb b/performance/shopify/liquid.rb index f9d52007..40444c3c 100644 --- a/performance/shopify/liquid.rb +++ b/performance/shopify/liquid.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(__dir__ + '/../../lib') require_relative '../../lib/liquid' diff --git a/performance/shopify/money_filter.rb b/performance/shopify/money_filter.rb index 4cc7280d..b0135e3e 100644 --- a/performance/shopify/money_filter.rb +++ b/performance/shopify/money_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MoneyFilter def money_with_currency(money) return '' if money.nil? diff --git a/performance/shopify/paginate.rb b/performance/shopify/paginate.rb index 29e7c9ee..f7238238 100644 --- a/performance/shopify/paginate.rb +++ b/performance/shopify/paginate.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Paginate < Liquid::Block Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/ diff --git a/performance/shopify/shop_filter.rb b/performance/shopify/shop_filter.rb index b2a0a9d0..9f0cdc2f 100644 --- a/performance/shopify/shop_filter.rb +++ b/performance/shopify/shop_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ShopFilter def asset_url(input) "/files/1/[shop_id]/[shop_id]/assets/#{input}" diff --git a/performance/shopify/tag_filter.rb b/performance/shopify/tag_filter.rb index 34b426f2..58f066be 100644 --- a/performance/shopify/tag_filter.rb +++ b/performance/shopify/tag_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module TagFilter def link_to_tag(label, tag) "#{label}" diff --git a/performance/shopify/weight_filter.rb b/performance/shopify/weight_filter.rb index b05bcce1..6ba95f37 100644 --- a/performance/shopify/weight_filter.rb +++ b/performance/shopify/weight_filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module WeightFilter def weight(grams) format("%.2f", grams / 1000) diff --git a/performance/theme_runner.rb b/performance/theme_runner.rb index 9268558c..5ad01c55 100644 --- a/performance/theme_runner.rb +++ b/performance/theme_runner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This profiler run simulates Shopify. # We are looking in the tests directory for liquid files and render them within the designated layout file. # We will also export a substantial database to liquid which the templates can render values of. diff --git a/test/integration/assign_test.rb b/test/integration/assign_test.rb index 55022899..ffcb8a3f 100644 --- a/test/integration/assign_test.rb +++ b/test/integration/assign_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class AssignTest < Minitest::Test diff --git a/test/integration/blank_test.rb b/test/integration/blank_test.rb index 654ee987..f92490b2 100644 --- a/test/integration/blank_test.rb +++ b/test/integration/blank_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class FoobarTag < Liquid::Tag diff --git a/test/integration/block_test.rb b/test/integration/block_test.rb index 08245301..5603b53f 100644 --- a/test/integration/block_test.rb +++ b/test/integration/block_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class BlockTest < Minitest::Test diff --git a/test/integration/capture_test.rb b/test/integration/capture_test.rb index 8d965b35..f28e1b1b 100644 --- a/test/integration/capture_test.rb +++ b/test/integration/capture_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class CaptureTest < Minitest::Test diff --git a/test/integration/context_test.rb b/test/integration/context_test.rb index 2d109bb7..cd6d7a88 100644 --- a/test/integration/context_test.rb +++ b/test/integration/context_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ContextTest < Minitest::Test diff --git a/test/integration/document_test.rb b/test/integration/document_test.rb index bcc4a21c..375ccfa7 100644 --- a/test/integration/document_test.rb +++ b/test/integration/document_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class DocumentTest < Minitest::Test diff --git a/test/integration/drop_test.rb b/test/integration/drop_test.rb index e5405599..3fe61750 100644 --- a/test/integration/drop_test.rb +++ b/test/integration/drop_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ContextDrop < Liquid::Drop @@ -31,7 +33,7 @@ class ProductDrop < Liquid::Drop class CatchallDrop < Liquid::Drop def liquid_method_missing(method) - 'catchall_method: ' << method.to_s + "catchall_method: #{method}" end end @@ -48,7 +50,7 @@ class ProductDrop < Liquid::Drop end def user_input - "foo".taint + (+"foo").taint end protected diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index 875f4267..265632cb 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ErrorHandlingTest < Minitest::Test diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb index 0af29efb..270477e6 100644 --- a/test/integration/filter_test.rb +++ b/test/integration/filter_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module MoneyFilter diff --git a/test/integration/hash_ordering_test.rb b/test/integration/hash_ordering_test.rb index 8592395a..27d0b9b9 100644 --- a/test/integration/hash_ordering_test.rb +++ b/test/integration/hash_ordering_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class HashOrderingTest < Minitest::Test diff --git a/test/integration/output_test.rb b/test/integration/output_test.rb index d94b0f89..687cad87 100644 --- a/test/integration/output_test.rb +++ b/test/integration/output_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module FunnyFilter diff --git a/test/integration/parsing_quirks_test.rb b/test/integration/parsing_quirks_test.rb index 29cb6d6d..c210b486 100644 --- a/test/integration/parsing_quirks_test.rb +++ b/test/integration/parsing_quirks_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ParsingQuirksTest < Minitest::Test diff --git a/test/integration/render_profiling_test.rb b/test/integration/render_profiling_test.rb index 283b8bdd..753b2be2 100644 --- a/test/integration/render_profiling_test.rb +++ b/test/integration/render_profiling_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class RenderProfilingTest < Minitest::Test diff --git a/test/integration/security_test.rb b/test/integration/security_test.rb index f603ff08..28e9d392 100644 --- a/test/integration/security_test.rb +++ b/test/integration/security_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module SecurityFilter diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 7863cfeb..bf285399 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +# frozen_string_literal: true require 'test_helper' diff --git a/test/integration/tags/break_tag_test.rb b/test/integration/tags/break_tag_test.rb index 0fbde837..c3a46791 100644 --- a/test/integration/tags/break_tag_test.rb +++ b/test/integration/tags/break_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class BreakTagTest < Minitest::Test diff --git a/test/integration/tags/continue_tag_test.rb b/test/integration/tags/continue_tag_test.rb index ce4c158b..00cca17c 100644 --- a/test/integration/tags/continue_tag_test.rb +++ b/test/integration/tags/continue_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ContinueTagTest < Minitest::Test diff --git a/test/integration/tags/echo_test.rb b/test/integration/tags/echo_test.rb index eab329db..c64932e3 100644 --- a/test/integration/tags/echo_test.rb +++ b/test/integration/tags/echo_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class EchoTest < Minitest::Test diff --git a/test/integration/tags/for_tag_test.rb b/test/integration/tags/for_tag_test.rb index 47e3e5f7..667efac0 100644 --- a/test/integration/tags/for_tag_test.rb +++ b/test/integration/tags/for_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ThingWithValue < Liquid::Drop diff --git a/test/integration/tags/if_else_tag_test.rb b/test/integration/tags/if_else_tag_test.rb index 276b15b7..d54b2fb8 100644 --- a/test/integration/tags/if_else_tag_test.rb +++ b/test/integration/tags/if_else_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class IfElseTagTest < Minitest::Test diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index dea932a0..45410a7a 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TestFileSystem diff --git a/test/integration/tags/increment_tag_test.rb b/test/integration/tags/increment_tag_test.rb index a793b6d2..d561a1b5 100644 --- a/test/integration/tags/increment_tag_test.rb +++ b/test/integration/tags/increment_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class IncrementTagTest < Minitest::Test diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb index 628eb85e..b5f6b492 100644 --- a/test/integration/tags/liquid_tag_test.rb +++ b/test/integration/tags/liquid_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class LiquidTagTest < Minitest::Test diff --git a/test/integration/tags/raw_tag_test.rb b/test/integration/tags/raw_tag_test.rb index 634d052d..461e5bfc 100644 --- a/test/integration/tags/raw_tag_test.rb +++ b/test/integration/tags/raw_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class RawTagTest < Minitest::Test diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index ea144771..154783ad 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class RenderTagTest < Minitest::Test diff --git a/test/integration/tags/standard_tag_test.rb b/test/integration/tags/standard_tag_test.rb index cee4caca..7939cd36 100644 --- a/test/integration/tags/standard_tag_test.rb +++ b/test/integration/tags/standard_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class StandardTagTest < Minitest::Test diff --git a/test/integration/tags/statements_test.rb b/test/integration/tags/statements_test.rb index eeff166f..0d024d0f 100644 --- a/test/integration/tags/statements_test.rb +++ b/test/integration/tags/statements_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class StatementsTest < Minitest::Test diff --git a/test/integration/tags/table_row_test.rb b/test/integration/tags/table_row_test.rb index d7bc14cf..71df4f38 100644 --- a/test/integration/tags/table_row_test.rb +++ b/test/integration/tags/table_row_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TableRowTest < Minitest::Test diff --git a/test/integration/tags/unless_else_tag_test.rb b/test/integration/tags/unless_else_tag_test.rb index c414a715..469d1c0b 100644 --- a/test/integration/tags/unless_else_tag_test.rb +++ b/test/integration/tags/unless_else_tag_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class UnlessElseTagTest < Minitest::Test diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index e2b54c79..75dd95b4 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' require 'timeout' diff --git a/test/integration/trim_mode_test.rb b/test/integration/trim_mode_test.rb index 4e35deee..438f86b5 100644 --- a/test/integration/trim_mode_test.rb +++ b/test/integration/trim_mode_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TrimModeTest < Minitest::Test diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb index 244ba95e..94ed1eca 100644 --- a/test/integration/variable_test.rb +++ b/test/integration/variable_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class VariableTest < Minitest::Test diff --git a/test/test_helper.rb b/test/test_helper.rb index defc67ec..d7a66413 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true ENV["MT_NO_EXPECTATIONS"] = "1" require 'minitest/autorun' diff --git a/test/unit/block_unit_test.rb b/test/unit/block_unit_test.rb index 9f7b94f8..fa06a877 100644 --- a/test/unit/block_unit_test.rb +++ b/test/unit/block_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class BlockUnitTest < Minitest::Test @@ -61,7 +63,7 @@ class BlockUnitTest < Minitest::Test assert_equal 'hello', template.render - buf = '' + buf = +'' output = template.render({}, output: buf) assert_equal 'hello', output assert_equal 'hello', buf @@ -79,7 +81,7 @@ class BlockUnitTest < Minitest::Test assert_equal 'foohellobar', template.render - buf = '' + buf = +'' output = template.render({}, output: buf) assert_equal 'foohellobar', output assert_equal 'foohellobar', buf diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index 165a7cf9..69f6b902 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ConditionUnitTest < Minitest::Test diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index 6d7042c8..67a8c916 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class HundredCentes diff --git a/test/unit/file_system_unit_test.rb b/test/unit/file_system_unit_test.rb index 2c7250ba..c76a7edd 100644 --- a/test/unit/file_system_unit_test.rb +++ b/test/unit/file_system_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class FileSystemUnitTest < Minitest::Test diff --git a/test/unit/i18n_unit_test.rb b/test/unit/i18n_unit_test.rb index b57500e3..338787e3 100644 --- a/test/unit/i18n_unit_test.rb +++ b/test/unit/i18n_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class I18nUnitTest < Minitest::Test diff --git a/test/unit/lexer_unit_test.rb b/test/unit/lexer_unit_test.rb index 5adcf2bd..7a2a4a56 100644 --- a/test/unit/lexer_unit_test.rb +++ b/test/unit/lexer_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class LexerUnitTest < Minitest::Test diff --git a/test/unit/parser_unit_test.rb b/test/unit/parser_unit_test.rb index 9f23337a..7456bf30 100644 --- a/test/unit/parser_unit_test.rb +++ b/test/unit/parser_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ParserUnitTest < Minitest::Test diff --git a/test/unit/partial_cache_unit_test.rb b/test/unit/partial_cache_unit_test.rb index 5778efb5..dd431853 100644 --- a/test/unit/partial_cache_unit_test.rb +++ b/test/unit/partial_cache_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class PartialCacheUnitTest < Minitest::Test diff --git a/test/unit/regexp_unit_test.rb b/test/unit/regexp_unit_test.rb index 0821229f..666bc664 100644 --- a/test/unit/regexp_unit_test.rb +++ b/test/unit/regexp_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class RegexpUnitTest < Minitest::Test diff --git a/test/unit/strainer_unit_test.rb b/test/unit/strainer_unit_test.rb index 5ae2660f..2fb9ad44 100644 --- a/test/unit/strainer_unit_test.rb +++ b/test/unit/strainer_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class StrainerUnitTest < Minitest::Test diff --git a/test/unit/tag_unit_test.rb b/test/unit/tag_unit_test.rb index a3fb40e3..c9543e92 100644 --- a/test/unit/tag_unit_test.rb +++ b/test/unit/tag_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TagUnitTest < Minitest::Test @@ -31,7 +33,7 @@ class TagUnitTest < Minitest::Test assert_equal 'hello', template.render - buf = '' + buf = +'' output = template.render({}, output: buf) assert_equal 'hello', output assert_equal 'hello', buf @@ -49,7 +51,7 @@ class TagUnitTest < Minitest::Test assert_equal 'foohellobar', template.render - buf = '' + buf = +'' output = template.render({}, output: buf) assert_equal 'foohellobar', output assert_equal 'foohellobar', buf diff --git a/test/unit/tags/case_tag_unit_test.rb b/test/unit/tags/case_tag_unit_test.rb index 71103081..0f3a61f7 100644 --- a/test/unit/tags/case_tag_unit_test.rb +++ b/test/unit/tags/case_tag_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class CaseTagUnitTest < Minitest::Test diff --git a/test/unit/tags/for_tag_unit_test.rb b/test/unit/tags/for_tag_unit_test.rb index b8fc5206..e6306c3a 100644 --- a/test/unit/tags/for_tag_unit_test.rb +++ b/test/unit/tags/for_tag_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class ForTagUnitTest < Minitest::Test diff --git a/test/unit/tags/if_tag_unit_test.rb b/test/unit/tags/if_tag_unit_test.rb index 71408b3e..32243b73 100644 --- a/test/unit/tags/if_tag_unit_test.rb +++ b/test/unit/tags/if_tag_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class IfTagUnitTest < Minitest::Test diff --git a/test/unit/template_unit_test.rb b/test/unit/template_unit_test.rb index 6328be53..bc02896d 100644 --- a/test/unit/template_unit_test.rb +++ b/test/unit/template_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TemplateUnitTest < Minitest::Test diff --git a/test/unit/tokenizer_unit_test.rb b/test/unit/tokenizer_unit_test.rb index de84c1fb..d094aa1f 100644 --- a/test/unit/tokenizer_unit_test.rb +++ b/test/unit/tokenizer_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class TokenizerTest < Minitest::Test diff --git a/test/unit/variable_unit_test.rb b/test/unit/variable_unit_test.rb index 5a21ace8..da1d4ea3 100644 --- a/test/unit/variable_unit_test.rb +++ b/test/unit/variable_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class VariableUnitTest < Minitest::Test From d8403af515bfe929d616044f87e0e48a4fae6c2b Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Wed, 18 Sep 2019 13:25:55 +1000 Subject: [PATCH 66/74] Reimplementation of Static Registers (#1157) --- lib/liquid.rb | 1 + lib/liquid/context.rb | 9 +- lib/liquid/static_registers.rb | 34 ++++ test/unit/context_unit_test.rb | 16 +- test/unit/static_registers_unit_test.rb | 209 ++++++++++++++++++++++++ 5 files changed, 260 insertions(+), 9 deletions(-) create mode 100644 lib/liquid/static_registers.rb create mode 100644 test/unit/static_registers_unit_test.rb diff --git a/lib/liquid.rb b/lib/liquid.rb index 66895663..d102530c 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -78,6 +78,7 @@ require 'liquid/tokenizer' require 'liquid/parse_context' require 'liquid/partial_cache' require 'liquid/usage' +require 'liquid/static_registers' # Load all the tags of the standard library # diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 7e4350aa..88e46713 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -18,18 +18,17 @@ module Liquid attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters # rubocop:disable Metrics/ParameterLists - def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}, static_environments: {}) - new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers, static_environments) + def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}) + new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments) end - def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {}) + def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}) @environments = [environments] @environments.flatten! @static_environments = [static_environments].flat_map(&:freeze).freeze @scopes = [(outer_scope || {})] @registers = registers - @static_registers = static_registers.freeze @errors = [] @partial = false @strict_variables = false @@ -137,7 +136,7 @@ module Liquid Context.build( resource_limits: resource_limits, static_environments: static_environments, - static_registers: static_registers + registers: StaticRegisters.new(registers) ).tap do |subcontext| subcontext.base_scope_depth = base_scope_depth + 1 subcontext.exception_renderer = exception_renderer diff --git a/lib/liquid/static_registers.rb b/lib/liquid/static_registers.rb new file mode 100644 index 00000000..b05056d7 --- /dev/null +++ b/lib/liquid/static_registers.rb @@ -0,0 +1,34 @@ +module Liquid + class StaticRegisters + attr_reader :static_registers, :registers + + def initialize(registers = {}) + @static_registers = registers.is_a?(StaticRegisters) ? registers.static_registers : registers.freeze + @registers = {} + end + + def []=(key, value) + @registers[key] = value + end + + def [](key) + if @registers.key?(key) + @registers[key] + else + @static_registers[key] + end + end + + def delete(key) + @registers.delete(key) + end + + def fetch(key, default = nil) + key?(key) ? self[key] : default + end + + def key?(key) + @registers.key?(key) || @static_registers.key?(key) + end + end +end diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index 67a8c916..fe790cfb 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -518,15 +518,23 @@ class ContextUnitTest < Minitest::Test registers = { my_register: :my_value, } - super_context = Context.new({}, {}, registers) + super_context = Context.new({}, {}, StaticRegisters.new(registers)) + super_context.registers[:my_register] = :my_alt_value subcontext = super_context.new_isolated_subcontext - assert_nil subcontext.registers[:my_register] + assert_equal :my_value, subcontext.registers[:my_register] end def test_new_isolated_subcontext_inherits_static_registers - super_context = Context.build(static_registers: { my_register: :my_value }) + super_context = Context.build(registers: { my_register: :my_value }) subcontext = super_context.new_isolated_subcontext - assert_equal :my_value, subcontext.static_registers[:my_register] + assert_equal :my_value, subcontext.registers[:my_register] + end + + def test_new_isolated_subcontext_registers_do_not_pollute_context + super_context = Context.build(registers: { my_register: :my_value }) + subcontext = super_context.new_isolated_subcontext + subcontext.registers[:my_register] = :my_alt_value + assert_equal :my_value, super_context.registers[:my_register] end def test_new_isolated_subcontext_inherits_filters diff --git a/test/unit/static_registers_unit_test.rb b/test/unit/static_registers_unit_test.rb new file mode 100644 index 00000000..389cb3fb --- /dev/null +++ b/test/unit/static_registers_unit_test.rb @@ -0,0 +1,209 @@ +require 'test_helper' + +class StaticRegistersUnitTest < Minitest::Test + include Liquid + + def set + static_register = StaticRegisters.new + static_register[nil] = true + static_register[1] = :one + static_register[:one] = "one" + static_register["two"] = "three" + static_register["two"] = 3 + static_register[false] = nil + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.registers) + + static_register + end + + def test_get + static_register = set + + assert_equal true, static_register[nil] + assert_equal :one, static_register[1] + assert_equal "one", static_register[:one] + assert_equal 3, static_register["two"] + assert_nil static_register[false] + assert_nil static_register["unknown"] + end + + def test_delete + static_register = set + + assert_equal true, static_register.delete(nil) + assert_equal :one, static_register.delete(1) + assert_equal "one", static_register.delete(:one) + assert_equal 3, static_register.delete("two") + assert_nil static_register.delete(false) + assert_nil static_register.delete("unknown") + + assert_equal({}, static_register.registers) + end + + def test_fetch + static_register = set + + assert_equal true, static_register.fetch(nil) + assert_equal :one, static_register.fetch(1) + assert_equal "one", static_register.fetch(:one) + assert_equal 3, static_register.fetch("two") + assert_nil static_register.fetch(false) + assert_nil static_register.fetch("unknown") + end + + def test_fetch_default + static_register = StaticRegisters.new + + assert_equal true, static_register.fetch(nil, true) + assert_equal :one, static_register.fetch(1, :one) + assert_equal "one", static_register.fetch(:one, "one") + assert_equal 3, static_register.fetch("two", 3) + assert_nil static_register.fetch(false, nil) + end + + def test_key + static_register = set + + assert_equal true, static_register.key?(nil) + assert_equal true, static_register.key?(1) + assert_equal true, static_register.key?(:one) + assert_equal true, static_register.key?("two") + assert_equal true, static_register.key?(false) + assert_equal false, static_register.key?("unknown") + assert_equal false, static_register.key?(true) + end + + def set_with_static + static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil) + static_register[nil] = false + static_register["two"] = 4 + static_register[true] = "foo" + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + assert_equal({ nil => false, "two" => 4, true => "foo" }, static_register.registers) + + static_register + end + + def test_get_with_static + static_register = set_with_static + + assert_equal false, static_register[nil] + assert_equal :one, static_register[1] + assert_equal "one", static_register[:one] + assert_equal 4, static_register["two"] + assert_equal "foo", static_register[true] + assert_nil static_register[false] + end + + def test_delete_with_static + static_register = set_with_static + + assert_equal false, static_register.delete(nil) + assert_equal 4, static_register.delete("two") + assert_equal "foo", static_register.delete(true) + assert_nil static_register.delete("unknown") + assert_nil static_register.delete(:one) + + assert_equal({}, static_register.registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + end + + def test_fetch_with_static + static_register = set_with_static + + assert_equal false, static_register.fetch(nil) + assert_equal :one, static_register.fetch(1) + assert_equal "one", static_register.fetch(:one) + assert_equal 4, static_register.fetch("two") + assert_equal "foo", static_register.fetch(true) + assert_nil static_register.fetch(false) + end + + def test_key_with_static + static_register = set_with_static + + assert_equal true, static_register.key?(nil) + assert_equal true, static_register.key?(1) + assert_equal true, static_register.key?(:one) + assert_equal true, static_register.key?("two") + assert_equal true, static_register.key?(false) + assert_equal false, static_register.key?("unknown") + assert_equal true, static_register.key?(true) + end + + def test_static_register_frozen + static_register = set_with_static + + static = static_register.static_registers + + assert_raises(RuntimeError) do + static["two"] = "foo" + end + + assert_raises(RuntimeError) do + static["unknown"] = "foo" + end + + assert_raises(RuntimeError) do + static.delete("two") + end + end + + def test_new_static_retains_static + static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil) + static_register["one"] = 1 + static_register["two"] = 2 + static_register["three"] = 3 + + new_register = StaticRegisters.new(static_register) + assert_equal({}, new_register.registers) + + new_register["one"] = 4 + new_register["two"] = 5 + new_register["three"] = 6 + + newest_register = StaticRegisters.new(new_register) + assert_equal({}, newest_register.registers) + + newest_register["one"] = 7 + newest_register["two"] = 8 + newest_register["three"] = 9 + + assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers) + assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers) + assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static_registers) + end + + def test_multiple_instances_are_unique + static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil) + static_register["one"] = 1 + static_register["two"] = 2 + static_register["three"] = 3 + + new_register = StaticRegisters.new(foo: :bar) + assert_equal({}, new_register.registers) + + new_register["one"] = 4 + new_register["two"] = 5 + new_register["three"] = 6 + + newest_register = StaticRegisters.new(bar: :foo) + assert_equal({}, newest_register.registers) + + newest_register["one"] = 7 + newest_register["two"] = 8 + newest_register["three"] = 9 + + assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers) + assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers) + assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + assert_equal({ foo: :bar }, new_register.static_registers) + assert_equal({ bar: :foo }, newest_register.static_registers) + end +end From adb40c41b7177fe0f84358ee50558126d1444214 Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Wed, 18 Sep 2019 13:40:07 +1000 Subject: [PATCH 67/74] Enable frozen_string_literal --- lib/liquid/static_registers.rb | 2 ++ test/unit/static_registers_unit_test.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/liquid/static_registers.rb b/lib/liquid/static_registers.rb index b05056d7..6f22f705 100644 --- a/lib/liquid/static_registers.rb +++ b/lib/liquid/static_registers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Liquid class StaticRegisters attr_reader :static_registers, :registers diff --git a/test/unit/static_registers_unit_test.rb b/test/unit/static_registers_unit_test.rb index 389cb3fb..0db80cdd 100644 --- a/test/unit/static_registers_unit_test.rb +++ b/test/unit/static_registers_unit_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class StaticRegistersUnitTest < Minitest::Test From ef133435913436c491f8dfddeb6ab56dedcd3c77 Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Fri, 20 Sep 2019 00:24:48 +1000 Subject: [PATCH 68/74] Changes static registers to not be frozen (#1163) * Changes static registers to not be frozen * Add frozen test to static registers --- lib/liquid/static_registers.rb | 8 ++-- test/unit/static_registers_unit_test.rb | 57 ++++++++++++++++++++----- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/liquid/static_registers.rb b/lib/liquid/static_registers.rb index 6f22f705..06c52ab0 100644 --- a/lib/liquid/static_registers.rb +++ b/lib/liquid/static_registers.rb @@ -2,10 +2,10 @@ module Liquid class StaticRegisters - attr_reader :static_registers, :registers + attr_reader :static, :registers def initialize(registers = {}) - @static_registers = registers.is_a?(StaticRegisters) ? registers.static_registers : registers.freeze + @static = registers.is_a?(StaticRegisters) ? registers.static : registers @registers = {} end @@ -17,7 +17,7 @@ module Liquid if @registers.key?(key) @registers[key] else - @static_registers[key] + @static[key] end end @@ -30,7 +30,7 @@ module Liquid end def key?(key) - @registers.key?(key) || @static_registers.key?(key) + @registers.key?(key) || @static.key?(key) end end end diff --git a/test/unit/static_registers_unit_test.rb b/test/unit/static_registers_unit_test.rb index 0db80cdd..125440f2 100644 --- a/test/unit/static_registers_unit_test.rb +++ b/test/unit/static_registers_unit_test.rb @@ -82,7 +82,7 @@ class StaticRegistersUnitTest < Minitest::Test static_register["two"] = 4 static_register[true] = "foo" - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static) assert_equal({ nil => false, "two" => 4, true => "foo" }, static_register.registers) static_register @@ -109,7 +109,7 @@ class StaticRegistersUnitTest < Minitest::Test assert_nil static_register.delete(:one) assert_equal({}, static_register.registers) - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static) end def test_fetch_with_static @@ -135,10 +135,10 @@ class StaticRegistersUnitTest < Minitest::Test assert_equal true, static_register.key?(true) end - def test_static_register_frozen + def test_static_register_can_be_frozen static_register = set_with_static - static = static_register.static_registers + static = static_register.static.freeze assert_raises(RuntimeError) do static["two"] = "foo" @@ -176,9 +176,9 @@ class StaticRegistersUnitTest < Minitest::Test assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers) assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers) assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers) - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static_registers) - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static) end def test_multiple_instances_are_unique @@ -204,8 +204,45 @@ class StaticRegistersUnitTest < Minitest::Test assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers) assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers) assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers) - assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static_registers) - assert_equal({ foo: :bar }, new_register.static_registers) - assert_equal({ bar: :foo }, newest_register.static_registers) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static) + assert_equal({ foo: :bar }, new_register.static) + assert_equal({ bar: :foo }, newest_register.static) + end + + def test_can_update_static_directly_and_updates_all_instances + static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil) + static_register["one"] = 1 + static_register["two"] = 2 + static_register["three"] = 3 + + new_register = StaticRegisters.new(static_register) + assert_equal({}, new_register.registers) + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static) + + new_register["one"] = 4 + new_register["two"] = 5 + new_register["three"] = 6 + new_register.static["four"] = 10 + + newest_register = StaticRegisters.new(new_register) + assert_equal({}, newest_register.registers) + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 10 }, new_register.static) + + newest_register["one"] = 7 + newest_register["two"] = 8 + newest_register["three"] = 9 + new_register.static["four"] = 5 + new_register.static["five"] = 15 + + assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers) + assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers) + assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers) + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, newest_register.static) + + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, static_register.static) + assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, new_register.static) end end From ca207ed93f03c86f2cbcd348a25eaea6f5e7cd89 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Thu, 19 Sep 2019 20:25:01 +0530 Subject: [PATCH 69/74] Cleanup RuboCop configuration file (#1161) --- .rubocop.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6b9aa9fe..b2316249 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ inherit_from: - - https://shopify.github.io/ruby-style-guide/rubocop.yml + - 'https://shopify.github.io/ruby-style-guide/rubocop.yml' - .rubocop_todo.yml require: rubocop-performance @@ -10,7 +10,7 @@ Performance: AllCops: Exclude: - 'vendor/bundle/**/*' - + Naming/MethodName: Exclude: - - 'example/server/liquid_servlet.rb' \ No newline at end of file + - 'example/server/liquid_servlet.rb' From 2c14e0b2ba0ce1a4f4b940175f1962a027ab77bd Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Thu, 19 Sep 2019 21:37:52 +0530 Subject: [PATCH 70/74] Use `Regexp#match?` when `MatchData` is not used (#1165) * Use `Regexp#match?` when `MatchData` is not used * Add `TargetRubyVersion: 2.4` to RuboCop config --- .rubocop.yml | 1 + lib/liquid/file_system.rb | 2 +- lib/liquid/tags/raw.rb | 2 +- lib/liquid/utils.rb | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index b2316249..1c0f8324 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,6 +8,7 @@ Performance: Enabled: true AllCops: + TargetRubyVersion: 2.4 Exclude: - 'vendor/bundle/**/*' diff --git a/lib/liquid/file_system.rb b/lib/liquid/file_system.rb index b2093aee..27ab6325 100644 --- a/lib/liquid/file_system.rb +++ b/lib/liquid/file_system.rb @@ -59,7 +59,7 @@ module Liquid end def full_path(template_path) - raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ %r{\A[^./][a-zA-Z0-9_/]+\z} + raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path) full_path = if template_path.include?('/') File.join(root, File.dirname(template_path), @pattern % File.basename(template_path)) diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index 093a37e1..fde3ee10 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -40,7 +40,7 @@ module Liquid protected def ensure_valid_markup(tag_name, markup, parse_context) - unless markup =~ Syntax + unless Syntax.match?(markup) raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name) end end diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb index 406d667b..709fb002 100644 --- a/lib/liquid/utils.rb +++ b/lib/liquid/utils.rb @@ -52,7 +52,7 @@ module Liquid when Numeric obj when String - obj.strip =~ /\A-?\d+\.\d+\z/ ? BigDecimal(obj) : obj.to_i + /\A-?\d+\.\d+\z/.match?(obj.strip) ? BigDecimal(obj) : obj.to_i else if obj.respond_to?(:to_number) obj.to_number From b667bcb48b26dab6c87827bd23ea99c6216bc7b4 Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Fri, 20 Sep 2019 02:08:11 +1000 Subject: [PATCH 71/74] Shopify stye guide fixes (#1160) --- .rubocop_todo.yml | 48 +------------------------ lib/liquid/block_body.rb | 10 +++--- lib/liquid/lexer.rb | 20 +++++++---- lib/liquid/parse_context.rb | 1 - lib/liquid/standardfilters.rb | 2 +- lib/liquid/tags/for.rb | 2 +- lib/liquid/tags/if.rb | 4 +-- lib/liquid/tags/raw.rb | 2 +- lib/liquid/variable.rb | 2 +- performance/profile.rb | 2 +- performance/shopify/database.rb | 4 +-- test/integration/error_handling_test.rb | 10 ++++-- test/integration/template_test.rb | 20 ++++++++--- test/test_helper.rb | 2 +- test/unit/condition_unit_test.rb | 4 +-- test/unit/context_unit_test.rb | 20 ++++++++--- test/unit/tokenizer_unit_test.rb | 2 +- 17 files changed, 72 insertions(+), 83 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 12716289..34a2e244 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,26 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -Lint/AmbiguousOperator: - Exclude: - - 'test/unit/condition_unit_test.rb' - -# Offense count: 21 -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: - Exclude: - - 'lib/liquid/block_body.rb' - - 'lib/liquid/lexer.rb' - - 'lib/liquid/standardfilters.rb' - - 'lib/liquid/tags/for.rb' - - 'lib/liquid/tags/if.rb' - - 'lib/liquid/tags/raw.rb' - - 'lib/liquid/variable.rb' - - 'performance/profile.rb' - - 'test/test_helper.rb' - - 'test/unit/tokenizer_unit_test.rb' - # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -34,17 +14,6 @@ Lint/InheritException: Exclude: - 'lib/liquid/interrupts.rb' -# Offense count: 2 -Lint/UselessAssignment: - Exclude: - - 'performance/shopify/database.rb' - -# Offense count: 1 -# Configuration parameters: CheckForMethodsWithNoSideEffects. -Lint/Void: - Exclude: - - 'lib/liquid/parse_context.rb' - # Offense count: 98 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. @@ -76,19 +45,4 @@ Style/ClassVars: Exclude: - 'lib/liquid/condition.rb' - 'lib/liquid/strainer.rb' - - 'lib/liquid/template.rb' - -# Offense count: 1 -# Configuration parameters: AllowCoercion. -Style/DateTime: - Exclude: - - 'test/unit/context_unit_test.rb' - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Exclude: - - 'test/integration/error_handling_test.rb' - - 'test/integration/template_test.rb' - - 'test/unit/context_unit_test.rb' + - 'lib/liquid/template.rb' \ No newline at end of file diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index c4ce2671..9400e383 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -27,7 +27,7 @@ module Liquid end private def parse_for_liquid_tag(tokenizer, parse_context) - while token = tokenizer.shift + while (token = tokenizer.shift) unless token.empty? || token =~ WhitespaceOrNothing unless token =~ LiquidTagToken # line isn't empty but didn't match tag syntax, yield and let the @@ -36,7 +36,7 @@ module Liquid end tag_name = Regexp.last_match(1) markup = Regexp.last_match(2) - unless tag = registered_tags[tag_name] + unless (tag = registered_tags[tag_name]) # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed return yield tag_name, markup @@ -52,7 +52,7 @@ module Liquid end private def parse_for_document(tokenizer, parse_context, &block) - while token = tokenizer.shift + while (token = tokenizer.shift) next if token.empty? case when token.start_with?(TAGSTART) @@ -74,7 +74,7 @@ module Liquid next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block) end - unless tag = registered_tags[tag_name] + unless (tag = registered_tags[tag_name]) # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed return yield tag_name, markup @@ -122,7 +122,7 @@ module Liquid context.resource_limits.render_score += @nodelist.length idx = 0 - while node = @nodelist[idx] + while (node = @nodelist[idx]) previous_output_size = output.bytesize case node diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb index 04e0c116..a251c3e5 100644 --- a/lib/liquid/lexer.rb +++ b/lib/liquid/lexer.rb @@ -33,15 +33,21 @@ module Liquid until @ss.eos? @ss.skip(WHITESPACE_OR_NOTHING) break if @ss.eos? - tok = if t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] - elsif t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] - elsif t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t] - elsif t = @ss.scan(NUMBER_LITERAL) then [:number, t] - elsif t = @ss.scan(IDENTIFIER) then [:id, t] - elsif t = @ss.scan(DOTDOT) then [:dotdot, t] + tok = if (t = @ss.scan(COMPARISON_OPERATOR)) + [:comparison, t] + elsif (t = @ss.scan(SINGLE_STRING_LITERAL)) + [:string, t] + elsif (t = @ss.scan(DOUBLE_STRING_LITERAL)) + [:string, t] + elsif (t = @ss.scan(NUMBER_LITERAL)) + [:number, t] + elsif (t = @ss.scan(IDENTIFIER)) + [:id, t] + elsif (t = @ss.scan(DOTDOT)) + [:dotdot, t] else c = @ss.getch - if s = SPECIALS[c] + if (s = SPECIALS[c]) [s, c] else raise SyntaxError, "Unexpected character #{c}" diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb index 2da3ad75..4afdbe59 100644 --- a/lib/liquid/parse_context.rb +++ b/lib/liquid/parse_context.rb @@ -21,7 +21,6 @@ module Liquid @partial = value @options = value ? partial_options : @template_options @error_mode = @options[:error_mode] || Template.error_mode - value end def partial_options diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 517857ac..6855cd21 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -327,7 +327,7 @@ module Liquid def date(input, format) return input if format.to_s.empty? - return input unless date = Utils.to_date(input) + return input unless (date = Utils.to_date(input)) date.strftime(format.to_s) end diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index d9613697..5c7b5601 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -111,7 +111,7 @@ module Liquid @reversed = p.id?('reversed') while p.look(:id) && p.look(:colon, 1) - unless attribute = p.id?('limit') || p.id?('offset') + unless (attribute = p.id?('limit') || p.id?('offset')) raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute") end p.consume diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index c3d1a777..b68e309a 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -94,7 +94,7 @@ module Liquid def parse_binary_comparisons(p) condition = parse_comparison(p) first_condition = condition - while op = (p.id?('and') || p.id?('or')) + while (op = (p.id?('and') || p.id?('or'))) child_condition = parse_comparison(p) condition.send(op, child_condition) condition = child_condition @@ -104,7 +104,7 @@ module Liquid def parse_comparison(p) a = Expression.parse(p.expression) - if op = p.consume?(:comparison) + if (op = p.consume?(:comparison)) b = Expression.parse(p.expression) Condition.new(a, op, b) else diff --git a/lib/liquid/tags/raw.rb b/lib/liquid/tags/raw.rb index fde3ee10..e4a78a81 100644 --- a/lib/liquid/tags/raw.rb +++ b/lib/liquid/tags/raw.rb @@ -13,7 +13,7 @@ module Liquid def parse(tokens) @body = +'' - while token = tokens.shift + while (token = tokens.shift) if token =~ FullTokenPossiblyInvalid @body << Regexp.last_match(1) if Regexp.last_match(1) != "" return if block_delimiter == Regexp.last_match(2) diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 2fc2ea85..265748d3 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -110,7 +110,7 @@ module Liquid filter_args = [] keyword_args = nil unparsed_args.each do |a| - if matches = a.match(JustTagAttributes) + if (matches = a.match(JustTagAttributes)) keyword_args ||= {} keyword_args[matches[1]] = Expression.parse(matches[2]) else diff --git a/performance/profile.rb b/performance/profile.rb index 101f6e5d..70740778 100644 --- a/performance/profile.rb +++ b/performance/profile.rb @@ -15,7 +15,7 @@ profiler.run end end - if profile_type == :cpu && graph_filename = ENV['GRAPH_FILENAME'] + if profile_type == :cpu && (graph_filename = ENV['GRAPH_FILENAME']) File.open(graph_filename, 'w') do |f| StackProf::Report.new(results).print_graphviz(nil, f) end diff --git a/performance/shopify/database.rb b/performance/shopify/database.rb index 9836cd46..2db6d300 100644 --- a/performance/shopify/database.rb +++ b/performance/shopify/database.rb @@ -32,8 +32,8 @@ module Database db['article'] = db['blog']['articles'].first db['cart'] = { - 'total_price' => db['line_items'].values.inject(0) { |sum, item| sum += item['line_price'] * item['quantity'] }, - 'item_count' => db['line_items'].values.inject(0) { |sum, item| sum += item['quantity'] }, + 'total_price' => db['line_items'].values.inject(0) { |sum, item| sum + item['line_price'] * item['quantity'] }, + 'item_count' => db['line_items'].values.inject(0) { |sum, item| sum + item['quantity'] }, 'items' => db['line_items'].values, } diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb index 265632cb..7abaec04 100644 --- a/test/integration/error_handling_test.rb +++ b/test/integration/error_handling_test.rb @@ -211,7 +211,10 @@ class ErrorHandlingTest < Minitest::Test def test_setting_default_exception_renderer old_exception_renderer = Liquid::Template.default_exception_renderer exceptions = [] - Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' } + Liquid::Template.default_exception_renderer = ->(e) { + exceptions << e + '' + } template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}') output = template.render('errors' => ErrorDrop.new) @@ -225,7 +228,10 @@ class ErrorHandlingTest < Minitest::Test def test_exception_renderer_exposing_non_liquid_error template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true) exceptions = [] - handler = ->(e) { exceptions << e; e.cause } + handler = ->(e) { + exceptions << e + e.cause + } output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler) diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb index 75dd95b4..48549f55 100644 --- a/test/integration/template_test.rb +++ b/test/integration/template_test.rb @@ -81,7 +81,10 @@ class TemplateTest < Minitest::Test def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders t = Template.new - t.assigns['number'] = -> { @global ||= 0; @global += 1 } + t.assigns['number'] = -> { + @global ||= 0 + @global += 1 + } assert_equal '1', t.parse("{{number}}").render! assert_equal '1', t.parse("{{number}}").render! assert_equal '1', t.render! @@ -90,7 +93,10 @@ class TemplateTest < Minitest::Test def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders t = Template.new - assigns = { 'number' => -> { @global ||= 0; @global += 1 } } + assigns = { 'number' => -> { + @global ||= 0 + @global += 1 + } } assert_equal '1', t.parse("{{number}}").render!(assigns) assert_equal '1', t.parse("{{number}}").render!(assigns) assert_equal '1', t.render!(assigns) @@ -237,7 +243,10 @@ class TemplateTest < Minitest::Test def test_exception_renderer_that_returns_string exception = nil - handler = ->(e) { exception = e; '' } + handler = ->(e) { + exception = e + '' + } output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler) @@ -248,7 +257,10 @@ class TemplateTest < Minitest::Test def test_exception_renderer_that_raises exception = nil assert_raises(Liquid::ZeroDivisionError) do - Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise }) + Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { + exception = e + raise + }) end assert exception.is_a?(Liquid::ZeroDivisionError) end diff --git a/test/test_helper.rb b/test/test_helper.rb index d7a66413..9606ef8f 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,7 +9,7 @@ require 'liquid.rb' require 'liquid/profiler' mode = :strict -if env_mode = ENV['LIQUID_PARSER_MODE'] +if (env_mode = ENV['LIQUID_PARSER_MODE']) puts "-- #{env_mode.upcase} ERROR MODE" mode = env_mode.to_sym end diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index 69f6b902..8d4e02f9 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -26,9 +26,9 @@ class ConditionUnitTest < Minitest::Test assert_evaluates_true 1, '<=', 1 # negative numbers assert_evaluates_true 1, '>', -1 - assert_evaluates_true -1, '<', 1 + assert_evaluates_true(-1, '<', 1) assert_evaluates_true 1.0, '>', -1.0 - assert_evaluates_true -1.0, '<', 1.0 + assert_evaluates_true(-1.0, '<', 1.0) end def test_default_operators_evalute_false diff --git a/test/unit/context_unit_test.rb b/test/unit/context_unit_test.rb index fe790cfb..3b460d7a 100644 --- a/test/unit/context_unit_test.rb +++ b/test/unit/context_unit_test.rb @@ -85,7 +85,7 @@ class ContextUnitTest < Minitest::Test @context['date'] = Date.today assert_equal Date.today, @context['date'] - now = DateTime.now + now = Time.now @context['datetime'] = now assert_equal now, @context['datetime'] @@ -405,7 +405,11 @@ class ContextUnitTest < Minitest::Test end def test_lambda_is_called_once - @context['callcount'] = proc { @global ||= 0; @global += 1; @global.to_s } + @context['callcount'] = proc { + @global ||= 0 + @global += 1 + @global.to_s + } assert_equal '1', @context['callcount'] assert_equal '1', @context['callcount'] @@ -415,7 +419,11 @@ class ContextUnitTest < Minitest::Test end def test_nested_lambda_is_called_once - @context['callcount'] = { "lambda" => proc { @global ||= 0; @global += 1; @global.to_s } } + @context['callcount'] = { "lambda" => proc { + @global ||= 0 + @global += 1 + @global.to_s + } } assert_equal '1', @context['callcount.lambda'] assert_equal '1', @context['callcount.lambda'] @@ -425,7 +433,11 @@ class ContextUnitTest < Minitest::Test end def test_lambda_in_array_is_called_once - @context['callcount'] = [1, 2, proc { @global ||= 0; @global += 1; @global.to_s }, 4, 5] + @context['callcount'] = [1, 2, proc { + @global ||= 0 + @global += 1 + @global.to_s + }, 4, 5] assert_equal '1', @context['callcount[2]'] assert_equal '1', @context['callcount[2]'] diff --git a/test/unit/tokenizer_unit_test.rb b/test/unit/tokenizer_unit_test.rb index d094aa1f..44342d6c 100644 --- a/test/unit/tokenizer_unit_test.rb +++ b/test/unit/tokenizer_unit_test.rb @@ -35,7 +35,7 @@ class TokenizerTest < Minitest::Test def tokenize(source) tokenizer = Liquid::Tokenizer.new(source) tokens = [] - while t = tokenizer.shift + while (t = tokenizer.shift) tokens << t end tokens From f4d134cd5cc920945e47acfcfe446492fc93bd3a Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Fri, 20 Sep 2019 02:28:43 +1000 Subject: [PATCH 72/74] Remove jruby and truffleruby testing (#1167) --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6a7db0d..f9c8c7fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ rvm: - &latest_ruby 2.6 - 2.7 - ruby-head - - jruby-head - - truffleruby matrix: include: @@ -17,8 +15,6 @@ matrix: name: Profiling Memory Usage allow_failures: - rvm: ruby-head - - rvm: jruby-head - - rvm: truffleruby branches: only: From 0db9c56f348f5325c52b1a86f71d7b9b92092569 Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Thu, 26 Sep 2019 00:18:30 +1000 Subject: [PATCH 73/74] Disable rendering of tag based on register (#1162) * Disable rendering of tag based on register * Improvements to disable tag * Resolve disbale tag tests * Test disable_tags register * disabled_tags is now always avaiable * Allow multiple tags to be disabled at once * Move disabled check to block_body * Code improvements * Remove redundant nil check * Improve disabled tag error output * Improve disable tag API * Code improvements * Switch disabled? to not mutate output * Fix array handling shortcut in disable_tags --- lib/liquid.rb | 2 ++ lib/liquid/block_body.rb | 13 ++++++- lib/liquid/locales/en.yml | 2 ++ lib/liquid/register.rb | 6 ++++ lib/liquid/registers/disabled_tags.rb | 32 +++++++++++++++++ lib/liquid/tag.rb | 20 +++++++++++ lib/liquid/tags/render.rb | 6 ++++ lib/liquid/template.rb | 18 +++++++++- lib/liquid/variable.rb | 8 +++++ .../registers/disabled_tags_test.rb | 27 ++++++++++++++ test/integration/tags/render_tag_test.rb | 29 +++++++++++---- .../unit/registers/disabled_tags_unit_test.rb | 36 +++++++++++++++++++ 12 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 lib/liquid/register.rb create mode 100644 lib/liquid/registers/disabled_tags.rb create mode 100644 test/integration/registers/disabled_tags_test.rb create mode 100644 test/unit/registers/disabled_tags_unit_test.rb diff --git a/lib/liquid.rb b/lib/liquid.rb index d102530c..cfaccee5 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -78,8 +78,10 @@ require 'liquid/tokenizer' require 'liquid/parse_context' require 'liquid/partial_cache' require 'liquid/usage' +require 'liquid/register' require 'liquid/static_registers' # Load all the tags of the standard library # Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f } +Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f } diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 9400e383..c543d822 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -154,7 +154,13 @@ module Liquid private def render_node(context, output, node) - node.render_to_output_buffer(context, output) + if node.disabled?(context) + output << node.disabled_error_message + return + end + disable_tags(context, node.disabled_tags) do + node.render_to_output_buffer(context, output) + end rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e context.handle_error(e, node.line_number) rescue ::StandardError => e @@ -162,6 +168,11 @@ module Liquid output << context.handle_error(e, line_number) end + def disable_tags(context, tags, &block) + return yield if tags.empty? + context.registers['disabled_tags'].disable(tags, &block) + end + def raise_if_resource_limits_reached(context, length) context.resource_limits.render_length += length return unless context.resource_limits.reached? diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index c0a9aff7..a26320bc 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -25,3 +25,5 @@ render: "Syntax error in tag 'render' - Template name must be a quoted string" argument: include: "Argument error in tag 'include' - Illegal template name" + disabled: + tag: "usage is not allowed in this context" diff --git a/lib/liquid/register.rb b/lib/liquid/register.rb new file mode 100644 index 00000000..92d02266 --- /dev/null +++ b/lib/liquid/register.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Liquid + class Register + end +end diff --git a/lib/liquid/registers/disabled_tags.rb b/lib/liquid/registers/disabled_tags.rb new file mode 100644 index 00000000..79b64727 --- /dev/null +++ b/lib/liquid/registers/disabled_tags.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +module Liquid + class DisabledTags < Register + def initialize + @disabled_tags = {} + end + + def disabled?(tag) + @disabled_tags.key?(tag) && @disabled_tags[tag] > 0 + end + + def disable(tags) + tags.each(&method(:increment)) + yield + ensure + tags.each(&method(:decrement)) + end + + private + + def increment(tag) + @disabled_tags[tag] ||= 0 + @disabled_tags[tag] += 1 + end + + def decrement(tag) + @disabled_tags[tag] -= 1 + end + end + + Template.add_register('disabled_tags', DisabledTags.new) +end diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index 14606391..832e32c7 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -13,7 +13,15 @@ module Liquid tag end + def disable_tags(*tags) + disabled_tags.push(*tags) + end + private :new + + def disabled_tags + @disabled_tags ||= [] + end end def initialize(tag_name, markup, parse_context) @@ -38,6 +46,14 @@ module Liquid '' end + def disabled?(context) + context.registers['disabled_tags'].disabled?(tag_name) + end + + def disabled_error_message + "#{tag_name} #{options[:locale].t('errors.disabled.tag')}" + end + # For backwards compatibility with custom tags. In a future release, the semantics # of the `render_to_output_buffer` method will become the default and the `render` # method will be removed. @@ -49,5 +65,9 @@ module Liquid def blank? false end + + def disabled_tags + self.class.disabled_tags + end end end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index e6c62232..1403b584 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -4,6 +4,8 @@ module Liquid class Render < Tag SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o + disable_tags "include" + attr_reader :template_name_expr, :attributes def initialize(tag_name, markup, options) @@ -22,6 +24,10 @@ module Liquid end def render_to_output_buffer(context, output) + render_tag(context, output) + end + + def render_tag(context, output) # Though we evaluate this here we will only ever parse it as a string literal. template_name = context.evaluate(@template_name_expr) raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index e77ba8aa..2f0bed4b 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -92,6 +92,14 @@ module Liquid @tags ||= TagRegistry.new end + def add_register(name, klass) + registers[name.to_s] = klass + end + + def registers + @registers ||= {} + end + def error_mode @error_mode ||= :lax end @@ -191,18 +199,26 @@ module Liquid output = nil + context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers + case args.last when Hash options = args.pop output = options[:output] if options[:output] - registers.merge!(options[:registers]) if options[:registers].is_a?(Hash) + options[:registers]&.each do |key, register| + context_register[key] = register + end apply_options_to_context(context, options) when Module, Array context.add_filters(args.pop) end + Template.registers.each do |key, register| + context_register[key] = register + end + # Retrying a render resets resource usage context.resource_limits.reset diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 265748d3..5b686e2d 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -104,6 +104,14 @@ module Liquid output end + def disabled?(_context) + false + end + + def disabled_tags + [] + end + private def parse_filter_expressions(filter_name, unparsed_args) diff --git a/test/integration/registers/disabled_tags_test.rb b/test/integration/registers/disabled_tags_test.rb new file mode 100644 index 00000000..1fb24589 --- /dev/null +++ b/test/integration/registers/disabled_tags_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'test_helper' + +class DisabledTagsTest < Minitest::Test + include Liquid + + class DisableRaw < Block + disable_tags "raw" + end + + class DisableRawEcho < Block + disable_tags "raw", "echo" + end + + def test_disables_raw + with_custom_tag('disable', DisableRaw) do + assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}' + end + end + + def test_disables_echo_and_raw + with_custom_tag('disable', DisableRawEcho) do + assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}' + end + end +end diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index 154783ad..87373a28 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -89,14 +89,12 @@ class RenderTagTest < Minitest::Test end end - def test_includes_and_renders_count_towards_the_same_recursion_limit + def test_sub_contexts_count_towards_the_same_recursion_limit Liquid::Template.file_system = StubFileSystem.new( - 'loop_render' => '{% render "loop_include" %}', - 'loop_include' => '{% include "loop_render" %}' + 'loop_render' => '{% render "loop_render" %}', ) - - assert_raises Liquid::StackLevelError do - Template.parse('{% render "loop_include" %}').render! + assert_raises Liquid::StackLevelError do + Template.parse('{% render "loop_render" %}').render! end end @@ -148,4 +146,23 @@ class RenderTagTest < Minitest::Test Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}') assert_template_result '-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}' end + + def test_includes_will_not_render_inside_render_tag + Liquid::Template.file_system = StubFileSystem.new( + 'foo' => 'bar', + 'test_include' => '{% include "foo" %}' + ) + + assert_template_result 'include usage is not allowed in this context', '{% render "test_include" %}' + end + + def test_includes_will_not_render_inside_nested_sibling_tags + Liquid::Template.file_system = StubFileSystem.new( + 'foo' => 'bar', + 'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}', + 'test_include' => '{% include "foo" %}' + ) + + assert_template_result 'include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}' + end end diff --git a/test/unit/registers/disabled_tags_unit_test.rb b/test/unit/registers/disabled_tags_unit_test.rb new file mode 100644 index 00000000..90ac0166 --- /dev/null +++ b/test/unit/registers/disabled_tags_unit_test.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'test_helper' + +class DisabledTagsUnitTest < Minitest::Test + include Liquid + + def test_disables_tag_specified + register = DisabledTags.new + register.disable(%w(foo bar)) do + assert_equal true, register.disabled?("foo") + assert_equal true, register.disabled?("bar") + assert_equal false, register.disabled?("unknown") + end + end + + def test_disables_nested_tags + register = DisabledTags.new + register.disable(["foo"]) do + register.disable(["foo"]) do + assert_equal true, register.disabled?("foo") + assert_equal false, register.disabled?("bar") + end + register.disable(["bar"]) do + assert_equal true, register.disabled?("foo") + assert_equal true, register.disabled?("bar") + register.disable(["foo"]) do + assert_equal true, register.disabled?("foo") + assert_equal true, register.disabled?("bar") + end + end + assert_equal true, register.disabled?("foo") + assert_equal false, register.disabled?("bar") + end + end +end From 1aa7d3d2bab67defbf61b0f3d014a610b233e506 Mon Sep 17 00:00:00 2001 From: Mike Angell <53470248+shopmike@users.noreply.github.com> Date: Fri, 27 Sep 2019 04:32:24 +1000 Subject: [PATCH 74/74] Change registers to by symbols (#1178) --- lib/liquid/block_body.rb | 2 +- lib/liquid/registers/disabled_tags.rb | 2 +- lib/liquid/tag.rb | 2 +- lib/liquid/template.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index c543d822..45be8b8a 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -170,7 +170,7 @@ module Liquid def disable_tags(context, tags, &block) return yield if tags.empty? - context.registers['disabled_tags'].disable(tags, &block) + context.registers[:disabled_tags].disable(tags, &block) end def raise_if_resource_limits_reached(context, length) diff --git a/lib/liquid/registers/disabled_tags.rb b/lib/liquid/registers/disabled_tags.rb index 79b64727..b1cd3bda 100644 --- a/lib/liquid/registers/disabled_tags.rb +++ b/lib/liquid/registers/disabled_tags.rb @@ -28,5 +28,5 @@ module Liquid end end - Template.add_register('disabled_tags', DisabledTags.new) + Template.add_register(:disabled_tags, DisabledTags.new) end diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb index 832e32c7..ffd22868 100644 --- a/lib/liquid/tag.rb +++ b/lib/liquid/tag.rb @@ -47,7 +47,7 @@ module Liquid end def disabled?(context) - context.registers['disabled_tags'].disabled?(tag_name) + context.registers[:disabled_tags].disabled?(tag_name) end def disabled_error_message diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 2f0bed4b..e23df246 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -93,7 +93,7 @@ module Liquid end def add_register(name, klass) - registers[name.to_s] = klass + registers[name.to_sym] = klass end def registers