theme-check 1.10.1 → 1.11.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/.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