theme-check 1.6.0 → 1.7.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/data/shopify_liquid/tags.yml +9 -9
  4. data/docs/api/html_check.md +7 -7
  5. data/docs/api/liquid_check.md +10 -10
  6. data/docs/checks/convert_include_to_render.md +1 -1
  7. data/docs/checks/missing_enable_comment.md +1 -1
  8. data/lib/theme_check/analyzer.rb +41 -17
  9. data/lib/theme_check/asset_file.rb +1 -1
  10. data/lib/theme_check/check.rb +2 -2
  11. data/lib/theme_check/checks/html_parsing_error.rb +2 -2
  12. data/lib/theme_check/checks/matching_translations.rb +1 -1
  13. data/lib/theme_check/checks/missing_template.rb +6 -6
  14. data/lib/theme_check/checks/nested_snippet.rb +2 -2
  15. data/lib/theme_check/checks/required_layout_theme_object.rb +2 -2
  16. data/lib/theme_check/checks/syntax_error.rb +5 -5
  17. data/lib/theme_check/checks/template_length.rb +2 -2
  18. data/lib/theme_check/checks/translation_key_exists.rb +1 -13
  19. data/lib/theme_check/checks/undefined_object.rb +7 -7
  20. data/lib/theme_check/checks/unused_assign.rb +4 -4
  21. data/lib/theme_check/checks/unused_snippet.rb +7 -7
  22. data/lib/theme_check/checks/valid_json.rb +1 -1
  23. data/lib/theme_check/checks.rb +4 -2
  24. data/lib/theme_check/cli.rb +1 -1
  25. data/lib/theme_check/corrector.rb +6 -6
  26. data/lib/theme_check/disabled_check.rb +3 -3
  27. data/lib/theme_check/disabled_checks.rb +9 -9
  28. data/lib/theme_check/exceptions.rb +1 -0
  29. data/lib/theme_check/file_system_storage.rb +4 -0
  30. data/lib/theme_check/html_node.rb +36 -28
  31. data/lib/theme_check/html_visitor.rb +6 -6
  32. data/lib/theme_check/in_memory_storage.rb +1 -1
  33. data/lib/theme_check/json_check.rb +2 -2
  34. data/lib/theme_check/language_server/bridge.rb +128 -0
  35. data/lib/theme_check/language_server/channel.rb +69 -0
  36. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +3 -1
  37. data/lib/theme_check/language_server/diagnostics_engine.rb +125 -0
  38. data/lib/theme_check/language_server/diagnostics_tracker.rb +8 -8
  39. data/lib/theme_check/language_server/handler.rb +20 -117
  40. data/lib/theme_check/language_server/io_messenger.rb +97 -0
  41. data/lib/theme_check/language_server/messenger.rb +27 -0
  42. data/lib/theme_check/language_server/server.rb +95 -104
  43. data/lib/theme_check/language_server.rb +6 -1
  44. data/lib/theme_check/{template.rb → liquid_file.rb} +2 -2
  45. data/lib/theme_check/liquid_node.rb +291 -0
  46. data/lib/theme_check/{visitor.rb → liquid_visitor.rb} +4 -4
  47. data/lib/theme_check/locale_diff.rb +14 -7
  48. data/lib/theme_check/node.rb +12 -225
  49. data/lib/theme_check/offense.rb +15 -15
  50. data/lib/theme_check/position.rb +1 -1
  51. data/lib/theme_check/shopify_liquid/system_translations.rb +35 -0
  52. data/lib/theme_check/shopify_liquid/tag.rb +19 -1
  53. data/lib/theme_check/shopify_liquid.rb +1 -0
  54. data/lib/theme_check/theme.rb +1 -1
  55. data/lib/theme_check/{template_rewriter.rb → theme_file_rewriter.rb} +1 -1
  56. data/lib/theme_check/version.rb +1 -1
  57. data/lib/theme_check.rb +11 -10
  58. data/theme-check.gemspec +1 -1
  59. metadata +14 -7
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ # A node from the Liquid AST, the result of parsing a liquid file.
5
+ class LiquidNode < Node
6
+ attr_reader :value, :parent, :theme_file
7
+
8
+ def initialize(value, parent, theme_file)
9
+ raise ArgumentError, "Expected a Liquid AST Node" if value.is_a?(LiquidNode)
10
+ @value = value
11
+ @parent = parent
12
+ @theme_file = theme_file
13
+ @tag_markup = nil
14
+ @line_number_offset = 0
15
+ end
16
+
17
+ # Array of children nodes.
18
+ def children
19
+ @children ||= begin
20
+ nodes =
21
+ if comment?
22
+ []
23
+ elsif defined?(@value.class::ParseTreeVisitor)
24
+ @value.class::ParseTreeVisitor.new(@value, {}).children
25
+ elsif @value.respond_to?(:nodelist)
26
+ Array(@value.nodelist)
27
+ else
28
+ []
29
+ end
30
+ # Work around a bug in Liquid::Variable::ParseTreeVisitor that doesn't return
31
+ # the args in a hash as children nodes.
32
+ nodes = nodes.flat_map do |node|
33
+ case node
34
+ when Hash
35
+ node.values
36
+ else
37
+ node
38
+ end
39
+ end
40
+ nodes.map { |node| LiquidNode.new(node, self, @theme_file) }
41
+ end
42
+ end
43
+
44
+ # The original source code of the node. Doesn't contain wrapping braces.
45
+ def markup
46
+ if tag?
47
+ tag_markup
48
+ elsif @value.instance_variable_defined?(:@markup)
49
+ @value.instance_variable_get(:@markup)
50
+ end
51
+ end
52
+
53
+ def markup=(markup)
54
+ if @value.instance_variable_defined?(:@markup)
55
+ @value.instance_variable_set(:@markup, markup)
56
+ end
57
+ end
58
+
59
+ # Most nodes have a line number, but it's not guaranteed.
60
+ def line_number
61
+ if tag? && @value.respond_to?(:line_number)
62
+ markup # initialize the line_number_offset
63
+ @value.line_number - @line_number_offset
64
+ elsif @value.respond_to?(:line_number)
65
+ @value.line_number
66
+ end
67
+ end
68
+
69
+ def start_index
70
+ position.start_index
71
+ end
72
+
73
+ def end_index
74
+ position.end_index
75
+ end
76
+
77
+ # Literals are hard-coded values in the liquid file.
78
+ def literal?
79
+ @value.is_a?(String) || @value.is_a?(Integer)
80
+ end
81
+
82
+ # A {% tag %} node?
83
+ def tag?
84
+ @value.is_a?(Liquid::Tag)
85
+ end
86
+
87
+ def variable?
88
+ @value.is_a?(Liquid::Variable)
89
+ end
90
+
91
+ # A {% comment %} block node?
92
+ def comment?
93
+ @value.is_a?(Liquid::Comment)
94
+ end
95
+
96
+ # Top level node of every liquid_file.
97
+ def document?
98
+ @value.is_a?(Liquid::Document)
99
+ end
100
+ alias_method :root?, :document?
101
+
102
+ # A {% tag %}...{% endtag %} node?
103
+ def block_tag?
104
+ @value.is_a?(Liquid::Block)
105
+ end
106
+
107
+ # The body of blocks
108
+ def block_body?
109
+ @value.is_a?(Liquid::BlockBody)
110
+ end
111
+
112
+ # A block of type of node?
113
+ def block?
114
+ block_tag? || block_body? || document?
115
+ end
116
+
117
+ # The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
118
+ # and `after_<type_name>` check methods.
119
+ def type_name
120
+ @type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
121
+ end
122
+
123
+ def source
124
+ theme_file&.source
125
+ end
126
+
127
+ WHITESPACE = /\s/
128
+
129
+ # Is this node inside a `{% liquid ... %}` block?
130
+ def inside_liquid_tag?
131
+ # What we're doing here is starting at the start of the tag and
132
+ # backtrack on all the whitespace until we land on something. If
133
+ # that something is {% or %-, then we can safely assume that
134
+ # we're inside a full tag and not a liquid tag.
135
+ @inside_liquid_tag ||= if tag? && start_index && source
136
+ i = 1
137
+ i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
138
+ first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
139
+ first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
140
+ else
141
+ false
142
+ end
143
+ end
144
+
145
+ # Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-
146
+ def whitespace_trimmed_start?
147
+ @whitespace_trimmed_start ||= if start_index && source && !inside_liquid_tag?
148
+ i = 1
149
+ i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
150
+ source[start_index - i] == "-"
151
+ else
152
+ false
153
+ end
154
+ end
155
+
156
+ # Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}
157
+ def whitespace_trimmed_end?
158
+ @whitespace_trimmed_end ||= if end_index && source && !inside_liquid_tag?
159
+ i = 0
160
+ i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
161
+ source[end_index + i] == "-"
162
+ else
163
+ false
164
+ end
165
+ end
166
+
167
+ def start_token
168
+ return "" if inside_liquid_tag?
169
+ output = ""
170
+ output += "{{" if variable?
171
+ output += "{%" if tag?
172
+ output += "-" if whitespace_trimmed_start?
173
+ output
174
+ end
175
+
176
+ def end_token
177
+ return "" if inside_liquid_tag?
178
+ output = ""
179
+ output += "-" if whitespace_trimmed_end?
180
+ output += "}}" if variable?
181
+ output += "%}" if tag?
182
+ output
183
+ end
184
+
185
+ private
186
+
187
+ def position
188
+ @position ||= Position.new(
189
+ markup,
190
+ theme_file&.source,
191
+ line_number_1_indexed: line_number
192
+ )
193
+ end
194
+
195
+ # Here we're hacking around a glorious bug in Liquid that makes it so the
196
+ # line_number and markup of a tag is wrong if there's whitespace
197
+ # between the tag_name and the markup of the tag.
198
+ #
199
+ # {%
200
+ # render
201
+ # 'foo'
202
+ # %}
203
+ #
204
+ # Returns a raw value of "render 'foo'\n".
205
+ # The "\n " between render and 'foo' got replaced by a single space.
206
+ #
207
+ # And the line number is the one of 'foo'\n%}. Yay!
208
+ #
209
+ # This breaks any kind of position logic we have since that string
210
+ # does not exist in the theme_file.
211
+ def tag_markup
212
+ return @tag_markup if @tag_markup
213
+
214
+ l = 1
215
+ scanner = StringScanner.new(source)
216
+ scanner.scan_until(/\n/) while l < @value.line_number && (l += 1)
217
+ start = scanner.charpos
218
+
219
+ tag_name = @value.tag_name
220
+ tag_markup = @value.instance_variable_get('@markup')
221
+
222
+ # This is tricky, if the tag_markup is empty, then the tag could
223
+ # either start on a previous line, or the tag could start on the
224
+ # same line.
225
+ #
226
+ # Consider this:
227
+ # 1 {%
228
+ # 2 comment
229
+ # 3 %}{% endcomment %}{%comment%}
230
+ #
231
+ # Both comments would markup == "" AND line_number == 3
232
+ #
233
+ # There's no way to determine which one is the correct one, but
234
+ # we'll try our best to at least give you one.
235
+ #
236
+ # To screw with you even more, the name of the tag could be
237
+ # outside of a tag on the same line :) But I won't do anything
238
+ # about that (yet?).
239
+ #
240
+ # {% comment
241
+ # %}comment{% endcomment %}
242
+ if tag_markup.empty?
243
+ eol = source.index("\n", start) || source.size
244
+
245
+ # OK here I'm trying one of two things. Either tag_start is on
246
+ # the same line OR tag_start is on a previous line. The line
247
+ # number would be at the end of the whitespace after tag_name.
248
+ unless (tag_start = source.index(tag_name, start)) && tag_start < eol
249
+ tag_start = start
250
+ tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
251
+ tag_start -= @value.tag_name.size
252
+
253
+ # keep track of the error in line_number
254
+ @line_number_offset = source[tag_start...start].count("\n")
255
+ end
256
+ tag_end = tag_start + tag_name.size
257
+ tag_end += 1 while source[tag_end] =~ WHITESPACE
258
+
259
+ # return the real raw content
260
+ @tag_markup = source[tag_start...tag_end]
261
+ return @tag_markup
262
+
263
+ # See https://github.com/Shopify/theme-check/pull/423/files#r701936559 for a detailed explanation
264
+ # of why we're doing the check below.
265
+ #
266
+ # TL;DR it's because line_numbers are not enough to accurately
267
+ # determine the position of the raw markup and because that
268
+ # markup could be present on the same line outside of a Tag. e.g.
269
+ #
270
+ # uhoh {% if uhoh %}
271
+ elsif (match = /#{tag_name} +#{Regexp.escape(tag_markup)}/.match(source, start))
272
+ return @tag_markup = match[0]
273
+ end
274
+
275
+ # find the markup
276
+ markup_start = source.index(tag_markup, start)
277
+ markup_end = markup_start + tag_markup.size
278
+
279
+ # go back until you find the tag_name
280
+ tag_start = markup_start
281
+ tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
282
+ tag_start -= tag_name.size
283
+
284
+ # keep track of the error in line_number
285
+ @line_number_offset = source[tag_start...markup_start].count("\n")
286
+
287
+ # return the real raw content
288
+ @tag_markup = source[tag_start...markup_end]
289
+ end
290
+ end
291
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- class Visitor
3
+ class LiquidVisitor
4
4
  attr_reader :checks
5
5
 
6
6
  def initialize(checks, disabled_checks)
@@ -8,10 +8,10 @@ module ThemeCheck
8
8
  @disabled_checks = disabled_checks
9
9
  end
10
10
 
11
- def visit_template(template)
12
- visit(Node.new(template.root, nil, template))
11
+ def visit_liquid_file(liquid_file)
12
+ visit(LiquidNode.new(liquid_file.root, nil, liquid_file))
13
13
  rescue Liquid::Error => exception
14
- exception.template_name = template.name
14
+ exception.template_name = liquid_file.name
15
15
  call_checks(:on_error, exception)
16
16
  end
17
17
 
@@ -14,26 +14,26 @@ module ThemeCheck
14
14
  visit_object(@default, @other, [])
15
15
  end
16
16
 
17
- def add_as_offenses(check, key_prefix: [], node: nil, template: nil)
17
+ def add_as_offenses(check, key_prefix: [], node: nil, theme_file: nil)
18
18
  if extra_keys.any?
19
19
  add_keys_offense(check, "Extra translation keys", extra_keys,
20
- key_prefix: key_prefix, node: node, template: template)
20
+ key_prefix: key_prefix, node: node, theme_file: theme_file)
21
21
  end
