theme-check 1.7.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +49 -0
  4. data/README.md +10 -0
  5. data/RELEASING.md +13 -0
  6. data/config/default.yml +5 -0
  7. data/data/shopify_liquid/deprecated_filters.yml +4 -0
  8. data/data/shopify_liquid/filters.yml +3 -1
  9. data/data/shopify_liquid/tags.yml +9 -9
  10. data/docs/checks/TEMPLATE.md.erb +24 -19
  11. data/docs/checks/schema_json_format.md +76 -0
  12. data/docs/language_server/code-action-command-palette.png +0 -0
  13. data/docs/language_server/code-action-flow.png +0 -0
  14. data/docs/language_server/code-action-keyboard.png +0 -0
  15. data/docs/language_server/code-action-light-bulb.png +0 -0
  16. data/docs/language_server/code-action-problem.png +0 -0
  17. data/docs/language_server/code-action-quickfix.png +0 -0
  18. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  19. data/exe/theme-check-language-server +0 -4
  20. data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
  21. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
  22. data/lib/theme_check/checks/asset_url_filters.rb +2 -0
  23. data/lib/theme_check/checks/default_locale.rb +1 -1
  24. data/lib/theme_check/checks/deprecated_filter.rb +79 -4
  25. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
  26. data/lib/theme_check/checks/matching_schema_translations.rb +14 -9
  27. data/lib/theme_check/checks/matching_translations.rb +1 -0
  28. data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
  29. data/lib/theme_check/checks/missing_template.rb +1 -1
  30. data/lib/theme_check/checks/pagination_size.rb +2 -3
  31. data/lib/theme_check/checks/remote_asset.rb +5 -0
  32. data/lib/theme_check/checks/required_directories.rb +1 -1
  33. data/lib/theme_check/checks/required_layout_theme_object.rb +9 -4
  34. data/lib/theme_check/checks/schema_json_format.rb +29 -0
  35. data/lib/theme_check/checks/space_inside_braces.rb +132 -87
  36. data/lib/theme_check/checks/translation_key_exists.rb +33 -25
  37. data/lib/theme_check/checks/unused_assign.rb +3 -2
  38. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  39. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  40. data/lib/theme_check/checks/valid_schema.rb +2 -2
  41. data/lib/theme_check/corrector.rb +34 -23
  42. data/lib/theme_check/exceptions.rb +1 -0
  43. data/lib/theme_check/file_system_storage.rb +8 -3
  44. data/lib/theme_check/html_node.rb +99 -6
  45. data/lib/theme_check/html_visitor.rb +1 -32
  46. data/lib/theme_check/in_memory_storage.rb +9 -0
  47. data/lib/theme_check/json_helpers.rb +14 -0
  48. data/lib/theme_check/language_server/bridge.rb +142 -0
  49. data/lib/theme_check/language_server/channel.rb +69 -0
  50. data/lib/theme_check/language_server/client_capabilities.rb +27 -0
  51. data/lib/theme_check/language_server/code_action_engine.rb +32 -0
  52. data/lib/theme_check/language_server/code_action_provider.rb +42 -0
  53. data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
  54. data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
  55. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +3 -1
  56. data/lib/theme_check/language_server/configuration.rb +69 -0
  57. data/lib/theme_check/language_server/diagnostic.rb +124 -0
  58. data/lib/theme_check/language_server/diagnostics_engine.rb +80 -0
  59. data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
  60. data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
  61. data/lib/theme_check/language_server/document_link_provider.rb +6 -6
  62. data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
  63. data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
  64. data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  65. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
  66. data/lib/theme_check/language_server/handler.rb +92 -217
  67. data/lib/theme_check/language_server/io_messenger.rb +112 -0
  68. data/lib/theme_check/language_server/messenger.rb +12 -42
  69. data/lib/theme_check/language_server/protocol.rb +4 -0
  70. data/lib/theme_check/language_server/server.rb +54 -110
  71. data/lib/theme_check/language_server/uri_helper.rb +1 -0
  72. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
  73. data/lib/theme_check/language_server.rb +28 -6
  74. data/lib/theme_check/liquid_node.rb +255 -12
  75. data/lib/theme_check/locale_diff.rb +48 -10
  76. data/lib/theme_check/node.rb +16 -0
  77. data/lib/theme_check/offense.rb +27 -23
  78. data/lib/theme_check/position.rb +4 -4
  79. data/lib/theme_check/regex_helpers.rb +1 -1
  80. data/lib/theme_check/schema_helper.rb +70 -0
  81. data/lib/theme_check/shopify_liquid/system_translations.rb +35 -0
  82. data/lib/theme_check/shopify_liquid/tag.rb +19 -1
  83. data/lib/theme_check/shopify_liquid.rb +1 -0
  84. data/lib/theme_check/storage.rb +4 -0
  85. data/lib/theme_check/tags.rb +0 -1
  86. data/lib/theme_check/theme.rb +1 -1
  87. data/lib/theme_check/theme_file.rb +8 -1
  88. data/lib/theme_check/theme_file_rewriter.rb +28 -6
  89. data/lib/theme_check/version.rb +1 -1
  90. data/lib/theme_check.rb +11 -2
  91. metadata +31 -3
  92. 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,
