theme-check 1.0.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +2 -6
  3. data/CHANGELOG.md +50 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +39 -0
  6. data/RELEASING.md +34 -2
  7. data/bin/theme-check +29 -0
  8. data/bin/theme-check-language-server +29 -0
  9. data/config/default.yml +28 -1
  10. data/config/nothing.yml +11 -0
  11. data/config/theme_app_extension.yml +168 -0
  12. data/data/shopify_liquid/objects.yml +1 -0
  13. data/docs/checks/app_block_valid_tags.md +40 -0
  14. data/docs/checks/asset_size_app_block_css.md +52 -0
  15. data/docs/checks/asset_size_app_block_javascript.md +57 -0
  16. data/docs/checks/deprecate_lazysizes.md +0 -3
  17. data/docs/checks/missing_template.md +25 -0
  18. data/docs/checks/pagination_size.md +44 -0
  19. data/docs/checks/template_length.md +1 -1
  20. data/docs/checks/undefined_object.md +5 -0
  21. data/lib/theme_check/analyzer.rb +26 -21
  22. data/lib/theme_check/asset_file.rb +3 -15
  23. data/lib/theme_check/bug.rb +3 -1
  24. data/lib/theme_check/check.rb +26 -4
  25. data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
  26. data/lib/theme_check/checks/asset_size_app_block_css.rb +44 -0
  27. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +44 -0
  28. data/lib/theme_check/checks/asset_size_css.rb +3 -3
  29. data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
  30. data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
  31. data/lib/theme_check/checks/default_locale.rb +3 -1
  32. data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
  33. data/lib/theme_check/checks/deprecate_lazysizes.rb +7 -4
  34. data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
  35. data/lib/theme_check/checks/img_width_and_height.rb +3 -3
  36. data/lib/theme_check/checks/missing_template.rb +21 -5
  37. data/lib/theme_check/checks/pagination_size.rb +65 -0
  38. data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
  39. data/lib/theme_check/checks/remote_asset.rb +3 -3
  40. data/lib/theme_check/checks/space_inside_braces.rb +27 -7
  41. data/lib/theme_check/checks/template_length.rb +1 -1
  42. data/lib/theme_check/checks/undefined_object.rb +1 -1
  43. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  44. data/lib/theme_check/checks.rb +11 -1
  45. data/lib/theme_check/cli.rb +52 -15
  46. data/lib/theme_check/config.rb +56 -10
  47. data/lib/theme_check/corrector.rb +9 -0
  48. data/lib/theme_check/exceptions.rb +29 -27
  49. data/lib/theme_check/file_system_storage.rb +12 -0
  50. data/lib/theme_check/html_check.rb +0 -1
  51. data/lib/theme_check/html_node.rb +37 -16
  52. data/lib/theme_check/html_visitor.rb +17 -3
  53. data/lib/theme_check/json_check.rb +2 -2
  54. data/lib/theme_check/json_file.rb +11 -27
  55. data/lib/theme_check/json_printer.rb +26 -0
  56. data/lib/theme_check/language_server/constants.rb +21 -6
  57. data/lib/theme_check/language_server/document_link_engine.rb +3 -31
  58. data/lib/theme_check/language_server/document_link_provider.rb +70 -0
  59. data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
  60. data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
  61. data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
  62. data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
  63. data/lib/theme_check/language_server/handler.rb +7 -4
  64. data/lib/theme_check/language_server/server.rb +13 -2
  65. data/lib/theme_check/language_server.rb +5 -0
  66. data/lib/theme_check/node.rb +6 -4
  67. data/lib/theme_check/offense.rb +56 -3
  68. data/lib/theme_check/parsing_helpers.rb +4 -3
  69. data/lib/theme_check/position.rb +98 -14
  70. data/lib/theme_check/regex_helpers.rb +5 -2
  71. data/lib/theme_check/tags.rb +26 -9
  72. data/lib/theme_check/template.rb +3 -32
  73. data/lib/theme_check/theme.rb +3 -0
  74. data/lib/theme_check/theme_file.rb +40 -0
  75. data/lib/theme_check/version.rb +1 -1
  76. data/lib/theme_check.rb +16 -0
  77. data/theme-check.gemspec +1 -1
  78. metadata +24 -6
  79. data/bin/liquid-server +0 -4