22
22
 
23
23
  if missing_keys.any?
24
24
  add_keys_offense(check, "Missing translation keys", missing_keys,
25
- key_prefix: key_prefix, node: node, template: template)
25
+ key_prefix: key_prefix, node: node, theme_file: theme_file)
26
26
  end
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def add_keys_offense(check, cause, keys, key_prefix:, node: nil, template: nil)
31
+ def add_keys_offense(check, cause, keys, key_prefix:, node: nil, theme_file: nil)
32
32
  message = "#{cause}: #{format_keys(key_prefix, keys)}"
33
33
  if node
34
34
  check.add_offense(message, node: node)
35
35
  else
36
- check.add_offense(message, template: template)
36
+ check.add_offense(message, theme_file: theme_file)
37
37
  end
38
38
  end
39
39
 
@@ -46,10 +46,12 @@ module ThemeCheck
46
46
  other = {} unless other.is_a?(Hash)
47
47
  return if pluralization?(default) && pluralization?(other)
48
48
 
49
- @extra_keys += (other.keys - default.keys).map { |key| path + [key] }
49
+ shopify_translations = system_translations(path)
50
+
51
+ @extra_keys += (other.keys - default.keys - shopify_translations.keys).map { |key| path + [key] }
50
52
 
51
53
  default.each do |key, default_value|
