theme-check 1.5.2 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +12 -4
- data/CHANGELOG.md +37 -0
- data/docs/api/html_check.md +7 -7
- data/docs/api/liquid_check.md +10 -10
- data/docs/checks/convert_include_to_render.md +1 -1
- data/docs/checks/missing_enable_comment.md +1 -1
- data/lib/theme_check/analyzer.rb +46 -17
- data/lib/theme_check/asset_file.rb +13 -2
- data/lib/theme_check/check.rb +3 -3
- data/lib/theme_check/checks/asset_size_css.rb +15 -0
- data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
- data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
- data/lib/theme_check/checks/html_parsing_error.rb +2 -2
- data/lib/theme_check/checks/matching_translations.rb +1 -1
- data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
- data/lib/theme_check/checks/missing_template.rb +6 -6
- data/lib/theme_check/checks/nested_snippet.rb +2 -2
- data/lib/theme_check/checks/required_layout_theme_object.rb +2 -2
- data/lib/theme_check/checks/syntax_error.rb +5 -5
- data/lib/theme_check/checks/template_length.rb +2 -2
- data/lib/theme_check/checks/translation_key_exists.rb +3 -1
- data/lib/theme_check/checks/undefined_object.rb +7 -7
- data/lib/theme_check/checks/unused_assign.rb +4 -4
- data/lib/theme_check/checks/unused_snippet.rb +8 -6
- data/lib/theme_check/checks/valid_json.rb +1 -1
- data/lib/theme_check/checks.rb +4 -2
- data/lib/theme_check/cli.rb +7 -4
- data/lib/theme_check/corrector.rb +21 -12
- data/lib/theme_check/disabled_check.rb +3 -3
- data/lib/theme_check/disabled_checks.rb +9 -9
- data/lib/theme_check/file_system_storage.rb +7 -2
- data/lib/theme_check/html_node.rb +40 -32
- data/lib/theme_check/html_visitor.rb +24 -12
- data/lib/theme_check/in_memory_storage.rb +5 -1
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +9 -4
- data/lib/theme_check/language_server/diagnostics_tracker.rb +8 -8
- data/lib/theme_check/language_server/handler.rb +88 -6
- data/lib/theme_check/language_server/messenger.rb +57 -0
- data/lib/theme_check/language_server/server.rb +105 -40
- data/lib/theme_check/language_server.rb +1 -0
- data/lib/theme_check/{template.rb → liquid_file.rb} +6 -20
- data/lib/theme_check/liquid_node.rb +291 -0
- data/lib/theme_check/{visitor.rb → liquid_visitor.rb} +4 -4
- data/lib/theme_check/locale_diff.rb +5 -5
- data/lib/theme_check/node.rb +12 -230
- data/lib/theme_check/offense.rb +41 -15
- data/lib/theme_check/position.rb +1 -1
- data/lib/theme_check/regex_helpers.rb +1 -15
- data/lib/theme_check/theme.rb +1 -1
- data/lib/theme_check/theme_file.rb +18 -1
- data/lib/theme_check/theme_file_rewriter.rb +57 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +11 -9
- data/theme-check.gemspec +2 -1
- metadata +23 -6
@@ -25,21 +25,21 @@ module ThemeCheck
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def on_document(node)
|
28
|
-
@templates[node.
|
28
|
+
@templates[node.theme_file.name] = TemplateInfo.new(Set.new, {}, Set.new)
|
29
29
|
end
|
30
30
|
|
31
31
|
def on_assign(node)
|
32
|
-
@templates[node.
|
32
|
+
@templates[node.theme_file.name].assign_nodes[node.value.to] = node
|
33
33
|
end
|
34
34
|
|
35
35
|
def on_include(node)
|
36
36
|
if node.value.template_name_expr.is_a?(String)
|
37
|
-
@templates[node.
|
37
|
+
@templates[node.theme_file.name].includes << "snippets/#{node.value.template_name_expr}"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
def on_variable_lookup(node)
|
42
|
-
@templates[node.
|
42
|
+
@templates[node.theme_file.name].used_assigns << node.value.name
|
43
43
|
end
|
44
44
|
|
45
45
|
def on_end
|
@@ -8,28 +8,30 @@ module ThemeCheck
|
|
8
8
|
doc docs_url(__FILE__)
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@
|
11
|
+
@used_snippets = Set.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def on_include(node)
|
15
15
|
if node.value.template_name_expr.is_a?(String)
|
16
|
-
@
|
16
|
+
@used_snippets << "snippets/#{node.value.template_name_expr}"
|
17
17
|
else
|
18
18
|
# Can't reliably track unused snippets if an expression is used, ignore this check
|
19
|
-
@
|
19
|
+
@used_snippets.clear
|
20
20
|
ignore!
|
21
21
|
end
|
22
22
|
end
|
23
23
|
alias_method :on_render, :on_include
|
24
24
|
|
25
25
|
def on_end
|
26
|
-
missing_snippets.each do |
|
27
|
-
add_offense("This
|
26
|
+
missing_snippets.each do |theme_file|
|
27
|
+
add_offense("This snippet is not used", theme_file: theme_file) do |corrector|
|
28
|
+
corrector.remove(@theme, theme_file.relative_path.to_s)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
33
|
def missing_snippets
|
32
|
-
theme.snippets.reject { |t| @
|
34
|
+
theme.snippets.reject { |t| @used_snippets.include?(t.name) }
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
data/lib/theme_check/checks.rb
CHANGED
@@ -47,9 +47,10 @@ module ThemeCheck
|
|
47
47
|
raise
|
48
48
|
rescue => e
|
49
49
|
node = args.first
|
50
|
-
|
50
|
+
theme_file = node.respond_to?(:theme_file) ? node.theme_file.relative_path : "?"
|
51
51
|
markup = node.respond_to?(:markup) ? node.markup : ""
|
52
52
|
node_class = node.respond_to?(:value) ? node.value.class : "?"
|
53
|
+
line_number = node.respond_to?(:line_number) ? node.line_number : "?"
|
53
54
|
|
54
55
|
ThemeCheck.bug(<<~EOS)
|
55
56
|
Exception while running `#{check.code_name}##{method}`:
|
@@ -58,12 +59,13 @@ module ThemeCheck
|
|
58
59
|
#{e.backtrace.join("\n ")}
|
59
60
|
```
|
60
61
|
|
61
|
-
|
62
|
+
Theme File: `#{theme_file}`
|
62
63
|
Node: `#{node_class}`
|
63
64
|
Markup:
|
64
65
|
```
|
65
66
|
#{markup}
|
66
67
|
```
|
68
|
+
Line number: #{line_number}
|
67
69
|
Check options: `#{check.options.pretty_inspect}`
|
68
70
|
EOS
|
69
71
|
end
|
data/lib/theme_check/cli.rb
CHANGED
@@ -49,7 +49,7 @@ module ThemeCheck
|
|
49
49
|
"Automatically fix offenses"
|
50
50
|
) { @auto_correct = true }
|
51
51
|
@option_parser.on(
|
52
|
-
"--fail-level SEVERITY", Check::SEVERITIES,
|
52
|
+
"--fail-level SEVERITY", [:crash] + Check::SEVERITIES,
|
53
53
|
"Minimum severity (error|suggestion|style) for exit with error code"
|
54
54
|
) do |severity|
|
55
55
|
@fail_level = severity.to_sym
|
@@ -186,12 +186,15 @@ module ThemeCheck
|
|
186
186
|
storage = ThemeCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
187
187
|
theme = ThemeCheck::Theme.new(storage)
|
188
188
|
if theme.all.empty?
|
189
|
-
raise Abort, "No
|
189
|
+
raise Abort, "No theme files found."
|
190
190
|
end
|
191
191
|
analyzer = ThemeCheck::Analyzer.new(theme, @config.enabled_checks, @config.auto_correct)
|
192
192
|
analyzer.analyze_theme
|
193
193
|
analyzer.correct_offenses
|
194
|
-
|
194
|
+
print_with_format(theme, analyzer, out_stream)
|
195
|
+
# corrections are committed after printing so that the
|
196
|
+
# source_excerpts are still pointing to the uncorrected source.
|
197
|
+
analyzer.write_corrections
|
195
198
|
raise Abort, "" if analyzer.uncorrectable_offenses.any? do |offense|
|
196
199
|
offense.check.severity_value <= Check.severity_value(@fail_level)
|
197
200
|
end
|
@@ -211,7 +214,7 @@ module ThemeCheck
|
|
211
214
|
STDERR.puts "Profiling is only available in development"
|
212
215
|
end
|
213
216
|
|
214
|
-
def
|
217
|
+
def print_with_format(theme, analyzer, out_stream)
|
215
218
|
case @format
|
216
219
|
when :text
|
217
220
|
ThemeCheck::Printer.new(out_stream).print(theme, analyzer.offenses, @config.auto_correct)
|
@@ -2,30 +2,25 @@
|
|
2
2
|
|
3
3
|
module ThemeCheck
|
4
4
|
class Corrector
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(theme_file:)
|
6
|
+
@theme_file = theme_file
|
7
7
|
end
|
8
8
|
|
9
9
|
def insert_after(node, content)
|
10
|
-
|
11
|
-
line.insert(node.range[1] + 1, content)
|
10
|
+
@theme_file.rewriter.insert_after(node, content)
|
12
11
|
end
|
13
12
|
|
14
13
|
def insert_before(node, content)
|
15
|
-
|
16
|
-
line.insert(node.range[0], content)
|
14
|
+
@theme_file.rewriter.insert_before(node, content)
|
17
15
|
end
|
18
16
|
|
19
17
|
def replace(node, content)
|
20
|
-
|
21
|
-
line[node.range[0]..node.range[1]] = content
|
18
|
+
@theme_file.rewriter.replace(node, content)
|
22
19
|
node.markup = content
|
23
20
|
end
|
24
21
|
|
25
22
|
def wrap(node, insert_before, insert_after)
|
26
|
-
|
27
|
-
line.insert(node.range[0], insert_before)
|
28
|
-
line.insert(node.range[1] + 1 + insert_before.length, insert_after)
|
23
|
+
@theme_file.rewriter.wrap(node, insert_before, insert_after)
|
29
24
|
end
|
30
25
|
|
31
26
|
def create(theme, relative_path, content)
|
@@ -34,11 +29,25 @@ module ThemeCheck
|
|
34
29
|
|
35
30
|
def create_default_locale_json(theme)
|
36
31
|
theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
|
37
|
-
theme.default_locale_json.update_contents(
|
32
|
+
theme.default_locale_json.update_contents({})
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(theme, relative_path)
|
36
|
+
theme.storage.remove(relative_path)
|
38
37
|
end
|
39
38
|
|
40
39
|
def mkdir(theme, relative_path)
|
41
40
|
theme.storage.mkdir(relative_path)
|
42
41
|
end
|
42
|
+
|
43
|
+
def add_default_translation_key(file, key, value)
|
44
|
+
hash = file.content
|
45
|
+
key.reduce(hash) do |pointer, token|
|
46
|
+
return pointer[token] = value if token == key.last
|
47
|
+
pointer[token] = {} unless pointer.key?(token)
|
48
|
+
pointer[token]
|
49
|
+
end
|
50
|
+
file.update_contents(hash)
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
@@ -4,11 +4,11 @@
|
|
4
4
|
# We'll use the node position to figure out if the test is disabled or not.
|
5
5
|
module ThemeCheck
|
6
6
|
class DisabledCheck
|
7
|
-
attr_reader :name, :
|
7
|
+
attr_reader :name, :theme_file, :ranges
|
8
8
|
attr_accessor :first_line
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(theme_file, name)
|
11
|
+
@theme_file = theme_file
|
12
12
|
@name = name
|
13
13
|
@ranges = []
|
14
14
|
@first_line = false
|
@@ -11,8 +11,8 @@ module ThemeCheck
|
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@disabled_checks = Hash.new do |hash, key|
|
14
|
-
|
15
|
-
hash[key] = DisabledCheck.new(
|
14
|
+
theme_file, check_name = key
|
15
|
+
hash[key] = DisabledCheck.new(theme_file, check_name)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -20,26 +20,26 @@ module ThemeCheck
|
|
20
20
|
text = comment_text(node)
|
21
21
|
if start_disabling?(text)
|
22
22
|
checks_from_text(text).each do |check_name|
|
23
|
-
disabled = @disabled_checks[[node.
|
23
|
+
disabled = @disabled_checks[[node.theme_file, check_name]]
|
24
24
|
disabled.start_index = node.start_index
|
25
25
|
disabled.first_line = true if node.line_number == 1
|
26
26
|
end
|
27
27
|
elsif stop_disabling?(text)
|
28
28
|
checks_from_text(text).each do |check_name|
|
29
|
-
disabled = @disabled_checks[[node.
|
29
|
+
disabled = @disabled_checks[[node.theme_file, check_name]]
|
30
30
|
next unless disabled
|
31
31
|
disabled.end_index = node.end_index
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
def disabled?(check,
|
36
|
+
def disabled?(check, theme_file, check_name, index)
|
37
37
|
return true if check.ignored_patterns&.any? do |pattern|
|
38
|
-
|
38
|
+
theme_file.relative_path.fnmatch?(pattern)
|
39
39
|
end
|
40
40
|
|
41
|
-
@disabled_checks[[
|
42
|
-
@disabled_checks[[
|
41
|
+
@disabled_checks[[theme_file, :all]]&.disabled?(index) ||
|
42
|
+
@disabled_checks[[theme_file, check_name]]&.disabled?(index)
|
43
43
|
end
|
44
44
|
|
45
45
|
def checks_missing_end_index
|
@@ -51,7 +51,7 @@ module ThemeCheck
|
|
51
51
|
def remove_disabled_offenses(checks)
|
52
52
|
checks.disableable.each do |check|
|
53
53
|
check.offenses.reject! do |offense|
|
54
|
-
disabled?(check, offense.
|
54
|
+
disabled?(check, offense.theme_file, offense.code_name, offense.start_index)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -16,14 +16,19 @@ module ThemeCheck
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def read(relative_path)
|
19
|
-
file(relative_path).read
|
19
|
+
file(relative_path).read(mode: 'rb', encoding: 'UTF-8')
|
20
20
|
end
|
21
21
|
|
22
22
|
def write(relative_path, content)
|
23
23
|
reset_memoizers unless file_exists?(relative_path)
|
24
24
|
|
25
25
|
file(relative_path).dirname.mkpath unless file(relative_path).dirname.directory?
|
26
|
-
file(relative_path).write(content)
|
26
|
+
file(relative_path).write(content, mode: 'w+b', encoding: 'UTF-8')
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove(relative_path)
|
30
|
+
file(relative_path).delete
|
31
|
+
reset_memoizers
|
27
32
|
end
|
28
33
|
|
29
34
|
def mkdir(relative_path)
|
@@ -2,18 +2,50 @@
|
|
2
2
|
require "forwardable"
|
3
3
|
|
4
4
|
module ThemeCheck
|
5
|
-
class HtmlNode
|
5
|
+
class HtmlNode < Node
|
6
6
|
extend Forwardable
|
7
7
|
include RegexHelpers
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :theme_file, :parent
|
9
9
|
|
10
|
-
def initialize(value,
|
10
|
+
def initialize(value, theme_file, placeholder_values = [], parent = nil)
|
11
11
|
@value = value
|
12
|
-
@
|
12
|
+
@theme_file = theme_file
|
13
13
|
@placeholder_values = placeholder_values
|
14
14
|
@parent = parent
|
15
15
|
end
|
16
16
|
|
17
|
+
# @value is not forwarded because we _need_ to replace the
|
18
|
+
# placeholders for the HtmlNode to make sense.
|
19
|
+
def value
|
20
|
+
if literal?
|
21
|
+
content
|
22
|
+
else
|
23
|
+
markup
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def children
|
28
|
+
@children ||= @value
|
29
|
+
.children
|
30
|
+
.map { |child| HtmlNode.new(child, theme_file, @placeholder_values, self) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def markup
|
34
|
+
@markup ||= replace_placeholders(@value.to_html)
|
35
|
+
end
|
36
|
+
|
37
|
+
def line_number
|
38
|
+
@value.line
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_index
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def end_index
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
17
49
|
def literal?
|
18
50
|
@value.name == "text"
|
19
51
|
end
|
@@ -22,12 +54,6 @@ module ThemeCheck
|
|
22
54
|
@value.element?
|
23
55
|
end
|
24
56
|
|
25
|
-
def children
|
26
|
-
@children ||= @value
|
27
|
-
.children
|
28
|
-
.map { |child| HtmlNode.new(child, template, @placeholder_values, self) }
|
29
|
-
end
|
30
|
-
|
31
57
|
def attributes
|
32
58
|
@attributes ||= @value.attributes
|
33
59
|
.map { |k, v| [replace_placeholders(k), replace_placeholders(v.value)] }
|
@@ -38,16 +64,6 @@ module ThemeCheck
|
|
38
64
|
@content ||= replace_placeholders(@value.content)
|
39
65
|
end
|
40
66
|
|
41
|
-
# @value is not forwarded because we _need_ to replace the
|
42
|
-
# placeholders for the HtmlNode to make sense.
|
43
|
-
def value
|
44
|
-
if literal?
|
45
|
-
content
|
46
|
-
else
|
47
|
-
markup
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
67
|
def name
|
52
68
|
if @value.name == "#document-fragment"
|
53
69
|
"document"
|
@@ -56,21 +72,13 @@ module ThemeCheck
|
|
56
72
|
end
|
57
73
|
end
|
58
74
|
|
59
|
-
def markup
|
60
|
-
@markup ||= replace_placeholders(@value.to_html)
|
61
|
-
end
|
62
|
-
|
63
|
-
def line_number
|
64
|
-
@value.line
|
65
|
-
end
|
66
|
-
|
67
75
|
private
|
68
76
|
|
69
77
|
def replace_placeholders(string)
|
70
|
-
# Replace all {
|
71
|
-
string.gsub(
|
72
|
-
key =
|
73
|
-
@placeholder_values[key.to_i]
|
78
|
+
# Replace all ≬{i}####≬ with the actual content.
|
79
|
+
string.gsub(HTML_LIQUID_PLACEHOLDER) do |match|
|
80
|
+
key = /[0-9a-z]+/.match(match)[0]
|
81
|
+
@placeholder_values[key.to_i(36)]
|
74
82
|
end
|
75
83
|
end
|
76
84
|
end
|
@@ -9,32 +9,44 @@ module ThemeCheck
|
|
9
9
|
|
10
10
|
def initialize(checks)
|
11
11
|
@checks = checks
|
12
|
-
@placeholder_values = []
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
16
|
-
doc = parse(
|
17
|
-
visit(HtmlNode.new(doc,
|
14
|
+
def visit_liquid_file(liquid_file)
|
15
|
+
doc, placeholder_values = parse(liquid_file)
|
16
|
+
visit(HtmlNode.new(doc, liquid_file, placeholder_values))
|
18
17
|
rescue ArgumentError => e
|
19
|
-
call_checks(:on_parse_error, e,
|
18
|
+
call_checks(:on_parse_error, e, liquid_file)
|
20
19
|
end
|
21
20
|
|
22
21
|
private
|
23
22
|
|
24
|
-
def parse(
|
25
|
-
|
23
|
+
def parse(liquid_file)
|
24
|
+
placeholder_values = []
|
25
|
+
parseable_source = +liquid_file.source.clone
|
26
26
|
|
27
|
-
# Replace all liquid tags with {
|
27
|
+
# Replace all non-empty liquid tags with ≬{i}######≬ to prevent the HTML
|
28
28
|
# parser from freaking out. We transparently replace those placeholders in
|
29
29
|
# HtmlNode.
|
30
|
+
#
|
31
|
+
# We're using base36 to prevent index bleeding on 36^3 tags.
|
32
|
+
# `{{x}}` -> `≬#{i}≬` would properly be transformed for 46656 tags in a single file.
|
33
|
+
# Should be enough.
|
34
|
+
#
|
35
|
+
# The base10 alternative would have overflowed at 1000 (`{{x}}` -> `≬1000≬`) which seemed more likely.
|
36
|
+
#
|
37
|
+
# Didn't go with base64 because of the `=` character that would have messed with HTML parsing.
|
30
38
|
matches(parseable_source, LIQUID_TAG_OR_VARIABLE).each do |m|
|
31
39
|
value = m[0]
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
next unless value.size > 4 # skip empty tags/variables {%%} and {{}}
|
41
|
+
placeholder_values.push(value)
|
42
|
+
key = (placeholder_values.size - 1).to_s(36)
|
43
|
+
parseable_source[m.begin(0)...m.end(0)] = "≬#{key.ljust(m.end(0) - m.begin(0) - 2, '#')}≬"
|
35
44
|
end
|
36
45
|
|
37
|
-
|
46
|
+
[
|
47
|
+
Nokogiri::HTML5.fragment(parseable_source, max_tree_depth: 400, max_attributes: 400),
|
48
|
+
placeholder_values,
|
49
|
+
]
|
38
50
|
end
|
39
51
|
|
40
52
|
def visit(node)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# An in-memory storage is not written to disk. The reasons why you'd
|
4
4
|
# want to do that are your own. The idea is to not write to disk
|
5
|
-
# something that doesn't need to be there. If you have your
|
5
|
+
# something that doesn't need to be there. If you have your theme
|
6
6
|
# as a big hash already, leave it like that and save yourself some IO.
|
7
7
|
module ThemeCheck
|
8
8
|
class InMemoryStorage < Storage
|
@@ -23,6 +23,10 @@ module ThemeCheck
|
|
23
23
|
@files[relative_path] = content
|
24
24
|
end
|
25
25
|
|
26
|
+
def remove(relative_path)
|
27
|
+
@files.delete(relative_path)
|
28
|
+
end
|
29
|
+
|
26
30
|
def mkdir(relative_path)
|
27
31
|
@files[relative_path] = nil
|
28
32
|
end
|
@@ -4,8 +4,8 @@ module ThemeCheck
|
|
4
4
|
class JsonCheck < Check
|
5
5
|
extend ChecksTracking
|
6
6
|
|
7
|
-
def add_offense(message, markup: nil, line_number: nil,
|
8
|
-
offenses << Offense.new(check: self, message: message, markup: markup, line_number: line_number,
|
7
|
+
def add_offense(message, markup: nil, line_number: nil, theme_file: nil, &block)
|
8
|
+
offenses << Offense.new(check: self, message: message, markup: markup, line_number: line_number, theme_file: theme_file, correction: block)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -20,14 +20,19 @@ module ThemeCheck
|
|
20
20
|
@parser_error
|
21
21
|
end
|
22
22
|
|
23
|
-
def update_contents(new_content =
|
23
|
+
def update_contents(new_content = {})
|
24
|
+
raise ArgumentError if new_content.is_a?(String)
|
24
25
|
@content = new_content
|
25
26
|
end
|
26
27
|
|
27
28
|
def write
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
pretty = JSON.pretty_generate(@content)
|
30
|
+
if source.rstrip != pretty.rstrip
|
31
|
+
# Most editors add a trailing \n at the end of files. Here we
|
32
|
+
# try to maintain the convention.
|
33
|
+
eof = source.end_with?("\n") ? "\n" : ""
|
34
|
+
@storage.write(@relative_path, pretty.gsub("\n", @eol) + eof)
|
35
|
+
@source = pretty
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
@@ -19,22 +19,22 @@ module ThemeCheck
|
|
19
19
|
new_single_file_offenses = {}
|
20
20
|
analyzed_files = analyzed_files.map { |path| Pathname.new(path) } if analyzed_files
|
21
21
|
|
22
|
-
offenses.group_by(&:
|
23
|
-
next unless
|
22
|
+
offenses.group_by(&:theme_file).each do |theme_file, template_offenses|
|
23
|
+
next unless theme_file
|
24
24
|
reported_offenses = template_offenses
|
25
|
-
previous_offenses = @single_files_offenses[
|
26
|
-
if analyzed_files.nil? || analyzed_files.include?(
|
25
|
+
previous_offenses = @single_files_offenses[theme_file.path]
|
26
|
+
if analyzed_files.nil? || analyzed_files.include?(theme_file.path)
|
27
27
|
# We re-analyzed the file, so we know the template_offenses are update to date.
|
28
28
|
reported_single_file_offenses = reported_offenses.select(&:single_file?)
|
29
29
|
if reported_single_file_offenses.any?
|
30
|
-
new_single_file_offenses[
|
30
|
+
new_single_file_offenses[theme_file.path] = reported_single_file_offenses
|
31
31
|
end
|
32
32
|
elsif previous_offenses
|
33
33
|
# Merge in the previous ones, if some
|
34
34
|
reported_offenses |= previous_offenses
|
35
35
|
end
|
36
|
-
yield
|
37
|
-
reported_files <<
|
36
|
+
yield theme_file.path, reported_offenses
|
37
|
+
reported_files << theme_file.path
|
38
38
|
end
|
39
39
|
|
40
40
|
@single_files_offenses.each do |path, _|
|
@@ -51,7 +51,7 @@ module ThemeCheck
|
|
51
51
|
reported_files << path
|
52
52
|
end
|
53
53
|
|
54
|
-
# Publish diagnostics with empty array if all issues on a previously reported
|
54
|
+
# Publish diagnostics with empty array if all issues on a previously reported theme_file
|
55
55
|
# have been fixed.
|
56
56
|
(@previously_reported_files - reported_files).each do |path|
|
57
57
|
yield path, []
|