theme-check 0.3.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -0
  3. data/CHANGELOG.md +50 -0
  4. data/CONTRIBUTING.md +5 -2
  5. data/README.md +9 -4
  6. data/RELEASING.md +2 -2
  7. data/config/default.yml +7 -0
  8. data/data/shopify_liquid/tags.yml +27 -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 +4 -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 +29 -10
  57. data/lib/theme_check/checks/unknown_filter.rb +1 -0
  58. data/lib/theme_check/checks/unused_assign.rb +5 -3
  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/cli.rb +22 -6
  64. data/lib/theme_check/config.rb +2 -2
  65. data/lib/theme_check/in_memory_storage.rb +1 -1
  66. data/lib/theme_check/language_server.rb +10 -0
  67. data/lib/theme_check/language_server/completion_engine.rb +38 -0
  68. data/lib/theme_check/language_server/completion_helper.rb +25 -0
  69. data/lib/theme_check/language_server/completion_provider.rb +24 -0
  70. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +47 -0
  71. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
  72. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +31 -0
  73. data/lib/theme_check/language_server/handler.rb +62 -6
  74. data/lib/theme_check/language_server/position_helper.rb +27 -0
  75. data/lib/theme_check/language_server/protocol.rb +41 -0
  76. data/lib/theme_check/language_server/server.rb +6 -1
  77. data/lib/theme_check/language_server/tokens.rb +55 -0
  78. data/lib/theme_check/offense.rb +51 -14
  79. data/lib/theme_check/regex_helpers.rb +15 -0
  80. data/lib/theme_check/remote_asset_file.rb +44 -0
  81. data/lib/theme_check/shopify_liquid.rb +1 -0
  82. data/lib/theme_check/shopify_liquid/tag.rb +16 -0
  83. data/lib/theme_check/theme.rb +7 -1
  84. data/lib/theme_check/version.rb +1 -1
  85. metadata +44 -2
