theme-check 0.9.0 → 1.0.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/CONTRIBUTING.md +20 -91
  4. data/Rakefile +31 -0
  5. data/config/default.yml +31 -3
  6. data/data/shopify_liquid/objects.yml +2 -0
  7. data/docs/api/check.md +15 -0
  8. data/docs/api/html_check.md +46 -0
  9. data/docs/api/json_check.md +19 -0
  10. data/docs/api/liquid_check.md +99 -0
  11. data/docs/checks/{CHECK_DOCS_TEMPLATE.md → TEMPLATE.md.erb} +5 -5
  12. data/docs/checks/asset_size_css_stylesheet_tag.md +50 -0
  13. data/docs/checks/asset_url_filters.md +56 -0
  14. data/docs/checks/deprecate_bgsizes.md +66 -0
  15. data/docs/checks/deprecate_lazysizes.md +61 -0
  16. data/docs/checks/html_parsing_error.md +50 -0
  17. data/docs/checks/img_lazy_loading.md +61 -0
  18. data/docs/checks/liquid_tag.md +2 -2
  19. data/docs/checks/template_length.md +12 -2
  20. data/lib/theme_check/check.rb +1 -1
  21. data/lib/theme_check/checks/TEMPLATE.rb.erb +11 -0
  22. data/lib/theme_check/checks/asset_size_css.rb +11 -74
  23. data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +24 -0
  24. data/lib/theme_check/checks/asset_size_javascript.rb +10 -36
  25. data/lib/theme_check/checks/asset_url_filters.rb +46 -0
  26. data/lib/theme_check/checks/deprecate_bgsizes.rb +14 -0
  27. data/lib/theme_check/checks/deprecate_lazysizes.rb +16 -0
  28. data/lib/theme_check/checks/html_parsing_error.rb +12 -0
  29. data/lib/theme_check/checks/img_lazy_loading.rb +20 -0
  30. data/lib/theme_check/checks/liquid_tag.rb +2 -2
  31. data/lib/theme_check/checks/remote_asset.rb +23 -79
  32. data/lib/theme_check/checks/template_length.rb +18 -4
  33. data/lib/theme_check/html_check.rb +2 -0
  34. data/lib/theme_check/html_node.rb +4 -0
  35. data/lib/theme_check/html_visitor.rb +5 -1
  36. data/lib/theme_check/json_file.rb +5 -0
  37. data/lib/theme_check/language_server/diagnostics_tracker.rb +2 -0
  38. data/lib/theme_check/liquid_check.rb +0 -11
  39. data/lib/theme_check/offense.rb +5 -5
  40. data/lib/theme_check/parsing_helpers.rb +3 -1
  41. data/lib/theme_check/regex_helpers.rb +17 -0
  42. data/lib/theme_check/tags.rb +37 -0
  43. data/lib/theme_check/template.rb +1 -0
  44. data/lib/theme_check/version.rb +1 -1
  45. metadata +21 -4
@@ -5,9 +5,11 @@ module ThemeCheck
5
5
  category :liquid
6
6
  doc docs_url(__FILE__)
7
7
 
8
- def initialize(max_length: 200, exclude_schema: true)
8
+ def initialize(max_length: 500, exclude_schema: true, exclude_stylesheet: true, exclude_javascript: true)
9
9
  @max_length = max_length
10
10
  @exclude_schema = exclude_schema
11
+ @exclude_stylesheet = exclude_stylesheet
12
+ @exclude_javascript = exclude_javascript
11
13
  end
12
14
 
13
15
  def on_document(_node)
@@ -15,9 +17,15 @@ module ThemeCheck
15
17
  end
16
18
 
17
19
  def on_schema(node)
18
- if @exclude_schema
19
- @excluded_lines += node.value.nodelist.join.count("\n")
20
- end
20
+ exclude_node_lines(node) if @exclude_schema
21
+ end
22
+
23
+ def on_stylesheet(node)
24
+ exclude_node_lines(node) if @exclude_stylesheet
25
+ end
26
+
27
+ def on_javascript(node)
28
+ exclude_node_lines(node) if @exclude_javascript
21
29
  end
