theme-check 1.5.0 → 1.6.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +12 -4
  3. data/CHANGELOG.md +34 -0
  4. data/CONTRIBUTING.md +58 -0
  5. data/Gemfile +3 -0
  6. data/docs/flamegraph.svg +18488 -0
  7. data/lib/theme_check/analyzer.rb +5 -0
  8. data/lib/theme_check/asset_file.rb +13 -2
  9. data/lib/theme_check/check.rb +1 -1
  10. data/lib/theme_check/checks/asset_size_css.rb +15 -0
  11. data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
  12. data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
  13. data/lib/theme_check/checks/liquid_tag.rb +1 -1
  14. data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
  15. data/lib/theme_check/checks/pagination_size.rb +30 -10
  16. data/lib/theme_check/checks/required_directories.rb +3 -1
  17. data/lib/theme_check/checks/space_inside_braces.rb +47 -24
  18. data/lib/theme_check/checks/translation_key_exists.rb +3 -1
  19. data/lib/theme_check/checks/unused_snippet.rb +3 -1
  20. data/lib/theme_check/checks.rb +2 -0
  21. data/lib/theme_check/cli.rb +32 -6
  22. data/lib/theme_check/corrector.rb +23 -10
  23. data/lib/theme_check/file_system_storage.rb +13 -2
  24. data/lib/theme_check/html_node.rb +4 -4
  25. data/lib/theme_check/html_visitor.rb +20 -8
  26. data/lib/theme_check/in_memory_storage.rb +8 -0
  27. data/lib/theme_check/json_file.rb +9 -4
  28. data/lib/theme_check/json_printer.rb +5 -1
  29. data/lib/theme_check/node.rb +118 -11
  30. data/lib/theme_check/offense.rb +26 -0
  31. data/lib/theme_check/position.rb +27 -16
  32. data/lib/theme_check/position_helper.rb +13 -15
  33. data/lib/theme_check/printer.rb +9 -5
  34. data/lib/theme_check/regex_helpers.rb +1 -15
  35. data/lib/theme_check/remote_asset_file.rb +4 -0
  36. data/lib/theme_check/template.rb +5 -19
  37. data/lib/theme_check/template_rewriter.rb +57 -0
  38. data/lib/theme_check/theme_file.rb +18 -1
  39. data/lib/theme_check/version.rb +1 -1
  40. data/lib/theme_check.rb +1 -0
  41. data/theme-check.gemspec +1 -0
  42. metadata +18 -2
@@ -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.5.0"
3
+ VERSION = "1.6.1"
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.5.0
4
+ version: 1.6.1
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-09-07 00:00:00.000000000 Z
11
+ date: 2021-09-15 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
@@ -118,6 +132,7 @@ files:
118
132
  - docs/checks/valid_html_translation.md
119
133
  - docs/checks/valid_json.md
120
134
  - docs/checks/valid_schema.md
135
+ - docs/flamegraph.svg
121
136
  - docs/preview.png
122
137
  - exe/theme-check
123
138
  - exe/theme-check-language-server
@@ -228,6 +243,7 @@ files:
228
243
  - lib/theme_check/string_helpers.rb
229
244
  - lib/theme_check/tags.rb
230
245
  - lib/theme_check/template.rb
246
+ - lib/theme_check/template_rewriter.rb
231
247
  - lib/theme_check/theme.rb
232
248
  - lib/theme_check/theme_file.rb
233
249
  - lib/theme_check/version.rb