theme-check 1.7.2 → 1.9.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/.gitignore +1 -0
- data/CHANGELOG.md +47 -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 +3 -1
- data/docs/checks/TEMPLATE.md.erb +24 -19
- 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/exe/theme-check-language-server +0 -4
- 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 +81 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
- data/lib/theme_check/checks/matching_schema_translations.rb +14 -9
- 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/required_layout_theme_object.rb +9 -4
- 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_assign.rb +3 -2
- 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 +34 -23
- data/lib/theme_check/file_system_storage.rb +4 -3
- data/lib/theme_check/html_node.rb +122 -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 +19 -5
- 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 +83 -29
- data/lib/theme_check/language_server/io_messenger.rb +11 -1
- data/lib/theme_check/language_server/protocol.rb +4 -0
- data/lib/theme_check/language_server/server.rb +29 -11
- 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 +255 -12
- data/lib/theme_check/locale_diff.rb +39 -8
- data/lib/theme_check/node.rb +16 -0
- data/lib/theme_check/offense.rb +27 -23
- data/lib/theme_check/position.rb +4 -4
- 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/tags.rb +0 -1
- 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 +28 -6
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +11 -2
- metadata +26 -3
- data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
|
@@ -37,12 +37,12 @@ module ThemeCheck
|
|
|
37
37
|
|
|
38
38
|
def document_links(buffer)
|
|
39
39
|
matches(buffer, partial_regexp).map do |match|
|
|
40
|
-
|
|
40
|
+
start_row, start_column = from_index_to_row_column(
|
|
41
41
|
buffer,
|
|
42
42
|
match.begin(:partial),
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
end_row, end_column = from_index_to_row_column(
|
|
46
46
|
buffer,
|
|
47
47
|
match.end(:partial)
|
|
48
48
|
)
|
|
@@ -51,12 +51,12 @@ module ThemeCheck
|
|
|
51
51
|
target: file_link(match[:partial]),
|
|
52
52
|
range: {
|
|
53
53
|
start: {
|
|
54
|
-
line:
|
|
55
|
-
character:
|
|
54
|
+
line: start_row,
|
|
55
|
+
character: start_column,
|
|
56
56
|
},
|
|
57
57
|
end: {
|
|
58
|
-
line:
|
|
59
|
-
character:
|
|
58
|
+
line: end_row,
|
|
59
|
+
character: end_column,
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class ExecuteCommandEngine
|
|
6
|
+
def initialize
|
|
7
|
+
@providers = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def <<(provider)
|
|
11
|
+
@providers[provider.command] = provider
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute(command, arguments)
|
|
15
|
+
@providers[command].execute(arguments)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class ExecuteCommandProvider
|
|
6
|
+
class << self
|
|
7
|
+
def all
|
|
8
|
+
@all ||= []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inherited(subclass)
|
|
12
|
+
all << subclass
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def command(cmd = nil)
|
|
16
|
+
@command = cmd unless cmd.nil?
|
|
17
|
+
@command
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute(arguments)
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def command
|
|
26
|
+
self.class.command
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class CorrectionExecuteCommandProvider < ExecuteCommandProvider
|
|
6
|
+
include URIHelper
|
|
7
|
+
|
|
8
|
+
command "correction"
|
|
9
|
+
|
|
10
|
+
attr_reader :storage, :bridge, :diagnostics_manager
|
|
11
|
+
|
|
12
|
+
def initialize(storage, bridge, diagnostics_manager)
|
|
13
|
+
@storage = storage
|
|
14
|
+
@bridge = bridge
|
|
15
|
+
@diagnostics_manager = diagnostics_manager
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The arguments passed to this method are the ones forwarded
|
|
19
|
+
# from the selected CodeAction by the client.
|
|
20
|
+
#
|
|
21
|
+
# @param diagnostic_hashes [Array] - of diagnostics
|
|
22
|
+
def execute(diagnostic_hashes)
|
|
23
|
+
# attempt to apply the document changes
|
|
24
|
+
workspace_edit = diagnostics_manager.workspace_edit(diagnostic_hashes)
|
|
25
|
+
result = bridge.send_request('workspace/applyEdit', {
|
|
26
|
+
label: 'Theme Check correction',
|
|
27
|
+
edit: workspace_edit,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
# Bail if unable to apply changes
|
|
31
|
+
return unless result[:applied]
|
|
32
|
+
|
|
33
|
+
# Clean up internal representation of fixed diagnostics
|
|
34
|
+
diagnostics_update = diagnostics_manager.delete_applied(diagnostic_hashes)
|
|
35
|
+
|
|
36
|
+
# Send updated diagnostics to client
|
|
37
|
+
diagnostics_update
|
|
38
|
+
.map do |relative_path, diagnostics|
|
|
39
|
+
bridge.send_notification('textDocument/publishDiagnostics', {
|
|
40
|
+
uri: file_uri(storage.path(relative_path)),
|
|
41
|
+
diagnostics: diagnostics.map(&:to_h),
|
|
42
|
+
})
|
|
43
|
+
storage.path(relative_path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ThemeCheck
|
|
4
|
+
module LanguageServer
|
|
5
|
+
class RunChecksExecuteCommandProvider < ExecuteCommandProvider
|
|
6
|
+
include URIHelper
|
|
7
|
+
|
|
8
|
+
command "runChecks"
|
|
9
|
+
|
|
10
|
+
def initialize(diagnostics_engine, root_path, root_config)
|
|
11
|
+
@diagnostics_engine = diagnostics_engine
|
|
12
|
+
@root_path = root_path
|
|
13
|
+
@root_config = root_config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute(_args)
|
|
17
|
+
@diagnostics_engine.analyze_and_send_offenses(@root_path, @root_config, force: true)
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -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,52 +45,94 @@ 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
|
+
|
|
71
|
+
def on_shutdown(id, _params)
|
|
72
|
+
@bridge.send_response(id, nil)
|
|
73
|
+
end
|
|
74
|
+
|
|
50
75
|
def on_exit(_id, _params)
|
|
51
76
|
close!
|
|
52
77
|
end
|
|
53
|
-
alias_method :on_shutdown, :on_exit
|
|
54
78
|
|
|
55
|
-
def
|
|
79
|
+
def on_text_document_did_open(_id, params)
|
|
56
80
|
relative_path = relative_path_from_text_document_uri(params)
|
|
57
|
-
@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?
|
|
58
83
|
end
|
|
59
84
|
|
|
60
|
-
def
|
|
85
|
+
def on_text_document_did_change(_id, params)
|
|
61
86
|
relative_path = relative_path_from_text_document_uri(params)
|
|
62
|
-
@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?
|
|
63
89
|
end
|
|
64
90
|
|
|
65
|
-
def
|
|
91
|
+
def on_text_document_did_close(_id, params)
|
|
66
92
|
relative_path = relative_path_from_text_document_uri(params)
|
|
67
|
-
|
|
68
|
-
|
|
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)
|
|
69
96
|
end
|
|
70
97
|
|
|
71
98
|
def on_text_document_did_save(_id, params)
|
|
72
|
-
analyze_and_send_offenses(text_document_uri(params))
|
|
99
|
+
analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_save?
|
|
73
100
|
end
|
|
74
101
|
|
|
75
102
|
def on_text_document_document_link(id, params)
|
|
76
103
|
relative_path = relative_path_from_text_document_uri(params)
|
|
77
|
-
@bridge.send_response(id, document_links(relative_path))
|
|
104
|
+
@bridge.send_response(id, @document_link_engine.document_links(relative_path))
|
|
78
105
|
end
|
|
79
106
|
|
|
80
107
|
def on_text_document_completion(id, params)
|
|
81
108
|
relative_path = relative_path_from_text_document_uri(params)
|
|
82
|
-
line = params.dig(
|
|
83
|
-
col = params.dig(
|
|
84
|
-
@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)
|
|
85
136
|
end
|
|
86
137
|
|
|
87
138
|
private
|
|
@@ -95,16 +146,16 @@ module ThemeCheck
|
|
|
95
146
|
ignored_patterns: config.ignored_patterns
|
|
96
147
|
)
|
|
97
148
|
|
|
98
|
-
# Turn that into a hash of
|
|
149
|
+
# Turn that into a hash of buffers
|
|
99
150
|
files = fs.files
|
|
100
|
-
.map { |fn| [fn,
|
|
151
|
+
.map { |fn| [fn, fs.read(fn)] }
|
|
101
152
|
.to_h
|
|
102
153
|
|
|
103
|
-
|
|
154
|
+
VersionedInMemoryStorage.new(files, config.root)
|
|
104
155
|
end
|
|
105
156
|
|
|
106
157
|
def text_document_uri(params)
|
|
107
|
-
file_path(params.dig(
|
|
158
|
+
file_path(params.dig(:textDocument, :uri))
|
|
108
159
|
end
|
|
109
160
|
|
|
110
161
|
def relative_path_from_text_document_uri(params)
|
|
@@ -112,8 +163,8 @@ module ThemeCheck
|
|
|
112
163
|
end
|
|
113
164
|
|
|
114
165
|
def root_path_from_params(params)
|
|
115
|
-
root_uri = params[
|
|
116
|
-
root_path = params[
|
|
166
|
+
root_uri = params[:rootUri]
|
|
167
|
+
root_path = params[:rootPath]
|
|
117
168
|
if root_uri
|
|
118
169
|
file_path(root_uri)
|
|
119
170
|
elsif root_path
|
|
@@ -122,11 +173,15 @@ module ThemeCheck
|
|
|
122
173
|
end
|
|
123
174
|
|
|
124
175
|
def text_document_text(params)
|
|
125
|
-
params.dig(
|
|
176
|
+
params.dig(:textDocument, :text)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def text_document_version(params)
|
|
180
|
+
params.dig(:textDocument, :version)
|
|
126
181
|
end
|
|
127
182
|
|
|
128
183
|
def content_changes_text(params)
|
|
129
|
-
params.dig(
|
|
184
|
+
params.dig(:contentChanges, 0, :text)
|
|
130
185
|
end
|
|
131
186
|
|
|
132
187
|
def config_for_path(path)
|
|
@@ -141,12 +196,11 @@ module ThemeCheck
|
|
|
141
196
|
)
|
|
142
197
|
end
|
|
143
198
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
@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
|
+
]
|
|
150
204
|
end
|
|
151
205
|
|
|
152
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
|
|
|
@@ -40,6 +48,8 @@ module ThemeCheck
|
|
|
40
48
|
content += chunk
|
|
41
49
|
end
|
|
42
50
|
content.lstrip!
|
|
51
|
+
rescue IOError
|
|
52
|
+
raise DoneStreaming
|
|
43
53
|
end
|
|
44
54
|
|
|
45
55
|
def send_message(message_body)
|
|
@@ -37,7 +37,7 @@ module ThemeCheck
|
|
|
37
37
|
@handlers = []
|
|
38
38
|
|
|
39
39
|
# The error queue holds blocks the main thread. When filled, we exit the program.
|
|
40
|
-
@error = SizedQueue.new(
|
|
40
|
+
@error = SizedQueue.new(number_of_threads)
|
|
41
41
|
|
|
42
42
|
@should_raise_errors = should_raise_errors
|
|
43
43
|
end
|
|
@@ -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.
|
|
@@ -94,27 +94,36 @@ module ThemeCheck
|
|
|
94
94
|
|
|
95
95
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
96
96
|
raise e if should_raise_errors
|
|
97
|
-
@bridge.log(e)
|
|
98
|
-
@bridge.log(e.backtrace)
|
|
97
|
+
@bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
99
98
|
2
|
|
100
99
|
end
|
|
101
100
|
|
|
102
101
|
private
|
|
103
102
|
|
|
104
103
|
def handle_message(message)
|
|
105
|
-
id = message[
|
|
106
|
-
method_name = message[
|
|
104
|
+
id = message[:id]
|
|
105
|
+
method_name = message[:method]
|
|
107
106
|
method_name &&= "on_#{to_snake_case(method_name)}"
|
|
108
|
-
params = message[
|
|
107
|
+
params = message[:params]
|
|
109
108
|
|
|
110
109
|
if @handler.respond_to?(method_name)
|
|
111
110
|
@handler.send(method_name, id, params)
|
|
112
111
|
end
|
|
112
|
+
|
|
113
|
+
rescue DoneStreaming => e
|
|
114
|
+
raise e
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
is_request = id
|
|
117
|
+
raise e unless is_request
|
|
118
|
+
# Errors obtained in request handlers should be sent
|
|
119
|
+
# back as internal errors instead of closing the program.
|
|
120
|
+
@bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
121
|
+
@bridge.send_internal_error(id, e)
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
def handle_response(message)
|
|
116
|
-
id = message[
|
|
117
|
-
result = message[
|
|
125
|
+
id = message[:id]
|
|
126
|
+
result = message[:result]
|
|
118
127
|
@bridge.receive_response(id, result)
|
|
119
128
|
end
|
|
120
129
|
|
|
@@ -135,7 +144,16 @@ module ThemeCheck
|
|
|
135
144
|
|
|
136
145
|
# Hijack the status_code if an error occurred while cleaning up.
|
|
137
146
|
# 👀 unit tests.
|
|
138
|
-
|
|
147
|
+
until @error.empty?
|
|
148
|
+
code = status_code_from_error(@error.pop)
|
|
149
|
+
# Promote the status_code to ERROR if one of the threads
|
|
150
|
+
# resulted in an error, otherwise leave the status_code as
|
|
151
|
+
# is. That's because one thread could end successfully in a
|
|
152
|
+
# DoneStreaming error while the other failed with an
|
|
153
|
+
# internal error. If we had an internal error, we should
|
|
154
|
+
# return with a status_code that fits.
|
|
155
|
+
status_code = code if code > status_code
|
|
156
|
+
end
|
|
139
157
|
status_code
|
|
140
158
|
ensure
|
|
141
159
|
@messenger.close_output
|
|
@@ -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
|