@@ -54,6 +54,7 @@
54
54
  - part
55
55
  - policy
56
56
  - powered_by_link
57
+ - predictive_search
57
58
  - product
58
59
  - product_option
59
60
  - product_variant
@@ -0,0 +1,40 @@
1
+ # Reject Forbidden Tags from Theme App Extension Blocks (`AppBlockValidTags`)
2
+
3
+ This rule exists to prevent theme app extension blocks from containing forbidden tags in their liquid code.
4
+
5
+ ## Check Details
6
+
7
+ This rule verifies none of the below tags are used in theme app extension blocks.
8
+
9
+ - `{% javascript %}`
10
+ - `{% stylesheet %}`
11
+ - `{% include 'foo' %}`
12
+ - `{% layout 'foo' %}`
13
+ - `{% section 'foo' %}`
14
+
15
+ :-1: **Incorrect** code for this check occurs with the use of any of the above tags in the liquid code of theme app extension blocks.
16
+
17
+ ## Check Options
18
+
19
+ The default configuration for theme app extensions is the following:
20
+
21
+ ```yaml
22
+ AppBlockValidTags:
23
+ enabled: true
24
+ ```
25
+
26
+ ## When Not To Use It
27
+
28
+ This rule should not be disabled locally.
29
+
30
+ ## Version
31
+
32
+ This check has been introduced in 1.3.0
33
+
34
+ ## Resources
35
+
36
+ - [Rule Source][codesource]
37
+ - [Documentation Source][docsource]
38
+
39
+ [codesource]: /lib/theme_check/checks/app_block_valid_tags.rb
40
+ [docsource]: /docs/checks/app_block_valid_tags.md
@@ -0,0 +1,52 @@
1
+ # Prevent Large CSS bundles (`AssetSizeAppBlockCSS`)
2
+
3
+ This rule exists to prevent large CSS bundles from being included via Theme App Extensions (for speed).
4
+
5
+ ## Check Details
6
+
7
+ This rule disallows the use of too much CSS in themes, as configured by `threshold_in_bytes`.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+ ```liquid
11
+ <!-- Here, assets/app.css is **greater** than `threshold_in_bytes` compressed. -->
12
+ {% schema %}
13
+ {
14
+ ...
15
+ "stylesheet": "app.css"
16
+ }
17
+ {% endschema %}
18
+ ```
19
+
20
+ ## Check Options
21
+
22
+ The default configuration is the following:
23
+
24
+ ```yaml
25
+ AssetSizeAppBlockCSS:
26
+ enabled: true
27
+ threshold_in_bytes: 100_000
28
+ ```
29
+
30
+ ### `threshold_in_bytes`
31
+
32
+ The `threshold_in_bytes` option (default: `100_000`) determines the maximum allowed compressed size in bytes that a single CSS file can take.
33
+
34
+ This includes theme and remote stylesheets.
35
+
36
+ ## When Not To Use It
37
+
38
+ This rule should not be disabled locally since the check will be enforced when
39
+ promoting new versions of the extension.
40
+
41
+ ## Version
42
+
43
+ This check has been introduced in 1.1.0
44
+
45
+ ## Resources
46
+
47
+ - [The Performance Inequality Gap](https://infrequently.org/2021/03/the-performance-inequality-gap/)
48
+ - [Rule Source][codesource]
49
+ - [Documentation Source][docsource]
50
+
51
+ [codesource]: /lib/theme_check/checks/asset_size_app_block_css.rb
52
+ [docsource]: /docs/checks/asset_size_app_block_css.md
@@ -0,0 +1,57 @@
1
+ # Prevent Abuse on Server Rendered App Blocks (`AssetSizeAppBlockJavaScript`)
2
+
3
+ For server rendered app blocks, it is an anti-pattern to execute large JavaScript bundles on every page load
4
+
5
+ This doesn't mean they don't have a reason to exist. For instance, chat widgets are mini applications embedded inside web pages. Designing such an app with server rendered updates would be absurd. However, if only 10% of the users interact with the chat widget, the other 90% should not have to execute the entire bundle on every page load.
6
+
7
+ The natural solution to this problem is to implement the chat widget using the [Import on Interaction Pattern][ioip].
8
+
9
+ ## Check Details
10
+
11
+ This rule disallows the use of block JavaScript files and external scripts to have a compressed size greater than a configured `threshold_in_bytes`.
12
+
13
+ :-1: Examples of **incorrect** code for this check:
14
+ ```liquid
15
+ <!-- Here assets/chat-widget.js is more than 10KB gzipped. -->
16
+ {% schema %}
17
+ {
18
+ ...
19
+ "javascript": "chat-widget.js"
20
+ }
21
+ {% endschema %}
22
+ ```
23
+
24
+ ## Check Options
25
+
26
+ The default configuration is the following:
27
+
28
+ ```yaml
29
+ AssetSizeAppBlockJavaScript:
30
+ enabled: true
31
+ threshold_in_bytes: 10000
32
+ ```
33
+
34
+ ### `threshold_in_bytes`
35
+
36
+ The `threshold_in_bytes` option (default: `10000`) determines the maximum allowed compressed size in bytes that a single JavaScript file can take.
37
+
38
+ This includes theme and remote scripts.
39
+
40
+ ## When Not To Use It
41
+
42
+ This rule should not be disabled locally since the check will be enforced when
43
+ promoting new versions of the extension.
44
+
45
+ ## Version
46
+
47
+ This check has been introduced in 1.1.0
48
+
49
+ ## Resources
50
+
51
+ - [The Import On Interaction Pattern][ioip]
52
+ - [Rule Source][codesource]
53
+ - [Documentation Source][docsource]
54
+
55
+ [ioip]: https://addyosmani.com/blog/import-on-interaction/
56
+ [codesource]: /lib/theme_check/checks/asset_size_app_block_javascript.rb
57
+ [docsource]: /docs/checks/asset_size_app_block_javascript.md
@@ -10,9 +10,6 @@ This check is aimed at discouraging the use of the lazysizes JavaScript library
10
10
 
11
11
  ```liquid
12
12
 
13
- <!-- Reports use of "lazyload" class -->
14
- <img src="a.jpg" class="lazyload">
15
-
16
13
  <!-- Reports use of "data-srcset" and "data-sizes" attribute. Reports data-sizes="auto" -->
17
14
  <img
18
15
  alt="House by the lake"
@@ -25,8 +25,33 @@ The default configuration for this check is the following:
25
25
  ```yaml
26
26
  MissingTemplate:
27
27
  enabled: true
28
+ ignore_missing: []
28
29
  ```
29
30
 
31
+ ### `ignore_missing`
32
+
33
+ Specify a list of patterns of missing template files to ignore.
34
+
35
+ While the `ignore` option will ignore all occurrences of `MissingTemplate` according to the file in which they appear, `ignore_missing` allows ignoring all occurrences of `MissingTemplate` based on the target template, the template being rendered.
36
+
37
+ For example:
38
+
39
+ ```yaml
40
+ MissingTemplate:
41
+ ignore_missing:
42
+ - snippets/icon-*
43
+ ```
44
+
45
+ Would ignore offenses on `{% render 'icon-missing' %}` across all theme files.
46
+
47
+ ```yaml
48
+ MissingTemplate:
49
+ ignore:
50
+ - templates/index.liquid
51
+ ```
52
+
53
+ Would ignore all `MissingTemplate` in `templates/index.liquid`, no mater the file being rendered.
54
+
30
55
  ## Version
31
56
 
32
57
  This check has been introduced in Theme Check 0.1.0.
@@ -0,0 +1,44 @@
1
+ # Ensure `paginate` tags are used with performant sizes
2
+
3
+ ## Check Details
4
+
5
+ This check is aimed at keeping response times low.
6
+
7
+ :-1: Examples of **incorrect** code for this check:
8
+
9
+ ```liquid
10
+ <!-- Using too large of page size -->
11
+ {% paginate collection.products by 999 %}
12
+ ```
13
+
14
+ :+1: Examples of **correct** code for this check:
15
+
16
+ ```liquid
17
+ {% paginate collection.products by 12 %}
18
+ ```
19
+
20
+ Use sizes that are integers below the `max_size`, and above the `min_size`.
21
+
22
+ ## Check Options
23
+
24
+ The default configuration for this check is the following:
25
+
26
+ ```yaml
27
+ PaginationSize:
28
+ enabled: true
29
+ ignore: []
30
+ min_size: 1
31
+ max_size: 50
32
+ ```
33
+
34
+ ## When Not To Use It
35
+
36
+ N/A
37
+
38
+ ## Version
39
+
40
+ This check has been introduced in Theme Check 1.3.0.
41
+
42
+ ## Resources
43
+
44
+ [paginate]: https://shopify.dev/api/liquid/objects/paginate
@@ -21,7 +21,7 @@ The default configuration for this check is the following:
21
21
  ```yaml
22
22
  TemplateLength:
23
23
  enabled: true
24
- max_length: 500
24
+ max_length: 600
25
25
  exclude_schema: true
26
26
  exclude_stylesheet: true
27
27
  exclude_javascript: true
@@ -33,8 +33,13 @@ The default configuration for this check is the following:
33
33
  ```yaml
34
34
  UndefinedObject:
35
35
  enabled: true
36
+ exclude_snippets: true
36
37
  ```
37
38
 
39
+ ### `exclude_snippets`
40
+
41
+ The `exclude_snippets` (Default: `true`) option determines whether to check for undefined objects in snippets file (as objects _may_ be defined as arguments)
42
+
38
43
  ## When Not To Use It
39
44
 
40
45
  It is discouraged to disable this rule.
@@ -34,9 +34,11 @@ module ThemeCheck
34
34
 
35
35
  liquid_visitor = Visitor.new(@liquid_checks, @disabled_checks)
36
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)
37
+ ThemeCheck.with_liquid_c_disabled do
38
+ @theme.liquid.each do |template|
39
+ liquid_visitor.visit_template(template)
40
+ html_visitor.visit_template(template)
41
+ end
40
42
  end
