theme-check 0.7.2 → 0.8.3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +1 -0
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +33 -0
  5. data/RELEASING.md +5 -3
  6. data/config/default.yml +1 -1
  7. data/data/shopify_liquid/tags.yml +3 -0
  8. data/data/shopify_translation_keys.yml +1 -0
  9. data/dev.yml +1 -1
  10. data/docs/checks/nested_snippet.md +1 -1
  11. data/docs/checks/space_inside_braces.md +28 -0
  12. data/exe/theme-check +1 -1
  13. data/lib/theme_check.rb +5 -0
  14. data/lib/theme_check/analyzer.rb +19 -9
  15. data/lib/theme_check/bug.rb +20 -0
  16. data/lib/theme_check/check.rb +5 -1
  17. data/lib/theme_check/checks.rb +39 -8
  18. data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
  19. data/lib/theme_check/checks/nested_snippet.rb +1 -1
  20. data/lib/theme_check/checks/space_inside_braces.rb +8 -2
  21. data/lib/theme_check/cli.rb +99 -64
  22. data/lib/theme_check/config.rb +6 -2
  23. data/lib/theme_check/disabled_check.rb +39 -0
  24. data/lib/theme_check/disabled_checks.rb +20 -32
  25. data/lib/theme_check/exceptions.rb +32 -0
  26. data/lib/theme_check/json_file.rb +5 -1
  27. data/lib/theme_check/language_server.rb +1 -1
  28. data/lib/theme_check/language_server/completion_engine.rb +1 -1
  29. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -8
  30. data/lib/theme_check/language_server/constants.rb +5 -1
  31. data/lib/theme_check/language_server/document_link_engine.rb +2 -2
  32. data/lib/theme_check/language_server/handler.rb +32 -24
  33. data/lib/theme_check/language_server/variable_lookup_finder.rb +295 -0
  34. data/lib/theme_check/node.rb +12 -0
  35. data/lib/theme_check/offense.rb +14 -48
  36. data/lib/theme_check/parsing_helpers.rb +1 -1
  37. data/lib/theme_check/position.rb +77 -0
  38. data/lib/theme_check/position_helper.rb +37 -0
  39. data/lib/theme_check/remote_asset_file.rb +3 -0
  40. data/lib/theme_check/shopify_liquid/tag.rb +13 -0
  41. data/lib/theme_check/version.rb +1 -1
  42. data/lib/theme_check/visitor.rb +9 -10
  43. data/theme-check.gemspec +2 -0
  44. metadata +10 -5
  45. data/lib/theme_check/language_server/position_helper.rb +0 -27
@@ -125,5 +125,17 @@ module ThemeCheck
125
125
  start = template.full_line(line_number).index(markup)
126
126
  [start, start + markup.length - 1]
127
127
  end
128
+
129
+ def position
130
+ @position ||= Position.new(markup, template&.source, line_number)
131
+ end
132
+
133
+ def start_index
134
+ position.start_index
135
+ end
136
+
137
+ def end_index
138
+ position.end_index
139
+ end
128
140
  end
129
141
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- Position = Struct.new(:line, :column)
4
-
5
3
  class Offense
6
4
  MAX_SOURCE_EXCERPT_SIZE = 120
7
5
 
@@ -20,6 +18,7 @@ module ThemeCheck
20
18
  end
21
19
 
22
20
  @node = node
21
+ @template = nil
23
22
  if node
24
23
  @template = node.template
25
24
  elsif template
@@ -40,8 +39,7 @@ module ThemeCheck
40
39
  @node.line_number
41
40
  end
42
41
 
43
- @start_position = nil
44
- @end_position = nil
42
+ @position = Position.new(@markup, @template&.source, @line_number)
45
43
  end
46
44
 
47
45
  def source_excerpt
@@ -56,20 +54,28 @@ module ThemeCheck
56
54
  end
57
55
  end
58
56
 
57
+ def start_index
58
+ @position.start_index
59
+ end
60
+
59
61
  def start_line
60
- start_position.line
62
+ @position.start_row
61
63
  end
62
64
 
63
65
  def start_column
64
- start_position.column
66
+ @position.start_column
67
+ end
68
+
69
+ def end_index
70
+ @position.end_index
65
71
  end
66
72
 
67
73
  def end_line
68
- end_position.line
74
+ @position.end_row
69
75
  end
70
76
 
71
77
  def end_column
72
- end_position.column
78
+ @position.end_column
73
79
  end
74
80
 
75
81
  def code_name
@@ -115,45 +121,5 @@ module ThemeCheck
115
121
  message
116
122
  end
117
123
  end
