theme-check 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/bin/theme-check +29 -0
  5. data/bin/theme-check-language-server +29 -0
  6. data/config/default.yml +11 -0
  7. data/config/theme_app_extension.yml +15 -0
  8. data/docs/checks/app_block_valid_tags.md +40 -0
  9. data/docs/checks/asset_size_app_block_css.md +1 -1
  10. data/docs/checks/missing_template.md +25 -0
  11. data/docs/checks/pagination_size.md +44 -0
  12. data/docs/checks/undefined_object.md +5 -0
  13. data/lib/theme_check/check.rb +2 -2
  14. data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
  15. data/lib/theme_check/checks/asset_size_css.rb +3 -3
  16. data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
  17. data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
  18. data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
  19. data/lib/theme_check/checks/deprecate_lazysizes.rb +2 -2
  20. data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
  21. data/lib/theme_check/checks/img_width_and_height.rb +3 -3
  22. data/lib/theme_check/checks/missing_template.rb +21 -5
  23. data/lib/theme_check/checks/pagination_size.rb +65 -0
  24. data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
  25. data/lib/theme_check/checks/remote_asset.rb +2 -2
  26. data/lib/theme_check/checks/space_inside_braces.rb +26 -6
  27. data/lib/theme_check/checks/undefined_object.rb +1 -1
  28. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  29. data/lib/theme_check/checks.rb +11 -1
  30. data/lib/theme_check/cli.rb +18 -2
  31. data/lib/theme_check/corrector.rb +4 -0
  32. data/lib/theme_check/file_system_storage.rb +12 -0
  33. data/lib/theme_check/html_check.rb +0 -1
  34. data/lib/theme_check/html_node.rb +37 -16
  35. data/lib/theme_check/html_visitor.rb +17 -3
  36. data/lib/theme_check/json_check.rb +2 -2
  37. data/lib/theme_check/json_printer.rb +26 -0
  38. data/lib/theme_check/language_server/handler.rb +6 -2
  39. data/lib/theme_check/node.rb +6 -4
  40. data/lib/theme_check/offense.rb +56 -3
  41. data/lib/theme_check/parsing_helpers.rb +4 -3
  42. data/lib/theme_check/position.rb +98 -14
  43. data/lib/theme_check/regex_helpers.rb +5 -2
  44. data/lib/theme_check/theme.rb +2 -0
  45. data/lib/theme_check/version.rb +1 -1
  46. data/lib/theme_check.rb +1 -0
  47. data/theme-check.gemspec +1 -1
  48. metadata +12 -10
  49. data/bin/liquid-server +0 -4
  50. data/exe/theme-check-language-server.bat +0 -3
  51. data/exe/theme-check.bat +0 -3
@@ -4,8 +4,8 @@ module ThemeCheck
4
4
  class JsonCheck < Check
5
5
  extend ChecksTracking
6
6
 
7
- def add_offense(message, markup: nil, line_number: nil, template: nil)
8
- offenses << Offense.new(check: self, message: message, markup: markup, line_number: line_number, template: template)
7
+ def add_offense(message, markup: nil, line_number: nil, template: nil, &block)
8
+ offenses << Offense.new(check: self, message: message, markup: markup, line_number: line_number, template: template, correction: block)
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module ThemeCheck
5
+ class JsonPrinter
6
+ def print(offenses)
7
+ json = offenses_by_path(offenses)
8
+ puts JSON.dump(json)
9
+ end
10
+
11
+ def offenses_by_path(offenses)
12
+ offenses
13
+ .map(&:to_h)
14
+ .group_by { |offense| offense[:path] }
15
+ .map do |(path, path_offenses)|
16
+ {
17
+ path: path,
18
+ offenses: path_offenses.map { |offense| offense.filter { |k, _v| k != :path } },
19
+ errorCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:error] },
20
+ suggestionCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:suggestion] },
21
+ styleCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:style] },
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "benchmark"
3
+ require "uri"
4
+ require "cgi"
3
5
 
