theme-check 0.7.3 → 0.9.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +4 -0
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +43 -0
  5. data/CONTRIBUTING.md +2 -1
  6. data/README.md +4 -1
  7. data/RELEASING.md +5 -3
  8. data/config/default.yml +38 -1
  9. data/data/shopify_liquid/tags.yml +3 -0
  10. data/data/shopify_translation_keys.yml +1 -0
  11. data/dev.yml +1 -1
  12. data/docs/checks/content_for_header_modification.md +42 -0
  13. data/docs/checks/nested_snippet.md +1 -1
  14. data/docs/checks/parser_blocking_script_tag.md +53 -0
  15. data/docs/checks/space_inside_braces.md +28 -0
  16. data/exe/theme-check-language-server +1 -2
  17. data/lib/theme_check.rb +13 -1
  18. data/lib/theme_check/analyzer.rb +79 -13
  19. data/lib/theme_check/bug.rb +20 -0
  20. data/lib/theme_check/check.rb +36 -7
  21. data/lib/theme_check/checks.rb +47 -8
  22. data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
  23. data/lib/theme_check/checks/img_width_and_height.rb +18 -49
  24. data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
  25. data/lib/theme_check/checks/missing_template.rb +1 -0
  26. data/lib/theme_check/checks/nested_snippet.rb +1 -1
  27. data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
  28. data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
  29. data/lib/theme_check/checks/space_inside_braces.rb +8 -2
  30. data/lib/theme_check/checks/template_length.rb +3 -0
  31. data/lib/theme_check/checks/valid_html_translation.rb +1 -0
  32. data/lib/theme_check/cli.rb +1 -1
  33. data/lib/theme_check/config.rb +8 -2
  34. data/lib/theme_check/disabled_check.rb +41 -0
  35. data/lib/theme_check/disabled_checks.rb +33 -29
  36. data/lib/theme_check/exceptions.rb +32 -0
  37. data/lib/theme_check/html_check.rb +7 -0
  38. data/lib/theme_check/html_node.rb +52 -0
  39. data/lib/theme_check/html_visitor.rb +36 -0
  40. data/lib/theme_check/json_file.rb +13 -1
  41. data/lib/theme_check/language_server.rb +2 -1
  42. data/lib/theme_check/language_server/completion_engine.rb +1 -1
  43. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
  44. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -8
  45. data/lib/theme_check/language_server/constants.rb +5 -1
  46. data/lib/theme_check/language_server/diagnostics_tracker.rb +64 -0
  47. data/lib/theme_check/language_server/document_link_engine.rb +2 -2
  48. data/lib/theme_check/language_server/handler.rb +63 -50
  49. data/lib/theme_check/language_server/server.rb +1 -1
  50. data/lib/theme_check/language_server/variable_lookup_finder.rb +295 -0
  51. data/lib/theme_check/liquid_check.rb +0 -4
  52. data/lib/theme_check/node.rb +12 -0
  53. data/lib/theme_check/offense.rb +30 -46
  54. data/lib/theme_check/parsing_helpers.rb +1 -1
  55. data/lib/theme_check/position.rb +77 -0
  56. data/lib/theme_check/position_helper.rb +37 -0
  57. data/lib/theme_check/remote_asset_file.rb +3 -0
  58. data/lib/theme_check/shopify_liquid/tag.rb +13 -0
  59. data/lib/theme_check/template.rb +8 -0
  60. data/lib/theme_check/theme.rb +7 -2
  61. data/lib/theme_check/version.rb +1 -1
  62. data/lib/theme_check/visitor.rb +4 -14
  63. data/theme-check.gemspec +2 -0
  64. metadata +18 -5
  65. data/lib/theme_check/language_server/position_helper.rb +0 -27
@@ -16,9 +16,5 @@ module ThemeCheck
16
16
  ATTR = /[a-z0-9-]+/i
17
17
  HTML_ATTRIBUTE = /#{ATTR}(?:=#{QUOTED_LIQUID_ATTRIBUTE})?/omix
