theme-check 0.4.0 → 0.5.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/CHANGELOG.md +10 -0
  4. data/CONTRIBUTING.md +5 -2
  5. data/README.md +9 -2
  6. data/RELEASING.md +1 -1
  7. data/config/default.yml +6 -0
  8. data/data/shopify_liquid/tags.yml +1 -0
  9. data/docs/checks/CHECK_DOCS_TEMPLATE.md +47 -0
  10. data/docs/checks/asset_size_javascript.md +79 -0
  11. data/docs/checks/convert_include_to_render.md +48 -0
  12. data/docs/checks/default_locale.md +46 -0
  13. data/docs/checks/deprecated_filter.md +46 -0
  14. data/docs/checks/liquid_tag.md +65 -0
  15. data/docs/checks/matching_schema_translations.md +93 -0
  16. data/docs/checks/matching_translations.md +72 -0
  17. data/docs/checks/missing_enable_comment.md +50 -0
  18. data/docs/checks/missing_required_template_files.md +26 -0
  19. data/docs/checks/missing_template.md +40 -0
  20. data/docs/checks/nested_snippet.md +69 -0
  21. data/docs/checks/parser_blocking_javascript.md +97 -0
  22. data/docs/checks/required_directories.md +25 -0
  23. data/docs/checks/required_layout_theme_object.md +28 -0
  24. data/docs/checks/space_inside_braces.md +63 -0
  25. data/docs/checks/syntax_error.md +49 -0
  26. data/docs/checks/template_length.md +50 -0
  27. data/docs/checks/translation_key_exists.md +63 -0
  28. data/docs/checks/undefined_object.md +53 -0
  29. data/docs/checks/unknown_filter.md +45 -0
  30. data/docs/checks/unused_assign.md +47 -0
  31. data/docs/checks/unused_snippet.md +32 -0
  32. data/docs/checks/valid_html_translation.md +53 -0
  33. data/docs/checks/valid_json.md +60 -0
  34. data/docs/checks/valid_schema.md +50 -0
  35. data/lib/theme_check.rb +3 -0
  36. data/lib/theme_check/asset_file.rb +34 -0
  37. data/lib/theme_check/check.rb +19 -9
  38. data/lib/theme_check/checks/asset_size_javascript.rb +74 -0
  39. data/lib/theme_check/checks/convert_include_to_render.rb +1 -1
  40. data/lib/theme_check/checks/default_locale.rb +1 -0
  41. data/lib/theme_check/checks/deprecated_filter.rb +1 -1
  42. data/lib/theme_check/checks/liquid_tag.rb +3 -3
  43. data/lib/theme_check/checks/matching_schema_translations.rb +1 -0
  44. data/lib/theme_check/checks/matching_translations.rb +1 -0
  45. data/lib/theme_check/checks/missing_enable_comment.rb +1 -0
  46. data/lib/theme_check/checks/missing_required_template_files.rb +1 -2
  47. data/lib/theme_check/checks/missing_template.rb +1 -0
  48. data/lib/theme_check/checks/nested_snippet.rb +1 -0
  49. data/lib/theme_check/checks/parser_blocking_javascript.rb +2 -1
  50. data/lib/theme_check/checks/required_directories.rb +1 -1
  51. data/lib/theme_check/checks/required_layout_theme_object.rb +1 -1
  52. data/lib/theme_check/checks/space_inside_braces.rb +1 -0
  53. data/lib/theme_check/checks/syntax_error.rb +1 -0
  54. data/lib/theme_check/checks/template_length.rb +1 -0
  55. data/lib/theme_check/checks/translation_key_exists.rb +1 -0
  56. data/lib/theme_check/checks/undefined_object.rb +10 -4
  57. data/lib/theme_check/checks/unknown_filter.rb +1 -0
  58. data/lib/theme_check/checks/unused_assign.rb +1 -0
  59. data/lib/theme_check/checks/unused_snippet.rb +1 -0
  60. data/lib/theme_check/checks/valid_html_translation.rb +1 -0
  61. data/lib/theme_check/checks/valid_json.rb +1 -0
  62. data/lib/theme_check/checks/valid_schema.rb +1 -0
  63. data/lib/theme_check/config.rb +2 -2
  64. data/lib/theme_check/language_server/completion_helper.rb +0 -10
  65. data/lib/theme_check/language_server/completion_provider.rb +1 -0
  66. data/lib/theme_check/language_server/handler.rb +11 -3
  67. data/lib/theme_check/language_server/server.rb +6 -1
  68. data/lib/theme_check/regex_helpers.rb +15 -0
  69. data/lib/theme_check/remote_asset_file.rb +44 -0
  70. data/lib/theme_check/theme.rb +7 -1
  71. data/lib/theme_check/version.rb +1 -1
  72. metadata +32 -2
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class NestedSnippet < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  class TemplateInfo < Struct.new(:includes)
9
10
  def with_deep_nested(templates, max, current_level = 0)
