@@ -7,12 +7,21 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
7
7
8
8
def normalize ( diagnostics , root_path , mixfile ) do
9
9
for % Mix.Task.Compiler.Diagnostic { } = diagnostic <- diagnostics do
10
- { type , file , position , stacktrace } =
11
- extract_message_info ( diagnostic . message , root_path )
10
+ case diagnostic |> dbg do
11
+ % Mix.Task.Compiler.Diagnostic { details: payload = % _ { line: _ } , compiler_name: compiler_name } ->
12
+ # remove stacktrace
13
+ message = Exception . format_banner ( :error , payload )
14
+ compiler_name = if compiler_name == "Elixir" , do: "ElixirLS" , else: compiler_name
15
+ % Mix.Task.Compiler.Diagnostic { diagnostic | message: message , compiler_name: compiler_name }
12
16
13
- diagnostic
14
- |> maybe_update_file ( file , mixfile )
15
- |> maybe_update_position ( type , position , stacktrace )
17
+ _ ->
18
+ { type , file , position , stacktrace } =
19
+ extract_message_info ( diagnostic . message , root_path )
20
+
21
+ diagnostic
22
+ |> maybe_update_file ( file , mixfile )
23
+ |> maybe_update_position ( type , position , stacktrace )
24
+ end
16
25
end
17
26
end
18
27
@@ -175,47 +184,6 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
175
184
end )
176
185
end
177
186
178
- def publish_file_diagnostics ( uri , uri_diagnostics , source_file , version ) do
179
- diagnostics_json =
180
- for diagnostic <- uri_diagnostics do
181
- severity =
182
- case diagnostic . severity do
183
- :error -> 1
184
- :warning -> 2
185
- :information -> 3
186
- :hint -> 4
187
- end
188
-
189
- message =
190
- case diagnostic . message do
191
- m when is_binary ( m ) -> m
192
- m when is_list ( m ) -> m |> Enum . join ( "\n " )
193
- end
194
-
195
- % {
196
- "message" => message ,
197
- "severity" => severity ,
198
- "range" => range ( diagnostic . position , source_file ) ,
199
- "source" => diagnostic . compiler_name
200
- }
201
- end
202
- |> Enum . sort_by ( & & 1 [ "range" ] [ "start" ] )
203
-
204
- message = % {
205
- "uri" => uri ,
206
- "diagnostics" => diagnostics_json
207
- }
208
-
209
- message =
210
- if is_integer ( version ) do
211
- Map . put ( message , "version" , version )
212
- else
213
- message
214
- end
215
-
216
- JsonRpc . notify ( "textDocument/publishDiagnostics" , message )
217
- end
218
-
219
187
def mixfile_diagnostic ( { file , position , message } , severity ) when not is_nil ( file ) do
220
188
% Mix.Task.Compiler.Diagnostic {
221
189
compiler_name: "ElixirLS" ,
@@ -242,15 +210,19 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
242
210
}
243
211
end
244
212
245
- def error_to_diagnostic ( :error , % kind { } = payload , _stacktrace , path , project_dir )
246
- when kind in [ EEx.SyntaxError , SyntaxError , TokenMissingError , MismatchedDelimiterError ] do
213
+ def error_to_diagnostic ( :error , % _ { line: _ } = payload , _stacktrace , path , project_dir ) do
247
214
path = SourceFile.Path . absname ( path , project_dir )
248
215
message = Exception . format_banner ( :error , payload )
249
216
217
+ position = case payload do
218
+ % { line: line , column: column } -> { line , column }
219
+ % { line: line } -> line
220
+ end
221
+
250
222
% Mix.Task.Compiler.Diagnostic {
251
223
compiler_name: "ElixirLS" ,
252
224
file: path ,
253
- position: { payload . line , payload . column } ,
225
+ position: position ,
254
226
message: message ,
255
227
severity: :error ,
256
228
details: payload
@@ -303,6 +275,161 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
303
275
}
304
276
end
305
277
278
+ def publish_file_diagnostics ( uri , uri_diagnostics , source_file , version ) do
279
+ diagnostics_json =
280
+ for diagnostic <- uri_diagnostics do
281
+ severity =
282
+ case diagnostic . severity do
283
+ :error -> 1
284
+ :warning -> 2
285
+ :information -> 3
286
+ :hint -> 4
287
+ end
288
+
289
+ message =
290
+ case diagnostic . message do
291
+ m when is_binary ( m ) -> m
292
+ m when is_list ( m ) -> m |> Enum . join ( "\n " )
293
+ end
294
+
295
+ % {
296
+ "message" => message ,
297
+ "severity" => severity ,
298
+ "range" => range ( diagnostic . position , source_file ) ,
299
+ "source" => diagnostic . compiler_name ,
300
+ "relatedInformation" => build_related_information ( diagnostic , uri , source_file ) ,
301
+ "tags" => get_tags ( diagnostic )
302
+ }
303
+ end
304
+ |> Enum . sort_by ( & & 1 [ "range" ] [ "start" ] )
305
+
306
+ message = % {
307
+ "uri" => uri ,
308
+ "diagnostics" => diagnostics_json
309
+ }
310
+
311
+ message =
312
+ if is_integer ( version ) do
313
+ Map . put ( message , "version" , version )
314
+ else
315
+ message
316
+ end
317
+
318
+ JsonRpc . notify ( "textDocument/publishDiagnostics" , message )
319
+ end
320
+
321
+ defp get_tags ( diagnostic ) do
322
+ unused = if Regex . match? ( ~r/ unused|no effect/ u , diagnostic . message ) do
323
+ [ 1 ]
324
+ else
325
+ [ ]
326
+ end
327
+ deprecated = if Regex . match? ( ~r/ deprecated/ u , diagnostic . message ) do
328
+ [ 2 ]
329
+ else
330
+ [ ]
331
+ end
332
+
333
+ unused ++ deprecated
334
+ end
335
+
336
+ defp get_related_information_description ( description , uri , source_file ) do
337
+ line = case Regex . run (
338
+ ~r/ line (\d +)/ u ,
339
+ description
340
+ ) do
341
+ [ _ , line ] -> String . to_integer ( line )
342
+ _ -> nil
343
+ end
344
+
345
+ message = case String . split ( description , "hint: " ) do
346
+ [ _ , hint ] -> hint
347
+ _ -> description
348
+ end
349
+
350
+ if line do
351
+ [
352
+ % {
353
+ "location" => % {
354
+ "uri" => uri ,
355
+ "range" => range ( line , source_file )
356
+ } ,
357
+ "message" => message
358
+ }
359
+ ]
360
+ else
361
+ [ ]
362
+ end
363
+ end
364
+
365
+ defp get_related_information_message ( message , uri , source_file ) do
366
+ line = case Regex . run (
367
+ ~r/ line (\d +)/ u ,
368
+ message
369
+ ) do
370
+ [ _ , line ] -> String . to_integer ( line )
371
+ _ -> nil
372
+ end
373
+
374
+ if line do
375
+ [
376
+ % {
377
+ "location" => % {
378
+ "uri" => uri ,
379
+ "range" => range ( line , source_file )
380
+ } ,
381
+ "message" => "related"
382
+ }
383
+ ]
384
+ else
385
+ [ ]
386
+ end
387
+ end
388
+
389
+ defp build_related_information ( diagnostic , uri , source_file ) do
390
+ case diagnostic . details do
391
+ # for backwards compatibility with elixir < 1.16
392
+ % kind { } = payload when kind == MismatchedDelimiterError ->
393
+ [
394
+ % {
395
+ "location" => % {
396
+ "uri" => uri ,
397
+ "range" => range ( { payload . line , payload . column - 1 , payload . line , payload . column - 1 + String . length ( to_string ( payload . opening_delimiter ) ) } , source_file )
398
+ } ,
399
+ "message" => "opening delimiter: #{ payload . opening_delimiter } "
400
+ } ,
401
+ % {
402
+ "location" => % {
403
+ "uri" => uri ,
404
+ "range" => range ( { payload . end_line , payload . end_column - 1 , payload . end_line , payload . end_column - 1 + String . length ( to_string ( payload . closing_delimiter ) ) } , source_file )
405
+ } ,
406
+ "message" => "closing delimiter: #{ payload . closing_delimiter } "
407
+ }
408
+ ]
409
+ % kind { end_line: end_line , opening_delimiter: opening_delimiter } = payload when kind == TokenMissingError and not is_nil ( opening_delimiter ) ->
410
+ message = String . split ( payload . description , "hint: " ) |> hd
411
+ [
412
+ % {
413
+ "location" => % {
414
+ "uri" => uri ,
415
+ "range" => range ( { payload . line , payload . column - 1 , payload . line , payload . column - 1 + String . length ( to_string ( payload . opening_delimiter ) ) } , source_file )
416
+ } ,
417
+ "message" => "opening delimiter: #{ payload . opening_delimiter } "
418
+ } ,
419
+ % {
420
+ "location" => % {
421
+ "uri" => uri ,
422
+ "range" => range ( end_line , source_file )
423
+ } ,
424
+ "message" => message
425
+ }
426
+ ] ++ get_related_information_description ( payload . description , uri , source_file )
427
+ % { description: description } ->
428
+ get_related_information_description ( description , uri , source_file )
429
+ _ -> [ ]
430
+ end ++ get_related_information_message ( diagnostic . message , uri , source_file )
431
+ end
432
+
306
433
# for details see
307
434
# https://hexdocs.pm/mix/1.13.4/Mix.Task.Compiler.Diagnostic.html#t:position/0
308
435
# https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#diagnostic
@@ -351,11 +478,11 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
351
478
when not is_nil ( source_file ) do
352
479
# some diagnostics are broken
353
480
line_start = line_start || 1
354
- char_start = char_start || 1
481
+ char_start = char_start || 0
355
482
lines = SourceFile . lines ( source_file )
356
483
# elixir_position_to_lsp will handle positions outside file range
357
484
{ line_start_lsp , char_start_lsp } =
358
- SourceFile . elixir_position_to_lsp ( lines , { line_start , char_start - 1 } )
485
+ SourceFile . elixir_position_to_lsp ( lines , { line_start , char_start + 1 } )
359
486
360
487
% {
361
488
"start" => % {
@@ -375,18 +502,18 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
375
502
when not is_nil ( source_file ) do
376
503
# some diagnostics are broken
377
504
line_start = line_start || 1
378
- char_start = char_start || 1
505
+ char_start = char_start || 0
379
506
380
507
line_end = line_end || 1
381
- char_end = char_end || 1
508
+ char_end = char_end || 0
382
509
383
510
lines = SourceFile . lines ( source_file )
384
511
# elixir_position_to_lsp will handle positions outside file range
385
512
{ line_start_lsp , char_start_lsp } =
386
- SourceFile . elixir_position_to_lsp ( lines , { line_start , char_start - 1 } )
513
+ SourceFile . elixir_position_to_lsp ( lines , { line_start , char_start + 1 } )
387
514
388
515
{ line_end_lsp , char_end_lsp } =
389
- SourceFile . elixir_position_to_lsp ( lines , { line_end , char_end - 1 } )
516
+ SourceFile . elixir_position_to_lsp ( lines , { line_end , char_end + 1 } )
390
517
391
518
% {
392
519
"start" => % {
0 commit comments