4
6
  module ThemeCheck
5
7
  module LanguageServer
@@ -98,8 +100,10 @@ module ThemeCheck
98
100
  path_from_uri(params.dig('textDocument', 'uri'))
99
101
  end
100
102
 
101
- def path_from_uri(uri)
102
- uri&.sub('file://', '')&.sub('/c%3A', '')
103
+ def path_from_uri(uri_string)
104
+ return if uri_string.nil?
105
+ uri = URI(uri_string)
106
+ CGI.unescape(uri.path)
103
107
  end
104
108
 
105
109
  def relative_path_from_text_document_uri(params)
@@ -22,9 +22,7 @@ module ThemeCheck
22
22
  end
23
23
 
24
24
  def markup=(markup)
25
- if tag?
26
- @value.raw = markup
27
- elsif @value.instance_variable_defined?(:@markup)
25
+ if @value.instance_variable_defined?(:@markup)
28
26
  @value.instance_variable_set(:@markup, markup)
29
27
  end
30
28
  end
@@ -127,7 +125,11 @@ module ThemeCheck
127
125
  end
128
126
 
129
127
  def position
130
- @position ||= Position.new(markup, template&.source, line_number)
128
+ @position ||= Position.new(
129
+ markup,
130
+ template&.source,
131
+ line_number_1_indexed: line_number
132
+ )
131
133
  end
132
134
 
133
135
  def start_index
@@ -1,11 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
3
  class Offense
4
+ include PositionHelper
5
+
4
6
  MAX_SOURCE_EXCERPT_SIZE = 120
5
7
 
6
8
  attr_reader :check, :message, :template, :node, :markup, :line_number, :correction
7
9
 
8
- def initialize(check:, message: nil, template: nil, node: nil, markup: nil, line_number: nil, correction: nil)
10
+ def initialize(
11
+ check:, # instance of a ThemeCheck::Check
12
+ message: nil, # error message for the offense
13
+ template: nil, # Template
14
+ node: nil, # Node or HtmlNode
15
+ markup: nil, # string
16
+ line_number: nil, # line number of the error (1-indexed)
17
+ # node_markup_offset is the index inside node.markup to start
18
+ # looking for markup :mindblow:.
19
+ # This is so we can accurately highlight node substrings.
20
+ # e.g. if we have the following scenario in which we
21
+ # want to highlight the middle comma
22
+ # * node.markup == "replace ',',', '"
23
+ # * markup == ","
24
+ # Then we need some way of telling our Position class to start
25
+ # looking for the second comma. This is done with node_markup_offset.
26
+ # More context can be found in #376.
27
+ node_markup_offset: 0,
28
+ correction: nil # block
29
+ )
9
30
  @check = check
10
31
  @correction = correction
11
32
 
@@ -39,7 +60,13 @@ module ThemeCheck
39
60
  @node.line_number
40
61
  end
41
62
 
42
- @position = Position.new(@markup, @template&.source, @line_number)
63
+ @position = Position.new(
64
+ @markup,
65
+ @template&.source,
66
+ line_number_1_indexed: @line_number,
67
+ node_markup_offset: node_markup_offset,
68
+ node_markup: node&.markup
69
+ )
43
70
  end
44
71
 
45
72
  def source_excerpt
@@ -103,8 +130,13 @@ module ThemeCheck
103
130
  tokens.join(":") if tokens.any?
104
131
  end
105
132
 
133
+ def location_range
134
+ tokens = [template&.relative_path, start_index, end_index].compact
135
+ tokens.join(":") if tokens.any?
136
+ end
137
+
106
138
  def correctable?
107
- line_number && correction
139
+ !!correction
108
140
  end
109
141
 
110
142
  def correct
@@ -139,5 +171,26 @@ module ThemeCheck
139
171
  message
140
172
  end
141
173
  end
