theme-check 1.6.1 → 1.7.2
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/CHANGELOG.md +27 -0
- data/data/shopify_liquid/tags.yml +9 -9
- 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 +41 -17
- data/lib/theme_check/asset_file.rb +1 -1
- data/lib/theme_check/check.rb +2 -2
- 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_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 +1 -13
- 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 +7 -7
- data/lib/theme_check/checks/valid_json.rb +1 -1
- data/lib/theme_check/checks.rb +2 -2
- data/lib/theme_check/cli.rb +1 -1
- data/lib/theme_check/corrector.rb +6 -6
- data/lib/theme_check/disabled_check.rb +3 -3
- data/lib/theme_check/disabled_checks.rb +9 -9
- data/lib/theme_check/exceptions.rb +1 -0
- data/lib/theme_check/file_system_storage.rb +4 -0
- data/lib/theme_check/html_node.rb +36 -28
- data/lib/theme_check/html_visitor.rb +6 -6
- data/lib/theme_check/in_memory_storage.rb +1 -1
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/language_server/bridge.rb +128 -0
- data/lib/theme_check/language_server/channel.rb +69 -0
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +3 -1
- data/lib/theme_check/language_server/diagnostics_engine.rb +125 -0
- data/lib/theme_check/language_server/diagnostics_tracker.rb +8 -8
- data/lib/theme_check/language_server/handler.rb +20 -117
- data/lib/theme_check/language_server/io_messenger.rb +102 -0
- data/lib/theme_check/language_server/messenger.rb +27 -0
- data/lib/theme_check/language_server/server.rb +95 -104
- data/lib/theme_check/language_server.rb +6 -1
- data/lib/theme_check/{template.rb → liquid_file.rb} +2 -2
- 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 +14 -7
- data/lib/theme_check/node.rb +12 -225
- data/lib/theme_check/offense.rb +15 -15
- data/lib/theme_check/position.rb +1 -1
- data/lib/theme_check/shopify_liquid/system_translations.rb +35 -0
- data/lib/theme_check/shopify_liquid/tag.rb +19 -1
- data/lib/theme_check/shopify_liquid.rb +1 -0
- data/lib/theme_check/theme.rb +1 -1
- data/lib/theme_check/{template_rewriter.rb → theme_file_rewriter.rb} +1 -1
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +11 -10
- data/theme-check.gemspec +1 -1
- metadata +14 -7
@@ -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
|
@@ -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,14 +72,6 @@ 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)
|
@@ -11,18 +11,18 @@ module ThemeCheck
|
|
11
11
|
@checks = checks
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
doc, placeholder_values = parse(
|
16
|
-
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))
|
17
17
|
rescue ArgumentError => e
|
18
|
-
call_checks(:on_parse_error, e,
|
18
|
+
call_checks(:on_parse_error, e, liquid_file)
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
-
def parse(
|
23
|
+
def parse(liquid_file)
|
24
24
|
placeholder_values = []
|
25
|
-
parseable_source = +
|
25
|
+
parseable_source = +liquid_file.source.clone
|
26
26
|
|
27
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
|
@@ -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
|
@@ -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
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class exists as a bridge (or boundary) between our handlers and the outside world.
|
4
|
+
#
|
5
|
+
# It is concerned with all the Language Server Protocol constructs. i.e.
|
6
|
+
#
|
7
|
+
# - sending Hash messages as JSON
|
8
|
+
# - reading JSON messages as Hashes
|
9
|
+
# - preparing, sending and resolving requests
|
10
|
+
# - preparing and sending responses
|
11
|
+
# - preparing and sending notifications
|
12
|
+
# - preparing and sending progress notifications
|
13
|
+
#
|
14
|
+
# But it _not_ concerned by _how_ those messages are sent to the
|
15
|
+
# outside world. That's the job of the messenger.
|
16
|
+
#
|
17
|
+
# This enables us to have all the language server protocol logic
|
18
|
+
# in here living independently of how we communicate with the
|
19
|
+
# client (STDIO or websocket)
|
20
|
+
module ThemeCheck
|
21
|
+
module LanguageServer
|
22
|
+
class Bridge
|
23
|
+
attr_writer :supports_work_done_progress
|
24
|
+
|
25
|
+
def initialize(messenger)
|
26
|
+
# The messenger is responsible for IO.
|
27
|
+
# Could be STDIO or WebSockets or Mock.
|
28
|
+
@messenger = messenger
|
29
|
+
|
30
|
+
# Whether the client supports work done progress notifications
|
31
|
+
@supports_work_done_progress = false
|
32
|
+
end
|
33
|
+
|
34
|
+
def log(message)
|
35
|
+
@messenger.log(message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_message
|
39
|
+
message_body = @messenger.read_message
|
40
|
+
message_json = JSON.parse(message_body)
|
41
|
+
@messenger.log(JSON.pretty_generate(message_json)) if $DEBUG
|
42
|
+
message_json
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_message(message_hash)
|
46
|
+
message_hash[:jsonrpc] = '2.0'
|
47
|
+
message_body = JSON.dump(message_hash)
|
48
|
+
@messenger.log(JSON.pretty_generate(message_hash)) if $DEBUG
|
49
|
+
@messenger.send_message(message_body)
|
50
|
+
end
|
51
|
+
|
52
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#requestMessage
|
53
|
+
def send_request(method, params = nil)
|
54
|
+
channel = Channel.create
|
55
|
+
message = { id: channel.id }
|
56
|
+
message[:method] = method
|
57
|
+
message[:params] = params if params
|
58
|
+
send_message(message)
|
59
|
+
channel.pop
|
60
|
+
ensure
|
61
|
+
channel.close
|
62
|
+
end
|
63
|
+
|
64
|
+
def receive_response(id, result)
|
65
|
+
Channel.by_id(id) << result
|
66
|
+
end
|
67
|
+
|
68
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
|
69
|
+
def send_response(id, result = nil, error = nil)
|
70
|
+
message = { id: id }
|
71
|
+
message[:result] = result if result
|
72
|
+
message[:error] = error if error
|
73
|
+
send_message(message)
|
74
|
+
end
|
75
|
+
|
76
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
77
|
+
def send_notification(method, params)
|
78
|
+
message = { method: method }
|
79
|
+
message[:params] = params
|
80
|
+
send_message(message)
|
81
|
+
end
|
82
|
+
|
83
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
|
84
|
+
def send_progress(token, value)
|
85
|
+
send_notification("$/progress", token: token, value: value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def supports_work_done_progress?
|
89
|
+
@supports_work_done_progress
|
90
|
+
end
|
91
|
+
|
92
|
+
def send_create_work_done_progress_request(token)
|
93
|
+
return unless supports_work_done_progress?
|
94
|
+
send_request("window/workDoneProgress/create", {
|
95
|
+
token: token,
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
def send_work_done_progress_begin(token, title)
|
100
|
+
return unless supports_work_done_progress?
|
101
|
+
send_progress(token, {
|
102
|
+
kind: 'begin',
|
103
|
+
title: title,
|
104
|
+
cancellable: false,
|
105
|
+
percentage: 0,
|
106
|
+
})
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_work_done_progress_report(token, message, percentage)
|
110
|
+
return unless supports_work_done_progress?
|
111
|
+
send_progress(token, {
|
112
|
+
kind: 'report',
|
113
|
+
message: message,
|
114
|
+
cancellable: false,
|
115
|
+
percentage: percentage,
|
116
|
+
})
|
117
|
+
end
|
118
|
+
|
119
|
+
def send_work_done_progress_end(token, message)
|
120
|
+
return unless supports_work_done_progress?
|
121
|
+
send_progress(token, {
|
122
|
+
kind: 'end',
|
123
|
+
message: message,
|
124
|
+
})
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
# How you'd use this class:
|
6
|
+
#
|
7
|
+
# In thread #1:
|
8
|
+
# def foo
|
9
|
+
# chan = Channel.create
|
10
|
+
# send_request(chan.id, ...)
|
11
|
+
# result = chan.pop
|
12
|
+
# do_stuff_with_result(result)
|
13
|
+
# ensure
|
14
|
+
# chan.close
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# In thread #2:
|
18
|
+
# Channel.by_id(id) << result
|
19
|
+
class Channel
|
20
|
+
MUTEX = Mutex.new
|
21
|
+
CHANNELS = {}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def create
|
25
|
+
id = new_id
|
26
|
+
CHANNELS[id] = new(id)
|
27
|
+
CHANNELS[id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def by_id(id)
|
31
|
+
CHANNELS[id]
|
32
|
+
end
|
33
|
+
|
34
|
+
def close(id)
|
35
|
+
CHANNELS.delete(id)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def new_id
|
41
|
+
MUTEX.synchronize do
|
42
|
+
@id ||= 0
|
43
|
+
@id += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :id
|
49
|
+
|
50
|
+
def initialize(id)
|
51
|
+
@id = id
|
52
|
+
@response = SizedQueue.new(1)
|
53
|
+
end
|
54
|
+
|
55
|
+
def pop
|
56
|
+
@response.pop
|
57
|
+
end
|
58
|
+
|
59
|
+
def <<(value)
|
60
|
+
@response << value
|
61
|
+
end
|
62
|
+
|
63
|
+
def close
|
64
|
+
@response.close
|
65
|
+
Channel.close(id)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -6,7 +6,9 @@ module ThemeCheck
|
|
6
6
|
def completions(content, cursor)
|
7
7
|
return [] unless can_complete?(content, cursor)
|
8
8
|
partial = first_word(content) || ''
|
9
|
-
ShopifyLiquid::Tag.labels
|
9
|
+
labels = ShopifyLiquid::Tag.labels
|
10
|
+
labels += ShopifyLiquid::Tag.end_labels
|
11
|
+
labels
|
10
12
|
.select { |w| w.start_with?(partial) }
|
11
13
|
.map { |tag| tag_to_completion(tag) }
|
12
14
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module LanguageServer
|
5
|
+
class DiagnosticsEngine
|
6
|
+
include URIHelper
|
7
|
+
|
8
|
+
def initialize(bridge)
|
9
|
+
@diagnostics_lock = Mutex.new
|
10
|
+
@diagnostics_tracker = DiagnosticsTracker.new
|
11
|
+
@bridge = bridge
|
12
|
+
@token = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def first_run?
|
16
|
+
@diagnostics_tracker.first_run?
|
17
|
+
end
|
18
|
+
|
19
|
+
def analyze_and_send_offenses(absolute_path, config)
|
20
|
+
return unless @diagnostics_lock.try_lock
|
21
|
+
@token += 1
|
22
|
+
@bridge.send_create_work_done_progress_request(@token)
|
23
|
+
storage = ThemeCheck::FileSystemStorage.new(
|
24
|
+
config.root,
|
25
|
+
ignored_patterns: config.ignored_patterns
|
26
|
+
)
|
27
|
+
theme = ThemeCheck::Theme.new(storage)
|
28
|
+
analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
|
29
|
+
|
30
|
+
if @diagnostics_tracker.first_run?
|
31
|
+
@bridge.send_work_done_progress_begin(@token, "Full theme check")
|
32
|
+
@bridge.log("Checking #{config.root}")
|
33
|
+
offenses = nil
|
34
|
+
time = Benchmark.measure do
|
35
|
+
offenses = analyzer.analyze_theme do |path, i, total|
|
36
|
+
@bridge.send_work_done_progress_report(@token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
|
40
|
+
@bridge.send_work_done_progress_end(@token, end_message)
|
41
|
+
send_diagnostics(offenses)
|
42
|
+
else
|
43
|
+
# Analyze selected files
|
44
|
+
relative_path = Pathname.new(storage.relative_path(absolute_path))
|
45
|
+
file = theme[relative_path]
|
46
|
+
# Skip if not a theme file
|
47
|
+
if file
|
48
|
+
@bridge.send_work_done_progress_begin(@token, "Partial theme check")
|
49
|
+
offenses = nil
|
50
|
+
time = Benchmark.measure do
|
51
|
+
offenses = analyzer.analyze_files([file]) do |path, i, total|
|
52
|
+
@bridge.send_work_done_progress_report(@token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
|
56
|
+
@bridge.send_work_done_progress_end(@token, end_message)
|
57
|
+
@bridge.log(end_message)
|
58
|
+
send_diagnostics(offenses, [absolute_path])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@diagnostics_lock.unlock
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def send_diagnostics(offenses, analyzed_files = nil)
|
67
|
+
@diagnostics_tracker.build_diagnostics(offenses, analyzed_files: analyzed_files) do |path, diagnostic_offenses|
|
68
|
+
send_diagnostic(path, diagnostic_offenses)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_diagnostic(path, offenses)
|
73
|
+
# https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
|
74
|
+
@bridge.send_notification('textDocument/publishDiagnostics', {
|
75
|
+
uri: file_uri(path),
|
76
|
+
diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
|
77
|
+
})
|
78
|
+
end
|
79
|
+
|
80
|
+
def offense_to_diagnostic(offense)
|
81
|
+
diagnostic = {
|
82
|
+
code: offense.code_name,
|
83
|
+
message: offense.message,
|
84
|
+
range: range(offense),
|
85
|
+
severity: severity(offense),
|
86
|
+
source: "theme-check",
|
87
|
+
}
|
88
|
+
diagnostic["codeDescription"] = code_description(offense) unless offense.doc.nil?
|
89
|
+
diagnostic
|
90
|
+
end
|
91
|
+
|
92
|
+
def code_description(offense)
|
93
|
+
{
|
94
|
+
href: offense.doc,
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def severity(offense)
|
99
|
+
case offense.severity
|
100
|
+
when :error
|
101
|
+
1
|
102
|
+
when :suggestion
|
103
|
+
2
|
104
|
+
when :style
|
105
|
+
3
|
106
|
+
else
|
107
|
+
4
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def range(offense)
|
112
|
+
{
|
113
|
+
start: {
|
114
|
+
line: offense.start_line,
|
115
|
+
character: offense.start_column,
|
116
|
+
},
|
117
|
+
end: {
|
118
|
+
line: offense.end_line,
|
119
|
+
character: offense.end_column,
|
120
|
+
},
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -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, []
|