theme-check 1.10.3 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
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