118
-
119
- private
120
-
121
- def full_line(line)
122
- # Liquid::Template is 1-indexed.
123
- template.full_line(line + 1)
124
- end
125
-
126
- def lines_of_content
127
- @lines ||= markup.lines.map { |x| x.sub(/\n$/, '') }
128
- end
129
-
130
- # 0-indexed, inclusive
131
- def start_position
132
- return @start_position if @start_position
133
- return @start_position = Position.new(0, 0) unless line_number && markup
134
-
135
- position = Position.new
136
- position.line = line_number - 1
137
- position.column = full_line(position.line).index(lines_of_content.first) || 0
138
-
139
- @start_position = position
140
- end
141
-
142
- # 0-indexed, exclusive. It's the line + col that are exclusive.
143
- # This is why it doesn't make sense to calculate them separately.
144
- def end_position
145
- return @end_position if @end_position
146
- return @end_position = Position.new(0, 0) unless line_number && markup
147
-
148
- position = Position.new
149
- position.line = start_line + lines_of_content.size - 1
150
- position.column = if start_line == position.line
151
- start_column + markup.size
152
- else
153
- lines_of_content.last.size
154
- end
155
-
156
- @end_position = position
157
- end
158
124
  end
159
125
  end
@@ -6,7 +6,7 @@ module ThemeCheck
6
6
  scanner = StringScanner.new(markup)
7
7
 
