theme-check 0.3.2 → 0.7.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +10 -3
  3. data/.rubocop.yml +12 -3
  4. data/CHANGELOG.md +42 -0
  5. data/CONTRIBUTING.md +5 -2
  6. data/Gemfile +5 -3
  7. data/LICENSE.md +2 -0
  8. data/README.md +12 -4
  9. data/RELEASING.md +10 -3
  10. data/Rakefile +6 -0
  11. data/config/default.yml +16 -0
  12. data/data/shopify_liquid/tags.yml +27 -0
  13. data/data/shopify_translation_keys.yml +850 -0
  14. data/docs/checks/CHECK_DOCS_TEMPLATE.md +47 -0
  15. data/docs/checks/asset_size_css.md +52 -0
  16. data/docs/checks/asset_size_javascript.md +79 -0
  17. data/docs/checks/convert_include_to_render.md +48 -0
  18. data/docs/checks/default_locale.md +46 -0
  19. data/docs/checks/deprecated_filter.md +46 -0
  20. data/docs/checks/img_width_and_height.md +79 -0
  21. data/docs/checks/liquid_tag.md +65 -0
  22. data/docs/checks/matching_schema_translations.md +93 -0
  23. data/docs/checks/matching_translations.md +72 -0
  24. data/docs/checks/missing_enable_comment.md +50 -0
  25. data/docs/checks/missing_required_template_files.md +26 -0
  26. data/docs/checks/missing_template.md +40 -0
  27. data/docs/checks/nested_snippet.md +69 -0
  28. data/docs/checks/parser_blocking_javascript.md +97 -0
  29. data/docs/checks/remote_asset.md +82 -0
  30. data/docs/checks/required_directories.md +25 -0
  31. data/docs/checks/required_layout_theme_object.md +28 -0
  32. data/docs/checks/space_inside_braces.md +63 -0
  33. data/docs/checks/syntax_error.md +49 -0
  34. data/docs/checks/template_length.md +50 -0
  35. data/docs/checks/translation_key_exists.md +63 -0
  36. data/docs/checks/undefined_object.md +53 -0
  37. data/docs/checks/unknown_filter.md +45 -0
  38. data/docs/checks/unused_assign.md +47 -0
  39. data/docs/checks/unused_snippet.md +32 -0
  40. data/docs/checks/valid_html_translation.md +53 -0
  41. data/docs/checks/valid_json.md +60 -0
  42. data/docs/checks/valid_schema.md +50 -0
  43. data/lib/theme_check.rb +4 -0
  44. data/lib/theme_check/asset_file.rb +34 -0
  45. data/lib/theme_check/check.rb +20 -10
  46. data/lib/theme_check/checks/asset_size_css.rb +89 -0
  47. data/lib/theme_check/checks/asset_size_javascript.rb +68 -0
  48. data/lib/theme_check/checks/convert_include_to_render.rb +1 -1
  49. data/lib/theme_check/checks/default_locale.rb +1 -0
  50. data/lib/theme_check/checks/deprecated_filter.rb +1 -1
  51. data/lib/theme_check/checks/img_width_and_height.rb +74 -0
  52. data/lib/theme_check/checks/liquid_tag.rb +3 -3
  53. data/lib/theme_check/checks/matching_schema_translations.rb +1 -0
  54. data/lib/theme_check/checks/matching_translations.rb +2 -1
  55. data/lib/theme_check/checks/missing_enable_comment.rb +1 -0
  56. data/lib/theme_check/checks/missing_required_template_files.rb +1 -2
  57. data/lib/theme_check/checks/missing_template.rb +1 -0
  58. data/lib/theme_check/checks/nested_snippet.rb +1 -0
  59. data/lib/theme_check/checks/parser_blocking_javascript.rb +8 -15
  60. data/lib/theme_check/checks/remote_asset.rb +98 -0
  61. data/lib/theme_check/checks/required_directories.rb +1 -1
  62. data/lib/theme_check/checks/required_layout_theme_object.rb +1 -1
  63. data/lib/theme_check/checks/space_inside_braces.rb +1 -0
  64. data/lib/theme_check/checks/syntax_error.rb +1 -0
  65. data/lib/theme_check/checks/template_length.rb +1 -0
  66. data/lib/theme_check/checks/translation_key_exists.rb +14 -1
  67. data/lib/theme_check/checks/undefined_object.rb +16 -7
  68. data/lib/theme_check/checks/unknown_filter.rb +1 -0
  69. data/lib/theme_check/checks/unused_assign.rb +5 -3
  70. data/lib/theme_check/checks/unused_snippet.rb +1 -0
  71. data/lib/theme_check/checks/valid_html_translation.rb +2 -1
  72. data/lib/theme_check/checks/valid_json.rb +1 -0
  73. data/lib/theme_check/checks/valid_schema.rb +1 -0
  74. data/lib/theme_check/cli.rb +49 -13
  75. data/lib/theme_check/config.rb +5 -2
  76. data/lib/theme_check/disabled_checks.rb +2 -2
  77. data/lib/theme_check/in_memory_storage.rb +13 -8
  78. data/lib/theme_check/language_server.rb +12 -0
  79. data/lib/theme_check/language_server/completion_engine.rb +38 -0
  80. data/lib/theme_check/language_server/completion_helper.rb +25 -0
  81. data/lib/theme_check/language_server/completion_provider.rb +28 -0
  82. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +51 -0
  83. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +31 -0
  84. data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +43 -0
  85. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +31 -0
  86. data/lib/theme_check/language_server/constants.rb +10 -0
  87. data/lib/theme_check/language_server/document_link_engine.rb +48 -0
  88. data/lib/theme_check/language_server/handler.rb +105 -10
  89. data/lib/theme_check/language_server/position_helper.rb +27 -0
  90. data/lib/theme_check/language_server/protocol.rb +41 -0
  91. data/lib/theme_check/language_server/server.rb +9 -4
  92. data/lib/theme_check/language_server/tokens.rb +55 -0
  93. data/lib/theme_check/liquid_check.rb +11 -0
  94. data/lib/theme_check/node.rb +1 -2
  95. data/lib/theme_check/offense.rb +52 -15
  96. data/lib/theme_check/regex_helpers.rb +15 -0
  97. data/lib/theme_check/releaser.rb +39 -0
  98. data/lib/theme_check/remote_asset_file.rb +44 -0
  99. data/lib/theme_check/shopify_liquid.rb +1 -0
  100. data/lib/theme_check/shopify_liquid/deprecated_filter.rb +10 -8
  101. data/lib/theme_check/shopify_liquid/filter.rb +3 -5
  102. data/lib/theme_check/shopify_liquid/object.rb +2 -6
  103. data/lib/theme_check/shopify_liquid/tag.rb +14 -0
  104. data/lib/theme_check/storage.rb +3 -3
  105. data/lib/theme_check/string_helpers.rb +47 -0
  106. data/lib/theme_check/tags.rb +1 -2
  107. data/lib/theme_check/theme.rb +7 -1
  108. data/lib/theme_check/version.rb +1 -1
  109. data/theme-check.gemspec +1 -2
  110. metadata +57 -18
