theme-check 0.8.2 → 0.10.1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +3 -0
  3. data/CHANGELOG.md +45 -0
  4. data/CONTRIBUTING.md +20 -90
  5. data/README.md +4 -1
  6. data/RELEASING.md +5 -3
  7. data/Rakefile +31 -0
  8. data/config/default.yml +45 -0
  9. data/docs/api/check.md +15 -0
  10. data/docs/api/html_check.md +46 -0
  11. data/docs/api/json_check.md +19 -0
  12. data/docs/api/liquid_check.md +99 -0
  13. data/docs/checks/{CHECK_DOCS_TEMPLATE.md → TEMPLATE.md.erb} +5 -5
  14. data/docs/checks/asset_url_filters.md +56 -0
  15. data/docs/checks/content_for_header_modification.md +42 -0
  16. data/docs/checks/img_lazy_loading.md +61 -0
  17. data/docs/checks/parser_blocking_script_tag.md +53 -0
  18. data/exe/theme-check-language-server +1 -2
  19. data/lib/theme_check.rb +8 -1
  20. data/lib/theme_check/analyzer.rb +72 -16
  21. data/lib/theme_check/bug.rb +1 -0
  22. data/lib/theme_check/check.rb +32 -7
  23. data/lib/theme_check/checks.rb +9 -1
  24. data/lib/theme_check/checks/TEMPLATE.rb.erb +11 -0
  25. data/lib/theme_check/checks/asset_url_filters.rb +46 -0
  26. data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
  27. data/lib/theme_check/checks/img_lazy_loading.rb +25 -0
  28. data/lib/theme_check/checks/img_width_and_height.rb +18 -49
  29. data/lib/theme_check/checks/missing_template.rb +1 -0
  30. data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
  31. data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
  32. data/lib/theme_check/checks/remote_asset.rb +21 -79
  33. data/lib/theme_check/checks/template_length.rb +3 -0
  34. data/lib/theme_check/checks/valid_html_translation.rb +1 -0
  35. data/lib/theme_check/config.rb +2 -0
  36. data/lib/theme_check/disabled_check.rb +6 -4
  37. data/lib/theme_check/disabled_checks.rb +25 -9
  38. data/lib/theme_check/html_check.rb +7 -0
  39. data/lib/theme_check/html_node.rb +56 -0
  40. data/lib/theme_check/html_visitor.rb +38 -0
  41. data/lib/theme_check/json_file.rb +13 -0
  42. data/lib/theme_check/language_server.rb +1 -0
  43. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
  44. data/lib/theme_check/language_server/diagnostics_tracker.rb +66 -0
  45. data/lib/theme_check/language_server/handler.rb +31 -26
  46. data/lib/theme_check/language_server/server.rb +1 -1
  47. data/lib/theme_check/liquid_check.rb +1 -4
  48. data/lib/theme_check/offense.rb +18 -0
  49. data/lib/theme_check/template.rb +9 -0
  50. data/lib/theme_check/theme.rb +7 -2
  51. data/lib/theme_check/version.rb +1 -1
  52. data/lib/theme_check/visitor.rb +2 -11
  53. metadata +20 -3