174
+
175
+ def to_s_range
176
+ if template
177
+ "#{message} at #{location_range}"
178
+ else
179
+ message
180
+ end
181
+ end
182
+
183
+ def to_h
184
+ {
185
+ check: check.code_name,
186
+ path: template&.relative_path,
187
+ severity: check.severity_value,
188
+ start_line: start_line,
189
+ start_column: start_column,
190
+ end_line: end_line,
191
+ end_column: end_column,
192
+ message: message,
193
+ }
194
+ end
142
195
  end
143
196
  end
@@ -5,15 +5,16 @@ module ThemeCheck
5
5
  def outside_of_strings(markup)
6
6
  scanner = StringScanner.new(markup)
7
7
 
8
- while scanner.scan(/.*?("|')/)
9
- yield scanner.matched[0..-2]
8
+ while scanner.scan(/.*?("|')/m)
9
+ chunk_start = scanner.pre_match.size
10
+ yield scanner.matched[0..-2], chunk_start
10
11
  quote = scanner.matched[-1] == "'" ? "'" : "\""
11
12
  # Skip to the end of the string
12
13
  # Check for empty string first, since follow regexp uses lookahead
13
14
  scanner.skip(/#{quote}/) || scanner.skip_until(/[^\\]#{quote}/)
14
15
  end
15
16
 
16
- yield scanner.rest if scanner.rest?
17
+ yield scanner.rest, scanner.charpos if scanner.rest?
17
18
  end
18
19
  end
19
20
  end
@@ -4,42 +4,64 @@ module ThemeCheck
4
4
  class Position
5
5
  include PositionHelper
6
6
 
7
- def initialize(needle, contents, line_number_1_indexed)
8
- @needle = needle
9
- @contents = contents
7
+ def initialize(
8
+ needle_arg,
9
+ contents_arg,
10
+ line_number_1_indexed: nil,
11
+ node_markup: nil,
12
+ node_markup_offset: 0 # the index of markup inside the node_markup
13
+ )
14
+ @needle = needle_arg
15
+ @contents = contents_arg
10
16
  @line_number_1_indexed = line_number_1_indexed
11
- @start_row_column = nil
12
- @end_row_column = nil
17
+ @node_markup_offset = node_markup_offset
18
+ @node_markup = node_markup
19
+ @strict_position = StrictPosition.new(
20
+ needle,
21
+ contents,
22
+ start_index,
23
+ )
13
24
  end
14
25
 
15
- def start_line_index
26
+ def start_line_offset
16
27
  from_row_column_to_index(contents, line_number, 0)
17
28
  end
18
29
 
30
+ 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
35
+ end
36
+
19
37
  # 0-indexed, inclusive
20
38
  def start_index
21
- contents.index(needle, start_line_index) || start_line_index
39
+ contents.index(needle, start_offset)
22
40
  end
23
41
 
24
42
  # 0-indexed, exclusive
25
43
  def end_index
26
- start_index + needle.size
44
+ @strict_position.end_index
27
45
  end
28
46
 
47
+ # 0-indexed, inclusive
29
48
  def start_row
30
- start_row_column[0]
49
+ @strict_position.start_row
31
50
  end
32
51
 
52
+ # 0-indexed, inclusive
33
53
  def start_column
34
- start_row_column[1]
54
+ @strict_position.start_column
35
55
  end
36
56
 
57
+ # 0-indexed, exclusive (both taken together are) therefore you
58
+ # might end up on a newline character or the next line
37
59
  def end_row
38
- end_row_column[0]
60
+ @strict_position.end_row
39
61
  end
40
62
 
41
63
  def end_column
42
- end_row_column[1]
64
+ @strict_position.end_column
43
65
  end
44
66
 
45
67
  private
@@ -55,15 +77,77 @@ module ThemeCheck
55
77
  end
56
78
 
57
79
  def needle
58
- if @needle.nil? && !contents.empty? && !@line_number_1_indexed.nil?
59
- contents.lines(chomp: true)[line_number] || ''
80
+ if has_content_and_line_number_but_no_needle?
81
+ entire_line_needle
60
82
  elsif contents.empty? || @needle.nil?
61
83
  ''
84
+ elsif !can_find_needle?
85
+ entire_line_needle
62
86
  else
63
87
  @needle
64
88
  end
65
89
  end
66
90
 
91
+ def has_content_and_line_number_but_no_needle?
92
+ @needle.nil? && !contents.empty? && @line_number_1_indexed.is_a?(Integer)
93
+ end
94
+
95
+ def can_find_needle?
96
+ !!contents.index(@needle)
97
+ end
98
+
99
+ def entire_line_needle
100
+ contents.lines(chomp: true)[line_number] || ''
101
+ end
102
+ end
103
+
104
+ # This method is stricter than Position in the sense that it doesn't
105
+ # accept invalid inputs. Makes for code that is easier to understand.
106
+ class StrictPosition
107
+ include PositionHelper
108
+
109
+ attr_reader :needle, :contents
110
+
111
+ def initialize(needle, contents, start_index)
112
+ raise ArgumentError, 'Bad start_index' unless start_index.is_a?(Integer)
113
+ raise ArgumentError, 'Bad contents' unless contents.is_a?(String)
114
+ raise ArgumentError, 'Bad needle' unless needle.is_a?(String) || !contents.index(needle, start_index)
115
+
116
+ @needle = needle
117
+ @contents = contents
118
+ @start_index = start_index
119
+ @start_row_column = nil
120
+ @end_row_column = nil
121
+ end
122
+
123
+ # 0-indexed, inclusive
124
+ def start_index
125
+ @contents.index(needle, @start_index)
126
+ end
127
+
128
+ # 0-indexed, exclusive
129
+ def end_index
130
+ start_index + needle.size
131
+ end
132
+
133
+ def start_row
134
+ start_row_column[0]
135
+ end
136
+
137
+ def start_column
138
+ start_row_column[1]
139
+ end
140
+
141
+ def end_row
142
+ end_row_column[0]
143
+ end
144
+
145
+ def end_column
146
+ end_row_column[1]
147
+ end
148
+
149
+ private
150
+
67
151
  def start_row_column
68
152
  return @start_row_column unless @start_row_column.nil?
69
153
  @start_row_column = from_index_to_row_column(contents, start_index)
@@ -2,8 +2,11 @@
2
2
 
3
3
  module ThemeCheck
4
4
  module RegexHelpers
5
- VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
5
+ LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
6
+ LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
7
+ LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
6
8
  START_OR_END_QUOTE = /(^['"])|(['"]$)/
9
+
7
10
  def matches(s, re)
8
11
  start_at = 0
9
12
  matches = []
@@ -16,7 +19,7 @@ module ThemeCheck
16
19
 
17
20
  def href_to_file_size(href)
18
21
  # asset_url (+ optional stylesheet_tag) variables
19
- if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
22
+ if href =~ /^#{LIQUID_VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
20
23
  asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
21
24
  asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
22
25
  return if asset.nil?
@@ -7,6 +7,8 @@ module ThemeCheck
7
7
  LIQUID_REGEX = /\.liquid$/i
8
8
  JSON_REGEX = /\.json$/i
9
9
 
10
+ attr_accessor :storage
11
+
10
12
  def initialize(storage)
11
13
  @storage = storage
12
14
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.2.0"
3
+ VERSION = "1.3.0"
4
4
  end
data/lib/theme_check.rb CHANGED
@@ -27,6 +27,7 @@ require_relative "theme_check/config"
27
27
  require_relative "theme_check/node"
28
28
  require_relative "theme_check/offense"
29
29
  require_relative "theme_check/printer"
30
+ require_relative "theme_check/json_printer"
30
31
  require_relative "theme_check/shopify_liquid"
31
32
  require_relative "theme_check/storage"
32
33
  require_relative "theme_check/string_helpers"
data/theme-check.gemspec CHANGED
@@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency('liquid', '>= 5.0.1')
28
- spec.add_dependency('nokogumbo')
28
+ spec.add_dependency('nokogiri', '>= 1.12')
29
29
  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.2.0
4
+ version: 1.3.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-07-15 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -25,27 +25,25 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.0.1
27
27
  - !ruby/object:Gem::Dependency
28
- name: nokogumbo
28
+ name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.12'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '1.12'
41
41
  description:
42
42
  email:
43
43
  - marcandre.cournoyer@shopify.com
44
44
  executables:
45
45
  - theme-check
46
46
  - theme-check-language-server
47
- - theme-check-language-server.bat
48
- - theme-check.bat
49
47
  extensions: []
50
48
  extra_rdoc_files: []
51
49
  files:
@@ -62,7 +60,8 @@ files:
62
60
  - README.md
63
61
  - RELEASING.md
64
62
  - Rakefile
65
- - bin/liquid-server
63
+ - bin/theme-check
64
+ - bin/theme-check-language-server
66
65
  - config/default.yml
67
66
  - config/nothing.yml
68
67
  - config/theme_app_extension.yml
@@ -78,6 +77,7 @@ files:
78
77
  - docs/api/json_check.md
79
78
  - docs/api/liquid_check.md
80
79
  - docs/checks/TEMPLATE.md.erb
80
+ - docs/checks/app_block_valid_tags.md
81
81
  - docs/checks/asset_size_app_block_css.md
82
82
  - docs/checks/asset_size_app_block_javascript.md
83
83
  - docs/checks/asset_size_css.md
@@ -100,6 +100,7 @@ files:
100
100
  - docs/checks/missing_required_template_files.md
101
101
  - docs/checks/missing_template.md
102
102
  - docs/checks/nested_snippet.md
103
+ - docs/checks/pagination_size.md
103
104
  - docs/checks/parser_blocking_javascript.md
104
105
  - docs/checks/parser_blocking_script_tag.md
105
106
  - docs/checks/remote_asset.md
@@ -119,8 +120,6 @@ files:
119
120
  - docs/preview.png
120
121
  - exe/theme-check
121
122
  - exe/theme-check-language-server
122
- - exe/theme-check-language-server.bat
123
- - exe/theme-check.bat
124
123
  - lib/theme_check.rb
125
124
  - lib/theme_check/analyzer.rb
126
125
  - lib/theme_check/asset_file.rb
@@ -128,6 +127,7 @@ files:
128
127
  - lib/theme_check/check.rb
129
128
  - lib/theme_check/checks.rb
130
129
  - lib/theme_check/checks/TEMPLATE.rb.erb
130
+ - lib/theme_check/checks/app_block_valid_tags.rb
131
131
  - lib/theme_check/checks/asset_size_app_block_css.rb
132
132
  - lib/theme_check/checks/asset_size_app_block_javascript.rb
133
133
  - lib/theme_check/checks/asset_size_css.rb
@@ -150,6 +150,7 @@ files:
150
150
  - lib/theme_check/checks/missing_required_template_files.rb
151
151
  - lib/theme_check/checks/missing_template.rb
152
152
  - lib/theme_check/checks/nested_snippet.rb
153
+ - lib/theme_check/checks/pagination_size.rb
153
154
  - lib/theme_check/checks/parser_blocking_javascript.rb
154
155
  - lib/theme_check/checks/parser_blocking_script_tag.rb
155
156
  - lib/theme_check/checks/remote_asset.rb
@@ -181,6 +182,7 @@ files:
181
182
  - lib/theme_check/json_check.rb
182
183
  - lib/theme_check/json_file.rb
183
184
  - lib/theme_check/json_helpers.rb
185
+ - lib/theme_check/json_printer.rb
184
186
  - lib/theme_check/language_server.rb
185
187
  - lib/theme_check/language_server/completion_engine.rb
186
188
  - lib/theme_check/language_server/completion_helper.rb