Skip to content

Commit d1d3a08

Browse files
committed
Support operators in container_cursor_to_quoted
1 parent faf81cd commit d1d3a08

File tree

3 files changed

+35
-39
lines changed

3 files changed

+35
-39
lines changed

lib/elixir/lib/code/fragment.ex

+5-22
Original file line numberDiff line numberDiff line change
@@ -962,9 +962,8 @@ defmodule Code.Fragment do
962962
This function receives a string with an Elixir code fragment,
963963
representing a cursor position, and converts such string to
964964
AST with the inclusion of special `__cursor__()` node representing
965-
the cursor position within its parent.
965+
the cursor position within its container (i.e. its parent).
966966
967-
The parent node is any function call, tuple, list, map, and so on.
968967
For example, take this code, which would be given as input:
969968
970969
max(some_value,
@@ -995,16 +994,7 @@ defmodule Code.Fragment do
995994
Calls without parenthesis are also supported, as we assume the
996995
brackets are implicit.
997996
998-
Operators and anonymous functions are not containers, and therefore
999-
will be discarded. The following will all return the same AST:
1000-
1001-
max(some_value,
1002-
max(some_value, fn x -> x end
1003-
max(some_value, 1 + another_val
1004-
max(some_value, 1 |> some_fun() |> another_fun
1005-
1006-
On the other hand, tuples, lists, maps, and binaries all retain the
1007-
cursor position:
997+
Tuples, lists, maps, and binaries all retain the cursor position:
1008998
1009999
max(some_value, [1, 2,
10101000
@@ -1049,18 +1039,11 @@ defmodule Code.Fragment do
10491039
10501040
iex> Code.Fragment.container_cursor_to_quoted("if(is_atom(var))")
10511041
{:ok, {:__cursor__, [line: 1], []}}
1052-
iex> Code.Fragment.container_cursor_to_quoted("alias Foo.Bar")
1053-
{:ok, {:__cursor__, [line: 1], []}}
1054-
1055-
Operators are never considered containers:
1056-
1057-
iex> Code.Fragment.container_cursor_to_quoted("if(foo +")
1058-
{:ok, {:if, [line: 1], [{:__cursor__, [line: 1], []}]}}
10591042
1060-
with the exception of `::` inside binaries and `|` inside maps:
1043+
Operators are also included from Elixir v1.15:
10611044
1062-
iex> Code.Fragment.container_cursor_to_quoted("<<some::integer")
1063-
{:ok, {:<<>>, [line: 1], [{:"::", [line: 1], [{:some, [line: 1], nil}, {:__cursor__, [line: 1], []}]}]}}
1045+
iex> Code.Fragment.container_cursor_to_quoted("foo +")
1046+
{:ok, {:+, [line: 1], [{:foo, [line: 1], nil}, {:__cursor__, [line: 1], []}]}}
10641047
10651048
## Options
10661049

lib/elixir/src/elixir_tokenizer.erl

+7-4
Original file line numberDiff line numberDiff line change
@@ -1784,10 +1784,13 @@ prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, [], Terminators) ->
17841784
{Tokens, Terminators};
17851785
prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, [], Terminators) ->
17861786
{Tokens, Terminators};
1787-
%%% we usually skip operators, except these contextual ones
1788-
prune_tokens([{type_op, _, '::'} | _] = Tokens, [], [{'<<', _, _} | _] = Terminators) ->
1789-
{Tokens, Terminators};
1790-
prune_tokens([{pipe_op, _, '|'} | _] = Tokens, [], [{'{', _, _} | _] = Terminators) ->
1787+
prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
1788+
when OpType =:= comp_op; OpType =:= at_op; OpType =:= unary_op; OpType =:= and_op;
1789+
OpType =:= or_op; OpType =:= arrow_op; OpType =:= match_op; OpType =:= in_op;
1790+
OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
1791+
OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
1792+
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
1793+
OpType =:= rel_op; OpType =:= ternary_op ->
17911794
{Tokens, Terminators};
17921795
%%% or we traverse until the end.
17931796
prune_tokens([_ | Tokens], Opener, Terminators) ->

lib/elixir/test/elixir/code_fragment_test.exs

+23-13
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,17 @@ defmodule CodeFragmentTest do
10961096
assert cc2q(~S|"foo #{"bar #{{|) == s2q(~S|"foo #{"bar #{{__cursor__()}}"}"|)
10971097
end
10981098

1099+
test "keeps operators" do
1100+
assert cc2q("1 + 2") == s2q("1 + __cursor__()")
1101+
end
1102+
1103+
test "keeps function calls without parens" do
1104+
# assert cc2q("alias foo") == s2q("alias __cursor__()")
1105+
# assert cc2q("alias Foo.Bar") == s2q("alias __cursor__()")
1106+
assert cc2q("alias Foo.Bar,") == s2q("alias Foo.Bar, __cursor__()")
1107+
assert cc2q("alias Foo.Bar, as: ") == s2q("alias Foo.Bar, as: __cursor__()")
1108+
end
1109+
10991110
test "do-end blocks" do
11001111
assert cc2q("foo do baz") == s2q("foo do __cursor__() end")
11011112
assert cc2q("foo do bar; baz") == s2q("foo do bar; __cursor__() end")
@@ -1166,20 +1177,19 @@ defmodule CodeFragmentTest do
11661177
assert cc2q("(123") == s2q("(__cursor__())")
11671178
assert cc2q("[foo") == s2q("[__cursor__()]")
11681179
assert cc2q("{'foo'") == s2q("{__cursor__()}")
1169-
assert cc2q("<<1+2") == s2q("<<__cursor__()>>")
11701180
assert cc2q("foo do :atom") == s2q("foo do __cursor__() end")
11711181
assert cc2q("foo(:atom") == s2q("foo(__cursor__())")
11721182
end
11731183

11741184
test "removes tokens until comma" do
11751185
assert cc2q("[bar, 123") == s2q("[bar, __cursor__()]")
11761186
assert cc2q("{bar, 'foo'") == s2q("{bar, __cursor__()}")
1177-
assert cc2q("<<bar, 1+2") == s2q("<<bar, __cursor__()>>")
1187+
assert cc2q("<<bar, \"sample\"") == s2q("<<bar, __cursor__()>>")
11781188
assert cc2q("foo(bar, :atom") == s2q("foo(bar, __cursor__())")
11791189
assert cc2q("foo bar, :atom") == s2q("foo(bar, __cursor__())")
11801190
end
11811191

1182-
test "removes functions" do
1192+
test "removes anonymous functions" do
11831193
assert cc2q("(fn") == s2q("(__cursor__())")
11841194
assert cc2q("(fn x") == s2q("(__cursor__())")
11851195
assert cc2q("(fn x ->") == s2q("(__cursor__())")
@@ -1194,20 +1204,20 @@ defmodule CodeFragmentTest do
11941204
end
11951205

11961206
test "removes closed terminators" do
1197-
assert cc2q("foo([1, 2, 3] |>") == s2q("foo(__cursor__())")
1198-
assert cc2q("foo({1, 2, 3} |>") == s2q("foo(__cursor__())")
1199-
assert cc2q("foo((1, 2, 3) |>") == s2q("foo(__cursor__())")
1200-
assert cc2q("foo(<<1, 2, 3>> |>") == s2q("foo(__cursor__())")
1201-
assert cc2q("foo(bar do :done end |>") == s2q("foo(__cursor__())")
1207+
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
1208+
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
1209+
assert cc2q("foo((1, 2, 3)") == s2q("foo(__cursor__())")
1210+
assert cc2q("foo(<<1, 2, 3>>") == s2q("foo(__cursor__())")
1211+
assert cc2q("foo(bar do :done end") == s2q("foo(__cursor__())")
12021212
end
12031213

12041214
test "incomplete expressions" do
1205-
# assert cc2q("foo(123, :") == s2q("foo(123, __cursor__())")
1206-
# assert cc2q("foo(123, %") == s2q("foo(123, __cursor__())")
1207-
# assert cc2q("foo(123, 0x") == s2q("foo(123, __cursor__())")
1208-
# assert cc2q("foo(123, ~") == s2q("foo(123, __cursor__())")
1215+
assert cc2q("foo(123, :") == s2q("foo(123, __cursor__())")
1216+
assert cc2q("foo(123, %") == s2q("foo(123, __cursor__())")
1217+
assert cc2q("foo(123, 0x") == s2q("foo(123, __cursor__())")
1218+
assert cc2q("foo(123, ~") == s2q("foo(123, __cursor__())")
12091219
assert cc2q("foo(123, ~r") == s2q("foo(123, __cursor__())")
1210-
# assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
1220+
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
12111221
end
12121222

12131223
test "no warnings" do

0 commit comments

Comments
 (0)