8
8
  while scanner.scan(/.*?("|')/)
9
- yield scanner.matched[..-2]
9
+ yield scanner.matched[0..-2]
10
10
  # Skip to the end of the string
11
11
  scanner.skip_until(scanner.matched[-1] == "'" ? /[^\\]'/ : /[^\\]"/)
12
12
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ class Position
5
+ include PositionHelper
6
+
7
+ def initialize(needle, contents, line_number_1_indexed)
8
+ @needle = needle
9
+ @contents = contents
10
+ @line_number_1_indexed = line_number_1_indexed
11
+ @start_row_column = nil
12
+ @end_row_column = nil
13
+ end
14
+
15
+ def start_line_index
16
+ from_row_column_to_index(contents, line_number, 0)
17
+ end
18
+
19
+ # 0-indexed, inclusive
20
+ def start_index
21
+ contents.index(needle, start_line_index) || start_line_index
22
+ end
23
+
24
+ # 0-indexed, exclusive
25
+ def end_index
26
+ start_index + needle.size
27
+ end
28
+
29
+ def start_row
30
+ start_row_column[0]
31
+ end
32
+
33
+ def start_column
34
+ start_row_column[1]
35
+ end
36
+
37
+ def end_row
38
+ end_row_column[0]
39
+ end
40
+
41
+ def end_column
42
+ end_row_column[1]
43
+ end
44
+
45
+ private
46
+
47
+ def contents
48
+ return '' unless @contents.is_a?(String) && !@contents.empty?
49
+ @contents
50
+ end
51
+
52
+ def line_number
53
+ return 0 if @line_number_1_indexed.nil?
54
+ bounded(0, @line_number_1_indexed - 1, contents.lines.size - 1)
55
+ end
56
+
57
+ def needle
58
+ if @needle.nil? && !contents.empty? && !@line_number_1_indexed.nil?
59
+ contents.lines(chomp: true)[line_number] || ''
60
+ elsif contents.empty? || @needle.nil?
61
+ ''
62
+ else
63
+ @needle
64
+ end
65
+ end
66
+
67
+ def start_row_column
68
+ return @start_row_column unless @start_row_column.nil?
69
+ @start_row_column = from_index_to_row_column(contents, start_index)
70
+ end
71
+
72
+ def end_row_column
73
+ return @end_row_column unless @end_row_column.nil?
74
+ @end_row_column = from_index_to_row_column(contents, end_index)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # Note: Everything is 0-indexed here.
3
+
4
+ module ThemeCheck
5
+ module PositionHelper
6
+ def from_row_column_to_index(content, row, col)
7
+ return 0 unless content.is_a?(String) && !content.empty?
8
+ return 0 unless row.is_a?(Integer) && col.is_a?(Integer)
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
21
+ end
22
+
23
+ def from_index_to_row_column(content, index)
24
+ return [0, 0] unless content.is_a?(String) && !content.empty?
25
+ return [0, 0] unless index.is_a?(Integer)
26
+ 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
30
+ [row, col]
31
+ end
32
+
33
+ def bounded(a, x, b)
34
+ [a, [x, b].min].max
35
+ end
36
+ end
37
+ end
@@ -35,6 +35,9 @@ module ThemeCheck
35
35
  end
36
36
 
37
37
  @content = res.body
38
+
39
+ rescue OpenSSL::SSL::SSLError, Zlib::StreamError, *NET_HTTP_EXCEPTIONS
40
+ @contents = ''
38
41
  end
39
42
 
40
43
  def gzipped_size
@@ -8,6 +8,19 @@ module ThemeCheck
8
8
 
9
9
  def labels
10
10
  @tags ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/tags.yml"))
11
+ .to_set
12
+ end
13
+
14
+ def tag_regex(tag)
15
+ return unless labels.include?(tag)
16
+ @tag_regexes ||= {}
17
+ @tag_regexes[tag] ||= /\A#{Liquid::TagStart}-?\s*#{tag}/m
18
+ end
19
+
20
+ def liquid_tag_regex(tag)
21
+ return unless labels.include?(tag)
22
+ @tag_liquid_regexes ||= {}
23
+ @tag_liquid_regexes[tag] ||= /^\s*#{tag}/m
11
24
  end
12
25
  end
13
26
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.7.2"
3
+ VERSION = "0.8.3"
4
4
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
3
  class Visitor
4
+ attr_reader :checks
5
+
4
6
  def initialize(checks)
5
7
  @checks = checks
6
8
  end
@@ -8,6 +10,7 @@ module ThemeCheck
8
10
  def visit_template(template)
9
11
  @disabled_checks = DisabledChecks.new
10
12
  visit(Node.new(template.root, nil, template))
13
+ remove_disabled_offenses
11
14
  rescue Liquid::Error => exception
12
15
  exception.template_name = template.name
13
16
  call_checks(:on_error, exception)
@@ -29,20 +32,16 @@ module ThemeCheck
29
32
  @disabled_checks.update(node) if node.comment?
30
33
  end
31
34
 
32
- def visit_children(node)
33
- node.children.each { |child| visit(child) }
34
- end
35
-
36
35
  def call_checks(method, *args)
37
36
  checks.call(method, *args)
38
37
  end
39
38
 
40
- def checks
41
- return @checks unless @disabled_checks.any?
42
-
43
- return @checks.always_enabled if @disabled_checks.all_disabled?
44
-
45
- @checks.except_for(@disabled_checks)
39
+ def remove_disabled_offenses
40
+ checks.disableable.each do |check|
41
+ check.offenses.reject! do |offense|
42
+ @disabled_checks.disabled?(offense.code_name, offense.start_index)
43
+ end
44
+ end
46
45
  end
47
46
  end
48
47
  end
data/theme-check.gemspec CHANGED
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/Shopify/theme-check"
14
14
  spec.license = "MIT"
15
15
 
16
+ spec.required_ruby_version = ">= 2.6"
17
+
16
18
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
17
19
 
18
20
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
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: 0.7.2
4
+ version: 0.8.3
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-04-12 00:00:00.000000000 Z
11
+ date: 2021-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -104,6 +104,7 @@ files:
104
104
  - lib/theme_check.rb
105
105
  - lib/theme_check/analyzer.rb
106
106
  - lib/theme_check/asset_file.rb
107
+ - lib/theme_check/bug.rb
107
108
  - lib/theme_check/check.rb
108
109
  - lib/theme_check/checks.rb
109
110
  - lib/theme_check/checks/asset_size_css.rb
@@ -138,7 +139,9 @@ files:
138
139
  - lib/theme_check/cli.rb
139
140
  - lib/theme_check/config.rb
140
141
  - lib/theme_check/corrector.rb
142
+ - lib/theme_check/disabled_check.rb
141
143
  - lib/theme_check/disabled_checks.rb
144
+ - lib/theme_check/exceptions.rb
142
145
  - lib/theme_check/file_system_storage.rb
143
146
  - lib/theme_check/in_memory_storage.rb
144
147
  - lib/theme_check/json_check.rb
@@ -155,16 +158,18 @@ files:
155
158
  - lib/theme_check/language_server/constants.rb
156
159
  - lib/theme_check/language_server/document_link_engine.rb
157
160
  - lib/theme_check/language_server/handler.rb
158
- - lib/theme_check/language_server/position_helper.rb
159
161
  - lib/theme_check/language_server/protocol.rb
160
162
  - lib/theme_check/language_server/server.rb
161
163
  - lib/theme_check/language_server/tokens.rb
164
+ - lib/theme_check/language_server/variable_lookup_finder.rb
162
165
  - lib/theme_check/liquid_check.rb
163
166
  - lib/theme_check/locale_diff.rb
164
167
  - lib/theme_check/node.rb
165
168
  - lib/theme_check/offense.rb
166
169
  - lib/theme_check/packager.rb
167
170
  - lib/theme_check/parsing_helpers.rb
171
+ - lib/theme_check/position.rb
172
+ - lib/theme_check/position_helper.rb
168
173
  - lib/theme_check/printer.rb
169
174
  - lib/theme_check/regex_helpers.rb
170
175
  - lib/theme_check/releaser.rb
@@ -196,14 +201,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
196
201
  requirements:
197
202
  - - ">="
198
203
  - !ruby/object:Gem::Version
199
- version: '0'
204
+ version: '2.6'
200
205
  required_rubygems_version: !ruby/object:Gem::Requirement
201
206
  requirements:
202
207
  - - ">="
203
208
  - !ruby/object:Gem::Version
204
209
  version: '0'
205
210
  requirements: []
206
- rubygems_version: 3.0.3
211
+ rubygems_version: 3.2.17
207
212
  signing_key:
208
213
  specification_version: 4
209
214
  summary: A Shopify Theme Linter
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
- # Note: Everything is 0-indexed here.
3
-
4
- module ThemeCheck
5
- module LanguageServer
6
- module PositionHelper
7
- def from_line_column_to_index(content, row, col)
8
- i = 0
9
- result = 0
10
- lines = content.lines
11
- while i < row
12
- result += lines[i].size
13
- i += 1
14
- end
15
- result += col
16
- result
17
- end
18
-
19
- def from_index_to_line_column(content, index)
20
- lines = content[0..index].lines
21
- row = lines.size - 1
22
- col = lines.last.size - 1
23
- [row, col]
24
- end
25
- end
26
- end
27
- end