Class: ThemeCheck::LiquidNode

Inherits:
Node
  • Object
show all
Defined in:
lib/theme_check/liquid_node.rb

Overview

A node from the Liquid AST, the result of parsing a liquid file.

Constant Summary collapse

WHITESPACE =
/\s/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, parent, theme_file) ⇒ LiquidNode

Returns a new instance of LiquidNode.

Raises:

  • (ArgumentError)


8
9
10
11
12
13
14
15
# File 'lib/theme_check/liquid_node.rb', line 8

def initialize(value, parent, theme_file)
  raise ArgumentError, "Expected a Liquid AST Node" if value.is_a?(LiquidNode)
  @value = value
  @parent = parent
  @theme_file = theme_file
  @tag_markup = nil
  @line_number_offset = 0
end

Instance Attribute Details

#parentObject (readonly)

Returns the value of attribute parent.



6
7
8
# File 'lib/theme_check/liquid_node.rb', line 6

def parent
  @parent
end

#theme_fileObject (readonly)

Returns the value of attribute theme_file.



6
7
8
# File 'lib/theme_check/liquid_node.rb', line 6

def theme_file
  @theme_file
end

#valueObject (readonly)

Returns the value of attribute value.



6
7
8
# File 'lib/theme_check/liquid_node.rb', line 6

def value
  @value
end

Instance Method Details

#assigned_or_echoed_variable?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/theme_check/liquid_node.rb', line 143

def assigned_or_echoed_variable?
  variable? && start_token == ""
end

#block?Boolean

A block of type of node?

Returns:

  • (Boolean)


173
174
175
# File 'lib/theme_check/liquid_node.rb', line 173

def block?
  block_tag? || block_body? || document?
end

#block_body?Boolean

The body of blocks

Returns:

  • (Boolean)


168
169
170
# File 'lib/theme_check/liquid_node.rb', line 168

def block_body?
  @value.is_a?(Liquid::BlockBody)
end

#block_end_end_indexObject



223
224
225
226
# File 'lib/theme_check/liquid_node.rb', line 223

def block_end_end_index
  return block_end_start_index unless tag? && block?
  @block_end_end_index ||= block_end_match&.end(0) || block_start_end_index
end

#block_end_markupObject



214
215
216
# File 'lib/theme_check/liquid_node.rb', line 214

def block_end_markup
  source[block_end_start_index...block_end_end_index]
end

#block_end_start_indexObject



218
219
220
221
# File 'lib/theme_check/liquid_node.rb', line 218

def block_end_start_index
  return block_start_end_index unless tag? && block?
  @block_end_start_index ||= block_end_match&.begin(0) || block_start_end_index
end

#block_start_end_indexObject



210
211
212
# File 'lib/theme_check/liquid_node.rb', line 210

def block_start_end_index
  @block_start_end_index ||= position.end_index + end_token.size
end

#block_start_markupObject



196
197
198
# File 'lib/theme_check/liquid_node.rb', line 196

def block_start_markup
  source[block_start_start_index...block_start_end_index]
end

#block_start_start_indexObject



200
201
202
203
204
205
206
207
208
# File 'lib/theme_check/liquid_node.rb', line 200

def block_start_start_index
  @block_start_start_index ||= if inside_liquid_tag?
    backtrack_on_whitespace(source, start_index, /[ \t]/)
  elsif tag?
    backtrack_on_whitespace(source, start_index) - start_token.length
  else
    position.start_index - start_token.length
  end
end

#block_tag?Boolean

A tag %…endtag % node?

Returns:

  • (Boolean)


163
164
165
# File 'lib/theme_check/liquid_node.rb', line 163

def block_tag?
  @value.is_a?(Liquid::Block)
end

#childrenObject

Array of children nodes.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/theme_check/liquid_node.rb', line 18

def children
  @children ||= begin
    nodes =
      if comment?
        []
      elsif defined?(@value.class::ParseTreeVisitor)
        @value.class::ParseTreeVisitor.new(@value, {}).children
      elsif @value.respond_to?(:nodelist)
        Array(@value.nodelist)
      else
        []
      end
    # Work around a bug in Liquid::Variable::ParseTreeVisitor that doesn't return
    # the args in a hash as children nodes.
    nodes = nodes.flat_map do |node|
      case node
      when Hash
        node.values
      else
        node
      end
    end
    nodes.map { |node| LiquidNode.new(node, self, @theme_file) }
  end
end

#comment?Boolean

A comment % block node?

Returns:

  • (Boolean)


152
153
154
# File 'lib/theme_check/liquid_node.rb', line 152

def comment?
  @value.is_a?(Liquid::Comment)
