Skip to content

Commit 5fe75ba

Browse files
committed
Guard against invalid positions in diagnostics
Return line with column from message Fixes #707
1 parent 911688b commit 5fe75ba

File tree

7 files changed

+72
-32
lines changed

7 files changed

+72
-32
lines changed

apps/language_server/lib/language_server/diagnostics.ex

+28-25
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
44

55
def normalize(diagnostics, root_path) do
66
for diagnostic <- diagnostics do
7-
{type, file, line, description, stacktrace} =
7+
{type, file, position, description, stacktrace} =
88
extract_message_info(diagnostic.message, root_path)
99

1010
diagnostic
1111
|> update_message(type, description, stacktrace)
1212
|> maybe_update_file(file)
13-
|> maybe_update_position(type, line, stacktrace)
13+
|> maybe_update_position(type, position, stacktrace)
1414
end
1515
end
1616

@@ -32,9 +32,9 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
3232
stacktrace = reversed_stacktrace |> Enum.map(&String.trim/1) |> Enum.reverse()
3333

3434
{type, message_without_type} = split_type_and_message(message)
35-
{file, line, description} = split_file_and_description(message_without_type, root_path)
35+
{file, position, description} = split_file_and_description(message_without_type, root_path)
3636

37-
{type, file, line, description, stacktrace}
37+
{type, file, position, description, stacktrace}
3838
end
3939

4040
defp update_message(diagnostic, type, description, stacktrace) do
@@ -68,31 +68,31 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
6868
end
6969
end
7070

71-
defp maybe_update_position(diagnostic, "TokenMissingError", line, stacktrace) do
71+
defp maybe_update_position(diagnostic, "TokenMissingError", position, stacktrace) do
7272
case extract_line_from_missing_hint(diagnostic.message) do
73-
line when is_integer(line) ->
73+
line when is_integer(line) and line > 0 ->
7474
%{diagnostic | position: line}
7575

7676
_ ->
77-
do_maybe_update_position(diagnostic, line, stacktrace)
77+
do_maybe_update_position(diagnostic, position, stacktrace)
7878
end
7979
end
8080

81-
defp maybe_update_position(diagnostic, _type, line, stacktrace) do
82-
do_maybe_update_position(diagnostic, line, stacktrace)
81+
defp maybe_update_position(diagnostic, _type, position, stacktrace) do
82+
do_maybe_update_position(diagnostic, position, stacktrace)
8383
end
8484

85-
defp do_maybe_update_position(diagnostic, line, stacktrace) do
85+
defp do_maybe_update_position(diagnostic, position, stacktrace) do
8686
cond do
87-
line ->
88-
%{diagnostic | position: line}
87+
position != nil ->
88+
%{diagnostic | position: position}
8989

9090
diagnostic.position ->
9191
diagnostic
9292

9393
true ->
9494
line = extract_line_from_stacktrace(diagnostic.file, stacktrace)
95-
%{diagnostic | position: line}
95+
%{diagnostic | position: max(line, 0)}
9696
end
9797
end
9898

@@ -107,9 +107,18 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
107107
end
108108

109109
defp split_file_and_description(message, root_path) do
110-
with {file, line, _column, description} <- get_message_parts(message),
110+
with {file, line, column, description} <- get_message_parts(message),
111111
{:ok, path} <- file_path(file, root_path) do
112-
{path, String.to_integer(line), description}
112+
line = String.to_integer(line)
113+
114+
position =
115+
cond do
116+
line == 0 -> 0
117+
column == "" -> line
118+
true -> {line, String.to_integer(column)}
119+
end
120+
121+
{path, position, description}
113122
else
114123
_ ->
115124
{nil, nil, message}
@@ -279,7 +288,7 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
279288
lines = SourceFile.lines(source_file)
280289
# line is 1 based
281290
start_line = Enum.at(lines, line_start - 1)
282-
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based bere
291+
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based here
283292
character = SourceFile.elixir_character_to_lsp(start_line, char_start + 1)
284293

285294
%{
@@ -303,7 +312,7 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
303312
start_line = Enum.at(lines, line_start - 1)
304313
end_line = Enum.at(lines, line_end - 1)
305314

306-
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based bere
315+
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based here
307316
start_char = SourceFile.elixir_character_to_lsp(start_line, char_start + 1)
308317
end_char = SourceFile.elixir_character_to_lsp(end_line, char_end + 1)
309318

@@ -319,16 +328,10 @@ defmodule ElixirLS.LanguageServer.Diagnostics do
319328
}
320329
end
321330