@@ -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"
@@ -19,6 +22,7 @@ require_relative "theme_check/offense"
19
22
  require_relative "theme_check/printer"
20
23
  require_relative "theme_check/shopify_liquid"
21
24
  require_relative "theme_check/storage"
25
+ require_relative "theme_check/string_helpers"
22
26
  require_relative "theme_check/file_system_storage"
23
27
  require_relative "theme_check/in_memory_storage"
24
28
  require_relative "theme_check/tags"
@@ -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
@@ -72,7 +82,7 @@ module ThemeCheck
72
82
  end
73
83
 
74
84
  def code_name
75
- self.class.name.demodulize
85
+ StringHelpers.demodulize(self.class.name)
76
86
  end
77
87
 
78
88
  def ignore!
@@ -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,89 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ class AssetSizeCSS < LiquidCheck
4
+ include RegexHelpers
5
+ severity :error
6
+ category :performance
7
+ doc docs_url(__FILE__)
8
+
9
+ Link = Struct.new(:href, :index)
10
+
11
+ LINK_TAG_HREF = %r{
12
+ <link
13
+ (?=[^>]+?rel=['"]?stylesheet['"]?) # Make sure rel=stylesheet is in the link with lookahead
14
+ [^>]+ # any non closing tag character
15
+ href= # href attribute start
16
+ (?<href>#{QUOTED_LIQUID_ATTRIBUTE}) # href attribute value (may contain liquid)
17
+ [^>]* # any non closing character till the end
18
+ >
19
+ }omix
20
+ STYLESHEET_TAG = %r{
21
+ #{Liquid::VariableStart} # VariableStart
22
+ (?:(?!#{Liquid::VariableEnd}).)*? # anything that isn't followed by a VariableEnd
23
+ \|\s*asset_url\s* # | asset_url
24
+ \|\s*stylesheet_tag\s* # | stylesheet_tag
25
+ #{Liquid::VariableEnd} # VariableEnd
26
+ }omix
27
+
28
+ attr_reader :threshold_in_bytes
29
+
30
+ def initialize(threshold_in_bytes: 100_000)
31
+ @threshold_in_bytes = threshold_in_bytes
32
+ end
33
+
34
+ def on_document(node)
35
+ @node = node
36
+ @source = node.template.source
37
+ record_offenses
38
+ end
39
+
40
+ def record_offenses
41
+ stylesheets(@source).each do |stylesheet|
42
+ file_size = href_to_file_size(stylesheet.href)
43
+ next if file_size.nil?
44
+ next if file_size <= threshold_in_bytes
45
+ add_offense(
46
+ "CSS on every page load exceding compressed size threshold (#{threshold_in_bytes} Bytes).",
47
+ node: @node,
48
+ markup: stylesheet.href,
49
+ line_number: @source[0...stylesheet.index].count("\n") + 1
50
+ )
51
+ end
52
+ end
53
+
54
+ def stylesheets(source)
55
+ stylesheet_links = matches(source, LINK_TAG_HREF)
56
+ .map do |m|
57
+ Link.new(
58
+ m[:href].gsub(START_OR_END_QUOTE, ""),
59
+ m.begin(:href),
60
+ )
61
+ end
62
+
63
+ stylesheet_tags = matches(source, STYLESHEET_TAG)
64
+ .map do |m|
65
+ Link.new(
66
+ m[0],
67
+ m.begin(0),
68
+ )
69
+ end
70
+
71
+ stylesheet_links + stylesheet_tags
72
+ end
73
+
74
+ def href_to_file_size(href)
75
+ # asset_url (+ optional stylesheet_tag) variables
76
+ if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
77
+ asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
78
+ asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
79
+ return if asset.nil?
80
+ asset.gzipped_size
81
+
82
+ # remote URLs
83
+ elsif href =~ %r{^(https?:)?//}
84
+ asset = RemoteAssetFile.from_src(href)
85
+ asset.gzipped_size
86
+ end
87
+ end
88
+ end
89
+ end