18
18
  HTML_ATTRIBUTES = /(?:#{HTML_ATTRIBUTE}|\s)*/omix
19
-
20
- def add_offense(message, node: nil, template: node&.template, markup: nil, line_number: nil, &block)
21
- offenses << Offense.new(check: self, message: message, template: template, node: node, markup: markup, line_number: line_number, correction: block)
22
- end
23
19
  end
24
20
  end
@@ -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
@@ -108,52 +114,30 @@ module ThemeCheck
108
114
  end
109
115
  end
110
116
 
111
- def to_s
112
- if template
113
- "#{message} at #{location}"
114
- else
115
- message
116
- end
117
- end
118
-
119
- private
120
-
121
- def full_line(line)
122
- # Liquid::Template is 1-indexed.
123
- template.full_line(line + 1)
117
+ def whole_theme?
118
+ check.whole_theme?
124
119
  end
125
120
 
126
- def lines_of_content
127
- @lines ||= markup.lines.map { |x| x.sub(/\n$/, '') }
121
+ def single_file?
122
+ check.single_file?
128
123
  end
129
124
 
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
125
+ def ==(other)
126
+ other.is_a?(Offense) &&
127
+ check == other.check &&
128
+ message == other.message &&
129
+ template == other.template &&
130
+ node == other.node &&
131
+ markup == other.markup &&
132
+ line_number == other.line_number
140
133
  end
141
134
 
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
135
+ def to_s
136
+ if template
137
+ "#{message} at #{location}"
152
138
  else
153
- lines_of_content.last.size
139
+ message
154
140
  end
155
-
156
- @end_position = position
157
141
  end
158
142
  end
159
143
  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
@@ -32,6 +32,14 @@ module ThemeCheck
32
32
  relative_path.sub_ext('').to_s
33
33
  end
34
34
 
35
+ def json?
36
+ false
37
+ end
38
+
39
+ def liquid?
40
+ true
41
+ end
42
+
35
43
  def template?
36
44
  name.start_with?('templates')
37
45
  end
@@ -52,8 +52,13 @@ module ThemeCheck
52
52
  @all ||= json + liquid + assets
53
53
  end
54
54
 
55
- def [](name)
56
- all.find { |t| t.name == name }
55
+ def [](name_or_relative_path)
56
+ case name_or_relative_path
57
+ when Pathname
58
+ all.find { |t| t.relative_path == name_or_relative_path }
59
+ else
60
+ all.find { |t| t.name == name_or_relative_path }
61
+ end
57
62
  end
58
63
 
59
64
  def templates
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "0.7.3"
3
+ VERSION = "0.9.0"
4
4
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
3
  class Visitor
4
- def initialize(checks)
4
+ attr_reader :checks
5
+
6
+ def initialize(checks, disabled_checks)
5
7
  @checks = checks
8
+ @disabled_checks = disabled_checks
6
9
  end
7
10
 
8
11
  def visit_template(template)
9
- @disabled_checks = DisabledChecks.new
10
12
  visit(Node.new(template.root, nil, template))
11
13
  rescue Liquid::Error => exception
12
14
  exception.template_name = template.name
@@ -29,20 +31,8 @@ module ThemeCheck
29
31
  @disabled_checks.update(node) if node.comment?
30
32
  end
31
33
 
32
- def visit_children(node)
33
- node.children.each { |child| visit(child) }
34
- end
35
-
36
34
  def call_checks(method, *args)
37
35
  checks.call(method, *args)
38
36
  end
39
-
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)
46
- end
47
37
  end
48
38
  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.3
4
+ version: 0.9.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-04-13 00:00:00.000000000 Z
11
+ date: 2021-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -72,6 +72,7 @@ files:
72
72
  - docs/checks/CHECK_DOCS_TEMPLATE.md
73
73
  - docs/checks/asset_size_css.md
74
74
  - docs/checks/asset_size_javascript.md
75
+ - docs/checks/content_for_header_modification.md
75
76
  - docs/checks/convert_include_to_render.md
76
77
  - docs/checks/default_locale.md
77
78
  - docs/checks/deprecated_filter.md
@@ -84,6 +85,7 @@ files:
84
85
  - docs/checks/missing_template.md
85
86
  - docs/checks/nested_snippet.md
86
87
  - docs/checks/parser_blocking_javascript.md
