Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
3769ffb
ignore no-paren, no-body function heads. closes #185
novaugust Aug 7, 2024
1661f41
minor optimization
novaugust Aug 7, 2024
b3ccc02
Wrap up 1.0.0 docs overhaul
novaugust Aug 8, 2024
ea8c723
1.0.0
novaugust Aug 8, 2024
a9bc689
Add missing documentation. Closes #188
novaugust Aug 27, 2024
ee0a6fa
update readme link
novaugust Aug 27, 2024
c29d10d
add more verbiage re styler can add bugs. Closes #186
novaugust Aug 27, 2024
3a66e42
reword warning to list examples of breakages
novaugust Aug 27, 2024
f53cf7c
Update styles.md
ktekinay Aug 30, 2024
b4bca9c
Merge pull request #192 from ktekinay/feature/fix-style-example
novaugust Aug 30, 2024
f2b269e
Add Stream.run optimizations, fix optimizations shrinking pipes to on…
novaugust Sep 11, 2024
d6c90cd
fix infinite loop rewriting negated if with empty do body. closes #196
novaugust Sep 23, 2024
cde90d3
Remove unless from codebases (#194)
novaugust Sep 23, 2024
926ad51
use `not` over `!` when rewriting `unless a (> >= < <= in) b`
novaugust Sep 23, 2024
9125157
actually, lets only do `not` for `in`
novaugust Sep 23, 2024
42b5d40
v1.1.0
novaugust Sep 23, 2024
79dce09
update version in readme
novaugust Sep 23, 2024
4aa18ba
Don't pipe into `Kernel.!` when rewriting unless with pipes
novaugust Sep 23, 2024
7fb05fa
configs: improve comment handling when moving a small number of nodes…
novaugust Oct 18, 2024
b38b990
v1.1.2
novaugust Oct 18, 2024
548fbf6
pipes: improve comment behaviour in optimizations (Closes #176)
novaugust Oct 18, 2024
fe32a84
One-line unpiped assignments (#197)
novaugust Nov 7, 2024
60f79c7
Pipify: `d(a |> b |> c)` => `a |> b() |> c() |> d()`(#198)
novaugust Nov 7, 2024
5016027
1.18 hard deprecations (#203)
novaugust Nov 19, 2024
929d217
docs and changelog
novaugust Nov 19, 2024
da3f3c9
docs prep for 1.2
novaugust Nov 20, 2024
65007ba
v1.2.0
novaugust Nov 20, 2024
d930cab
correct docs on Enum.into
novaugust Nov 20, 2024
872ad34
Fix pipifying pipes-in-pipes (Closes #204)
novaugust Nov 21, 2024
4ec6ba7
v1.2.1
novaugust Nov 21, 2024
b7dcfd5
introduce `# styler:sort` comment directive (#205)
novaugust Nov 23, 2024
411cc93
styler:sort - handle tuples
novaugust Nov 23, 2024
b13d045
mix format :X
novaugust Nov 23, 2024
0e8485b
style:sort - dont dedupe
novaugust Nov 23, 2024
5b56613
docs and fix a typo
novaugust Nov 25, 2024
d35a28d
fiddle with private apis to ease iex playing
novaugust Nov 27, 2024
98c5c1c
remove Zipper.reduce/3 - bugged, but more importantly unused
novaugust Nov 27, 2024
cef6015
Maintain comment-node relations when applying `#styler:sort` directiv…
novaugust Jan 13, 2025
13bc947
v1.3.0
novaugust Jan 13, 2025
03b1ee6
correct changelog
novaugust Jan 13, 2025
d6a2a5e
fix twople bug in sort directive, add map sorting
novaugust Jan 13, 2025
30446bf
v1.3.1
novaugust Jan 13, 2025
5d3d620
defstruct with list literal
novaugust Jan 13, 2025
bef00c9
ci: update elixir and erlang versions
tfiedlerdejanze Jan 13, 2025
d132d79
sort directive: sort values of keys. Closes #208
novaugust Jan 13, 2025
877007a
Merge pull request #209 from tfiedlerdejanze/ci-elixir-patch-upgrades
novaugust Jan 13, 2025
88d3334
v1.3.2
novaugust Jan 14, 2025
9b0207b
fix comments bug in styler:sort directive
novaugust Jan 15, 2025
efb2cb9
styler:sort arbitrary ast within do end blocks
novaugust Jan 16, 2025
9983bf2
improve `with` statement replacements
novaugust Jan 21, 2025
470b390
v1.3.3
novaugust Jan 21, 2025
7932147
alias lifting: shrink when alias already exists. closes #201
novaugust Jan 24, 2025
ff7755d
alias lifting: be better about conflicts. Closes #193
novaugust Jan 24, 2025
3750e0c
improve alias lift collision case
novaugust Jan 24, 2025
fc71aee
remove vestigial with rewriting head
novaugust Jan 25, 2025
9404d5f
pipes: handle pipifying functions whose first arg is itself a pipe. c…
novaugust Feb 13, 2025
ff004ca
cleanup the messes left in the previous commit 🙈
novaugust Feb 13, 2025
5a23833
correct issue number in change log
novaugust Feb 13, 2025
297106a
test against 1.18
novaugust Feb 20, 2025
ee34edd
1.18 warnings + formatting
novaugust Feb 20, 2025
8d921d9
no one saw that right?
novaugust Feb 20, 2025
e083b4b
ex1.17+: replace `:timer.units(x)` with the new `to_timeout(unit: x)`…
novaugust Feb 20, 2025
d0ecf1d
1.18+: change struct updates to map updates. Closes #199
novaugust Feb 20, 2025
74d6fd2
ensure test works across versions
novaugust Feb 20, 2025
fc6fb5d
fix `with` rewrites when keyword with an else stab (#220)
novaugust Feb 20, 2025
a46c43f
pipify nested function calls with pipe as the first argument. closes …
novaugust Feb 20, 2025
ceb827a
change struct update deprecation to ex1.19+
novaugust Feb 20, 2025
d015a99
docs docs docs docs docs!
novaugust Jan 25, 2025
6896d97
ship struct update to map update changes after all
novaugust Feb 20, 2025
aaedd0c
v1.4.0
novaugust Feb 20, 2025
3d5a485
Add OTP26/27 but only run for 1.17/1.18
halfdan Feb 21, 2025
d3318fd
Merge pull request #222 from halfdan/expand-matrix
novaugust Feb 21, 2025
941d067
fix `with` redundant body + non-arrow behind redundant clause. Closes…
novaugust Feb 21, 2025
9990eb6
link to quokka
novaugust Feb 24, 2025
3b0571e
rewrite `to_timeout(unit: x * m)` to use larger units in some cases
novaugust Mar 3, 2025
8fe1ca0
defs test describe formatting
novaugust Mar 3, 2025
13320e9
dont crash on invalid defs
novaugust Mar 3, 2025
1df5f1d
fix CI for older elixir
novaugust Mar 6, 2025
be4dcec
v1.4.1
novaugust Mar 17, 2025
6b42462
if: drop empty do bodies. Closes #227
novaugust Apr 1, 2025
5b1c946
Fix large comment block mangling bug when ordering sibling AST (#232)
novaugust Apr 29, 2025
50ae386
if: treat is_nil as a negator
novaugust Apr 29, 2025
17434b6
Revert "if: treat is_nil as a negator"
novaugust Apr 29, 2025
aa3e7ce
Revert "if: drop empty do bodies. Closes #227"
novaugust Apr 29, 2025
c511610
v1.4.2
novaugust May 1, 2025
f66fc54
changelog: fix GH md formatting issues
novaugust May 8, 2025
e2d4e11
optimize zipper performance
novaugust May 22, 2025
0fcfcdc
Revert "optimize zipper performance"
novaugust Jun 12, 2025
9d596b3
fix bug in unpiping syntax-sugared non-atom-valued keyword lists
novaugust Jul 14, 2025
d0f0c3c
add `minimum_supported_elixir_version` configuration. closes #231
novaugust Jul 14, 2025
54dd92d
Add license preambles to four files
novaugust Jul 14, 2025
501ec6d
feat: Apply aliases (#237)
novaugust Jul 15, 2025
0a756d6
fix: config sorting mangling floating comment blocks. Closes #230
novaugust Jul 15, 2025
e9246fb
styler:sort sorts struct/map based typespec keys. Closes #213
novaugust Jul 15, 2025
5bcd666
lift aliases in snippets. closes #189
novaugust Jul 15, 2025
529d9fe
inline module attribute
novaugust Jul 15, 2025
710b5bf
v1.5.0
novaugust Jul 15, 2025
1d67a00
docs updates
novaugust Jul 16, 2025
45fec17
more docs
novaugust Jul 16, 2025
20954a4
ahem. nothing to see here.
novaugust Jul 17, 2025
be66c30
fix a -> @ typo in module directive "interesting?" check
novaugust Jul 18, 2025
5a01051
fix: comment mangling when lifting aliases in snippets. closes #239
novaugust Jul 18, 2025
af740c3
clean up comment - that "bug" isnt a bug
novaugust Jul 18, 2025
e7a3b81
v1.5.1
novaugust Jul 20, 2025
51d1cc7
fix: alias lifting snippets with non-block root. closes #240
novaugust Jul 23, 2025
d8ea620
rewrite negated assert/refute
novaugust Jul 28, 2025
4d8e05b
assert Enum.foo rewrites, and tweaks to the negated rewrites
novaugust Jul 28, 2025
0ffe339
fix link in docs
novaugust Jul 28, 2025
4d6fca6
v1.6.0
novaugust Jul 28, 2025
45c7b1a
cond: standardize on `true` for the literal in the final clause
novaugust Aug 4, 2025
79d65e9
consolidate config integration tests
novaugust Aug 4, 2025
f859949
Enum.filter(fun) |> List.first([default]) => Enum.find([default], fun…
novaugust Aug 9, 2025
7895461
update pipes docs
novaugust Aug 9, 2025
b29ac40
more pipes docs
novaugust Aug 10, 2025
97c6aff
add some preaching around why styler does a weird thing
novaugust Aug 12, 2025
b5d7d9d
v1.7.0
novaugust Aug 12, 2025
cb5dc63
rewrite single-clause case statements to assignments
novaugust Sep 5, 2025
be86c5b
v1.8.0
novaugust Sep 9, 2025
1df3405
to_timeout: rewrite plurals to singulars
novaugust Sep 19, 2025
cf55c61
😍
novaugust Sep 19, 2025
f9d5179
one last bit of nerd
novaugust Sep 19, 2025
0e7b977
fix :timer.foo regression
novaugust Sep 22, 2025
6722b19
all the work in one spot
novaugust Sep 22, 2025
8c5d919
v1.9.0
novaugust Sep 22, 2025
c96d722
add changelog link to hex package
novaugust Sep 24, 2025
dcfaa74
readme updates
novaugust Sep 25, 2025
81bb7ef
more readme tweakin
novaugust Sep 25, 2025
78f35eb
Fix rewrite of single-clause case statement with assignment parent. C…
novaugust Sep 30, 2025
bb0cde8
v1.9.1
novaugust Sep 30, 2025
793cf27
optimize Req pipes
novaugust Oct 30, 2025
e48ca6c
less is more
novaugust Oct 30, 2025
1a6a375
tweak intro sentence
novaugust Nov 4, 2025
78ced6b
TIL capital sigils cant be escaped
novaugust Nov 4, 2025
a490ad6
sort |> reverse => sort(:desc)
novaugust Nov 18, 2025
7884561
allow docs for Styler.string_to_ast
novaugust Dec 2, 2025
2af7d19
Enum.map |> Enum.intersperse => Enum.map_intersperse
novaugust Dec 2, 2025
70b5045
v1.10.0
novaugust Dec 2, 2025
ae19d31
bump version in changelog
novaugust Dec 2, 2025
77861bf
pipes docs reorganization
novaugust Dec 3, 2025
3e29cae
Add experimental mix styler.remove_unused task
novaugust Jan 12, 2026
6055b07
Add `mix styler.inline_attrs <file>` refactor tool
novaugust Jan 12, 2026
4e013e0
v1.10.1
novaugust Jan 12, 2026
02292c6
`mix styler.remove_unused`: allow multiple file args
novaugust Jan 15, 2026
5f6d2ba
readme task link fix
novaugust Jan 18, 2026
a68788c
fix inline_attributes task docs
novaugust Feb 8, 2026
f839aa6
module attributes: dont break references from use, moduledoc, etc
novaugust Feb 25, 2026
bb07d16
1.11.0
novaugust Feb 26, 2026
5e43421
Merge remote-tracking branch 'upstream/main' into upstream-merge-1.11.0
JesseHerrick May 5, 2026
0d190bf
A few fixes for v1.11.0
JesseHerrick May 6, 2026
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
12 changes: 11 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Copyright 2024 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

[
inputs: [
"{mix,.formatter}.exs",
Expand All @@ -8,6 +18,6 @@
assert_style: 2
],
plugins: [Styler],
styler: [alias_lifting_exclude: []],
styler: [alias_lifting_exclude: [], minimum_supported_elixir_version: nil],
line_length: 122
]
246 changes: 246 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,254 @@

**Note** Styler's only public API is its usage as a formatter plugin. While you're welcome to play with its internals,
they can and will change without that change being reflected in Styler's semantic version.

## main

## 1.11.0

### Improvements

- `mix styler.inline_attrs`: Allow multiple file paths to be specified: `mix styler.inline_attrs <file1> [<file2> ...]`

#### Module Directive References

Module directives got smarter. Styler will no longer move module attributes below their references in `use` or `@moduledoc`s.

In other words, Styler will leave the following code untouched:

```elixir
defmodule MyGreatLibrary do
@library_options [...]
@moduledoc make_pretty_docs(@library_options)
use OptionsMagic, my_opts: @library_options
end
```

## 1.10.1

### Improvements

Adds two experimental refactoring features as mix tasks.

#### `mix styler.remove_unused`

With Elixir 1.20 on the horizon, many projects are about to discover that they have _a lot_ of unnecessary `require Logger` lines throughout their codebase.

`mix styler.remove_unused` will automate the removal of those `unused require:` statements, alongside any `unused import:` and `unused alias:` warnings.

This has long been an internal script useful for running after a bigger refactor that resulted in many superfluous aliases, but with 1.20 coming it seems it might be useful for others as well.

This will never be an integrated part of `Styler`'s format plugin features, as it would _not_ be correct to remove unused nodes whenever running format. It's typical to have unused warnings while in the midst of an implementation, and deleting that code would be obnoxious.

#### `mix styler.inline_attrs <file>`

Inlines one-off module attributes that define literal values.

This is something that sometimes is good, and sometimes is bad. In general, defining a module attribute when you could've just written an atom is bad, so inlining is good!

It would probably be most useful as a refactor ability for a language server, but CLIs are a nice second place.

An example of a situation where it results in an improvement:

```elixir
# Unnecessary indirection with single-use literal-value module attributes
defmodule A do
@http_client_key :http_key
@default_client MyHTTPClient

def http_client, do: Application.get_env(:my_app, @http_client_key, @default_client)
end
# Much better! styler.inline_attrs will perform this refactor
defmodule A do
def http_client, do: Application.get_env(:my_app, :http_key, MyHTTPClient)
end
```

It's worthwhile to run this on some suspicious files, then followup with manual intervention when it went too far. This style is not aware of quote boundaries, and so might do some broken things. (Hence "EXPERIMENTAL")

You've been warned =)

## 1.10.0

### Improvements

Two new standard-library pipe optimizations

- `enum |> Enum.map(fun) |> Enum.intersperse(separator)` => `Enum.map_intersperse(enum, separator, fun)`
- `enum |> Enum.sort() |> Enum.reverse()` => `Enum.sort(enum, :desc)`

And Req (the http client library) pipe optimizations, as detailed below

#### Req pipe optimizations

[Req](https://github.com/wojtekmach/req) is a popular HTTP Client. If you aren't using it, you can just ignore this whole section!

Reqs 1-arity "execute the request" functions (`delete get head patch post put request run`) have a 2-arity version that takes a superset of the arguments `Req.new/1` does as its first argument, and the typical `options` keyword list as its second argument. And so, many places developers are calling a 1-arity function can be replaced with a 2-arity function.

More succinctly, these two statements are equivalent:

- `foo |> Req.new() |> Req.merge(bar) |> Req.post!()`
- `Req.post!(foo, bar)`

Styler now rewrites the former to the latter, since "less is more" or "code is a liability".

It also rewrites `|> Keyword.merge(bar) |> Req.foo()` to `|> Req.foo(bar)`. **This changes the program's behaviour**, since `Keyword.merge` would overwrite existing values in all cases, whereas `Req` 2-arity functions intelligently deep-merge values for some keys, like `:headers`.

## 1.9.1

### Fix

- fixes rewrites of single-clause case statement with assignment parent (Closes #247, h/t @vasspilka)

## 1.9.0

This was a weird one, but I found myself often writing `to_timeout` with plural units and then having to go back and fix
the code to be singular units instead. Polling a few colleagues, it seemed I wasn't alone in that mistake. So for the first time,
Styler will correct code that would otherwise produce a runtime error, saving you from flow-breaking backtracking.

### Improvements

`to_timeout` improvements:

- translate plural units to singular `to_timeout(hours: 2)` -> `to_timeout(hour: 2)` (plurals are valid ast, but invalid arguments to this function)
- transform when there are multiple keys: `to_timeout(hours: 24 * 1, seconds: 60 * 4)` -> `to_timeout(day: 1, minute: 4)`. **this can introduce runtime bugs** due to duplicate keys, as in the following scenario: `to_timeout(minute: 60, hours: 3)` -> `to_timeout(hour: 1, hour: 3)`

## 1.8.0

### Improvements

Rewrite single-clause case statements to be assignments (h/t 🤖)

```elixir
# before
case foo |> Bar.baz() |> Bop.boop() do
{:ok, widget} ->
x = y
wodget(widget)
end

# after
{:ok, widget} = foo |> Bar.baz() |> Bop.boop()
x = y
wodget(widget)
```

## 1.7.0

Surprising how fast numbers go up when you're following semver.

Two new features, one being a pipe optimization and the other a style-consistency-enforcer in `cond` statements.

### Improvements

- `|> Enum.filter(fun) |> List.first([default])` => `|> Enum.find([default], fun)` (#242, h/t @janpieper)

#### `cond`

If the last clause's left-hand-side is a truthy atom, map literal, or tuple, rewrite it to be `true`

```elixir
# before
cond do
a -> b
c -> d
:else -> e
end

# styled
cond do
a -> b
c -> d
true -> e
end
```

This also helps Styler identify 2-clause conds that can be rewritten to `if/else` more readily, like the following:

```elixir
# before
cond do
a -> b
:else -> c
end

# styled
if a do
b
else
c
end
```

## 1.6.0

That's right, a feature release again so soon!

### Improvements

This version of Styler adds many readability improvements around ExUnit `assert` and `refute`, specifically when working with 1. negations or 2. some `Enum` stdlib functions.

Some of these rewrites are not semantically equivalent due to `refute` passing for both `nil` and `false`.

#### ExUnit assert/refute rewrites

Styler now inverts negated (`!, not`) assert/refute (eg `assert !x` => `refute x`) statements, and further inverts `refute` with boolean comparison operators (`refute x < y` => `assert x >= y`) because non-trivial refutes are harder to reason about \[ _citation needed_ ]. Asserting something is not nil is the same as just asserting that something, so that's gone too now.

These changes are best summarized by the following table:

| before | styled |
|---------------------|-------------------|
| `assert !x` | `refute x` |
| `assert not x` | `refute x` |
| `assert !!x` | `assert x` |
| `assert x != nil` | `assert x` |
| `assert x == nil` | _no change_ |
| `assert is_nil(x)` | _no change_ |
| `assert !is_nil(x)` | `assert x` |
| `assert x not in y` | `refute x in y` |
| refute negated | |
| `refute x` | _no change_ |
| `refute !x` | `assert x` |
| `refute not x` | `assert x` |
| `refute x != y` | `assert x == y` |
| `refute x !== y` | `assert x === y` |
| `refute x != nil` | `assert x == nil` |
| `refute x not in y` | `assert x in y` |
| refute comparison | |
| `refute x < y` | `assert x >= y` |
| `refute x <= y` | `assert x > y` |
| `refute x > y` | `assert x <= y` |
| `refute x >= y` | `assert x < y` |

- `assert Enum.member?(y, x)` -> `assert x in y`
- `assert Enum.find(x, y)` -> `assert Enum.any?(x, y)` (nb. not semantically equivalent in theory, but equivalent in practice)
- `assert Enum.any?(y, & &1 == x)` -> `assert x in y`
- `assert Enum.any?(y, fn var -> var == x end)` -> `assert x in y`

### Fixes

- alias lifting: fix bug lifting in snippets with a single ast node at the root level (like a credo config file) (#240, h/t @defndaines)

## 1.5.1

### Fixes

- alias lifting: handle comments in snippets with no existing directives (#239, h/t @kerryb)

## 1.5.0

### Improvements

- apply aliases to code. if a module is aliased, and then later referenced with its full name, Styler will now shorten it to its alias. (#235, h/t me)
- added `:minimum_supported_elixir_version` configuration to better support libraries using Styler (#231, h/t @maennchen)
- `# styler:sort` will now sort keys for struct/map typespecs (#213, h/t @rojnwa)

### Fixes

- apply alias lifting to snippets with no modules or module directives in them. (#189, @h/t @halfdan)
- fix de-sugaring of syntax-sugared keyword lists whose values weren't atoms in map values (#236, h/t @RisPNG)
- fix mix config sorting mangling floating comment blocks in some cases (#230 again, h/t @ryoung786)

## 1.4.2

### Fixes
Expand Down
Loading
Loading