@@ -0,0 +1,19 @@
1
+ # JSON check API
2
+
3
+ For checking the content of `.json` files.
4
+
5
+ ```ruby
6
+ module ThemeCheck
7
+ class MyCheckName < JsonCheck
8
+ category :json,
9
+ # A check can belong to multiple categories. Valid ones:
10
+ categories :translation, :performance
11
+ severity :suggestion # :error or :style
12
+
13
+ def on_file(file)
14
+ file # an instance of `ThemeCheck::JsonFile`
15
+ file.content # the parsed JSON, as a Ruby object, usually a Hash
16
+ end
17
+ end
18
+ end
19
+ ```
@@ -0,0 +1,99 @@
1
+ # Liquid check API
2
+
3
+ For checking the Liquid code in `.liquid` files.
4
+
5
+ All code inside `{% ... %}` or `{{ ... }}` is Liquid code.
6
+
7
+ Liquid files are parsed using the Liquid parser, by consequence you will get Liquid nodes (tags, blocks) in your callback methods. Check the Liquid source for details on those nodes: [Liquid source][liquidsource].
8
+
9
+
10
+ ```ruby
11
+ module ThemeCheck
12
+ class MyCheckName < LiquidCheck
13
+ category :liquid,
14
+ # A check can belong to multiple categories. Valid ones:
15
+ categories :translation, :performance
16
+ severity :suggestion # :error or :style
17
+
18
+ def on_document(node)
19
+ # Called with the root node of all templates
20
+ node.value # is the original Liquid object for this node. See Liquid source code for details.
21
+ node.template # is the template being analyzed, See lib/theme_check/template.rb.
22
+ node.parent # is the parent node.
23
+ node.children # are the children nodes.
24
+ # See lib/theme_check/node.rb for more helper methods
25
+ theme # Gives you access to all the templates in the theme. See lib/theme_check/theme.rb.
26
+ end
27
+
28
+ def on_node(node)
29
+ # Called for every node
30
+ end
31
+
32
+ def on_tag(node)
33
+ # Called for each tag (if, include, for, assign, etc.)
34
+ end
35
+
36
+ def after_tag(node)
37
+ # Called after the tag children have been visited
38
+
39
+ # If you find an issue, add an offense:
40
+ add_offense("Describe the problem...", node: node)
41
+ # Or, if the offense is related to the whole template:
42
+ add_offense("Describe the problem...", template: node.template)
43
+ end
44
+
45
+ def on_assign(node)
46
+ # Called only for {% assign ... %} tags
47
+ end
48
+
49
+ def on_string(node)
50
+ # Called for every `String` (including inside if conditions).
51
+ if node.parent.block?
52
+ # If parent is a block, `node.value` is a String written directly to the output when
53
+ # the template is rendered.
54
+ end
55
+ end
56
+
57
+ def on_variable(node)
58
+ # Called for each {{ ... }}
59
+ end
60
+
61
+ def on_error(exception)
62
+ # Called each time a Liquid exception is raised while parsing the template
63
+ end
64
+
65
+ def on_end
66
+ # A special callback after we're done visiting all the files of the theme
67
+ end
68
+
69
+ # Each type of node has a corresponding `on_node_class_name` & `after_node_class_name`
70
+ # A few common examples:
71
+ # on_capture(node)
72
+ # on_case(node)
73
+ # on_comment(node)
74
+ # on_if(node)
75
+ # on_condition(node)
76
+ # on_else_condition(node)
77
+ # on_for(node)
78
+ # on_form(node)
79
+ # on_include(node)
80
+ # on_integer(node)
81
+ # on_layout(node)
82
+ # on_method_literal(node)
83
+ # on_paginate(node)
84
+ # on_range(node)
85
+ # on_render(node)
86
+ # on_schema(node)
87
+ # on_section(node)
88
+ # on_style(node)
89
+ # on_unless(node)
90
+ # on_variable_lookup(node)
91
+ end
92
+ end
93
+ ```
94
+
95
+ ## Resources
96
+
97
+ - [Liquid source][liquidsource]
98
+
99
+ [liquidsource]: https://github.com/Shopify/liquid/tree/master/lib/liquid
@@ -1,4 +1,4 @@
1
- # Check Title (`CheckClassName`)
1
+ # Check Title (`<%= class_name %>`)
2
2
 
3
3
  A brief paragraph explaining why the check exists.
4
4
 
@@ -21,7 +21,7 @@ This check is aimed at eliminating ...
21
21
  The default configuration for this check is the following:
22
22
 
23
23
  ```yaml
24
- CheckClassName:
24
+ <%= class_name %>:
25
25
  enabled: true
26
26
  some_option: 10
27
27
  ```
@@ -36,12 +36,12 @@ If you don't want to ..., then it's safe to disable this rule.
36
36
 
37
37
  ## Version
38
38
 
39
- This check has been introduced in Theme Check X.X.X.
39
+ This check has been introduced in Theme Check THEME_CHECK_VERSION.
40
40
 
41
41
  ## Resources
42
42
 
43
43
  - [Rule Source][codesource]
44
44
  - [Documentation Source][docsource]
45
45
 