41
43
 
42
44
  @theme.json.each { |json_file| @json_checks.call(:on_file, json_file) }
@@ -47,24 +49,26 @@ module ThemeCheck
47
49
  def analyze_files(files)
48
50
  reset
49
51
 
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)
52
+ ThemeCheck.with_liquid_c_disabled do
53
+ # Call all checks that run on the whole theme
54
+ liquid_visitor = Visitor.new(@liquid_checks.whole_theme, @disabled_checks)
55
+ html_visitor = HtmlVisitor.new(@html_checks.whole_theme)
56
+ @theme.liquid.each do |template|
57
+ liquid_visitor.visit_template(template)
58
+ html_visitor.visit_template(template)
59
+ end
60
+ @theme.json.each { |json_file| @json_checks.whole_theme.call(:on_file, json_file) }
61
+
62
+ # Call checks that run on a single files, only on specified file
63
+ liquid_visitor = Visitor.new(@liquid_checks.single_file, @disabled_checks)
64
+ html_visitor = HtmlVisitor.new(@html_checks.single_file)
65
+ files.each do |file|
66
+ if file.liquid?
67
+ liquid_visitor.visit_template(file)
68
+ html_visitor.visit_template(file)
69
+ elsif file.json?
70
+ @json_checks.single_file.call(:on_file, file)
71
+ end
68
72
  end