@@ -26,68 +35,104 @@ module ThemeCheck
26
35
  },
27
36
  }
28
37
 
29
- def initialize(server)
30
- @server = server
31
- @diagnostics_tracker = DiagnosticsTracker.new
32
- @diagnostics_lock = Mutex.new
33
- @supports_progress = false
34
- end
35
-
36
- def supports_progress_notifications?
37
- @supports_progress
38
+ def initialize(bridge)
39
+ @bridge = bridge
38
40
  end
39
41
 
40
42
  def on_initialize(id, params)
41
43
  @root_path = root_path_from_params(params)
42
- @supports_progress = params.dig('capabilities', 'window', 'workDoneProgress')
43
44
 
44
45
  # Tell the client we don't support anything if there's no rootPath
45
- return send_response(id, { capabilities: {} }) if @root_path.nil?
46
+ return @bridge.send_response(id, { capabilities: {} }) if @root_path.nil?
47
+
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?
46
51
  @storage = in_memory_storage(@root_path)
52
+ @diagnostics_manager = DiagnosticsManager.new
47
53
  @completion_engine = CompletionEngine.new(@storage)
48
54
  @document_link_engine = DocumentLinkEngine.new(@storage)
49
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
50
- send_response(id, {
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)
60
+ @bridge.send_response(id, {
51
61
  capabilities: CAPABILITIES,
52
62
  serverInfo: SERVER_INFO,
53
63
  })
54
64
  end
55
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
+
56
75
  def on_exit(_id, _params)
57
76
  close!
58
77
  end
59
- alias_method :on_shutdown, :on_exit
60
78
 
61
- def on_text_document_did_change(_id, params)
79
+ def on_text_document_did_open(_id, params)
62
80
  relative_path = relative_path_from_text_document_uri(params)
63
- @storage.write(relative_path, content_changes_text(params))
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?
64
83
  end
65
84
 
66
- def on_text_document_did_close(_id, params)
85
+ def on_text_document_did_change(_id, params)
67
86
  relative_path = relative_path_from_text_document_uri(params)
68
- @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?
69
89
  end
70
90
 
71
- def on_text_document_did_open(_id, params)
91
+ def on_text_document_did_close(_id, params)
72
92
  relative_path = relative_path_from_text_document_uri(params)
73
- @storage.write(relative_path, text_document_text(params))
74
- analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_tracker.first_run?
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)
75
96
  end
76
97
 
77
98
  def on_text_document_did_save(_id, params)
78
- analyze_and_send_offenses(text_document_uri(params))
99
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_save?
79
100
  end
80
101
 
81
102
  def on_text_document_document_link(id, params)
82
103
  relative_path = relative_path_from_text_document_uri(params)
83
- send_response(id, document_links(relative_path))
104
+ @bridge.send_response(id, @document_link_engine.document_links(relative_path))
84
105
  end
85
106
 
86
107
  def on_text_document_completion(id, params)
87
108
  relative_path = relative_path_from_text_document_uri(params)
88
- line = params.dig('position', 'line')
89
- col = params.dig('position', 'character')
90
- 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)
91
136
  end
92
137
 
93
138
  private
@@ -101,16 +146,16 @@ module ThemeCheck
101
146
  ignored_patterns: config.ignored_patterns