52
- translated_value = other[key]
54
+ translated_value = other[key] || shopify_translations[key]
53
55
  new_path = path + [key]
54
56
 
55
57
  if translated_value.nil?
@@ -65,5 +67,10 @@ module ThemeCheck
65
67
  PLURALIZATION_KEYS.include?(key) && !value.is_a?(Hash)
66
68
  end
67
69
  end
70
+
71
+ def system_translations(path)
72
+ return ShopifyLiquid::SystemTranslations.translations_hash if path.empty?
73
+ ShopifyLiquid::SystemTranslations.translations_hash.dig(*path) || {}
74
+ end
68
75
  end
69
76
  end
@@ -1,250 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThemeCheck
4
- # A node from the Liquid AST, the result of parsing a template.
5
4
  class Node
6
- attr_reader :value, :parent, :template
7
-
8
- def initialize(value, parent, template)
9
- raise ArgumentError, "Expected a Liquid AST Node" if value.is_a?(Node)
10
- @value = value
11
- @parent = parent
12
- @template = template
13
- @tag_markup = nil
14
- @line_number_offset = 0
5
+ def parent
6
+ raise NotImplementedError
15
7
  end
16
8
 
17
- # The original source code of the node. Doesn't contain wrapping braces.
18
- def markup
19
- if tag?
20
- tag_markup
21
- elsif @value.instance_variable_defined?(:@markup)
22
- @value.instance_variable_get(:@markup)
23
- end
9
+ def theme_file
10
+ raise NotImplementedError
24
11
  end