46
- [codesource]: /lib/theme_check/checks/check_class_name.rb
47
- [docsource]: /docs/checks/check_class_name.md
46
+ [codesource]: /<%= code_source %>
47
+ [docsource]: /<%= doc_source %>
@@ -0,0 +1,56 @@
1
+ # Ensure `asset_url` filters are used when serving assets (`AssetUrlFilters`)
2
+
3
+ See the [`RemoteAsset` check documentation][remote_asset] for a detailed explanation on why remote assets are discouraged.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating unnecessary HTTP connections.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```liquid
12
+ <!-- Using multiple CDNs -->
13
+ {{ "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" | stylesheet_tag }}
14
+
15
+ <!-- Missing img_url filter -->
16
+ {{ url | img_tag }}
17
+ ```
18
+
19
+ :+1: Examples of **correct** code for this check:
20
+
21
+ ```liquid
22
+ {{ 'bootstrap.min.css' | asset_url | stylesheet_tag }}
23
+
24
+ <!-- Images -->
25
+ {{ url | img_url | img_tag }}
26
+ ```
27
+
28
+ Use the [`assets_url`](asset_url) or [`img_url`](img_url) filter to load the files in your theme's `assets/` folder from the Shopify CDN.
29
+
30
+ ## Check Options
31
+
32
+ The default configuration for this check is the following:
33
+
34
+ ```yaml
35
+ AssetUrlFilters:
36
+ enabled: true
37
+ ```
38
+
39
+ ## When Not To Use It
40
+
41
+ When the remote content is highly dynamic.
42
+
43
+ ## Version
44
+
45
+ This check has been introduced in Theme Check 0.9.1.
46
+
47
+ ## Resources
48
+
49
+ - [Rule Source][codesource]
50
+ - [Documentation Source][docsource]
51
+
52
+ [codesource]: /lib/theme_check/checks/remote_asset_filters.rb
53
+ [docsource]: /docs/checks/remote_asset_filters.md
54
+ [remote_asset]: /docs/checks/remote_asset.md
55
+ [asset_url]: https://shopify.dev/docs/themes/liquid/reference/filters/url-filters#assert_url
56
+ [img_url]: https://shopify.dev/docs/themes/liquid/reference/filters/url-filters#img_url
@@ -0,0 +1,42 @@
1
+ # Do not depend on the content of `content_for_header` (`ContentForHeaderModification`)
2
+
3
+ Do not rely on the content of `content_for_header` as it might change in the future, which could cause your Liquid code behavior to change.
4
+
5
+ ## Check Details
6
+
7
+ :-1: Examples of **incorrect** code for this check:
8
+
9
+ ```liquid
10
+ {% assign parts = content_for_header | split: ',' %}
11
+ ```
12
+
13
+ :+1: Examples of **correct** code for this check:
14
+
15
+ The only acceptable usage of `content_for_header` is:
16
+
17
+ ```liquid
18
+ {{ content_for_header }}
19
+ ```
20
+
21
+ ## Check Options
22
+
23
+ The default configuration for this check is the following:
24
+
25
+ ```yaml
26
+ ContentForHeaderModification:
27
+ enabled: true
28
+ ```
29
+
30
+ ## Version
31
+
32
+ This check has been introduced in Theme Check 0.9.0.
33
+
34
+ ## Resources
35
+
36
+ - [Rule Source][codesource]
37
+ - [Documentation Source][docsource]
38
+ - [`theme.liquid` template considerations][considerations]
39
+
40
+ [codesource]: /lib/theme_check/checks/check_class_name.rb
41
+ [docsource]: /docs/checks/check_class_name.md
42
+ [considerations]: https://shopify.dev/docs/themes/theme-templates/theme-liquid#template-considerations
@@ -0,0 +1,61 @@
1
+ # Lazy loading image tags (`ImgLazyLoading`)
2
+
3
+ Lazy loading is a strategy to identify resources as non-blocking (non-critical) and load these only when needed. It's a way to shorten the length of the critical rendering path, which translates into reduced page load times.
4
+
5
+ Lazy loading can occur on different moments in the application, but it typically happens on some user interactions such as scrolling and navigation.
6
+
7
+ Very often, webpages contain many images that contribute to data-usage and how fast a page can load. Most of those images are off-screen (non-critical), requiring user interaction (an example being scroll) in order to view them.
8
+
9
+ _Quoted from [MDN - Lazy loading][mdn]_
10
+
11
+ ## Check Details
12
+
13
+ This check is aimed at defering loading non-critical images.
14
+
15
+ :-1: Examples of **incorrect** code for this check:
16
+
17
+ ```liquid
18
+ <img src="a.jpg">
19
+
20
+ <!-- Replaces lazysize.js -->
21
+ <img src="a.jpg" class="lazyload">
22
+
23
+ <!-- `auto` is deprecated -->
24
+ <img src="a.jpg" loading="auto">
25
+ ```
26
+
27
+ :+1: Examples of **correct** code for this check:
28
+
29
+ ```liquid
30
+ <img src="a.jpg" loading="lazy">
31
+
32
+ <!-- `eager` is also accepted, but prefer `lazy` -->
33
+ <img src="a.jpg" loading="eager">
34
+ ```
35
+
36
+ ## Check Options
37
+
38
+ The default configuration for this check is the following:
39
+
40
+ ```yaml
41
+ ImgLazyLoading:
42
+ enabled: true
43
+ ```
44
+
45
+ ## When Not To Use It
46
+
47
+ If you don't want to defer loading of images, then it's safe to disable this rule.
48
+
49
+ ## Version
50
+
51
+ This check has been introduced in Theme Check 0.10.0.
52
+
53
+ ## Resources
54
+
55
+ - [Rule Source][codesource]
56
+ - [Documentation Source][docsource]
57
+ - [MDN - Lazy loading][mdn]
58
+
59
+ [codesource]: /lib/theme_check/checks/img_lazy_loading.rb
60
+ [docsource]: /docs/checks/img_lazy_loading.md
61
+ [mdn]: https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
@@ -0,0 +1,53 @@
1
+ # Discourage use of parser-blocking `script_tag` filter (`ParserBlockingScriptTag`)
2
+
3
+ The `script_tag` filter emits a parser-blocking script tag.
4
+
5
+ See the [ParserBlockingJavaScript check documentation][parser_blocking_javascript] for why this is generally discouraged.
6
+
7
+ ## Check Details
8
+
9
+ This check is aimed at eliminating parser-blocking JavaScript on themes.
10
+
11
+ :-1: Examples of **incorrect** code for this check:
12
+
13
+ ```liquid
14
+ <!-- The script_tag filter outputs a parser-blocking script -->
15
+ {{ 'app-code.js' | asset_url | script_tag }}
16
+ ```
17
+
18
+ :+1: Examples of **correct** code for this check:
19
+
20
+ ```liquid
21
+ <!-- Good. Using the asset_url filter + defer -->
22
+ <script src="{{ 'theme.js' | asset_url }}" defer></script>
23
+
24
+ <!-- Also good. Using the asset_url filter + async -->
25
+ <script src="{{ 'theme.js' | asset_url }}" async></script>
26
+ ```
27
+
28
+ ## Check Options
29
+
30
+ The default configuration for this check is the following:
31
+
32
+ ```yaml
33
+ ParserBlockingScriptTag:
34
+ enabled: true
35
+ ```
36
+
37
+ ## When Not To Use It
38
+
39
+ This should only be turned off with the `theme-check-disable` comment when there's no better way to accomplish what you're doing than with a parser-blocking script.
40
+
41
+ It is discouraged to turn this rule off.
42
+
43
+ ## Version
44
+
45
+ This check has been introduced in Theme Check 0.9.0.
46
+
47
+ ## Resources
48
+
49
+ - [ParserBlockingJavaScript check][parser_blocking_javascript]
50
+ - [Documentation Source][docsource]
51
+
52
+ [parser_blocking_javascript]: /docs/checks/parser_blocking_javascript.md
53
+ [docsource]: /docs/checks/parser_blocking_script_tag.md
@@ -6,7 +6,6 @@ require 'theme_check'
6
6
  if ENV["THEME_CHECK_DEBUG"] == "true"