@@ -0,0 +1,47 @@
1
+ # Prevent unused assigns (`UnusedAssign`)
2
+
3
+ This check exists to prevent bloat in themes by surfacing variable definitions that are not used.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating bloat in themes and highlight user errors.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```liquid
12
+ {% assign this_variable_is_not_used = 1 %}
13
+ ```
14
+
15
+ :+1: Examples of **correct** code for this check:
16
+
17
+ ```liquid
18
+ {% assign this_variable_is_used = 1 %}
19
+ {% if this_variable_is_used == 1 %}
20
+ <span>Hello!</span>
21
+ {% endif %}
22
+ ```
23
+
24
+ ## Check Options
25
+
26
+ The default configuration for this check is the following:
27
+
28
+ ```yaml
29
+ UnusedAssign:
30
+ enabled: true
31
+ ```
32
+
33
+ ## When Not To Use It
34
+
35
+ It's safe to disable this rule.
36
+
37
+ ## Version
38
+
39
+ This check has been introduced in Theme Check 0.1.0.
40
+
41
+ ## Resources
42
+
43
+ - [Rule Source][codesource]
44
+ - [Documentation Source][docsource]
45
+
46
+ [codesource]: /lib/theme_check/checks/unused_assign.rb
47
+ [docsource]: /docs/checks/unused_assign.md
@@ -0,0 +1,32 @@
1
+ # Remove unused snippets in themes (`UnusedSnippet`)
2
+
3
+ This check warns the user about snippets that are not used (Could not find a `render` tag that uses that snippet)
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating unused snippets.
8
+
9
+ ## Check Options
10
+
11
+ The default configuration for this check is the following:
12
+
13
+ ```yaml
14
+ UnusedSnippet:
15
+ enabled: true
16
+ ```
17
+
18
+ ## When Not To Use It
19
+
20
+ It's safe to disable this rule.
21
+
22
+ ## Version
23
+
24
+ This check has been introduced in Theme Check 0.1.0.
25
+
26
+ ## Resources
27
+
28
+ - [Rule Source][codesource]
29
+ - [Documentation Source][docsource]
30
+
31
+ [codesource]: /lib/theme_check/checks/unused_snippet.rb
32
+ [docsource]: /docs/checks/unused_snippet.md
@@ -0,0 +1,53 @@
1
+ # Prevent invalid HTML inside translations (`ValidHTMLTranslation`)
2
+
3
+ This check exists to prevent invalid HTML inside translations.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating invalid HTML in translations.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```liquid
12
+ {
13
+ "hello_html": "<h2>Hello, world</h1>",
14
+ "image_html": "<a href='/spongebob'>Unclosed"
15
+ }
16
+ ```
17
+
18
+ :+1: Examples of **correct** code for this check:
19
+
20
+ ```liquid
21
+ {% comment %}locales/en.default.json{% endcomment %}
22
+ {
23
+ "hello_html": "<h1>Hello, world</h1>",
24
+ "image_html": "<img src='spongebob.png'>",
25
+ "line_break_html": "<br>",
26
+ "self_closing_svg_html": "<svg />"
27
+ }
28
+ ```
29
+
30
+ ## Check Options
31
+
32
+ The default configuration for this check is the following:
33
+
34
+ ```yaml
35
+ ValidHTMLTranslation:
36
+ enabled: true
37
+ ```
38
+
39
+ ## When Not To Use It
40
+
41
+ It is discouraged to to disable this rule.
42
+
43
+ ## Version
44
+
45
+ This check has been introduced in Theme Check 0.1.0.
46
+
47
+ ## Resources
48
+
49
+ - [Rule Source][codesource]
50
+ - [Documentation Source][docsource]
51
+
52
+ [codesource]: /lib/theme_check/checks/valid_html_translation.rb
53
+ [docsource]: /docs/checks/valid_html_translation.md
@@ -0,0 +1,60 @@
1
+ # Enforce valid JSON (`ValidJson`)
2
+
3
+ This check exists to prevent invalid JSON files in themes.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating errors in JSON files.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```json
12
+ {
13
+ "comma": "trailing",
14
+ }
15
+ ```
16
+
17
+ ```json
18
+ {
19
+ "quotes": 'Oops, those are single quotes'
20
+ }
21
+ ```
22
+
23
+ :+1: Examples of **correct** code for this check:
24
+
25
+ ```json
26
+ {
27
+ "comma": "not trailing"
28
+ }
29
+ ```
30
+
31
+ ```json
32
+ {
33
+ "quotes": "Yes. Double quotes."
34
+ }
35
+ ```
36
+
37
+ ## Check Options
38
+
39
+ The default configuration for this check is the following:
40
+
41
+ ```yaml
42
+ ValidJson:
43
+ enabled: true
44
+ ```
45
+
46
+ ## When Not To Use It
47
+
48
+ It is not safe to disable this rule.
49
+
50
+ ## Version
51
+
52
+ This check has been introduced in Theme Check 0.1.0.
53
+
54
+ ## Resources
55
+
56
+ - [Rule Source][codesource]
57
+ - [Documentation Source][docsource]
58
+
59
+ [codesource]: /lib/theme_check/checks/valid_json.rb
60
+ [docsource]: /docs/checks/valid_json.md
@@ -0,0 +1,50 @@
1
+ # Enforce valid JSON in schema tags (`ValidSchema`)
2
+
3
+ This check exists to prevent invalid JSON in `{% schema %}` tags.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating JSON errors in schema tags.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```liquid
12
+ {% schema %}
13
+ {
14
+ "comma": "trailing",
15
+ }
16
+ {% endschema %}
17
+ ```
18
+
19
+ :+1: Examples of **correct** code for this check:
20
+
21
+ ```liquid
22
+ {
23
+ "comma": "not trailing"
24
+ }
25
+ ```
26
+
27
+ ## Check Options
28
+
29
+ The default configuration for this check is the following:
30
+
31
+ ```yaml
32
+ ValidSchema:
33
+ enabled: true
34
+ ```
35
+
36
+ ## When Not To Use It
37
+
38
+ It is not safe to disable this check.
39
+
40
+ ## Version
41
+
42
+ This check has been introduced in Theme Check 0.1.0.
43
+
44
+ ## Resources
45
+
46
+ - [Rule Source][codesource]
47
+ - [Documentation Source][docsource]
48
+
49
+ [codesource]: /lib/theme_check/checks/valid_schema.rb
50
+ [docsource]: /docs/checks/valid_schema.md
data/lib/theme_check.rb CHANGED
@@ -8,6 +8,9 @@ require_relative "theme_check/cli"
8
8
  require_relative "theme_check/disabled_checks"