@@ -3,7 +3,8 @@ module ThemeCheck
3
3
  # Reports errors when trying to use parser-blocking script tags
4
4
  class ParserBlockingJavaScript < LiquidCheck
5
5
  severity :error
6
- category :liquid
6
+ categories :liquid, :performance
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  PARSER_BLOCKING_SCRIPT_TAG = %r{
9
10
  <script # Find the start of a script tag
@@ -5,7 +5,7 @@ module ThemeCheck
5
5
  class RequiredDirectories < LiquidCheck
6
6
  severity :error
7
7
  category :liquid
8
- doc "https://shopify.dev/tutorials/develop-theme-files"
8
+ doc docs_url(__FILE__)
9
9
 
10
10
  REQUIRED_DIRECTORIES = %w(assets config layout locales sections snippets templates)
11
11
 
@@ -4,7 +4,7 @@ module ThemeCheck
4
4
  class RequiredLayoutThemeObject < LiquidCheck
5
5
  severity :error
6
6
  category :liquid
7
- doc "https://shopify.dev/docs/themes/theme-templates/theme-liquid"
7
+ doc docs_url(__FILE__)
8
8
 
9
9
  LAYOUT_FILENAME = "layout/theme"
10
10
 
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class SpaceInsideBraces < LiquidCheck
5
5
  severity :style
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def initialize
9
10
  @ignore = false
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class SyntaxError < LiquidCheck
5
5
  severity :error
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  def on_document(node)
9
10
  node.template.warnings.each do |warning|
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class TemplateLength < LiquidCheck
4
4
  severity :suggestion
5
5
  category :liquid
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def initialize(max_length: 200, exclude_schema: true)
8
9
  @max_length = max_length
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class TranslationKeyExists < LiquidCheck
4
4
  severity :error
5
5
  category :translation
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def on_variable(node)
8
9
  return unless @theme.default_locale_json&.content&.is_a?(Hash)
@@ -2,7 +2,7 @@
2
2
  module ThemeCheck
3
3
  class UndefinedObject < LiquidCheck
4
4
  category :liquid
5
- doc "https://shopify.dev/docs/themes/liquid/reference/objects"
5
+ doc docs_url(__FILE__)
6
6
  severity :error
7
7
 
8
8
  class TemplateInfo
@@ -21,7 +21,13 @@ module ThemeCheck
21
21
  end
22
22
 
23
23
  def add_variable_lookup(name:, node:)
24
- line_number = node.parent.line_number
24
+ parent = node
25
+ line_number = nil
26
+ loop do
27
+ line_number = parent.line_number
28
+ parent = parent.parent
29
+ break unless line_number.nil? && parent
30
+ end
25
31
  key = [name, line_number]
26
32
  @all_variable_lookups[key] = node
27
33
  end
@@ -154,7 +160,7 @@ module ThemeCheck
154
160
  all_variables = info.all_variables
155
161
 
156
162
  info.each_variable_lookup(!!render_node) do |(key, node)|
157
- name, _line_number = key
163
+ name, line_number = key
158
164
  next if all_variables.include?(name)
159
165
  next if all_global_objects.include?(name)
160
166
 
@@ -164,7 +170,7 @@ module ThemeCheck
164
170
  if render_node
165
171
  add_offense("Missing argument `#{name}`", node: render_node)
166
172
  else
167
- add_offense("Undefined object `#{name}`", node: node)
173
+ add_offense("Undefined object `#{name}`", node: node, line_number: line_number)
168
174
  end
169
175
  end
170
176
  end
@@ -12,6 +12,7 @@ module ThemeCheck
12
12
  class UnknownFilter < LiquidCheck
13
13
  severity :error
14
14
  category :liquid
15
+ doc docs_url(__FILE__)
15
16
 
16
17
  def on_variable(node)
17
18
  used_filters = node.value.filters.map { |name, *_rest| name }
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  class UnusedAssign < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
+ doc docs_url(__FILE__)
7
8
 
8
9
  class TemplateInfo < Struct.new(:used_assigns, :assign_nodes, :includes)
9
10
  def collect_used_assigns(templates, visited = Set.new)
@@ -5,6 +5,7 @@ module ThemeCheck
5
5
  class UnusedSnippet < LiquidCheck
6
6
  severity :suggestion
7
7
  category :liquid
8
+ doc docs_url(__FILE__)
8
9
 
9
10
  def initialize
10
11
  @used_templates = Set.new
@@ -5,6 +5,7 @@ require 'nokogumbo'
5
5
  module ThemeCheck
6
6
  class ValidHTMLTranslation < JsonCheck
7
7
  severity :suggestion
8
+ doc docs_url(__FILE__)
8
9
 
9
10
  def on_file(file)
10
11
  return unless file.name.starts_with?("locales/")
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class ValidJson < JsonCheck
4
4
  severity :error
5
5
  category :json
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def on_file(file)
8
9
  if file.parse_error
@@ -3,6 +3,7 @@ module ThemeCheck
3
3
  class ValidSchema < LiquidCheck
4
4
  severity :suggestion
5
5
  category :json
6
+ doc docs_url(__FILE__)
6
7
 
7
8
  def on_schema(node)
8
9
  JSON.parse(node.value.nodelist.join)
@@ -83,8 +83,8 @@ module ThemeCheck
83
83
 
84
84
  check_class = ThemeCheck.const_get(check_name)
85
85
 
86
- next if exclude_categories.include?(check_class.category)
87
- next if only_categories.any? && !only_categories.include?(check_class.category)
86
+ next if check_class.categories.any? { |category| exclude_categories.include?(category) }
87
+ next if only_categories.any? && check_class.categories.none? { |category| only_categories.include?(category) }
88
88
 
89
89
  options_for_check = options.transform_keys(&:to_sym)
90
90
  options_for_check.delete(:enabled)
@@ -20,16 +20,6 @@ module ThemeCheck
20
20
  def first_word(content)
21
21
  return content.match(WORD)[0] if content.match?(WORD)
22
22
  end
23
-
24
- def matches(s, re)
25
- start_at = 0
26
- matches = []
27
- while (m = s.match(re, start_at))
28
- matches.push(m)
29
- start_at = m.end(0)
30
- end
31
- matches
32
- end
33
23
  end
34
24
  end
35
25
  end
@@ -4,6 +4,7 @@ module ThemeCheck
4
4
  module LanguageServer
5
5
  class CompletionProvider
6
6
  include CompletionHelper
7
+ include RegexHelpers
7
8
 
8
9
  class << self
9
10
  def all
@@ -138,12 +138,20 @@ module ThemeCheck
138
138
  end
139
139
 
140
140
  def offense_to_diagnostic(offense)
141
- {
141
+ diagnostic = {
142
+ code: offense.code_name,
143
+ message: offense.message,
142
144
  range: range(offense),
143
145
  severity: severity(offense),
144
- code: offense.code_name,
145
146
  source: "theme-check",
146
- message: offense.message,
147
+ }
148
+ diagnostic["codeDescription"] = code_description(offense) unless offense.doc.nil?
149
+ diagnostic
150
+ end
151
+
152
+ def code_description(offense)
153
+ {
154
+ href: offense.doc,
147
155
  }
148
156
  end
149
157
 
@@ -10,11 +10,13 @@ module ThemeCheck
10
10
 
11
11
  class Server
12
12
  attr_reader :handler
13
+ attr_reader :should_raise_errors
13
14
 
14
15
  def initialize(
15
16
  in_stream: STDIN,
16
17
  out_stream: STDOUT,
17
- err_stream: $DEBUG ? File.open('/tmp/lsp.log', 'a') : STDERR
18
+ err_stream: $DEBUG ? File.open('/tmp/lsp.log', 'a') : STDERR,
19
+ should_raise_errors: false
18
20
  )
19
21
  validate!([in_stream, out_stream, err_stream])
20
22
 
@@ -25,6 +27,8 @@ module ThemeCheck
25
27
 
26
28
  @out.sync = true # do not buffer
27
29
  @err.sync = true # do not buffer
30
+
31
+ @should_raise_errors = should_raise_errors
28
32
  end
29
33
 
30
34
  def listen
@@ -37,6 +41,7 @@ module ThemeCheck
37
41
  return 0
38
42
 
39
43
  rescue Exception => e # rubocop:disable Lint/RescueException
44
+ raise e if should_raise_errors
40
45
  log(e)
41
46
  log(e.backtrace)
42
47
  return 1
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module RegexHelpers
5
+ def matches(s, re)
6
+ start_at = 0
7
+ matches = []
8
+ while (m = s.match(re, start_at))
9
+ matches.push(m)
10
+ start_at = m.end(0)
11
+ end
12
+ matches
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require "net/http"
3
+ require "pathname"
4
+
5
+ module ThemeCheck
6
+ class RemoteAssetFile
7
+ class << self
8
+ def cache
9
+ @cache ||= {}
10
+ end
11
+
12
+ def from_src(src)
13
+ key = uri(src).to_s
14
+ cache[key] = RemoteAssetFile.new(src) unless cache.key?(key)
15
+ cache[key]
16
+ end
17
+
18
+ def uri(src)
19
+ URI.parse(src.sub(%r{^//}, "https://"))
20
+ end
21
+ end
22
+
23
+ def initialize(src)
24
+ @uri = RemoteAssetFile.uri(src)
25
+ @content = nil
26
+ end
27
+
28
+ def content
29
+ return @content unless @content.nil?
30
+
31
+ res = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == 'https') do |http|
32
+ req = Net::HTTP::Get.new(@uri)
33
+ req['Accept-Encoding'] = 'gzip, deflate, br'
34
+ http.request(req)
35
+ end
36
+
37
+ @content = res.body
38
+ end
39
+
40
+ def gzipped_size
41
+ @gzipped_size ||= @content.bytesize
42
+ end
43
+ end
44
+ end
@@ -11,6 +11,12 @@ module ThemeCheck
11
11
  @storage = storage
12
12
  end
13
13
 
14
+ def assets
15
+ @assets ||= @storage.files
16
+ .select { |path| path.starts_with?("assets/") }
17
+ .map { |path| AssetFile.new(path, @storage) }
18
+ end
19
+
14
20
  def liquid
15
21
  @liquid ||= @storage.files
16
22
  .select { |path| LIQUID_REGEX.match?(path) }
@@ -43,7 +49,7 @@ module ThemeCheck
43
49
  end
44
50
 
45
51
  def all
46
- @all ||= json + liquid
52
+ @all ||= json + liquid + assets
47
53
  end
48
54
 
49
55
  def [](name)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.0"
4
4
  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: 0.4.0
4
+ version: 0.5.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: 2021-02-25 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -82,13 +82,41 @@ files:
82
82
  - data/shopify_liquid/plus_objects.yml
83
83
  - data/shopify_liquid/tags.yml
84
84
  - dev.yml
85
+ - docs/checks/CHECK_DOCS_TEMPLATE.md
86
+ - docs/checks/asset_size_javascript.md
87
+ - docs/checks/convert_include_to_render.md
88
+ - docs/checks/default_locale.md
89
+ - docs/checks/deprecated_filter.md
90
+ - docs/checks/liquid_tag.md
91
+ - docs/checks/matching_schema_translations.md
92
+ - docs/checks/matching_translations.md
93
+ - docs/checks/missing_enable_comment.md
94
+ - docs/checks/missing_required_template_files.md
95
+ - docs/checks/missing_template.md
96
+ - docs/checks/nested_snippet.md
97
+ - docs/checks/parser_blocking_javascript.md
98
+ - docs/checks/required_directories.md
99
+ - docs/checks/required_layout_theme_object.md
100
+ - docs/checks/space_inside_braces.md
101
+ - docs/checks/syntax_error.md
102
+ - docs/checks/template_length.md
103
+ - docs/checks/translation_key_exists.md
104
+ - docs/checks/undefined_object.md
105
+ - docs/checks/unknown_filter.md
106
+ - docs/checks/unused_assign.md
107
+ - docs/checks/unused_snippet.md
108
+ - docs/checks/valid_html_translation.md
109
+ - docs/checks/valid_json.md
110
+ - docs/checks/valid_schema.md
85
111
  - docs/preview.png
86
112
  - exe/theme-check
87
113
  - exe/theme-check-language-server
88
114
  - lib/theme_check.rb
89
115
  - lib/theme_check/analyzer.rb
116
+ - lib/theme_check/asset_file.rb
90
117
  - lib/theme_check/check.rb
91
118
  - lib/theme_check/checks.rb
119
+ - lib/theme_check/checks/asset_size_javascript.rb
92
120
  - lib/theme_check/checks/convert_include_to_render.rb
93
121
  - lib/theme_check/checks/default_locale.rb
94
122
  - lib/theme_check/checks/deprecated_filter.rb
@@ -142,6 +170,8 @@ files:
142
170
  - lib/theme_check/packager.rb
143
171
  - lib/theme_check/parsing_helpers.rb
144
172
  - lib/theme_check/printer.rb
173
+ - lib/theme_check/regex_helpers.rb
174
+ - lib/theme_check/remote_asset_file.rb
145
175
  - lib/theme_check/shopify_liquid.rb
146
176
  - lib/theme_check/shopify_liquid/deprecated_filter.rb
147
177
  - lib/theme_check/shopify_liquid/filter.rb