7
7
  $DEBUG = true
8
8
  end
9
- # Force encoding to UTF-8 to fix VSCode
10
- Encoding.default_external = Encoding::UTF_8
9
+
11
10
  status_code = ThemeCheck::LanguageServer.start
12
11
  exit! status_code
data/lib/theme_check.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "liquid"
3
3
 
4
+ require_relative "theme_check/version"
4
5
  require_relative "theme_check/bug"
5
6
  require_relative "theme_check/exceptions"
6
7
  require_relative "theme_check/analyzer"
@@ -35,6 +36,12 @@ require_relative "theme_check/template"
35
36
  require_relative "theme_check/theme"
36
37
  require_relative "theme_check/visitor"
37
38
  require_relative "theme_check/corrector"
38
- require_relative "theme_check/version"
39
+ require_relative "theme_check/html_node"
40
+ require_relative "theme_check/html_visitor"
41
+ require_relative "theme_check/html_check"
39
42
 
40
43
  Dir[__dir__ + "/theme_check/checks/*.rb"].each { |file| require file }
44
+
45
+ # UTF-8 is the default internal and external encoding, like in Rails & Shopify.
46
+ Encoding.default_external = Encoding::UTF_8
47
+ Encoding.default_internal = Encoding::UTF_8
@@ -7,6 +7,7 @@ module ThemeCheck
7
7
 