88
+ - docs/checks/parser_blocking_script_tag.md
87
89
  - docs/checks/remote_asset.md
88
90
  - docs/checks/required_directories.md
89
91
  - docs/checks/required_layout_theme_object.md
@@ -104,10 +106,12 @@ files:
104
106
  - lib/theme_check.rb
105
107
  - lib/theme_check/analyzer.rb
106
108
  - lib/theme_check/asset_file.rb
109
+ - lib/theme_check/bug.rb
107
110
  - lib/theme_check/check.rb
108
111
  - lib/theme_check/checks.rb
109
112
  - lib/theme_check/checks/asset_size_css.rb
110
113
  - lib/theme_check/checks/asset_size_javascript.rb
114
+ - lib/theme_check/checks/content_for_header_modification.rb
111
115
  - lib/theme_check/checks/convert_include_to_render.rb
112
116
  - lib/theme_check/checks/default_locale.rb
113
117
  - lib/theme_check/checks/deprecated_filter.rb
@@ -120,6 +124,7 @@ files:
120
124
  - lib/theme_check/checks/missing_template.rb
121
125
  - lib/theme_check/checks/nested_snippet.rb
122
126
  - lib/theme_check/checks/parser_blocking_javascript.rb
127
+ - lib/theme_check/checks/parser_blocking_script_tag.rb
123
128
  - lib/theme_check/checks/remote_asset.rb
124
129
  - lib/theme_check/checks/required_directories.rb
125
130
  - lib/theme_check/checks/required_layout_theme_object.rb
@@ -138,8 +143,13 @@ files:
138
143
  - lib/theme_check/cli.rb
139
144
  - lib/theme_check/config.rb
140
145
  - lib/theme_check/corrector.rb
146
+ - lib/theme_check/disabled_check.rb
141
147
  - lib/theme_check/disabled_checks.rb
148
+ - lib/theme_check/exceptions.rb
142
149
  - lib/theme_check/file_system_storage.rb
150
+ - lib/theme_check/html_check.rb
151
+ - lib/theme_check/html_node.rb
152
+ - lib/theme_check/html_visitor.rb
143
153
  - lib/theme_check/in_memory_storage.rb
144
154
  - lib/theme_check/json_check.rb
145
155
  - lib/theme_check/json_file.rb
@@ -153,18 +163,21 @@ files:
153
163
  - lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb
154
164
  - lib/theme_check/language_server/completion_providers/tag_completion_provider.rb
155
165
  - lib/theme_check/language_server/constants.rb
166
+ - lib/theme_check/language_server/diagnostics_tracker.rb
156
167
  - lib/theme_check/language_server/document_link_engine.rb
157
168
  - lib/theme_check/language_server/handler.rb
158
- - lib/theme_check/language_server/position_helper.rb
159
169
  - lib/theme_check/language_server/protocol.rb
160
170
  - lib/theme_check/language_server/server.rb
161
171
  - lib/theme_check/language_server/tokens.rb
172
+ - lib/theme_check/language_server/variable_lookup_finder.rb
162
173
  - lib/theme_check/liquid_check.rb
163
174
  - lib/theme_check/locale_diff.rb
164
175
  - lib/theme_check/node.rb
165
176
  - lib/theme_check/offense.rb
166
177
  - lib/theme_check/packager.rb
167
178
  - lib/theme_check/parsing_helpers.rb
179
+ - lib/theme_check/position.rb
180
+ - lib/theme_check/position_helper.rb
168
181
  - lib/theme_check/printer.rb
169
182
  - lib/theme_check/regex_helpers.rb
170
183
  - lib/theme_check/releaser.rb
@@ -196,14 +209,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
196
209
  requirements:
197
210
  - - ">="
198
211
  - !ruby/object:Gem::Version
199
- version: '0'
212
+ version: '2.6'
200
213
  required_rubygems_version: !ruby/object:Gem::Requirement
201
214
  requirements:
202
215
  - - ">="
203
216
  - !ruby/object:Gem::Version
204
217
  version: '0'
205
218
  requirements: []
206
- rubygems_version: 3.0.3
219
+ rubygems_version: 3.2.17
207
220
  signing_key:
208
221
  specification_version: 4
209
222
  summary: A Shopify Theme Linter