69
73
  end
70
74
 
@@ -83,6 +87,7 @@ module ThemeCheck
83
87
  if @auto_correct
84
88
  offenses.each(&:correct)
85
89
  @theme.liquid.each(&:write)
90
+ @theme.json.each(&:write)
86
91
  end
87
92
  end
88
93
 
@@ -1,27 +1,15 @@
1
1
  # frozen_string_literal: true
2
- require "pathname"
3
2
  require "zlib"
4
3
 
5
4
  module ThemeCheck
6
- class AssetFile
5
+ class AssetFile < ThemeFile
7
6
  def initialize(relative_path, storage)
8
- @relative_path = relative_path
9
- @storage = storage
7
+ super
10
8
  @loaded = false
11
9
  @content = nil
12
10
  end
13
11
 
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
12
+ alias_method :content, :source
25
13
 
26
14
  def gzipped_size
27
15
  @gzipped_size ||= Zlib.gzip(content).bytesize
@@ -2,6 +2,8 @@
2
2
  require 'theme_check/version'
3
3
 
4
4
  module ThemeCheck
5
+ class ThemeCheckError < StandardError; end
6
+
5
7
  BUG_POSTAMBLE = <<~EOS
6
8
  Theme Check Version: #{VERSION}
7
9
  Ruby Version: #{RUBY_VERSION}
