theme-check 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60f0635001a9f808d12b55e0328536911bde9ea36160bcf34a63f10114401d18
4
- data.tar.gz: 78365fe98014904e058191934663b75d24701db8a2a2585edac2557192cec5d5
3
+ metadata.gz: 960f944628496ea317205e34678a78ec1e560dbfb48f40cfa33be2d82ebe4770
4
+ data.tar.gz: 66e2abe15609c6e5a40d13d05aa0daedf9fbd783e31226bbfb0c6961c1d89ad6
5
5
  SHA512:
6
- metadata.gz: 0520e083dac843ed6273ba17e0cb44ee0e9e2c412f85a81b176da8b0912a583ab262c0c25b0b580df9c53cabefca305dce468559efa6df786ff37b7583e7ecc4
7
- data.tar.gz: 58b7483ad5935de781eddf850a73c88f04aadbae394416f901fa38e173b44b4af3717947fc72cc009c899866ce1d27cc3f6597d5de7dd7ea1ef6984393d71ff4
6
+ metadata.gz: d920362e7729fc739c0a0202e00a605025db3d80c2fdfac5be9d6d9ea6c74bc4922baa82e093851432417863106c14964f6fdc76f66e55fd7947f33887b989c1
7
+ data.tar.gz: 26eb4ba427e8380ece15160c2c27cd052802ec233bb61a4a17e36dcf7f008cc7bc569b71649c525c6d57a7d79654469af65ec664726665584806928cf7258483
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
1
 
