theme-check 1.10.1 → 1.11.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/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
|