end

#document?Boolean Also known as: root?

Top level node of every liquid_file.

Returns:

  • (Boolean)


157
158
159
# File 'lib/theme_check/liquid_node.rb', line 157

def document?
  @value.is_a?(Liquid::Document)
end

#end_columnObject



125
126
127
# File 'lib/theme_check/liquid_node.rb', line 125

def end_column
  position.end_column
end

#end_indexObject



117
118
119
# File 'lib/theme_check/liquid_node.rb', line 117

def end_index
  position.end_index
end

#end_rowObject



121
122
123
# File 'lib/theme_check/liquid_node.rb', line 121

def end_row
  position.end_row
end

#end_tokenObject



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/theme_check/liquid_node.rb', line 332

def end_token
  if inside_liquid_tag? && source[end_index] == "\n"
    "\n"
  elsif inside_liquid_tag?
    ""
  elsif variable? && source[end_index...end_index + 3] == "-}}"
    "-}}"
  elsif variable? && source[end_index...end_index + 2] == "}}"
    "}}"
  elsif tag? && whitespace_trimmed_end?
    "-%}"
  elsif tag?
    "%}"
  else # this could happen because we're in an assign statement (variable)
    ""
  end
end

#filtersObject

Raises:

  • (TypeError)


187
188
189
190
# File 'lib/theme_check/liquid_node.rb', line 187

def filters
  raise TypeError, "Attempting to lookup filters of #{type_name}. Only variables have filters." unless variable?
  @value.filters
end

#inner_jsonObject



81
82
83
84
85
86
87
# File 'lib/theme_check/liquid_node.rb', line 81

def inner_json
  return nil unless schema?
  @inner_json ||= JSON.parse(inner_markup)
rescue JSON::ParserError
  # Handled by ValidSchema
  @inner_json = nil
end

#inner_markupObject



76
77
78
79
# File 'lib/theme_check/liquid_node.rb', line 76

def inner_markup
  return '' unless block?
  @inner_markup ||= source[block_start_end_index...block_end_start_index]
end

#inner_markup_end_columnObject



272
273
274
# File 'lib/theme_check/liquid_node.rb', line 272

def inner_markup_end_column
  inner_markup_position.end_column
end

#inner_markup_end_indexObject



256
257
258
# File 'lib/theme_check/liquid_node.rb', line 256

def inner_markup_end_index
  inner_markup_position.end_index
end

#inner_markup_end_rowObject



268
269
270
# File 'lib/theme_check/liquid_node.rb', line 268

def inner_markup_end_row
  inner_markup_position.end_row
end

#inner_markup_start_columnObject



264
265
266
# File 'lib/theme_check/liquid_node.rb', line 264

def inner_markup_start_column
  inner_markup_position.start_column
end

#inner_markup_start_indexObject



252
253
254
# File 'lib/theme_check/liquid_node.rb', line 252

def inner_markup_start_index
  inner_markup_position.start_index
end

#inner_markup_start_rowObject



260
261
262
# File 'lib/theme_check/liquid_node.rb', line 260

def inner_markup_start_row
  inner_markup_position.start_row
end

#inside_liquid_tag?Boolean

