theme-check 1.10.1 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/cla.yml +22 -0
- data/.github/workflows/theme-check.yml +1 -1
- data/CHANGELOG.md +58 -0
- data/README.md +7 -8
- data/config/default.yml +5 -0
- data/config/theme_app_extension.yml +1 -0
- data/data/shopify_liquid/filters.yml +18 -0
- data/data/shopify_liquid/theme_app_extension_objects.yml +2 -0
- data/dev.yml +1 -1
- data/docs/api/check.md +1 -1
- data/docs/checks/TEMPLATE.md.erb +1 -1
- data/docs/checks/asset_preload.md +60 -0
- data/docs/checks/asset_size_javascript.md +2 -2
- data/docs/checks/missing_enable_comment.md +3 -3
- data/docs/checks/nested_snippet.md +8 -8
- data/docs/checks/schema_json_format.md +1 -1
- data/docs/checks/translation_key_exists.md +4 -4
- data/docs/checks/valid_html_translation.md +1 -1
- data/lib/theme_check/analyzer.rb +18 -3
- data/lib/theme_check/check.rb +6 -1
- data/lib/theme_check/checks/asset_preload.rb +20 -0
- data/lib/theme_check/checks/deprecated_filter.rb +29 -5
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +5 -1
- data/lib/theme_check/checks/missing_template.rb +5 -1
- data/lib/theme_check/checks/translation_key_exists.rb +1 -0
- data/lib/theme_check/checks/undefined_object.rb +9 -1
- data/lib/theme_check/checks/unused_assign.rb +6 -1
- data/lib/theme_check/checks/unused_snippet.rb +50 -2
- data/lib/theme_check/config.rb +4 -3
- data/lib/theme_check/disabled_checks.rb +11 -4
- data/lib/theme_check/file_system_storage.rb +2 -0
- data/lib/theme_check/in_memory_storage.rb +1 -1
- data/lib/theme_check/json_printer.rb +1 -1
- data/lib/theme_check/language_server/bridge.rb +31 -6
- data/lib/theme_check/language_server/diagnostics_engine.rb +80 -34
- data/lib/theme_check/language_server/diagnostics_manager.rb +27 -6
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +7 -6
- data/lib/theme_check/language_server/handler.rb +90 -8
- data/lib/theme_check/language_server/server.rb +42 -14
- data/lib/theme_check/language_server/versioned_in_memory_storage.rb +17 -2
- data/lib/theme_check/liquid_file.rb +22 -1
- data/lib/theme_check/liquid_node.rb +33 -1
- data/lib/theme_check/liquid_visitor.rb +1 -1
- data/lib/theme_check/schema_helper.rb +1 -1
- data/lib/theme_check/shopify_liquid/object.rb +4 -0
- data/lib/theme_check/tags.rb +20 -3
- data/lib/theme_check/version.rb +1 -1
- data/theme-check.gemspec +2 -2
- metadata +12 -7
- data/.github/probots.yml +0 -3
@@ -12,6 +12,16 @@ module ThemeCheck
|
|
12
12
|
version: ThemeCheck::VERSION,
|
13
13
|
}
|
14
14
|
|
15
|
+
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#fileOperationFilter
|
16
|
+
FILE_OPERATION_FILTER = {
|
17
|
+
filters: [{
|
18
|
+
scheme: 'file',
|
19
|
+
pattern: {
|
20
|
+
glob: '**/*',
|
21
|
+
},
|
22
|
+
}],
|
23
|
+
}
|
24
|
+
|
15
25
|
CAPABILITIES = {
|
16
26
|
completionProvider: {
|
17
27
|
triggerCharacters: ['.', '{{ ', '{% '],
|
@@ -33,6 +43,13 @@ module ThemeCheck
|
|
33
43
|
willSave: false,
|
34
44
|
save: true,
|
35
45
|
},
|
46
|
+
workspace: {
|
47
|
+
fileOperations: {
|
48
|
+
didCreate: FILE_OPERATION_FILTER,
|
49
|
+
didDelete: FILE_OPERATION_FILTER,
|
50
|
+
willRename: FILE_OPERATION_FILTER,
|
51
|
+
},
|
52
|
+
},
|
36
53
|
}
|
37
54
|
|
38
55
|
def initialize(bridge)
|
@@ -55,7 +72,12 @@ module ThemeCheck
|
|
55
72
|
@diagnostics_engine = DiagnosticsEngine.new(@storage, @bridge, @diagnostics_manager)
|
56
73
|
@execute_command_engine = ExecuteCommandEngine.new
|
57
74
|
@execute_command_engine << CorrectionExecuteCommandProvider.new(@storage, @bridge, @diagnostics_manager)
|
58
|
-
@execute_command_engine << RunChecksExecuteCommandProvider.new(
|
75
|
+
@execute_command_engine << RunChecksExecuteCommandProvider.new(
|
76
|
+
@diagnostics_engine,
|
77
|
+
@storage,
|
78
|
+
config_for_path(@root_path),
|
79
|
+
@configuration,
|
80
|
+
)
|
59
81
|
@code_action_engine = CodeActionEngine.new(@storage, @diagnostics_manager)
|
60
82
|
@bridge.send_response(id, {
|
61
83
|
capabilities: CAPABILITIES,
|
@@ -65,6 +87,7 @@ module ThemeCheck
|
|
65
87
|
|
66
88
|
def on_initialized(_id, _params)
|
67
89
|
return unless @configuration
|
90
|
+
|
68
91
|
@configuration.fetch
|
69
92
|
@configuration.register_did_change_capability
|
70
93
|
end
|
@@ -91,9 +114,17 @@ module ThemeCheck
|
|
91
114
|
|
92
115
|
def on_text_document_did_close(_id, params)
|
93
116
|
relative_path = relative_path_from_text_document_uri(params)
|
94
|
-
|
95
|
-
|
96
|
-
|
117
|
+
begin
|
118
|
+
file_system_content = Pathname.new(text_document_uri(params)).read(mode: 'rb', encoding: 'UTF-8')
|
119
|
+
# On close, the file system becomes the source of truth
|
120
|
+
@storage.write(relative_path, file_system_content, nil)
|
121
|
+
|
122
|
+
# the file no longer exists because either the user deleted it, or the user renamed it.
|
123
|
+
rescue Errno::ENOENT
|
124
|
+
@storage.remove(relative_path)
|
125
|
+
ensure
|
126
|
+
@diagnostics_engine.clear_diagnostics(relative_path) if @configuration.only_single_file?
|
127
|
+
end
|
97
128
|
end
|
98
129
|
|
99
130
|
def on_text_document_did_save(_id, params)
|
@@ -125,6 +156,52 @@ module ThemeCheck
|
|
125
156
|
))
|
126
157
|
end
|
127
158
|
|
159
|
+
def on_workspace_did_create_files(_id, params)
|
160
|
+
paths = params[:files]
|
161
|
+
&.map { |file| file[:uri] }
|
162
|
+
&.map { |uri| file_path(uri) }
|
163
|
+
return unless paths
|
164
|
+
|
165
|
+
paths.each do |path|
|
166
|
+
relative_path = @storage.relative_path(path)
|
167
|
+
file_system_content = Pathname.new(path).read(mode: 'rb', encoding: 'UTF-8')
|
168
|
+
@storage.write(relative_path, file_system_content, nil)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def on_workspace_did_delete_files(_id, params)
|
173
|
+
absolute_paths = params[:files]
|
174
|
+
&.map { |file| file[:uri] }
|
175
|
+
&.map { |uri| file_path(uri) }
|
176
|
+
return unless absolute_paths
|
177
|
+
|
178
|
+
absolute_paths.each do |path|
|
179
|
+
relative_path = @storage.relative_path(path)
|
180
|
+
@storage.remove(relative_path)
|
181
|
+
end
|
182
|
+
|
183
|
+
analyze_and_send_offenses(absolute_paths)
|
184
|
+
end
|
185
|
+
|
186
|
+
# We're using workspace/willRenameFiles here because we want this to run
|
187
|
+
# before textDocument/didOpen and textDocumetn/didClose of the files
|
188
|
+
# (which might trigger another theme analysis).
|
189
|
+
def on_workspace_will_rename_files(id, params)
|
190
|
+
relative_paths = params[:files]
|
191
|
+
&.map { |file| [file[:oldUri], file[:newUri]] }
|
192
|
+
&.map { |(old_uri, new_uri)| [relative_path_from_uri(old_uri), relative_path_from_uri(new_uri)] }
|
193
|
+
return @bridge.send_response(id, nil) unless relative_paths
|
194
|
+
|
195
|
+
relative_paths.each do |(old_path, new_path)|
|
196
|
+
@storage.write(new_path, @storage.read(old_path), nil)
|
197
|
+
@storage.remove(old_path)
|
198
|
+
end
|
199
|
+
@bridge.send_response(id, nil)
|
200
|
+
|
201
|
+
absolute_paths = relative_paths.flatten(2).map { |p| @storage.path(p) }
|
202
|
+
analyze_and_send_offenses(absolute_paths)
|
203
|
+
end
|
204
|
+
|
128
205
|
def on_workspace_execute_command(id, params)
|
129
206
|
@bridge.send_response(id, @execute_command_engine.execute(
|
130
207
|
params[:command],
|
@@ -159,6 +236,10 @@ module ThemeCheck
|
|
159
236
|
file_path(params.dig(:textDocument, :uri))
|
160
237
|
end
|
161
238
|
|
239
|
+
def relative_path_from_uri(uri)
|
240
|
+
@storage.relative_path(file_path(uri))
|
241
|
+
end
|
242
|
+
|
162
243
|
def relative_path_from_text_document_uri(params)
|
163
244
|
@storage.relative_path(text_document_uri(params))
|
164
245
|
end
|
@@ -185,15 +266,16 @@ module ThemeCheck
|
|
185
266
|
params.dig(:contentChanges, 0, :text)
|
186
267
|
end
|
187
268
|
|
188
|
-
def config_for_path(
|
269
|
+
def config_for_path(path_or_paths)
|
270
|
+
path = path_or_paths.is_a?(Array) ? path_or_paths[0] : path_or_paths
|
189
271
|
root = ThemeCheck::Config.find(path) || @root_path
|
190
272
|
ThemeCheck::Config.from_path(root)
|
191
273
|
end
|
192
274
|
|
193
|
-
def analyze_and_send_offenses(
|
275
|
+
def analyze_and_send_offenses(absolute_path_or_paths, only_single_file: nil)
|
194
276
|
@diagnostics_engine.analyze_and_send_offenses(
|
195
|
-
|
196
|
-
config_for_path(
|
277
|
+
absolute_path_or_paths,
|
278
|
+
config_for_path(absolute_path_or_paths),
|
197
279
|
only_single_file: only_single_file.nil? ? @configuration.only_single_file? : only_single_file
|
198
280
|
)
|
199
281
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
require 'stringio'
|
5
|
+
require 'timeout'
|
4
6
|
|
5
7
|
module ThemeCheck
|
6
8
|
module LanguageServer
|
@@ -45,10 +47,29 @@ module ThemeCheck
|
|
45
47
|
def listen
|
46
48
|
start_handler_threads
|
47
49
|
start_json_rpc_thread
|
48
|
-
|
50
|
+
err = @error.pop
|
51
|
+
status_code = status_code_from_error(err)
|
52
|
+
|
53
|
+
if status_code > 0
|
54
|
+
# For a reason I can't comprehend, this hangs but prints
|
55
|
+
# anyway. So it's wrapped in this ugly timeout...
|
56
|
+
Timeout.timeout(1) do
|
57
|
+
$stderr.puts err.full_message
|
58
|
+
end
|
59
|
+
|
60
|
+
# Warn user of error, otherwise server might restart
|
61
|
+
# without telling you.
|
62
|
+
@bridge.send_notification("window/showMessage", {
|
63
|
+
type: 1,
|
64
|
+
message: "A theme-check-language-server error has occurred, search OUTPUT logs for details.",
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
49
68
|
cleanup(status_code)
|
50
69
|
rescue SignalException
|
51
70
|
0
|
71
|
+
rescue StandardError
|
72
|
+
2
|
52
73
|
end
|
53
74
|
|
54
75
|
def start_json_rpc_thread
|
@@ -65,36 +86,43 @@ module ThemeCheck
|
|
65
86
|
else
|
66
87
|
@queue << message
|
67
88
|
end
|
68
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
69
|
-
break @error << e
|
70
89
|
end
|
90
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
91
|
+
@bridge.log("rescuing #{e.class} in jsonrpc thread")
|
92
|
+
@error << e
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
74
96
|
def start_handler_threads
|
75
97
|
@number_of_threads.times do
|
76
98
|
@handlers << Thread.new do
|
77
|
-
|
78
|
-
message = @queue.pop
|
79
|
-
break if @queue.closed? && @queue.empty?
|
80
|
-
handle_message(message)
|
81
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
82
|
-
break @error << e
|
83
|
-
end
|
99
|
+
handle_messages
|
84
100
|
end
|
85
101
|
end
|
86
102
|
end
|
87
103
|
|
104
|
+
def handle_messages
|
105
|
+
loop do
|
106
|
+
message = @queue.pop
|
107
|
+
return if @queue.closed? && @queue.empty?
|
108
|
+
|
109
|
+
handle_message(message)
|
110
|
+
end
|
111
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
112
|
+
@bridge.log("rescuing #{e.class} in handler thread")
|
113
|
+
@error << e
|
114
|
+
end
|
115
|
+
|
88
116
|
def status_code_from_error(e)
|
89
117
|
raise e
|
90
118
|
|
91
119
|
# support ctrl+c and stuff
|
92
120
|
rescue SignalException, DoneStreaming
|
93
121
|
0
|
94
|
-
|
95
122
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
96
123
|
raise e if should_raise_errors
|
97
|
-
|
124
|
+
|
125
|
+
@bridge.log("Fatal #{e.class}")
|
98
126
|
2
|
99
127
|
end
|
100
128
|
|
@@ -109,15 +137,14 @@ module ThemeCheck
|
|
109
137
|
if @handler.respond_to?(method_name)
|
110
138
|
@handler.send(method_name, id, params)
|
111
139
|
end
|
112
|
-
|
113
140
|
rescue DoneStreaming => e
|
114
141
|
raise e
|
115
142
|
rescue StandardError => e
|
116
143
|
is_request = id
|
117
144
|
raise e unless is_request
|
145
|
+
|
118
146
|
# Errors obtained in request handlers should be sent
|
119
147
|
# back as internal errors instead of closing the program.
|
120
|
-
@bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
121
148
|
@bridge.send_internal_error(id, e)
|
122
149
|
end
|
123
150
|
|
@@ -132,6 +159,7 @@ module ThemeCheck
|
|
132
159
|
end
|
133
160
|
|
134
161
|
def cleanup(status_code)
|
162
|
+
@bridge.log("Closing server... status code = #{status_code}")
|
135
163
|
# Stop listenting to RPC calls
|
136
164
|
@messenger.close_input
|
137
165
|
# Wait for rpc loop to close
|
@@ -8,7 +8,7 @@ module ThemeCheck
|
|
8
8
|
|
9
9
|
def initialize(files, root = "/dev/null")
|
10
10
|
super(files, root)
|
11
|
-
@versions = {}
|
11
|
+
@versions = {} # Hash<relative_path, number>
|
12
12
|
@mutex = Mutex.new
|
13
13
|
end
|
14
14
|
|
@@ -49,7 +49,11 @@ module ThemeCheck
|
|
49
49
|
# - Only offer fixes on "clean" files (or offer the change but specify the version so the editor knows what to do with it)
|
50
50
|
def write(relative_path, content, version)
|
51
51
|
@mutex.synchronize do
|
52
|
-
|
52
|
+
if version.nil?
|
53
|
+
@versions.delete(relative_path)
|
54
|
+
else
|
55
|
+
@versions[relative_path] = version
|
56
|
+
end
|
53
57
|
super(relative_path, content)
|
54
58
|
end
|
55
59
|
end
|
@@ -58,6 +62,13 @@ module ThemeCheck
|
|
58
62
|
@mutex.synchronize { [read(relative_path), version(relative_path)] }
|
59
63
|
end
|
60
64
|
|
65
|
+
def remove(relative_path)
|
66
|
+
@mutex.synchronize do
|
67
|
+
@versions.delete(relative_path)
|
68
|
+
super(relative_path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
61
72
|
def versioned?
|
62
73
|
true
|
63
74
|
end
|
@@ -65,5 +76,9 @@ module ThemeCheck
|
|
65
76
|
def version(relative_path)
|
66
77
|
@versions[relative_path.to_s]
|
67
78
|
end
|
79
|
+
|
80
|
+
def opened_files
|
81
|
+
@versions.keys
|
82
|
+
end
|
68
83
|
end
|
69
84
|
end
|
@@ -33,7 +33,22 @@ module ThemeCheck
|
|
33
33
|
|
34
34
|
def source_excerpt(line)
|
35
35
|
original_lines = source.split("\n")
|
36
|
-
original_lines[line - 1].strip
|
36
|
+
original_lines[bounded(0, line - 1, original_lines.size - 1)].strip
|
37
|
+
rescue => e
|
38
|
+
ThemeCheck.bug(<<~EOS)
|
39
|
+
Exception while running `source_excerpt(#{line})`:
|
40
|
+
```
|
41
|
+
#{e.class}: #{e.message}
|
42
|
+
#{e.backtrace.join("\n ")}
|
43
|
+
```
|
44
|
+
|
45
|
+
path: #{path}
|
46
|
+
|
47
|
+
source:
|
48
|
+
```
|
49
|
+
#{source}
|
50
|
+
```
|
51
|
+
EOS
|
37
52
|
end
|
38
53
|
|
39
54
|
def parse
|
@@ -57,5 +72,11 @@ module ThemeCheck
|
|
57
72
|
disable_liquid_c_nodes: true,
|
58
73
|
)
|
59
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def bounded(lower, x, upper)
|
79
|
+
[lower, [x, upper].min].max
|
80
|
+
end
|
60
81
|
end
|
61
82
|
end
|
@@ -7,6 +7,7 @@ module ThemeCheck
|
|
7
7
|
|
8
8
|
def initialize(value, parent, theme_file)
|
9
9
|
raise ArgumentError, "Expected a Liquid AST Node" if value.is_a?(LiquidNode)
|
10
|
+
|
10
11
|
@value = value
|
11
12
|
@parent = parent
|
12
13
|
@theme_file = theme_file
|
@@ -37,7 +38,9 @@ module ThemeCheck
|
|
37
38
|
node
|
38
39
|
end
|
39
40
|
end
|
40
|
-
nodes
|
41
|
+
nodes
|
42
|
+
.reject(&:nil?) # We don't want nil nodes, and they can happen
|
43
|
+
.map { |node| LiquidNode.new(node, self, @theme_file) }
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
@@ -75,11 +78,13 @@ module ThemeCheck
|
|
75
78
|
|
76
79
|
def inner_markup
|
77
80
|
return '' unless block?
|
81
|
+
|
78
82
|
@inner_markup ||= source[block_start_end_index...block_end_start_index]
|
79
83
|
end
|
80
84
|
|
81
85
|
def inner_json
|
82
86
|
return nil unless schema?
|
87
|
+
|
83
88
|
@inner_json ||= JSON.parse(inner_markup)
|
84
89
|
rescue JSON::ParserError
|
85
90
|
# Handled by ValidSchema
|
@@ -153,6 +158,11 @@ module ThemeCheck
|
|
153
158
|
@value.is_a?(Liquid::Comment)
|
154
159
|
end
|
155
160
|
|
161
|
+
# {% # comment %}
|
162
|
+
def inline_comment?
|
163
|
+
@value.is_a?(Liquid::InlineComment)
|
164
|
+
end
|
165
|
+
|
156
166
|
# Top level node of every liquid_file.
|
157
167
|
def document?
|
158
168
|
@value.is_a?(Liquid::Document)
|
@@ -186,6 +196,7 @@ module ThemeCheck
|
|
186
196
|
|
187
197
|
def filters
|
188
198
|
raise TypeError, "Attempting to lookup filters of #{type_name}. Only variables have filters." unless variable?
|
199
|
+
|
189
200
|
@value.filters
|
190
201
|
end
|
191
202
|
|
@@ -193,6 +204,25 @@ module ThemeCheck
|
|
193
204
|
theme_file&.source
|
194
205
|
end
|
195
206
|
|
207
|
+
# For debugging purposes, this might be easier for the eyes.
|
208
|
+
def to_h
|
209
|
+
if literal?
|
210
|
+
return @value
|
211
|
+
elsif variable_lookup?
|
212
|
+
return {
|
213
|
+
type_name: type_name,
|
214
|
+
name: value.name.to_s,
|
215
|
+
lookups: children.map(&:to_h),
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
{
|
220
|
+
type_name: type_name,
|
221
|
+
markup: outer_markup,
|
222
|
+
children: children.map(&:to_h),
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
196
226
|
def block_start_markup
|
197
227
|
source[block_start_start_index...block_start_end_index]
|
198
228
|
end
|
@@ -217,11 +247,13 @@ module ThemeCheck
|
|
217
247
|
|
218
248
|
def block_end_start_index
|
219
249
|
return block_start_end_index unless tag? && block?
|
250
|
+
|
220
251
|
@block_end_start_index ||= block_end_match&.begin(0) || block_start_end_index
|
221
252
|
end
|
222
253
|
|
223
254
|
def block_end_end_index
|
224
255
|
return block_end_start_index unless tag? && block?
|
256
|
+
|
225
257
|
@block_end_end_index ||= block_end_match&.end(0) || block_start_end_index
|
226
258
|
end
|
227
259
|
|
@@ -8,7 +8,7 @@ module ThemeCheck
|
|
8
8
|
path.each_with_index.reduce(hash) do |pointer, (token, index)|
|
9
9
|
if index == path.size - 1
|
10
10
|
pointer[token] = value
|
11
|
-
elsif !pointer.key?(token)
|
11
|
+
elsif !pointer.key?(token) || !pointer[token].is_a?(Hash)
|
12
12
|
pointer[token] = {}
|
13
13
|
end
|
14
14
|
pointer[token]
|
@@ -13,6 +13,10 @@ module ThemeCheck
|
|
13
13
|
def plus_labels
|
14
14
|
@plus_labels ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/plus_objects.yml"))
|
15
15
|
end
|
16
|
+
|
17
|
+
def theme_app_extension_labels
|
18
|
+
@theme_app_extension_labels ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/theme_app_extension_objects.yml"))
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
data/lib/theme_check/tags.rb
CHANGED
@@ -125,16 +125,32 @@ module ThemeCheck
|
|
125
125
|
end
|
126
126
|
|
127
127
|
class Render < Liquid::Tag
|
128
|
-
SYNTAX =
|
128
|
+
SYNTAX = %r{
|
129
|
+
(
|
130
|
+
## for {% render "snippet" %}
|
131
|
+
#{Liquid::QuotedString}+ |
|
132
|
+
## for {% render block %}
|
133
|
+
## We require the variable # segment to be at the beginning of the
|
134
|
+
## string (with \A). This is to prevent code like {% render !foo! %}
|
135
|
+
## from parsing
|
136
|
+
\A#{Liquid::VariableSegment}+
|
137
|
+
)
|
138
|
+
## for {% render "snippet" with product as p %}
|
139
|
+
## or {% render "snippet" for products p %}
|
140
|
+
(\s+(with|#{Liquid::Render::FOR})\s+(#{Liquid::QuotedFragment}+))?
|
141
|
+
(\s+(?:as)\s+(#{Liquid::VariableSegment}+))?
|
142
|
+
## variables passed into the tag (e.g. {% render "snippet", var1: value1, var2: value2 %}
|
143
|
+
## are not matched by this regex and are handled by Liquid::Render.initialize
|
144
|
+
}xo
|
129
145
|
|
130
146
|
disable_tags "include"
|
131
147
|
|
132
|
-
attr_reader :template_name_expr, :attributes
|
148
|
+
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
133
149
|
|
134
150
|
def initialize(tag_name, markup, options)
|
135
151
|
super
|
136
152
|
|
137
|
-
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
153
|
+
raise Liquid::SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
|
138
154
|
|
139
155
|
template_name = Regexp.last_match(1)
|
140
156
|
with_or_for = Regexp.last_match(3)
|
@@ -155,6 +171,7 @@ module ThemeCheck
|
|
155
171
|
def children
|
156
172
|
[
|
157
173
|
@node.template_name_expr,
|
174
|
+
@node.variable_name_expr,
|
158
175
|
] + @node.attributes.values
|
159
176
|
end
|
160
177
|
end
|
data/lib/theme_check/version.rb
CHANGED
data/theme-check.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://github.com/Shopify/theme-check"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.required_ruby_version = ">= 2.
|
16
|
+
spec.required_ruby_version = ">= 2.7"
|
17
17
|
|
18
18
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
19
19
|
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
|
-
spec.add_dependency('liquid', '>= 5.
|
27
|
+
spec.add_dependency('liquid', '>= 5.4.0')
|
28
28
|
spec.add_dependency('nokogiri', '>= 1.12')
|
29
29
|
spec.add_dependency('parser', '~> 3')
|
30
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: theme-check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc-André Cournoyer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.
|
19
|
+
version: 5.4.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.
|
26
|
+
version: 5.4.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nokogiri
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,7 +61,9 @@ executables:
|
|
61
61
|
extensions: []
|
62
62
|
extra_rdoc_files: []
|
63
63
|
files:
|
64
|
-
- ".github/
|
64
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
65
|
+
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
66
|
+
- ".github/workflows/cla.yml"
|
65
67
|
- ".github/workflows/theme-check.yml"
|
66
68
|
- ".gitignore"
|
67
69
|
- ".rubocop.yml"
|
@@ -84,6 +86,7 @@ files:
|
|
84
86
|
- data/shopify_liquid/objects.yml
|
85
87
|
- data/shopify_liquid/plus_objects.yml
|
86
88
|
- data/shopify_liquid/tags.yml
|
89
|
+
- data/shopify_liquid/theme_app_extension_objects.yml
|
87
90
|
- data/shopify_translation_keys.yml
|
88
91
|
- dev.yml
|
89
92
|
- docs/api/check.md
|
@@ -92,6 +95,7 @@ files:
|
|
92
95
|
- docs/api/liquid_check.md
|
93
96
|
- docs/checks/TEMPLATE.md.erb
|
94
97
|
- docs/checks/app_block_valid_tags.md
|
98
|
+
- docs/checks/asset_preload.md
|
95
99
|
- docs/checks/asset_size_app_block_css.md
|
96
100
|
- docs/checks/asset_size_app_block_javascript.md
|
97
101
|
- docs/checks/asset_size_css.md
|
@@ -152,6 +156,7 @@ files:
|
|
152
156
|
- lib/theme_check/checks.rb
|
153
157
|
- lib/theme_check/checks/TEMPLATE.rb.erb
|
154
158
|
- lib/theme_check/checks/app_block_valid_tags.rb
|
159
|
+
- lib/theme_check/checks/asset_preload.rb
|
155
160
|
- lib/theme_check/checks/asset_size_app_block_css.rb
|
156
161
|
- lib/theme_check/checks/asset_size_app_block_javascript.rb
|
157
162
|
- lib/theme_check/checks/asset_size_css.rb
|
@@ -293,14 +298,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
293
298
|
requirements:
|
294
299
|
- - ">="
|
295
300
|
- !ruby/object:Gem::Version
|
296
|
-
version: '2.
|
301
|
+
version: '2.7'
|
297
302
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
298
303
|
requirements:
|
299
304
|
- - ">="
|
300
305
|
- !ruby/object:Gem::Version
|
301
306
|
version: '0'
|
302
307
|
requirements: []
|
303
|
-
rubygems_version: 3.
|
308
|
+
rubygems_version: 3.3.3
|
304
309
|
signing_key:
|
305
310
|
specification_version: 4
|
306
311
|
summary: A Shopify Theme Linter
|
data/.github/probots.yml
DELETED