theme-check 1.10.1 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/cla.yml +22 -0
- data/.github/workflows/theme-check.yml +1 -1
- data/CHANGELOG.md +58 -0
- data/README.md +7 -8
- data/config/default.yml +5 -0
- data/config/theme_app_extension.yml +1 -0
- data/data/shopify_liquid/filters.yml +18 -0
- data/data/shopify_liquid/theme_app_extension_objects.yml +2 -0
- data/dev.yml +1 -1
- data/docs/api/check.md +1 -1
- data/docs/checks/TEMPLATE.md.erb +1 -1
- data/docs/checks/asset_preload.md +60 -0
- data/docs/checks/asset_size_javascript.md +2 -2
- data/docs/checks/missing_enable_comment.md +3 -3
- data/docs/checks/nested_snippet.md +8 -8
- data/docs/checks/schema_json_format.md +1 -1
- data/docs/checks/translation_key_exists.md +4 -4
- data/docs/checks/valid_html_translation.md +1 -1
- data/lib/theme_check/analyzer.rb +18 -3
- data/lib/theme_check/check.rb +6 -1
- data/lib/theme_check/checks/asset_preload.rb +20 -0
- data/lib/theme_check/checks/deprecated_filter.rb +29 -5
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +5 -1
- data/lib/theme_check/checks/missing_template.rb +5 -1
- data/lib/theme_check/checks/translation_key_exists.rb +1 -0
- data/lib/theme_check/checks/undefined_object.rb +9 -1
- data/lib/theme_check/checks/unused_assign.rb +6 -1
- data/lib/theme_check/checks/unused_snippet.rb +50 -2
- data/lib/theme_check/config.rb +4 -3
- data/lib/theme_check/disabled_checks.rb +11 -4
- data/lib/theme_check/file_system_storage.rb +2 -0
- data/lib/theme_check/in_memory_storage.rb +1 -1
- data/lib/theme_check/json_printer.rb +1 -1
- data/lib/theme_check/language_server/bridge.rb +31 -6
- data/lib/theme_check/language_server/diagnostics_engine.rb +80 -34
- data/lib/theme_check/language_server/diagnostics_manager.rb +27 -6
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +7 -6
- data/lib/theme_check/language_server/handler.rb +90 -8
- data/lib/theme_check/language_server/server.rb +42 -14
- data/lib/theme_check/language_server/versioned_in_memory_storage.rb +17 -2
- data/lib/theme_check/liquid_file.rb +22 -1
- data/lib/theme_check/liquid_node.rb +33 -1
- data/lib/theme_check/liquid_visitor.rb +1 -1
- data/lib/theme_check/schema_helper.rb +1 -1
- data/lib/theme_check/shopify_liquid/object.rb +4 -0
- data/lib/theme_check/tags.rb +20 -3
- data/lib/theme_check/version.rb +1 -1
- data/theme-check.gemspec +2 -2
- metadata +12 -7
- data/.github/probots.yml +0 -3
@@ -28,7 +28,11 @@ module ThemeCheck
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def ignore?(path)
|
31
|
-
|
31
|
+
all_ignored_patterns.any? { |pattern| File.fnmatch?(pattern, path) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_ignored_patterns
|
35
|
+
@all_ignored_patterns ||= @ignore_missing + ignored_patterns
|
32
36
|
end
|
33
37
|
|
34
38
|
def add_missing_offense(name, node:)
|
@@ -55,7 +55,8 @@ module ThemeCheck
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def initialize(exclude_snippets: true)
|
58
|
+
def initialize(config_type: :default, exclude_snippets: true)
|
59
|
+
@config_type = config_type
|
59
60
|
@exclude_snippets = exclude_snippets
|
60
61
|
@files = {}
|
61
62
|
end
|
@@ -111,6 +112,9 @@ module ThemeCheck
|
|
111
112
|
shopify_plus_objects = ThemeCheck::ShopifyLiquid::Object.plus_labels
|
112
113
|
shopify_plus_objects.freeze
|
113
114
|
|
115
|
+
theme_app_extension_objects = ThemeCheck::ShopifyLiquid::Object.theme_app_extension_labels
|
116
|
+
theme_app_extension_objects.freeze
|
117
|
+
|
114
118
|
each_template do |(name, info)|
|
115
119
|
if 'templates/customers/reset_password' == name
|
116
120
|
# NOTE: `email` is exceptionally exposed as a theme object in
|
@@ -121,6 +125,8 @@ module ThemeCheck
|
|
121
125
|
# the checkout template
|
122
126
|
# https://shopify.dev/docs/themes/theme-templates/checkout-liquid#optional-objects
|
123
127
|
check_object(info, all_global_objects + shopify_plus_objects)
|
128
|
+
elsif config_type == :theme_app_extension
|
129
|
+
check_object(info, all_global_objects + theme_app_extension_objects)
|
124
130
|
else
|
125
131
|
check_object(info, all_global_objects)
|
126
132
|
end
|
@@ -129,6 +135,8 @@ module ThemeCheck
|
|
129
135
|
|
130
136
|
private
|
131
137
|
|
138
|
+
attr_reader :config_type
|
139
|
+
|
132
140
|
def ignore?(node)
|
133
141
|
@exclude_snippets && node.theme_file.snippet?
|
134
142
|
end
|
@@ -39,7 +39,12 @@ module ThemeCheck
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def on_variable_lookup(node)
|
42
|
-
@templates[node.theme_file.name].used_assigns << node.value.name
|
42
|
+
@templates[node.theme_file.name].used_assigns << case node.value.name
|
43
|
+
when Liquid::VariableLookup
|
44
|
+
node.value.name.name
|
45
|
+
else
|
46
|
+
node.value.name
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
def on_end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "set"
|
3
4
|
|
4
5
|
module ThemeCheck
|
@@ -11,16 +12,22 @@ module ThemeCheck
|
|
11
12
|
@used_snippets = Set.new
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def on_render(node)
|
15
16
|
if node.value.template_name_expr.is_a?(String)
|
16
17
|
@used_snippets << "snippets/#{node.value.template_name_expr}"
|
18
|
+
|
19
|
+
elsif might_have_a_block_as_variable_lookup?(node)
|
20
|
+
# We ignore this case, because that's a "proper" use case for
|
21
|
+
# the render tag with OS 2.0
|
22
|
+
# {% render block %} shouldn't turn off the UnusedSnippet check
|
23
|
+
|
17
24
|
else
|
18
25
|
# Can't reliably track unused snippets if an expression is used, ignore this check
|
19
26
|
@used_snippets.clear
|
20
27
|
ignore!
|
21
28
|
end
|
22
29
|
end
|
23
|
-
alias_method :
|
30
|
+
alias_method :on_include, :on_render
|
24
31
|
|
25
32
|
def on_end
|
26
33
|
missing_snippets.each do |theme_file|
|
@@ -33,5 +40,46 @@ module ThemeCheck
|
|
33
40
|
def missing_snippets
|
34
41
|
theme.snippets.reject { |t| @used_snippets.include?(t.name) }
|
35
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# This function returns true when the render node passed might have a
|
47
|
+
# variable lookup that refers to a block as template_name_expr.
|
48
|
+
#
|
49
|
+
# e.g.
|
50
|
+
#
|
51
|
+
# {% for block in col %}
|
52
|
+
# {% render block %}
|
53
|
+
# {% endfor %}
|
54
|
+
#
|
55
|
+
# In this case, the `block` variable_lookup in the render tag might be
|
56
|
+
# a Block because col might be an array of blocks.
|
57
|
+
#
|
58
|
+
# @param node [Node]
|
59
|
+
def might_have_a_block_as_variable_lookup?(node)
|
60
|
+
return false unless node.type_name == :render
|
61
|
+
|
62
|
+
return false unless node.value.template_name_expr.is_a?(Liquid::VariableLookup)
|
63
|
+
|
64
|
+
name = node.value.template_name_expr.name
|
65
|
+
return false unless name.is_a?(String)
|
66
|
+
|
67
|
+
# We're going through all the parents of the nodes until we find
|
68
|
+
# a For node with variable_name === to the template_name_expr's name
|
69
|
+
find_parent(node.parent) do |parent_node|
|
70
|
+
next false unless parent_node.type_name == :for
|
71
|
+
|
72
|
+
parent_node.value.variable_name == name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param node [Node]
|
77
|
+
def find_parent(node, &pred)
|
78
|
+
return nil unless node
|
79
|
+
|
80
|
+
return node if yield node
|
81
|
+
|
82
|
+
find_parent(node.parent, &pred)
|
83
|
+
end
|
36
84
|
end
|
37
85
|
end
|
data/lib/theme_check/config.rb
CHANGED
@@ -126,14 +126,14 @@ module ThemeCheck
|
|
126
126
|
options_for_check = options.transform_keys(&:to_sym)
|
127
127
|
options_for_check.delete(:enabled)
|
128
128
|
severity = options_for_check.delete(:severity)
|
129
|
-
|
129
|
+
check_ignored_patterns = options_for_check.delete(:ignore) || []
|
130
130
|
check = if options_for_check.empty?
|
131
131
|
check_class.new
|
132
132
|
else
|
133
133
|
check_class.new(**options_for_check)
|
134
134
|
end
|
135
135
|
check.severity = severity.to_sym if severity
|
136
|
-
check.ignored_patterns = ignored_patterns
|
136
|
+
check.ignored_patterns = check_ignored_patterns + ignored_patterns
|
137
137
|
check.options = options_for_check
|
138
138
|
check
|
139
139
|
end.compact
|
@@ -209,7 +209,8 @@ module ThemeCheck
|
|
209
209
|
|
210
210
|
def resolve_requires
|
211
211
|
self["require"]&.each do |path|
|
212
|
-
|
212
|
+
file_to_require = @root.join(path).realpath
|
213
|
+
require(file_to_require.to_s)
|
213
214
|
end
|
214
215
|
end
|
215
216
|
end
|
@@ -34,14 +34,16 @@ module ThemeCheck
|
|
34
34
|
# We want to disable checks inside comments
|
35
35
|
# (e.g. html checks inside {% comment %})
|
36
36
|
disabled = @disabled_checks[[node.theme_file, :all]]
|
37
|
-
disabled.
|
38
|
-
|
37
|
+
unless disabled.first_line
|
38
|
+
disabled.start_index = node.inner_markup_start_index
|
39
|
+
disabled.end_index = node.inner_markup_end_index
|
40
|
+
end
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
44
|
def disabled?(check, theme_file, check_name, index)
|
43
45
|
return true if check.ignored_patterns&.any? do |pattern|
|
44
|
-
theme_file
|
46
|
+
theme_file&.relative_path&.fnmatch?(pattern)
|
45
47
|
end
|
46
48
|
|
47
49
|
@disabled_checks[[theme_file, :all]]&.disabled?(index) ||
|
@@ -65,7 +67,12 @@ module ThemeCheck
|
|
65
67
|
private
|
66
68
|
|
67
69
|
def comment_text(node)
|
68
|
-
node.
|
70
|
+
case node.type_name
|
71
|
+
when :comment
|
72
|
+
node.value.nodelist.join
|
73
|
+
when :inline_comment
|
74
|
+
node.markup.sub(/\s*#+\s*/, '')
|
75
|
+
end
|
69
76
|
end
|
70
77
|
|
71
78
|
def start_disabling?(text)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "timeout"
|
4
|
+
|
3
5
|
# This class exists as a bridge (or boundary) between our handlers and the outside world.
|
4
6
|
#
|
5
7
|
# It is concerned with all the Language Server Protocol constructs. i.e.
|
@@ -29,6 +31,9 @@ module ThemeCheck
|
|
29
31
|
|
30
32
|
# Whether the client supports work done progress notifications
|
31
33
|
@supports_work_done_progress = false
|
34
|
+
|
35
|
+
@work_done_progress_mutex = Mutex.new
|
36
|
+
@work_done_progress_token = 1
|
32
37
|
end
|
33
38
|
|
34
39
|
def log(message)
|
@@ -78,12 +83,17 @@ module ThemeCheck
|
|
78
83
|
|
79
84
|
# https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#responseError
|
80
85
|
def send_internal_error(id, e)
|
86
|
+
# For a reason I can't comprehend, sometimes
|
87
|
+
# e.full_message _hangs_ and brings your CPU to 100%.
|
88
|
+
# It's wrapped in here because it prints anyway...
|
89
|
+
# This shit is weird, yo.
|
90
|
+
Timeout.timeout(1) do
|
91
|
+
$stderr.puts e.full_message
|
92
|
+
end
|
93
|
+
ensure
|
81
94
|
send_response(id, nil, {
|
82
95
|
code: ErrorCodes::INTERNAL_ERROR,
|
83
|
-
message:
|
84
|
-
#{e.class}: #{e.message}
|
85
|
-
#{e.backtrace.join("\n ")}
|
86
|
-
EOS
|
96
|
+
message: "A theme-check-language-server has occured, inspect OUTPUT logs for details.",
|
87
97
|
})
|
88
98
|
end
|
89
99
|
|
@@ -103,15 +113,28 @@ module ThemeCheck
|
|
103
113
|
@supports_work_done_progress
|
104
114
|
end
|
105
115
|
|
106
|
-
def send_create_work_done_progress_request
|
107
|
-
|
116
|
+
def send_create_work_done_progress_request
|
117
|
+
# This isn't necessary, but it kind of is to make it obvious
|
118
|
+
# that this variable is not thread safe. Don't try to refactor
|
119
|
+
# this with @work_done_progress_token because you're going to
|
120
|
+
# have a hard time.
|
121
|
+
token = @work_done_progress_mutex.synchronize do
|
122
|
+
@work_done_progress_token += 1
|
123
|
+
end
|
124
|
+
|
125
|
+
return token unless supports_work_done_progress?
|
126
|
+
|
127
|
+
# We're going to wait for a response here...
|
108
128
|
send_request("window/workDoneProgress/create", {
|
109
129
|
token: token,
|
110
130
|
})
|
131
|
+
|
132
|
+
token
|
111
133
|
end
|
112
134
|
|
113
135
|
def send_work_done_progress_begin(token, title)
|
114
136
|
return unless supports_work_done_progress?
|
137
|
+
|
115
138
|
send_progress(token, {
|
116
139
|
kind: 'begin',
|
117
140
|
title: title,
|
@@ -122,6 +145,7 @@ module ThemeCheck
|
|
122
145
|
|
123
146
|
def send_work_done_progress_report(token, message, percentage)
|
124
147
|
return unless supports_work_done_progress?
|
148
|
+
|
125
149
|
send_progress(token, {
|
126
150
|
kind: 'report',
|
127
151
|
message: message,
|
@@ -132,6 +156,7 @@ module ThemeCheck
|
|
132
156
|
|
133
157
|
def send_work_done_progress_end(token, message)
|
134
158
|
return unless supports_work_done_progress?
|
159
|
+
|
135
160
|
send_progress(token, {
|
136
161
|
kind: 'end',
|
137
162
|
message: message,
|
@@ -12,57 +12,103 @@ module ThemeCheck
|
|
12
12
|
@diagnostics_manager = diagnostics_manager
|
13
13
|
@storage = storage
|
14
14
|
@bridge = bridge
|
15
|
-
@token = 0
|
16
15
|
end
|
17
16
|
|
18
17
|
def first_run?
|
19
18
|
@diagnostics_manager.first_run?
|
20
19
|
end
|
21
20
|
|
22
|
-
def analyze_and_send_offenses(
|
21
|
+
def analyze_and_send_offenses(absolute_path_or_paths, config, force: false, only_single_file: false)
|
23
22
|
return unless @diagnostics_lock.try_lock
|
24
|
-
|
25
|
-
@bridge.send_create_work_done_progress_request(@token)
|
23
|
+
|
26
24
|
theme = ThemeCheck::Theme.new(storage)
|
27
25
|
analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
|
28
26
|
|
29
|
-
if
|
30
|
-
|
31
|
-
@bridge.log("Checking #{storage.root}")
|
32
|
-
offenses = nil
|
33
|
-
time = Benchmark.measure do
|
34
|
-
offenses = analyzer.analyze_theme do |path, i, total|
|
35
|
-
@bridge.send_work_done_progress_report(@token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
|
39
|
-
@bridge.send_work_done_progress_end(@token, end_message)
|
40
|
-
@bridge.log(end_message)
|
41
|
-
send_diagnostics(offenses)
|
27
|
+
if !only_single_file && (@diagnostics_manager.first_run? || force)
|
28
|
+
run_full_theme_check(analyzer)
|
42
29
|
else
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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, [relative_path], only_single_file: only_single_file)
|
59
|
-
end
|
30
|
+
run_partial_theme_check(absolute_path_or_paths, theme, analyzer, only_single_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
@diagnostics_lock.unlock
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear_diagnostics(relative_paths)
|
37
|
+
return unless @diagnostics_lock.try_lock
|
38
|
+
|
39
|
+
as_array(relative_paths).each do |relative_path|
|
40
|
+
send_clearing_diagnostics(relative_path)
|
60
41
|
end
|
42
|
+
|
61
43
|
@diagnostics_lock.unlock
|
62
44
|
end
|
63
45
|
|
64
46
|
private
|
65
47
|
|
48
|
+
def run_full_theme_check(analyzer)
|
49
|
+
raise 'Unsafe operation' unless @diagnostics_lock.owned?
|
50
|
+
|
51
|
+
token = @bridge.send_create_work_done_progress_request
|
52
|
+
@bridge.send_work_done_progress_begin(token, "Full theme check")
|
53
|
+
@bridge.log("Checking #{storage.root}")
|
54
|
+
offenses = nil
|
55
|
+
time = Benchmark.measure do
|
56
|
+
offenses = analyzer.analyze_theme do |path, i, total|
|
57
|
+
@bridge.send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
|
61
|
+
@bridge.send_work_done_progress_end(token, end_message)
|
62
|
+
@bridge.log(end_message)
|
63
|
+
send_diagnostics(offenses)
|
64
|
+
end
|
65
|
+
|
66
|
+
def run_partial_theme_check(absolute_path_or_paths, theme, analyzer, only_single_file)
|
67
|
+
raise 'Unsafe operation' unless @diagnostics_lock.owned?
|
68
|
+
|
69
|
+
relative_paths = as_array(absolute_path_or_paths).map do |absolute_path|
|
70
|
+
Pathname.new(storage.relative_path(absolute_path))
|
71
|
+
end
|
72
|
+
|
73
|
+
theme_files = relative_paths
|
74
|
+
.map { |relative_path| theme[relative_path] }
|
75
|
+
.reject(&:nil?)
|
76
|
+
|
77
|
+
deleted_relative_paths = relative_paths - theme_files.map(&:relative_path)
|
78
|
+
deleted_relative_paths
|
79
|
+
.each { |p| send_clearing_diagnostics(p) }
|
80
|
+
|
81
|
+
token = @bridge.send_create_work_done_progress_request
|
82
|
+
@bridge.send_work_done_progress_begin(token, "Partial theme check")
|
83
|
+
offenses = nil
|
84
|
+
time = Benchmark.measure do
|
85
|
+
offenses = analyzer.analyze_files(theme_files, only_single_file: only_single_file) do |path, i, total|
|
86
|
+
@bridge.send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
|
90
|
+
@bridge.send_work_done_progress_end(token, end_message)
|
91
|
+
@bridge.log(end_message)
|
92
|
+
send_diagnostics(offenses, theme_files.map(&:relative_path), only_single_file: only_single_file)
|
93
|
+
end
|
94
|
+
|
95
|
+
def send_clearing_diagnostics(relative_path)
|
96
|
+
raise 'Unsafe operation' unless @diagnostics_lock.owned?
|
97
|
+
|
98
|
+
relative_path = Pathname.new(relative_path) unless relative_path.is_a?(Pathname)
|
99
|
+
@diagnostics_manager.clear_diagnostics(relative_path)
|
100
|
+
send_diagnostic(relative_path, DiagnosticsManager::NO_DIAGNOSTICS)
|
101
|
+
end
|
102
|
+
|
103
|
+
def as_array(maybe_array)
|
104
|
+
case maybe_array
|
105
|
+
when Array
|
106
|
+
maybe_array
|
107
|
+
else
|
108
|
+
[maybe_array]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
66
112
|
def send_diagnostics(offenses, analyzed_files = nil, only_single_file: false)
|
67
113
|
@diagnostics_manager.build_diagnostics(
|
68
114
|
offenses,
|
@@ -4,6 +4,11 @@ require "logger"
|
|
4
4
|
module ThemeCheck
|
5
5
|
module LanguageServer
|
6
6
|
class DiagnosticsManager
|
7
|
+
# The empty array is used in the protocol to mean that no
|
8
|
+
# diagnostics exist for this file. It's not always evident when
|
9
|
+
# reading code.
|
10
|
+
NO_DIAGNOSTICS = [].freeze
|
11
|
+
|
7
12
|
# This class exists to facilitate LanguageServer diagnostics tracking.
|
8
13
|
#
|
9
14
|
# Motivations:
|
@@ -49,27 +54,27 @@ module ThemeCheck
|
|
49
54
|
# When doing single file checks, we keep the whole theme old
|
50
55
|
# ones and accept the new single ones
|
51
56
|
if only_single_file && analyzed_paths.include?(path)
|
52
|
-
single_file_diagnostics = current_diagnostics[path] ||
|
53
|
-
whole_theme_diagnostics = whole_theme_diagnostics(path) ||
|
57
|
+
single_file_diagnostics = current_diagnostics[path] || NO_DIAGNOSTICS
|
58
|
+
whole_theme_diagnostics = whole_theme_diagnostics(path) || NO_DIAGNOSTICS
|
54
59
|
[path, single_file_diagnostics + whole_theme_diagnostics]
|
55
60
|
|
56
61
|
# If doing single file checks that are not in the
|
57
62
|
# analyzed_paths array then we just keep the old
|
58
63
|
# diagnostics
|
59
64
|
elsif only_single_file
|
60
|
-
[path, previous_diagnostics(path) ||
|
65
|
+
[path, previous_diagnostics(path) || NO_DIAGNOSTICS]
|
61
66
|
|
62
67
|
# When doing a full_check, we either send the current
|
63
68
|
# diagnostics or an empty array to clear the diagnostics
|
64
69
|
# for that file.
|
65
70
|
elsif full_check
|
66
|
-
[path, current_diagnostics[path] ||
|
71
|
+
[path, current_diagnostics[path] || NO_DIAGNOSTICS]
|
67
72
|
|
68
73
|
# When doing a partial check, the single file diagnostics
|
69
74
|
# from the previous runs should be sent. Otherwise the
|
70
75
|
# latest results are the good ones.
|
71
76
|
else
|
72
|
-
new_diagnostics = current_diagnostics[path] ||
|
77
|
+
new_diagnostics = current_diagnostics[path] || NO_DIAGNOSTICS
|
73
78
|
should_use_cached_results = !analyzed_paths.include?(path)
|
74
79
|
old_diagnostics = should_use_cached_results ? single_file_diagnostics(path) : []
|
75
80
|
[path, new_diagnostics + old_diagnostics]
|
@@ -113,6 +118,13 @@ module ThemeCheck
|
|
113
118
|
end.to_h
|
114
119
|
end
|
115
120
|
|
121
|
+
# For when you know there shouldn't be anything on that file
|
122
|
+
# anymore. (e.g. file delete or file rename)
|
123
|
+
def clear_diagnostics(relative_path)
|
124
|
+
relative_path = sanitize_path(relative_path)
|
125
|
+
@latest_diagnostics.delete(relative_path)
|
126
|
+
end
|
127
|
+
|
116
128
|
private
|
117
129
|
|
118
130
|
def sanitize(diagnostics)
|
@@ -120,8 +132,17 @@ module ThemeCheck
|
|
120
132
|
diagnostics
|
121
133
|
end
|
122
134
|
|
135
|
+
def sanitize_path(relative_path)
|
136
|
+
case relative_path
|
137
|
+
when String
|
138
|
+
Pathname.new(relative_path)
|
139
|
+
else
|
140
|
+
relative_path
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
123
144
|
def delete(relative_path, diagnostic)
|
124
|
-
relative_path =
|
145
|
+
relative_path = sanitize_path(relative_path)
|
125
146
|
@mutex.synchronize do
|
126
147
|
@latest_diagnostics[relative_path]&.delete(diagnostic)
|
127
148
|
@latest_diagnostics.delete(relative_path) if @latest_diagnostics[relative_path]&.empty?
|
@@ -7,17 +7,18 @@ module ThemeCheck
|
|
7
7
|
|
8
8
|
command "runChecks"
|
9
9
|
|
10
|
-
def initialize(diagnostics_engine,
|
10
|
+
def initialize(diagnostics_engine, storage, linter_config, language_server_config)
|
11
11
|
@diagnostics_engine = diagnostics_engine
|
12
|
-
@
|
13
|
-
@
|
12
|
+
@storage = storage
|
13
|
+
@linter_config = linter_config
|
14
|
+
@language_server_config = language_server_config
|
14
15
|
end
|
15
16
|
|
16
17
|
def execute(_args)
|
17
18
|
@diagnostics_engine.analyze_and_send_offenses(
|
18
|
-
@
|
19
|
-
@
|
20
|
-
only_single_file:
|
19
|
+
@storage.opened_files.map { |relative_path| @storage.path(relative_path) },
|
20
|
+
@linter_config,
|
21
|
+
only_single_file: @language_server_config.only_single_file?,
|
21
22
|
force: true
|
22
23
|
)
|
23
24
|
nil
|