theme-check 1.3.0 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +3 -3
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +38 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/Gemfile +3 -0
  7. data/config/default.yml +4 -1
  8. data/data/shopify_liquid/objects.yml +1 -0
  9. data/docs/checks/deprecate_lazysizes.md +0 -3
  10. data/docs/checks/deprecated_global_app_block_type.md +65 -0
  11. data/docs/checks/template_length.md +1 -1
  12. data/docs/flamegraph.svg +18488 -0
  13. data/lib/theme_check/analyzer.rb +1 -0
  14. data/lib/theme_check/checks/default_locale.rb +3 -1
  15. data/lib/theme_check/checks/deprecate_lazysizes.rb +6 -3
  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/pagination_size.rb +33 -14
  19. data/lib/theme_check/checks/remote_asset.rb +2 -2
  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/template_length.rb +1 -1
  23. data/lib/theme_check/cli.rb +28 -5
  24. data/lib/theme_check/corrector.rb +9 -0
  25. data/lib/theme_check/file_system_storage.rb +6 -0
  26. data/lib/theme_check/in_memory_storage.rb +4 -0
  27. data/lib/theme_check/json_file.rb +11 -0
  28. data/lib/theme_check/json_printer.rb +6 -1
  29. data/lib/theme_check/language_server/constants.rb +18 -11
  30. data/lib/theme_check/language_server/document_link_engine.rb +3 -67
  31. data/lib/theme_check/language_server/document_link_provider.rb +71 -0
  32. data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
  33. data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
  34. data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
  35. data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
  36. data/lib/theme_check/language_server/handler.rb +17 -13
  37. data/lib/theme_check/language_server/server.rb +11 -13
  38. data/lib/theme_check/language_server/uri_helper.rb +37 -0
  39. data/lib/theme_check/language_server.rb +6 -0
  40. data/lib/theme_check/node.rb +120 -8
  41. data/lib/theme_check/position.rb +27 -16
  42. data/lib/theme_check/position_helper.rb +13 -15
  43. data/lib/theme_check/printer.rb +9 -5
  44. data/lib/theme_check/remote_asset_file.rb +4 -0
  45. data/lib/theme_check/theme.rb +2 -1
  46. data/lib/theme_check/version.rb +1 -1
  47. metadata +11 -2
@@ -10,12 +10,14 @@ module ThemeCheck
10
10
  @value = value
11
11
  @parent = parent
12
12
  @template = template
13
+ @tag_markup = nil
14
+ @line_number_offset = 0
13
15
  end
14
16
 
15
17
  # The original source code of the node. Doesn't contain wrapping braces.
16
18
  def markup
17
19
  if tag?
18
- @value.raw
20
+ tag_markup
19
21
  elsif @value.instance_variable_defined?(:@markup)
20
22
  @value.instance_variable_get(:@markup)
21
23
  end
@@ -64,6 +66,10 @@ module ThemeCheck
64
66
  @value.is_a?(Liquid::Tag)
65
67
  end
66
68
 
69
+ def variable?
70
+ @value.is_a?(Liquid::Variable)
71
+ end
72
+
67
73
  # A {% comment %} block node?
68
74
  def comment?
69
75
  @value.is_a?(Liquid::Comment)
@@ -92,7 +98,12 @@ module ThemeCheck
92
98
 
93
99
  # Most nodes have a line number, but it's not guaranteed.
94
100
  def line_number
95
- @value.line_number if @value.respond_to?(:line_number)
101
+ if tag? && @value.respond_to?(:line_number)
102
+ markup # initialize the line_number_offset
103
+ @value.line_number - @line_number_offset
104
+ elsif @value.respond_to?(:line_number)
105
+ @value.line_number
106
+ end
96
107
  end
97
108
 
98
109
  # The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
@@ -101,19 +112,45 @@ module ThemeCheck
101
112
  @type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
102
113
  end
103
114
 
115
+ def source
116
+ template&.source
117
+ end
118
+
119
+ WHITESPACE = /\s/
120
+
104
121
  # Is this node inside a `{% liquid ... %}` block?
105
122
  def inside_liquid_tag?
106
- if line_number
107
- template.excerpt(line_number).start_with?("{%")
123
+ # What we're doing here is starting at the start of the tag and
124
+ # backtrack on all the whitespace until we land on something. If
125
+ # that something is {% or %-, then we can safely assume that
126
+ # we're inside a full tag and not a liquid tag.
127
+ @inside_liquid_tag ||= if tag? && line_number && source
128
+ i = 1
129
+ i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
130
+ first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
131
+ first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
132
+ else
133
+ false
134
+ end
135
+ end
136
+
137
+ # Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-
138
+ def whitespace_trimmed_start?
139
+ @whitespace_trimmed_start ||= if line_number && source && !inside_liquid_tag?
140
+ i = 1
141
+ i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
142
+ source[start_index - i] == "-"
108
143
  else
109
144
  false
110
145
  end
111
146
  end
112
147
 
113
- # Is this node inside a `{%- ... -%}`
114
- def whitespace_trimmed?
115
- if line_number
116
- template.excerpt(line_number).start_with?("{%-")
148
+ # Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}
149
+ def whitespace_trimmed_end?
150
+ @whitespace_trimmed_end ||= if line_number && source && !inside_liquid_tag?
151
+ i = 0
152
+ i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
153
+ source[end_index + i] == "-"
117
154
  else
118
155
  false
119
156
  end
@@ -132,6 +169,24 @@ module ThemeCheck
132
169
  )
133
170
  end
134
171
 
