Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- os: ubuntu-24.04
otp-version: 28
rebar3-version: 3.25
- os: ubuntu-24.04
otp-version: OTP-29.0-rc1
rebar3-version: 3.26
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
Expand Down
6 changes: 6 additions & 0 deletions doc/erlfmt_parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,9 @@ In `erlfmt_parse` the following AST nodes have different definitions:
* Representation for types is in general the same as for corresponding values.
The `type` node is not used at all. This means new binary operators inside types
are defined: `|`, `::`, and `..`.

* Comprehension nodes (`lc`, `mc`, `bc`) always represent the expression(s) before
`||` as a list, even for single-value comprehensions. For example, `[X || X <- List]`
produces `{lc, Anno, [X], Generators}` and `[I, -I || I <- List]` produces
`{lc, Anno, [I, {op, _, '-', I}], Generators}`. In `erl_parse`, single-value
comprehensions use a bare expression instead of a list.
24 changes: 15 additions & 9 deletions src/erlfmt_format.erl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ to_algebra({attribute, Meta, {atom, _, record}, [Name, {tuple, TMeta, Values} =
Doc = surround(HeadD, <<"">>, expr_to_algebra(Tuple), <<"">>, <<")">>),
combine_comments_with_dot(Meta, Doc)
end;
to_algebra({attribute, Meta, {atom, _, record}, [RecordExpr]}) ->
Doc = concat(<<"-record ">>, expr_to_algebra(RecordExpr)),
combine_comments_with_dot(Meta, Doc);
to_algebra({attribute, Meta, {atom, _, RawName} = Name, [Value]}) when
RawName =:= doc; RawName =:= moduledoc
->
Expand Down Expand Up @@ -184,12 +187,12 @@ do_expr_to_algebra({record_field, Meta, Expr, Name, Key}) ->
concat(wrap_if_integer(expr_to_algebra(Expr), Expr), record_access_to_algebra(Meta, Name, Key));
do_expr_to_algebra({record_name, Meta, Name}) ->
record_name_to_algebra(Meta, Name);
do_expr_to_algebra({lc, _Meta, Expr, LcExprs}) ->
comprehension_to_algebra(Expr, LcExprs, <<"[">>, <<"]">>);
do_expr_to_algebra({mc, _Meta, Expr, LcExprs}) ->
comprehension_to_algebra(Expr, LcExprs, <<"#{">>, <<"}">>);
do_expr_to_algebra({bc, _Meta, Expr, LcExprs}) ->
comprehension_to_algebra(Expr, LcExprs, <<"<<">>, <<">>">>);
do_expr_to_algebra({lc, _Meta, Exprs, LcExprs}) ->
comprehension_to_algebra(Exprs, LcExprs, <<"[">>, <<"]">>);
do_expr_to_algebra({mc, _Meta, Exprs, LcExprs}) ->
comprehension_to_algebra(Exprs, LcExprs, <<"#{">>, <<"}">>);
do_expr_to_algebra({bc, _Meta, Exprs, LcExprs}) ->
comprehension_to_algebra(Exprs, LcExprs, <<"<<">>, <<">>">>);
do_expr_to_algebra({generate, _Meta, Op, Left, Right}) ->
field_to_algebra(atom_to_binary(Op), Left, Right);
do_expr_to_algebra({call, Meta, Name, Args}) ->
Expand Down Expand Up @@ -711,12 +714,15 @@ record_name_to_algebra(Meta, Name) ->
false -> concat(<<"#">>, expr_to_algebra(Name))
end.

comprehension_to_algebra(Expr, [LcExpr | _] = LcExprs, Left, Right) ->
ExprD = expr_to_algebra(Expr),
comprehension_to_algebra(Exprs, [LcExpr | _] = LcExprs, Left, Right) ->
ExprsD = lists:map(fun expr_to_algebra/1, Exprs),
ExprD = group(fold_doc(fun(D, Acc) -> break(concat(D, <<",">>), Acc) end, ExprsD)),
LcExprsD = lists:map(fun expr_to_algebra/1, LcExprs),
LcExprD = fold_doc(fun(D, Acc) -> break(concat(D, <<",">>), Acc) end, LcExprsD),
PostBreak = maybe_force_breaks(has_any_break_between(LcExprs)),
PreBreak = concat(maybe_force_breaks(has_break_between(Expr, LcExpr)), break(<<"">>)),
PreBreak = concat(
maybe_force_breaks(has_break_between(lists:last(Exprs), LcExpr)), break(<<"">>)
),
IndentExpr = nest(concat(break(<<"">>), ExprD), ?INDENT, break),
IdentLcs = nest(group(concat(PostBreak, LcExprD)), ?INDENT),
group(concat([Left, IndentExpr, PreBreak, <<" || ">>, IdentLcs, break(<<"">>), Right])).
Expand Down
58 changes: 37 additions & 21 deletions src/erlfmt_parse.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ node
attribute attr_val
function function_clauses function_clause
clause_guard clause_body
expr expr_max expr_max_remote
expr expr_max expr_callee
pid dotted_special dotted_seq
pat_expr pat_expr_max map_pat_expr record_pat_expr
pat_argument_list pat_exprs
Expand Down Expand Up @@ -51,7 +51,7 @@ macro_name macro_def_expr macro_def_expr_body macro_def_type macro_def_clause.
Terminals
char integer float atom string var

'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' '<:-' ';' ':' '#' '.' '?=' '&&'
'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' '<:-' ';' ':' '#' '#_' '.' '?=' '&&'
'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when' 'maybe' 'else'
'andalso' 'orelse'
'bnot' 'not'
Expand Down Expand Up @@ -229,13 +229,13 @@ expr -> expr ':=' expr : {map_field_exact, ?range_anno('$1', '$3'), '$1', '$3'}.
expr -> prefix_op expr : ?mkop1('$1', '$2').
expr -> expr '::' type : ?mkop2('$1', '$2', '$3').
expr -> expr '?=' expr : ?mkop2('$1', '$2', '$3').
expr -> map_expr : '$1'.
expr -> function_call : '$1'.
expr -> record_expr : '$1'.
expr -> expr_max_remote : '$1'.
expr -> expr_callee : '$1'.

expr_max_remote -> expr_max ':' expr_max_remote : {remote,?range_anno('$1', '$3'),'$1','$3'}.
expr_max_remote -> expr_max : '$1'.
expr_callee -> expr_max ':' expr_callee : {remote,?range_anno('$1', '$3'),'$1','$3'}.
expr_callee -> function_call : '$1'.
expr_callee -> record_expr : '$1'.
expr_callee -> map_expr : '$1'.
expr_callee -> expr_max : '$1'.

expr_max -> macro_call_expr : '$1'.
expr_max -> macro_record_or_concatable : '$1'.
Expand Down Expand Up @@ -289,6 +289,8 @@ record_pat_expr -> '#' record_name '.' record_field_name :
{record_index, ?range_anno('$1', '$4'), '$2', '$4'}.
record_pat_expr -> '#' record_name record_tuple :
{record, ?range_anno('$1', '$3'), '$2', ?val('$3')}.
record_pat_expr -> '#_' record_tuple :
{record, ?range_anno('$1', '$2'), any_record_name('$1'), ?val('$2')}.

list -> '[' ']' : {list, ?range_anno('$1', '$2'), []}.
list -> '[' list_exprs ']' : {list, ?range_anno('$1', '$3'), '$2'}.
Expand Down Expand Up @@ -337,11 +339,15 @@ dotted_seq -> integer '.' dotted_seq : ['$1' | '$3'].
dotted_seq -> float '.' dotted_seq : ['$1' | '$3'].

list_comprehension -> '[' expr '||' lc_exprs ']' :
{lc, ?range_anno('$1', '$5'), '$2', '$4'}.
{lc, ?range_anno('$1', '$5'), ['$2'], '$4'}.
list_comprehension -> '[' expr ',' exprs '||' lc_exprs ']' :
{lc, ?range_anno('$1', '$7'), ['$2' | '$4'], '$6'}.
map_comprehension -> '#' '{' expr '||' lc_exprs '}' :
{mc, ?range_anno('$1', '$6'), '$3', '$5'}.
{mc, ?range_anno('$1', '$6'), ['$3'], '$5'}.
map_comprehension -> '#' '{' expr ',' exprs '||' lc_exprs '}' :
{mc, ?range_anno('$1', '$8'), ['$3' | '$5'], '$7'}.
binary_comprehension -> '<<' expr_max '||' lc_exprs '>>' :
{bc, ?range_anno('$1', '$5'), '$2', '$4'}.
{bc, ?range_anno('$1', '$5'), ['$2'], '$4'}.

lc_exprs -> lc_expr : ['$1'].
lc_exprs -> lc_expr ',' : ['$1'].
Expand All @@ -363,9 +369,7 @@ tuple -> '{' exprs '}' : {tuple,?range_anno('$1', '$3'),'$2'}.

map_expr -> '#' map_tuple :
{map, ?range_anno('$1', '$2'), ?val('$2')}.
map_expr -> expr_max '#' map_tuple :
{map, ?range_anno('$1', '$3'), '$1', ?val('$3')}.
map_expr -> map_expr '#' map_tuple :
map_expr -> expr_callee '#' map_tuple :
{map, ?range_anno('$1', '$3'), '$1', ?val('$3')}.

map_tuple -> '{' '}' : {[], ?anno('$2')}.
Expand All @@ -379,14 +383,16 @@ record_expr -> '#' record_name '.' record_field_name :
{record_index, ?range_anno('$1', '$4'), '$2', '$4'}.
record_expr -> '#' record_name record_tuple :
{record, ?range_anno('$1', '$3'), '$2', ?val('$3')}.
record_expr -> expr_max '#' record_name '.' record_field_name :
{record_field, ?range_anno('$1', '$5'), '$1', '$3', '$5'}.
record_expr -> expr_max '#' record_name record_tuple :
{record, ?range_anno('$1', '$4'), '$1', '$3', ?val('$4')}.
record_expr -> record_expr '#' record_name '.' record_field_name :
record_expr -> expr_callee '#' record_name '.' record_field_name :
{record_field, ?range_anno('$1', '$5'), '$1', '$3', '$5'}.
record_expr -> record_expr '#' record_name record_tuple :
record_expr -> expr_callee '#' record_name record_tuple :
{record, ?range_anno('$1', '$4'), '$1', '$3', ?val('$4')}.
record_expr -> '#_' record_tuple :
{record, ?range_anno('$1', '$2'), any_record_name('$1'), ?val('$2')}.
record_expr -> expr_callee '#_' '.' record_field_name :
{record_field, ?range_anno('$1', '$4'), '$1', any_record_name('$2'), '$4'}.
record_expr -> expr_callee '#_' record_tuple :
{record, ?range_anno('$1', '$3'), '$1', any_record_name('$2'), ?val('$3')}.

macro_record_or_concatable -> var macro_call_none '.' record_field_name :
Anno = (?range_anno('$1', '$4'))#{macro_record => true},
Expand All @@ -405,12 +411,17 @@ record_fields -> record_field ',' : ['$1'].
record_fields -> record_field ',' record_fields : ['$1' | '$3'].

record_field -> record_field_name '=' expr : {record_field,?range_anno('$1', '$3'),'$1','$3'}.
record_field -> record_field_name '::' type :
{op, ?range_anno('$1', '$3'), '::', {record_field,?anno('$1'),'$1'}, '$3'}.
record_field -> record_field_name : {record_field,?anno('$1'),'$1'}.

record_name -> atom_or_var_or_macro : '$1'.
record_name -> atom_or_var_or_macro ':' atom_or_var_or_macro :
{remote, ?range_anno('$1', '$3'), '$1', '$3'}.

record_field_name -> atom_or_var_or_macro : '$1'.

function_call -> expr_max_remote argument_list :
function_call -> expr_callee argument_list :
{call, ?range_anno('$1', '$2'), '$1', ?val('$2')}.

begin_block -> 'begin' 'end' : {block, ?range_anno('$1', '$2'), []}.
Expand Down Expand Up @@ -1152,6 +1163,8 @@ build_macro_def({'-', Anno}, {_, AttrAnno}, {Name, Body}) ->

build_attribute({'-', Anno}, {atom, _, record} = Attr, [Name, Tuple]) ->
{attribute, Anno, Attr, [Name, record_tuple(Tuple)]};
build_attribute({'-', Anno}, {atom, _, record} = Attr, Values) ->
{attribute, Anno, Attr, Values};
build_attribute({'-', Anno}, {atom, _, _} = Attr, Values) ->
{attribute, Anno, Attr, Values};
build_attribute({'-', Anno}, {Name, NameAnno}, Values) ->
Expand All @@ -1178,6 +1191,9 @@ record_fields([Other | _Fields]) ->
record_fields([]) ->
[].

any_record_name({'#_', Anno}) ->
{var, maps:put(text, "_", Anno), '_'}.

-spec ret_err(_, _) -> no_return().
ret_err(Anno, S) ->
return_error(erlfmt_scan:get_anno(location, Anno), S).
Expand Down
6 changes: 3 additions & 3 deletions src/erlfmt_recomment.erl
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@ insert_nested({record, Meta, Expr0, Name, Values0}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Values = insert_expr_container(Values0, Comments1),
{{record, Meta, Expr, Name, Values}, []};
insert_nested({Comprehension, Meta, Expr0, LcExprs0}, Comments0) when
insert_nested({Comprehension, Meta, Exprs0, LcExprs0}, Comments0) when
Comprehension =:= lc; Comprehension =:= bc; Comprehension =:= mc
->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
{Exprs, Comments1} = insert_expr_list(Exprs0, Comments0),
LcExprs = insert_expr_container(LcExprs0, Comments1),
{{Comprehension, Meta, Expr, LcExprs}, []};
{{Comprehension, Meta, Exprs, LcExprs}, []};
insert_nested({Field, Meta, Key0, Value0}, Comments0) when
Field =:= map_field_assoc;
Field =:= map_field_exact;
Expand Down
42 changes: 39 additions & 3 deletions test/erlfmt_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@
skip_pragma_escript/1,
overlong_warning/1,
do_not_crash_on_bad_record/1,
raw_string_anno/1
raw_string_anno/1,
left_assoc_call/1,
multi_value_comprehension/1
]).

suite() ->
Expand Down Expand Up @@ -159,7 +161,9 @@ groups() ->
do_not_crash_on_bad_record,
raw_string_anno,
dotted,
map_comprehension
map_comprehension,
left_assoc_call,
multi_value_comprehension
]},
{snapshot_tests, [parallel], [
snapshot_simple_comments,
Expand Down Expand Up @@ -1015,7 +1019,7 @@ dotted(Config) when is_list(Config) ->

map_comprehension(Config) when is_list(Config) ->
?assertMatch(
{mc, _, {map_field_assoc, _, {var, _, 'A'}, {var, _, 'B'}}, [
{mc, _, [{map_field_assoc, _, {var, _, 'A'}, {var, _, 'B'}}], [
{generate, _, '<-', {map_field_exact, _, {var, _, 'A'}, {var, _, 'B'}}, {var, _, 'M'}}
]},
parse_expr("#{A => B || A := B <- M}")
Expand Down Expand Up @@ -1826,3 +1830,35 @@ raw_string_anno(Config) when is_list(Config) ->
unicode_string() ->
"% Overlong, in bytes: "
"色は匂へど 散りぬるを 我が世誰ぞ 常ならむ 有為の奥山 今日越えて 浅き夢見じ 酔ひもせず\n".

left_assoc_call(Config) when is_list(Config) ->
?assertMatch(
{call, _, {call, _, {atom, _, f}, [{var, _, 'X'}]}, [{var, _, 'Y'}]},
parse_expr("f(X)(Y)")
),
?assertMatch(
{call, _, {call, _, {call, _, {atom, _, f}, [{var, _, 'X'}]}, [{var, _, 'Y'}]}, [
{var, _, 'Z'}
]},
parse_expr("f(X)(Y)(Z)")
),
?assertMatch(
{call, _, {call, _, {remote, _, {var, _, 'Mod'}, {atom, _, f}}, [{var, _, 'X'}]}, [
{var, _, 'Y'}
]},
parse_expr("Mod:f(X)(Y)")
).

multi_value_comprehension(Config) when is_list(Config) ->
?assertMatch(
{lc, _, [{var, _, 'I'}, {op, _, '-', {var, _, 'I'}}], _},
parse_expr("[I, -I || I <- List]")
),
?assertMatch(
{lc, _, [{var, _, 'X'}], _},
parse_expr("[X || X <- List]")
),
?assertMatch(
{mc, _, [{var, _, 'A'}, {var, _, 'B'}], _},
parse_expr("#{A, B || X <- List}")
).
Loading