25
12
 
26
- def markup=(markup)
27
- if @value.instance_variable_defined?(:@markup)
28
- @value.instance_variable_set(:@markup, markup)
29
- end
13
+ def value
14
+ raise NotImplementedError
30
15
  end
31
16
 
32
- # Array of children nodes.
33
17
  def children
34
- @children ||= begin
35
- nodes =
36
- if comment?
37
- []
38
- elsif defined?(@value.class::ParseTreeVisitor)
39
- @value.class::ParseTreeVisitor.new(@value, {}).children
40
- elsif @value.respond_to?(:nodelist)
41
- Array(@value.nodelist)
42
- else
43
- []
44
- end
45
- # Work around a bug in Liquid::Variable::ParseTreeVisitor that doesn't return
46
- # the args in a hash as children nodes.
47
- nodes = nodes.flat_map do |node|
48
- case node
49
- when Hash
50
- node.values
51
- else
52
- node
53
- end
54
- end
55
- nodes.map { |node| Node.new(node, self, @template) }
56
- end
57
- end
58
-
59
- # Literals are hard-coded values in the template.
60
- def literal?
61
- @value.is_a?(String) || @value.is_a?(Integer)
62
- end
63
-
64
- # A {% tag %} node?
65
- def tag?
66
- @value.is_a?(Liquid::Tag)
67
- end
68
-
69
- def variable?
70
- @value.is_a?(Liquid::Variable)
71
- end
72
-
73
- # A {% comment %} block node?
74
- def comment?
75
- @value.is_a?(Liquid::Comment)
76
- end
77
-
78
- # Top level node of every template.
79
- def document?
80
- @value.is_a?(Liquid::Document)
81
- end
82
- alias_method :root?, :document?
83
-
84
- # A {% tag %}...{% endtag %} node?
85
- def block_tag?
86
- @value.is_a?(Liquid::Block)
18
+ raise NotImplementedError
87
19
  end
