theme-check 0.8.0 → 0.9.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.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +3 -0
- data/CHANGELOG.md +44 -0
- data/CONTRIBUTING.md +2 -1
- data/README.md +4 -1
- data/RELEASING.md +5 -3
- data/config/default.yml +42 -1
- data/data/shopify_liquid/tags.yml +3 -0
- data/data/shopify_translation_keys.yml +1 -0
- data/docs/checks/asset_url_filters.md +56 -0
- data/docs/checks/content_for_header_modification.md +42 -0
- data/docs/checks/nested_snippet.md +1 -1
- data/docs/checks/parser_blocking_script_tag.md +53 -0
- data/docs/checks/space_inside_braces.md +28 -0
- data/exe/theme-check-language-server +1 -2
- data/lib/theme_check.rb +13 -1
- data/lib/theme_check/analyzer.rb +79 -13
- data/lib/theme_check/bug.rb +20 -0
- data/lib/theme_check/check.rb +36 -7
- data/lib/theme_check/checks.rb +47 -8
- data/lib/theme_check/checks/asset_url_filters.rb +46 -0
- data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
- data/lib/theme_check/checks/img_width_and_height.rb +18 -49
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
- data/lib/theme_check/checks/missing_template.rb +1 -0
- data/lib/theme_check/checks/nested_snippet.rb +1 -1
- data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
- data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
- data/lib/theme_check/checks/remote_asset.rb +21 -79
- data/lib/theme_check/checks/space_inside_braces.rb +8 -2
- data/lib/theme_check/checks/template_length.rb +3 -0
- data/lib/theme_check/checks/valid_html_translation.rb +1 -0
- data/lib/theme_check/config.rb +2 -0
- data/lib/theme_check/disabled_check.rb +41 -0
- data/lib/theme_check/disabled_checks.rb +33 -29
- data/lib/theme_check/exceptions.rb +32 -0
- data/lib/theme_check/html_check.rb +7 -0
- data/lib/theme_check/html_node.rb +56 -0
- data/lib/theme_check/html_visitor.rb +38 -0
- data/lib/theme_check/json_file.rb +13 -1
- data/lib/theme_check/language_server.rb +2 -1
- data/lib/theme_check/language_server/completion_engine.rb +1 -1
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -8
- data/lib/theme_check/language_server/constants.rb +5 -1
- data/lib/theme_check/language_server/diagnostics_tracker.rb +64 -0
- data/lib/theme_check/language_server/document_link_engine.rb +2 -2
- data/lib/theme_check/language_server/handler.rb +63 -50
- data/lib/theme_check/language_server/server.rb +1 -1
- data/lib/theme_check/language_server/variable_lookup_finder.rb +295 -0
- data/lib/theme_check/liquid_check.rb +1 -4
- data/lib/theme_check/node.rb +12 -0
- data/lib/theme_check/offense.rb +30 -46
- data/lib/theme_check/position.rb +77 -0
- data/lib/theme_check/position_helper.rb +37 -0
- data/lib/theme_check/remote_asset_file.rb +3 -0
- data/lib/theme_check/shopify_liquid/tag.rb +13 -0
- data/lib/theme_check/template.rb +8 -0
- data/lib/theme_check/theme.rb +7 -2
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check/visitor.rb +4 -14
- metadata +19 -4
- data/lib/theme_check/language_server/position_helper.rb +0 -27
@@ -52,7 +52,7 @@ module ThemeCheck
|
|
52
52
|
response_body = JSON.dump(response)
|
53
53
|
log(JSON.pretty_generate(response)) if $DEBUG
|
54
54
|
|
55
|
-
@out.write("Content-Length: #{response_body.
|
55
|
+
@out.write("Content-Length: #{response_body.bytesize}\r\n")
|
56
56
|
@out.write("\r\n")
|
57
57
|
@out.write(response_body)
|
58
58
|
@out.flush
|
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
module VariableLookupFinder
|
6
|
+
extend self
|
7
|
+
|
8
|
+
UNCLOSED_SQUARE_BRACKET = /\[[^\]]*\Z/
|
9
|
+
ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED = %r{
|
10
|
+
(
|
11
|
+
# quotes not preceded by a [
|
12
|
+
(?<!\[)['"]|
|
13
|
+
# closing ]
|
14
|
+
\]|
|
15
|
+
# opening [
|
16
|
+
\[
|
17
|
+
)$
|
18
|
+
}x
|
19
|
+
|
20
|
+
VARIABLE_LOOKUP_CHARACTERS = /[a-z0-9_.'"\]\[]/i
|
21
|
+
VARIABLE_LOOKUP = /#{VARIABLE_LOOKUP_CHARACTERS}+/o
|
22
|
+
SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS = %r{
|
23
|
+
(?:
|
24
|
+
\s(?:
|
25
|
+
if|elsif|unless|and|or|#{Liquid::Condition.operators.keys.join("|")}
|
26
|
+
|echo
|
27
|
+
|case|when
|
28
|
+
|cycle
|
29
|
+
|in
|
30
|
+
)
|
31
|
+
|[:,=]
|
32
|
+
)
|
33
|
+
\s+
|
34
|
+
}omix
|
35
|
+
ENDS_WITH_BLANK_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}$/oimx
|
36
|
+
ENDS_WITH_POTENTIAL_LOOKUP = /#{SYMBOLS_PRECEDING_POTENTIAL_LOOKUPS}#{VARIABLE_LOOKUP}$/oimx
|
37
|
+
|
38
|
+
def lookup(content, cursor)
|
39
|
+
return if cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
|
40
|
+
potential_lookup = lookup_liquid_variable(content, cursor) || lookup_liquid_tag(content, cursor)
|
41
|
+
|
42
|
+
# And we only return it if it's parsed by Liquid as VariableLookup
|
43
|
+
return unless potential_lookup.is_a?(Liquid::VariableLookup)
|
44
|
+
potential_lookup
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def cursor_is_on_bracket_position_that_cant_be_completed(content, cursor)
|
50
|
+
content[0..cursor - 1] =~ ENDS_IN_BRACKET_POSITION_THAT_CANT_BE_COMPLETED
|
51
|
+
end
|
52
|
+
|
53
|
+
def cursor_is_on_liquid_variable_lookup_position(content, cursor)
|
54
|
+
previous_char = content[cursor - 1]
|
55
|
+
is_liquid_variable = content =~ Liquid::VariableStart
|
56
|
+
is_in_variable_segment = previous_char =~ VARIABLE_LOOKUP_CHARACTERS
|
57
|
+
is_on_blank_variable_lookup_position = content[0..cursor - 1] =~ /[{:,-]\s+$/
|
58
|
+
(
|
59
|
+
is_liquid_variable && (
|
60
|
+
is_in_variable_segment ||
|
61
|
+
is_on_blank_variable_lookup_position
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_liquid_variable(content, cursor)
|
67
|
+
return unless cursor_is_on_liquid_variable_lookup_position(content, cursor)
|
68
|
+
start_index = content.match(/#{Liquid::VariableStart}-?/o).end(0) + 1
|
69
|
+
end_index = cursor - 1
|
70
|
+
|
71
|
+
# We take the following content
|
72
|
+
# - start after the first two {{
|
73
|
+
# - end at cursor position
|
74
|
+
#
|
75
|
+
# That way, we'll have a partial liquid variable that
|
76
|
+
# can be parsed such that the "last" variable_lookup
|
77
|
+
# will be the one we're trying to complete.
|
78
|
+
markup = content[start_index..end_index]
|
79
|
+
|
80
|
+
# Early return for incomplete variables
|
81
|
+
return empty_lookup if markup =~ /\s+$/
|
82
|
+
|
83
|
+
# Now we go to hack city... The cursor might be in the middle
|
84
|
+
# of a string/square bracket lookup. We need to close those
|
85
|
+
# otherwise the variable parse won't work.
|
86
|
+
markup += "'" if markup.count("'").odd?
|
87
|
+
markup += '"' if markup.count('"').odd?
|
88
|
+
markup += "]" if markup =~ UNCLOSED_SQUARE_BRACKET
|
89
|
+
|
90
|
+
variable = variable_from_markup(markup)
|
91
|
+
|
92
|
+
variable_lookup_for_liquid_variable(variable)
|
93
|
+
end
|
94
|
+
|
95
|
+
def cursor_is_on_liquid_tag_lookup_position(content, cursor)
|
96
|
+
markup = content[0..cursor - 1]
|
97
|
+
is_liquid_tag = content.match?(Liquid::TagStart)
|
98
|
+
is_in_variable_segment = markup =~ ENDS_WITH_POTENTIAL_LOOKUP
|
99
|
+
is_on_blank_variable_lookup_position = markup =~ ENDS_WITH_BLANK_POTENTIAL_LOOKUP
|
100
|
+
(
|
101
|
+
is_liquid_tag && (
|
102
|
+
is_in_variable_segment ||
|
103
|
+
is_on_blank_variable_lookup_position
|
104
|
+
)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Context:
|
109
|
+
#
|
110
|
+
# We know full well that the code as it is being typed is probably not
|
111
|
+
# something that can be parsed by liquid.
|
112
|
+
#
|
113
|
+
# How this works:
|
114
|
+
#
|
115
|
+
# 1. Attempt to turn the code of the token until the cursor position into
|
116
|
+
# valid liquid code with some hacks.
|
117
|
+
# 2. If the code ends in space at a "potential lookup" spot
|
118
|
+
# a. Then return an empty variable lookup
|
119
|
+
# 3. Parse the valid liquid code
|
120
|
+
# 4. Attempt to extract a VariableLookup from Liquid::Template
|
121
|
+
def lookup_liquid_tag(content, cursor)
|
122
|
+
return unless cursor_is_on_liquid_tag_lookup_position(content, cursor)
|
123
|
+
|
124
|
+
markup = parseable_markup(content, cursor)
|
125
|
+
return empty_lookup if markup == :empty_lookup_markup
|
126
|
+
|
127
|
+
template = Liquid::Template.parse(markup)
|
128
|
+
current_tag = template.root.nodelist[0]
|
129
|
+
|
130
|
+
case current_tag.tag_name
|
131
|
+
when "if", "unless"
|
132
|
+
variable_lookup_for_if_tag(current_tag)
|
133
|
+
when "case"
|
134
|
+
variable_lookup_for_case_tag(current_tag)
|
135
|
+
when "cycle"
|
136
|
+
variable_lookup_for_cycle_tag(current_tag)
|
137
|
+
when "for"
|
138
|
+
variable_lookup_for_for_tag(current_tag)
|
139
|
+
when "tablerow"
|
140
|
+
variable_lookup_for_tablerow_tag(current_tag)
|
141
|
+
when "render"
|
142
|
+
variable_lookup_for_render_tag(current_tag)
|
143
|
+
when "assign"
|
144
|
+
variable_lookup_for_assign_tag(current_tag)
|
145
|
+
when "echo"
|
146
|
+
variable_lookup_for_echo_tag(current_tag)
|
147
|
+
end
|
148
|
+
|
149
|
+
# rubocop:disable Style/RedundantReturn
|
150
|
+
rescue Liquid::SyntaxError
|
151
|
+
# We don't complete variable for liquid syntax errors
|
152
|
+
return
|
153
|
+
end
|
154
|
+
# rubocop:enable Style/RedundantReturn
|
155
|
+
|
156
|
+
def parseable_markup(content, cursor)
|
157
|
+
start_index = 0
|
158
|
+
end_index = cursor - 1
|
159
|
+
markup = content[start_index..end_index]
|
160
|
+
|
161
|
+
# Welcome to Hackcity
|
162
|
+
markup += "'" if markup.count("'").odd?
|
163
|
+
markup += '"' if markup.count('"').odd?
|
164
|
+
markup += "]" if markup =~ UNCLOSED_SQUARE_BRACKET
|
165
|
+
|
166
|
+
# Now check if it's a liquid tag
|
167
|
+
is_liquid_tag = markup =~ tag_regex('liquid')
|
168
|
+
ends_with_blank_potential_lookup = markup =~ ENDS_WITH_BLANK_POTENTIAL_LOOKUP
|
169
|
+
last_line = markup.rstrip.lines.last
|
170
|
+
markup = "{% #{last_line}" if is_liquid_tag
|
171
|
+
|
172
|
+
# Close the tag
|
173
|
+
markup += ' %}'
|
174
|
+
|
175
|
+
# if statements
|
176
|
+
is_if_tag = markup =~ tag_regex('if')
|
177
|
+
return :empty_lookup_markup if is_if_tag && ends_with_blank_potential_lookup
|
178
|
+
markup += '{% endif %}' if is_if_tag
|
179
|
+
|
180
|
+
# unless statements
|
181
|
+
is_unless_tag = markup =~ tag_regex('unless')
|
182
|
+
return :empty_lookup_markup if is_unless_tag && ends_with_blank_potential_lookup
|
183
|
+
markup += '{% endunless %}' if is_unless_tag
|
184
|
+
|
185
|
+
# elsif statements
|
186
|
+
is_elsif_tag = markup =~ tag_regex('elsif')
|
187
|
+
return :empty_lookup_markup if is_elsif_tag && ends_with_blank_potential_lookup
|
188
|
+
markup = '{% if x %}' + markup + '{% endif %}' if is_elsif_tag
|
189
|
+
|
190
|
+
# case statements
|
191
|
+
is_case_tag = markup =~ tag_regex('case')
|
192
|
+
return :empty_lookup_markup if is_case_tag && ends_with_blank_potential_lookup
|
193
|
+
markup += "{% endcase %}" if is_case_tag
|
194
|
+
|
195
|
+
# when
|
196
|
+
is_when_tag = markup =~ tag_regex('when')
|
197
|
+
return :empty_lookup_markup if is_when_tag && ends_with_blank_potential_lookup
|
198
|
+
markup = "{% case x %}" + markup + "{% endcase %}" if is_when_tag
|
199
|
+
|
200
|
+
# for statements
|
201
|
+
is_for_tag = markup =~ tag_regex('for')
|
202
|
+
return :empty_lookup_markup if is_for_tag && ends_with_blank_potential_lookup
|
203
|
+
markup += "{% endfor %}" if is_for_tag
|
204
|
+
|
205
|
+
# tablerow statements
|
206
|
+
is_tablerow_tag = markup =~ tag_regex('tablerow')
|
207
|
+
return :empty_lookup_markup if is_tablerow_tag && ends_with_blank_potential_lookup
|
208
|
+
markup += "{% endtablerow %}" if is_tablerow_tag
|
209
|
+
|
210
|
+
markup
|
211
|
+
end
|
212
|
+
|
213
|
+
def variable_lookup_for_if_tag(if_tag)
|
214
|
+
condition = if_tag.blocks.last
|
215
|
+
variable_lookup_for_condition(condition)
|
216
|
+
end
|
217
|
+
|
218
|
+
def variable_lookup_for_condition(condition)
|
219
|
+
return variable_lookup_for_condition(condition.child_condition) if condition.child_condition
|
220
|
+
return condition.right if condition.right
|
221
|
+
condition.left
|
222
|
+
end
|
223
|
+
|
224
|
+
def variable_lookup_for_case_tag(case_tag)
|
225
|
+
return variable_lookup_for_case_block(case_tag.blocks.last) unless case_tag.blocks.empty?
|
226
|
+
case_tag.left
|
227
|
+
end
|
228
|
+
|
229
|
+
def variable_lookup_for_case_block(condition)
|
230
|
+
condition.right
|
231
|
+
end
|
232
|
+
|
233
|
+
def variable_lookup_for_cycle_tag(cycle_tag)
|
234
|
+
cycle_tag.variables.last
|
235
|
+
end
|
236
|
+
|
237
|
+
def variable_lookup_for_for_tag(for_tag)
|
238
|
+
for_tag.collection_name
|
239
|
+
end
|
240
|
+
|
241
|
+
def variable_lookup_for_tablerow_tag(tablerow_tag)
|
242
|
+
tablerow_tag.collection_name
|
243
|
+
end
|
244
|
+
|
245
|
+
def variable_lookup_for_render_tag(render_tag)
|
246
|
+
return empty_lookup if render_tag.raw =~ /:\s*$/
|
247
|
+
render_tag.attributes.values.last
|
248
|
+
end
|
249
|
+
|
250
|
+
def variable_lookup_for_assign_tag(assign_tag)
|
251
|
+
variable_lookup_for_liquid_variable(assign_tag.from)
|
252
|
+
end
|
253
|
+
|
254
|
+
def variable_lookup_for_echo_tag(echo_tag)
|
255
|
+
variable_lookup_for_liquid_variable(echo_tag.variable)
|
256
|
+
end
|
257
|
+
|
258
|
+
def variable_lookup_for_liquid_variable(variable)
|
259
|
+
has_filters = !variable.filters.empty?
|
260
|
+
|
261
|
+
# Can complete after trailing comma or :
|
262
|
+
if has_filters && variable.raw =~ /[:,]\s*$/
|
263
|
+
empty_lookup
|
264
|
+
elsif has_filters
|
265
|
+
last_filter_argument(variable.filters)
|
266
|
+
elsif variable.name.nil?
|
267
|
+
empty_lookup
|
268
|
+
else
|
269
|
+
variable.name
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def empty_lookup
|
274
|
+
Liquid::VariableLookup.parse('')
|
275
|
+
end
|
276
|
+
|
277
|
+
# We want the last thing in variable.filters which is at most
|
278
|
+
# an array that looks like [name, positional_args, hash_arg]
|
279
|
+
def last_filter_argument(filters)
|
280
|
+
filter = filters.last
|
281
|
+
return filter[2].values.last if filter.size == 3
|
282
|
+
return filter[1].last if filter.size == 2
|
283
|
+
nil
|
284
|
+
end
|
285
|
+
|
286
|
+
def variable_from_markup(markup, parse_context = Liquid::ParseContext.new)
|
287
|
+
Liquid::Variable.new(markup, parse_context)
|
288
|
+
end
|
289
|
+
|
290
|
+
def tag_regex(tag)
|
291
|
+
ShopifyLiquid::Tag.tag_regex(tag)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -6,6 +6,7 @@ module ThemeCheck
|
|
6
6
|
extend ChecksTracking
|
7
7
|
include ParsingHelpers
|
8
8
|
|
9
|
+
# TODO: remove this once all regex checks are migrate to HtmlCheck# TODO: remove this once all regex checks are migrate to HtmlCheck
|
9
10
|
TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
|
10
11
|
VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
|
11
12
|
START_OR_END_QUOTE = /(^['"])|(['"]$)/
|
@@ -16,9 +17,5 @@ module ThemeCheck
|
|
16
17
|
ATTR = /[a-z0-9-]+/i
|
17
18
|
HTML_ATTRIBUTE = /#{ATTR}(?:=#{QUOTED_LIQUID_ATTRIBUTE})?/omix
|
18
19
|
HTML_ATTRIBUTES = /(?:#{HTML_ATTRIBUTE}|\s)*/omix
|
19
|
-
|
20
|
-
def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
|
21
|
-
offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
|
22
|
-
end
|
23
20
|
end
|
24
21
|
end
|
data/lib/theme_check/node.rb
CHANGED
@@ -125,5 +125,17 @@ module ThemeCheck
|
|
125
125
|
start = template.full_line(line_number).index(markup)
|
126
126
|
[start, start + markup.length - 1]
|
127
127
|
end
|
128
|
+
|
129
|
+
def position
|
130
|
+
@position ||= Position.new(markup, template&.source, line_number)
|
131
|
+
end
|
132
|
+
|
133
|
+
def start_index
|
134
|
+
position.start_index
|
135
|
+
end
|
136
|
+
|
137
|
+
def end_index
|
138
|
+
position.end_index
|
139
|
+
end
|
128
140
|
end
|
129
141
|
end
|
data/lib/theme_check/offense.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ThemeCheck
|
3
|
-
Position = Struct.new(:line, :column)
|
4
|
-
|
5
3
|
class Offense
|
6
4
|
MAX_SOURCE_EXCERPT_SIZE = 120
|
7
5
|
|
@@ -20,6 +18,7 @@ module ThemeCheck
|
|
20
18
|
end
|
21
19
|
|
22
20
|
@node = node
|
21
|
+
@template = nil
|
23
22
|
if node
|
24
23
|
@template = node.template
|
25
24
|
elsif template
|
@@ -40,8 +39,7 @@ module ThemeCheck
|
|
40
39
|
@node.line_number
|
41
40
|
end
|
42
41
|
|
43
|
-
@
|
44
|
-
@end_position = nil
|
42
|
+
@position = Position.new(@markup, @template&.source, @line_number)
|
45
43
|
end
|
46
44
|
|
47
45
|
def source_excerpt
|
@@ -56,20 +54,28 @@ module ThemeCheck
|
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
57
|
+
def start_index
|
58
|
+
@position.start_index
|
59
|
+
end
|
60
|
+
|
59
61
|
def start_line
|
60
|
-
|
62
|
+
@position.start_row
|
61
63
|
end
|
62
64
|
|
63
65
|
def start_column
|
64
|
-
|
66
|
+
@position.start_column
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_index
|
70
|
+
@position.end_index
|
65
71
|
end
|
66
72
|
|
67
73
|
def end_line
|
68
|
-
|
74
|
+
@position.end_row
|
69
75
|
end
|
70
76
|
|
71
77
|
def end_column
|
72
|
-
|
78
|
+
@position.end_column
|
73
79
|
end
|
74
80
|
|
75
81
|
def code_name
|
@@ -108,52 +114,30 @@ module ThemeCheck
|
|
108
114
|
end
|
109
115
|
end
|
110
116
|
|
111
|
-
def
|
112
|
-
|
113
|
-
"#{message} at #{location}"
|
114
|
-
else
|
115
|
-
message
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
def full_line(line)
|
122
|
-
# Liquid::Template is 1-indexed.
|
123
|
-
template.full_line(line + 1)
|
117
|
+
def whole_theme?
|
118
|
+
check.whole_theme?
|
124
119
|
end
|
125
120
|
|
126
|
-
def
|
127
|
-
|
121
|
+
def single_file?
|
122
|
+
check.single_file?
|
128
123
|
end
|
129
124
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
@start_position = position
|
125
|
+
def ==(other)
|
126
|
+
other.is_a?(Offense) &&
|
127
|
+
check == other.check &&
|
128
|
+
message == other.message &&
|
129
|
+
template == other.template &&
|
130
|
+
node == other.node &&
|
131
|
+
markup == other.markup &&
|
132
|
+
line_number == other.line_number
|
140
133
|
end
|
141
134
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
return @end_position if @end_position
|
146
|
-
return @end_position = Position.new(0, 0) unless line_number && markup
|
147
|
-
|
148
|
-
position = Position.new
|
149
|
-
position.line = start_line + lines_of_content.size - 1
|
150
|
-
position.column = if start_line == position.line
|
151
|
-
start_column + markup.size
|
135
|
+
def to_s
|
136
|
+
if template
|
137
|
+
"#{message} at #{location}"
|
152
138
|
else
|
153
|
-
|
139
|
+
message
|
154
140
|
end
|
155
|
-
|
156
|
-
@end_position = position
|
157
141
|
end
|
158
142
|
end
|
159
143
|
end
|