Is this node inside a ‘liquid … %` block?

Returns:

  • (Boolean)


279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/theme_check/liquid_node.rb', line 279

def inside_liquid_tag?
  # What we're doing here is starting at the start of the tag and
  # backtrack on all the whitespace until we land on something. If
  # that something is {% or %-, then we can safely assume that
  # we're inside a full tag and not a liquid tag.
  @inside_liquid_tag ||= if tag? && start_index && source
    i = 1
    i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
    first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
    first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
  else
    false
  end
end

#line_numberObject

Most nodes have a line number, but it’s not guaranteed.



96
97
98
99
100
101
102
103
# File 'lib/theme_check/liquid_node.rb', line 96

def line_number
  if tag? && @value.respond_to?(:line_number)
    markup # initialize the line_number_offset
    @value.line_number - @line_number_offset
  elsif @value.respond_to?(:line_number)
    @value.line_number
  end
end

#literal?Boolean

Literals are hard-coded values in the liquid file.

Returns:

  • (Boolean)


130
131
132
# File 'lib/theme_check/liquid_node.rb', line 130

def literal?
  @value.is_a?(String) || @value.is_a?(Integer)
end

#markupObject

The original source code of the node. Doesn’t contain wrapping braces.



45
46
47
48
49
50
51
52
53
# File 'lib/theme_check/liquid_node.rb', line 45

def markup
  if tag?
    tag_markup
  elsif literal?
    value.to_s
  elsif @value.instance_variable_defined?(:@markup)
    @value.instance_variable_get(:@markup)
  end
end

#markup=(markup) ⇒ Object



89
90
91
92
93
# File 'lib/theme_check/liquid_node.rb', line 89

def markup=(markup)
  if @value.instance_variable_defined?(:@markup)
    @value.instance_variable_set(:@markup, markup)
  end
end

#outer_markupObject

The original source code of the node. Does contain wrapping braces.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/theme_check/liquid_node.rb', line 56

def outer_markup
  if literal?
    markup
  elsif variable_lookup?
    ''
  elsif variable?
    start_token + markup + end_token
  elsif tag? && block?
    start_index = block_start_start_index
    end_index = block_start_end_index
    end_index += inner_markup.size
    end_index = find_block_delimiter(end_index)&.end(0)
    source[start_index...end_index]
  elsif tag?
    source[block_start_start_index...block_start_end_index]
  else
    inner_markup
  end
end

#outer_markup_end_columnObject



248
249
250
# File 'lib/theme_check/liquid_node.rb', line 248

def outer_markup_end_column
  outer_markup_position.end_column
end

#outer_markup_end_indexObject



232
233
234
# File 'lib/theme_check/liquid_node.rb', line 232

def outer_markup_end_index
  outer_markup_position.end_index
end

#outer_markup_end_rowObject



244
245
246
# File 'lib/theme_check/liquid_node.rb', line 244

def outer_markup_end_row
  outer_markup_position.end_row
end

#outer_markup_start_columnObject



240
241
242
# File 'lib/theme_check/liquid_node.rb', line 240

def outer_markup_start_column
  outer_markup_position.start_column
end

#outer_markup_start_indexObject



228
229
230
# File 'lib/theme_check/liquid_node.rb', line 228

def outer_markup_start_index
  outer_markup_position.start_index
end

#outer_markup_start_rowObject



236
237
238
# File 'lib/theme_check/liquid_node.rb', line 236

def outer_markup_start_row
  outer_markup_position.start_row
end

#schema?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/theme_check/liquid_node.rb', line 177

def schema?
  @value.is_a?(ThemeCheck::Tags::Schema)
end

#sourceObject



192
193
194
# File 'lib/theme_check/liquid_node.rb', line 192

def source
  theme_file&.source
end

#start_columnObject



113
114
115
# File 'lib/theme_check/liquid_node.rb', line 113

def start_column
  position.start_column
end

#start_indexObject



105
106
107
# File 'lib/theme_check/liquid_node.rb', line 105

def start_index
  position.start_index
end

#start_rowObject



109
110
111
# File 'lib/theme_check/liquid_node.rb', line 109

def start_row
  position.start_row
end

#start_tokenObject



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/theme_check/liquid_node.rb', line 316

def start_token
  if inside_liquid_tag?
    ""
  elsif variable? && source[start_index - 3..start_index - 1] == "{{-"
    "{{-"
  elsif variable? && source[start_index - 2..start_index - 1] == "{{"
    "{{"
  elsif tag? && whitespace_trimmed_start?
    "{%-"
  elsif tag?
    "{%"
  else
    ""
  end
end

#tag?Boolean

A tag % node?

Returns:

  • (Boolean)


135
136
137
# File 'lib/theme_check/liquid_node.rb', line 135

def tag?
  @value.is_a?(Liquid::Tag)
end

#type_nameObject

The ‘:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>` and `after_<type_name>` check methods.



183
184
185
# File 'lib/theme_check/liquid_node.rb', line 183

def type_name
  @type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
end

#variable?Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/theme_check/liquid_node.rb', line 139

def variable?
  @value.is_a?(Liquid::Variable)
end

#variable_lookup?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/theme_check/liquid_node.rb', line 147

def variable_lookup?
  @value.is_a?(Liquid::VariableLookup)
end

#whitespace_trimmed_end?Boolean

Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}

Returns:

  • (Boolean)


306
307
308
309
310
311
312
313
314
# File 'lib/theme_check/liquid_node.rb', line 306

def whitespace_trimmed_end?
  @whitespace_trimmed_end ||= if end_index && source && !inside_liquid_tag?
    i = 0
    i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
    source[end_index + i] == "-"
  else
    false
  end
end

#whitespace_trimmed_start?Boolean

Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-

Returns:

  • (Boolean)


295
296
297
298
299
300
301
302
303
# File 'lib/theme_check/liquid_node.rb', line 295

def whitespace_trimmed_start?
  @whitespace_trimmed_start ||= if start_index && source && !inside_liquid_tag?
    i = 1
    i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
    source[start_index - i] == "-"
  else
    false
  end
end