liquid2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +46 -0
  4. data/.ruby-version +1 -0
  5. data/.vscode/settings.json +32 -0
  6. data/CHANGELOG.md +5 -0
  7. data/LICENSE.txt +21 -0
  8. data/LICENSE_SHOPIFY.txt +20 -0
  9. data/README.md +219 -0
  10. data/Rakefile +23 -0
  11. data/Steepfile +26 -0
  12. data/lib/liquid2/context.rb +297 -0
  13. data/lib/liquid2/environment.rb +287 -0
  14. data/lib/liquid2/errors.rb +79 -0
  15. data/lib/liquid2/expression.rb +20 -0
  16. data/lib/liquid2/expressions/arguments.rb +25 -0
  17. data/lib/liquid2/expressions/array.rb +20 -0
  18. data/lib/liquid2/expressions/blank.rb +41 -0
  19. data/lib/liquid2/expressions/boolean.rb +20 -0
  20. data/lib/liquid2/expressions/filtered.rb +136 -0
  21. data/lib/liquid2/expressions/identifier.rb +43 -0
  22. data/lib/liquid2/expressions/lambda.rb +53 -0
  23. data/lib/liquid2/expressions/logical.rb +71 -0
  24. data/lib/liquid2/expressions/loop.rb +79 -0
  25. data/lib/liquid2/expressions/path.rb +33 -0
  26. data/lib/liquid2/expressions/range.rb +28 -0
  27. data/lib/liquid2/expressions/relational.rb +119 -0
  28. data/lib/liquid2/expressions/template_string.rb +20 -0
  29. data/lib/liquid2/filter.rb +95 -0
  30. data/lib/liquid2/filters/array.rb +202 -0
  31. data/lib/liquid2/filters/date.rb +20 -0
  32. data/lib/liquid2/filters/default.rb +16 -0
  33. data/lib/liquid2/filters/json.rb +15 -0
  34. data/lib/liquid2/filters/math.rb +87 -0
  35. data/lib/liquid2/filters/size.rb +11 -0
  36. data/lib/liquid2/filters/slice.rb +17 -0
  37. data/lib/liquid2/filters/sort.rb +96 -0
  38. data/lib/liquid2/filters/string.rb +204 -0
  39. data/lib/liquid2/loader.rb +59 -0
  40. data/lib/liquid2/loaders/file_system_loader.rb +76 -0
  41. data/lib/liquid2/loaders/mixins.rb +52 -0
  42. data/lib/liquid2/node.rb +113 -0
  43. data/lib/liquid2/nodes/comment.rb +18 -0
  44. data/lib/liquid2/nodes/output.rb +24 -0
  45. data/lib/liquid2/nodes/tags/assign.rb +35 -0
  46. data/lib/liquid2/nodes/tags/block_comment.rb +26 -0
  47. data/lib/liquid2/nodes/tags/capture.rb +40 -0
  48. data/lib/liquid2/nodes/tags/case.rb +111 -0
  49. data/lib/liquid2/nodes/tags/cycle.rb +63 -0
  50. data/lib/liquid2/nodes/tags/decrement.rb +29 -0
  51. data/lib/liquid2/nodes/tags/doc.rb +24 -0
  52. data/lib/liquid2/nodes/tags/echo.rb +31 -0
  53. data/lib/liquid2/nodes/tags/extends.rb +3 -0
  54. data/lib/liquid2/nodes/tags/for.rb +155 -0
  55. data/lib/liquid2/nodes/tags/if.rb +84 -0
  56. data/lib/liquid2/nodes/tags/include.rb +123 -0
  57. data/lib/liquid2/nodes/tags/increment.rb +29 -0
  58. data/lib/liquid2/nodes/tags/inline_comment.rb +28 -0
  59. data/lib/liquid2/nodes/tags/liquid.rb +29 -0
  60. data/lib/liquid2/nodes/tags/macro.rb +3 -0
  61. data/lib/liquid2/nodes/tags/raw.rb +30 -0
  62. data/lib/liquid2/nodes/tags/render.rb +137 -0
  63. data/lib/liquid2/nodes/tags/tablerow.rb +143 -0
  64. data/lib/liquid2/nodes/tags/translate.rb +3 -0
  65. data/lib/liquid2/nodes/tags/unless.rb +23 -0
  66. data/lib/liquid2/nodes/tags/with.rb +3 -0
  67. data/lib/liquid2/parser.rb +917 -0
  68. data/lib/liquid2/scanner.rb +595 -0
  69. data/lib/liquid2/static_analysis.rb +301 -0
  70. data/lib/liquid2/tag.rb +22 -0
  71. data/lib/liquid2/template.rb +182 -0
  72. data/lib/liquid2/undefined.rb +131 -0
  73. data/lib/liquid2/utils/cache.rb +80 -0
  74. data/lib/liquid2/utils/chain_hash.rb +40 -0
  75. data/lib/liquid2/utils/unescape.rb +119 -0
  76. data/lib/liquid2/version.rb +5 -0
  77. data/lib/liquid2.rb +90 -0
  78. data/performance/benchmark.rb +73 -0
  79. data/performance/memory_profile.rb +62 -0
  80. data/performance/profile.rb +71 -0
  81. data/sig/liquid2.rbs +2348 -0
  82. data.tar.gz.sig +0 -0
  83. metadata +164 -0
  84. metadata.gz.sig +0 -0
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _case_ tag.
7
+ class CaseTag < Tag
8
+ END_BLOCK = Set["endcase", "when", "else"]
9
+ WHEN_DELIM = Set[:token_comma, :token_or]
10
+
11
+ def self.parse(token, parser)
12
+ expression = parser.parse_primary
13
+ parser.carry_whitespace_control
14
+ parser.eat(:token_tag_end)
15
+ parser.eat(:token_other) if parser.current_kind == :token_other
16
+
17
+ whens = [] # : Array[MultiEqualBlock]
18
+ default = nil # : Block?
19
+
20
+ whens << parse_when(parser, expression) while parser.tag?("when")
21
+
22
+ if parser.tag?("else")
23
+ parser.eat_empty_tag("else")
24
+ default = parser.parse_block(END_BLOCK)
25
+ end
26
+
27
+ parser.eat_empty_tag("endcase")
28
+ new(token, expression, whens, default)
29
+ end
30
+
31
+ # @return [MultiEqualBlock]
32
+ def self.parse_when(parser, expr)
33
+ parser.eat(:token_tag_start)
34
+ parser.skip_whitespace_control
35
+ token = parser.eat(:token_tag_name)
36
+
37
+ parser.next if parser.current_kind == :token_comma
38
+
39
+ args = [] # : Array[Expression]
40
+
41
+ loop do
42
+ args << parser.parse_primary(infix: false)
43
+ break unless WHEN_DELIM.member?(parser.current_kind)
44
+
45
+ parser.next
46
+ end
47
+
48
+ parser.carry_whitespace_control
49
+ parser.eat(:token_tag_end)
50
+
51
+ block = parser.parse_block(END_BLOCK)
52
+ MultiEqualBlock.new(token, expr, args, block)
53
+ end
54
+
55
+ def initialize(token, expression, whens, default)
56
+ super(token)
57
+ @expression = expression
58
+ @whens = whens
59
+ @default = default
60
+ @blank = whens.map(&:blank).all? && (!default || default.blank)
61
+ end
62
+
63
+ def render(context, buffer)
64
+ rendered = false
65
+ index = 0
66
+ while (node = @whens[index])
67
+ rendered_ = node.render(context, buffer)
68
+ rendered ||= rendered_
69
+ index += 1
70
+ end
71
+
72
+ (@default || raise).render(context, buffer) if @default && !rendered
73
+ end
74
+
75
+ def children(_static_context, include_partials: true)
76
+ # @type var nodes: Array[Node]
77
+ nodes = @whens.clone
78
+ nodes << (@default || raise) if @default
79
+ nodes
80
+ end
81
+
82
+ def expressions = [@expression]
83
+ end
84
+
85
+ # A Liquid block guarded by any one of multiple expressions.
86
+ class MultiEqualBlock < Node
87
+ # @param left [Expression]
88
+ # @param conditions [Array<Expression>]
89
+ # @param block [Block]
90
+ def initialize(token, left, conditions, block)
91
+ super(token)
92
+ @left = left
93
+ @conditions = conditions
94
+ @block = block
95
+ @blank = block.blank
96
+ end
97
+
98
+ def render(context, buffer)
99
+ left = context.evaluate(@left)
100
+ if @conditions.map { |right| Liquid2.eq?(left, context.evaluate(right)) }.any?
101
+ @block.render(context, buffer)
102
+ true
103
+ else
104
+ false
105
+ end
106
+ end
107
+
108
+ def children(_static_context, include_partials: true) = [@block]
109
+ def expressions = @conditions
110
+ end
111
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _cycle_ tag.
7
+ class CycleTag < Tag
8
+ # @param parser [Parser]
9
+ # @return [CycleTag]
10
+ def self.parse(token, parser)
11
+ items = [] # : Array[untyped]
12
+ group_name = nil # : untyped?
13
+ named = false
14
+ first = parser.parse_primary
15
+
16
+ # Is the first expression followed by a colon? If so, it is a group name
17
+ # followed by items to cycle.
18
+ if parser.current_kind == :token_colon
19
+ group_name = first
20
+ named = true
21
+ parser.next
22
+ else
23
+ items << first
24
+ end
25
+
26
+ parser.next if parser.current_kind == :token_comma
27
+
28
+ items.push(*parser.parse_positional_arguments)
29
+ parser.carry_whitespace_control
30
+ parser.eat(:token_tag_end)
31
+ new(token, group_name, items, named)
32
+ end
33
+
34
+ # @param name [Expression?]
35
+ # @param items [Array<Expression>]
36
+ def initialize(token, name, items, named)
37
+ super(token)
38
+ @name = name
39
+ @items = items
40
+ @named = named
41
+ @blank = false
42
+ end
43
+
44
+ def render(context, buffer)
45
+ args = @items.map { |expr| context.evaluate(expr) }
46
+
47
+ key = if @named
48
+ context.evaluate(@name).to_s
49
+ else
50
+ @items.to_s
51
+ end
52
+
53
+ index = context.tag_namespace[:cycles][key]
54
+ buffer << Liquid2.to_output_s(args[index])
55
+
56
+ index += 1
57
+ index = 0 if index >= @items.length
58
+ context.tag_namespace[:cycles][key] = index
59
+ end
60
+
61
+ def expressions = @items
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _decrement_ tag.
7
+ class DecrementTag < Tag
8
+ # @param parser [Parser]
9
+ # @return [DecrementTag]
10
+ def self.parse(token, parser)
11
+ name = parser.parse_identifier(trailing_question: false)
12
+ parser.carry_whitespace_control
13
+ parser.eat(:token_tag_end)
14
+ new(token, name)
15
+ end
16
+
17
+ # @param name [Identifier]
18
+ def initialize(token, name)
19
+ super(token)
20
+ @name = name
21
+ end
22
+
23
+ def render(context, buffer)
24
+ buffer << context.decrement(@name.name).to_s
25
+ end
26
+
27
+ def template_scope = [@name]
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _doc_ tag.
7
+ class DocTag < Tag
8
+ def self.parse(token, parser)
9
+ parser.carry_whitespace_control
10
+ parser.eat(:token_tag_end)
11
+ text_token = parser.eat(:token_doc)
12
+ parser.eat_empty_tag("enddoc")
13
+ new(token, text_token[1] || raise)
14
+ end
15
+
16
+ # @param text [String]
17
+ def initialize(token, text)
18
+ super(token)
19
+ @text = text
20
+ end
21
+
22
+ def render(_context, _buffer) = 0
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _echo_ tag.
7
+ class EchoTag < Tag
8
+ # @param parser [Parser]
9
+ # @return [EchoTag]
10
+ def self.parse(token, parser)
11
+ expression = unless %i[token_whitespace_control token_tag_end].include?(parser.current_kind)
12
+ parser.parse_filtered_expression
13
+ end
14
+ parser.carry_whitespace_control
15
+ parser.eat(:token_tag_end)
16
+ new(token, expression)
17
+ end
18
+
19
+ def initialize(token, expression)
20
+ super(token)
21
+ @expression = expression
22
+ @blank = false
23
+ end
24
+
25
+ def render(context, buffer)
26
+ buffer << Liquid2.to_output_s(context.evaluate(@expression))
27
+ end
28
+
29
+ def expressions = [@expression]
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _for_ tag.
7
+ class ForTag < Tag
8
+ END_BLOCK = Set["endfor", "else"]
9
+
10
+ def self.parse(token, parser)
11
+ parser.expect_expression
12
+ expression = parser.parse_loop_expression
13
+ parser.carry_whitespace_control
14
+ parser.eat(:token_tag_end)
15
+ block = parser.parse_block(END_BLOCK)
16
+
17
+ if parser.tag?("else")
18
+ parser.eat_empty_tag("else")
19
+ default = parser.parse_block(END_BLOCK)
20
+ else
21
+ default = nil
22
+ end
23
+
24
+ parser.eat_empty_tag("endfor")
25
+ new(token, expression, block, default)
26
+ end
27
+
28
+ # @param token [[Symbol, String?, Integer]]
29
+ # @param expression [LoopExpression]
30
+ # @param block [Block]
31
+ # @param default [Block?]
32
+ def initialize(token, expression, block, default)
33
+ super(token)
34
+ @expression = expression
35
+ @block = block
36
+ @default = default
37
+ @blank = block.blank && (!default || default.blank)
38
+ end
39
+
40
+ def render(context, buffer)
41
+ array = @expression.evaluate(context)
42
+
43
+ if array.empty?
44
+ return @default ? (@default || raise).render(context, buffer) : 0
45
+ end
46
+
47
+ name = @expression.identifier.name
48
+ forloop = ForLoop.new(@expression.name, array.length, context.parent_loop(self))
49
+ namespace = { "forloop" => forloop }
50
+
51
+ context.loop(namespace, forloop) do
52
+ index = 0
53
+ while index < array.length
54
+ namespace[name] = array[index]
55
+ index += 1
56
+ forloop.next
57
+ @block.render(context, buffer)
58
+ case context.interrupts.pop
59
+ when :continue
60
+ next
61
+ when :break
62
+ break
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def children(_static_context, include_partials: true)
69
+ # @type var nodes: Array[Node]
70
+ nodes = [@block]
71
+ nodes << (@default || raise) if @default
72
+ nodes
73
+ end
74
+
75
+ def expressions = [@expression]
76
+
77
+ def block_scope
78
+ [@expression.identifier, Identifier.new([:token_word, "forloop", @expression.token.last])]
79
+ end
80
+ end
81
+
82
+ # The standard _break_ tag.
83
+ class BreakTag < Tag
84
+ def self.parse(token, parser)
85
+ parser.carry_whitespace_control
86
+ parser.eat(:token_tag_end)
87
+ new(token)
88
+ end
89
+
90
+ def render(context, _buffer)
91
+ context.interrupts << :break
92
+ end
93
+ end
94
+
95
+ # The standard _continue_ tag.
96
+ class ContinueTag < Tag
97
+ def self.parse(token, parser)
98
+ parser.carry_whitespace_control
99
+ parser.eat(:token_tag_end)
100
+ new(token)
101
+ end
102
+
103
+ def render(context, _buffer)
104
+ context.interrupts << :continue
105
+ end
106
+ end
107
+
108
+ # `for` loop helper variables.
109
+ class ForLoop
110
+ attr_reader :name, :length, :parentloop
111
+
112
+ KEYS = Set[
113
+ "name",
114
+ "length",
115
+ "index",
116
+ "index0",
117
+ "rindex",
118
+ "rindex0",
119
+ "first",
120
+ "last",
121
+ "parentloop"
122
+ ]
123
+
124
+ def initialize(name, length, parent_loop)
125
+ @name = name
126
+ @length = length
127
+ @parentloop = parent_loop
128
+ @index = -1
129
+ end
130
+
131
+ def key?(key)
132
+ KEYS.member?(key)
133
+ end
134
+
135
+ def [](key)
136
+ send(key) if KEYS.member?(key)
137
+ end
138
+
139
+ def fetch(key, default = :undefined)
140
+ if KEYS.member?(key)
141
+ send(key)
142
+ else
143
+ default
144
+ end
145
+ end
146
+
147
+ def next = @index += 1
148
+ def index = @index + 1
149
+ def index0 = @index
150
+ def rindex = @length - @index
151
+ def rindex0 = @length - @index - 1
152
+ def first = @index.zero?
153
+ def last = @index == @length - 1
154
+ end
155
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require_relative "../../tag"
5
+
6
+ module Liquid2
7
+ # The standard _if_ tag
8
+ class IfTag < Tag
9
+ END_TAG = "endif"
10
+ END_BLOCK = Set["else", "elsif", "endif"].freeze
11
+
12
+ # @param parser [Parser]
13
+ # @return [IfTag]
14
+ def self.parse(token, parser)
15
+ parser.expect_expression
16
+ expression = BooleanExpression.new(parser.current, parser.parse_primary)
17
+ parser.carry_whitespace_control
18
+ parser.eat(:token_tag_end)
19
+
20
+ block = parser.parse_block(self::END_BLOCK)
21
+ alternatives = [] # : Array[ConditionalBlock]
22
+ alternatives << parse_elsif(parser) while parser.tag?("elsif")
23
+
24
+ if parser.tag?("else")
25
+ parser.eat_empty_tag("else")
26
+ default = parser.parse_block(self::END_BLOCK)
27
+ else
28
+ default = nil
29
+ end
30
+
31
+ parser.eat_empty_tag(self::END_TAG)
32
+ new(token, expression, block, alternatives, default)
33
+ end
34
+
35
+ # @return [ConditionalBlock]
36
+ def self.parse_elsif(parser)
37
+ parser.eat(:token_tag_start)
38
+ parser.skip_whitespace_control
39
+ token = parser.eat(:token_tag_name)
40
+ parser.expect_expression
41
+ expression = BooleanExpression.new(parser.current, parser.parse_primary)
42
+ parser.carry_whitespace_control
43
+ parser.eat(:token_tag_end)
44
+
45
+ block = parser.parse_block(self::END_BLOCK)
46
+ ConditionalBlock.new(token, expression, block)
47
+ end
48
+
49
+ # @param token [[Symbol, String?, Integer]]
50
+ # @param expression [Expression]
51
+ # @param block [Block]
52
+ # @param alternatives [Array<[ConditionalBlock]>]
53
+ # @param default [Block?]
54
+ def initialize(token, expression, block, alternatives, default)
55
+ super(token)
56
+ @expression = expression
57
+ @block = block
58
+ @alternatives = alternatives
59
+ @default = default
60
+ @blank = block.blank && alternatives.all?(&:blank) && (!default || default.blank)
61
+ end
62
+
63
+ def render(context, buffer)
64
+ return @block.render(context, buffer) if context.evaluate(@expression)
65
+
66
+ index = 0
67
+ while (alt = @alternatives[index])
68
+ index += 1
69
+ return alt.block.render(context, buffer) if context.evaluate(alt.expression)
70
+ end
71
+
72
+ (@default || raise).render(context, buffer) if @default
73
+ end
74
+
75
+ def children(_static_context, include_partials: true)
76
+ # @type var nodes: Array[Node]
77
+ nodes = [@block, *@alternatives]
78
+ nodes << (@default || raise) if @default
79
+ nodes
80
+ end
81
+
82
+ def expressions = [@expression]
83
+ end
84
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _include_ tag.
7
+ class IncludeTag < Tag
8
+ # @param parser [Parser]
9
+ # @return [IncludeTag]
10
+ def self.parse(token, parser)
11
+ name = parser.parse_primary
12
+
13
+ repeat = false
14
+ var = nil # : Expression?
15
+ as = nil # : Identifier?
16
+
17
+ if parser.current_kind == :token_for && !%i[token_comma
18
+ token_colon].include?(parser.peek_kind)
19
+ parser.next
20
+ repeat = true
21
+ var = parser.parse_primary
22
+ if parser.current_kind == :token_as
23
+ parser.next
24
+ as = parser.parse_identifier
25
+ end
26
+ elsif parser.current_kind == :token_with && !%i[token_comma
27
+ token_colon].include?(parser.peek_kind)
28
+ parser.next
29
+ var = parser.parse_primary
30
+ if parser.current_kind == :token_as
31
+ parser.next
32
+ as = parser.parse_identifier
33
+ end
34
+ end
35
+
36
+ parser.next if parser.current_kind == :token_comma
37
+ args = parser.parse_keyword_arguments
38
+ parser.carry_whitespace_control
39
+ parser.eat(:token_tag_end)
40
+ new(token, name, repeat, var, as, args)
41
+ end
42
+
43
+ # @param name [Expression]
44
+ # @param repeat [bool]
45
+ # @param var [Expression?]
46
+ # @param as [Identifier?]
47
+ # @param args [Array<KeywordArgument> | nil]
48
+ def initialize(token, name, repeat, var, as, args)
49
+ super(token)
50
+ @name = name
51
+ @repeat = repeat
52
+ @var = var
53
+ @as = as
54
+ @args = args
55
+ @blank = false
56
+ end
57
+
58
+ def render(context, buffer)
59
+ name = context.evaluate(@name)
60
+ template = context.env.get_template(name.to_s, context: context, tag: :include)
61
+ namespace = @args.to_h { |arg| [arg.name, context.evaluate(arg.value)] }
62
+
63
+ context.extend(namespace, template: template) do
64
+ if @var
65
+ val = context.evaluate(@var || raise)
66
+ key = @as&.name || template.name.split(".").first
67
+
68
+ if val.is_a?(Array)
69
+ context.raise_for_loop_limit(length: val.size)
70
+ index = 0
71
+ while index < val.length
72
+ namespace[key] = val[index]
73
+ template.render_with_context(context, buffer, partial: true, block_scope: false)
74
+ index += 1
75
+ end
76
+ else
77
+ namespace[key] = val
78
+ template.render_with_context(context, buffer, partial: true, block_scope: false)
79
+ end
80
+ else
81
+ template.render_with_context(context, buffer, partial: true, block_scope: false)
82
+ end
83
+ end
84
+ rescue LiquidTemplateNotFoundError => e
85
+ e.token = @token
86
+ e.template_name = context.template.full_name unless context.template.full_name.empty?
87
+ raise e
88
+ end
89
+
90
+ def children(static_context, include_partials: true)
91
+ return [] unless include_partials
92
+
93
+ name = static_context.evaluate(@name)
94
+ template = static_context.env.get_template(name.to_s, context: static_context, tag: :include)
95
+ template.ast
96
+ rescue LiquidTemplateNotFoundError => e
97
+ e.token = @token
98
+ e.template_name = static_context.template.full_name
99
+ raise e
100
+ end
101
+
102
+ def expressions
103
+ exprs = [@name]
104
+ exprs << @var if @var
105
+ exprs.concat(@args.map(&:value))
106
+ exprs
107
+ end
108
+
109
+ def partial_scope
110
+ scope = @args.map { |arg| Identifier.new([:token_word, arg.name, arg.token.last]) }
111
+
112
+ if @var
113
+ if @as
114
+ scope << @as # steep:ignore
115
+ elsif @name.is_a?(String)
116
+ scope << Identifier.new([:token_word, @name.split(".").first, @token.last])
117
+ end
118
+ end
119
+
120
+ Partial.new(@name, :shared, scope)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # The standard _increment_ tag.
7
+ class IncrementTag < Tag
8
+ # @param parser [Parser]
9
+ # @return [DecrementTag]
10
+ def self.parse(token, parser)
11
+ name = parser.parse_identifier(trailing_question: false)
12
+ parser.carry_whitespace_control
13
+ parser.eat(:token_tag_end)
14
+ new(token, name)
15
+ end
16
+
17
+ # @param name [Identifier]
18
+ def initialize(token, name)
19
+ super(token)
20
+ @name = name
21
+ end
22
+
23
+ def render(context, buffer)
24
+ buffer << context.increment(@name.name).to_s
25
+ end
26
+
27
+ def template_scope = [@name]
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../tag"
4
+
5
+ module Liquid2
6
+ # `{% # comment %}` style comments.
7
+ class InlineComment < Tag
8
+ attr_reader :text
9
+
10
+ def self.parse(token, parser)
11
+ comment = parser.eat(:token_comment)
12
+ parser.carry_whitespace_control
13
+ parser.eat(:token_tag_end)
14
+ new(token, comment[1] || "")
15
+ end
16
+
17
+ # @param text [String]
18
+ def initialize(token, text)
19
+ super(token)
20
+ @text = text
21
+ return unless /\n\s*[^#\s]/.match?(text)
22
+
23
+ raise LiquidSyntaxError.new("every line must start with a '#'", token)
24
+ end
25
+
26
+ def render(_context, _buffer) = 0
27
+ end
28
+ end