theme-check 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +14 -6
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +42 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/Gemfile +3 -0
  7. data/config/default.yml +3 -0
  8. data/docs/checks/deprecated_global_app_block_type.md +65 -0
  9. data/docs/flamegraph.svg +18488 -0
  10. data/lib/theme_check/analyzer.rb +5 -0
  11. data/lib/theme_check/asset_file.rb +13 -2
  12. data/lib/theme_check/check.rb +1 -1
  13. data/lib/theme_check/checks/asset_size_css.rb +15 -0
  14. data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
  15. data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
  16. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
  17. data/lib/theme_check/checks/liquid_tag.rb +1 -1
  18. data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
  19. data/lib/theme_check/checks/pagination_size.rb +33 -14
  20. data/lib/theme_check/checks/required_directories.rb +3 -1
  21. data/lib/theme_check/checks/space_inside_braces.rb +47 -24
  22. data/lib/theme_check/checks/translation_key_exists.rb +3 -1
  23. data/lib/theme_check/checks/unused_snippet.rb +3 -1
  24. data/lib/theme_check/cli.rb +32 -6
  25. data/lib/theme_check/corrector.rb +23 -10
  26. data/lib/theme_check/file_system_storage.rb +13 -2
  27. data/lib/theme_check/html_node.rb +4 -4
  28. data/lib/theme_check/html_visitor.rb +20 -8
  29. data/lib/theme_check/in_memory_storage.rb +8 -0
  30. data/lib/theme_check/json_file.rb +9 -4
  31. data/lib/theme_check/json_printer.rb +6 -1
  32. data/lib/theme_check/language_server/document_link_provider.rb +2 -1
  33. data/lib/theme_check/language_server/handler.rb +16 -11
  34. data/lib/theme_check/language_server/server.rb +11 -13
  35. data/lib/theme_check/language_server/uri_helper.rb +37 -0
  36. data/lib/theme_check/language_server.rb +1 -0
  37. data/lib/theme_check/node.rb +118 -11
  38. data/lib/theme_check/offense.rb +26 -0
  39. data/lib/theme_check/position.rb +27 -16
  40. data/lib/theme_check/position_helper.rb +13 -15
  41. data/lib/theme_check/printer.rb +9 -5
  42. data/lib/theme_check/regex_helpers.rb +1 -15
  43. data/lib/theme_check/remote_asset_file.rb +4 -0
  44. data/lib/theme_check/template.rb +5 -19
  45. data/lib/theme_check/template_rewriter.rb +57 -0
  46. data/lib/theme_check/theme_file.rb +18 -1
  47. data/lib/theme_check/version.rb +1 -1
  48. data/lib/theme_check.rb +1 -0
  49. data/theme-check.gemspec +1 -0
  50. metadata +21 -2
@@ -144,6 +144,32 @@ module ThemeCheck
144
144
  corrector = Corrector.new(template: template)
145
145
  correction.call(corrector)
146
146
  end
147
+ rescue => e
148
+ ThemeCheck.bug(<<~EOS)
149
+ Exception while running `Offense#correct`:
150
+ ```
151
+ #{e.class}: #{e.message}
152
+ #{e.backtrace.join("\n ")}
153
+ ```
154
+
155
+ Offense:
156
+ ```
157
+ #{JSON.pretty_generate(to_h)}
158
+ ```
159
+ Check options:
160
+ ```
161
+ #{check.options.pretty_inspect}
162
+ ```
163
+ Markup:
164
+ ```
165
+ #{markup}
166
+ ```
167
+ Node.Markup:
168
+ ```
169
+ #{node&.markup}
170
+ ```
171
+ EOS
172
+ exit(2)
147
173
  end
148
174
 
149
175
  def whole_theme?
@@ -16,22 +16,22 @@ module ThemeCheck
16
16
  @line_number_1_indexed = line_number_1_indexed
17
17
  @node_markup_offset = node_markup_offset
18
18
  @node_markup = node_markup