@@ -15,6 +17,6 @@ module ThemeCheck
15
17
  EOS
16
18
 
17
19
  def self.bug(message)
18
- abort(message + BUG_POSTAMBLE)
20
+ raise ThemeCheckError, message + BUG_POSTAMBLE
19
21
  end
20
22
  end
@@ -9,12 +9,19 @@ module ThemeCheck
9
9
  attr_accessor :options, :ignored_patterns
10
10
  attr_writer :offenses
11
11
 
12
+ # The order matters.
12
13
  SEVERITIES = [
13
14
  :error,
14
15
  :suggestion,
15
16
  :style,
16
17
  ]
17
18
 
19
+ # [severity: sym] => number
20
+ SEVERITY_VALUES = SEVERITIES
21
+ .map
22
+ .with_index { |sev, i| [sev, i] }
23
+ .to_h
24
+
18
25
  CATEGORIES = [
19
26
  :liquid,
20
27
  :translation,
@@ -38,6 +45,10 @@ module ThemeCheck
38
45
  @severity if defined?(@severity)
39
46
  end
40
47
 
48
+ def severity_value(severity)
49
+ SEVERITY_VALUES[severity]
50
+ end
51
+
41
52
  def categories(*categories)
42
53
  @categories ||= []
43
54
  if categories.any?
@@ -58,7 +69,7 @@ module ThemeCheck
58
69
  end
59
70
 
60
71
  def docs_url(path)
61
- "https://github.com/Shopify/theme-check/blob/master/docs/checks/#{File.basename(path, '.rb')}.md"
72
+ "https://github.com/Shopify/theme-check/blob/main/docs/checks/#{File.basename(path, '.rb')}.md"
62
73
  end
63
74
 
64
75
  def can_disable(disableable = nil)
@@ -80,12 +91,23 @@ module ThemeCheck
80
91
  @offenses ||= []
81
92
  end
82
93
 
83
- def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
84
- offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
94
+ def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, node_markup_offset: 0, &block)
95
+ offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, node_markup_offset: node_markup_offset, correction: block)
85
96
  end
86
97
 
87
98
  def severity
88
- self.class.severity
99
+ @severity ||= self.class.severity
100
+ end
101
+
102
+ def severity=(severity)
103
+ unless SEVERITIES.include?(severity)
104
+ raise ArgumentError, "unknown severity. Use: #{SEVERITIES.join(', ')}"
105
+ end
106
+ @severity = severity
107
+ end
108
+
109
+ def severity_value
110
+ SEVERITY_VALUES[severity]
89
111
  end
90
112
 
91
113
  def categories