Skip to content

Commit b9da0a2

Browse files
committed
refactor and fix tests
1 parent 0f998ff commit b9da0a2

File tree

4 files changed

+138
-75
lines changed

4 files changed

+138
-75
lines changed

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

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -76,36 +76,54 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
7676
{:ok, docs}
7777

7878
_ ->
79-
# Try as builtin type
80-
if arity == nil or arity == 0 do
81-
# TODO: doesn't work for types with arity > 0
82-
case BuiltinTypes.get_builtin_type_doc(function) do
83-
doc when doc != "" ->
84-
{:ok,
85-
%{
86-
type: "#{function}()",
87-
documentation: doc
88-
}}
89-
90-
_ ->
91-
# Check if it's a builtin function or try other modules
92-
case BuiltinFunctions.get_docs({function, arity}) do
93-
"" ->
94-
{:error, "Local call #{function}/#{arity || "?"} - no documentation found"}
95-
96-
builtin_docs when is_binary(builtin_docs) ->
97-
{
98-
:ok,
79+
# Try as builtin type using the new API that returns a list
80+
case BuiltinTypes.get_builtin_types_doc(function, arity || :any) do
81+
[] ->
82+
# No builtin types found, check if it's a builtin function using the new API
83+
case BuiltinFunctions.get_builtin_functions_doc(function, arity || :any) do
84+
[] ->
85+
{:error, "Local call #{function}/#{arity || "?"} - no documentation found"}
86+
87+
builtin_functions ->
88+
# Return all matching builtin functions
89+
results =
90+
Enum.map(builtin_functions, fn {_function_name, func_arity, doc, specs} ->
9991
%{
10092
function: Atom.to_string(function),
101-
arity: arity,
102-
documentation: builtin_docs
93+
arity: func_arity,
94+
documentation: format_builtin_function_doc(doc, specs)
10395
}
104-
}
105-
end
106-
end
107-
else
108-
{:error, "Local call #{function}/#{arity || "?"} - no documentation found"}
96+
end)
97+
98+
# Return single result if only one match, otherwise return list
99+
case results do
100+
[single_result] -> {:ok, single_result}
101+
multiple_results -> {:ok, multiple_results}
102+
end
103+
end
104+
105+
builtin_types ->
106+
# Return all matching builtin types
107+
results =
108+
Enum.map(builtin_types, fn {_type_name, type_arity, doc} ->
109+
type_name =
110+
if type_arity == 0 do
111+
"#{function}()"
112+
else
113+
"#{function}/#{type_arity}"
114+
end
115+
116+
%{
117+
type: type_name,
118+
documentation: doc
119+
}
120+
end)
121+
122+
# Return single result if only one match, otherwise return list
123+
case results do
124+
[single_result] -> {:ok, single_result}
125+
multiple_results -> {:ok, multiple_results}
126+
end
109127
end
110128
end
111129
end
@@ -137,29 +155,11 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
137155
module: inspect(module),
138156
callback: Atom.to_string(function),
139157
arity: callback_info[:arity] || arity,
140-
documentation: callback_info[:documentation] || ""
158+
documentation: callback_info[:documentation] || "",
159+
spec: callback_info[:spec],
160+
kind: callback_info[:kind],
161+
metadata: callback_info[:metadata] || %{}
141162
}
142-
143-
# Add spec if available
144-
base_result = if callback_info[:spec] do
145-
Map.put(base_result, :spec, callback_info[:spec])
146-
else
147-
base_result
148-
end
149-
150-
# Add kind if available
151-
base_result = if callback_info[:kind] do
152-
Map.put(base_result, :kind, callback_info[:kind])
153-
else
154-
base_result
155-
end
156-
157-
# Add metadata if available
158-
if callback_info[:metadata] && callback_info[:metadata] != %{} do
159-
Map.put(base_result, :metadata, callback_info[:metadata])
160-
else
161-
base_result
162-
end
163163
end)
164164

165165
{:ok, results}
@@ -551,9 +551,9 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
551551
kind: callback_info.kind,
552552
metadata: callback_info.metadata
553553
}
554-
554+
555555
callback_result = Map.put(callback_result, :metadata, callback_info.metadata)
556-
556+
557557
callback_result
558558
end)
559559
end
@@ -569,15 +569,17 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
569569
{{spec_name, spec_arity}, _} ->
570570
# Check if it's a regular function match
571571
regular_match = spec_name == function and (arity == nil or spec_arity == arity)
572-
572+
573573
# Check if it's a macro match (MACRO-name/arity+1)
574574
macro_name = String.to_atom("MACRO-#{function}")
575-
macro_match = if arity == nil do
576-
spec_name == macro_name
577-
else
578-
spec_name == macro_name and spec_arity == arity + 1
579-
end
580-
575+
576+
macro_match =
577+
if arity == nil do
578+
spec_name == macro_name
579+
else
580+
spec_name == macro_name and spec_arity == arity + 1
581+
end
582+
581583
regular_match or macro_match
582584