172
+ def start_token
173
+ return "" if inside_liquid_tag?
174
+ output = ""
175
+ output += "{{" if variable?
176
+ output += "{%" if tag?
177
+ output += "-" if whitespace_trimmed_start?
178
+ output
179
+ end
180
+
181
+ def end_token
182
+ return "" if inside_liquid_tag?
183
+ output = ""
184
+ output += "-" if whitespace_trimmed_end?
185
+ output += "}}" if variable?
186
+ output += "%}" if tag?
187
+ output
188
+ end
189
+
135
190
  def start_index
136
191
  position.start_index
137
192
  end
@@ -139,5 +194,62 @@ module ThemeCheck
139
194
  def end_index
140
195
  position.end_index
141
196
  end
197
+
198
+ private
199
+
200
+ # Here we're hacking around a glorious bug in Liquid that makes it so the
201
+ # line_number and markup of a tag is wrong if there's whitespace
202
+ # between the tag_name and the markup of the tag.
203
+ #
204
+ # {%
205
+ # render
206
+ # 'foo'
207
+ # %}
208
+ #
209
+ # Returns a raw value of "render 'foo'\n".
210
+ # The "\n " between render and 'foo' got replaced by a single space.
211
+ #
212
+ # And the line number is the one of 'foo'\n%}. Yay!
213
+ #
214
+ # This breaks any kind of position logic we have since that string
215
+ # does not exist in the template.
216
+ def tag_markup
217
+ return @value.raw if @value.instance_variable_get('@markup').empty?
218
+ return @tag_markup if @tag_markup
219
+
220
+ l = 1
221
+ scanner = StringScanner.new(source)
222
+ scanner.scan_until(/\n/) while l < @value.line_number && (l += 1)
223
+ start = scanner.charpos
224
+
225
+ tag_markup = @value.instance_variable_get('@markup')
226
+
227
+ # See https://github.com/Shopify/theme-check/pull/423/files#r701936559 for a detailed explanation
228
+ # of why we're doing the check below.
229
+ #
230
+ # TL;DR it's because line_numbers are not enough to accurately
231
+ # determine the position of the raw markup and because that
232
+ # markup could be present on the same line outside of a Tag. e.g.
233
+ #
234
+ # uhoh {% if uhoh %}
235
+ if (match = /#{@value.tag_name} +#{Regexp.escape(tag_markup)}/.match(source, start))
236
+ return @tag_markup = match[0]
237
+ end
238
+
239
+ # find the markup
240
+ markup_start = source.index(tag_markup, start)
241
+ markup_end = markup_start + tag_markup.size
242
+
243
+ # go back until you find the tag_name
244
+ tag_start = markup_start
245
+ tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
246
+ tag_start -= @value.tag_name.size
247
+
248
+ # keep track of the error in line_number
249
+ @line_number_offset = source[tag_start...markup_start].count("\n")
250
+
251
+ # return the real raw content
252
+ @tag_markup = source[tag_start...markup_end]
253
+ end
142
254
  end
143
255
  end
@@ -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
@@ -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
@@ -7,7 +7,8 @@ module ThemeCheck
7
7
  LIQUID_REGEX = /\.liquid$/i
8
8
  JSON_REGEX = /\.json$/i
9
9
 
10
- attr_accessor :storage
10
+ attr_reader :storage
11
+ attr_writer :default_locale_json
11
12
 
12
13
  def initialize(storage)
13
14
  @storage = storage
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.3.0"
3
+ VERSION = "1.5.2"
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: 1.3.0
4
+ version: 1.5.2
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-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -90,6 +90,7 @@ files:
90
90
  - docs/checks/deprecate_bgsizes.md
91
91
  - docs/checks/deprecate_lazysizes.md
92
92
  - docs/checks/deprecated_filter.md
93
+ - docs/checks/deprecated_global_app_block_type.md
93
94
  - docs/checks/html_parsing_error.md
94
95
  - docs/checks/img_lazy_loading.md
95
96
  - docs/checks/img_width_and_height.md
@@ -117,6 +118,7 @@ files:
117
118
  - docs/checks/valid_html_translation.md
118
119
  - docs/checks/valid_json.md
119
120
  - docs/checks/valid_schema.md
121
+ - docs/flamegraph.svg
120
122
  - docs/preview.png
121
123
  - exe/theme-check
122
124
  - exe/theme-check-language-server
@@ -140,6 +142,7 @@ files:
140
142
  - lib/theme_check/checks/deprecate_bgsizes.rb
141
143
  - lib/theme_check/checks/deprecate_lazysizes.rb
142
144
  - lib/theme_check/checks/deprecated_filter.rb
145
+ - lib/theme_check/checks/deprecated_global_app_block_type.rb
143
146
  - lib/theme_check/checks/html_parsing_error.rb
144
147
  - lib/theme_check/checks/img_lazy_loading.rb
145
148
  - lib/theme_check/checks/img_width_and_height.rb
@@ -194,10 +197,16 @@ files:
194
197
  - lib/theme_check/language_server/constants.rb
195
198
  - lib/theme_check/language_server/diagnostics_tracker.rb
196
199
  - lib/theme_check/language_server/document_link_engine.rb
200
+ - lib/theme_check/language_server/document_link_provider.rb
201
+ - lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb
202
+ - lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb
203
+ - lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb
204
+ - lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb
197
205
  - lib/theme_check/language_server/handler.rb
198
206
  - lib/theme_check/language_server/protocol.rb
199
207
  - lib/theme_check/language_server/server.rb
200
208
  - lib/theme_check/language_server/tokens.rb
209
+ - lib/theme_check/language_server/uri_helper.rb
201
210
  - lib/theme_check/language_server/variable_lookup_finder.rb
202
211
  - lib/theme_check/liquid_check.rb
203
212
  - lib/theme_check/locale_diff.rb