2
+ v1.5.2 / 2021-09-09
3
+ ===================
4
+
5
+ * Handle invalid URIs in RemoteAssetFile ([#418](https://github.com/shopify/theme-check/issues/418), [#438](https://github.com/shopify/theme-check/issues/438))
6
+ * Fix `Bad start_index` error in SpaceInsideBraces ([#423](https://github.com/shopify/theme-check/issues/423))
7
+ * Autocorrect missing directories ([#389](https://github.com/shopify/theme-check/issues/389))
8
+
2
9
  v1.5.1 / 2021-09-08
3
10
  ===================
4
11
 
@@ -13,7 +13,7 @@ module ThemeCheck
13
13
  end
14
14
 
15
15
  def on_tag(node)
16
- if !node.inside_liquid_tag?
16
+ if node.inside_liquid_tag?
17
17
  reset_consecutive_statements
18
18
  # Ignore comments
19
19
  elsif !node.comment?
@@ -18,7 +18,9 @@ module ThemeCheck
18
18
  private
19
19
 
20
20
  def add_missing_directories_offense(directory)
21
- add_offense("Theme is missing '#{directory}' directory")
21
+ add_offense("Theme is missing '#{directory}' directory") do |corrector|
22
+ corrector.mkdir(@theme, directory)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -15,52 +15,57 @@ module ThemeCheck
15
15
  return if :assign == node.type_name
16
16
 
17
17
  outside_of_strings(node.markup) do |chunk, chunk_start|
18
- chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=) +/) do |_match|
18
+ chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=)( +)/) do |_match|
19
19
  add_offense(
20
20
  "Too many spaces after '#{Regexp.last_match(1)}'",
21
21
  node: node,
22
- markup: Regexp.last_match(0),
23
- node_markup_offset: chunk_start + Regexp.last_match.begin(0)
22
+ markup: Regexp.last_match(2),
23
+ node_markup_offset: chunk_start + Regexp.last_match.begin(2)
24
24
  )
25
25
  end
26
26
  chunk.scan(/([,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z)/) do |_match|
27
27
  add_offense(
28
28
  "Space missing after '#{Regexp.last_match(1)}'",
29
29
  node: node,
30
- markup: Regexp.last_match(0),
30
+ markup: Regexp.last_match(1),
31
31
  node_markup_offset: chunk_start + Regexp.last_match.begin(0),
32
32
  )
33
33
  end
34
- chunk.scan(/ (\||==|<>|<=|>=|<|>|!=)+/) do |_match|
34
+ chunk.scan(/( +)(\||==|<>|<=|>=|<|>|!=)+/) do |_match|
35
35
  add_offense(
36
- "Too many spaces before '#{Regexp.last_match(1)}'",
36
+ "Too many spaces before '#{Regexp.last_match(2)}'",
37
37
  node: node,
38
- markup: Regexp.last_match(0),
39
- node_markup_offset: chunk_start + Regexp.last_match.begin(0)
38
+ markup: Regexp.last_match(1),
39
+ node_markup_offset: chunk_start + Regexp.last_match.begin(1)
40
40
  )
41
41
  end
42
42
  chunk.scan(/(\A|\S)(?<match>\||==|<>|<=|>=|<|\b>|!=)/) do |_match|
43
43
  add_offense(
44
44
  "Space missing before '#{Regexp.last_match(1)}'",
45
45
  node: node,
46
- markup: Regexp.last_match(0),
47
- node_markup_offset: chunk_start + Regexp.last_match.begin(0)
46
+ markup: Regexp.last_match(:match),
47
+ node_markup_offset: chunk_start + Regexp.last_match.begin(:match)
48
48
  )
49
49
  end
50
50
  end
51
51
  end
52
52
 
53
53
  def on_tag(node)
54
- if node.inside_liquid_tag?
55
- markup = if node.whitespace_trimmed?
56
- "-%}"
57
- else
58
- "%}"
59
- end
54
+ unless node.inside_liquid_tag?
60
55
  if node.markup[-1] != " " && node.markup[-1] != "\n"
61
- add_offense("Space missing before '#{markup}'", node: node, markup: node.markup[-1] + markup)
56
+ add_offense(
57
+ "Space missing before '#{node.end_token}'",
58
+ node: node,
59
+ markup: node.markup[-1],
60
+ node_markup_offset: node.markup.size - 1,
61
+ )
62
62
  elsif node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
63
- add_offense("Too many spaces before '#{markup}'", node: node, markup: Regexp.last_match(2) + markup)
63
+ add_offense(
64
+ "Too many spaces before '#{node.end_token}'",
65
+ node: node,
66
+ markup: Regexp.last_match(2),
67
+ node_markup_offset: node.markup.size - Regexp.last_match(2).size
68
+ )
64
69
  end
65
70
  end
66
71
  @ignore = true
@@ -73,22 +78,40 @@ module ThemeCheck
73
78
  def on_variable(node)
74
79
  return if @ignore || node.markup.empty?
75
80
  if node.markup[0] != " "
76
- add_offense("Space missing after '{{'", node: node) do |corrector|
81
+ add_offense(
82
+ "Space missing after '#{node.start_token}'",
83
+ node: node,
84
+ markup: node.markup[0]
85
+ ) do |corrector|
77
86
  corrector.insert_before(node, " ")
78
87
  end
79
88
  end
80
89
  if node.markup[-1] != " " && node.markup[-1] != "\n"
81
- add_offense("Space missing before '}}'", node: node) do |corrector|
90
+ add_offense(
91
+ "Space missing before '#{node.end_token}'",
92
+ node: node,
93
+ markup: node.markup[-1],
94
+ node_markup_offset: node.markup.size - 1,
95
+ ) do |corrector|
82
96
  corrector.insert_after(node, " ")
83
97
  end
84
98
  end
85
- if node.markup[0] == " " && node.markup[1] == " "
86
- add_offense("Too many spaces after '{{'", node: node) do |corrector|
99
+ if node.markup =~ /\A( +)/m
100
+ add_offense(
101
+ "Too many spaces after '#{node.start_token}'",
102
+ node: node,
103
+ markup: Regexp.last_match(1),
104
+ ) do |corrector|
87
105
  corrector.replace(node, " #{node.markup.lstrip}")
88
106
  end
89
107
  end
90
- if node.markup[-1] == " " && node.markup[-2] == " "
91
- add_offense("Too many spaces before '}}'", node: node) do |corrector|
108
+ if node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
109
+ add_offense(
110
+ "Too many spaces before '#{node.end_token}'",
111
+ node: node,
112
+ markup: Regexp.last_match(2),
113
+ node_markup_offset: node.markup.size - Regexp.last_match(2).size
114
+ ) do |corrector|
92
115
  corrector.replace(node, "#{node.markup.rstrip} ")
93
116
  end
94
117
  end
@@ -36,5 +36,9 @@ module ThemeCheck
36
36
  theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
37
37
  theme.default_locale_json.update_contents('{}')
38
38
  end
39
+
40
+ def mkdir(theme, relative_path)
41
+ theme.storage.mkdir(relative_path)
42
+ end
39
43
  end
40
44
  end
@@ -26,6 +26,12 @@ module ThemeCheck
26
26
  file(relative_path).write(content)
27
27
  end
28
28
 
29
+ def mkdir(relative_path)
30
+ reset_memoizers unless file_exists?(relative_path)
31
+
32
+ file(relative_path).mkpath unless file(relative_path).directory?
33
+ end
34
+
29
35
  def files
30
36
  @file_array ||= glob("**/*")
31
37
  .map { |path| path.relative_path_from(@root).to_s }
@@ -23,6 +23,10 @@ module ThemeCheck
23
23
  @files[relative_path] = content
24
24
  end
25
25
 
26
+ def mkdir(relative_path)
27
+ @files[relative_path] = nil
28
+ end
29
+
26
30
  def files
27
31
  @files.keys
28
32
  end
@@ -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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.5.1"
3
+ VERSION = "1.5.2"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theme-check
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-André Cournoyer