Skip to content

Commit 468228a

Browse files
authored
Add :indentation to Code.string_to_quoted/2 (#14406)
1 parent f628d95 commit 468228a

File tree

8 files changed

+89
-20
lines changed

8 files changed

+89
-20
lines changed

lib/eex/lib/eex/compiler.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ defmodule EEx.Compiler do
304304
source: source,
305305
line: line,
306306
quoted: [],
307-
parser_options: parser_options,
307+
parser_options: [indentation: indentation] ++ parser_options,
308308
indentation: indentation
309309
}
310310

lib/eex/test/eex_test.exs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,59 @@ defmodule EExTest do
502502
end
503503
end
504504

505+
test "from Elixir parser" do
506+
line = __ENV__.line + 6
507+
508+
message =
509+
assert_raise TokenMissingError, fn ->
510+
EEx.compile_string(
511+
"""
512+
<li>
513+
<strong>Some:</strong>
514+
<%= true && @some[ %>
515+
</li>
516+
""",
517+
file: __ENV__.file,
518+
line: line,
519+
indentation: 12
520+
)
521+
end
522+
523+
assert message |> Exception.message() |> strip_ansi() =~ """
524+
525+
514 │ true && @some[\s
526+
│ │ └ missing closing delimiter (expected "]")
527+
│ └ unclosed delimiter
528+
"""
529+
end
530+
531+
test "from Elixir parser with line breaks" do
532+
line = __ENV__.line + 6
533+
534+
message =
535+
assert_raise TokenMissingError, fn ->
536+
EEx.compile_string(
537+
"""
538+
<li>
539+
<strong>Some:</strong>
540+
<%= true &&
541+
@some[ %>
542+
</li>
543+
""",
544+
file: __ENV__.file,
545+
line: line,
546+
indentation: 12
547+
)
548+
end
549+
550+
assert message |> Exception.message() |> strip_ansi() =~ """
551+
552+
#{line + 3} │ @some[\s
553+
│ │ └ missing closing delimiter (expected "]")
554+
│ └ unclosed delimiter
555+
"""
556+
end
557+
505558
test "honor line numbers" do
506559
assert_raise EEx.SyntaxError,
507560
"nofile:100:6: expected closing '%>' for EEx expression",
@@ -948,6 +1001,12 @@ defmodule EExTest do
9481001
end
9491002
end
9501003

1004+
@strip_ansi [IO.ANSI.green(), IO.ANSI.red(), IO.ANSI.reset()]
1005+
1006+
defp strip_ansi(doc) do
1007+
String.replace(doc, @strip_ansi, "")
1008+
end
1009+
9511010
defp assert_eval(expected, actual, binding \\ [], opts \\ []) do
9521011
opts = Keyword.merge([file: __ENV__.file, engine: opts[:engine] || EEx.Engine], opts)
9531012
result = EEx.eval_string(actual, binding, opts)

lib/elixir/lib/code.ex

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,8 @@ defmodule Code do
10681068
Macro arguments are typically transformed by unquoting them into the
10691069
returned quoted expressions (instead of evaluated).
10701070
1071-
See `eval_string/3` for a description of `binding` and `opts`.
1071+
See `eval_string/3` for a description of arguments and return types.
1072+
The options are described under `env_for_eval/1`.
10721073
10731074
## Examples
10741075
@@ -1168,6 +1169,10 @@ defmodule Code do
11681169
* `:column` - (since v1.11.0) the starting column of the string being parsed.
11691170
Defaults to 1.
11701171
1172+
* `:indentation` - (since v1.19.0) the indentation for the string being parsed.
1173+
This is useful when the code parsed is embedded within another document.
1174+
Defaults to 0.
1175+
11711176
* `:columns` - when `true`, attach a `:column` key to the quoted
11721177
metadata. Defaults to `false`.
11731178
@@ -1360,13 +1365,11 @@ defmodule Code do
13601365
{forms, comments}
13611366

13621367
{:error, {location, error, token}} ->
1363-
:elixir_errors.parse_error(
1364-
location,
1365-
Keyword.get(opts, :file, "nofile"),
1366-
error,
1367-
token,
1368-
{charlist, Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1)}
1369-
)
1368+
file = Keyword.get(opts, :file, "nofile")
1369+
line = Keyword.get(opts, :line, 1)
1370+
column = Keyword.get(opts, :column, 1)
1371+
input = {charlist, line, column, Keyword.get(opts, :indentation, 0)}
1372+
:elixir_errors.parse_error(location, file, error, token, input)
13701373
end
13711374
end
13721375