19
- @strict_position = StrictPosition.new(
20
- needle,
21
- contents,
22
- start_index,
23
- )
24
19
  end
25
20
 
26
21
  def start_line_offset
27
- from_row_column_to_index(contents, line_number, 0)
22
+ @start_line_offset ||= from_row_column_to_index(contents, line_number, 0)
28
23
  end
29
24
 
30
25
  def start_offset
31
- return start_line_offset if @node_markup.nil?
32
- node_markup_start = contents.index(@node_markup, start_line_offset)
33
- return start_line_offset if node_markup_start.nil?
34
- node_markup_start + @node_markup_offset
26
+ @start_offset ||= compute_start_offset
27
+ end
28
+
29
+ def strict_position
30
+ @strict_position ||= StrictPosition.new(
31
+ needle,
32
+ contents,
33
+ start_index,
34
+ )
35
35
  end
36
36
 
37
37
  # 0-indexed, inclusive
@@ -41,39 +41,50 @@ module ThemeCheck
41
41
 
42
42
  # 0-indexed, exclusive
43
43
  def end_index
44
- @strict_position.end_index
44
+ strict_position.end_index
45
45
  end
46
46
 
47
47
  # 0-indexed, inclusive
48
48
  def start_row
49
- @strict_position.start_row
49
+ strict_position.start_row
50
50
  end
51
51
 
52
52
  # 0-indexed, inclusive
53
53
  def start_column
54
- @strict_position.start_column
54
+ strict_position.start_column
55
55
  end
56
56
 
57
57
  # 0-indexed, exclusive (both taken together are) therefore you
58
58
  # might end up on a newline character or the next line
59
59
  def end_row
60
- @strict_position.end_row
60
+ strict_position.end_row
61
61
  end
62
62
 
63
63
  def end_column
64
- @strict_position.end_column
64
+ strict_position.end_column
65
65
  end
66
66
 
67
67
  private
68
68
 
69
+ def compute_start_offset
70
+ return start_line_offset if @node_markup.nil?
71
+ node_markup_start = contents.index(@node_markup, start_line_offset)
72
+ return start_line_offset if node_markup_start.nil?
73
+ node_markup_start + @node_markup_offset
74
+ end
75
+
69
76
  def contents
70
77
  return '' unless @contents.is_a?(String) && !@contents.empty?
71
78
  @contents
72
79
  end
73
80
 
81
+ def content_line_count
82
+ @content_line_count ||= contents.count("\n")
83
+ end
84
+
74
85
  def line_number
75
86
  return 0 if @line_number_1_indexed.nil?
76
- bounded(0, @line_number_1_indexed - 1, contents.lines.size - 1)
87
+ bounded(0, @line_number_1_indexed - 1, content_line_count)
77
88
  end
78
89
 
79
90
  def needle
@@ -7,31 +7,29 @@ module ThemeCheck
7
7
  return 0 unless content.is_a?(String) && !content.empty?
8
8
  return 0 unless row.is_a?(Integer) && col.is_a?(Integer)
9
9
  i = 0
10
- result = 0
11
- safe_row = bounded(0, row, content.lines.size - 1)
12
- lines = content.lines
13
- line_size = lines[i].size
14
- while i < safe_row
15
- result += line_size
16
- i += 1
17
- line_size = lines[i].size
18
- end
19
- result += bounded(0, col, line_size - 1)
20
- result
10
+ safe_row = bounded(0, row, content.count("\n"))
11
+ scanner = StringScanner.new(content)
12
+ scanner.scan_until(/\n/) while i < safe_row && (i += 1)
13
+ result = scanner.charpos || 0
14
+ scanner.scan_until(/\n|\z/)
15
+ bounded(result, result + col, scanner.pre_match.size)
21
16
  end
22
17
 
23
18
  def from_index_to_row_column(content, index)
24
19
  return [0, 0] unless content.is_a?(String) && !content.empty?
25
20
  return [0, 0] unless index.is_a?(Integer)