88
20
 
89
- # The body of blocks
90
- def block_body?
91
- @value.is_a?(Liquid::BlockBody)
92
- end
93
-
94
- # A block of type of node?
95
- def block?
96
- block_tag? || block_body? || document?
21
+ def markup
22
+ raise NotImplementedError
97
23
  end
98
24
 
99
- # Most nodes have a line number, but it's not guaranteed.
100
25
  def line_number
101
- if tag? && @value.respond_to?(:line_number)
102
- markup # initialize the line_number_offset
103
- @value.line_number - @line_number_offset
104
- elsif @value.respond_to?(:line_number)
105
- @value.line_number
106
- end
107
- end
108
-
109
- # The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
110
- # and `after_<type_name>` check methods.
111
- def type_name
112
- @type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
113
- end
114
-
115
- def source
116
- template&.source
117
- end
118
-
119
- WHITESPACE = /\s/
120
-
121
- # Is this node inside a `{% liquid ... %}` block?
122
- def inside_liquid_tag?
123
- # What we're doing here is starting at the start of the tag and
124
- # backtrack on all the whitespace until we land on something. If
125
- # that something is {% or %-, then we can safely assume that
126
- # we're inside a full tag and not a liquid tag.
127
- @inside_liquid_tag ||= if tag? && line_number && source
128
- i = 1
129
- i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
130
- first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
131
- first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
132
- else
133
- false
134
- end
135
- end
136
-
137
- # Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-
138
- def whitespace_trimmed_start?
139
- @whitespace_trimmed_start ||= if line_number && source && !inside_liquid_tag?
140
- i = 1
141
- i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
142
- source[start_index - i] == "-"
143
- else
144
- false
145
- end
146
- end
147
-
148
- # Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}
149
- def whitespace_trimmed_end?
150
- @whitespace_trimmed_end ||= if line_number && source && !inside_liquid_tag?
151
- i = 0
152
- i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
153
- source[end_index + i] == "-"
154
- else
155
- false
156
- end
157
- end
158
-
159
- def position
160
- @position ||= Position.new(
161
- markup,
162
- template&.source,
163
- line_number_1_indexed: line_number
164
- )
26
+ raise NotImplementedError
165
27
  end
