theme-check 0.8.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +3 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTING.md +2 -1
  5. data/README.md +4 -1
  6. data/config/default.yml +37 -0
  7. data/docs/checks/content_for_header_modification.md +42 -0
  8. data/docs/checks/parser_blocking_script_tag.md +53 -0
  9. data/exe/theme-check-language-server +1 -2
  10. data/lib/theme_check.rb +8 -1
  11. data/lib/theme_check/analyzer.rb +72 -16
  12. data/lib/theme_check/check.rb +31 -6
  13. data/lib/theme_check/checks.rb +9 -1
  14. data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
  15. data/lib/theme_check/checks/img_width_and_height.rb +18 -49
  16. data/lib/theme_check/checks/missing_template.rb +1 -0
  17. data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
  18. data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
  19. data/lib/theme_check/checks/template_length.rb +3 -0
  20. data/lib/theme_check/checks/valid_html_translation.rb +1 -0
  21. data/lib/theme_check/config.rb +2 -0
  22. data/lib/theme_check/disabled_check.rb +6 -4
  23. data/lib/theme_check/disabled_checks.rb +25 -9
  24. data/lib/theme_check/html_check.rb +7 -0
  25. data/lib/theme_check/html_node.rb +52 -0
  26. data/lib/theme_check/html_visitor.rb +36 -0
  27. data/lib/theme_check/json_file.rb +8 -0
  28. data/lib/theme_check/language_server.rb +1 -0
  29. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
  30. data/lib/theme_check/language_server/diagnostics_tracker.rb +64 -0
  31. data/lib/theme_check/language_server/handler.rb +31 -26
  32. data/lib/theme_check/language_server/server.rb +1 -1
  33. data/lib/theme_check/liquid_check.rb +0 -4
  34. data/lib/theme_check/offense.rb +18 -0
  35. data/lib/theme_check/template.rb +8 -0
  36. data/lib/theme_check/theme.rb +7 -2
  37. data/lib/theme_check/version.rb +1 -1
  38. data/lib/theme_check/visitor.rb +2 -11
  39. metadata +10 -2
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ require "forwardable"
3
+
4
+ module ThemeCheck
5
+ class HtmlNode
6
+ extend Forwardable
7
+ attr_reader :template
8
+
9
+ def_delegators :@value, :content, :attributes
10
+
11
+ def initialize(value, template)
12
+ @value = value
13
+ @template = template
14
+ end
15
+
16
+ def literal?
17
+ @value.name == "text"
18
+ end
19
+
20
+ def children
21
+ @value.children.map { |child| HtmlNode.new(child, template) }
22
+ end
23
+
24
+ def parent
25
+ HtmlNode.new(@value.parent, template)
26
+ end
27
+
28
+ def name
29
+ if @value.name == "#document-fragment"
30
+ "document"
31
+ else
32
+ @value.name
33
+ end
34
+ end
35
+
36
+ def value
37
+ if literal?
38
+ @value.content
39
+ else
40
+ @value
41
+ end
42
+ end
43
+
44
+ def markup
45
+ @value.to_html
46
+ end
47
+
48
+ def line_number
49
+ @value.line
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require "nokogumbo"
3
+ require "forwardable"
4
+
5
+ module ThemeCheck
6
+ class HtmlVisitor
7
+ attr_reader :checks
8
+
9
+ def initialize(checks)
10
+ @checks = checks
11
+ end
12
+
13
+ def visit_template(template)
14
+ doc = parse(template)
15
+ visit(HtmlNode.new(doc, template))
16
+ end
17
+
18
+ private
19
+
20
+ def parse(template)
21
+ Nokogiri::HTML5.fragment(template.source)
22
+ end
23
+
24
+ def visit(node)
25
+ call_checks(:"on_#{node.name}", node)
26
+ node.children.each { |child| visit(child) }
27
+ unless node.literal?
28
+ call_checks(:"after_#{node.name}", node)
29
+ end
30
+ end
31
+
32
+ def call_checks(method, *args)
33
+ checks.call(method, *args)
34
+ end
35
+ end
36
+ end
@@ -38,6 +38,14 @@ module ThemeCheck
38
38
  relative_path.sub_ext('').to_s
39
39
  end
40
40
 
41
+ def json?
42
+ true
43
+ end
44
+
45
+ def liquid?
46
+ false
47
+ end
48
+
41
49
  private
42
50
 
43
51
  def load!
@@ -9,6 +9,7 @@ require_relative "language_server/completion_helper"
9
9
  require_relative "language_server/completion_provider"
10
10
  require_relative "language_server/completion_engine"
11
11
  require_relative "language_server/document_link_engine"
12
+ require_relative "language_server/diagnostics_tracker"
12
13
 
13
14
  Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
14
15
  require file