9
9
  require_relative "theme_check/liquid_check"
10
10
  require_relative "theme_check/locale_diff"
11
+ require_relative "theme_check/asset_file"
12
+ require_relative "theme_check/remote_asset_file"
13
+ require_relative "theme_check/regex_helpers"
11
14
  require_relative "theme_check/json_check"
12
15
  require_relative "theme_check/json_file"
13
16
  require_relative "theme_check/json_helpers"
@@ -26,5 +29,6 @@ require_relative "theme_check/template"
26
29
  require_relative "theme_check/theme"
27
30
  require_relative "theme_check/visitor"
28
31
  require_relative "theme_check/corrector"
32
+ require_relative "theme_check/version"
29
33
 
30
34
  Dir[__dir__ + "/theme_check/checks/*.rb"].each { |file| require file }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require "pathname"
3
+ require "zlib"
4
+
5
+ module ThemeCheck
6
+ class AssetFile
7
+ def initialize(relative_path, storage)
8
+ @relative_path = relative_path
9
+ @storage = storage
10
+ @loaded = false
11
+ @content = nil
12
+ end
13
+
14
+ def path
15
+ @storage.path(@relative_path)
16
+ end
17
+
18
+ def relative_path
19
+ @relative_pathname ||= Pathname.new(@relative_path)
20
+ end
21
+
22
+ def content
23
+ @content ||= @storage.read(@relative_path)
24
+ end
25
+
26
+ def gzipped_size
27
+ @gzipped_size ||= Zlib.gzip(content).bytesize
28
+ end
29
+
30
+ def name
31
+ relative_path.to_s
32
+ end
33
+ end
34
+ end
@@ -18,7 +18,9 @@ module ThemeCheck
18
18
  CATEGORIES = [
19
19
  :liquid,
20
20
  :translation,
21
+ :performance,
21
22
  :json,
23
+ :performance,
22
24
  ]
23
25
 
24
26
  class << self
@@ -36,21 +38,29 @@ module ThemeCheck
36
38
  @severity if defined?(@severity)
37
39
  end
38
40
 
39
- def category(category = nil)
40
- if category
41
- unless CATEGORIES.include?(category)
42
- raise ArgumentError, "unknown category. Use: #{CATEGORIES.join(', ')}"
41
+ def categories(*categories)
42
+ @categories ||= []
43
+ if categories.any?
44
+ unknown_categories = categories.select { |category| !CATEGORIES.include?(category) }
45
+ if unknown_categories.any?
46
+ raise ArgumentError,
47
+ "unknown categories: #{unknown_categories.join(', ')}. Use: #{CATEGORIES.join(', ')}"
43
48
  end
44
- @category = category
49
+ @categories = categories
45
50
  end
46
- @category if defined?(@category)
51
+ @categories
47
52
  end
53
+ alias_method :category, :categories
48
54
 
49
55
  def doc(doc = nil)
50
56
  @doc = doc if doc
51
57
  @doc if defined?(@doc)
52
58
  end
53
59
 
60
+ def docs_url(path)
61
+ "https://github.com/Shopify/theme-check/blob/master/docs/checks/#{File.basename(path, '.rb')}.md"
62
+ end
63
+
54
64
  def can_disable(disableable = nil)
55
65
  unless disableable.nil?
56
66
  @can_disable = disableable
@@ -63,8 +73,8 @@ module ThemeCheck
63
73
  self.class.severity
64
74
  end
65
75
 