102
147
  )
103
148
 
104
- # Turn that into a hash of empty buffers
149
+ # Turn that into a hash of buffers
105
150
  files = fs.files
106
- .map { |fn| [fn, ""] }
151
+ .map { |fn| [fn, fs.read(fn)] }
107
152
  .to_h
108
153
 
109
- InMemoryStorage.new(files, config.root)
154
+ VersionedInMemoryStorage.new(files, config.root)
110
155
  end
111
156
 
112
157
  def text_document_uri(params)
113
- file_path(params.dig('textDocument', 'uri'))
158
+ file_path(params.dig(:textDocument, :uri))
114
159
  end
115
160
 
116
161
  def relative_path_from_text_document_uri(params)
@@ -118,8 +163,8 @@ module ThemeCheck
118
163
  end
119
164
 
120
165
  def root_path_from_params(params)
121
- root_uri = params["rootUri"]
122
- root_path = params["rootPath"]
166
+ root_uri = params[:rootUri]
167
+ root_path = params[:rootPath]
123
168
  if root_uri
124
169
  file_path(root_uri)
125
170
  elsif root_path
@@ -128,11 +173,15 @@ module ThemeCheck
128
173
  end
129
174
 
130
175
  def text_document_text(params)
131
- params.dig('textDocument', 'text')
176
+ params.dig(:textDocument, :text)
177
+ end
178
+
179
+ def text_document_version(params)
180
+ params.dig(:textDocument, :version)
132
181
  end
133
182
 
134
183
  def content_changes_text(params)
135
- params.dig('contentChanges', 0, 'text')
184
+ params.dig(:contentChanges, 0, :text)
136
185
  end
137
186
 
138
187
  def config_for_path(path)
@@ -141,195 +190,21 @@ module ThemeCheck
141
190
  end
142
191
 
143
192
  def analyze_and_send_offenses(absolute_path)
