Current section

Files

Jump to
erlfmt src erlfmt_recomment.erl
Raw

src/erlfmt_recomment.erl

%% Copyright (c) Meta Platforms, Inc. and its affiliates.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(erlfmt_recomment).
-include("erlfmt_scan.hrl").
-export([recomment/2, put_pre_comments/2, put_post_comments/2, take_comments/2]).
-define(IS_COLLECTION(Kind),
Kind =:= map orelse
Kind =:= list orelse
Kind =:= tuple orelse
Kind =:= bin orelse
Kind =:= block
).
-spec recomment(erlfmt_parse:abstract_node(), [erlfmt_scan:comment()]) ->
erlfmt_parse:abstract_node().
recomment(Node, Comments) ->
insert_node(Node, Comments).
insert_node(Node, []) ->
Node;
insert_node({function, Meta0, Clauses0}, Comments0) ->
{PreComments, InnerComments, PostComments} = split_comments(Meta0, Comments0),
{Clauses, RestComments} = insert_expr_list(Clauses0, InnerComments),
Meta1 = put_pre_comments(Meta0, PreComments),
Meta2 = put_post_comments(Meta1, PostComments),
Meta = put_pre_dot_comments(Meta2, RestComments),
{function, Meta, Clauses};
insert_node({attribute, Meta0, {atom, _, RawName} = Name, Values0}, Comments) when
RawName =:= spec; RawName =:= callback; RawName =:= type; RawName =:= opaque
->
{PreComments, InnerComments, PostComments} = split_comments(Meta0, Comments),
{Values, RestComments} = insert_expr_list(Values0, InnerComments),
Meta1 = put_pre_comments(Meta0, PreComments),
Meta2 = put_post_comments(Meta1, PostComments),
Meta = put_pre_dot_comments(Meta2, RestComments),
{attribute, Meta, Name, Values};
insert_node({attribute, Meta0, Name, Values0}, Comments) ->
{PreComments, InnerComments, PostComments} = split_comments(Meta0, Comments),
Values = insert_expr_container(Values0, InnerComments),
Meta1 = put_pre_comments(Meta0, PreComments),
Meta = put_post_comments(Meta1, PostComments),
{attribute, Meta, Name, Values};
insert_node({exprs, Meta0, Exprs0}, Comments0) ->
{PreComments, InnerComments, PostComments} = split_comments(Meta0, Comments0),
{Exprs, RestComments} = insert_expr_list(Exprs0, InnerComments),
Meta1 = put_pre_comments(Meta0, PreComments),
Meta2 = put_post_comments(Meta1, PostComments),
Meta = put_pre_dot_comments(Meta2, RestComments),
{exprs, Meta, Exprs};
insert_node(Expr0, Comments) ->
{PreComments, InnerComments, PostComments} = split_comments(
element(2, Expr0),
Comments
),
{Expr1, RestComments} = insert_expr(Expr0, InnerComments),
Expr = put_pre_comments(Expr1, PreComments),
put_post_comments(Expr, RestComments ++ PostComments).
insert_expr(Node0, Comments) ->
{PreComments, InnerComments, RestComments} =
split_comments(element(2, Node0), Comments),
{Node1, []} = insert_nested(Node0, InnerComments),
Node = put_pre_comments(Node1, PreComments),
{Node, RestComments}.
insert_expr_list(Exprs, Comments) -> insert_expr_list(Exprs, Comments, []).
insert_expr_list(Exprs, [], Acc) ->
{lists:reverse(Acc, Exprs), []};
insert_expr_list([Expr0 | Exprs], Comments0, Acc) when is_tuple(Expr0) ->
{Expr, Comments} = insert_expr(Expr0, Comments0),
insert_expr_list(Exprs, Comments, [Expr | Acc]);
insert_expr_list([], Comments, Acc) ->
{lists:reverse(Acc), Comments}.
insert_expr_container(Exprs, []) ->
Exprs;
insert_expr_container([Expr0], Comments0) when is_tuple(Expr0) ->
{Expr, Comments} = insert_expr(Expr0, Comments0),
[put_post_comments(Expr, Comments)];
insert_expr_container([Expr0 | Exprs], Comments0) when is_tuple(Expr0) ->
{Expr, Comments} = insert_expr(Expr0, Comments0),
[Expr | insert_expr_container(Exprs, Comments)];
%% final comments in containers become standalone comment elements
insert_expr_container([], Comments) ->
Comments.
split_comments(#{location := {SLine, _}, end_location := {ELine, _}}, Comments0) ->
{PreComments, Comments1} = take_comments(SLine, Comments0),
{InnerComments, PostComments} = take_comments(ELine, Comments1),
{PreComments, InnerComments, PostComments}.
take_comments(Line, Comments) ->
F = fun({_, #{end_location := {CLine, _}}, _}) -> CLine =< Line end,
lists:splitwith(F, Comments).
insert_nested(Node, []) ->
{Node, []};
insert_nested({Atomic, _, _} = Node0, Comments) when ?IS_ATOMIC(Atomic) ->
Node = put_pre_comments(Node0, Comments),
{Node, []};
insert_nested({pid, _, _} = Node0, Comments) ->
Node = put_pre_comments(Node0, Comments),
{Node, []};
insert_nested({dotted_special, _, _, _} = Node0, Comments) ->
Node = put_pre_comments(Node0, Comments),
{Node, []};
insert_nested({concat, Meta, Strings0}, Comments0) ->
{Strings, Comments} = insert_expr_list(Strings0, Comments0),
{{concat, Meta, Strings}, Comments};
insert_nested({op, Meta, Op, Expr0}, Comments0) ->
{Expr, Comments} = insert_expr(Expr0, Comments0),
{{op, Meta, Op, Expr}, Comments};
insert_nested({op, Meta, Op, Left0, Right0}, Comments0) ->
{Left, Comments1} = insert_expr(Left0, Comments0),
{Right, Comments} = insert_expr(Right0, Comments1),
{{op, Meta, Op, Left, Right}, Comments};
insert_nested({cons, Meta, Head0, Tail0}, Comments0) ->
{Head, Comments1} = insert_expr(Head0, Comments0),
{Tail, Comments} = insert_expr(Tail0, Comments1),
{{cons, Meta, Head, Tail}, Comments};
insert_nested({spec_clause, Meta, Head0, Body0, empty}, Comments0) ->
{Head, Comments1} = insert_expr(Head0, Comments0),
{Body, Comments} = insert_expr(Body0, Comments1),
{{spec_clause, Meta, Head, Body, empty}, Comments};
insert_nested({spec_clause, Meta, Head0, Body0, Guards0}, Comments0) ->
{Head, Comments1} = insert_expr(Head0, Comments0),
{Body, Comments2} = insert_expr(Body0, Comments1),
{Guards, Comments} = insert_expr(Guards0, Comments2),
{{spec_clause, Meta, Head, Body, Guards}, Comments};
insert_nested({clause, Meta, Head0, empty, Body0}, Comments0) ->
{Head, Comments1} = insert_expr(Head0, Comments0),
{Body, Comments} = insert_expr_list(Body0, Comments1),
{{clause, Meta, Head, empty, Body}, Comments};
insert_nested({clause, Meta, empty, Guards0, Body0}, Comments0) ->
{Guards, Comments1} = insert_expr(Guards0, Comments0),
{Body, Comments} = insert_expr_list(Body0, Comments1),
{{clause, Meta, empty, Guards, Body}, Comments};
insert_nested({clause, Meta, Head0, Guards0, Body0}, Comments0) ->
{Head, Comments1} = insert_expr(Head0, Comments0),
{Guards, Comments2} = insert_expr(Guards0, Comments1),
{Body, Comments} = insert_expr_list(Body0, Comments2),
{{clause, Meta, Head, Guards, Body}, Comments};
insert_nested({Guard, Meta, Guards0}, Comments0) when Guard =:= guard_or; Guard =:= guard_and ->
{Guards, Comments} = insert_expr_list(Guards0, Comments0),
{{Guard, Meta, Guards}, Comments};
insert_nested({Collection, Meta, Values0}, Comments0) when ?IS_COLLECTION(Collection) ->
Values = insert_expr_container(Values0, Comments0),
{{Collection, Meta, Values}, []};
insert_nested({map, Meta, Expr0, Values0}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Values = insert_expr_container(Values0, Comments1),
{{map, Meta, Expr, Values}, []};
insert_nested({record, Meta, Name, Values0}, Comments0) ->
Values = insert_expr_container(Values0, Comments0),
{{record, Meta, Name, Values}, []};
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
Comprehension =:= lc; Comprehension =:= bc; Comprehension =:= mc
->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
LcExprs = insert_expr_container(LcExprs0, Comments1),
{{Comprehension, Meta, Expr, LcExprs}, []};
insert_nested({Field, Meta, Key0, Value0}, Comments0) when
Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field
->
{Key, Comments1} = insert_expr(Key0, Comments0),
{Value, Comments} = insert_expr(Value0, Comments1),
{{Field, Meta, Key, Value}, Comments};
insert_nested({generate, Meta, Op, Key0, Value0}, Comments0) ->
{Key, Comments1} = insert_expr(Key0, Comments0),
{Value, Comments} = insert_expr(Value0, Comments1),
{{generate, Meta, Op, Key, Value}, Comments};
%% no internal comments in record_indexm record_field, or bin_element
insert_nested({record_index, _, _, _} = Node, Comments) ->
{put_pre_comments(Node, Comments), []};
insert_nested({record_field, _, _} = Node, Comments) ->
{put_pre_comments(Node, Comments), []};
insert_nested({record_field, Meta0, Expr0, Name, Key}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Meta = put_pre_comments(Meta0, Comments1),
{{record_field, Meta, Expr, Name, Key}, []};
insert_nested({bin_element, Meta0, Expr0, Size, Types}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Meta = put_pre_comments(Meta0, Comments1),
{{bin_element, Meta, Expr, Size, Types}, []};
insert_nested({call, Meta, Name0, Args0}, Comments0) ->
{Name, Comments1} = insert_expr(Name0, Comments0),
Args = insert_expr_container(Args0, Comments1),
{{call, Meta, Name, Args}, []};
insert_nested({macro_call, Meta0, Name, none}, Comments0) ->
Meta = put_pre_comments(Meta0, Comments0),
{{macro_call, Meta, Name, none}, []};
insert_nested({macro_call, Meta, Name, Args0}, Comments0) ->
Args = insert_expr_container(Args0, Comments0),
{{macro_call, Meta, Name, Args}, []};
insert_nested({macro_string, Meta0, Name}, Comments0) ->
Meta = put_pre_comments(Meta0, Comments0),
{{macro_string, Meta, Name}, []};
insert_nested({remote, Meta, Mod0, Name0}, Comments0) ->
{Mod, Comments1} = insert_expr(Mod0, Comments0),
{Name, Comments} = insert_expr(Name0, Comments1),
{{remote, Meta, Mod, Name}, Comments};
insert_nested({'case', Meta, Expr0, Clauses0}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Clauses = insert_expr_container(Clauses0, Comments1),
{{'case', Meta, Expr, Clauses}, []};
insert_nested({'maybe', Meta, MaybeExpressions0}, Comments0) ->
MaybeExpressions = insert_expr_container(MaybeExpressions0, Comments0),
{{'maybe', Meta, MaybeExpressions}, []};
insert_nested({'maybe', Meta, MaybeExpressions0, ElseClause0}, Comments0) ->
{MaybeExpressions, Comments1} = insert_expr_list(MaybeExpressions0, Comments0),
{ElseClause, []} = insert_nested(ElseClause0, Comments1),
{{'maybe', Meta, MaybeExpressions, ElseClause}, []};
insert_nested({'else_clause', Meta, Clauses0}, Comments0) ->
Clauses = insert_expr_container(Clauses0, Comments0),
{{'else_clause', Meta, Clauses}, []};
insert_nested({'receive', Meta, Clauses0}, Comments0) ->
{Clauses, []} = insert_expr(Clauses0, Comments0),
{{'receive', Meta, Clauses}, []};
insert_nested({'receive', Meta, empty, After0}, Comments0) ->
{After, []} = insert_expr(After0, Comments0),
{{'receive', Meta, empty, After}, []};
insert_nested({'receive', Meta, Clauses0, After0}, Comments0) ->
{Clauses, Comments1} = insert_expr(Clauses0, Comments0),
{After, []} = insert_expr(After0, Comments1),
{{'receive', Meta, Clauses, After}, []};
insert_nested({after_clause, Meta, Expr0, Body0}, Comments0) ->
{Expr, Comments1} = insert_expr(Expr0, Comments0),
Body = insert_expr_container(Body0, Comments1),
{{after_clause, Meta, Expr, Body}, []};
insert_nested({'if', Meta, Clauses0}, Comments0) ->
Clauses = insert_expr_container(Clauses0, Comments0),
{{'if', Meta, Clauses}, []};
insert_nested({'try', Meta, Body0, OfClauses0, CatchClauses0, []}, Comments0) ->
{Body, Comments1} = insert_expr(Body0, Comments0),
{OfClauses, Comments2} = insert_expr_or_none(OfClauses0, Comments1),
{CatchClauses, []} = insert_expr_or_none(CatchClauses0, Comments2),
{{'try', Meta, Body, OfClauses, CatchClauses, []}, []};
insert_nested({'try', Meta, Body0, OfClauses0, CatchClauses0, After0}, Comments0) ->
{Body, Comments1} = insert_expr(Body0, Comments0),
{OfClauses, Comments2} = insert_expr_or_none(OfClauses0, Comments1),
{CatchClauses, Comments3} = insert_expr_or_none(CatchClauses0, Comments2),
After = insert_expr_container(After0, Comments3),
{{'try', Meta, Body, OfClauses, CatchClauses, After}, []};
insert_nested({spec, Meta, Name, Clauses0}, Comments0) ->
Clauses = insert_expr_container(Clauses0, Comments0),
{{spec, Meta, Name, Clauses}, []};
insert_nested({'fun', _, Fun} = Node, Comments0) when
element(1, Fun) =:= 'function'; Fun =:= type
->
{put_pre_comments(Node, Comments0), []};
insert_nested({'fun', Meta, {type, InnerMeta, Args0, Res0}}, Comments0) ->
{Args, Comments1} = insert_expr(Args0, Comments0),
{Res, Comments} = insert_expr(Res0, Comments1),
{{'fun', Meta, {type, InnerMeta, Args, Res}}, Comments};
insert_nested({'fun', Meta, {clauses, InnerMeta, Clauses0}}, Comments0) ->
Clauses = insert_expr_container(Clauses0, Comments0),
{{'fun', Meta, {clauses, InnerMeta, Clauses}}, []};
insert_nested({'catch', Meta, Args0}, Comments0) ->
{Args, Comments} = insert_expr_list(Args0, Comments0),
{{'catch', Meta, Args}, Comments};
insert_nested({args, Meta, Args0}, Comments0) ->
Args = insert_expr_container(Args0, Comments0),
{{args, Meta, Args}, []};
insert_nested({clauses, Meta, Clauses0}, Comments0) ->
Clauses = insert_expr_container(Clauses0, Comments0),
{{clauses, Meta, Clauses}, []};
insert_nested({body, Meta, Exprs0}, Comments0) ->
Exprs = insert_expr_container(Exprs0, Comments0),
{{body, Meta, Exprs}, []};
insert_nested({Name, Meta}, Comments) ->
{{Name, Meta}, Comments}.
insert_expr_or_none(none, Comments) -> {none, Comments};
insert_expr_or_none(Expr, Comments) -> insert_expr(Expr, Comments).
put_pre_dot_comments(NodeOrMeta, []) ->
NodeOrMeta;
put_pre_dot_comments(NodeOrMeta, Comments) ->
Existing = erlfmt_scan:get_anno(pre_dot_comments, NodeOrMeta, []),
erlfmt_scan:merge_anno(
#{
pre_dot_comments => Existing ++ Comments
},
NodeOrMeta
).
put_post_comments(NodeOrMeta, []) ->
NodeOrMeta;
put_post_comments(NodeOrMeta, Comments) ->
Loc = erlfmt_scan:get_anno(end_location, NodeOrMeta),
CommentLoc = erlfmt_scan:get_anno(end_location, lists:last(Comments)),
Existing = erlfmt_scan:get_anno(post_comments, NodeOrMeta, []),
erlfmt_scan:merge_anno(
#{
end_location => max(Loc, CommentLoc),
inner_end_location => Loc,
post_comments => Existing ++ Comments
},
NodeOrMeta
).
put_pre_comments(NodeOrMeta, []) ->
NodeOrMeta;
put_pre_comments(NodeOrMeta, Comments) ->
Loc = erlfmt_scan:get_anno(location, NodeOrMeta),
CommentLoc = erlfmt_scan:get_anno(location, hd(Comments)),
Existing = erlfmt_scan:get_anno(pre_comments, NodeOrMeta, []),
erlfmt_scan:merge_anno(
#{
location => min(Loc, CommentLoc),
inner_location => Loc,
pre_comments => Comments ++ Existing
},
NodeOrMeta
).