Skip to content

Commit

Permalink
Bump erlfmt to upstream 1.3.0 (#1469)
Browse files Browse the repository at this point in the history
* Bump erlfmt to 1.3.0

* Test ;-separated guards in macro expressions

Supported added to erlfmt WhatsApp/erlfmt#341

* Add parsing test for map (and other) comprehensions

* Support maybe expressions in the parser

* Convert error location returned by erlfmt to a consistent format

* Test parsing macro as case clause

* Skip test of maybe expr/map compr if not supported by OTP version

* Silence a dilayzer warning because of wrong erlfmt spec
  • Loading branch information
gomoripeti authored Jan 17, 2024
1 parent 87aa757 commit ca03a27
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 18 deletions.
4 changes: 4 additions & 0 deletions apps/els_lsp/src/els_erlfmt_ast.erl
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ erlfmt_to_st(Node) ->
{clause, _, _, _, _} = Clause ->
%% clauses of case/if/receive/try
erlfmt_clause_to_st(Clause);
{else_clause, Pos, Clauses} ->
%% The else clause of a maybe expression - in OTP it is just called
%% 'else' but has the same format and content
erlfmt_to_st_1({'else', Pos, Clauses});
%% Lists are represented as a `list` node instead of a chain of `cons`
%% and `nil` nodes, similar to the `tuple` node. The last element of
%% the list can be a `cons` node representing explicit consing syntax.
Expand Down
31 changes: 24 additions & 7 deletions apps/els_lsp/src/els_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
-dialyzer([{nowarn_function, parse_text/1}]).
-dialyzer([{nowarn_function, fix_erlfmt/1}]).

%% Spec of erlfmt_parse:parse_node/1 is wrong,
%% error location can be returned in various formats
%% see https://github.com/WhatsApp/erlfmt/pull/352

-dialyzer([{nowarn_function, loc_to_pos/1}]).

%%==============================================================================
%% API
%%==============================================================================
Expand Down Expand Up @@ -149,25 +155,36 @@ parse_incomplete_tokens(Tokens) ->
{ok, Form} ->
{ok, Form};
{error, {ErrorLoc, erlfmt_parse, _Reason}} ->
TrimmedTokens = tokens_until(Tokens, ErrorLoc),
ErrorPos = loc_to_pos(ErrorLoc),
TrimmedTokens = tokens_until(Tokens, ErrorPos),
parse_incomplete_tokens(TrimmedTokens)
end.

%% Convert location in various formats to a consistent position that can always be compared
-spec loc_to_pos(erlfmt_scan:anno() | erl_anno:location()) -> pos().
loc_to_pos(#{location := Loc}) ->
%% erlfmt_scan:anno()
loc_to_pos(Loc);
loc_to_pos({Line, Col} = Loc) when is_integer(Line), is_integer(Col) ->
Loc;
loc_to_pos(Line) when is_integer(Line) ->
{Line, 0}.

%% @doc Drop tokens after given location but keep final dot, to preserve its
%% location
-spec tokens_until([erlfmt_scan:token()], erl_anno:location()) ->
-spec tokens_until([erlfmt_scan:token()], pos()) ->
[erlfmt_scan:token()].
tokens_until([_Hd, {dot, _} = Dot], _Loc) ->
tokens_until([_Hd, {dot, _} = Dot], _Pos) ->
%% We need to drop at least one token before the dot.
%% Otherwise if error location is at the dot, we cannot just drop the dot and
%% add a dot again, because it would result in an infinite loop.
[Dot];
tokens_until([Hd | Tail], Loc) ->
case erlfmt_scan:get_anno(location, Hd) < Loc of
tokens_until([Hd | Tail], Pos) ->
case erlfmt_scan:get_anno(location, Hd) < Pos of
true ->
[Hd | tokens_until(Tail, Loc)];
[Hd | tokens_until(Tail, Pos)];
false ->
tokens_until(Tail, Loc)
tokens_until(Tail, Pos)
end.

%% `erlfmt_scan' does not support start location other than {1,1}
Expand Down
59 changes: 58 additions & 1 deletion apps/els_lsp/test/els_parser_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
-export([
all/0,
init_per_suite/1,
end_per_suite/1
end_per_suite/1,
init_per_testcase/2
]).

%% Test cases
Expand Down Expand Up @@ -32,6 +33,9 @@
opaque_recursive/1,
record_def_recursive/1,
var_in_application/1,
comprehensions/1,
map_comprehensions/1,
maybe_expr/1,
unicode_clause_pattern/1,
latin1_source_code/1,
record_comment/1,
Expand Down Expand Up @@ -61,6 +65,23 @@ end_per_suite(_Config) -> ok.
-spec all() -> [atom()].
all() -> els_test_utils:all(?MODULE).

init_per_testcase(map_comprehensions, Config) ->
case list_to_integer(erlang:system_info(otp_release)) < 26 of
true ->
{skip, "Map comprehensions are only supported from OTP 26"};
false ->
Config
end;
init_per_testcase(maybe_expr, Config) ->
case list_to_integer(erlang:system_info(otp_release)) < 25 of
true ->
{skip, "Maybe expressions are only supported from OTP 25"};
false ->
Config
end;
init_per_testcase(_, Config) ->
Config.

%%==============================================================================
%% Testcases
%%==============================================================================
Expand Down Expand Up @@ -443,6 +464,42 @@ var_in_application(_Config) ->
),
ok.

comprehensions(_Config) ->
Text1 = "[X || X <- L]",
?assertMatch(
[#{id := 'X'}, #{id := 'X'}, #{id := 'L'}],
parse_find_pois(Text1, variable)
),

Text2 = "<< <<Y, X>> || <<X, Y>> <= B >>",
?assertMatch(
[#{id := 'Y'}, #{id := 'X'}, #{id := 'X'}, #{id := 'Y'}, #{id := 'B'}],
parse_find_pois(Text2, variable)
),
ok.

map_comprehensions(_Config) ->
Text3 = "#{ Y => X || X := Y <- M }",
?assertMatch(
[#{id := 'Y'}, #{id := 'X'}, #{id := 'X'}, #{id := 'Y'}, #{id := 'M'}],
parse_find_pois(Text3, variable)
),
ok.

maybe_expr(_Config) ->
Text1 = "maybe {ok, X} ?= f(), {ok, Y} ?= g() end",
?assertMatch(
[#{id := 'X'}, #{id := 'Y'}],
parse_find_pois(Text1, variable)
),

Text2 = "maybe {ok, X} ?= f() else {error, Err} -> Err end",
?assertMatch(
[#{id := 'X'}, #{id := 'Err'}, #{id := 'Err'}],
parse_find_pois(Text2, variable)
),
ok.

-spec unicode_clause_pattern(config()) -> ok.
unicode_clause_pattern(_Config) ->
%% From OTP compiler's bs_utf_SUITE.erl
Expand Down
28 changes: 27 additions & 1 deletion apps/els_lsp/test/els_parser_macros_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
macro_in_application/1,
record_def_field_macro/1,
module_macro_as_record_name/1,
other_macro_as_record_name/1
other_macro_as_record_name/1,
macro_guards/1,
macro_as_case_clause/1
]).

%%==============================================================================
Expand Down Expand Up @@ -229,6 +231,30 @@ other_macro_as_record_name(_Config) ->
?assertMatch([_], parse_find_pois(Text4, macro, 'M')),
ok.

macro_guards(_Config) ->
Text1 = "?foo(Expr when Guard1)",
?assertMatch([#{id := 'Expr'}, #{id := 'Guard1'}], parse_find_pois(Text1, variable)),

Text2 = "?foo(Expr when Guard1; Guard2)",
?assertMatch(
[#{id := 'Expr'}, #{id := 'Guard1'}, #{id := 'Guard2'}],
parse_find_pois(Text2, variable)
),
ok.

%% Supperted by erlfmt since erlfmt#350
macro_as_case_clause(_Config) ->
Text1 = "case X of ?M1(Y); ?M2(2) end",
?assertMatch(
[#{id := {'M1', 1}}, #{id := {'M2', 1}}],
parse_find_pois(Text1, macro)
),
?assertMatch(
[#{id := 'X'}, #{id := 'Y'}],
parse_find_pois(Text1, variable)
),
ok.

%%==============================================================================
%% Helper functions
%%==============================================================================
Expand Down
6 changes: 1 addition & 5 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
{docsh, "0.7.2"},
{elvis_core, "~> 1.3"},
{rebar3_format, "0.8.2"},
%%, {erlfmt, "1.0.0"}

%% Temp until erlfmt PR 325 is merged (commit d4422d1)
{erlfmt,
{git, "https://github.com/gomoripeti/erlfmt.git", {tag, "erlang_ls_parser_error_loc"}}},
{erlfmt, "1.3.0"},
{ephemeral, "2.0.4"},
{tdiff, "0.1.2"},
{uuid, "2.0.1", {pkg, uuid_erl}},
Expand Down
7 changes: 3 additions & 4 deletions rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
{<<"docsh">>,{pkg,<<"docsh">>,<<"0.7.2">>},0},
{<<"elvis_core">>,{pkg,<<"elvis_core">>,<<"1.3.1">>},0},
{<<"ephemeral">>,{pkg,<<"ephemeral">>,<<"2.0.4">>},0},
{<<"erlfmt">>,
{git,"https://github.com/gomoripeti/erlfmt.git",
{ref,"d4422d1fd79a73ef534c2bcbe5b5da4da5338833"}},
0},
{<<"erlfmt">>,{pkg,<<"erlfmt">>,<<"1.3.0">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},2},
{<<"gradualizer">>,
{git,"https://github.com/josefs/Gradualizer.git",
Expand All @@ -31,6 +28,7 @@
{<<"docsh">>, <<"F893D5317A0E14269DD7FE79CF95FB6B9BA23513DA0480EC6E77C73221CAE4F2">>},
{<<"elvis_core">>, <<"844C339300DD3E9F929A045932D25DC5C99B4603D47536E995198143169CDF26">>},
{<<"ephemeral">>, <<"B3E57886ADD5D90C82FE3880F5954978222A122CB8BAA123667401BBAAEC51D6">>},
{<<"erlfmt">>, <<"672994B92B1A809C04C46F0B781B447BF9AB7A515F5856A96177BC1962F100A9">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
{<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>},
{<<"katana_code">>, <<"B2195859DF57D8BEBF619A9FD3327CD7D01563A98417156D0F4C5FAB435F2630">>},
Expand All @@ -46,6 +44,7 @@
{<<"docsh">>, <<"4E7DB461BB07540D2BC3D366B8513F0197712D0495BB85744F367D3815076134">>},
{<<"elvis_core">>, <<"7A8890BF8185A3252CD4EBD826FE5F8AD6B93024EDF88576EB27AE9E5DC19D69">>},
{<<"ephemeral">>, <<"4B293D80F75F9C4575FF4B9C8E889A56802F40B018BF57E74F19644EFEE6C850">>},
{<<"erlfmt">>, <<"2A84AA1EBA2F4FCD7DD31D5C57E9DE2BC2705DDA18DA4553F27DF7114CFAA052">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>},
{<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>},
{<<"katana_code">>, <<"8448AD3F56D9814F98A28BE650F7191BDD506575E345CC16D586660B10F6E992">>},
Expand Down

0 comments on commit ca03a27

Please sign in to comment.