Skip to content

Commit

Permalink
Support for OTP 27 Sigils & Triple Quoted Strings (#358)
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen authored Sep 2, 2024
1 parent c2286a8 commit 13b0510
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- os: ubuntu-22.04
otp-version: 26
rebar3-version: 3.22
- os: ubuntu-22.04
otp-version: 27
rebar3-version: 3.23
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
Expand Down
20 changes: 20 additions & 0 deletions src/erlfmt_format.erl
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,22 @@ do_expr_to_algebra({clauses, _Meta, Clauses}) ->
clauses_to_algebra(Clauses);
do_expr_to_algebra({body, _Meta, Exprs}) ->
block_to_algebra(Exprs);
do_expr_to_algebra({sigil, _Meta, Prefix, Content, Suffix}) ->
concat(
concat(
concat(<<"~">>, do_expr_to_algebra(Prefix)),
do_expr_to_algebra(Content)
),
do_expr_to_algebra(Suffix)
);
do_expr_to_algebra({sigil_prefix, _Meta, ''}) ->
<<"">>;
do_expr_to_algebra({sigil_prefix, _Meta, SigilName}) ->
atom_to_binary(SigilName, utf8);
do_expr_to_algebra({sigil_suffix, _Meta, []}) ->
<<>>;
do_expr_to_algebra({sigil_suffix, _Meta, Modifiers}) ->
list_to_binary(Modifiers);
do_expr_to_algebra(Other) ->
error(unsupported, [Other]).
Expand All @@ -274,6 +290,10 @@ surround_block(Left, Doc, Right) ->
string_to_algebra(Text) ->
case string:split(Text, "\n", all) of
["\"\"\"", Line, "\"\"\""] ->
string_to_algebra(["\"", Line, "\""]);
["\"\"\"" | _Rest] ->
string(Text);
[Line] ->
string(Line);
[First, "\""] ->
Expand Down
4 changes: 3 additions & 1 deletion src/erlfmt_parse.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ char integer float atom string var
'<<' '>>'
'?' '!' '=' '::' '..' '...'
spec callback define_expr define_type define_clause standalone_exprs % helper
dot.
dot
sigil_prefix sigil_suffix.

%% Conflict comes from optional parens on macro calls.
Expect 1.
Expand Down Expand Up @@ -607,6 +608,7 @@ atomic -> string : '$1'.
atomic -> string concatables : {concat, ?range_anno('$1', '$2'), ['$1' | ?val('$2')]}.
atomic -> macro_call_none concatables : {concat, ?range_anno('$1', '$2'), ['$1' | ?val('$2')]}.
atomic -> macro_string concatables : {concat, ?range_anno('$1', '$2'), ['$1' | ?val('$2')]}.
atomic -> sigil_prefix string sigil_suffix : {sigil, ?range_anno('$1', '$3'), '$1', '$2', '$3'}.

concatables_no_initial_call -> concatable_no_call concatables : {['$1' | ?val('$2')], ?anno('$2')}.
concatables_no_initial_call -> concatable_no_call : {['$1'], ?anno('$1')}.
Expand Down
4 changes: 3 additions & 1 deletion src/erlfmt_scan.erl
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ atomic_anno([{text, Text}, {location, {Line, Col} = Location}]) ->
#{text => Text, location => Location, end_location => end_location(Text, Line, Col)}.

token_anno([{text, Text}, {location, {Line, Col} = Location}]) ->
#{location => Location, end_location => end_location(Text, Line, Col)}.
#{location => Location, end_location => end_location(Text, Line, Col)};
token_anno({_Line, _Col} = Location) ->
#{location => Location, end_location => Location}.

comment_anno([{text, _}, {location, Location}], [{text, Text}, {location, {Line, Col}}]) ->
#{location => Location, end_location => end_location(Text, Line, Col)}.
Expand Down
50 changes: 48 additions & 2 deletions test/erlfmt_format_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@
force_break/1,
binary_operator_more/1,
binary_operator_equal/1,
update_edgecase/1
update_edgecase/1,
sigils/1,
doc_attributes/1,
doc_macros/1
]).

suite() ->
Expand All @@ -83,6 +86,13 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.

init_per_group(otp_27_features, Config) ->
case erlang:system_info(otp_release) >= "27" of
true -> Config;
false -> {skip, "Skipping tests for features from OTP >= 27"}
end;
init_per_group(_, Config) ->
Config;
init_per_group(_GroupName, Config) ->
Config.

Expand Down Expand Up @@ -117,7 +127,8 @@ groups() ->
receive_expression,
try_expression,
if_expression,
macro
macro,
doc_macros
]},
{forms, [parallel], [
function,
Expand Down Expand Up @@ -157,6 +168,10 @@ groups() ->
list_comprehension,
map_comprehension,
binary_comprehension
]},
{otp_27_features, [parallel], [
sigils,
doc_attributes
]}
].

Expand Down Expand Up @@ -232,6 +247,25 @@ literals(Config) when is_list(Config) ->
?assertSame("_Bar\n"),
?assertFormat("$ ", "$\\s\n").

sigils(Config) when is_list(Config) ->
%% https://www.erlang.org/blog/highlights-otp-27/#sigils
?assertSame("~b\"abc\\txyz\"\n"),
?assertSame("~\"abc\\txyz\"\n"),
?assertSame("~s{\"abc\\txyz\"}\n"),
%% The modifier X does not technically exist, but there seems to be no supported
%% modifiers yet even though they are correctly parsed.
?assertSame("~b\"abc\\txyz\"x\n"),
?assertSame("~s\"\"\"\n\\tabc\n\\tdef\n\"\"\"\n"),
%% https://www.erlang.org/blog/highlights-otp-27/#triple-quoted-strings
?assertFormat(
"\"\"\"\n"
"Test\n"
"\"\"\"\n",
"\"Test\"\n"
),
?assertSame("\"\"\"\nTest\nMultiline\n\"\"\"\n"),
?assertSame("~\"\"\"\nTest\nMultiline\n\"\"\"\n").

dotted(Config) when is_list(Config) ->
?assertSame("<0.1.2>\n"),
?assertSame("#Ref<0.1.2.3>\n"),
Expand Down Expand Up @@ -4234,3 +4268,15 @@ comment(Config) when is_list(Config) ->
"\"a,\\n\"\n"
"\"b\".\n"
).

doc_attributes(Config) when is_list(Config) ->
?assertSame("-moduledoc(\"Test\").\n-moduledoc(#{since => <<\"1.0.0\">>}).\n"),
?assertSame("-moduledoc(\"\"\"\nTest\nMultiline\n\"\"\").\n"),
?assertSame("-doc(\"Test\").\n-doc(#{since => <<\"1.0.0\">>}).\ntest() -> ok.\n"),
?assertSame("-doc(\"Test\").\n-doc(#{since => <<\"1.0.0\">>}).\n-type t() :: ok.\n").

doc_macros(Config) when is_list(Config) ->
%% Doc Attributes as macros is a common pattern for OTP < 27 compatibility.
?assertSame("?MODULEDOC(\"Test\").\n?MODULEDOC(#{since => <<\"1.0.0\">>}).\n"),
?assertSame("?DOC(\"Test\").\n?DOC(#{since => <<\"1.0.0\">>}).\ntest() -> ok.\n"),
?assertSame("?DOC(\"Test\").\n?DOC(#{since => <<\"1.0.0\">>}).\n-type t() :: ok.\n").

0 comments on commit 13b0510

Please sign in to comment.