66
- def category
67
- self.class.category
76
+ def categories
77
+ self.class.categories
68
78
  end
69
79
 
70
80
  def doc
@@ -93,7 +103,7 @@ module ThemeCheck
93
103
 
94
104
  def to_s
95
105
  s = +"#{code_name}:\n"
96
- properties = { severity: severity, category: category, doc: doc }.merge(options)
106
+ properties = { severity: severity, categories: categories, doc: doc }.merge(options)
97
107
  properties.each_pair do |name, value|
98
108
  s << " #{name}: #{value}\n" if value
99
109
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ # Reports errors when trying to use too much JavaScript on page load
4
+ # Encourages the use of the Import on Interaction pattern [1].
5
+ # [1]: https://addyosmani.com/blog/import-on-interaction/
6
+ class AssetSizeJavaScript < LiquidCheck
7
+ include RegexHelpers
8
+ severity :error
9
+ category :performance
10
+ doc docs_url(__FILE__)
11
+
12
+ Script = Struct.new(:src, :match)
13
+
14
+ TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
15
+ VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
16
+ START_OR_END_QUOTE = /(^['"])|(['"]$)/
17
+ SCRIPT_TAG_SRC = %r{
18
+ <script
19
+ [^>]+ # any non closing tag character
20
+ src= # src attribute start
21
+ (?<src>
22
+ '(?:#{TAG}|#{VARIABLE}|[^']+)*'| # any combination of tag/variable or non straight quote inside straight quotes
23
+ "(?:#{TAG}|#{VARIABLE}|[^"]+)*" # any combination of tag/variable or non double quotes inside double quotes
24
+ )
25
+ [^>]* # any non closing character till the end
26
+ >
27
+ }omix
28
+
29
+ attr_reader :threshold_in_bytes
30
+
31
+ def initialize(threshold_in_bytes: 10000)
32
+ @threshold_in_bytes = threshold_in_bytes
33
+ end
34
+
35
+ def on_document(node)
36
+ @node = node
37
+ @source = node.template.source
38
+ record_offenses
39
+ end
40
+
41
+ def record_offenses
42
+ scripts(@source).each do |script|
43
+ file_size = src_to_file_size(script.src)
44
+ next if file_size.nil?
45
+ next if file_size <= threshold_in_bytes
46
+ add_offense(
47
+ "JavaScript on every page load exceding compressed size threshold (#{threshold_in_bytes} Bytes), consider using the import on interaction pattern.",
48
+ node: @node,
49
+ markup: script.src,
50
+ line_number: @source[0...script.match.begin(:src)].count("\n") + 1
51
+ )
52
+ end
53
+ end
54
+
55
+ def scripts(source)
56
+ matches(source, SCRIPT_TAG_SRC)
57
+ .map { |m| Script.new(m[:src].gsub(START_OR_END_QUOTE, ""), m) }
58
+ end
59
+
60
+ def src_to_file_size(src)
61
+ # We're kind of intentionally only looking at {{ 'asset' | asset_url }} or full urls in here.
62
+ # More complicated liquid statements are not in scope.
63
+ if src =~ /^#{VARIABLE}$/o && src =~ /asset_url/ && src =~ Liquid::QuotedString
64
+ asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
65
+ asset = @theme.assets.find { |a| a.name.ends_with?("/" + asset_id) }
66
+ return if asset.nil?
67
+ asset.gzipped_size
68
+ elsif src =~ %r{^(https?:)?//}
69
+ asset = RemoteAssetFile.from_src(src)
70
+ asset.gzipped_size
71
+ end
72
+ end
73
+ end
74
+ end
@@ -4,7 +4,7 @@ module ThemeCheck
4
4
  class ConvertIncludeToRender < LiquidCheck
5
5
  severity :suggestion
6
6
  category :liquid
7
- doc "https://shopify.dev/docs/themes/liquid/reference/tags/deprecated-tags#include"
7
+ doc docs_url(__FILE__)
8
8
 
9
9
  def on_include(node)
10
10
  add_offense("`include` is deprecated - convert it to `render`", node: node)