26
21
  safe_index = bounded(0, index, content.size - 1)
27
- lines = content[0..safe_index].lines
28
- row = lines.size - 1
29
- col = lines.last.size - 1
22
+ content_up_to_index = content[0...safe_index]
23
+ row = content_up_to_index.count("\n")
24
+ col = 0
25
+ col += 1 while (safe_index -= 1) && safe_index >= 0 && content[safe_index] != "\n"
30
26
  [row, col]
31
27
  end
32
28
 
33
29
  def bounded(a, x, b)
34
- [a, [x, b].min].max
30
+ return a if x < a
31
+ return b if x > b
32
+ x
35
33
  end
36
34
  end
37
35
  end
@@ -2,14 +2,18 @@
2
2
 
3
3
  module ThemeCheck
4
4
  class Printer
5
+ def initialize(out_stream = STDOUT)
6
+ @out = out_stream
7
+ end
8
+
5
9
  def print(theme, offenses, auto_correct)
6
10
  offenses.each do |offense|
7
11
  print_offense(offense, auto_correct)
8
- puts
12
+ @out.puts
9
13
  end
10
14
 
11
15
  correctable = offenses.select(&:correctable?)
12
- puts "#{theme.all.size} files inspected, #{red(offenses.size.to_s + ' offenses')} detected, \
16
+ @out.puts "#{theme.all.size} files inspected, #{red(offenses.size.to_s + ' offenses')} detected, \
13
17
  #{yellow(correctable.size.to_s + ' offenses')} #{auto_correct ? 'corrected' : 'auto-correctable'}"
14
18
  end
15
19
 
@@ -26,15 +30,15 @@ module ThemeCheck
26
30
  ""
27
31
  end
28
32
 
29
- puts location +
33
+ @out.puts location +
30
34
  colorized_severity(offense.severity) + ": " +
31
35
  yellow(offense.check_name) + ": " +
32
36
  corrected +
33
37
  offense.message + "."
34
38
  if offense.source_excerpt
35
- puts "\t#{offense.source_excerpt}"
39
+ @out.puts "\t#{offense.source_excerpt}"
36
40
  if offense.markup_start_in_excerpt
37
- puts "\t" + (" " * offense.markup_start_in_excerpt) + ("^" * offense.markup.size)
41
+ @out.puts "\t" + (" " * offense.markup_start_in_excerpt) + ("^" * offense.markup.size)
38
42
  end
39
43
  end
40
44
  end
@@ -5,6 +5,7 @@ module ThemeCheck
5
5
  LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
6
6
  LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
7
7
  LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
