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 7c18c0d..9ebb763 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 9ebb763..f14ba1d 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 9de2106..c53aae1 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 f14ba1d..34b4a49 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 c53aae1..6c91f16 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 c5dbcb8..3d939ae 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 3d939ae..d7e2dbb 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 af15e07..64abb7e 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 c5dbcb8..29c2cad 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 d39472e..75e6e8d 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 3e79849..72bd2ee 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 f6cd5fa..ee6fa76 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 453b4d6..f55aa61 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 ad116a6..6cf77a2 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 6c95624..c529aae 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 904369d..2a91741 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 a800703..a334d83 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 cfdef33..99d12ec 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 0000000..339fc2c --- /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 0000000..6254cb9 --- /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 7d9da26..eef582f 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 72bd2ee..aca9420 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 ee6fa76..53c9eb2 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 f55aa61..433d404 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 6cf77a2..aaac686 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 c529aae..68c1f92 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 2a91741..54560c2 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 a334d83..23cc591 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 99d12ec..3994553 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 339fc2c..0cebd96 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 5f88eb3..1258b2d 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 3ed4e4a..f1b7834 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 6254cb9..7bcfac8 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 0cebd96..d9d6fa8 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 eef582f..770d2f9 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 aca9420..3b51682 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 d9d6fa8..62f6c7b 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 53c9eb2..c8d0574 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 433d404..5036b27 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 aaac686..17aa860 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 68c1f92..b69aa78 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 54560c2..1451c25 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 23cc591..c9f2a28 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 3994553..7f391cf 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 1258b2d..c31bffe 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 f1b7834..8f7ad46 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 7bcfac8..4ffef08 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 4ffef08..6ad6a2d 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 62f6c7b..74f5563 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 0000000..1491d27 --- /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 4e18078..a622ef1 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 b675ba2..ee14f5c 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 e3633f5..3dba4cf 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 2dc8f3d..620f7a1 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 ee14f5c..6047258 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 c32f4bc..2709a6c 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 8860c19..bd80b38 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 1d01cdd..98be6db 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 e998d58..2671507 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 e9114df..f290744 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 6047258..e63d388 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 1451c25..02da42b 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 8f7ad46..62f4877 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 64abb7e..388a0c0 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 ba81861..b2d186c 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 23742dc..29cb6d6 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 2709a6c..0aa8bbf 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 5afa2b7..b3b90e8 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 d7e2dbb..af9b30b 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 d39472e..d7a575e 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 68ff2c5..0bddfa9 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 98818eb..6090951 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 620f7a1..91f6257 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 388a0c0..07bd0f1 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 266d8ed..ba29415 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 1d854b0..9c188d5 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 91f6257..9a82faa 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 07bd0f1..da01c47 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 a622ef1..6a306a1 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 bd80b38..6868a7a 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 bdeefac..6e3136d 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 c31bffe..717b1a2 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 bdeefac..60f1a7a 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 266d8ed..5f86545 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 48b3b1d..56c068f 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 06970c1..ac99ecd 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 c8d0574..55753d8 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 0000000..acb9ab4 --- /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 0000000..572920e --- /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 d03657e..d3fd676 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 0000000..ed5b821 --- /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 0000000..2cfd13a --- /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 0000000..412ee26 --- /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 ac5ab53..c55328d 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 572920e..0000000 --- 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 2cfd13a..56ca712 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 412ee26..0000000 --- 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 5f86545..4861bca 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 56ca712..d4be128 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 4861bca..cffb15b 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 56c068f..48b3b1d 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 ba29415..d4fab30 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 c8d0574..3b696dd 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 8674356..d5b8e29 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 d10e1c5..0dc0ae5 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 6e3136d..c7cde24 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 3dba4cf..9650abb 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 0000000..bfacde8 --- /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 00c59b2..843b6b7 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 d4fab30..f404d20 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 cb11cd7..112a7d3 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 06970c1..d9d4ff7 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 3b696dd..62a4747 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 d5b8e29..b4d418b 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 5036b27..385c878 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 c57c9cd..cb7739f 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 17aa860..cbcff79 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 b5cdaaa..9cf3073 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 b69aa78..01df484 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 02da42b..a1e4eec 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 d70cbe1..2c026f1 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 c9f2a28..28a7481 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 baa0cbb..4182c46 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 6b461bd..4f6aa15 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 7f391cf..09b69e9 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 1d4280d..8d4c32e 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 31a67e4..c22f164 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 717b1a2..5e03706 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 d661c31..ce33a2c 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 38a9a1a..875e58b 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 e9b56df..cc09781 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 9c188d5..cf83f59 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 0dc0ae5..c8b7770 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 b69aa78..f18fb71 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 cb7a822..9980e25 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 0e3e476..3a01754 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 7ac01c1..90bf6dc 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 6868a7a..4af3202 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 843b6b7..549a3a3 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 f404d20..c2478ce 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 112a7d3..cda166b 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 d9d4ff7..5099ccb 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 62a4747..767d874 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 b4d418b..d717b76 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 385c878..92b2ed0 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 cb7739f..cad3931 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 cbcff79..e42244d 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 9cf3073..08ddd4d 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 01df484..8dceab0 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 a1e4eec..25534a9 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 2c026f1..e3040ce 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 28a7481..24acf9d 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 4182c46..5af1242 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 4f6aa15..4fa75d9 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 09b69e9..9532102 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 8d4c32e..18856c3 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 c22f164..35db674 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 5e03706..cbf9986 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 ce33a2c..7b5bd53 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 875e58b..0abd11f 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 cc09781..2b46ad7 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 cf83f59..14bb8c3 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 c8b7770..0dc0ae5 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 ac5ab53..34e7553 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 6a27a7d..9f7b94f 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 c4b901b..a3fb40e 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 c7cde24..37ffe1d 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 6954343..152e04c 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 152e04c..2bc4f95 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 3a01754..24e755b 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 37ffe1d..ddff94a 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 bfacde8..9a15375 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 2bc4f95..16df6e9 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 0bddfa9..afcf479 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 35db674..9136967 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 9136967..91e30fb 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 6a306a1..671f076 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 4af3202..19c27e8 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 3a01754..082cb57 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 37ffe1d..370fa03 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 9650abb..f7186eb 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 e0e4ddb..89df19a 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 34e7553..affa2e4 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 acb9ab4..d3d30e3 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 19c27e8..992d135 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 9a15375..14b3770 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 d4be128..628eb85 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 e6fb9aa..b6a7db0 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 2dcc6af..b341a31 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 92b2ed0..e167b9d 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 e42244d..8c11d37 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 25534a9..709cf7f 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 e3040ce..ddd276c 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 18856c3..32aa3a4 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 0000000..5f5212c --- /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 671f076..6b9aa9f 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 992d135..22330ae 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 2dcc6af..a05cdaa 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 0000000..f0f6f10 --- /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 0000000..2352bc1 --- /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 0000000..928dc38 --- /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 fab19b8..1545763 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 770d2f9..0e198bb 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 a05cdaa..1b15ca7 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 48b3b1d..c0a9aff 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 0000000..d0b8845 --- /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 24acf9d..fd86ee4 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 5af1242..95875aa 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 f0f6f10..2e5310b 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 2352bc1..0000000 --- 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 928dc38..a31d018 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 210333d..27a2434 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 1545763..9252eb5 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 0000000..29f1144 --- /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 1b15ca7..90e6b78 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 b341a31..bf12dc1 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 22330ae..0bd77b1 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 f7186eb..f2a0d07 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 dbc7a4b..9f8c58a 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 b2bf515..895f274 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 703b361..f2f89a4 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 549a3a3..0036d7b 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 27b4eef..a52eb62 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 3b51682..c6e29ae 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 90e6b78..b5d5638 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 d035dd4..afd4e99 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 6b5aa99..14d6eac 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 defa5ea..4239746 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 98be6db..5568199 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 13f1f46..a2aa9b7 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 2671507..b2bb51b 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 41359d7..f3005e4 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 f290744..367f99e 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 abcdaeb..58437f4 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 74f5563..d50943f 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 16df6e9..c36de86 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 afcf479..b13d089 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 76d56d2..d885ae4 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 50a9553..6ff65d5 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 d717b76..a97e42a 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 92b2ed0..7d43019 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 e42244d..3399902 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 71c2f91..f20953f 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 25534a9..5c9b8bb 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 fd86ee4..d2c6cd2 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 4fa75d9..6991002 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 2e5310b..d728898 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 9532102..9393527 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 91e30fb..62250b2 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 d3fd676..9511420 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 516ac0c..ada4f39 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 cbf9986..6efcf70 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 62f4877..ab06bb6 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 89df19a..27b24aa 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 7b5bd53..65af1b5 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 2b5bca4..c9f18c3 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 8fbb5b6..3258316 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 7716deb..f9d5200 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 8dad789..4cc7280 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 0abd11f..29e7c9e 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 89c9083..b2a0a9d 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 ab5aef6..34b426f 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 a0a15fc..b05bcce 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 9f6a1fc..9268558 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 2b46ad7..654ee98 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 2de4a5a..9294b2f 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 b2d186c..875f426 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 d3c880e..0af29ef 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 dfc1c29..8592395 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 b4cf9d7..d94b0f8 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 6ad6a2d..d1af123 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 d0111e7..283b8bd 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 6090951..7863cfe 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 ed5b821..eab329d 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 9980e25..47e3e5f 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 45a5d3a..276b15b 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 14bb8c3..dea932a 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 97c51ac..a793b6d 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 a31d018..ea14477 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 4b4703a..cee4cac 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 0dc0ae5..e2b54c7 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 52248cf..4e35dee 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 abd6e70..244ba95 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 27a2434..defc67e 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 b3b90e8..165a7cf 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 9252eb5..6d7042c 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 29f1144..5778efb 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 5ce2100..5ae2660 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 7ecfc40..71408b3 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 2e5310b..8f9a91c 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 a31d018..ea14477 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 29f1144..5778efb 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 d77a738..00c1326 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 77e9ff4..75b1d5f 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 afcf479..f0c9868 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 0000000..4876ce9 --- /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 0e198bb..b98d4d9 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 f0c9868..cf72dba 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 4876ce9..e3267eb 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 75b1d5f..6802a71 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 f3005e4..48a820c 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 0bd77b1..83ced97 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 48a820c..41359d7 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 6b5aa99..1586c93 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 2de4a5a..5a2253b 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 83ced97..1271628 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 00c1326..f520934 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 f2a0d07..66ae0fa 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 9f8c58a..b09e3bb 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 895f274..55e21d2 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 f2f89a4..bb7a4bc 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 b98d4d9..6689566 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 0036d7b..f669844 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 a52eb62..c4ce267 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 c6e29ae..93ec68b 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 daae5de..7e4350a 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 afd4e99..e160886 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 669eae6..d4d8950 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 4239746..eda0bd2 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 5568199..9670906 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 0907819..d185498 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 a2aa9b7..b2093ae 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 81b2d1a..0ffa255 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 b2bb51b..4a2885e 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 41359d7..28355b8 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 367f99e..04e0c11 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 58437f4..2da3ad7 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 c36de86..6b9e837 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 3aa664a..402b056 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 d0b8845..43c2e39 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 dc9db60..dc3f1db 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 cda166b..e708653 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 93bb420..8e4d765 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 08b359b..5b7e8e4 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 45cf3a0..517857a 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 d885ae4..3f3417e 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 cda4a1e..0d00b6f 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 13b7e4b..1460639 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 6ff65d5..aaad14c 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 6fe0969..80f4627 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 a97e42a..1cace9c 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 2a72a18..30484c6 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 cad3931..a5460f9 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 9c81ec2..fb1f371 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 adc6e3d..b203c78 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 08ddd4d..d761a0c 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 d3d30e3..1f78937 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 f20953f..d961369 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 1a31395..c3d1a77 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 ddd276c..dd3be53 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 d2c6cd2..bbcfb1c 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 95875aa..241b316 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 6991002..093a37e 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 d9b8002..e6c6223 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 9393527..7c59bd3 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 32aa3a4..f67f57a 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 62250b2..e77ba8a 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 9511420..a89c789 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 e3267eb..141eccb 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 ada4f39..406d667 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 6efcf70..2fc2ea8 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 ab06bb6..112373d 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 da01c47..9af2973 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 27b24aa..54a11fb 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 68c568c..4d28b9a 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 c6fb193..101f6e5 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 65af1b5..7648e1a 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 c9f18c3..9836cd4 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 3258316..c7c25d8 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 f9d5200..40444c3 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 4cc7280..b0135e3 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 29e7c9e..f723823 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 b2a0a9d..9f0cdc2 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 34b426f..58f066b 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 b05bcce..6ba95f3 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 9268558..5ad01c5 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 5502289..ffcb8a3 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 654ee98..f92490b 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 0824530..5603b53 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 8d965b3..f28e1b1 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 2d109bb..cd6d7a8 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 bcc4a21..375ccfa 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 e540559..3fe6175 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 875f426..265632c 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 0af29ef..270477e 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 8592395..27d0b9b 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 d94b0f8..687cad8 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 29cb6d6..c210b48 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 283b8bd..753b2be 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 f603ff0..28e9d39 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 7863cfe..bf28539 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 0fbde83..c3a4679 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 ce4c158..00cca17 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 eab329d..c64932e 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 47e3e5f..667efac 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 276b15b..d54b2fb 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 dea932a..45410a7 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 a793b6d..d561a1b 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 628eb85..b5f6b49 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 634d052..461e5bf 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 ea14477..154783a 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 cee4cac..7939cd3 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 eeff166..0d024d0 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 d7bc14c..71df4f3 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 c414a71..469d1c0 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 e2b54c7..75dd95b 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 4e35dee..438f86b 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 244ba95..94ed1ec 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 defc67e..d7a6641 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 9f7b94f..fa06a87 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 165a7cf..69f6b90 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 6d7042c..67a8c91 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 2c7250b..c76a7ed 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 b57500e..338787e 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 5adcf2b..7a2a4a5 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 9f23337..7456bf3 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 5778efb..dd43185 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 0821229..666bc66 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 5ae2660..2fb9ad4 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 a3fb40e..c9543e9 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 7110308..0f3a61f 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 b8fc520..e6306c3 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 71408b3..32243b7 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 6328be5..bc02896 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 de84c1f..d094aa1 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 5a21ace..da1d4ea 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 6689566..d102530 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 7e4350a..88e4671 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 0000000..b05056d --- /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 67a8c91..fe790cf 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 0000000..389cb3f --- /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 b05056d..6f22f70 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 389cb3f..0db80cd 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 6f22f70..06c52ab 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 0db80cd..125440f 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 6b9aa9f..b231624 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 b231624..1c0f832 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 b2093ae..27ab632 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 093a37e..fde3ee1 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 406d667..709fb00 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 1271628..34a2e24 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 c4ce267..9400e38 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 04e0c11..a251c3e 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 2da3ad7..4afdbe5 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 517857a..6855cd2 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 d961369..5c7b560 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 c3d1a77..b68e309 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 fde3ee1..e4a78a8 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 2fc2ea8..265748d 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 101f6e5..7074077 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 9836cd4..2db6d30 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 265632c..7abaec0 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 75dd95b..48549f5 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 d7a6641..9606ef8 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 69f6b90..8d4e02f 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 fe790cf..3b460d7 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 d094aa1..44342d6 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 b6a7db0..f9c8c7f 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 d102530..cfaccee 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 9400e38..c543d82 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 c0a9aff..a26320b 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 0000000..92d0226 --- /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 0000000..79b6472 --- /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 1460639..832e32c 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 e6c6223..1403b58 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 e77ba8a..2f0bed4 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 265748d..5b686e2 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 0000000..1fb2458 --- /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 154783a..87373a2 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 0000000..90ac016 --- /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 c543d82..45be8b8 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 79b6472..b1cd3bd 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 832e32c..ffd2286 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 2f0bed4..e23df24 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