theme-check 0.10.1 → 1.2.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +2 -6
  3. data/CHANGELOG.md +41 -0
  4. data/README.md +39 -0
  5. data/RELEASING.md +34 -2
  6. data/Rakefile +1 -1
  7. data/config/default.yml +39 -3
  8. data/config/nothing.yml +11 -0
  9. data/config/theme_app_extension.yml +153 -0
  10. data/data/shopify_liquid/objects.yml +2 -0
  11. data/docs/checks/asset_size_app_block_css.md +52 -0
  12. data/docs/checks/asset_size_app_block_javascript.md +57 -0
  13. data/docs/checks/asset_size_css_stylesheet_tag.md +50 -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/liquid_tag.md +2 -2
  18. data/docs/checks/template_length.md +12 -2
  19. data/exe/theme-check-language-server.bat +3 -0
  20. data/exe/theme-check.bat +3 -0
  21. data/lib/theme_check.rb +15 -0
  22. data/lib/theme_check/analyzer.rb +25 -21
  23. data/lib/theme_check/asset_file.rb +3 -15
  24. data/lib/theme_check/bug.rb +3 -1
  25. data/lib/theme_check/check.rb +24 -2
  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 +11 -74
  29. data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +24 -0
  30. data/lib/theme_check/checks/asset_size_javascript.rb +10 -36
  31. data/lib/theme_check/checks/deprecate_bgsizes.rb +14 -0
  32. data/lib/theme_check/checks/deprecate_lazysizes.rb +16 -0
  33. data/lib/theme_check/checks/html_parsing_error.rb +12 -0
  34. data/lib/theme_check/checks/img_lazy_loading.rb +1 -6
  35. data/lib/theme_check/checks/liquid_tag.rb +2 -2
  36. data/lib/theme_check/checks/remote_asset.rb +2 -0
  37. data/lib/theme_check/checks/space_inside_braces.rb +1 -1
  38. data/lib/theme_check/checks/template_length.rb +18 -4
  39. data/lib/theme_check/cli.rb +34 -13
  40. data/lib/theme_check/config.rb +56 -10
  41. data/lib/theme_check/exceptions.rb +29 -27
  42. data/lib/theme_check/html_check.rb +2 -0
  43. data/lib/theme_check/html_visitor.rb +3 -1
  44. data/lib/theme_check/json_file.rb +2 -29
  45. data/lib/theme_check/language_server/constants.rb +8 -0
  46. data/lib/theme_check/language_server/document_link_engine.rb +40 -4
  47. data/lib/theme_check/language_server/handler.rb +1 -1
  48. data/lib/theme_check/language_server/server.rb +13 -2
  49. data/lib/theme_check/liquid_check.rb +0 -12
  50. data/lib/theme_check/parsing_helpers.rb +3 -1
  51. data/lib/theme_check/regex_helpers.rb +17 -0
  52. data/lib/theme_check/tags.rb +62 -8
  53. data/lib/theme_check/template.rb +3 -32
  54. data/lib/theme_check/theme_file.rb +40 -0
  55. data/lib/theme_check/version.rb +1 -1
  56. metadata +22 -3
@@ -10,5 +10,13 @@ module ThemeCheck
10
10
  ^\s*render\s+'(?<partial>[^']*)'|
11
11
  ^\s*render\s+"(?<partial>[^"]*)"
12
12
  }mix
13
+ ASSET_INCLUDE = %r{
14
+ \{\%-?\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
15
+ \{\%-?\s*"(?<partial>[^"]*)"\s*\|\s*asset_url|
16
+
17
+ # in liquid tags the whole line is white space until the asset partial
18
+ ^\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
19
+ ^\s*"(?<partial>[^"]*)"\s*\|\s*asset_url
20
+ }mix
13
21
  end
14
22
  end
@@ -13,7 +13,7 @@ module ThemeCheck
13
13
  def document_links(relative_path)
14
14
  buffer = @storage.read(relative_path)
15
15
  return [] unless buffer
16
- matches(buffer, PARTIAL_RENDER).map do |match|
16
+ snippet_matches = matches(buffer, PARTIAL_RENDER).map do |match|
17
17
  start_line, start_character = from_index_to_row_column(
18
18
  buffer,
19
19
  match.begin(:partial),
@@ -25,7 +25,7 @@ module ThemeCheck
25
25
  )