583585
_ ->
@@ -588,11 +590,11 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
588590
filtered_specs
589591
|> Map.new(fn {_key, {{name, spec_arity}, specs}} ->
590592
formatted_spec = Introspection.spec_to_string({{name, spec_arity}, specs}, :spec)
591-
593+
592594
# Normalize macro names for the result key
593595
{display_name, display_arity} = normalize_macro_name_and_arity(name, spec_arity)
594596
normalized_key = {String.to_atom(display_name), display_arity}
595-
597+
596598
# Return as a list containing the single spec for compatibility with Enum.map_join
597599
{normalized_key, [formatted_spec]}
598600
end)
@@ -654,6 +656,17 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregator do
654656
defp extract_doc(:none), do: nil
655657
defp extract_doc(_), do: nil
656658

659+
defp format_builtin_function_doc(doc, specs) do
660+
spec_part =
661+
if specs != [] do
662+
"\n\n**Specs:**\n#{Enum.map_join(specs, "\n", fn s -> "```elixir\n#{s}\n```" end)}"
663+
else
664+
""
665+
end
666+
667+
"#{doc}#{spec_part}"
668+
end
669+
657670
defp format_function_sections(sections) do
658671
sections
659672
|> Enum.map(fn section ->

apps/language_server/test/providers/execute_command/llm_docs_aggregator_test.exs

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
402402
assert macro_result.function == "some_macro"
403403
assert macro_result.arity == 2
404404
assert macro_result.documentation =~ "An example macro"
405-
405+
406406
# Check that metadata is included (since: "1.1.0")
407407
assert macro_result.documentation =~ "Since"
408408
assert macro_result.documentation =~ "1.1.0"
@@ -437,7 +437,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
437437
assert macrocallback_result.callback == "some_macrocallback"
438438
assert macrocallback_result.arity == 1
439439
assert macrocallback_result.documentation =~ "An example callback"
440-
440+
441441
# Check that we got the documentation (metadata may be formatted differently for callbacks)
442442
assert macrocallback_result.documentation
443443
end
@@ -459,23 +459,33 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
459459

460460
test "verifies macro and macrocallback specs are included" do
461461
# Test callback spec
462-
assert {:ok, result} = LlmDocsAggregator.execute([["ElixirSenseExample.ModuleWithDocs.some_callback/1"]], %{})
462+
assert {:ok, result} =
463+
LlmDocsAggregator.execute(
464+
[["ElixirSenseExample.ModuleWithDocs.some_callback/1"]],
465+
%{}
466+
)
467+
463468
assert Map.has_key?(result, :results)
464469
assert length(result.results) == 1
465-
470+
466471
callback_result = hd(result.results)
467472
assert Map.has_key?(callback_result, :spec)
468473
assert Map.has_key?(callback_result, :kind)
469474
assert Map.has_key?(callback_result, :metadata)
470475
assert callback_result.spec == "@callback some_callback(integer()) :: atom()"
471476
assert callback_result.kind == :callback
472477
assert callback_result.metadata.since == "1.1.0"
473-
478+
474479
# Test macrocallback spec
475-
assert {:ok, result} = LlmDocsAggregator.execute([["ElixirSenseExample.ModuleWithDocs.some_macrocallback/1"]], %{})
480+
assert {:ok, result} =
481+
LlmDocsAggregator.execute(
482+
[["ElixirSenseExample.ModuleWithDocs.some_macrocallback/1"]],
483+
%{}
484+
)
485+
476486
assert Map.has_key?(result, :results)
477487
assert length(result.results) == 1
478-
488+
479489
macrocallback_result = hd(result.results)
480490
assert Map.has_key?(macrocallback_result, :spec)
481491
assert Map.has_key?(macrocallback_result, :kind)
@@ -499,7 +509,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
499509
assert func_result.function == "some_fun"
500510
assert func_result.arity == 2
501511
assert func_result.documentation =~ "An example fun"
502-
512+
503513
# Verify that specs are included in the documentation
504514
assert func_result.documentation =~ "**Specs:**"
505515
assert func_result.documentation =~ "@spec some_fun"
@@ -521,7 +531,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
521531
assert macro_result.function == "some_macro"
522532
assert macro_result.arity == 2
523533
assert macro_result.documentation =~ "An example macro"
524-
534+
525535
# Verify that specs are included in the macro documentation
526536
assert macro_result.documentation =~ "**Specs:**"
527537
assert macro_result.documentation =~ "@spec some_macro"
@@ -541,11 +551,13 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
541551
# Find the result with arity 2 (which has specs)
542552
func_result = Enum.find(result.results, &(&1.arity == 2))
543553
assert func_result
544-
554+
545555
# Verify spec formatting
546556
assert func_result.documentation =~ "**Specs:**"
547557
assert func_result.documentation =~ "```elixir"
548-
assert func_result.documentation =~ "@spec some_fun(integer(), integer() | nil) :: integer()"
558+
559+
assert func_result.documentation =~
560+
"@spec some_fun(integer(), integer() | nil) :: integer()"
549561
end
550562

551563
test "verifies macro specs are properly formatted" do
@@ -560,13 +572,51 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.LlmDocsAggregatorTest
560572
# Find the result with arity 2 (which has specs)
561573
macro_result = Enum.find(result.results, &(&1.arity == 2))
562574
assert macro_result
563-
575+
564576
# Verify spec formatting for macro
565577
assert macro_result.documentation =~ "**Specs:**"
566578
assert macro_result.documentation =~ "```elixir"
567-
assert macro_result.documentation =~ "@spec some_macro(Macro.t(), Macro.t() | nil) :: Macro.t()"
579+
580+
assert macro_result.documentation =~
581+
"@spec some_macro(Macro.t(), Macro.t() | nil) :: Macro.t()"
568582
end
569583

584+
test "handles builtin types with various arities" do
585+
# Test builtin type without arity - returns all matching types
586+
modules = ["list"]
587+
assert {:ok, result} = LlmDocsAggregator.execute([modules], %{})
588+
assert Map.has_key?(result, :results)
589+
assert length(result.results) == 2
590+
591+
# Should get both list() and list/1
592+
list_result = Enum.find(result.results, &(&1.type == "list()"))
593+
assert list_result
594+
assert list_result.documentation == "A list"
595+
596+
list1_result = Enum.find(result.results, &(&1.type == "list/1"))
597+
assert list1_result
598+
assert list1_result.documentation == "Proper list ([]-terminated)"
599+
600+
# Test builtin type with arity 0
601+
modules = ["list/0"]
602+
assert {:ok, result} = LlmDocsAggregator.execute([modules], %{})
603+
assert Map.has_key?(result, :results)
604+
assert length(result.results) == 1
605+
606+
list0_result = hd(result.results)
607+
assert list0_result.type == "list()"
608+
assert list0_result.documentation == "A list"
609+
610+
# Test builtin type with arity 1
611+
modules = ["list/1"]
612+
assert {:ok, result} = LlmDocsAggregator.execute([modules], %{})
613+
assert Map.has_key?(result, :results)
614+
assert length(result.results) == 1
615+
616+
list1_single = hd(result.results)
617+
assert list1_single.type == "list/1"
618+
assert list1_single.documentation == "Proper list ([]-terminated)"
619+
end
570620

571621
test "returns error for invalid arguments" do
572622
# Test with non-list argument

dep_versions.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2-
elixir_sense: "9140a547e8bcbdbe9146eb57afc286c550fea241",
2+
elixir_sense: "bfefb1f7ef51fb38354183dd3e9d24d6d4d960fb",
33
dialyxir_vendored: "f8f64cfb6797c518294687e7c03ae817bacbc6ee",
44
jason_v: "f1c10fa9c445cb9f300266122ef18671054b2330",
55
erl2ex_vendored: "073ac6b9a44282e718b6050c7b27cedf9217a12a",

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"},
33
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
44
"dialyxir_vendored": {:git, "https://github.com/elixir-lsp/dialyxir.git", "f8f64cfb6797c518294687e7c03ae817bacbc6ee", [ref: "f8f64cfb6797c518294687e7c03ae817bacbc6ee"]},
5-
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "9140a547e8bcbdbe9146eb57afc286c550fea241", [ref: "9140a547e8bcbdbe9146eb57afc286c550fea241"]},
5+
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "bfefb1f7ef51fb38354183dd3e9d24d6d4d960fb", [ref: "bfefb1f7ef51fb38354183dd3e9d24d6d4d960fb"]},
66
"erl2ex_vendored": {:git, "https://github.com/elixir-lsp/erl2ex.git", "073ac6b9a44282e718b6050c7b27cedf9217a12a", [ref: "073ac6b9a44282e718b6050c7b27cedf9217a12a"]},
77
"erlex_vendored": {:git, "https://github.com/elixir-lsp/erlex.git", "c0e448db27bcbb3f369861d13e3b0607ed37048d", [ref: "c0e448db27bcbb3f369861d13e3b0607ed37048d"]},
88
"jason_v": {:git, "https://github.com/elixir-lsp/jason.git", "f1c10fa9c445cb9f300266122ef18671054b2330", [ref: "f1c10fa9c445cb9f300266122ef18671054b2330"]},

0 commit comments

Comments
 (0)