theme-check 1.7.2 → 1.9.2
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/.gitignore +1 -0
- data/CHANGELOG.md +47 -0
- data/README.md +10 -0
- data/RELEASING.md +13 -0
- data/config/default.yml +5 -0
- data/data/shopify_liquid/deprecated_filters.yml +4 -0
- data/data/shopify_liquid/filters.yml +3 -1
- data/docs/checks/TEMPLATE.md.erb +24 -19
- data/docs/checks/schema_json_format.md +76 -0
- data/docs/language_server/code-action-command-palette.png +0 -0
- data/docs/language_server/code-action-flow.png +0 -0
- data/docs/language_server/code-action-keyboard.png +0 -0
- data/docs/language_server/code-action-light-bulb.png +0 -0
- data/docs/language_server/code-action-problem.png +0 -0
- data/docs/language_server/code-action-quickfix.png +0 -0
- data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
- data/exe/theme-check-language-server +0 -4
- data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
- data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
- data/lib/theme_check/checks/asset_url_filters.rb +2 -0
- data/lib/theme_check/checks/default_locale.rb +1 -1
- data/lib/theme_check/checks/deprecated_filter.rb +81 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +14 -9
- data/lib/theme_check/checks/matching_translations.rb +1 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +1 -1
- data/lib/theme_check/checks/pagination_size.rb +2 -3
- data/lib/theme_check/checks/remote_asset.rb +5 -0
- data/lib/theme_check/checks/required_directories.rb +1 -1
- data/lib/theme_check/checks/required_layout_theme_object.rb +9 -4
- data/lib/theme_check/checks/schema_json_format.rb +29 -0
- data/lib/theme_check/checks/space_inside_braces.rb +132 -87
- data/lib/theme_check/checks/translation_key_exists.rb +33 -13
- data/lib/theme_check/checks/unused_assign.rb +3 -2
- data/lib/theme_check/checks/unused_snippet.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks/valid_schema.rb +2 -2
- data/lib/theme_check/corrector.rb +34 -23
- data/lib/theme_check/file_system_storage.rb +4 -3
- data/lib/theme_check/html_node.rb +122 -6
- data/lib/theme_check/html_visitor.rb +1 -32
- data/lib/theme_check/in_memory_storage.rb +9 -0
- data/lib/theme_check/json_helpers.rb +14 -0
- data/lib/theme_check/language_server/bridge.rb +19 -5
- data/lib/theme_check/language_server/client_capabilities.rb +27 -0
- data/lib/theme_check/language_server/code_action_engine.rb +32 -0
- data/lib/theme_check/language_server/code_action_provider.rb +42 -0
- data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
- data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
- data/lib/theme_check/language_server/configuration.rb +69 -0
- data/lib/theme_check/language_server/diagnostic.rb +124 -0
- data/lib/theme_check/language_server/diagnostics_engine.rb +15 -60
- data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
- data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
- data/lib/theme_check/language_server/document_link_provider.rb +6 -6
- data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
- data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
- data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
- data/lib/theme_check/language_server/handler.rb +83 -29
- data/lib/theme_check/language_server/io_messenger.rb +11 -1
- data/lib/theme_check/language_server/protocol.rb +4 -0
- data/lib/theme_check/language_server/server.rb +29 -11
- data/lib/theme_check/language_server/uri_helper.rb +1 -0
- data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
- data/lib/theme_check/language_server.rb +23 -5
- data/lib/theme_check/liquid_node.rb +255 -12
- data/lib/theme_check/locale_diff.rb +39 -8
- data/lib/theme_check/node.rb +16 -0
- data/lib/theme_check/offense.rb +27 -23
- data/lib/theme_check/position.rb +4 -4
- data/lib/theme_check/regex_helpers.rb +1 -1
- data/lib/theme_check/schema_helper.rb +70 -0
- data/lib/theme_check/storage.rb +4 -0
- data/lib/theme_check/tags.rb +0 -1
- data/lib/theme_check/theme.rb +1 -1
- data/lib/theme_check/theme_file.rb +8 -1
- data/lib/theme_check/theme_file_rewriter.rb +28 -6
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +11 -2
- metadata +26 -3
- data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
|
@@ -45,11 +45,47 @@ module ThemeCheck
|
|
|
45
45
|
def markup
|
|
46
46
|
if tag?
|
|
47
47
|
tag_markup
|
|
48
|
+
elsif literal?
|
|
49
|
+
value.to_s
|
|
48
50
|
elsif @value.instance_variable_defined?(:@markup)
|
|
49
51
|
@value.instance_variable_get(:@markup)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
# The original source code of the node. Does contain wrapping braces.
|
|
56
|
+
def outer_markup
|
|
57
|
+
if literal?
|
|
58
|
+
markup
|
|
59
|
+
elsif variable_lookup?
|
|
60
|
+
''
|
|
61
|
+
elsif variable?
|
|
62
|
+
start_token + markup + end_token
|
|
63
|
+
elsif tag? && block?
|
|
64
|
+
start_index = block_start_start_index
|
|
65
|
+
end_index = block_start_end_index
|
|
66
|
+
end_index += inner_markup.size
|
|
67
|
+
end_index = find_block_delimiter(end_index)&.end(0)
|
|
68
|
+
source[start_index...end_index]
|
|
69
|
+
elsif tag?
|
|
70
|
+
source[block_start_start_index...block_start_end_index]
|
|
71
|
+
else
|
|
72
|
+
inner_markup
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def inner_markup
|
|
77
|
+
return '' unless block?
|
|
78
|
+
@inner_markup ||= source[block_start_end_index...block_end_start_index]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def inner_json
|
|
82
|
+
return nil unless schema?
|
|
83
|
+
@inner_json ||= JSON.parse(inner_markup)
|
|
84
|
+
rescue JSON::ParserError
|
|
85
|
+
# Handled by ValidSchema
|
|
86
|
+
@inner_json = nil
|
|
87
|
+
end
|
|
88
|
+
|
|
53
89
|
def markup=(markup)
|
|
54
90
|
if @value.instance_variable_defined?(:@markup)
|
|
55
91
|
@value.instance_variable_set(:@markup, markup)
|
|
@@ -70,10 +106,26 @@ module ThemeCheck
|
|
|
70
106
|
position.start_index
|
|
71
107
|
end
|
|
72
108
|
|
|
109
|
+
def start_row
|
|
110
|
+
position.start_row
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def start_column
|
|
114
|
+
position.start_column
|
|
115
|
+
end
|
|
116
|
+
|
|
73
117
|
def end_index
|
|
74
118
|
position.end_index
|
|
75
119
|
end
|
|
76
120
|
|
|
121
|
+
def end_row
|
|
122
|
+
position.end_row
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def end_column
|
|
126
|
+
position.end_column
|
|
127
|
+
end
|
|
128
|
+
|
|
77
129
|
# Literals are hard-coded values in the liquid file.
|
|
78
130
|
def literal?
|
|
79
131
|
@value.is_a?(String) || @value.is_a?(Integer)
|
|
@@ -88,6 +140,14 @@ module ThemeCheck
|
|
|
88
140
|
@value.is_a?(Liquid::Variable)
|
|
89
141
|
end
|
|
90
142
|
|
|
143
|
+
def assigned_or_echoed_variable?
|
|
144
|
+
variable? && start_token == ""
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def variable_lookup?
|
|
148
|
+
@value.is_a?(Liquid::VariableLookup)
|
|
149
|
+
end
|
|
150
|
+
|
|
91
151
|
# A {% comment %} block node?
|
|
92
152
|
def comment?
|
|
93
153
|
@value.is_a?(Liquid::Comment)
|
|
@@ -114,6 +174,10 @@ module ThemeCheck
|
|
|
114
174
|
block_tag? || block_body? || document?
|
|
115
175
|
end
|
|
116
176
|
|
|
177
|
+
def schema?
|
|
178
|
+
@value.is_a?(ThemeCheck::Tags::Schema)
|
|
179
|
+
end
|
|
180
|
+
|
|
117
181
|
# The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
|
|
118
182
|
# and `after_<type_name>` check methods.
|
|
119
183
|
def type_name
|
|
@@ -124,6 +188,86 @@ module ThemeCheck
|
|
|
124
188
|
theme_file&.source
|
|
125
189
|
end
|
|
126
190
|
|
|
191
|
+
def block_start_markup
|
|
192
|
+
source[block_start_start_index...block_start_end_index]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def block_start_start_index
|
|
196
|
+
@block_start_start_index ||= if inside_liquid_tag?
|
|
197
|
+
backtrack_on_whitespace(source, start_index, /[ \t]/)
|
|
198
|
+
elsif tag?
|
|
199
|
+
backtrack_on_whitespace(source, start_index) - start_token.length
|
|
200
|
+
else
|
|
201
|
+
position.start_index - start_token.length
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def block_start_end_index
|
|
206
|
+
@block_start_end_index ||= position.end_index + end_token.size
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def block_end_markup
|
|
210
|
+
source[block_end_start_index...block_end_end_index]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def block_end_start_index
|
|
214
|
+
return block_start_end_index unless tag? && block?
|
|
215
|
+
@block_end_start_index ||= block_end_match&.begin(0) || block_start_end_index
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def block_end_end_index
|
|
219
|
+
return block_end_start_index unless tag? && block?
|
|
220
|
+
@block_end_end_index ||= block_end_match&.end(0) || block_start_end_index
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def outer_markup_start_index
|
|
224
|
+
outer_markup_position.start_index
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def outer_markup_end_index
|
|
228
|
+
outer_markup_position.end_index
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def outer_markup_start_row
|
|
232
|
+
outer_markup_position.start_row
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def outer_markup_start_column
|
|
236
|
+
outer_markup_position.start_column
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def outer_markup_end_row
|
|
240
|
+
outer_markup_position.end_row
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def outer_markup_end_column
|
|
244
|
+
outer_markup_position.end_column
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def inner_markup_start_index
|
|
248
|
+
inner_markup_position.start_index
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def inner_markup_end_index
|
|
252
|
+
inner_markup_position.end_index
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def inner_markup_start_row
|
|
256
|
+
inner_markup_position.start_row
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def inner_markup_start_column
|
|
260
|
+
inner_markup_position.start_column
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def inner_markup_end_row
|
|
264
|
+
inner_markup_position.end_row
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def inner_markup_end_column
|
|
268
|
+
inner_markup_position.end_column
|
|
269
|
+
end
|
|
270
|
+
|
|
127
271
|
WHITESPACE = /\s/
|
|
128
272
|
|
|
129
273
|
# Is this node inside a `{% liquid ... %}` block?
|
|
@@ -165,21 +309,37 @@ module ThemeCheck
|
|
|
165
309
|
end
|
|
166
310
|
|
|
167
311
|
def start_token
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
312
|
+
if inside_liquid_tag?
|
|
313
|
+
""
|
|
314
|
+
elsif variable? && source[start_index - 3..start_index - 1] == "{{-"
|
|
315
|
+
"{{-"
|
|
316
|
+
elsif variable? && source[start_index - 2..start_index - 1] == "{{"
|
|
317
|
+
"{{"
|
|
318
|
+
elsif tag? && whitespace_trimmed_start?
|
|
319
|
+
"{%-"
|
|
320
|
+
elsif tag?
|
|
321
|
+
"{%"
|
|
322
|
+
else
|
|
323
|
+
""
|
|
324
|
+
end
|
|
174
325
|
end
|
|
175
326
|
|
|
176
327
|
def end_token
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
328
|
+
if inside_liquid_tag? && source[end_index] == "\n"
|
|
329
|
+
"\n"
|
|
330
|
+
elsif inside_liquid_tag?
|
|
331
|
+
""
|
|
332
|
+
elsif variable? && source[end_index...end_index + 3] == "-}}"
|
|
333
|
+
"-}}"
|
|
334
|
+
elsif variable? && source[end_index...end_index + 2] == "}}"
|
|
335
|
+
"}}"
|
|
336
|
+
elsif tag? && whitespace_trimmed_end?
|
|
337
|
+
"-%}"
|
|
338
|
+
elsif tag?
|
|
339
|
+
"%}"
|
|
340
|
+
else # this could happen because we're in an assign statement (variable)
|
|
341
|
+
""
|
|
342
|
+
end
|
|
183
343
|
end
|
|
184
344
|
|
|
185
345
|
private
|
|
@@ -192,6 +352,22 @@ module ThemeCheck
|
|
|
192
352
|
)
|
|
193
353
|
end
|
|
194
354
|
|
|
355
|
+
def outer_markup_position
|
|
356
|
+
@outer_markup_position ||= StrictPosition.new(
|
|
357
|
+
outer_markup,
|
|
358
|
+
source,
|
|
359
|
+
block_start_start_index,
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def inner_markup_position
|
|
364
|
+
@inner_markup_position ||= StrictPosition.new(
|
|
365
|
+
inner_markup,
|
|
366
|
+
source,
|
|
367
|
+
block_start_end_index,
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
|
|
195
371
|
# Here we're hacking around a glorious bug in Liquid that makes it so the
|
|
196
372
|
# line_number and markup of a tag is wrong if there's whitespace
|
|
197
373
|
# between the tag_name and the markup of the tag.
|
|
@@ -287,5 +463,72 @@ module ThemeCheck
|
|
|
287
463
|
# return the real raw content
|
|
288
464
|
@tag_markup = source[tag_start...markup_end]
|
|
289
465
|
end
|
|
466
|
+
|
|
467
|
+
# Returns the index of the leftmost consecutive whitespace
|
|
468
|
+
# starting from start going backwards.
|
|
469
|
+
#
|
|
470
|
+
# e.g. backtrack_on_whitespace("01 45", 4) would return 2.
|
|
471
|
+
# e.g. backtrack_on_whitespace("{% render %}", 5) would return 2.
|
|
472
|
+
def backtrack_on_whitespace(string, start, whitespace = WHITESPACE)
|
|
473
|
+
i = start
|
|
474
|
+
i -= 1 while string[i - 1] =~ whitespace && i > 0
|
|
475
|
+
i
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def find_block_delimiter(start_index)
|
|
479
|
+
return nil unless tag? && block?
|
|
480
|
+
|
|
481
|
+
tag_start, tag_end = if inside_liquid_tag?
|
|
482
|
+
[
|
|
483
|
+
/^\s*#{@value.tag_name}\s*/,
|
|
484
|
+
/^\s*end#{@value.tag_name}\s*/,
|
|
485
|
+
]
|
|
486
|
+
else
|
|
487
|
+
[
|
|
488
|
+
/#{Liquid::TagStart}-?\s*#{@value.tag_name}/mi,
|
|
489
|
+
/#{Liquid::TagStart}-?\s*end#{@value.tag_name}\s*-?#{Liquid::TagEnd}/mi,
|
|
490
|
+
]
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# This little algorithm below find the _correct_ block delimiter
|
|
494
|
+
# (endif, endcase, endcomment) for the current tag. What do I
|
|
495
|
+
# mean by correct? It means the one you'd expect. Making sure
|
|
496
|
+
# that we don't do the naive regex find. Since you can have
|
|
497
|
+
# nested ifs, fors, etc.
|
|
498
|
+
#
|
|
499
|
+
# It works by having a stack, pushing onto the stack when we
|
|
500
|
+
# open a tag of our type_name. And popping when we find a
|
|
501
|
+
# closing tag of our type_name.
|
|
502
|
+
#
|
|
503
|
+
# When the stack is empty, we return the end tag match.
|
|
504
|
+
index = start_index
|
|
505
|
+
stack = []
|
|
506
|
+
stack.push("open")
|
|
507
|
+
loop do
|
|
508
|
+
tag_start_match = tag_start.match(source, index)
|
|
509
|
+
tag_end_match = tag_end.match(source, index)
|
|
510
|
+
|
|
511
|
+
return nil unless tag_end_match
|
|
512
|
+
|
|
513
|
+
# We have found a tag_start and it appeared _before_ the
|
|
514
|
+
# tag_end that we found, thus we push it onto the stack.
|
|
515
|
+
if tag_start_match && tag_start_match.end(0) < tag_end_match.end(0)
|
|
516
|
+
stack.push("open")
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# We have found a tag_end, therefore we pop
|
|
520
|
+
stack.pop
|
|
521
|
+
|
|
522
|
+
# Nothing left on the stack, we're done.
|
|
523
|
+
break tag_end_match if stack.empty?
|
|
524
|
+
|
|
525
|
+
# We keep looking from the end of the end tag we just found.
|
|
526
|
+
index = tag_end_match.end(0)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def block_end_match
|
|
531
|
+
@block_end_match ||= find_block_delimiter(block_start_end_index)
|
|
532
|
+
end
|
|
290
533
|
end
|
|
291
534
|
end
|
|
@@ -14,24 +14,55 @@ module ThemeCheck
|
|
|
14
14
|
visit_object(@default, @other, [])
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def add_as_offenses(check, key_prefix: [], node: nil, theme_file: nil)
|
|
17
|
+
def add_as_offenses(check, key_prefix: [], node: nil, theme_file: nil, schema: {})
|
|
18
18
|
if extra_keys.any?
|
|
19
|
-
|
|
20
|
-
key_prefix: key_prefix, node: node, theme_file: theme_file)
|
|
19
|
+
remove_extra_keys_offense(check, "Extra translation keys", extra_keys,
|
|
20
|
+
key_prefix: key_prefix, node: node, theme_file: theme_file, schema: schema)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
if missing_keys.any?
|
|
24
|
-
|
|
25
|
-
key_prefix: key_prefix, node: node, theme_file: theme_file)
|
|
24
|
+
add_missing_keys_offense(check, "Missing translation keys", missing_keys,
|
|
25
|
+
key_prefix: key_prefix, node: node, theme_file: theme_file, schema: schema)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
message = "#{cause}: #{format_keys(key_prefix,
|
|
31
|
+
def remove_extra_keys_offense(check, cause, extra_keys, key_prefix:, node: nil, theme_file: nil, schema: {})
|
|
32
|
+
message = "#{cause}: #{format_keys(key_prefix, extra_keys)}"
|
|
33
33
|
if node
|
|
34
|
-
check.add_offense(message, node: node)
|
|
34
|
+
check.add_offense(message, node: node) do |corrector|
|
|
35
|
+
extra_keys.each do |k|
|
|
36
|
+
SchemaHelper.delete(schema, key_prefix + k)
|
|
37
|
+
end
|
|
38
|
+
corrector.replace_inner_json(node, schema)
|
|
39
|
+
end
|
|
40
|
+
elsif theme_file.is_a?(JsonFile)
|
|
41
|
+
check.add_offense(message, theme_file: theme_file) do |corrector|
|
|
42
|
+
extra_keys.each do |k|
|
|
43
|
+
corrector.remove_translation(theme_file, key_prefix + k)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
check.add_offense(message, theme_file: theme_file)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_missing_keys_offense(check, cause, missing_keys, key_prefix:, node: nil, theme_file: nil, schema: {})
|
|
52
|
+
message = "#{cause}: #{format_keys(key_prefix, missing_keys)}"
|
|
53
|
+
if node
|
|
54
|
+
check.add_offense(message, node: node) do |corrector|
|
|
55
|
+
missing_keys.each do |k|
|
|
56
|
+
SchemaHelper.set(schema, key_prefix + k, "TODO")
|
|
57
|
+
end
|
|
58
|
+
corrector.replace_inner_json(node, schema)
|
|
59
|
+
end
|
|
60
|
+
elsif theme_file.is_a?(JsonFile)
|
|
61
|
+
check.add_offense(message, theme_file: theme_file) do |corrector|
|
|
62
|
+
missing_keys.each do |k|
|
|
63
|
+
corrector.add_translation(theme_file, key_prefix + k, "TODO")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
35
66
|
else
|
|
36
67
|
check.add_offense(message, theme_file: theme_file)
|
|
37
68
|
end
|
data/lib/theme_check/node.rb
CHANGED
|
@@ -30,8 +30,24 @@ module ThemeCheck
|
|
|
30
30
|
raise NotImplementedError
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def start_row
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start_column
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
def end_index
|
|
34
42
|
raise NotImplementedError
|
|
35
43
|
end
|
|
44
|
+
|
|
45
|
+
def end_row
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def end_column
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
end
|
|
36
52
|
end
|
|
37
53
|
end
|
data/lib/theme_check/offense.rb
CHANGED
|
@@ -39,26 +39,12 @@ module ThemeCheck
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
@node = node
|
|
42
|
-
@theme_file =
|
|
43
|
-
|
|
44
|
-
@theme_file = node.theme_file
|
|
45
|
-
elsif theme_file
|
|
46
|
-
@theme_file = theme_file
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
@markup = if markup
|
|
50
|
-
markup
|
|
51
|
-
else
|
|
52
|
-
node&.markup
|
|
53
|
-
end
|
|
42
|
+
@theme_file = node&.theme_file || theme_file
|
|
43
|
+
@markup = markup || node&.markup
|
|
54
44
|
|
|
55
45
|
raise ArgumentError, "Offense markup cannot be an empty string" if @markup.is_a?(String) && @markup.empty?
|
|
56
46
|
|
|
57
|
-
@line_number =
|
|
58
|
-
line_number
|
|
59
|
-
elsif @node
|
|
60
|
-
@node.line_number
|
|
61
|
-
end
|
|
47
|
+
@line_number = line_number || @node&.line_number
|
|
62
48
|
|
|
63
49
|
@position = Position.new(
|
|
64
50
|
@markup,
|
|
@@ -81,11 +67,25 @@ module ThemeCheck
|
|
|
81
67
|
end
|
|
82
68
|
end
|
|
83
69
|
|
|
70
|
+
def in_range?(other_range)
|
|
71
|
+
# Zero length ranges are OK and considered the same as size 1 ranges
|
|
72
|
+
other_range = other_range.first..other_range.end if other_range.size == 0 # rubocop:disable Style/ZeroLengthPredicate
|
|
73
|
+
range.cover?(other_range) || other_range.cover?(range)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def range
|
|
77
|
+
@range ||= if start_index == end_index
|
|
78
|
+
(start_index..end_index)
|
|
79
|
+
else
|
|
80
|
+
(start_index...end_index) # end_index is excluded
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
84
|
def start_index
|
|
85
85
|
@position.start_index
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def
|
|
88
|
+
def start_row
|
|
89
89
|
@position.start_row
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -97,7 +97,7 @@ module ThemeCheck
|
|
|
97
97
|
@position.end_index
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
def
|
|
100
|
+
def end_row
|
|
101
101
|
@position.end_row
|
|
102
102
|
end
|
|
103
103
|
|
|
@@ -121,6 +121,10 @@ module ThemeCheck
|
|
|
121
121
|
StringHelpers.demodulize(check.class.name)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
def version
|
|
125
|
+
theme_file&.version
|
|
126
|
+
end
|
|
127
|
+
|
|
124
128
|
def doc
|
|
125
129
|
check.doc
|
|
126
130
|
end
|
|
@@ -139,9 +143,9 @@ module ThemeCheck
|
|
|
139
143
|
!!correction
|
|
140
144
|
end
|
|
141
145
|
|
|
142
|
-
def correct
|
|
146
|
+
def correct(corrector = nil)
|
|
143
147
|
if correctable?
|
|
144
|
-
corrector
|
|
148
|
+
corrector ||= Corrector.new(theme_file: theme_file)
|
|
145
149
|
correction.call(corrector)
|
|
146
150
|
end
|
|
147
151
|
rescue => e
|
|
@@ -211,9 +215,9 @@ module ThemeCheck
|
|
|
211
215
|
check: check.code_name,
|
|
212
216
|
path: theme_file&.relative_path,
|
|
213
217
|
severity: check.severity_value,
|
|
214
|
-
|
|
218
|
+
start_row: start_row,
|
|
215
219
|
start_column: start_column,
|
|
216
|
-
|
|
220
|
+
end_row: end_row,
|
|
217
221
|
end_column: end_column,
|
|
218
222
|
message: message,
|
|
219
223
|
}
|
data/lib/theme_check/position.rb
CHANGED
|
@@ -64,6 +64,10 @@ module ThemeCheck
|
|
|
64
64
|
strict_position.end_column
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
def content_line_count
|
|
68
|
+
@content_line_count ||= contents.count("\n")
|
|
69
|
+
end
|
|
70
|
+
|
|
67
71
|
private
|
|
68
72
|
|
|
69
73
|
def compute_start_offset
|
|
@@ -78,10 +82,6 @@ module ThemeCheck
|
|
|
78
82
|
@contents
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
def content_line_count
|
|
82
|
-
@content_line_count ||= contents.count("\n")
|
|
83
|
-
end
|
|
84
|
-
|
|
85
85
|
def line_number
|
|
86
86
|
return 0 if @line_number_1_indexed.nil?
|
|
87
87
|
bounded(0, @line_number_1_indexed - 1, content_line_count)
|
|
@@ -5,7 +5,7 @@ module ThemeCheck
|
|
|
5
5
|
LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
|
|
6
6
|
LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
|
|
7
7
|
LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
|
|
8
|
-
HTML_LIQUID_PLACEHOLDER = /≬[0-9a-z]
|
|
8
|
+
HTML_LIQUID_PLACEHOLDER = /≬[0-9a-z\n]+[#\n]*≬/m
|
|
9
9
|
START_OR_END_QUOTE = /(^['"])|(['"]$)/
|
|
10
10
|
|
|
11
11
|
def matches(s, re)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
class SchemaHelper
|
|
5
|
+
# Deeply sets a value in a hash. Accepts both arrays and strings for path.
|
|
6
|
+
def self.set(hash, path, value)
|
|
7
|
+
path = path.split('.') if path.is_a?(String)
|
|
8
|
+
path.each_with_index.reduce(hash) do |pointer, (token, index)|
|
|
9
|
+
if index == path.size - 1
|
|
10
|
+
pointer[token] = value
|
|
11
|
+
elsif !pointer.key?(token)
|
|
12
|
+
pointer[token] = {}
|
|
13
|
+
end
|
|
14
|
+
pointer[token]
|
|
15
|
+
end
|
|
16
|
+
hash
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Deeply delete a key from a hash
|
|
20
|
+
def self.delete(hash, path)
|
|
21
|
+
path = path.split('.') if path.is_a?(String)
|
|
22
|
+
path.each_with_index.reduce(hash) do |pointer, (token, index)|
|
|
23
|
+
break pointer.delete(token) if index == path.size - 1
|
|
24
|
+
pointer[token]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Deeply add key/values inside a hash.
|
|
29
|
+
#
|
|
30
|
+
# Handles arrays by adding the key/value to all hashes inside the array.
|
|
31
|
+
#
|
|
32
|
+
# Specially handles objects that have the "id" key like this:
|
|
33
|
+
#
|
|
34
|
+
# e.g.
|
|
35
|
+
#
|
|
36
|
+
# schema = {
|
|
37
|
+
# "deep" => [
|
|
38
|
+
# { "id" => "hi" },
|
|
39
|
+
# { "id" => "oh" },
|
|
40
|
+
# ],
|
|
41
|
+
# }
|
|
42
|
+
# assert_equal(
|
|
43
|
+
# {
|
|
44
|
+
# "deep" => [
|
|
45
|
+
# { "id" => "hi", "ho" => "ho" },
|
|
46
|
+
# { "id" => "oh" },
|
|
47
|
+
# ],
|
|
48
|
+
# },
|
|
49
|
+
# SchemaHelper.schema_corrector(schema, "deep.hi.ho", "ho")
|
|
50
|
+
# )
|
|
51
|
+
def self.schema_corrector(schema, path, value)
|
|
52
|
+
return schema unless schema.is_a?(Hash)
|
|
53
|
+
path = path.split('.') if path.is_a?(String)
|
|
54
|
+
path.each_with_index.reduce(schema) do |pointer, (token, index)|
|
|
55
|
+
case pointer
|
|
56
|
+
when Array
|
|
57
|
+
pointer.each do |item|
|
|
58
|
+
schema_corrector(item, path.drop(1), value)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
when Hash
|
|
62
|
+
break pointer[token] = value if index == path.size - 1
|
|
63
|
+
pointer[token] = {} unless pointer.key?(token) || pointer.key?("id")
|
|
64
|
+
pointer[token].nil? && pointer["id"] == token ? pointer : pointer[token]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
schema
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/theme_check/storage.rb
CHANGED
data/lib/theme_check/tags.rb
CHANGED
data/lib/theme_check/theme.rb
CHANGED
|
@@ -3,10 +3,13 @@ require "pathname"
|
|
|
3
3
|
|
|
4
4
|
module ThemeCheck
|
|
5
5
|
class ThemeFile
|
|
6
|
+
attr_reader :version, :storage
|
|
7
|
+
|
|
6
8
|
def initialize(relative_path, storage)
|
|
7
9
|
@relative_path = relative_path
|
|
8
10
|
@storage = storage
|
|
9
11
|
@source = nil
|
|
12
|
+
@version = nil
|
|
10
13
|
@eol = "\n"
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -36,7 +39,11 @@ module ThemeCheck
|
|
|
36
39
|
# source file.
|
|
37
40
|
def source
|
|
38
41
|
return @source if @source
|
|
39
|
-
|
|
42
|
+
if @storage.versioned?
|
|
43
|
+
@source, @version = @storage.read_version(@relative_path)
|
|
44
|
+
else
|
|
45
|
+
@source = @storage.read(@relative_path)
|
|
46
|
+
end
|
|
40
47
|
@eol = @source.include?("\r\n") ? "\r\n" : "\n"
|
|
41
48
|
@source = @source.gsub("\r\n", "\n")
|
|
42
49
|
end
|