166
28
 
167
29
  def start_index
168
- position.start_index
30
+ raise NotImplementedError
169
31
  end
170
32
 
171
33
  def end_index
172
- position.end_index
173
- end
174
-
175
- def start_token
176
- return "" if inside_liquid_tag?
177
- output = ""
178
- output += "{{" if variable?
179
- output += "{%" if tag?
180
- output += "-" if whitespace_trimmed_start?
181
- output
182
- end
183
-
184
- def end_token
185
- return "" if inside_liquid_tag?
186
- output = ""
187
- output += "-" if whitespace_trimmed_end?
188
- output += "}}" if variable?
189
- output += "%}" if tag?
190
- output
191
- end
192
-
193
- private
194
-
195
- # Here we're hacking around a glorious bug in Liquid that makes it so the
196
- # line_number and markup of a tag is wrong if there's whitespace
197
- # between the tag_name and the markup of the tag.
198
- #
199
- # {%
200
- # render
201
- # 'foo'
202
- # %}
203
- #
204
- # Returns a raw value of "render 'foo'\n".
205
- # The "\n " between render and 'foo' got replaced by a single space.
206
- #
207
- # And the line number is the one of 'foo'\n%}. Yay!
208
- #
209
- # This breaks any kind of position logic we have since that string
210
- # does not exist in the template.
211
- def tag_markup
212
- return @value.raw if @value.instance_variable_get('@markup').empty?
213
- return @tag_markup if @tag_markup
214
-
215
- l = 1
216
- scanner = StringScanner.new(source)
217
- scanner.scan_until(/\n/) while l < @value.line_number && (l += 1)
218
- start = scanner.charpos
219
-
220
- tag_markup = @value.instance_variable_get('@markup')
221
-
222
- # See https://github.com/Shopify/theme-check/pull/423/files#r701936559 for a detailed explanation
223
- # of why we're doing the check below.
224
- #
225
- # TL;DR it's because line_numbers are not enough to accurately
226
- # determine the position of the raw markup and because that
227
- # markup could be present on the same line outside of a Tag. e.g.
228
- #
229
- # uhoh {% if uhoh %}
230
- if (match = /#{@value.tag_name} +#{Regexp.escape(tag_markup)}/.match(source, start))
231
- return @tag_markup = match[0]
232
- end
233
-
234
- # find the markup
235
- markup_start = source.index(tag_markup, start)
236
- markup_end = markup_start + tag_markup.size
237
-
238
- # go back until you find the tag_name
239
- tag_start = markup_start
240
- tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
241
- tag_start -= @value.tag_name.size
242
-
243
- # keep track of the error in line_number
244
- @line_number_offset = source[tag_start...markup_start].count("\n")
245
-
246
- # return the real raw content
247
- @tag_markup = source[tag_start...markup_end]
34
+ raise NotImplementedError
248
35
  end
249
36
  end
250
37
  end