theme-check 0.10.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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