lib/elixir/src/elixir.erl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,10 +490,14 @@ parser_location(Meta) ->
490490
{ok, Forms} ->
491491
Forms;
492492
{error, {Meta, Error, Token}} ->
493-
elixir_errors:parse_error(Meta, File, Error, Token, {String, StartLine, StartColumn})
493+
Indentation = proplists:get_value(indentation, Opts, 0),
494+
Input = {String, StartLine, StartColumn, Indentation},
495+
elixir_errors:parse_error(Meta, File, Error, Token, Input)
494496
end;
495497
{error, {Meta, Error, Token}} ->
496-
elixir_errors:parse_error(Meta, File, Error, Token, {String, StartLine, StartColumn})
498+
Indentation = proplists:get_value(indentation, Opts, 0),
499+
Input = {String, StartLine, StartColumn, Indentation},
500+
elixir_errors:parse_error(Meta, File, Error, Token, Input)
497501
end.
498502

499503
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);

lib/elixir/src/elixir_errors.erl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,11 +446,12 @@ cut_snippet(Location, Input) ->
446446
nil
447447
end.
448448

449-
cut_snippet({InputString, StartLine, StartColumn}, Line, Span) ->
449+
cut_snippet({InputString, StartLine, StartColumn, Indentation}, Line, Span) ->
450450
%% In case the code is indented, we need to add the indentation back
451451
%% for the snippets to match the reported columns.
452-
Indent = binary:copy(<<" ">>, StartColumn - 1),
453-
Lines = string:split(InputString, "\n", all),
452+
Prelude = lists:duplicate(max(StartColumn - Indentation - 1, 0), " "),
453+
Lines = string:split(Prelude ++ InputString, "\n", all),
454+
Indent = binary:copy(<<" ">>, Indentation),
454455
[Head | Tail] = lists:nthtail(Line - StartLine, Lines),
455456
IndentedTail = indent_n(Tail, Span - 1, <<"\n", Indent/binary>>),
456457
elixir_utils:characters_to_binary([Indent, Head, IndentedTail]).

lib/elixir/src/elixir_tokenizer.erl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ tokenize(String, Line, Column, Opts) ->
130130
Acc#elixir_tokenizer{preserve_comments=PreserveComments};
131131
({unescape, Unescape}, Acc) when is_boolean(Unescape) ->
132132
Acc#elixir_tokenizer{unescape=Unescape};
133+
({indentation, Indentation}, Acc) when Indentation >= 0 ->
134+
Acc#elixir_tokenizer{column=Indentation+1};
133135
(_, Acc) ->
134136
Acc
135-
end, #elixir_tokenizer{identifier_tokenizer=IdentifierTokenizer, column=Column}, Opts),
137+
end, #elixir_tokenizer{identifier_tokenizer=IdentifierTokenizer}, Opts),
136138

137139
tokenize(String, Line, Column, Scope, []).
138140

lib/elixir/test/elixir/code_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ defmodule CodeTest do
392392
Code.unrequire_files([fixture_path("code_sample.exs")])
393393
end
394394

395-
test "string_to_quoted!/2 errors take lines and columns into account" do
395+
test "string_to_quoted!/2 errors take lines/columns/indentation into account" do
396396
assert_exception(
397397
SyntaxError,
398398
["nofile:1:5:", "syntax error before:", "1 + * 3", "^"],
@@ -419,9 +419,9 @@ defmodule CodeTest do
419419

420420
assert_exception(
421421
SyntaxError,
422-
["nofile:11:7:", "syntax error before:", "1 + * 3", "^"],
422+
["nofile:11:15:", "syntax error before:", "1 + * 3", "^"],
423423
fn ->
424-
Code.string_to_quoted!(":ok\n1 + * 3", line: 10, column: 3)
424+
Code.string_to_quoted!(":ok\n1 + * 3", line: 10, column: 3, indentation: 10)
425425
end
426426
)
427427
end

lib/iex/lib/iex/evaluator.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ defmodule IEx.Evaluator do
8282
opts[:file],
8383
"incomplete expression",
8484
"",
85-
{~c"", Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1)}
85+
{~c"", Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1), 0}
8686
)
8787
end
8888

@@ -119,7 +119,7 @@ defmodule IEx.Evaluator do
119119
file,
120120
error,
121121
token,
122-
{charlist, line, column}
122+
{charlist, line, column, 0}
123123
)
124124
end
125125
end

0 commit comments

Comments
 (0)