theme-check 1.8.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +21 -0
- data/README.md +10 -0
- data/RELEASING.md +13 -0
- data/config/default.yml +5 -0
- data/data/shopify_liquid/deprecated_filters.yml +4 -0
- data/data/shopify_liquid/filters.yml +2 -1
- data/docs/checks/schema_json_format.md +76 -0
- data/docs/language_server/code-action-command-palette.png +0 -0
- data/docs/language_server/code-action-flow.png +0 -0
- data/docs/language_server/code-action-keyboard.png +0 -0
- data/docs/language_server/code-action-light-bulb.png +0 -0
- data/docs/language_server/code-action-problem.png +0 -0
- data/docs/language_server/code-action-quickfix.png +0 -0
- data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
- data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
- data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
- data/lib/theme_check/checks/asset_url_filters.rb +2 -0
- data/lib/theme_check/checks/default_locale.rb +1 -1
- data/lib/theme_check/checks/deprecated_filter.rb +79 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +4 -6
- data/lib/theme_check/checks/matching_translations.rb +1 -0
- data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +1 -1
- data/lib/theme_check/checks/pagination_size.rb +2 -3
- data/lib/theme_check/checks/remote_asset.rb +5 -0
- data/lib/theme_check/checks/required_directories.rb +1 -1
- data/lib/theme_check/checks/schema_json_format.rb +29 -0
- data/lib/theme_check/checks/space_inside_braces.rb +132 -87
- data/lib/theme_check/checks/translation_key_exists.rb +33 -13
- data/lib/theme_check/checks/unused_snippet.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks/valid_schema.rb +2 -2
- data/lib/theme_check/corrector.rb +28 -54
- data/lib/theme_check/file_system_storage.rb +4 -3
- data/lib/theme_check/html_node.rb +99 -6
- data/lib/theme_check/html_visitor.rb +1 -32
- data/lib/theme_check/in_memory_storage.rb +9 -0
- data/lib/theme_check/json_helpers.rb +14 -0
- data/lib/theme_check/language_server/bridge.rb +1 -1
- data/lib/theme_check/language_server/client_capabilities.rb +27 -0
- data/lib/theme_check/language_server/code_action_engine.rb +32 -0
- data/lib/theme_check/language_server/code_action_provider.rb +42 -0
- data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
- data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
- data/lib/theme_check/language_server/configuration.rb +69 -0
- data/lib/theme_check/language_server/diagnostic.rb +124 -0
- data/lib/theme_check/language_server/diagnostics_engine.rb +15 -60
- data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
- data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
- data/lib/theme_check/language_server/document_link_provider.rb +6 -6
- data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
- data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
- data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
- data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
- data/lib/theme_check/language_server/handler.rb +79 -28
- data/lib/theme_check/language_server/io_messenger.rb +9 -1
- data/lib/theme_check/language_server/server.rb +8 -7
- data/lib/theme_check/language_server/uri_helper.rb +1 -0
- data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
- data/lib/theme_check/language_server.rb +23 -5
- data/lib/theme_check/liquid_node.rb +249 -39
- data/lib/theme_check/locale_diff.rb +16 -4
- data/lib/theme_check/node.rb +16 -0
- data/lib/theme_check/offense.rb +27 -23
- data/lib/theme_check/regex_helpers.rb +1 -1
- data/lib/theme_check/schema_helper.rb +70 -0
- data/lib/theme_check/storage.rb +4 -0
- data/lib/theme_check/theme.rb +1 -1
- data/lib/theme_check/theme_file.rb +8 -1
- data/lib/theme_check/theme_file_rewriter.rb +18 -9
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +7 -2
- metadata +26 -3
- data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
@@ -17,7 +17,16 @@ module ThemeCheck
|
|
17
17
|
triggerCharacters: ['.', '{{ ', '{% '],
|
18
18
|
context: true,
|
19
19
|
},
|
20
|
+
codeActionProvider: {
|
21
|
+
codeActionKinds: CodeActionProvider.all.map(&:kind),
|
22
|
+
resolveProvider: false,
|
23
|
+
workDoneProgress: false,
|
24
|
+
},
|
20
25
|
documentLinkProvider: true,
|
26
|
+
executeCommandProvider: {
|
27
|
+
workDoneProgress: false,
|
28
|
+
commands: ExecuteCommandProvider.all.map(&:command),
|
29
|
+
},
|
21
30
|
textDocumentSync: {
|
22
31
|
openClose: true,
|
23
32
|
change: TextDocumentSyncKind::FULL,
|
@@ -36,17 +45,29 @@ module ThemeCheck
|
|
36
45
|
# Tell the client we don't support anything if there's no rootPath
|
37
46
|
return @bridge.send_response(id, { capabilities: {} }) if @root_path.nil?
|
38
47
|
|
39
|
-
@
|
48
|
+
@client_capabilities = ClientCapabilities.new(params.dig(:capabilities) || {})
|
49
|
+
@configuration = Configuration.new(@bridge, @client_capabilities)
|
50
|
+
@bridge.supports_work_done_progress = @client_capabilities.supports_work_done_progress?
|
40
51
|
@storage = in_memory_storage(@root_path)
|
52
|
+
@diagnostics_manager = DiagnosticsManager.new
|
41
53
|
@completion_engine = CompletionEngine.new(@storage)
|
42
54
|
@document_link_engine = DocumentLinkEngine.new(@storage)
|
43
|
-
@diagnostics_engine = DiagnosticsEngine.new(@bridge)
|
55
|
+
@diagnostics_engine = DiagnosticsEngine.new(@storage, @bridge, @diagnostics_manager)
|
56
|
+
@execute_command_engine = ExecuteCommandEngine.new
|
57
|
+
@execute_command_engine << CorrectionExecuteCommandProvider.new(@storage, @bridge, @diagnostics_manager)
|
58
|
+
@execute_command_engine << RunChecksExecuteCommandProvider.new(@diagnostics_engine, @root_path, config_for_path(@root_path))
|
59
|
+
@code_action_engine = CodeActionEngine.new(@storage, @diagnostics_manager)
|
44
60
|
@bridge.send_response(id, {
|
45
61
|
capabilities: CAPABILITIES,
|
46
62
|
serverInfo: SERVER_INFO,
|
47
63
|
})
|
48
64
|
end
|
49
65
|
|
66
|
+
def on_initialized(_id, _params)
|
67
|
+
@configuration.fetch
|
68
|
+
@configuration.register_did_change_capability
|
69
|
+
end
|
70
|
+
|
50
71
|
def on_shutdown(id, _params)
|
51
72
|
@bridge.send_response(id, nil)
|
52
73
|
end
|
@@ -55,36 +76,63 @@ module ThemeCheck
|
|
55
76
|
close!
|
56
77
|
end
|
57
78
|
|
58
|
-
def
|
79
|
+
def on_text_document_did_open(_id, params)
|
59
80
|
relative_path = relative_path_from_text_document_uri(params)
|
60
|
-
@storage.write(relative_path,
|
81
|
+
@storage.write(relative_path, text_document_text(params), text_document_version(params))
|
82
|
+
analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_open?
|
61
83
|
end
|
62
84
|
|
63
|
-
def
|
85
|
+
def on_text_document_did_change(_id, params)
|
64
86
|
relative_path = relative_path_from_text_document_uri(params)
|
65
|
-
@storage.write(relative_path,
|
87
|
+
@storage.write(relative_path, content_changes_text(params), text_document_version(params))
|
88
|
+
analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_change?
|
66
89
|
end
|
67
90
|
|
68
|
-
def
|
91
|
+
def on_text_document_did_close(_id, params)
|
69
92
|
relative_path = relative_path_from_text_document_uri(params)
|
70
|
-
|
71
|
-
|
93
|
+
file_system_content = Pathname.new(text_document_uri(params)).read(mode: 'rb', encoding: 'UTF-8')
|
94
|
+
# On close, the file system becomes the source of truth
|
95
|
+
@storage.write(relative_path, file_system_content, nil)
|
72
96
|
end
|
73
97
|
|
74
98
|
def on_text_document_did_save(_id, params)
|
75
|
-
analyze_and_send_offenses(text_document_uri(params))
|
99
|
+
analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_save?
|
76
100
|
end
|
77
101
|
|
78
102
|
def on_text_document_document_link(id, params)
|
79
103
|
relative_path = relative_path_from_text_document_uri(params)
|
80
|
-
@bridge.send_response(id, document_links(relative_path))
|
104
|
+
@bridge.send_response(id, @document_link_engine.document_links(relative_path))
|
81
105
|
end
|
82
106
|
|
83
107
|
def on_text_document_completion(id, params)
|
84
108
|
relative_path = relative_path_from_text_document_uri(params)
|
85
|
-
line = params.dig(
|
86
|
-
col = params.dig(
|
87
|
-
@bridge.send_response(id, completions(relative_path, line, col))
|
109
|
+
line = params.dig(:position, :line)
|
110
|
+
col = params.dig(:position, :character)
|
111
|
+
@bridge.send_response(id, @completion_engine.completions(relative_path, line, col))
|
112
|
+
end
|
113
|
+
|
114
|
+
def on_text_document_code_action(id, params)
|
115
|
+
absolute_path = text_document_uri(params)
|
116
|
+
start_position = range_element(params, :start)
|
117
|
+
end_position = range_element(params, :end)
|
118
|
+
only_code_action_kinds = params.dig(:context, :only) || []
|
119
|
+
@bridge.send_response(id, @code_action_engine.code_actions(
|
120
|
+
absolute_path,
|
121
|
+
start_position,
|
122
|
+
end_position,
|
123
|
+
only_code_action_kinds,
|
124
|
+
))
|
125
|
+
end
|
126
|
+
|
127
|
+
def on_workspace_execute_command(id, params)
|
128
|
+
@bridge.send_response(id, @execute_command_engine.execute(
|
129
|
+
params[:command],
|
130
|
+
params[:arguments],
|
131
|
+
))
|
132
|
+
end
|
133
|
+
|
134
|
+
def on_workspace_did_change_configuration(_id, _params)
|
135
|
+
@configuration.fetch(force: true)
|
88
136
|
end
|
89
137
|
|
90
138
|
private
|
@@ -98,16 +146,16 @@ module ThemeCheck
|
|
98
146
|
ignored_patterns: config.ignored_patterns
|
99
147
|
)
|
100
148
|
|
101
|
-
# Turn that into a hash of
|
149
|
+
# Turn that into a hash of buffers
|
102
150
|
files = fs.files
|
103
|
-
.map { |fn| [fn,
|
151
|
+
.map { |fn| [fn, fs.read(fn)] }
|
104
152
|
.to_h
|
105
153
|
|
106
|
-
|
154
|
+
VersionedInMemoryStorage.new(files, config.root)
|
107
155
|
end
|
108
156
|
|
109
157
|
def text_document_uri(params)
|
110
|
-
file_path(params.dig(
|
158
|
+
file_path(params.dig(:textDocument, :uri))
|
111
159
|
end
|
112
160
|
|
113
161
|
def relative_path_from_text_document_uri(params)
|
@@ -115,8 +163,8 @@ module ThemeCheck
|
|
115
163
|
end
|
116
164
|
|
117
165
|
def root_path_from_params(params)
|
118
|
-
root_uri = params[
|
119
|
-
root_path = params[
|
166
|
+
root_uri = params[:rootUri]
|
167
|
+
root_path = params[:rootPath]
|
120
168
|
if root_uri
|
121
169
|
file_path(root_uri)
|
122
170
|
elsif root_path
|
@@ -125,11 +173,15 @@ module ThemeCheck
|
|
125
173
|
end
|
126
174
|
|
127
175
|
def text_document_text(params)
|
128
|
-
params.dig(
|
176
|
+
params.dig(:textDocument, :text)
|
177
|
+
end
|
178
|
+
|
179
|
+
def text_document_version(params)
|
180
|
+
params.dig(:textDocument, :version)
|
129
181
|
end
|
130
182
|
|
131
183
|
def content_changes_text(params)
|
132
|
-
params.dig(
|
184
|
+
params.dig(:contentChanges, 0, :text)
|
133
185
|
end
|
134
186
|
|
135
187
|
def config_for_path(path)
|
@@ -144,12 +196,11 @@ module ThemeCheck
|
|
144
196
|
)
|
145
197
|
end
|
146
198
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
@document_link_engine.document_links(relative_path)
|
199
|
+
def range_element(params, start_or_end)
|
200
|
+
[
|
201
|
+
params.dig(:range, start_or_end, :line),
|
202
|
+
params.dig(:range, start_or_end, :character),
|
203
|
+
]
|
153
204
|
end
|
154
205
|
|
155
206
|
def log(message)
|
@@ -3,10 +3,18 @@
|
|
3
3
|
module ThemeCheck
|
4
4
|
module LanguageServer
|
5
5
|
class IOMessenger < Messenger
|
6
|
+
def self.err_stream
|
7
|
+
if ThemeCheck.debug_log_file
|
8
|
+
File.open(ThemeCheck.debug_log_file, "w")
|
9
|
+
else
|
10
|
+
STDERR
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
def initialize(
|
7
15
|
in_stream: STDIN,
|
8
16
|
out_stream: STDOUT,
|
9
|
-
err_stream:
|
17
|
+
err_stream: IOMessenger.err_stream
|
10
18
|
)
|
11
19
|
validate!([in_stream, out_stream, err_stream])
|
12
20
|
|
@@ -55,9 +55,9 @@ module ThemeCheck
|
|
55
55
|
@json_rpc_thread = Thread.new do
|
56
56
|
loop do
|
57
57
|
message = @bridge.read_message
|
58
|
-
if message[
|
58
|
+
if message[:method] == 'initialize'
|
59
59
|
handle_message(message)
|
60
|
-
elsif message.key?(
|
60
|
+
elsif message.key?(:result)
|
61
61
|
# Responses are handled on the main thread to prevent
|
62
62
|
# a potential deadlock caused by all handlers waiting
|
63
63
|
# for a responses.
|
@@ -101,10 +101,10 @@ module ThemeCheck
|
|
101
101
|
private
|
102
102
|
|
103
103
|
def handle_message(message)
|
104
|
-
id = message[
|
105
|
-
method_name = message[
|
104
|
+
id = message[:id]
|
105
|
+
method_name = message[:method]
|
106
106
|
method_name &&= "on_#{to_snake_case(method_name)}"
|
107
|
-
params = message[
|
107
|
+
params = message[:params]
|
108
108
|
|
109
109
|
if @handler.respond_to?(method_name)
|
110
110
|
@handler.send(method_name, id, params)
|
@@ -117,12 +117,13 @@ module ThemeCheck
|
|
117
117
|
raise e unless is_request
|
118
118
|
# Errors obtained in request handlers should be sent
|
119
119
|
# back as internal errors instead of closing the program.
|
120
|
+
@bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
120
121
|
@bridge.send_internal_error(id, e)
|
121
122
|
end
|
122
123
|
|
123
124
|
def handle_response(message)
|
124
|
-
id = message[
|
125
|
-
result = message[
|
125
|
+
id = message[:id]
|
126
|
+
result = message[:result]
|
126
127
|
@bridge.receive_response(id, result)
|
127
128
|
end
|
128
129
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
class VersionedInMemoryStorage < InMemoryStorage
|
5
|
+
Version = Struct.new(:id, :version)
|
6
|
+
|
7
|
+
attr_reader :versions
|
8
|
+
|
9
|
+
def initialize(files, root = "/dev/null")
|
10
|
+
super(files, root)
|
11
|
+
@versions = {}
|
12
|
+
@mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Motivations:
|
16
|
+
# - Need way for LanguageServer to know on which version of a file
|
17
|
+
# the check was run on, because we need to know where the
|
18
|
+
# TextEdit goes. If the text changed, our TextEdit might not be
|
19
|
+
# in the right spot. e.g.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# ```
|
24
|
+
# Hi
|
25
|
+
# {{world}}
|
26
|
+
# ```
|
27
|
+
#
|
28
|
+
# Would produce two "SpaceInsideBrace" errors:
|
29
|
+
#
|
30
|
+
# - One after {{ at index 5 to 6
|
31
|
+
# - One before }} at index 10 to 11
|
32
|
+
#
|
33
|
+
# If the user goes in and changes Hi to Sup, and _then_
|
34
|
+
# right clicks to apply the code edit at index 5 to 6, he'd
|
35
|
+
# get the following:
|
36
|
+
#
|
37
|
+
# ```
|
38
|
+
# Sup
|
39
|
+
# { {world}}
|
40
|
+
# ```
|
41
|
+
#
|
42
|
+
# Which is not a fix at all.
|
43
|
+
#
|
44
|
+
# Solution:
|
45
|
+
# - Have the LanguageServer store the version on textDocument/did{Open,Change,Close}
|
46
|
+
# - Have ThemeFile store the version right after @storage.read.
|
47
|
+
# - Add version to the diagnostic meta data
|
48
|
+
# - Use diagnostic meta data to determine if we can make a code edit or not
|
49
|
+
# - Only offer fixes on "clean" files (or offer the change but specify the version so the editor knows what to do with it)
|
50
|
+
def write(relative_path, content, version)
|
51
|
+
@mutex.synchronize do
|
52
|
+
@versions[relative_path] = version
|
53
|
+
super(relative_path, content)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_version(relative_path)
|
58
|
+
@mutex.synchronize { [read(relative_path), version(relative_path)] }
|
59
|
+
end
|
60
|
+
|
61
|
+
def versioned?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def version(relative_path)
|
66
|
+
@versions[relative_path.to_s]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,31 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative "language_server/protocol"
|
3
3
|
require_relative "language_server/constants"
|
4
|
+
require_relative "language_server/configuration"
|
4
5
|
require_relative "language_server/channel"
|
5
6
|
require_relative "language_server/messenger"
|
6
7
|
require_relative "language_server/io_messenger"
|
7
8
|
require_relative "language_server/bridge"
|
8
9
|
require_relative "language_server/uri_helper"
|
9
|
-
require_relative "language_server/handler"
|
10
10
|
require_relative "language_server/server"
|
11
11
|
require_relative "language_server/tokens"
|
12
12
|
require_relative "language_server/variable_lookup_finder"
|
13
|
+
require_relative "language_server/diagnostic"
|
14
|
+
require_relative "language_server/diagnostics_manager"
|
15
|
+
require_relative "language_server/diagnostics_engine"
|
16
|
+
require_relative "language_server/document_change_corrector"
|
17
|
+
require_relative "language_server/versioned_in_memory_storage"
|
18
|
+
require_relative "language_server/client_capabilities"
|
19
|
+
|
13
20
|
require_relative "language_server/completion_helper"
|
14
21
|
require_relative "language_server/completion_provider"
|
15
22
|
require_relative "language_server/completion_engine"
|
23
|
+
Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
|
24
|
+
require file
|
25
|
+
end
|
26
|
+
|
16
27
|
require_relative "language_server/document_link_provider"
|
17
28
|
require_relative "language_server/document_link_engine"
|
18
|
-
|
19
|
-
|
29
|
+
Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
|
30
|
+
require file
|
31
|
+
end
|
20
32
|
|
21
|
-
|
33
|
+
require_relative "language_server/execute_command_provider"
|
34
|
+
require_relative "language_server/execute_command_engine"
|
35
|
+
Dir[__dir__ + "/language_server/execute_command_providers/*.rb"].each do |file|
|
22
36
|
require file
|
23
37
|
end
|
24
38
|
|
25
|
-
|
39
|
+
require_relative "language_server/code_action_provider"
|
40
|
+
require_relative "language_server/code_action_engine"
|
41
|
+
Dir[__dir__ + "/language_server/code_action_providers/*.rb"].each do |file|
|
26
42
|
require file
|
27
43
|
end
|
28
44
|
|
45
|
+
require_relative "language_server/handler"
|
46
|
+
|
29
47
|
module ThemeCheck
|
30
48
|
module LanguageServer
|
31
49
|
def self.start
|