Compare commits

...
Author SHA1 Message Date
Ian Ker-SeymerandIan Ker-Seymer a0411e0927 Bump to v5.5.1 2024-07-22 23:25:56 -04:00
Ian Ker-SeymerandGitHub ed421202e2 Merge pull request #1811 from Shopify/marco-cycle-update
Add named? method to Cycle class for checking if the cycle is named
2024-07-16 15:58:21 -04:00
Ian Ker-Seymer d6ca569e8a Require base64 2024-07-16 15:56:47 -04:00
Marco Concetto RudilossoandIan Ker-Seymer d36937d17f Add named? method to Cycle class for checking if the cycle is named 2024-07-16 15:52:25 -04:00
Peter ZhuandGitHub 77bc56a1c2 Merge pull request #1792 from Shopify/centralize_ruby_version
Centralize Ruby Version to `.ruby-version`
2024-04-11 11:12:08 -04:00
Jenny Shen 88d013c8da Use latest compatible Bundler of each Ruby version run 2024-04-05 10:33:27 -04:00
Jenny Shen 36c7fc8e07 Remove ruby version definition in CI/CD
it will be read from .ruby-version
2024-04-05 10:33:06 -04:00
Jenny Shen 2b4810006b Remove TargetRubyVersion in RuboCop config
reads from required_ruby_version in RuboCop 1.61+
2024-04-05 10:23:39 -04:00
Jenny Shen fc4f19471e Update rubocop to 1.61.0 2024-04-05 10:23:37 -04:00
Jenny Shen 8596bb2e38 Commit a .ruby-version 2024-04-05 10:23:05 -04:00
Ian Ker-SeymerandGitHub 6bf18775e7 Merge pull request #1791 from Shopify/v5.5.0
Bump to v5.5.0
2024-03-21 15:50:59 -04:00
Ian Ker-Seymer 56a0b7c42b Bump to v5.5.0 2024-03-21 15:48:54 -04:00
Ian Ker-SeymerandGitHub dba733084e Merge pull request #1760 from mtasaka/nil-nil-comparison-fix
change: make nil_safe_casecmp judge compatible for nil-nil comparison
2024-03-21 15:38:13 -04:00
Michael GoandGitHub 4a4fe3c72a Merge pull request #1781 from Shopify/contextualize-before-to-liquid
update variable's context before invoking its to_liquid
2024-02-12 16:58:54 -04:00
Michael Go 4f35b0bc66 update liquid-c 2024-02-12 16:49:52 -04:00
Michael Go a5e5fab82a update variable's context before invoking its to_liquid 2024-02-12 16:48:20 -04:00
Ian Ker-SeymerandGitHub 02ecaab9d1 Merge pull request #1783 from Shopify/contains-encoding
Fallback to binary comparison when `contains` RHS is  UTF8 encoded
2024-01-31 11:14:01 -05:00
Ian Ker-Seymer 1b2b62964e Allow for binary comparison of incompatible strings 2024-01-30 19:03:05 -05:00
Michael GoandGitHub 9b38a15282 Merge pull request #1776 from Shopify/refactor/invalid-encoding-error
add new TemplateEncodingError
2024-01-12 12:38:34 -04:00
Michael Go 7b25b770af add new TemplateEncodingError 2024-01-12 12:17:20 -04:00
Michael GoandGitHub cf76c0bbec Merge pull request #1775 from Shopify/allow-nil-template-source
allow non-string template source
2024-01-12 10:48:30 -04:00
Michael Go 6a0fe3f7e3 convert template source to string to ensure encoding validity 2024-01-10 16:50:31 -04:00
Michael Go 730ad3684a allow nil template source 2024-01-10 16:32:08 -04:00
Michael GoandGitHub 3ac7e470e6 Merge pull request #1774 from Shopify/check-utf8-validity
check template UTF8 validity before parsing
2024-01-10 15:27:49 -04:00
Michael Go 369a6c55e3 check template UTF8 validity before parsing 2024-01-10 18:58:15 +00:00
Michael GoandGitHub f5ed5404b5 Merge pull request #1773 from Shopify/liquid-tag-whitespace-control-with-comment
don't reset Liquid tag's whitespace control from comment tag
2024-01-05 10:54:32 -04:00
Mamoru TASAKA 4924822c88 change: make nil_safe_casecmp judge compatible for nil-nil comparison
Ruby returns 0 (not nil) for nil <=> nil, i.e. nil and nil are judged
as equal for comparison, and so returns nil_safe_compare .
ref: https://github.com/Shopify/liquid/pull/1476