@@ -37,6 +37,7 @@ module ThemeCheck
37
37
  partial_match = matches(content, NAMED_FILTER).find do |match|
38
38
  match.begin(1) <= cursor && cursor < match.end(1) + 1 # including next character
39
39
  end
40
+ return '' if partial_match.nil?
40
41
  partial_match[1]
41
42
  end
42
43
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class DiagnosticsTracker
6
+ def initialize
7
+ @previously_reported_files = Set.new
8
+ @single_files_offenses = {}
9
+ @first_run = true
10
+ end
11
+
12
+ def first_run?
13
+ @first_run
14
+ end
15
+
16
+ def build_diagnostics(offenses, analyzed_files: nil)
17
+ reported_files = Set.new
18
+ new_single_file_offenses = {}
19
+
20
+ offenses.group_by(&:template).each do |template, template_offenses|
21
+ next unless template
22
+ reported_offenses = template_offenses
23
+ previous_offenses = @single_files_offenses[template.path]
24
+ if analyzed_files.nil? || analyzed_files.include?(template.path)
25
+ # We re-analyzed the file, so we know the template_offenses are update to date.
26
+ reported_single_file_offenses = reported_offenses.select(&:single_file?)
27
+ if reported_single_file_offenses.any?
28
+ new_single_file_offenses[template.path] = reported_single_file_offenses
29
+ end
30
+ elsif previous_offenses
31
+ # Merge in the previous ones, if some
32
+ reported_offenses |= previous_offenses
33
+ end
34
+ yield template.path, reported_offenses
35
+ reported_files << template.path
36
+ end
37
+
38
+ @single_files_offenses.each do |path, _|
39
+ # Already reported above, skip
40
+ next if reported_files.include?(path)
41
+
42
+ if analyzed_files.nil? || analyzed_files.include?(path)
43
+ # We re-analyzed this file, if it was not reported, all offenses in it got fixed
44
+ yield path, []
45
+ new_single_file_offenses[path] = nil
46
+ end
47
+ # NOTE: No need to re-report previous offenses as LSP should keep them around until
48
+ # we clear them.
49
+ reported_files << path
50
+ end
51
+
52
+ # Publish diagnostics with empty array if all issues on a previously reported template
53
+ # have been fixed.
54
+ (@previously_reported_files - reported_files).each do |path|
55
+ yield path, []
56
+ end
57
+
58
+ @previously_reported_files = reported_files
59
+ @single_files_offenses.merge!(new_single_file_offenses)
60
+ @first_run = false
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "benchmark"
2
3
 
3
4
  module ThemeCheck
4
5
  module LanguageServer
@@ -19,7 +20,7 @@ module ThemeCheck
19
20
 
20
21
  def initialize(server)
21
22
  @server = server
22
- @previously_reported_files = Set.new
23
+ @diagnostics_tracker = DiagnosticsTracker.new
23
24
  end
24
25
 
25
26
  def on_initialize(id, params)
@@ -52,6 +53,7 @@ module ThemeCheck
52
53
  end
53
54
 
54
55
  def on_text_document_did_open(_id, params)
56
+ return unless @diagnostics_tracker.first_run?
55
57
  relative_path = relative_path_from_text_document_uri(params)
56
58
  @storage.write(relative_path, text_document_text(params))
57
59
  analyze_and_send_offenses(text_document_uri(params))
@@ -124,17 +126,32 @@ module ThemeCheck
124
126
  ignored_patterns: config.ignored_patterns
125
127
  )
126
128
  theme = ThemeCheck::Theme.new(storage)
127
-
128
- offenses = analyze(theme, config)
129
- log("Found #{theme.all.size} templates, and #{offenses.size} offenses")
130
- send_diagnostics(offenses)
131
- end
132
-
133
- def analyze(theme, config)
134
129
  analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
135
- log("Checking #{config.root}")
136
- analyzer.analyze_theme
137
- analyzer.offenses
130
+
131
+ if @diagnostics_tracker.first_run?
132
+ # Analyze the full theme on first run
133
+ log("Checking #{config.root}")
134
+ offenses = nil
135
+ time = Benchmark.measure do
136
+ offenses = analyzer.analyze_theme
137
+ end
138
+ log("Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s")
139
+ send_diagnostics(offenses)
140
+ else
141
+ # Analyze selected files
142
+ relative_path = Pathname.new(@storage.relative_path(absolute_path))
143
+ file = theme[relative_path]
144
+ # Skip if not a theme file
145
+ if file
146
+ log("Checking #{relative_path}")
147
+ offenses = nil
148
+ time = Benchmark.measure do
149
+ offenses = analyzer.analyze_files([file])
150
+ end
151
+ log("Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s")
152
+ send_diagnostics(offenses, [absolute_path])
153
+ end
154
+ end
138
155
  end
139
156
 
140
157
  def completions(relative_path, line, col)