26
26
 
27
27
  {
28
- target: link(match[:partial]),
28
+ target: snippet_link(match[:partial]),
29
29
  range: {
30
30
  start: {
31
31
  line: start_line,
@@ -38,10 +38,46 @@ module ThemeCheck
38
38
  },
39
39
  }
40
40
  end
41
+ asset_matches = matches(buffer, ASSET_INCLUDE).map do |match|
42
+ start_line, start_character = from_index_to_row_column(
43
+ buffer,
44
+ match.begin(:partial),
45
+ )
46
+
47
+ end_line, end_character = from_index_to_row_column(
48
+ buffer,
49
+ match.end(:partial)
50
+ )
51
+
52
+ {
53
+ target: asset_link(match[:partial]),
54
+ range: {
55
+ start: {
56
+ line: start_line,
57
+ character: start_character,
58
+ },
59
+ end: {
60
+ line: end_line,
61
+ character: end_character,
62
+ },
63
+ },
64
+ }
65
+ end
66
+ snippet_matches + asset_matches
67
+ end
68
+
69
+ def snippet_link(partial)
70
+ file_link('snippets', partial, '.liquid')
41
71
  end
42
72
 
43
- def link(partial)
44
- "file://#{@storage.path('snippets/' + partial + '.liquid')}"
73
+ def asset_link(partial)
74
+ file_link('assets', partial, '')
75
+ end
76
+
77
+ private
78
+
79
+ def file_link(directory, partial, extension)
80
+ "file://#{@storage.path(directory + '/' + partial + extension)}"
45
81
  end
46
82
  end
47
83
  end
@@ -99,7 +99,7 @@ module ThemeCheck
99
99
  end
100
100
 
101
101
  def path_from_uri(uri)
102
- uri&.sub('file://', '')
102
+ uri&.sub('file://', '')&.sub('/c%3A', '')
103
103
  end
104
104
 
105
105
  def relative_path_from_text_document_uri(params)
@@ -52,8 +52,19 @@ module ThemeCheck
52
52
  response_body = JSON.dump(response)
53
53
  log(JSON.pretty_generate(response)) if $DEBUG
54
54
 
55
- @out.write("Content-Length: #{response_body.bytesize}\r\n")
56
- @out.write("\r\n")
55
+ # Because programming is fun,
56
+ #
57
+ # Ruby on Windows turns \n into \r\n. Which means that \r\n
58
+ # gets turned into \r\r\n. Which means that the protocol
59
+ # breaks on windows unless we turn STDOUT into binary mode and
60
+ # set the encoding manually (yuk!) or we do this little hack
61
+ # here and put \n which gets transformed into \r\n on windows
62
+ # only...
63
+ #
64
+ # Hours wasted: 8.
65
+ eol = Gem.win_platform? ? "\n" : "\r\n"
66
+ @out.write("Content-Length: #{response_body.bytesize}#{eol}")
67
+ @out.write(eol)
57
68
  @out.write(response_body)
58
69
  @out.flush
59
70
  end
@@ -5,17 +5,5 @@ module ThemeCheck
5
5
  class LiquidCheck < Check
6
6
  extend ChecksTracking
7
7
  include ParsingHelpers
8
-
9
- # TODO: remove this once all regex checks are migrate to HtmlCheck# TODO: remove this once all regex checks are migrate to HtmlCheck
10
- TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
11
- VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
12
- START_OR_END_QUOTE = /(^['"])|(['"]$)/
13
- QUOTED_LIQUID_ATTRIBUTE = %r{
14
- '(?:#{TAG}|#{VARIABLE}|[^'])*'| # any combination of tag/variable or non straight quote inside straight quotes
15
- "(?:#{TAG}|#{VARIABLE}|[^"])*" # any combination of tag/variable or non double quotes inside double quotes
16
- }omix
17
- ATTR = /[a-z0-9-]+/i
18
- HTML_ATTRIBUTE = /#{ATTR}(?:=#{QUOTED_LIQUID_ATTRIBUTE})?/omix
19
- HTML_ATTRIBUTES = /(?:#{HTML_ATTRIBUTE}|\s)*/omix
20
8
  end
21
9
  end
@@ -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
@@ -133,13 +169,31 @@ module ThemeCheck
133
169
 
134
170
  class Stylesheet < Liquid::Raw; end
135
171
 
136
- Liquid::Template.register_tag('form', Form)
137
- Liquid::Template.register_tag('layout', Layout)
138
- Liquid::Template.register_tag('paginate', Paginate)
139
- Liquid::Template.register_tag('section', Section)
140
- Liquid::Template.register_tag('style', Style)
141
- Liquid::Template.register_tag('schema', Schema)
142
- Liquid::Template.register_tag('javascript', Javascript)
143
- Liquid::Template.register_tag('stylesheet', Stylesheet)
172
+ class << self
173
+ attr_writer :register_tags
174
+
175
+ def register_tags?
176
+ @register_tags
177
+ end
178
+
179
+ def register_tag(name, klass)
180
+ Liquid::Template.register_tag(name, klass)
181
+ end
182
+
183
+ def register_tags!
184
+ return if !register_tags? || (defined?(@registered_tags) && @registered_tags)
185
+ @registered_tags = true
186
+ register_tag('form', Form)
187
+ register_tag('layout', Layout)
188
+ register_tag('render', Render)
189
+ register_tag('paginate', Paginate)
190
+ register_tag('section', Section)
191
+ register_tag('style', Style)
192
+ register_tag('schema', Schema)
193
+ register_tag('javascript', Javascript)
194
+ register_tag('stylesheet', Stylesheet)
195
+ end
196
+ end
197
+ self.register_tags = true
144
198
  end
145
199
  end
@@ -1,25 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require "pathname"
3
2
 
4
3
  module ThemeCheck
5
- class Template
6
- def initialize(relative_path, storage)
7
- @storage = storage
8
- @relative_path = relative_path
9
- end
10
-
11
- def path
12
- @storage.path(@relative_path)
13
- end
14
-
15
- def relative_path
16
- @relative_pathname ||= Pathname.new(@relative_path)
17
- end
18
-
19
- def source
20
- @source ||= @storage.read(@relative_path)
21
- end
22
-
4
+ class Template < ThemeFile
23
5
  def write
24
6
  content = updated_content
25
7
  if source != content
@@ -28,14 +10,6 @@ module ThemeCheck
28
10
  end
29
11
  end
30
12
 
31
- def name
32
- relative_path.sub_ext('').to_s
33
- end
34
-
35
- def json?
36
- false
37
- end
38
-
39
13
  def liquid?
40
14
  true
41
15
  end
@@ -88,16 +62,13 @@ module ThemeCheck
88
62
  parse.root
89
63
  end
90
64
 
91
- def ==(other)
92
- other.is_a?(Template) && relative_path == other.relative_path
93
- end
94
- alias_method :eql?, :==
95
-
96
65
  def self.parse(source)
66
+ Tags.register_tags!
97
67
  Liquid::Template.parse(
98
68
  source,
99
69
  line_numbers: true,
100
70
  error_mode: :warn,
71
+ disable_liquid_c_nodes: true,
101
72
  )
102
73
  end
103
74
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require "pathname"
3
+
4
+ module ThemeCheck
5
+ class ThemeFile
6
+ def initialize(relative_path, storage)
7
+ @relative_path = relative_path
8
+ @storage = storage
9
+ end
10
+
11
+ def path
12
+ @storage.path(@relative_path)
13
+ end
14
+
15
+ def relative_path
16
+ @relative_pathname ||= Pathname.new(@relative_path)
17
+ end
18
+
19
+ def name
20
+ relative_path.sub_ext('').to_s
21
+ end
22
+
23
+ def source
24
+ @source ||= @storage.read(@relative_path)
25
+ end
26
+
27
+ def json?
28
+ false
29
+ end
30
+
31
+ def liquid?
32
+ false
33
+ end
34
+
35
+ def ==(other)
36
+ other.is_a?(self.class) && relative_path == other.relative_path
37
+ end
38
+ alias_method :eql?, :==
39
+ end
40
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.10.1"
3
+ VERSION = "1.2.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.10.1
4
+ version: 1.2.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-06-11 00:00:00.000000000 Z
11
+ date: 2021-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -44,6 +44,8 @@ email:
44
44
  executables:
45
45
  - theme-check
46
46
  - theme-check-language-server
47
+ - theme-check-language-server.bat
48
+ - theme-check.bat
47
49
  extensions: []
48
50
  extra_rdoc_files: []
49
51
  files:
@@ -62,6 +64,8 @@ files:
62
64
  - Rakefile
63
65
  - bin/liquid-server
64
66
  - config/default.yml
67
+ - config/nothing.yml
68
+ - config/theme_app_extension.yml
65
69
  - data/shopify_liquid/deprecated_filters.yml
66
70
  - data/shopify_liquid/filters.yml
67
71
  - data/shopify_liquid/objects.yml
@@ -74,13 +78,19 @@ files:
74
78
  - docs/api/json_check.md
75
79
  - docs/api/liquid_check.md
76
80
  - docs/checks/TEMPLATE.md.erb
81
+ - docs/checks/asset_size_app_block_css.md
82
+ - docs/checks/asset_size_app_block_javascript.md
77
83
  - docs/checks/asset_size_css.md
84
+ - docs/checks/asset_size_css_stylesheet_tag.md
78
85
  - docs/checks/asset_size_javascript.md
79
86
  - docs/checks/asset_url_filters.md
80
87
  - docs/checks/content_for_header_modification.md
81
88
  - docs/checks/convert_include_to_render.md
82
89
  - docs/checks/default_locale.md
90
+ - docs/checks/deprecate_bgsizes.md
91
+ - docs/checks/deprecate_lazysizes.md
83
92
  - docs/checks/deprecated_filter.md
93
+ - docs/checks/html_parsing_error.md
84
94
  - docs/checks/img_lazy_loading.md
85
95
  - docs/checks/img_width_and_height.md
86
96
  - docs/checks/liquid_tag.md
@@ -109,6 +119,8 @@ files:
109
119
  - docs/preview.png
110
120
  - exe/theme-check
111
121
  - exe/theme-check-language-server
122
+ - exe/theme-check-language-server.bat
123
+ - exe/theme-check.bat
112
124
  - lib/theme_check.rb
113
125
  - lib/theme_check/analyzer.rb
114
126
  - lib/theme_check/asset_file.rb
@@ -116,13 +128,19 @@ files:
116
128
  - lib/theme_check/check.rb
117
129
  - lib/theme_check/checks.rb
118
130
  - lib/theme_check/checks/TEMPLATE.rb.erb
131
+ - lib/theme_check/checks/asset_size_app_block_css.rb
132
+ - lib/theme_check/checks/asset_size_app_block_javascript.rb
119
133
  - lib/theme_check/checks/asset_size_css.rb
134
+ - lib/theme_check/checks/asset_size_css_stylesheet_tag.rb
120
135
  - lib/theme_check/checks/asset_size_javascript.rb
121
136
  - lib/theme_check/checks/asset_url_filters.rb
122
137
  - lib/theme_check/checks/content_for_header_modification.rb
123
138
  - lib/theme_check/checks/convert_include_to_render.rb
124
139
  - lib/theme_check/checks/default_locale.rb
140
+ - lib/theme_check/checks/deprecate_bgsizes.rb
141
+ - lib/theme_check/checks/deprecate_lazysizes.rb
125
142
  - lib/theme_check/checks/deprecated_filter.rb
143
+ - lib/theme_check/checks/html_parsing_error.rb
126
144
  - lib/theme_check/checks/img_lazy_loading.rb
127
145
  - lib/theme_check/checks/img_width_and_height.rb
128
146
  - lib/theme_check/checks/liquid_tag.rb
@@ -201,6 +219,7 @@ files:
201
219
  - lib/theme_check/tags.rb
202
220
  - lib/theme_check/template.rb
203
221
  - lib/theme_check/theme.rb
222
+ - lib/theme_check/theme_file.rb
204
223
  - lib/theme_check/version.rb
205
224
  - lib/theme_check/visitor.rb
206
225
  - packaging/homebrew/theme_check.base.rb
@@ -225,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
225
244
  - !ruby/object:Gem::Version
226
245
  version: '0'
227
246
  requirements: []
228
- rubygems_version: 3.2.17
247
+ rubygems_version: 3.2.20
229
248
  signing_key:
230
249
  specification_version: 4
231
250
  summary: A Shopify Theme Linter