322-
# position is 0 which means unknown
323-
# we return the full file range
324-
defp range(0, source_file) when not is_nil(source_file) do
325-
SourceFile.full_range(source_file)
326-
end
327-
328-
# source file is unknown
331+
# source file is unknown, position is 0 or invalid
329332
# we discard any position information as it is meaningless
330333
# unfortunately LSP does not allow `null` range so we need to return something
331-
defp range(_, nil) do
334+
defp range(_, _) do
332335
# we don't care about utf16 positions here as we send 0
333336
%{"start" => %{"line" => 0, "character" => 0}, "end" => %{"line" => 0, "character" => 0}}
334337
end

apps/language_server/lib/language_server/dialyzer.ex

+20-3
Original file line numberDiff line numberDiff line change
@@ -475,25 +475,42 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
475475

476476
defp to_diagnostics(warnings_map, warn_opts, warning_format) do
477477
tags_enabled = Analyzer.matching_tags(warn_opts)
478+
deps_path = Mix.Project.deps_path()
478479

479480
for {_beam_file, warnings} <- warnings_map,
480-
{source_file, line, data} <- warnings,
481+
{source_file, position, data} <- warnings,
481482
{tag, _, _} = data,
482483
tag in tags_enabled,
483484
source_file = Path.absname(to_string(source_file)),
484485
in_project?(source_file),
485-
not String.starts_with?(source_file, Mix.Project.deps_path()) do
486+
not String.starts_with?(source_file, deps_path) do
486487
%Mix.Task.Compiler.Diagnostic{
487488
compiler_name: "ElixirLS Dialyzer",
488489
file: source_file,
489-
position: line,
490+
position: normalize_postion(position),
490491
message: warning_message(data, warning_format),
491492
severity: :warning,
492493
details: data
493494
}
494495
end
495496
end
496497

498+
# up until OTP 23 position was line :: non_negative_integer
499+
# starting from OTP 24 it is erl_anno:location() :: line | {line, column}
500+
defp normalize_postion({line, column}) when line > 0 do
501+
{line, column}
502+
end
503+
504+
# 0 means unknown line
505+
defp normalize_postion(line) when line >= 0 do
506+
line
507+
end
508+
509+
defp normalize_postion(position) do
510+
IO.warn("dialyzer returned warning with invalid position #{inspect(position)}")
511+
0
512+
end
513+
497514
defp warning_message({_, _, {warning_name, args}} = raw_warning, warning_format)
498515
when warning_format in ["dialyxir_long", "dialyxir_short"] do
499516
format_function =

apps/language_server/lib/language_server/providers/execute_command/apply_spec.ex

-1
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,3 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ApplySpec do
9494
end
9595
end
9696
end
97-

apps/language_server/lib/language_server/providers/formatting.ex

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
3636
do_format(source_file)
3737
end
3838

39-
4039
defp do_format(%SourceFile{} = source_file, opts \\ []), do: do_format(source_file, nil, opts)
4140

4241
defp do_format(%SourceFile{text: text}, formatter, opts) do

apps/language_server/lib/language_server/server.ex

-1
Original file line numberDiff line numberDiff line change
@@ -1278,4 +1278,3 @@ defmodule ElixirLS.LanguageServer.Server do
12781278
end)
12791279
end
12801280
end
1281-

apps/language_server/lib/language_server/source_file.ex

-1
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,3 @@ defmodule ElixirLS.LanguageServer.SourceFile do
431431
{elixir_line - 1, utf16_character}
432432
end
433433
end
434-

apps/language_server/test/diagnostics_test.exs

+24
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,30 @@ defmodule ElixirLS.LanguageServer.DiagnosticsTest do
6161
assert diagnostic.position == 3
6262
end
6363

64+
test "update file and position with column if file is present in the message" do
65+
root_path = Path.join(__DIR__, "fixtures/build_errors")
66+
file = Path.join(root_path, "lib/has_error.ex")
67+
position = 2
68+
69+
message = """
70+
** (CompileError) lib/has_error.ex:3:5: some message
71+
lib/my_app/my_module.ex:10: MyApp.MyModule.render/1
72+
"""
73+
74+
[diagnostic | _] =
75+
[build_diagnostic(message, file, position)]
76+
|> Diagnostics.normalize(root_path)
77+
78+
assert diagnostic.message == """
79+
(CompileError) some message
80+
81+
Stacktrace:
82+
│ lib/my_app/my_module.ex:10: MyApp.MyModule.render/1\
83+
"""
84+
85+
assert diagnostic.position == {3, 5}
86+
end
87+
6488
test "update file and position if file is present in the message (umbrella)" do
6589
root_path = Path.join(__DIR__, "fixtures/umbrella")
6690
file = Path.join(root_path, "lib/file_to_be_replaced.ex")

0 commit comments

Comments
 (0)