144
- return unless @diagnostics_lock.try_lock
145
- token = send_create_work_done_progress_request
146
- config = config_for_path(absolute_path)
147
- storage = ThemeCheck::FileSystemStorage.new(
148
- config.root,
149
- ignored_patterns: config.ignored_patterns
193
+ @diagnostics_engine.analyze_and_send_offenses(
194
+ absolute_path,
195
+ config_for_path(absolute_path)
150
196
  )
151
- theme = ThemeCheck::Theme.new(storage)
152
- analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
153
-
154
- if @diagnostics_tracker.first_run?
155
- send_work_done_progress_begin(token, "Full theme check")
156
- log("Checking #{config.root}")
157
- offenses = nil
158
- time = Benchmark.measure do
159
- offenses = analyzer.analyze_theme do |path, i, total|
160
- send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
161
- end
162
- end
163
- end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
164
- log(end_message)
165
- send_work_done_progress_end(token, end_message)
166
- send_diagnostics(offenses)
167
- else
168
- # Analyze selected files
169
- relative_path = Pathname.new(@storage.relative_path(absolute_path))
170
- file = theme[relative_path]
171
- # Skip if not a theme file
172
- if file
173
- log("Checking #{relative_path}")
174
- send_work_done_progress_begin(token, "Partial theme check")
175
- offenses = nil
176
- time = Benchmark.measure do
177
- offenses = analyzer.analyze_files([file]) do |path, i, total|
178
- send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
179
- end
180
- end
181
- end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
182
- send_work_done_progress_end(token, end_message)
183
- log(end_message)
184
- send_diagnostics(offenses, [absolute_path])
185
- end
186
- end
187
- @diagnostics_lock.unlock
188
- end
189
-
190
- def completions(relative_path, line, col)
191
- @completion_engine.completions(relative_path, line, col)
192
- end
193
-
194
- def document_links(relative_path)
195
- @document_link_engine.document_links(relative_path)
196
- end
197
-
198
- def send_diagnostics(offenses, analyzed_files = nil)
199
- @diagnostics_tracker.build_diagnostics(offenses, analyzed_files: analyzed_files) do |path, diagnostic_offenses|
200
- send_diagnostic(path, diagnostic_offenses)
201
- end
202
- end
203
-
204
- def send_diagnostic(path, offenses)
205
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
206
- send_notification('textDocument/publishDiagnostics', {
207
- uri: file_uri(path),
208
- diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
209
- })
210
- end
211
-
212
- def offense_to_diagnostic(offense)
213
- diagnostic = {
214
- code: offense.code_name,
215
- message: offense.message,
216
- range: range(offense),
217
- severity: severity(offense),
218
- source: "theme-check",
219
- }
220
- diagnostic["codeDescription"] = code_description(offense) unless offense.doc.nil?
221
- diagnostic
222
- end
223
-
224
- def code_description(offense)
225
- {
226
- href: offense.doc,
227
- }
228
- end
229
-
230
- def severity(offense)
231
- case offense.severity
232
- when :error
233
- 1
234
- when :suggestion
235
- 2
236
- when :style
237
- 3
238
- else
239
- 4
240
- end
241
- end
242
-
243
- def range(offense)
244
- {
245
- start: {
246
- line: offense.start_line,
247
- character: offense.start_column,
248
- },
249
- end: {
250
- line: offense.end_line,
251
- character: offense.end_column,
252
- },
253
- }
254
- end
255
-
256
- def send_create_work_done_progress_request
257
- return unless supports_progress_notifications?
258
- token = nil
259
- @server.request do |id|
260
- token = id # we'll reuse the RQID as token
261
- send_message({
262
- id: id,
263
- method: "window/workDoneProgress/create",
264
- params: {
265
- token: id,
266
- },
267
- })
268
- end
269
- token
270
- end
271
-
272
- def send_work_done_progress_begin(token, title)
273
- return unless supports_progress_notifications?
274
- send_progress(token, {
275
- kind: 'begin',
276
- title: title,
277
- cancellable: false,
278
- percentage: 0,
279
- })
280
- end
281
-
282
- def send_work_done_progress_report(token, message, percentage)
283
- return unless supports_progress_notifications?
284
- send_progress(token, {
285
- kind: 'report',
286
- message: message,
287
- cancellable: false,
288
- percentage: percentage,
289
- })
290
- end
291
-
292
- def send_work_done_progress_end(token, message)
293
- return unless supports_progress_notifications?
294
- send_progress(token, {
295
- kind: 'end',
296
- message: message,
297
- })
298
- end
299
-
300
- def send_progress(token, value)
301
- send_notification("$/progress", token: token, value: value)
302
- end
303
-
304
- def send_message(message)
305
- message[:jsonrpc] = '2.0'
306
- @server.send_message(message)
307
- end
308
-
309
- def send_response(id, result = nil, error = nil)
310
- message = { id: id }
311
- message[:result] = result if result
312
- message[:error] = error if error
313
- send_message(message)
314
- end
315
-
316
- def send_request(method, params = nil)
317
- @server.request do |id|
318
- message = { id: id }
319
- message[:method] = method
320
- message[:params] = params if params
321
- send_message(message)
322
- end
323
197
  end
324
198
 
325
- def send_notification(method, params)
326
- message = { method: method }
327
- message[:params] = params
328
- send_message(message)
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
+ ]
329
204
  end
330
205
 
331
206
  def log(message)
332
- @server.log(message)
207
+ @bridge.log(message)
333
208
  end
334
209
 
