theme-check 1.8.0 → 1.9.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/.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
|