8
+ HTML_LIQUID_PLACEHOLDER = /≬[0-9a-z]+#*≬/m
8
9
  START_OR_END_QUOTE = /(^['"])|(['"]$)/
9
10
 
10
11
  def matches(s, re)
@@ -16,20 +17,5 @@ module ThemeCheck
16
17
  end
17
18
  matches
18
19
  end
19
-
20
- def href_to_file_size(href)
21
- # asset_url (+ optional stylesheet_tag) variables
22
- if href =~ /^#{LIQUID_VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
23
- asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
24
- asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
25
- return if asset.nil?
26
- asset.gzipped_size
27
-
28
- # remote URLs
29
- elsif href =~ %r{^(https?:)?//}
30
- asset = RemoteAssetFile.from_src(href)
31
- asset.gzipped_size
32
- end
33
- end
34
20
  end
35
21
  end
@@ -17,6 +17,8 @@ module ThemeCheck
17
17
 
18
18
  def uri(src)
19
19
  URI.parse(src.sub(%r{^//}, "https://"))
20
+ rescue URI::InvalidURIError
21
+ nil
20
22
  end
21
23
  end
22
24
 
@@ -26,6 +28,7 @@ module ThemeCheck
26
28
  end
27
29
 
28
30
  def content
31
+ return if @uri.nil?
29
32
  return @content unless @content.nil?
30
33
 
31
34
  res = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == 'https') do |http|
@@ -41,6 +44,7 @@ module ThemeCheck
41
44
  end
42
45
 
43
46
  def gzipped_size
47
+ return if @uri.nil?
44
48
  @gzipped_size ||= content.bytesize
45
49
  end
46
50
  end
@@ -3,10 +3,11 @@
3
3
  module ThemeCheck
4
4
  class Template < ThemeFile
5
5
  def write
6
- content = updated_content
6
+ content = rewriter.to_s
7
7
  if source != content
8
- @storage.write(@relative_path, content)
8
+ @storage.write(@relative_path, content.gsub("\n", @eol))
9
9
  @source = content
10
+ @rewriter = nil
10
11
  end
11
12
  end
12
13
 
@@ -26,19 +27,8 @@ module ThemeCheck
26
27
  name.start_with?('snippets')
27
28
  end
28
29
 
29
- def lines
30
- # Retain trailing newline character
31
- @lines ||= source.split("\n", -1)
32
- end
33
-
34
- # Not entirely obvious but lines is mutable, corrections are to be
35
- # applied on @lines.
36
- def updated_content
37
- lines.join("\n")
38
- end
39
-
40
- def excerpt(line)
41
- lines[line - 1].strip
30
+ def rewriter
31
+ @rewriter ||= TemplateRewriter.new(@relative_path, source)
42
32
  end
43
33
 
44
34
  def source_excerpt(line)
@@ -46,10 +36,6 @@ module ThemeCheck
46
36
  original_lines[line - 1].strip
47
37
  end
48
38
 
49
- def full_line(line)
50
- lines[line - 1]
51
- end
52
-
53
39
  def parse
54
40
  @ast ||= self.class.parse(source)
55
41
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser'
4
+
5
+ module ThemeCheck
6
+ class TemplateRewriter
7
+ def initialize(name, source)
8
+ @buffer = Parser::Source::Buffer.new(name, source: source)
9
+ @rewriter = Parser::Source::TreeRewriter.new(
10
+ @buffer
11
+ )
12
+ end
13
+
14
+ def insert_before(node, content)
15
+ @rewriter.insert_before(
16
+ range(node.start_index, node.end_index),
17
+ content
18
+ )
19
+ end
20
+
21
+ def insert_after(node, content)
22
+ @rewriter.insert_after(
23
+ range(node.start_index, node.end_index),
24
+ content
25
+ )
26
+ end
27
+
28
+ def replace(node, content)
29
+ @rewriter.replace(
30
+ range(node.start_index, node.end_index),
31
+ content
32
+ )
33
+ end
34
+
35
+ def wrap(node, insert_before, insert_after)
36
+ @rewriter.wrap(
37
+ range(node.start_index, node.end_index),
38
+ insert_before,
39
+ insert_after,
40
+ )
41
+ end
42
+
43
+ def to_s
44
+ @rewriter.process
45
+ end
46
+
47
+ private
48
+
49
+ def range(start_index, end_index)
50
+ Parser::Source::Range.new(
51
+ @buffer,
52
+ start_index,
53
+ end_index,
54
+ )
55
+ end
56
+ end
57
+ end
@@ -6,6 +6,8 @@ module ThemeCheck
6
6
  def initialize(relative_path, storage)
7
7
  @relative_path = relative_path
8
8
  @storage = storage
9
+ @source = nil
10
+ @eol = "\n"
9
11
  end
10
12
 
11
13
  def path
@@ -20,8 +22,23 @@ module ThemeCheck
20
22
  relative_path.sub_ext('').to_s
21
23
  end
22
24
 
25
+ # For the corrector to work properly, we should have a
26
+ # simple mental model of the internal representation of eol
27
+ # characters (Windows uses \r\n, Linux uses \n).
28
+ #
29
+ # Parser::Source::Buffer strips the \r from the source file, so if
30
+ # you are autocorrecting the file you might lose that info and
31
+ # cause a git diff. It also makes the node.start_index/end_index
32
+ # calculation break. That's not cool.
33
+ #
34
+ # So in here we track whether the source file has \r\n in it and
35
+ # we'll make sure that the file we write has the same eol as the
36
+ # source file.
23
37
  def source
24
- @source ||= @storage.read(@relative_path)
38
+ return @source if @source
39
+ @source = @storage.read(@relative_path)
40
+ @eol = @source.include?("\r\n") ? "\r\n" : "\n"
41
+ @source = @source.gsub("\r\n", "\n")
25
42
  end
26
43
 
27
44
  def json?
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.4.0"
3
+ VERSION = "1.6.0"
4
4
  end
data/lib/theme_check.rb CHANGED
@@ -34,6 +34,7 @@ require_relative "theme_check/string_helpers"
34
34
  require_relative "theme_check/file_system_storage"
35
35
  require_relative "theme_check/in_memory_storage"
36
36
  require_relative "theme_check/tags"
37
+ require_relative "theme_check/template_rewriter"
37
38
  require_relative "theme_check/template"
38
39
  require_relative "theme_check/theme"
39
40
  require_relative "theme_check/visitor"
data/theme-check.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency('liquid', '>= 5.0.1')
28
28
  spec.add_dependency('nokogiri', '>= 1.12')
29
+ spec.add_dependency('parser', '~> 3')
29
30
  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: 1.4.0
4
+ version: 1.6.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-08-30 00:00:00.000000000 Z
11
+ date: 2021-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parser
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
41
55
  description:
42
56
  email:
43
57
  - marcandre.cournoyer@shopify.com
@@ -90,6 +104,7 @@ files:
90
104
  - docs/checks/deprecate_bgsizes.md
91
105
  - docs/checks/deprecate_lazysizes.md
92
106
  - docs/checks/deprecated_filter.md
107
+ - docs/checks/deprecated_global_app_block_type.md
93
108
  - docs/checks/html_parsing_error.md
94
109
  - docs/checks/img_lazy_loading.md
95
110
  - docs/checks/img_width_and_height.md
@@ -117,6 +132,7 @@ files:
117
132
  - docs/checks/valid_html_translation.md
118
133
  - docs/checks/valid_json.md
119
134
  - docs/checks/valid_schema.md
135
+ - docs/flamegraph.svg
120
136
  - docs/preview.png
121
137
  - exe/theme-check
122
138
  - exe/theme-check-language-server
@@ -140,6 +156,7 @@ files:
140
156
  - lib/theme_check/checks/deprecate_bgsizes.rb
141
157
  - lib/theme_check/checks/deprecate_lazysizes.rb
142
158
  - lib/theme_check/checks/deprecated_filter.rb
159
+ - lib/theme_check/checks/deprecated_global_app_block_type.rb
143
160
  - lib/theme_check/checks/html_parsing_error.rb
144
161
  - lib/theme_check/checks/img_lazy_loading.rb
145
162
  - lib/theme_check/checks/img_width_and_height.rb
@@ -203,6 +220,7 @@ files:
203
220
  - lib/theme_check/language_server/protocol.rb
204
221
  - lib/theme_check/language_server/server.rb
205
222
  - lib/theme_check/language_server/tokens.rb
223
+ - lib/theme_check/language_server/uri_helper.rb
206
224
  - lib/theme_check/language_server/variable_lookup_finder.rb
207
225
  - lib/theme_check/liquid_check.rb
208
226
  - lib/theme_check/locale_diff.rb
@@ -225,6 +243,7 @@ files:
225
243
  - lib/theme_check/string_helpers.rb
226
244
  - lib/theme_check/tags.rb
227
245
  - lib/theme_check/template.rb
246
+ - lib/theme_check/template_rewriter.rb
228
247
  - lib/theme_check/theme.rb
229
248
  - lib/theme_check/theme_file.rb
230
249
  - lib/theme_check/version.rb