Skip to content

Commit a4a29f3

Browse files
authored
Merge pull request #2034 from Shopify/fix-liquid-spec-without-activesupport
Require liquid-spec to be run on commit automatically and related fixes
2 parents a60a6c0 + 0058e43 commit a4a29f3

19 files changed

+396
-39
lines changed

.github/workflows/liquid.yml

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,29 @@ jobs:
99
test:
1010
runs-on: ubuntu-latest
1111
strategy:
12+
fail-fast: false
1213
matrix:
1314
entry:
14-
- { ruby: 3.0, allowed-failure: false } # minimum supported
15-
- { ruby: 3.2, allowed-failure: false }
16-
- { ruby: 3.3, allowed-failure: false }
17-
- { ruby: 3.3, allowed-failure: false }
18-
- { ruby: 3.4, allowed-failure: false } # latest
15+
- { ruby: 3.3, allowed-failure: false } # minimum supported
16+
- { ruby: 3.4, allowed-failure: false, rubyopt: "--yjit" }
17+
- { ruby: 4.0, allowed-failure: false } # latest stable
1918
- {
20-
ruby: 3.4,
19+
ruby: 4.0,
2120
allowed-failure: false,
2221
rubyopt: "--enable-frozen-string-literal",
2322
}
24-
- { ruby: 3.4, allowed-failure: false, rubyopt: "--yjit" }
25-
- { ruby: head, allowed-failure: false }
23+
- { ruby: 4.0, allowed-failure: false, rubyopt: "--yjit" }
24+
- { ruby: 4.0, allowed-failure: false, rubyopt: "--zjit" }
25+
26+
# Head can have failures due to being in development
27+
- { ruby: head, allowed-failure: true }
2628
- {
2729
ruby: head,
28-
allowed-failure: false,
30+
allowed-failure: true,
2931
rubyopt: "--enable-frozen-string-literal",
3032
}
31-
- { ruby: head, allowed-failure: false, rubyopt: "--yjit" }
33+
- { ruby: head, allowed-failure: true, rubyopt: "--yjit" }
34+
- { ruby: head, allowed-failure: true, rubyopt: "--zjit" }
3235
name: Test Ruby ${{ matrix.entry.ruby }}
3336
steps:
3437
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@@ -42,6 +45,23 @@ jobs:
4245
env:
4346
RUBYOPT: ${{ matrix.entry.rubyopt }}
4447

