theme-check 1.10.3 → 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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.github/workflows/cla.yml +22 -0
  5. data/.github/workflows/theme-check.yml +1 -1
  6. data/CHANGELOG.md +41 -0
  7. data/README.md +7 -8
  8. data/config/default.yml +4 -0
  9. data/data/shopify_liquid/filters.yml +18 -0
  10. data/dev.yml +1 -1
  11. data/docs/checks/asset_preload.md +60 -0
  12. data/docs/checks/asset_size_javascript.md +2 -2
  13. data/docs/checks/missing_enable_comment.md +3 -3
  14. data/docs/checks/nested_snippet.md +8 -8
  15. data/docs/checks/translation_key_exists.md +4 -4
  16. data/docs/checks/valid_html_translation.md +1 -1
  17. data/lib/theme_check/analyzer.rb +18 -3
  18. data/lib/theme_check/check.rb +6 -1
  19. data/lib/theme_check/checks/asset_preload.rb +20 -0
  20. data/lib/theme_check/checks/deprecated_filter.rb +29 -5
  21. data/lib/theme_check/checks/missing_enable_comment.rb +4 -0
  22. data/lib/theme_check/checks/missing_required_template_files.rb +5 -1
  23. data/lib/theme_check/checks/missing_template.rb +5 -1
  24. data/lib/theme_check/checks/unused_assign.rb +6 -1
  25. data/lib/theme_check/checks/unused_snippet.rb +50 -2
  26. data/lib/theme_check/config.rb +2 -2
  27. data/lib/theme_check/disabled_checks.rb +11 -4
  28. data/lib/theme_check/file_system_storage.rb +2 -0
  29. data/lib/theme_check/in_memory_storage.rb +1 -1
  30. data/lib/theme_check/language_server/bridge.rb +31 -6
  31. data/lib/theme_check/language_server/diagnostics_engine.rb +80 -34
  32. data/lib/theme_check/language_server/diagnostics_manager.rb +27 -6
  33. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +7 -6
  34. data/lib/theme_check/language_server/handler.rb +90 -8
  35. data/lib/theme_check/language_server/server.rb +42 -14
  36. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +17 -2
  37. data/lib/theme_check/liquid_file.rb +22 -1
  38. data/lib/theme_check/liquid_node.rb +33 -1
  39. data/lib/theme_check/liquid_visitor.rb +1 -1
  40. data/lib/theme_check/schema_helper.rb +1 -1
  41. data/lib/theme_check/tags.rb +2 -1
  42. data/lib/theme_check/version.rb +1 -1
  43. data/theme-check.gemspec +2 -2
  44. metadata +10 -6
  45. data/.github/probots.yml +0 -3
@@ -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
- status_code = status_code_from_error(@error.pop)
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
- loop do
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
- @bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
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
- @versions[relative_path] = version
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.map { |node| LiquidNode.new(node, self, @theme_file) }
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
 
@@ -28,7 +28,7 @@ module ThemeCheck
28
28
  call_checks(:after_node, node)
29
29
  end
30
30
 
31
- @disabled_checks.update(node) if node.comment?
31
+ @disabled_checks.update(node) if node.comment? || node.inline_comment?
32
32
  end
33
33
 
34
34
  def call_checks(method, *args)
@@ -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]
@@ -145,7 +145,7 @@ module ThemeCheck
145
145
 
146
146
  disable_tags "include"
147
147
 
148
- attr_reader :template_name_expr, :attributes
148
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
149
149
 
150
150
  def initialize(tag_name, markup, options)
151
151
  super
@@ -171,6 +171,7 @@ module ThemeCheck
171
171
  def children
172
172
  [
173
173
  @node.template_name_expr,
174
+ @node.variable_name_expr,
174
175
  ] + @node.attributes.values
175
176
  end
176
177
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.10.3"
3
+ VERSION = "1.11.0"
4
4
  end
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.6"
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.1.0')
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.10.3
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-06-17 00:00:00.000000000 Z
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.1.0
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.1.0
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/probots.yml"
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"
@@ -93,6 +95,7 @@ files:
93
95
  - docs/api/liquid_check.md
94
96
  - docs/checks/TEMPLATE.md.erb
95
97
  - docs/checks/app_block_valid_tags.md
98
+ - docs/checks/asset_preload.md
96
99
  - docs/checks/asset_size_app_block_css.md
97
100
  - docs/checks/asset_size_app_block_javascript.md
98
101
  - docs/checks/asset_size_css.md
@@ -153,6 +156,7 @@ files:
153
156
  - lib/theme_check/checks.rb
154
157
  - lib/theme_check/checks/TEMPLATE.rb.erb
155
158
  - lib/theme_check/checks/app_block_valid_tags.rb
159
+ - lib/theme_check/checks/asset_preload.rb
156
160
  - lib/theme_check/checks/asset_size_app_block_css.rb
157
161
  - lib/theme_check/checks/asset_size_app_block_javascript.rb
158
162
  - lib/theme_check/checks/asset_size_css.rb
@@ -294,7 +298,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
294
298
  requirements:
295
299
  - - ">="
296
300
  - !ruby/object:Gem::Version
297
- version: '2.6'
301
+ version: '2.7'
298
302
  required_rubygems_version: !ruby/object:Gem::Requirement
299
303
  requirements:
300
304
  - - ">="
data/.github/probots.yml DELETED
@@ -1,3 +0,0 @@
1
- # .github/probots.yml
2
- enabled:
3
- - cla