Skip to content

Commit f8075a3

Browse files
committed
Improve error messages when traversing source
1 parent 72bb758 commit f8075a3

File tree

4 files changed

+56
-57
lines changed

4 files changed

+56
-57
lines changed

lib/ex_doc/language.ex

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ defmodule ExDoc.Language do
2323
* `:source_file` - the source file the module code is located, defmodule in Elixir, or -module in Erlang
2424
2525
* `:source_basedir` - the absolute directory where the Elixir/Erlang compiler was run.
26-
See `ExDoc.Language.Source.get_basedir/2` for more details.
2726
2827
* `:callback_types` - a list of types that are considered callbacks
2928

lib/ex_doc/language/elixir.ex

+6-4
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ defmodule ExDoc.Language.Elixir do
3939
abst_code = Source.get_abstract_code(module) ->
4040
title = module_title(module, type)
4141

42-
source_basedir = Source.get_basedir(abst_code, module)
43-
{source_file, source_line} = Source.get_module_location(abst_code, source_basedir, module)
42+
source_basedir = Source.fetch_basedir!(abst_code, module)
43+
44+
{source_file, source_line} =
45+
Source.fetch_module_location!(abst_code, source_basedir, module)
4446

4547
optional_callbacks = Source.get_optional_callbacks(module, type)
4648

@@ -184,7 +186,7 @@ defmodule ExDoc.Language.Elixir do
184186
{{_kind, name, arity}, _anno, _signature, _doc, _metadata} = entry
185187

186188
%{type: type, spec: spec, source_file: source, source_line: line} =
187-
Source.get_type_from_module_data(module_data, name, arity)
189+
Source.fetch_type!(module_data, name, arity)
188190

189191
quoted = spec |> Code.Typespec.type_to_quoted() |> process_type_ast(type)
190192
signature = [get_typespec_signature(quoted, arity)]
@@ -530,7 +532,7 @@ defmodule ExDoc.Language.Elixir do
530532
end
531533

532534
defp find_function_line(module_data, na) do
533-
{_source, line} = Source.get_function_location(module_data, na)
535+
{_source, line} = Source.fetch_function_location!(module_data, na)
534536
line
535537
end
536538

lib/ex_doc/language/erlang.ex

+7-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ defmodule ExDoc.Language.Erlang do
2626
def module_data(module, docs_chunk, _config) do
2727
if abst_code = Source.get_abstract_code(module) do
2828
id = Atom.to_string(module)
29-
source_basedir = Source.get_basedir(abst_code, module)
30-
{source_file, source_line} = Source.get_module_location(abst_code, source_basedir, module)
29+
source_basedir = Source.fetch_basedir!(abst_code, module)
30+
31+
{source_file, source_line} =
32+
Source.fetch_module_location!(abst_code, source_basedir, module)
33+
3134
type = module_type(module)
3235