48+
spec:
49+
runs-on: ubuntu-latest
50+
env:
51+
BUNDLE_WITH: spec
52+
steps:
53+
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
54+
- uses: ruby/setup-ruby@a25f1e45f0e65a92fcb1e95e8847f78fb0a7197a # v1.273.0
55+
with:
56+
bundler-cache: true
57+
bundler: latest
58+
- name: Run liquid-spec for all adapters
59+
run: |
60+
for adapter in spec/*.rb; do
61+
echo "=== Running $adapter ==="
62+
bundle exec liquid-spec run "$adapter" --no-max-failures
63+
done
64+
4565
memory_profile:
4666
runs-on: ubuntu-latest
4767
steps:

.rubocop_todo.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,16 @@ Style/WordArray:
174174

175175
# Offense count: 117
176176
# This cop supports safe auto-correction (--auto-correct).
177-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
177+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, AllowCopDirectives, AllowedPatterns.
178178
# URISchemes: http, https
179179
Layout/LineLength:
180180
Max: 260
181+
182+
Naming/PredicatePrefix:
183+
Enabled: false
184+
185+
# Offense count: 1
186+
# This is intentional - early return from begin/rescue in assignment context
187+
Lint/NoReturnInBeginEndBlocks:
188+
Exclude:
189+
- 'lib/liquid/standardfilters.rb'

Gemfile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ group :development do
2525
end
2626

2727
group :test do
28-
gem 'rubocop', '~> 1.61.0'
29-
gem 'rubocop-shopify', '~> 2.12.0', require: false
28+
gem 'benchmark'
29+
gem 'rubocop', '~> 1.82.0'
30+
gem 'rubocop-shopify', '~> 2.18.0', require: false
3031
gem 'rubocop-performance', require: false
3132
end
33+
34+
group :spec do
35+
# Using feature branch until https://github.com/Shopify/liquid-spec/pull/97 is merged
36+
gem 'liquid-spec', github: 'Shopify/liquid-spec', branch: 'add-per-spec-required-features'
37+
gem 'activesupport', require: false
38+
end

lib/liquid/condition.rb

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,63 @@ def inspect
113113

114114
def equal_variables(left, right)
115115
if left.is_a?(MethodLiteral)
116-
if right.respond_to?(left.method_name)
117-
return right.send(left.method_name)
118-
else
119-
return nil
120-
end
116+
return call_method_literal(left, right)
121117
end
122118

123119
if right.is_a?(MethodLiteral)
124-
if left.respond_to?(right.method_name)
125-
return left.send(right.method_name)
126-
else
127-
return nil
128-
end
120+
return call_method_literal(right, left)
129121
end
130122

131123
left == right
132124
end
133125

126+
def call_method_literal(literal, value)
127+
method_name = literal.method_name
128+
129+
# If the object responds to the method, use it
130+
if value.respond_to?(method_name)
131+
return value.send(method_name)
132+
end
133+
134+
# Implement blank?/empty? for common types that don't have it
135+
# (ActiveSupport adds these, but Liquid should work without it)
136+
case method_name
137+
when :blank?
138+
liquid_blank?(value)
139+
when :empty?
140+
liquid_empty?(value)
141+
end
142+
end
143+
144+
# Implement blank? semantics matching ActiveSupport
145+
def liquid_blank?(value)
146+
case value
147+
when NilClass, FalseClass
148+
true
149+
when TrueClass, Numeric
150+
false
151+
when String
152+
# Blank if empty or whitespace only
153+
value.empty? || value.match?(/\A\s*\z/)
154+
when Array, Hash
155+
value.empty?
156+
else
157+
# Fall back to empty? if available, otherwise false
158+
value.respond_to?(:empty?) ? value.empty? : false
159+
end
160+
end
161+
162+
# Implement empty? semantics
163+
# Note: nil is NOT empty (but IS blank). empty? checks if a collection has zero elements.
164+
def liquid_empty?(value)
165+
case value
166+
when String, Array, Hash
167+
value.empty?
168+
else
169+
value.respond_to?(:empty?) ? value.empty? : false
170+
end
171+
end
172+
134173
def interpret_condition(left, right, op, context)
135174
# If the operator is empty this means that the decision statement is just
136175
# a single variable. We can just poll this variable from the context and
@@ -154,8 +193,8 @@ def interpret_condition(left, right, op, context)
154193
end
155194

156195
def deprecated_default_context
157-
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
158-
" and will be removed from Liquid 6.0.0.")
196+
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated " \
197+
"and will be removed from Liquid 6.0.0.")
159198
Context.new
160199
end
161200

lib/liquid/drop.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def initialize
3131

3232
# Catch all for the method
3333
def liquid_method_missing(method)
34-
return nil unless @context&.strict_variables
34+
return unless @context&.strict_variables
3535
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
3636
end
3737

lib/liquid/expression.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def parse(markup, ss = StringScanner.new(""), cache = nil)
5555
end
5656

5757
def inner_parse(markup, ss, cache)
58-
if (markup.start_with?("(") && markup.end_with?(")")) && markup =~ RANGES_REGEX
58+
if markup.start_with?("(") && markup.end_with?(")") && markup =~ RANGES_REGEX
5959
return RangeLookup.parse(
6060
Regexp.last_match(1),
6161
Regexp.last_match(2),

lib/liquid/i18n.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def locale
2828
def interpolate(name, vars)
2929
name.gsub(/%\{(\w+)\}/) do
3030
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
31-
(vars[Regexp.last_match(1).to_sym]).to_s
31+
vars[Regexp.last_match(1).to_sym].to_s
3232
end
3333
end
3434

lib/liquid/standardfilters.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,8 @@ def date(input, format)
768768
# @liquid_syntax array | first
769769
# @liquid_return [untyped]
770770
def first(array)
771+
# ActiveSupport returns "" for empty strings, not nil
772+
return array[0] || "" if array.is_a?(String)
771773
array.first if array.respond_to?(:first)
772774
end
773775

@@ -779,6 +781,8 @@ def first(array)
779781
# @liquid_syntax array | last
780782
# @liquid_return [untyped]
781783
def last(array)
784+
# ActiveSupport returns "" for empty strings, not nil
785+
return array[-1] || "" if array.is_a?(String)
782786
array.last if array.respond_to?(:last)
783787
end
784788

lib/liquid/tokenizer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def next_variable_token
117117
byte_a = byte_b = @ss.scan_byte
118118

119119
while byte_b
120-
byte_a = @ss.scan_byte while byte_a && (byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY)
120+
byte_a = @ss.scan_byte while byte_a && byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY
121121

122122
break unless byte_a
123123

lib/liquid/utils.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def self.to_date(obj)
6969
return obj if obj.respond_to?(:strftime)
7070

7171
if obj.is_a?(String)
72-
return nil if obj.empty?
72+
return if obj.empty?
7373
obj = obj.downcase
7474
end
7575

0 commit comments

Comments
 (0)