22
30
 
23
31
  def after_document(node)
@@ -26,5 +34,11 @@ module ThemeCheck
26
34
  add_offense("Template has too many lines [#{lines}/#{@max_length}]", template: node.template)
27
35
  end
28
36
  end
37
+
38
+ private
39
+
40
+ def exclude_node_lines(node)
41
+ @excluded_lines += node.value.nodelist.join.count("\n")
42
+ end
29
43
  end
30
44
  end
@@ -3,5 +3,7 @@
3
3
  module ThemeCheck
4
4
  class HtmlCheck < Check
5
5
  extend ChecksTracking
6
+ VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
7
+ START_OR_END_QUOTE = /(^['"])|(['"]$)/
6
8
  end
7
9
  end
@@ -17,6 +17,10 @@ module ThemeCheck
17
17
  @value.name == "text"
18
18
  end
19
19
 
20
+ def element?
21
+ @value.element?
22
+ end
23
+
20
24
  def children
21
25
  @value.children.map { |child| HtmlNode.new(child, template) }
22
26
  end
@@ -13,19 +13,23 @@ module ThemeCheck
13
13
  def visit_template(template)
14
14
  doc = parse(template)
15
15
  visit(HtmlNode.new(doc, template))
16
+ rescue ArgumentError => e
17
+ call_checks(:on_parse_error, e, template)
16
18
  end
17
19
 
18
20
  private
19
21
 
20
22
  def parse(template)
21
- Nokogiri::HTML5.fragment(template.source)
23
+ Nokogiri::HTML5.fragment(template.source, max_tree_depth: 400, max_attributes: 400)
22
24
  end
23
25
 
24
26
  def visit(node)
27
+ call_checks(:on_element, node) if node.element?
25
28
  call_checks(:"on_#{node.name}", node)
26
29
  node.children.each { |child| visit(child) }
27
30
  unless node.literal?
28
31
  call_checks(:"after_#{node.name}", node)
32
+ call_checks(:after_element, node) if node.element?
29
33
  end
30
34
  end
31
35
 
@@ -46,6 +46,11 @@ module ThemeCheck
46
46
  false
47
47
  end
48
48
 
49
+ def ==(other)
50
+ other.is_a?(JsonFile) && relative_path == other.relative_path
51
+ end
52
+ alias_method :eql?, :==
53
+
49
54
  private
50
55
 
51
56
  def load!
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "logger"
2
3
 
3
4
  module ThemeCheck
4
5
  module LanguageServer
@@ -16,6 +17,7 @@ module ThemeCheck
16
17
  def build_diagnostics(offenses, analyzed_files: nil)
17
18
  reported_files = Set.new
18
19
  new_single_file_offenses = {}
20
+ analyzed_files = analyzed_files.map { |path| Pathname.new(path) } if analyzed_files
19
21
 
20
22
  offenses.group_by(&:template).each do |template, template_offenses|
21
23
  next unless template
@@ -5,16 +5,5 @@ module ThemeCheck
5
5
  class LiquidCheck < Check
6
6
  extend ChecksTracking
7
7
  include ParsingHelpers
8
-
9
- TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
10
- VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
11
- START_OR_END_QUOTE = /(^['"])|(['"]$)/
12
- QUOTED_LIQUID_ATTRIBUTE = %r{
13
- '(?:#{TAG}|#{VARIABLE}|[^'])*'| # any combination of tag/variable or non straight quote inside straight quotes
14
- "(?:#{TAG}|#{VARIABLE}|[^"])*" # any combination of tag/variable or non double quotes inside double quotes
15
- }omix
16
- ATTR = /[a-z0-9-]+/i
17
- HTML_ATTRIBUTE = /#{ATTR}(?:=#{QUOTED_LIQUID_ATTRIBUTE})?/omix
18
- HTML_ATTRIBUTES = /(?:#{HTML_ATTRIBUTE}|\s)*/omix
19
8
  end
20
9
  end
@@ -124,13 +124,13 @@ module ThemeCheck
124
124
 
125
125
  def ==(other)
126
126
  other.is_a?(Offense) &&
127
- check == other.check &&
127
+ code_name == other.code_name &&
128
128
  message == other.message &&
129
- template == other.template &&
130
- node == other.node &&
131
- markup == other.markup &&
132
- line_number == other.line_number
129
+ location == other.location &&
130
+ start_index == other.start_index &&
131
+ end_index == other.end_index
133
132
  end
133
+ alias_method :eql?, :==
134
134
 
135
135
  def to_s
136
136
  if template
@@ -7,8 +7,10 @@ module ThemeCheck
7
7
 
8
8
  while scanner.scan(/.*?("|')/)
9
9
  yield scanner.matched[0..-2]
10
+ quote = scanner.matched[-1] == "'" ? "'" : "\""
10
11
  # Skip to the end of the string
11
- scanner.skip_until(scanner.matched[-1] == "'" ? /[^\\]'/ : /[^\\]"/)
12
+ # Check for empty string first, since follow regexp uses lookahead
13
+ scanner.skip(/#{quote}/) || scanner.skip_until(/[^\\]#{quote}/)
12
14
  end
13
15
 
14
16
  yield scanner.rest if scanner.rest?
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ThemeCheck
4
4
  module RegexHelpers
5
+ VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
6
+ START_OR_END_QUOTE = /(^['"])|(['"]$)/
5
7
  def matches(s, re)
6
8
  start_at = 0
7
9
  matches = []
@@ -11,5 +13,20 @@ module ThemeCheck
11
13
  end
12
14
  matches
13
15
  end
16
+
17
+ def href_to_file_size(href)
18
+ # asset_url (+ optional stylesheet_tag) variables
19
+ if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
20
+ asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
21
+ asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
22
+ return if asset.nil?
23
+ asset.gzipped_size
24
+
25
+ # remote URLs
26
+ elsif href =~ %r{^(https?:)?//}
27
+ asset = RemoteAssetFile.from_src(href)
28
+ asset.gzipped_size
29
+ end
30
+ end
14
31
  end
15
32
  end
@@ -125,6 +125,42 @@ module ThemeCheck
125
125
  end
126
126
  end
127
127
 
128
+ class Render < Liquid::Tag
129
+ SYNTAX = /((?:#{Liquid::QuotedString}|#{Liquid::VariableSegment})+)(\s+(with|#{Liquid::Render::FOR})\s+(#{Liquid::QuotedFragment}+))?(\s+(?:as)\s+(#{Liquid::VariableSegment}+))?/o
130
+
131
+ disable_tags "include"
132
+
133
+ attr_reader :template_name_expr, :attributes
134
+
135
+ def initialize(tag_name, markup, options)
136
+ super
137
+
138
+ raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
139
+
140
+ template_name = Regexp.last_match(1)
141
+ with_or_for = Regexp.last_match(3)
142
+ variable_name = Regexp.last_match(4)
143
+
144
+ @alias_name = Regexp.last_match(6)
145
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
146
+ @template_name_expr = parse_expression(template_name)
147
+ @for = (with_or_for == Liquid::Render::FOR)
148
+
149
+ @attributes = {}
150
+ markup.scan(Liquid::TagAttributes) do |key, value|
151
+ @attributes[key] = parse_expression(value)
152
+ end
153
+ end
154
+
155
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
156
+ def children
157
+ [
158
+ @node.template_name_expr,
159
+ ] + @node.attributes.values
160
+ end
161
+ end
162
+ end
163
+
128
164
  class Style < Liquid::Block; end
129
165
 
130
166
  class Schema < Liquid::Raw; end
@@ -135,6 +171,7 @@ module ThemeCheck
135
171
 
136
172
  Liquid::Template.register_tag('form', Form)
137
173
  Liquid::Template.register_tag('layout', Layout)
174
+ Liquid::Template.register_tag('render', Render)
138
175
  Liquid::Template.register_tag('paginate', Paginate)
139
176
  Liquid::Template.register_tag('section', Section)
140
177
  Liquid::Template.register_tag('style', Style)
@@ -91,6 +91,7 @@ module ThemeCheck
91
91
  def ==(other)
92
92
  other.is_a?(Template) && relative_path == other.relative_path
93
93
  end
94
+ alias_method :eql?, :==
94
95
 
95
96
  def self.parse(source)
96
97
  Liquid::Template.parse(
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.9.0"
3
+ VERSION = "1.0.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.9.0
4
+ version: 1.0.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-05-28 00:00:00.000000000 Z
11
+ date: 2021-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -69,13 +69,23 @@ files:
69
69
  - data/shopify_liquid/tags.yml
70
70
  - data/shopify_translation_keys.yml
71
71
  - dev.yml
72
- - docs/checks/CHECK_DOCS_TEMPLATE.md
72
+ - docs/api/check.md
73
+ - docs/api/html_check.md
74
+ - docs/api/json_check.md
75
+ - docs/api/liquid_check.md
76
+ - docs/checks/TEMPLATE.md.erb
73
77
  - docs/checks/asset_size_css.md
78
+ - docs/checks/asset_size_css_stylesheet_tag.md
74
79
  - docs/checks/asset_size_javascript.md
80
+ - docs/checks/asset_url_filters.md
75
81
  - docs/checks/content_for_header_modification.md
76
82
  - docs/checks/convert_include_to_render.md
77
83
  - docs/checks/default_locale.md
84
+ - docs/checks/deprecate_bgsizes.md
85
+ - docs/checks/deprecate_lazysizes.md
78
86
  - docs/checks/deprecated_filter.md
87
+ - docs/checks/html_parsing_error.md
88
+ - docs/checks/img_lazy_loading.md
79
89
  - docs/checks/img_width_and_height.md
80
90
  - docs/checks/liquid_tag.md
81
91
  - docs/checks/matching_schema_translations.md
@@ -109,12 +119,19 @@ files:
109
119
  - lib/theme_check/bug.rb
110
120
  - lib/theme_check/check.rb
111
121
  - lib/theme_check/checks.rb
122
+ - lib/theme_check/checks/TEMPLATE.rb.erb
112
123
  - lib/theme_check/checks/asset_size_css.rb
124
+ - lib/theme_check/checks/asset_size_css_stylesheet_tag.rb
113
125
  - lib/theme_check/checks/asset_size_javascript.rb
126
+ - lib/theme_check/checks/asset_url_filters.rb
114
127
  - lib/theme_check/checks/content_for_header_modification.rb
115
128
  - lib/theme_check/checks/convert_include_to_render.rb
116
129
  - lib/theme_check/checks/default_locale.rb
130
+ - lib/theme_check/checks/deprecate_bgsizes.rb
131
+ - lib/theme_check/checks/deprecate_lazysizes.rb
117
132
  - lib/theme_check/checks/deprecated_filter.rb
133
+ - lib/theme_check/checks/html_parsing_error.rb
134
+ - lib/theme_check/checks/img_lazy_loading.rb
118
135
  - lib/theme_check/checks/img_width_and_height.rb
119
136
  - lib/theme_check/checks/liquid_tag.rb
120
137
  - lib/theme_check/checks/matching_schema_translations.rb
@@ -216,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
233
  - !ruby/object:Gem::Version
217
234
  version: '0'
218
235
  requirements: []
219
- rubygems_version: 3.2.17
236
+ rubygems_version: 3.2.20
220
237
  signing_key:
221
238
  specification_version: 4
222
239
  summary: A Shopify Theme Linter