To make the behavior of nil_safe_casecmp consistent with
nil_safe_compare , change nil_safe_casecmp so that comparison between
nil <=> nil return 0 (equal).

Also change testsuite to reflect this change.

Fixes #1759 .
2023-12-15 15:09:57 +09:00
21 changed files with 194 additions and 31 deletions
+3 -2
View File
@@ -12,7 +12,8 @@ jobs:
matrix:
entry:
- { ruby: 2.7, allowed-failure: false } # minimum supported
- { ruby: 3.2, allowed-failure: false } # latest
- { ruby: 3.2, allowed-failure: false }
- { ruby: 3.3, allowed-failure: false } # latest
- { ruby: ruby-head, allowed-failure: true }
name: Test Ruby ${{ matrix.entry.ruby }}
steps:
@@ -21,6 +22,7 @@ jobs:
with:
ruby-version: ${{ matrix.entry.ruby }}
bundler-cache: true
bundler: latest
- run: bundle exec rake
continue-on-error: ${{ matrix.entry.allowed-failure }}
@@ -30,6 +32,5 @@ jobs:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- run: bundle exec rake memory_profile:run
-2
View File
@@ -4,7 +4,5 @@
pkg
*.rbc
.rvmrc
.ruby-version
Gemfile.lock
.bundle
.byebug_history
-1
View File
@@ -10,7 +10,6 @@ Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
SuggestExtensions: false
Exclude:
+1
View File
@@ -0,0 +1 @@
3.3.0
+4 -2
View File
@@ -7,6 +7,8 @@ end
gemspec
gem "base64"
group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
@@ -18,11 +20,11 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop', '~> 1.44.0'
gem 'rubocop', '~> 1.61.0'
gem 'rubocop-shopify', '~> 2.12.0', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'main'
end
end
+75
View File
@@ -0,0 +1,75 @@
GIT
remote: https://github.com/Shopify/liquid-c.git
revision: 5a786af7284df55e013ea20551c4b688d02e8326
ref: main
specs:
liquid-c (4.2.0)
liquid (>= 5.0.1)
PATH
remote: .
specs:
liquid (5.5.1)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
base64 (0.2.0)
benchmark-ips (2.13.0)
json (2.7.2)
language_server-protocol (3.17.0.3)
memory_profiler (1.0.1)
minitest (5.22.3)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
racc (1.7.3)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.0)
rexml (3.2.6)
rubocop (1.61.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-shopify (2.12.0)
rubocop (~> 1.44)
ruby-progressbar (1.13.0)
stackprof (0.2.26)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.5.0)
PLATFORMS
ruby
DEPENDENCIES
base64
benchmark-ips
liquid!
liquid-c!
memory_profiler
minitest
rake (~> 13.0)
rubocop (~> 1.61.0)
rubocop-performance
rubocop-shopify (~> 2.12.0)
stackprof
terminal-table
BUNDLED WITH
2.5.7
+4
View File
@@ -1,5 +1,9 @@
# Liquid Change Log
## 5.5.0 2024-03-21
Please reference the GitHub release for more information.
## 5.4.0 2022-07-29
### Breaking Changes
+3
View File
@@ -24,6 +24,9 @@ module Liquid
else
false
end
rescue Encoding::CompatibilityError
# "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
left.b.include?(right.b)
end,
}
+7 -3
View File
@@ -27,7 +27,7 @@ module Liquid
@environments.flatten!
@static_environments = [static_environments].flatten(1).freeze
@scopes = [(outer_scope || {})]
@scopes = [outer_scope || {}]
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
@errors = []
@partial = false
@@ -197,10 +197,14 @@ module Liquid
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end
variable = variable.to_liquid
# update variable's context before invoking #to_liquid
variable.context = self if variable.respond_to?(:context=)
variable
liquid_variable = variable.to_liquid
liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
liquid_variable
end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
+16 -15
View File
@@ -40,19 +40,20 @@ module Liquid
end
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
TemplateEncodingError = Class.new(Error)
end
+1
View File
@@ -15,6 +15,7 @@
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
invalid_template_encoding: "Invalid template encoding"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
tag_never_closed: "'%{block_name}' tag was never closed"
+2
View File
@@ -944,6 +944,8 @@ module Liquid
def nil_safe_casecmp(a, b)
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
elsif a.nil? && b.nil?
0
else
a.nil? ? 1 : -1
end
+6
View File
@@ -26,14 +26,20 @@ module Liquid
when NamedSyntax
@variables = variables_from_string(Regexp.last_match(2))
@name = parse_expression(Regexp.last_match(1))
@is_named = true
when SimpleSyntax
@variables = variables_from_string(markup)
@name = @variables.to_s
@is_named = !@name.match?(/\w+:0x\h{8}/)
else
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end
end
def named?
@is_named
end
def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {}
+1 -1
View File
@@ -111,7 +111,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
+6
View File
@@ -107,6 +107,12 @@ module Liquid
# Returns self for easy chaining
def parse(source, options = {})
parse_context = configure_options(options)
source = source.to_s.to_str
unless source.valid_encoding?
raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
end
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
@root = Document.parse(tokenizer, parse_context)
self
+1 -1
View File
@@ -5,7 +5,7 @@ module Liquid
attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source.to_s.to_str
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@offset = 0
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.4.0"
VERSION = "5.5.1"
end
+37
View File
@@ -36,6 +36,24 @@ class Category
end
end
class ProductsDrop < Liquid::Drop
def initialize(products)
@products = products
end
def size
@products.size
end
def to_liquid
if @context["forloop"]
@products.first(@context["forloop"].length)
else
@products
end
end
end
class CategoryDrop < Liquid::Drop
attr_accessor :category, :context
@@ -635,6 +653,25 @@ class ContextTest < Minitest::Test
assert_equal(:my_value, c.registers[:my_register])
end
def test_variable_to_liquid_returns_contextual_drop
context = {
"products" => ProductsDrop.new(["A", "B", "C", "D", "E"]),
}
template = Liquid::Template.parse(<<~LIQUID)
{%- for i in (1..3) -%}
for_loop_products_count: {{ products | size }}
{% endfor %}
unscoped_products_count: {{ products | size }}
LIQUID
result = template.render(context)
assert_includes(result, "for_loop_products_count: 3")
assert_includes(result, "unscoped_products_count: 5")
end
private
def assert_no_object_allocations
+1 -1
View File
@@ -331,8 +331,8 @@ class StandardFiltersTest < Minitest::Test
{ "price" => "1", "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" },
{ "handle" => "delta" },
]
assert_equal(expectation, @filters.sort_natural(input, "price"))
end
+18
View File
@@ -337,4 +337,22 @@ class TemplateTest < Minitest::Test
assert_equal("x=2", output)
assert_instance_of(String, output)
end
def test_raises_error_with_invalid_utf8
e = assert_raises(TemplateEncodingError) do
Template.parse(<<~LIQUID)
{% comment %}
\xC0
{% endcomment %}
LIQUID
end
assert_equal('Liquid error: Invalid template encoding', e.message)
end
def test_allows_non_string_values_as_source
assert_equal('', Template.parse(nil).render)
assert_equal('1', Template.parse(1).render)
assert_equal('true', Template.parse(true).render)
end
end
+7 -2
View File
@@ -55,6 +55,11 @@ class ConditionUnitTest < Minitest::Test
assert_evaluates_false('bob', 'contains', '---')
end
def test_contains_binary_encoding_compatibility_with_utf8
assert_evaluates_true('🙈'.b, 'contains', '🙈')
assert_evaluates_true('🙈', 'contains', '🙈'.b)
end
def test_invalid_comparation_operator
assert_evaluates_argument_error(1, '~~', 0)
end
@@ -166,14 +171,14 @@ class ConditionUnitTest < Minitest::Test
def assert_evaluates_true(left, op, right)
assert(
Condition.new(left, op, right).evaluate(@context),
"Evaluated false: #{left} #{op} #{right}",
"Evaluated false: #{left.inspect} #{op} #{right.inspect}",
)
end
def assert_evaluates_false(left, op, right)
assert(
!Condition.new(left, op, right).evaluate(@context),
"Evaluated true: #{left} #{op} #{right}",
"Evaluated true: #{left.inspect} #{op} #{right.inspect}",
)
end