Current section

37 Versions

Jump to

Compare versions

5 files changed
+129 additions
-32 deletions
  @@ -15,4 +15,4 @@
15 15 [{<<"app">>,<<"providers">>},
16 16 {<<"optional">>,false},
17 17 {<<"requirement">>,<<"1.8.1">>}]}]}.
18 - {<<"version">>,<<"0.9.0">>}.
18 + {<<"version">>,<<"0.9.1">>}.
  @@ -1,6 +1,6 @@
1 1 {application,steamroller,
2 2 [{description,"An opinionated Erlang code formatter."},
3 - {vsn,"0.9.0"},
3 + {vsn,"0.9.1"},
4 4 {registered,[]},
5 5 {applications,[kernel,stdlib]},
6 6 {env,[]},
  @@ -38,7 +38,7 @@
38 38 | function
39 39 | module_comment
40 40 | function_comment
41 - | dot.
41 + | expr.
42 42
43 43 -define(sp, <<" ">>).
44 44 -define(nl, <<"\n">>).
  @@ -208,13 +208,18 @@ generate_doc_([{'-', _}, {atom, _, Atom} | Tokens], Doc0, PrevTerm) ->
208 208 case {Atom, PrevTerm} of
209 209 {_, function_comment} -> newline(Doc0, Group);
210 210 {Atom, {attribute, Atom}} -> newline(Doc0, Group);
211 - {define, {attribute, IfDef}} when IfDef == ifdef orelse IfDef == else ->
211 + {define, {attribute, IfDef}}
212 + when IfDef == ifdef orelse IfDef == else orelse IfDef == 'if' ->
212 213 newline(Doc0, Group);
213 214 {IfDef, {attribute, define}} when IfDef == else orelse IfDef == endif ->
214 215 newline(Doc0, Group);
215 216 _ -> newlines(Doc0, Group)
216 217 end,
217 218 generate_doc_(Rest, Doc1, {attribute, Atom});
219 + generate_doc_([{'-', _}, {'if' = Atom, _} | Tokens], Doc0, _) ->
220 + % :'(
221 + {Group, Rest} = attribute(Atom, Tokens),
222 + generate_doc_(Rest, newlines(Doc0, Group), {attribute, Atom});
218 223 generate_doc_([{atom, _, _Atom} | _] = Tokens, Doc0, PrevTerm) ->
219 224 % Function
220 225 {Group, Rest} = function(Tokens),
  @@ -228,14 +233,14 @@ generate_doc_([{atom, _, _Atom} | _] = Tokens, Doc0, PrevTerm) ->
228 233 generate_doc_([{C, _} | _] = Tokens, Doc0, PrevTerm) when ?IS_LIST_OPEN_CHAR(C) ->
229 234 % List
230 235 % If this is at the top level this is probably a config file
231 - {ForceBreak, Group0, Rest} = list_group(Tokens),
236 + {ForceBreak, Group0, [{dot, _} | Rest]} = list_group(Tokens),
232 237 Group1 = force_break(ForceBreak, Group0),
233 238 Doc1 =
234 239 case PrevTerm of
235 240 function_comment -> newline(Doc0, Group1);
236 241 _ -> newlines(Doc0, Group1)
237 242 end,
238 - generate_doc_(Rest, Doc1, list);
243 + generate_doc_(Rest, cons(Doc1, text(?dot)), list);
239 244 generate_doc_([{comment, _, "%%" ++ _ = CommentText} | Rest], Doc0, PrevTerm) ->
240 245 % Module Comment
241 246 Comment = comment(CommentText),
  @@ -257,9 +262,13 @@ generate_doc_([{comment, _, CommentText} | Rest], Doc0, PrevTerm) ->
257 262 _ -> newlines(Doc0, Comment)
258 263 end,
259 264 generate_doc_(Rest, Doc1, function_comment);
260 - generate_doc_([{dot, _} | Rest], Doc, _PrevTerm) ->
261 - % Any undhandled dots, for example at the end of terms in config files.
262 - generate_doc_(Rest, cons(Doc, text(?dot)), dot).
265 + generate_doc_(Tokens, Doc, _PrevTerm) ->
266 + % Anything unhandled gets treated as an expression.
267 + % Things which fall through to here:
268 + % - Macros: can appear at the top level
269 + {_End, ForceBreak, Exprs, Rest} = exprs(Tokens),
270 + Group = force_break(ForceBreak, group(space(Exprs), inherit)),
271 + generate_doc_(Rest, newline(Doc, Group), expr).
263 272
264 273 %%
265 274 %% Erlang Source Elements
  @@ -272,6 +281,14 @@ attribute(Att, [{dot, _} | Rest]) ->
272 281 % -endif.
273 282 Attribute = group(cons([text(<<"-">>), text(a2b(Att)), text(?dot)])),
274 283 {Attribute, Rest};
284 + attribute('if', Tokens) ->
285 + % Easiest to handle this here...
286 + % a2b/1 will print 'if' with the single quotes.
287 + % op2b/1 will print `dot` as `.`.
288 + % so pattern match the 'if' and convert it to <<"if">>.
289 + {_ForceBreak, Expr, [{dot, _} | Rest]} = list_group(Tokens),
290 + Attribute = group(cons([text(<<"-">>), text(<<"if">>), Expr, text(?dot)])),
291 + {Attribute, Rest};
275 292 attribute(Att, Tokens) ->
276 293 {_ForceBreak, Expr, [{dot, _} | Rest]} = list_group(Tokens),
277 294 Attribute = group(cons([text(<<"-">>), text(a2b(Att)), Expr, text(?dot)])),
  @@ -416,8 +433,8 @@ try_([{'try', _} | Tokens]) ->
416 433 % There is no 'of' for this 'try'
417 434 {exprs, text(<<"try">>), TryTokens};
418 435 {TryArgTokens, Rest0, _} ->
419 - {empty, _, TryArg, []} = expr(TryArgTokens, no_force_break),
420 - {clauses, group(space(text(<<"try">>), TryArg)), Rest0}
436 + {empty, _, TryArgs, []} = exprs(TryArgTokens),
437 + {clauses, group(space(text(<<"try">>), space(TryArgs))), Rest0}
421 438 end,
422 439 {TryTokens1, AfterClauseTokens} =
423 440 case get_until('after', Rest1) of
  @@ -531,17 +548,34 @@ try_after_([{'after', _} | Tokens]) ->
531 548
532 549 -spec fun_(tokens()) -> {force_break(), doc(), tokens()}.
533 550 fun_([{'fun', _} | Tokens]) ->
534 - {ClauseTokens, Rest1, _} = get_until_end(Tokens),
535 - {ForceBreak, Clauses} = handle_trailing_comments(clauses(ClauseTokens)),
536 - Doc =
537 - force_break(
538 - ForceBreak,
539 - group(
540 - space(nest(?indent, space([text(<<"fun">>) | Clauses])), text(<<"end">>)),
541 - inherit
542 - )
543 - ),
544 - {ForceBreak, Doc, Rest1}.
551 + {ForceBreak, Doc, Rest} =
552 + case get_until_end(Tokens) of
553 + {[{'(', _}, {')', _}], [], {dot, _}} ->
554 + % This case can happen in typedefs if the type looks like
555 + % -type x() :: fun().
556 + {no_force_break, text(<<"fun().">>), []};
557 + {[{'(', _}, {')', _}, {'|', _} | _] = Tokens0, Rest0, End} ->
558 + % This case can happen in typedefs if the type looks like
559 + % -type x() :: fun() | map().
560 + [_, _ | Tokens1] = Tokens0,
561 + Rest1 = Tokens1 ++ [End] ++ Rest0,
562 + {no_force_break, text(<<"fun()">>), Rest1};
563 + {ClauseTokens, Rest0, _} ->
564 + {ForceBreak0, Clauses} = handle_trailing_comments(clauses(ClauseTokens)),
565 + Doc0 =
566 + force_break(
567 + ForceBreak0,
568 + group(
569 + space(
570 + nest(?indent, space([text(<<"fun">>) | Clauses])),
571 + text(<<"end">>)
572 + ),
573 + inherit
574 + )
575 + ),
576 + {ForceBreak0, Doc0, Rest0}
577 + end,
578 + {ForceBreak, Doc, Rest}.
545 579
546 580 -spec begin_(tokens()) -> {force_break(), doc(), tokens()}.
547 581 begin_([{'begin', _} | Tokens]) ->
  @@ -705,8 +739,9 @@ head_and_clause(Rest0, Doc0) ->
705 739 % The first case happens most of the time. The second case happens when we have
706 740 % things like:
707 741 % if X =:= test; X =:= other ->
708 - {_End, _ForceBreak, Expr, Leftovers} = expr(Tokens, no_force_break),
709 - head_and_clause(Leftovers ++ [Token | Rest1], space(Doc0, Expr)).
742 + {_End, ForceBreak, Exprs, Leftovers} = exprs(Tokens),
743 + Group = force_break(ForceBreak, group(space(Exprs), inherit)),
744 + head_and_clause(Leftovers ++ [Token | Rest1], space(Doc0, Group)).
710 745
711 746 -spec clause(tokens()) -> {continue(), force_break(), doc(), tokens()}.
712 747 clause(Tokens) ->
  @@ -903,10 +938,18 @@ expr_(
903 938 ]
904 939 ),
905 940 expr_(Rest, space(Doc, Fun), ForceBreak);
941 + expr_(
942 + [{'fun', _}, {var, LineNum, Var}, {'/', LineNum}, {integer, LineNum, Arity} | Rest],
943 + Doc,
944 + ForceBreak
945 + ) ->
946 + % Handle `fun Var/arity`
947 + Fun = cons([text(<<"fun ">>), text(v2b(Var)), text(<<"/">>), text(i2b(Arity))]),
948 + expr_(Rest, space(Doc, Fun), ForceBreak);
906 949 expr_(
907 950 [
908 951 {'fun', _},
909 - {var, LineNum, MacroName},
952 + {var, LineNum, Var},
910 953 {':', LineNum},
911 954 {atom, LineNum, FunctionName},
912 955 {'/', LineNum},
  @@ -920,7 +963,7 @@ expr_(
920 963 cons(
921 964 [
922 965 text(<<"fun ">>),
923 - text(v2b(MacroName)),
966 + text(v2b(Var)),
924 967 text(<<":">>),
925 968 text(a2b(FunctionName)),
926 969 text(<<"/">>),
  @@ -1213,6 +1256,14 @@ get_until(End, [{C, _} = Token | Rest], Acc, [C | Stack]) when ?IS_LIST_CLOSE_CH
1213 1256 get_until(End, [{C, _} = Token | Rest], Acc, Stack) when ?IS_LIST_OPEN_CHAR(C) ->
1214 1257 % If we hit an open bracket we ignore until the close bracket
1215 1258 get_until(End, Rest, [Token | Acc], [close_bracket(C) | Stack]);
1259 + get_until(End, [{'fun', _} = Token, {'(', _}, {')', _}, {Op, _} | _] = Rest0, Acc, Stack)
1260 + when Op == '|' orelse Op == dot ->
1261 + % 'fun' without 'end'
1262 + % -type x() :: fun().
1263 + % or
1264 + % -type x() :: fun() | y().
1265 + Rest1 = tl(Rest0),
1266 + get_until(End, Rest1, [Token | Acc], Stack);
1216 1267 get_until(End, [{'fun', _} = Token, {'(', _}, {'(', _} | _] = Rest0, Acc, Stack) ->
1217 1268 % 'fun' without 'end'
1218 1269 % fun((Arg :: type) -> other_type())
  @@ -1223,6 +1274,11 @@ get_until(End, [{'fun', _} = Token, {atom, _, _} | _] = Rest0, Acc, Stack) ->
1223 1274 % fun local/1
1224 1275 Rest1 = tl(Rest0),
1225 1276 get_until(End, Rest1, [Token | Acc], Stack);
1277 + get_until(End, [{'fun', _} = Token, {var, _, _}, {'/', _} | _] = Rest0, Acc, Stack) ->
1278 + % 'fun' without 'end'
1279 + % fun X/1
1280 + Rest1 = tl(Rest0),
1281 + get_until(End, Rest1, [Token | Acc], Stack);
1226 1282 get_until(End, [{'fun', _} = Token, {'?', _}, {var, _, _}, {':', _} | _] = Rest0, Acc, Stack) ->
1227 1283 % 'fun' without 'end'
1228 1284 % fun ?MACRO:x/1
  @@ -1370,11 +1426,36 @@ get_end_of_expr(
1370 1426 % fun((Arg :: type) -> other_type())
1371 1427 Rest1 = tl(Rest0),
1372 1428 get_end_of_expr(Rest1, [Token | Acc], LineNum, KeywordStack, Guard);
1429 + get_end_of_expr(
1430 + [{'fun', LineNum} = Token, {'(', _}, {')', _}, {Op, _} | _] = Rest0,
1431 + Acc,
1432 + _,
1433 + KeywordStack,
1434 + Guard
1435 + )
1436 + when Op == dot orelse Op == '|' ->
1437 + % 'fun' without 'end'
1438 + % -type x() :: fun().
1439 + % or
1440 + % -type x() :: fun() | y().
1441 + Rest1 = tl(Rest0),
1442 + get_end_of_expr(Rest1, [Token | Acc], LineNum, KeywordStack, Guard);
1373 1443 get_end_of_expr([{'fun', LineNum} = Token, {atom, _, _} | _] = Rest0, Acc, _, KeywordStack, Guard) ->
1374 1444 % 'fun' without 'end'
1375 1445 % fun local/1
1376 1446 Rest1 = tl(Rest0),
1377 1447 get_end_of_expr(Rest1, [Token | Acc], LineNum, KeywordStack, Guard);
1448 + get_end_of_expr(
1449 + [{'fun', LineNum} = Token, {var, _, _}, {'/', _} | _] = Rest0,
1450 + Acc,
1451 + _,
1452 + KeywordStack,
1453 + Guard
1454 + ) ->
1455 + % 'fun' without 'end'
1456 + % fun X/1
1457 + Rest1 = tl(Rest0),
1458 + get_end_of_expr(Rest1, [Token | Acc], LineNum, KeywordStack, Guard);
1378 1459 get_end_of_expr(
1379 1460 [{'fun', LineNum} = Token, {'?', _}, {var, _, _}, {':', _} | _] = Rest0,
1380 1461 Acc,
  @@ -87,11 +87,5 @@ eq_({_Left, _Right}, _) ->
87 87 false.
88 88
89 89 check_for_errors([], _) -> ok;
90 - check_for_errors([{error, {LineNum, erl_parse, ["syntax error before: ", Str]}} | _], File) ->
91 - Err =
92 - list_to_binary(
93 - io_lib:format("~s: Error on line ~p: syntax error before ~s", [File, LineNum, Str])
94 - ),
95 - {error, Err};
96 90 check_for_errors([{error, Msg} | _], File) -> {error, {File, Msg}};
97 91 check_for_errors([_ | Rest], File) -> check_for_errors(Rest, File).
  @@ -121,6 +121,28 @@ format_files([File | Rest], Opts) ->
121 121 [File, IncludeFile, Line]
122 122 ),
123 123 format_files(Rest, Opts);
124 + {error, {File, {Line, epp, Err}}} ->
125 + % These errors typically mean that the sorce does not compile.
126 + % Not really our problem so we warn instead of erroring.
127 + rebar_api:warn(
128 + "Steamroller Warn: File: ~s: epp error ~p on line ~p. Skipping...",
129 + [File, Err, Line]
130 + ),
131 + format_files(Rest, Opts);
132 + {error, {File, {Line, erl_parse, ["syntax error before: ", Str]}}} ->
133 + rebar_api:warn(
134 + "Steamroller Warn: File: ~s: Syntax error before ~s on line ~p. Skipping...",
135 + [File, Str, Line]
136 + ),
137 + format_files(Rest, Opts);
138 + {error, {File, {Line, erl_parse, Err}}} ->
139 + % These errors typically mean that the sorce does not compile.
140 + % Not really our problem so we warn instead of erroring.
141 + rebar_api:warn(
142 + "Steamroller Warn: File: ~s: erl_parse error ~p on line ~p. Skipping...",
143 + [File, Err, Line]
144 + ),
145 + format_files(Rest, Opts);
124 146 {error, _} = Err -> Err
125 147 end;
126 148 format_files([], _) -> ok.