theme-check 1.5.2 → 1.7.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.
- 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, []
|