3336
%{
@@ -115,7 +118,7 @@ defmodule ExDoc.Language.Erlang do
115118
end
116119
end
117120

118-
{file, line} = Source.get_function_location(module_data, {name, arity})
121+
{file, line} = Source.fetch_function_location!(module_data, {name, arity})
119122

120123
%{
121124
doc_fallback: fn -> equiv_data(module_data.module, file, line, metadata) end,
@@ -164,7 +167,7 @@ defmodule ExDoc.Language.Erlang do
164167
def type_data(entry, module_data) do
165168
{{kind, name, arity}, anno, signature, _doc, metadata} = entry
166169

167-
case Source.get_type_from_module_data(module_data, name, arity) do
170+
case Source.fetch_type!(module_data, name, arity) do
168171
%{} = map ->
169172
%{
170173
doc_fallback: fn ->

lib/ex_doc/language/source.ex

+43-48
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
defmodule ExDoc.Language.Source do
22
@moduledoc false
33

4+
def anno_line(line) when is_integer(line), do: abs(line)
5+
def anno_line(anno), do: anno |> :erl_anno.line() |> abs()
6+
7+
def anno_file(anno) do
8+
case :erl_anno.file(anno) do
9+
:undefined ->
10+
nil
11+
12+
file ->
13+
String.Chars.to_string(file)
14+
end
15+
end
16+
417
@doc """
518
Get abstract code and basedir for a module
619
@@ -18,7 +31,7 @@ defmodule ExDoc.Language.Source do
1831
end
1932

2033
defp expand_records_in_types(abst_code) do
21-
## Find all records in ast and collect any fields with type declarations
34+
# Find all records in ast and collect any fields with type declarations
2235
records =
2336
filtermap_ast(abst_code, nil, fn
2437
{:attribute, anno, :record, {name, fields}} ->
@@ -40,7 +53,7 @@ defmodule ExDoc.Language.Source do
4053
end)
4154
|> Map.new()
4255

43-
## Expand records in all specs, callbacks, types and opaques
56+
# Expand records in all specs, callbacks, types and opaques
4457
filtermap_ast(abst_code, nil, fn
4558
{:attribute, anno, kind, {mfa, ast}} when kind in [:spec, :callback] ->
4659
ast = Enum.map(ast, &expand_records(&1, records))
@@ -62,16 +75,16 @@ defmodule ExDoc.Language.Source do
6275
{:ann_type, anno, [name, expand_records(type, records)]}
6376
end
6477

65-
## When we encounter a record, we fetch the type definitions in the record and
66-
## merge then with the type. If there are duplicates we take the one in the type
67-
## declaration
78+
# When we encounter a record, we fetch the type definitions in the record and
79+
# merge then with the type. If there are duplicates we take the one in the type
80+
# declaration
6881
defp expand_records({:type, anno, :record, [{:atom, _, record} = name | args]}, records) do
6982
args =
7083
(args ++ Map.get(records, record, []))
7184
|> Enum.uniq_by(fn {:type, _, :field_type, [{:atom, _, name} | _]} -> name end)
7285

73-
## We delete the record from the map so that recursive
74-
## record definitions are not expanded.
86+
# We delete the record from the map so that recursive
87+
# record definitions are not expanded.
7588
records = Map.delete(records, record)
7689

7790
{:type, anno, :record, expand_records([name | args], records)}
@@ -90,32 +103,31 @@ defmodule ExDoc.Language.Source do
90103
end
91104

92105
@doc """
93-
Get the basedir of a module
106+
Fetches the basedir of a module.
94107
95108
The basedir is the cwd of the Elixir/Erlang compiler when compiling the module.
96109
All `-file` attributes in the module is relative to this directory.
97110
"""
98-
def get_basedir(abst_code, module) do
99-
## We look for the first -file attribute to see what the source file that
100-
## was compiled is called. Both Erlang and Elixir places one at the top.
111+
def fetch_basedir!(abst_code, module) do
112+
# We look for the first -file attribute to see what the source file that
113+
# was compiled is called. Both Erlang and Elixir places one at the top.
101114
filename =
102115
Enum.find_value(abst_code, fn
103116
{:attribute, _anno, :file, {filename, _line}} ->
104117
filename
105118

106119
_ ->
107120
nil
108-
end)
121+
end) || raise "could not find base directory for #{inspect(module)}"
109122

110-
## The first -file attribute will be either relative or absolute
111-
## depending on whether the compiler was called with an absolute
112-
## or relative path.
123+
# The first -file attribute will be either relative or absolute
124+
# depending on whether the compiler was called with an absolute
125+
# or relative path.
113126
if Path.type(filename) == :relative do
114-
## If the compiler was called with a relative path, then any other
115-
## relative -file attribute will be relative to the same directory.
116-
## We use `module_info(:compile)[:source]` to get an absolute path
117-
## to the source file and calculate the basedir from that
118-
127+
# If the compiler was called with a relative path, then any other
128+
# relative -file attribute will be relative to the same directory.
129+
# We use `module_info(:compile)[:source]` to get an absolute path
130+
# to the source file and calculate the basedir from that
119131
compile_source =
120132
cond do
121133
source = module.module_info(:compile)[:source] ->
@@ -143,27 +155,27 @@ defmodule ExDoc.Language.Source do
143155
|> Enum.drop(Path.split(filename) |> Enum.count() |> Kernel.*(-1))
144156
|> Path.join()
145157
else
146-
## If an absolute path was used, then any relative -file attribute
147-
## is relative to the directory of the source file
158+
# If an absolute path was used, then any relative -file attribute
159+
# is relative to the directory of the source file
148160
Path.dirname(filename)
149161
end
150162
end
151163

152-
def get_module_location(abst_code, source_basedir, module) do
164+
def fetch_module_location!(abst_code, source_basedir, module) do
153165
find_ast(abst_code, source_basedir, fn
154166
{:attribute, anno, :module, ^module} ->
155167
{anno_file(anno), anno_line(anno)}
156168

157169
_ ->
158170
nil
159-
end)
171+
end) || raise "could not find module definition for #{inspect(module)}"
160172
end
161173

162-
def get_function_location(module_data, {name, arity}) do
174+
def fetch_function_location!(module_data, {name, arity}) do
163175
find_ast(module_data.private.abst_code, module_data.source_basedir, fn
164176
{:function, anno, ^name, ^arity, _} -> {anno_file(anno), anno_line(anno)}
165177
_ -> nil
166-
end)
178+
end) || raise "could not find function definition for #{name}/#{arity}"
167179
end
168180

169181
# Returns a map of {name, arity} => spec.
@@ -178,7 +190,7 @@ defmodule ExDoc.Language.Source do
178190
|> Map.new()
179191
end
180192

181-
def get_type_from_module_data(module_data, name, arity) do
193+
def fetch_type!(module_data, name, arity) do
182194
find_ast(module_data.private.abst_code, module_data.source_basedir, fn
183195
{:attribute, anno, type, {^name, _, args} = spec} = attr ->
184196
if type in [:opaque, :type] and length(args) == arity do
@@ -193,7 +205,7 @@ defmodule ExDoc.Language.Source do
193205

194206
_ ->
195207
nil
196-
end)
208+
end) || raise "could not find type definition for #{name}/#{arity}"
197209
end
198210

199211
def get_callbacks(abst_code, source_basedir) do
@@ -215,20 +227,16 @@ defmodule ExDoc.Language.Source do
215227

216228
def get_optional_callbacks(_module, _type), do: []
217229

218-
def find_ast(ast, source_basedir, fun) do
219-
filtermap_ast(ast, source_basedir, fun) |> hd()
230+
defp find_ast(ast, source_basedir, fun) do
231+
filtermap_ast(ast, source_basedir, fun) |> List.first()
220232
end
221233

222-
@doc """
223-
Does a filtermap operation over the forms in an abstract syntax tree with
224-
updated anno for each form pointing to the correct file.
225-
"""
226234
# The file which a form belongs to is decided by the previous :file
227235
# attribute in the AST. The :file can be either relative, or absolute
228236
# depending on how the file was included. So when traversing the AST
229237
# we need to keep track of the :file attributes and update the anno
230238
# with the correct file.
231-
def filtermap_ast(ast, source_basedir, fun) do
239+
defp filtermap_ast(ast, source_basedir, fun) do
232240
Enum.reduce(ast, {nil, []}, fn
233241
{:attribute, _anno, :file, {filename, _line}} = entry, {_file, acc} ->
234242
{if Path.type(filename) == :relative && source_basedir do
@@ -262,17 +270,4 @@ defmodule ExDoc.Language.Source do
262270
|> elem(1)
263271
|> Enum.reverse()
264272
end
265-
266-
def anno_line(line) when is_integer(line), do: abs(line)
267-
def anno_line(anno), do: anno |> :erl_anno.line() |> abs()
268-
269-
def anno_file(anno) do
270-
case :erl_anno.file(anno) do
271-
:undefined ->
272-
nil
273-
274-
file ->
275-
String.Chars.to_string(file)
276-
end
277-
end
278273
end

0 commit comments

Comments
 (0)