theme-check 1.8.0 → 1.9.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +21 -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 +2 -1
- 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/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 +79 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +4 -6
- 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/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_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 +28 -54
- data/lib/theme_check/file_system_storage.rb +4 -3
- data/lib/theme_check/html_node.rb +99 -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 +1 -1
- 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 +79 -28
- data/lib/theme_check/language_server/io_messenger.rb +9 -1
- data/lib/theme_check/language_server/server.rb +8 -7
- 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 +249 -39
- data/lib/theme_check/locale_diff.rb +16 -4
- data/lib/theme_check/node.rb +16 -0
- data/lib/theme_check/offense.rb +27 -23
- 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/theme.rb +1 -1
- data/lib/theme_check/theme_file.rb +8 -1
- data/lib/theme_check/theme_file_rewriter.rb +18 -9
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +7 -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,36 +106,24 @@ module ThemeCheck
|
|
70
106
|
position.start_index
|
71
107
|
end
|
72
108
|
|
73
|
-
def
|
74
|
-
position.
|
75
|
-
end
|
76
|
-
|
77
|
-
def start_token_index
|
78
|
-
return position.start_index if inside_liquid_tag?
|
79
|
-
position.start_index - (start_token.length + 1)
|
109
|
+
def start_row
|
110
|
+
position.start_row
|
80
111
|
end
|
81
112
|
|
82
|
-
def
|
83
|
-
|
84
|
-
position.end_index + end_token.length
|
113
|
+
def start_column
|
114
|
+
position.start_column
|
85
115
|
end
|
86
116
|
|
87
|
-
def
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def render_end_tag
|
92
|
-
"#{start_token} #{@value.block_delimiter} #{end_token}"
|
117
|
+
def end_index
|
118
|
+
position.end_index
|
93
119
|
end
|
94
120
|
|
95
|
-
def
|
96
|
-
|
97
|
-
block_regex.begin(:body)
|
121
|
+
def end_row
|
122
|
+
position.end_row
|
98
123
|
end
|
99
124
|
|
100
|
-
def
|
101
|
-
|
102
|
-
block_regex.end(:body)
|
125
|
+
def end_column
|
126
|
+
position.end_column
|
103
127
|
end
|
104
128
|
|
105
129
|
# Literals are hard-coded values in the liquid file.
|
@@ -116,6 +140,14 @@ module ThemeCheck
|
|
116
140
|
@value.is_a?(Liquid::Variable)
|
117
141
|
end
|
118
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
|
+
|
119
151
|
# A {% comment %} block node?
|
120
152
|
def comment?
|
121
153
|
@value.is_a?(Liquid::Comment)
|
@@ -142,6 +174,10 @@ module ThemeCheck
|
|
142
174
|
block_tag? || block_body? || document?
|
143
175
|
end
|
144
176
|
|
177
|
+
def schema?
|
178
|
+
@value.is_a?(ThemeCheck::Tags::Schema)
|
179
|
+
end
|
180
|
+
|
145
181
|
# The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
|
146
182
|
# and `after_<type_name>` check methods.
|
147
183
|
def type_name
|
@@ -152,6 +188,86 @@ module ThemeCheck
|
|
152
188
|
theme_file&.source
|
153
189
|
end
|
154
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
|
+
|
155
271
|
WHITESPACE = /\s/
|
156
272
|
|
157
273
|
# Is this node inside a `{% liquid ... %}` block?
|
@@ -193,30 +309,41 @@ module ThemeCheck
|
|
193
309
|
end
|
194
310
|
|
195
311
|
def start_token
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
202
325
|
end
|
203
326
|
|
204
327
|
def end_token
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
211
343
|
end
|
212
344
|
|
213
345
|
private
|
214
346
|
|
215
|
-
def block_regex
|
216
|
-
return unless block_tag?
|
217
|
-
/(?<start_token>#{render_start_tag})(?<body>.*)(?<end_token>#{render_end_tag})/m.match(source)
|
218
|
-
end
|
219
|
-
|
220
347
|
def position
|
221
348
|
@position ||= Position.new(
|
222
349
|
markup,
|
@@ -225,6 +352,22 @@ module ThemeCheck
|
|
225
352
|
)
|
226
353
|
end
|
227
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
|
+
|
228
371
|
# Here we're hacking around a glorious bug in Liquid that makes it so the
|
229
372
|
# line_number and markup of a tag is wrong if there's whitespace
|
230
373
|
# between the tag_name and the markup of the tag.
|
@@ -320,5 +463,72 @@ module ThemeCheck
|
|
320
463
|
# return the real raw content
|
321
464
|
@tag_markup = source[tag_start...markup_end]
|
322
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
|
323
533
|
end
|
324
534
|
end
|
@@ -33,9 +33,15 @@ module ThemeCheck
|
|
33
33
|
if node
|
34
34
|
check.add_offense(message, node: node) do |corrector|
|
35
35
|
extra_keys.each do |k|
|
36
|
-
|
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)
|
37
44
|
end
|
38
|
-
corrector.replace_block_body(node, schema)
|
39
45
|
end
|
40
46
|
else
|
41
47
|
check.add_offense(message, theme_file: theme_file)
|
@@ -47,9 +53,15 @@ module ThemeCheck
|
|
47
53
|
if node
|
48
54
|
check.add_offense(message, node: node) do |corrector|
|
49
55
|
missing_keys.each do |k|
|
50
|
-
|
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")
|
51
64
|
end
|
52
|
-
corrector.replace_block_body(node, schema)
|
53
65
|
end
|
54
66
|
else
|
55
67
|
check.add_offense(message, theme_file: theme_file)
|
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
|
}
|
@@ -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/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
|
@@ -11,36 +11,45 @@ module ThemeCheck
|
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
14
|
-
def insert_before(node, content)
|
14
|
+
def insert_before(node, content, character_range = nil)
|
15
15
|
@rewriter.insert_before(
|
16
|
-
range(
|
16
|
+
range(
|
17
|
+
character_range&.begin || node.start_index,
|
18
|
+
character_range&.end || node.end_index,
|
19
|
+
),
|
17
20
|
content
|
18
21
|
)
|
19
22
|
end
|
20
23
|
|
21
|
-
def insert_after(node, content)
|
24
|
+
def insert_after(node, content, character_range = nil)
|
22
25
|
@rewriter.insert_after(
|
23
|
-
range(
|
26
|
+
range(
|
27
|
+
character_range&.begin || node.start_index,
|
28
|
+
character_range&.end || node.end_index,
|
29
|
+
),
|
24
30
|
content
|
25
31
|
)
|
26
32
|
end
|
27
33
|
|
28
34
|
def remove(node)
|
29
35
|
@rewriter.remove(
|
30
|
-
range(node.
|
36
|
+
range(node.outer_markup_start_index, node.outer_markup_end_index)
|
31
37
|
)
|
32
38
|
end
|
33
39
|
|
34
|
-
def replace(node, content)
|
40
|
+
def replace(node, content, character_range = nil)
|
35
41
|
@rewriter.replace(
|
36
|
-
range(
|
42
|
+
range(
|
43
|
+
character_range&.begin || node.start_index,
|
44
|
+
character_range&.end || node.end_index,
|
45
|
+
),
|
37
46
|
content
|
38
47
|
)
|
39
48
|
end
|
40
49
|
|
41
|
-
def
|
50
|
+
def replace_inner_markup(node, content)
|
42
51
|
@rewriter.replace(
|
43
|
-
range(node.
|
52
|
+
range(node.inner_markup_start_index, node.inner_markup_end_index),
|
44
53
|
content
|
45
54
|
)
|
46
55
|
end
|