- rewrite
a |> Enum.map(m) |> Enum.join()
tomap_join(a, m)
. we already did this forjoin/2
, but missed the case forjoin/1
At this point, 1.0.0 feels feature complete. Two things remains for a full release:
- feedback!
- documentation overhaul! monitor progress here
Styler's two biggest outstanding bugs have been fixed, both related to compilation breaking during module directive organization. One was references to aliases being moved above where the aliases were declared, and the other was similarly module directives being moved after their uses in module directives.
In both cases, Styler is now smart enough to auto-apply the fixes we recommended in the old Readme.
Other than that, a slew of powerful new features have been added, the neatest one (in the author's opinion anyways) being Alias Lifting.
Along the lines of Credo.Check.Design.AliasUsage
, Styler now "lifts" deeply nested aliases (depth >= 3, ala A.B.C....
) that are used more than once.
Put plainly, this code:
defmodule A do
def lift_me() do
A.B.C.foo()
A.B.C.baz()
end
end
will become
defmodule A do
@moduledoc false
alias A.B.C
def lift_me do
C.foo()
C.baz()
end
end
To exclude modules ending in .Foo
from being lifted, add styler: [alias_lifting_exclude: [Foo]]
to your .formatter.exs
A long outstanding breakage of a first pass with Styler was breaking directives that relied on module attributes which Styler moved after their uses. Styler now detects these potential breakages and automatically applies our suggested fix, which is creating a variable before the module. This usually happened when folks were using a library that autogenerated their moduledocs for them.
In code, this module:
defmodule MyGreatLibrary do
@library_options [...]
@moduledoc make_pretty_docs(@library_options)
use OptionsMagic, my_opts: @library_options
...
end
Will now be styled like so:
library_options = [...]
defmodule MyGreatLibrary do
@moduledoc make_pretty_docs(library_options)
use OptionsMagic, my_opts: unquote(library_options)
@library_options library_options
...
end
Styler now organizes Mix.Config.config/2,3
stanzas according to erlang term sorting. This helps manage large configuration files, removing the "where should I put this" burden from developers AND helping find duplicated configuration stanzas.
See the moduledoc for Styler.Style.Configs
for more.
if
/unless
: invert if and unless with!=
or!==
, like we do for!
andnot
#132@derive
: move@derive
beforedefstruct|schema|embedded_schema
declarations (fixes compiler warning!) #134- strings: rewrite double-quoted strings to use
~s
when there's 4+ escaped double-quotes ("\"\"\"\""
->~s("""")
) (Credo.Check.Readability.StringSigils
) #146 Map.drop(foo, [single_key])
=>Map.delete(foo, single_key)
#161 (also in pipes)Keyword.drop(foo, [single_key])
=>Keyword.delete(foo, single_key)
#161 (also in pipes)lhs |> Enum.reverse() |> Kernel.++(enum)
=>lhs |> Enum.reverse(enum)
alias
: expands aliases when moving an alias after another directive that relied on it (#137)- module directives: various fixes for unreported obscure crashes
- pipes: fix a comment-shifting scenario when unpiping
Timex.now/1
will no longer be rewritten toDateTime.now!/1
due to Timex accepting a wider domain of "timezones" than the stdlib (#145, h/t @ivymarkwell)with
: skip nodes which (unexpectedly) do not contain ado
body (#158, h/t @DavidB59)then(&fun/1)
: fix false positives on arithmetic&1 + x / 1
(#164, h/t @aenglisc)
- drop support for elixir
1.14
- ModuleDirectives: group callback attributes (
before_compile after_compile after_verify
) with nondirectives (previously, were grouped withuse
, their relative order maintained). to keep the desired behaviour, you can make newuse
macros that wrap these callbacks. Apologies if this makes using Styler untenable for your codebase, but it's probably not a good tool for macro-heavy libraries. - sorting configs for the first time can change your configuration; see
Styler.Style.Configs
moduledoc for more
- pipes: check for
Stream.foo
equivalents toEnum.foo
in a few more cases
- pipes:
|> then(&(&1 op y))
rewrites with|> Kernel.op(y)
as long as the operator is defined inKernel
; skips the rewrite otherwise (h/t @kerryb for the report & @saveman71 for the fix)
Two releases in one day!? @koudelka made too good a point about Map.new
not being special...
- pipes: treat
MapSet.new
andKeyword.new
the same way we doMap.new
(h/t @koudelka) - pipes: treat
Stream.map
the same asEnum.map
when piped|> Enum.into
- deprecations:
~R
->~r
,Date.range/2
->Date.range/3
with decreasing dates (h/t @milmazz) - if: rewrite
if not x, do: y
=>unless x, do: y
- pipes:
|> Enum.map(foo) |> Map.new()
=>|> Map.new(foo)
- pipes: remove unnecessary
then/2
on named function captures:|> then(&foo/1)
=>|> foo()
,|> then(&foo(&1, ...))
=>|> foo(...)
(thanks to @tfiedlerdejanze for the idea + impl!)
- directives: maintain order of module compilation callbacks (
@before_compile
etc) relative touse
statements (Closes #120, h/t @frankdugan3)
- fix parsing ranges with non-trivial integer bounds like
x..y
(Closes #119, h/t @maennchen)
Shoutout @milmazz for all the deprecation work below =)
- Deprecations: Rewrite 1.16 Deprecations (h/t @milmazz for all the work here)
- add
//1
step toEnum.slice/2|String.slice/2
with decreasing ranges File.stream!(file, options, line_or_bytes)
=>File.stream!(file, line_or_bytes, options)
- add
- Deprecations
Path.safe_relative_to/2
=>Path.safe_relative/2
- directives: fix infinite loop when encountering
@spec import(...) :: ...
(Closes #115, h/t @kerryb) with
: fix deletion of arrow-lesswith
statements within function invocations
pipes
: fix unpiping do-blocks into variables when the parent expression is a function invocation likea(if x do y end |> z(), b)
(Closes #114, h/t @wkirschbaum)
with
: fixwith
replacement when it's the only child of ado
or->
block (Closes #107, h/t @kerryb -- turns out those edge cases did exist in the wild!)
Styler will no longer make comments jump around in any situation, and will move comments with the appropriate node in all cases but module directive rearrangement (where they'll just be left behind - sorry! we're still working on it).
- Keep comments in logical places when rewriting if/unless/cond/with (#79, #97, #101, #103)
This release has a slew of improvements for with
statements. It's not surprising that there's lots of style rules for with
given that just about any case
, if
, or even cond do
could also be expressed as a with
. They're very powerful! And with great power...
- style trivial pattern matches ala
lhs <- rhs
tolhs = rhs
(#86) - style
_ <- rhs
torhs
- style keyword
, do:
todo end
rather than wrapping multiple statements in parens - style statements all the way to
if
statements when appropriate (#100)
- Rewrite
{Map|Keyword}.merge(single_key: value)
to useput/3
instead (#96)
with
: various edge cases we can only hope no one's encountered and thus never reported
After being bitten by two of them in a row, Styler's test suite now makes sure that there are no idempotency bugs as part of its tests.
In short, we now have assert style(x) == style(style(x))
as part of every test. Sorry for not thinking to include this before :)
- alias: fix single-module alias deletion newlines bug
- comments: ensure all generated nodes always include line meta (#101)
- alias: delete noop single-module aliases (
alias Foo
, #87, h/t @mgieger)
- pipes: unnest all pipe starts in one pass (
f(g(h(x))) |> j()
=>x |> h() |> g() |> f() |> j()
, #94, h/t @tomjschuster)
- charlists: leave charlist rewriting to elixir's formatter on elixir >= 1.15
- charlists: rewrite empty charlist to use sigil (
''
=>~c""
) - pipes: don't blow up extracting fully-qualified macros (
Foo.bar do end |> foo()
, #91, h/t @NikitaNaumenko)
with
: remove identity singleton else clause (egelse {:error, e} -> {:error, e} end
,else error -> error end
)
- Fix function head shrink-failures causing comments to jump into blocks (Closes #67, h/t @APB9785)
- hoist all block-starts to pipes to their own variables (makes styler play better with piped macros)
- fix pipes starting with a macro do-block creating invalid ast (#83, h/t @mhanberg)
- rewrite pipes starting with
quote
blocks like we do withcase|if|cond|with
blocks (#82, h/t @SteffenDE)
- removed
mix style
task
- fix mistaking
Timex.now/1
in a pipe forTimex.now/0
(#66, h/t @sabiwara)
- stop rewriting
Timex.today/0
given that we allowTimex.today/1
-- too inconsistent.
if
statements: dropelse
clauses whose body is simplynil
- fix
unless a do b else c end
rewrites toif
not flopping do/else bodies! (#77, h/t @jcowgar) - fix pipes styling ranges with steps (
a..b//c
) incorrectly (#76, h/t @cschmatzler)
- fix exception styling module attributes named
@def
(we confused them with realdef
s, whoops!) (#75, h/t @randycoulman)
the boolean blocks edition!
- auto-fix
Credo.Check.Refactor.CondStatements
(detects any truthy atom, not justtrue
) - if/unless rewrites:
Credo.Check.Refactor.NegatedConditionsWithElse
Credo.Check.Refactor.NegatedConditionsInUnless
Credo.Check.Refactor.UnlessWithElse
the with statement edition!
- Added right-hand-pattern-matching rewrites to
for
andwith
left arrow expressions<-
(ex:with map = %{} <- foo()
=>with %{} = map <- foo
) with
statement rewrites, solving the following credo rulesCredo.Check.Readability.WithSingleClause
Credo.Check.Refactor.RedundantWithClauseResult
Credo.Check.Refactor.WithClauses
- Fixed exception when encountering non-arrowed case statements ala
case foo, do: unquote(quoted)
(#69, h/t @brettinternet, nice)
- Timex related fixes (#66):
- Rewrite
Timex.now/1
toDateTime.now!/1
instead ofDateTime.utc_now/1
- Only rewrite
Timex.today/0
, don't changeTimex.today/1
- Rewrite
- DateTime rewrites (#62, ht @milmazz)
DateTime.compare
=>DateTime.{before/after}
(elixir >= 1.15)Timex.now
=>DateTime.utc_now
Timex.today
=>Date.utc_today
- Pipes: add
!=
,!==
,===
,and
, andor
to list of valid infix operators (#64)
- Pipes always de-sugars keyword lists when unpiping them (#60)
- ModuleDirectives doesn't mistake variables for directives (#57, h/t @leandrocp)
- ModuleDirectives no longer throws comments around a file when hoisting directives up (#53)
- rewrite
Logger.warn/1,2
toLogger.warning/1,2
due to Elixir 1.15 deprecation
- don't unpipe single-piped
unquote
expressions (h/t @elliottneilclark)
- fix 0-arity paren removal on metaprogramming creating uncompilable code (h/t @simonprev)
- fix crash from
mix style
running plugins as part of formatting (no longer runs formatter plugins)
- single-quote charlists are rewritten to use the
~c
sigil ('foo'
->~c'foo'
) (h/t @fhunleth) mix style
warns the user that Styler is primarily meant to be used as a plugin
- fix crash when encountering single-quote charlists (h/t @fhunleth)
- single-quote charlists are rewritten to use the
~c
sigil ('foo'
->~c'foo'
) - when encountering
_ = bar ->
, replace it withbar ->
- Fix a toggle state resulting from (ahem, nonsense) code like
_ = bar ->
encountering ParameterPatternMatching style
- Fix crash trying to remove 0-arity parens from metaprogramming ala
def unquote(foo)()
- Rewrite
Enum.into/2,3
intoMap.new/1,2
when the collectable is%{}
orMap.new/0
- Fix crash when single pipe had inner defs (h/t @michallepicki)
- Fix bug from
ParameterPatternMatching
implementation that re-ordered pattern matching incond do
->
clauses
- Implement
Credo.Check.Readability.PreferImplicitTry
- Implement
Credo.Check.Consistency.ParameterPatternMatching
fordef|defp|fn|case
- Remove parens from 0-arity function definitions (
Credo.Check.Readability.ParenthesesOnZeroArityDefs
)
- Rewrite
case ... true -> ...; _ -> ...
toif
statements as well
- Rewrite
case ... true / else ->
to beif
statements
Styler.Style.Simple
:- Optimize
Enum.reverse(foo) ++ bar
toEnum.reverse(foo, bar)
- Optimize
Styler.Style.Pipes
:- Rewrite
|> (& ...).()
to|> then(& ...)
(Credo.Check.Readability.PipeIntoAnonymousFunctions
) - Add parens to 1-arity pipe functions (
Credo.Check.Readability.OneArityFunctionInPipe
) - Optimize
a |> Enum.reverse() |> Enum.concat(enum)
toEnum.reverse(a, enum)
- Rewrite
- Better error handling:
mix format
will still format files if a style fails
mix style
: only run on.ex
and.exs
filesModuleDirectives
: now expandsalias __MODULE__.{A, B}
(h/t @adriankumpf)
mix style
: brought back to life for folks who want to incrementally introduce Styler
Styler.Style.Pipes
:- include
x in y
and^foo
(for ecto) as a valid pipe starts - work even harder to keep rewrites on one line
- include
ModuleDirectives
: handle dynamic module namesPipes
: includeEcto.Query.from
andQuery.from
as valid pipe starts
- Sped up styling just a little bit
Styler
now implementsMix.Task.Format
, meaning it is now an Elixir formatter plugin. See the README for new installation & usage instructions
- the
mix style
task has been removed
Pipes
rewrites|> Enum.into(%{}[, mapper])
andEnum.into(Map.new()[, mapper])
toMap.new/1,2
calls
Pipes
rewrites some two-step processes into one, fixing these credo issues in pipe chains:Credo.Check.Refactor.FilterCount
Credo.Check.Refactor.MapJoin
Credo.Check.Refactor.MapInto
ModuleDirectives
handles even weirder places to hide your aliases (anonymous functions, in this case)Pipes
tries even harder to keep single-pipe rewrites of invocations on one line
Pipes
- fixed omission of
==
as a valid pipe start operator (h/t @peake100 for the issue) - fixed rewrite of
a |> b
, whereb
was invoked without parenthesis
- fixed omission of
- Enabled
Defs
style and overhauled it to properly handles comments - Optimized and tweaked
ModuleDirectives
style- Now culls newlines between "groups" of the same directive
- sorts
@behaviour
directives - orders directives within non defmodule contexts (eg, a
def do
) if there's at least onealias|require|use|import
Pipes
will try to keep single-pipe rewrites on one line
- Added
ModuleDirectives
style- Note that this is potentially destructive in some rare cases. See moduledoc for more.
- This supersedes the
Aliases
style, which has been removed.
mix style -
reads and writes to stdin/stdout
Pipes
style is now aware ofunless
blocks
- Lots of README tweaking =)
- Optimized some Zipper operations
- Added
Simple
style, replacing the following Credo rule:Credo.Check.Readability.LargeNumbers
- Exceptions while parsing code now appropriately render filename rather than
nofile:xx
- Fixed opaque
Zipper.path()
typespec implementation mismatches (thanks @sega-yarkin) - Made
ex_doc
dev only, removing it as a dependency for users of Styler
- Initial release of Styler
- Added
Aliases
style, replacing the following Credo rules:Credo.Check.Readability.AliasOrder
Credo.Check.Readability.MultiAlias
Credo.Check.Readability.UnnecessaryAliasExpansion
- Added
Pipes
style, replacing the following Credo rules:Credo.Check.Readability.BlockPipe
Credo.Check.Readability.SinglePipe
Credo.Check.Refactor.PipeChainStart
- Added
Defs
style (currently disabled by default)