8
8
  @liquid_checks = Checks.new
9
9
  @json_checks = Checks.new
10
+ @html_checks = Checks.new
10
11
 
11
12
  checks.each do |check|
12
13
  check.theme = @theme
@@ -16,33 +17,58 @@ module ThemeCheck
16
17
  @liquid_checks << check
17
18
  when JsonCheck
18
19
  @json_checks << check
20
+ when HtmlCheck
21
+ @html_checks << check
19
22
  end
20
23
  end
21
-
22
- @visitor = Visitor.new(@liquid_checks)
23
24
  end
24
25
 
25
26
  def offenses
26
- @liquid_checks.flat_map(&:offenses) + @json_checks.flat_map(&:offenses)
27
+ @liquid_checks.flat_map(&:offenses) +
28
+ @json_checks.flat_map(&:offenses) +
29
+ @html_checks.flat_map(&:offenses)
27
30
  end
28
31
 
29
- def offenses_clear!
30
- @liquid_checks.each do |check|
31
- check.offenses.clear
32
- end
32
+ def analyze_theme
33
+ reset
33
34
 
34
- @json_checks.each do |check|
35
- check.offenses.clear
35
+ liquid_visitor = Visitor.new(@liquid_checks, @disabled_checks)
36
+ html_visitor = HtmlVisitor.new(@html_checks)
37
+ @theme.liquid.each do |template|
38
+ liquid_visitor.visit_template(template)
39
+ html_visitor.visit_template(template)
36
40
  end
37
- end
38
41
 
39
- def analyze_theme
40
- offenses_clear!
41
- @theme.liquid.each { |template| @visitor.visit_template(template) }
42
42
  @theme.json.each { |json_file| @json_checks.call(:on_file, json_file) }
43
- @liquid_checks.call(:on_end)
44
- @json_checks.call(:on_end)
45
- offenses
43
+
44
+ finish
45
+ end
46
+
47
+ def analyze_files(files)
48
+ reset
49
+
50
+ # Call all checks that run on the whole theme
51
+ liquid_visitor = Visitor.new(@liquid_checks.whole_theme, @disabled_checks)
52
+ html_visitor = HtmlVisitor.new(@html_checks.whole_theme)
53
+ @theme.liquid.each do |template|
54
+ liquid_visitor.visit_template(template)
55
+ html_visitor.visit_template(template)
56
+ end
57
+ @theme.json.each { |json_file| @json_checks.whole_theme.call(:on_file, json_file) }
58
+
59
+ # Call checks that run on a single files, only on specified file
60
+ liquid_visitor = Visitor.new(@liquid_checks.single_file, @disabled_checks)
61
+ html_visitor = HtmlVisitor.new(@html_checks.single_file)
62
+ files.each do |file|
63
+ if file.liquid?
64
+ liquid_visitor.visit_template(file)
65
+ html_visitor.visit_template(file)
66
+ elsif file.json?
67
+ @json_checks.single_file.call(:on_file, file)
68
+ end
69
+ end
70
+
71
+ finish
46
72
  end
47
73
 
48
74
  def uncorrectable_offenses
@@ -59,5 +85,35 @@ module ThemeCheck
59
85
  @theme.liquid.each(&:write)
60
86
  end
61
87
  end
88
+
89
+ private
90
+
91
+ def reset
92
+ @disabled_checks = DisabledChecks.new
93
+
94
+ @liquid_checks.each do |check|
95
+ check.offenses.clear
96
+ end
97
+
98
+ @html_checks.each do |check|
99
+ check.offenses.clear
100
+ end
101
+
102
+ @json_checks.each do |check|
103
+ check.offenses.clear
104
+ end
105
+ end
106
+
107
+ def finish
108
+ @liquid_checks.call(:on_end)
109
+ @html_checks.call(:on_end)
110
+ @json_checks.call(:on_end)
111
+
112
+ @disabled_checks.remove_disabled_offenses(@liquid_checks)
113
+ @disabled_checks.remove_disabled_offenses(@json_checks)
114
+ @disabled_checks.remove_disabled_offenses(@html_checks)
115
+
116
+ offenses
117
+ end
62
118
  end
63
119
  end