335
210
  def close!
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
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
+
14
+ def initialize(
15
+ in_stream: STDIN,
16
+ out_stream: STDOUT,
17
+ err_stream: IOMessenger.err_stream
18
+ )
19
+ validate!([in_stream, out_stream, err_stream])
20
+
21
+ @in = in_stream
22
+ @out = out_stream
23
+ @err = err_stream
24
+
25
+ # Because programming is fun,
26
+ #
27
+ # Ruby on Windows turns \n into \r\n. Which means that \r\n
28
+ # gets turned into \r\r\n. Which means that the protocol
29
+ # breaks on windows unless we turn STDOUT into binary mode.
30
+ #
31
+ # Hours wasted: 9.
32
+ @out.binmode
33
+
34
+ @out.sync = true # do not buffer
35
+ @err.sync = true # do not buffer
36
+
37
+ # Lock for writing, otherwise messages might be interspersed.
38
+ @writer = Mutex.new
39
+ end
40
+
41
+ def read_message
42
+ length = initial_line.match(/Content-Length: (\d+)/)[1].to_i
43
+ content = ''
44
+ length_to_read = 2 + length # 2 is the empty line length (\r\n)
45
+ while content.length < length_to_read
46
+ chunk = @in.read(length_to_read - content.length)
47
+ raise DoneStreaming if chunk.nil?
48
+ content += chunk
49
+ end
50
+ content.lstrip!
51
+ rescue IOError
52
+ raise DoneStreaming
53
+ end
54
+
55
+ def send_message(message_body)
56
+ @writer.synchronize do
57
+ @out.write("Content-Length: #{message_body.bytesize}\r\n")
58
+ @out.write("\r\n")
59
+ @out.write(message_body)
60
+ @out.flush
61
+ end
62
+ end
63
+
64
+ def log(message)
65
+ @err.puts(message)
66
+ @err.flush
67
+ end
68
+
69
+ def close_input
70
+ @in.close unless @in.closed?
71
+ end
72
+
73
+ def close_output
74
+ @err.close
75
+ @out.close
76
+ end
77
+
78
+ private
79
+
80
+ def initial_line
81
+ # Scanning for lines that fit the protocol.
82
+ while true
83
+ initial_line = @in.gets
84
+ # gets returning nil means the stream was closed.
85
+ raise DoneStreaming if initial_line.nil?
86
+
87
+ if initial_line.match(/Content-Length: (\d+)/)
88
+ break
89
+ end
90
+ end
91
+ initial_line
92
+ end
93
+
94
+ def supported_io_classes
95
+ [IO, StringIO]
96
+ end
97
+
98
+ def validate!(streams = [])
99
+ streams.each do |stream|
100
+ unless supported_io_classes.find { |klass| stream.is_a?(klass) }
101
+ raise IncompatibleStream, incompatible_stream_message
102
+ end
103
+ end
104
+ end
105
+
106
+ def incompatible_stream_message
107
+ 'if provided, in_stream, out_stream, and err_stream must be a kind of '\
108
+ "one of the following: #{supported_io_classes.join(', ')}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -3,54 +3,24 @@
3
3
  module ThemeCheck
4
4
  module LanguageServer
5
5
  class Messenger
6
- def initialize
7
- @responses = {}
8
- @mutex = Mutex.new
9
- @id = 0
6
+ def send_message
7
+ raise NotImplementedError
10
8
  end
11
9
 
12
- # Here's how you'd use this:
13
- #
14
- # def some_method_that_communicates_both_ways
15
- #
16
- # # this will block until the JSON rpc loop has an answer
17
- # token = @server.request do |id|
18
- # send_create_work_done_progress_request(id, ...)
19
- # end
20
- #
21
- # send_create_work_done_begin_notification(token, "...")
22
- #
23
- # do_stuff do |file, i, total|
24
- # send_create_work_done_progress_notification(token, "...")
25
- # end
26
- #
27
- # send_create_work_done_end_notification(token, "...")
28
- #
29
- # end
30
- def request(&block)
31
- id = @mutex.synchronize { @id += 1 }
32
- @responses[id] = SizedQueue.new(1)
33
-
34
- # Execute the block in the parent thread with an ID
35
- # So that we're able to relinquish control in the right
36
- # place when we have a response.
37
- block.call(id)
38
-
39
- # this call is blocking until we get a response from somewhere
40
- result = @responses[id].pop
10
+ def read_message
11
+ raise NotImplementedError
12
+ end
41
13
 
42
- # cleanup when done
43
- @responses.delete(id)
14
+ def log
15
+ raise NotImplementedError
16
+ end
44
17
 
45
- # return the response
46
- result
18
+ def close_input
19
+ raise NotImplementedError
47
20
  end
48
21
 
49
- # In the JSONRPC loop, when we find the response to the
50
- # request, we unblock the thread that made the request with the
51
- # response.
52
- def respond(id, value)
53
- @responses[id] << value
22
+ def close_output
23
+ raise NotImplementedError
54
24
  end
55
25
  end
56
26
  end
@@ -37,5 +37,9 @@ module ThemeCheck
37
37
  FULL = 1
38
38
  INCREMENTAL = 2
39
39
  end
40
+
41
+ module ErrorCodes
42
+ INTERNAL_ERROR = -32603
43
+ end
40
44
  end
41
45
  end