@@ -145,22 +162,10 @@ module ThemeCheck
145
162
  @document_link_engine.document_links(relative_path)
146
163
  end
147
164
 
148
- def send_diagnostics(offenses)
149
- reported_files = Set.new
150
-
151
- offenses.group_by(&:template).each do |template, template_offenses|
152
- next unless template
153
- send_diagnostic(template.path, template_offenses)
154
- reported_files << template.path
165
+ def send_diagnostics(offenses, analyzed_files = nil)
166
+ @diagnostics_tracker.build_diagnostics(offenses, analyzed_files: analyzed_files) do |path, diagnostic_offenses|
167
+ send_diagnostic(path, diagnostic_offenses)
155
168
  end
156
-
157
- # Publish diagnostics with empty array if all issues on a previously reported template
158
- # have been solved.
159
- (@previously_reported_files - reported_files).each do |path|
160
- send_diagnostic(path, [])
161
- end
162
-
163
- @previously_reported_files = reported_files
164
169
  end
165
170
 
166
171
  def send_diagnostic(path, offenses)
@@ -52,7 +52,7 @@ module ThemeCheck
52
52
  response_body = JSON.dump(response)
53
53
  log(JSON.pretty_generate(response)) if $DEBUG
54
54
 
55
- @out.write("Content-Length: #{response_body.size}\r\n")
55
+ @out.write("Content-Length: #{response_body.bytesize}\r\n")
56
56
  @out.write("\r\n")
57
57
  @out.write(response_body)
58
58
  @out.flush
@@ -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
@@ -114,6 +114,24 @@ module ThemeCheck
114
114
  end
115
115
  end
116
116
 
117
+ def whole_theme?
118
+ check.whole_theme?
119
+ end
120
+
121
+ def single_file?
122
+ check.single_file?
123
+ end
124
+
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
133
+ end
134
+
117
135
  def to_s
118
136
  if template
119
137
  "#{message} at #{location}"
@@ -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.8.3"
3
+ VERSION = "0.9.0"
4
4
  end
@@ -3,14 +3,13 @@ module ThemeCheck
3
3
  class Visitor
4
4
  attr_reader :checks
5
5
 
6
- def initialize(checks)
6
+ def initialize(checks, disabled_checks)
7
7
  @checks = checks
8
+ @disabled_checks = disabled_checks
8
9
  end
9
10
 
10
11
  def visit_template(template)
11
- @disabled_checks = DisabledChecks.new
12
12
  visit(Node.new(template.root, nil, template))
13
- remove_disabled_offenses
14
13
  rescue Liquid::Error => exception
15
14
  exception.template_name = template.name
16
15
  call_checks(:on_error, exception)
@@ -35,13 +34,5 @@ module ThemeCheck
35
34
  def call_checks(method, *args)
36
35
  checks.call(method, *args)
37
36
  end
38
-
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
45
- end
46
37
  end
47
38
  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: 0.8.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-05-17 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
@@ -109,6 +111,7 @@ files:
109
111
  - lib/theme_check/checks.rb
110
112
  - lib/theme_check/checks/asset_size_css.rb
111
113
  - lib/theme_check/checks/asset_size_javascript.rb
114
+ - lib/theme_check/checks/content_for_header_modification.rb
112
115
  - lib/theme_check/checks/convert_include_to_render.rb
113
116
  - lib/theme_check/checks/default_locale.rb
114
117
  - lib/theme_check/checks/deprecated_filter.rb
@@ -121,6 +124,7 @@ files:
121
124
  - lib/theme_check/checks/missing_template.rb
122
125
  - lib/theme_check/checks/nested_snippet.rb
123
126
  - lib/theme_check/checks/parser_blocking_javascript.rb
127
+ - lib/theme_check/checks/parser_blocking_script_tag.rb
124
128
  - lib/theme_check/checks/remote_asset.rb
125
129
  - lib/theme_check/checks/required_directories.rb
126
130
  - lib/theme_check/checks/required_layout_theme_object.rb
@@ -143,6 +147,9 @@ files:
143
147
  - lib/theme_check/disabled_checks.rb
144
148
  - lib/theme_check/exceptions.rb
145
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
146
153
  - lib/theme_check/in_memory_storage.rb
147
154
  - lib/theme_check/json_check.rb
148
155
  - lib/theme_check/json_file.rb
@@ -156,6 +163,7 @@ files:
156
163
  - lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb
157
164
  - lib/theme_check/language_server/completion_providers/tag_completion_provider.rb
158
165
  - lib/theme_check/language_server/constants.rb
166
+ - lib/theme_check/language_server/diagnostics_tracker.rb
159
167
  - lib/theme_check/language_server/document_link_engine.rb
160
168
  - lib/theme_check/language_server/handler.rb
161
169
  - lib/theme_check/language_server/protocol.rb