Skip to content

Commit 239ba7f

Browse files
WIP: Merge upstream v11.0.0 (has conflicts)
2 parents 4fdba98 + 7ebfbcc commit 239ba7f

371 files changed

Lines changed: 16082 additions & 11295 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
name: elixir-clause-grouping
3+
description: Use when refactoring Elixir multi-clause functions, extracting helper functions, or fixing Credo readability warnings caused by placing `defp` helpers between clauses of the same function. Keeps function clauses contiguous and moves helpers below the full clause group.
4+
---
5+
6+
## Overview
7+
8+
In Elixir modules, all clauses of the same function should stay together. Inserting a `defp` helper between clauses of a `def` or `defp` makes the function harder to read and can trigger Credo readability warnings. When shared logic needs to be extracted, keep the original clause group contiguous and place the helper after the full group.
9+
10+
## When to Use
11+
12+
- When refactoring a multi-clause `def` or `defp`
13+
- When extracting duplicated logic from multiple function clauses
14+
- When addressing Credo warnings about clause grouping or readability
15+
- When editing controller, view, or context modules with several clauses of the same function
16+
- During review when a helper was added in the middle of another function's clauses
17+
18+
## Core Rule
19+
20+
- Keep all clauses of the same function contiguous
21+
- Do not place `defp` helpers between clauses of another function
22+
- Extract shared logic into a helper placed after the full clause group
23+
24+
## Anti-Pattern
25+
26+
```elixir
27+
def decoded_input_data(%Transaction{to_address: nil}, _, _, _, _), do: {:error, :no_to_address}
28+
29+
defp decode_input_data_with_fallback(data, abi, input, hash, skip_sig_provider?, options, methods_map, abi_map) do
30+
...
31+
end
32+
33+
def decoded_input_data(%Transaction{to_address: %NotLoaded{}}, _, _, _, _), do: {:error, :contract_not_verified, []}
34+
```
35+
36+
This splits the `decoded_input_data/5` clause group and makes the function harder to scan.
37+
38+
## Preferred Pattern
39+
40+
```elixir
41+
def decoded_input_data(%Transaction{to_address: nil}, _, _, _, _), do: {:error, :no_to_address}
42+
43+
def decoded_input_data(%Transaction{to_address: %NotLoaded{}}, _, _, _, _), do: {:error, :contract_not_verified, []}
44+
45+
def decoded_input_data(%Transaction{to_address: %{smart_contract: smart_contract}} = transaction, skip_sig_provider?, options, methods_map, abi_map) do
46+
...
47+
end
48+
49+
defp decode_input_data_with_fallback(data, abi, input, hash, skip_sig_provider?, options, methods_map, abi_map) do
50+
...
51+
end
52+
```
53+
54+
## Refactoring Checklist
55+
56+
1. Identify every clause of the function being edited.
57+
2. Keep those clauses adjacent to each other.
58+
3. Extract shared logic only after the full clause group.
59+
4. Re-check that no unrelated `def` or `defp` appears inside the group.
60+
5. Run formatting after the refactor.
61+
62+
## Notes
63+
64+
- This applies to both public and private multi-clause functions.
65+
- If a helper is only used by one clause group, place it immediately after that group.
66+
- Preserving clause grouping is preferred even when the extracted helper is small.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
name: update-common-blockscout-env
3+
description: Ensure every newly introduced environment variable is also added to docker-compose/envs/common-blockscout.env so local Docker setups stay aligned with runtime configuration.
4+
---
5+
6+
## Overview
7+
8+
This skill keeps environment-variable documentation and defaults in sync for Docker users.
9+
10+
When adding or changing runtime env vars (for example in config/runtime.exs), also update docker-compose/envs/common-blockscout.env in the same task.
11+
12+
## Mandatory Rule
13+
14+
- Every new env variable introduced in code/config must be added to docker-compose/envs/common-blockscout.env.
15+
- Do not postpone this to a follow-up task.
16+
17+
## How To Apply
18+
19+
1. Identify newly added env vars in changed files (typically config/runtime.exs, config/*.exs, or modules reading System.get_env/1-2).
20+
2. Add each variable to docker-compose/envs/common-blockscout.env.
21+
3. Place it in the most relevant section (for example API flags near other API_* variables).
22+
4. Prefer non-breaking defaults:
23+
- Use a commented example line for optional flags (for example # MY_FLAG=false).
24+
- Use an uncommented value only when the project convention requires a default to be active.
25+
5. Keep naming and formatting consistent with existing entries.
26+
27+
## Checklist
28+
29+
- New env var exists in code.
30+
- Matching entry exists in docker-compose/envs/common-blockscout.env.
31+
- Placement is logical and discoverable.
32+
- Default value does not change behavior unexpectedly.
33+
34+
## Example
35+
36+
If code adds:
37+
38+
- DISABLE_TRANSACTIONS_BENS_PRELOAD
39+
40+
Then docker-compose/envs/common-blockscout.env should include:
41+
42+
- # DISABLE_TRANSACTIONS_BENS_PRELOAD=false
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
name: with-to-case-refactor
3+
description: Replace `with` expressions that contain only a single `<-` clause and an `else` branch with a `case` expression. This addresses the Credo warning "with contains only one <- clause and an else branch, consider using case instead" and produces cleaner, more idiomatic Elixir code.
4+
---
5+
6+
## Overview
7+
8+
Elixir's `with` construct is designed for chaining multiple pattern-matching steps. When only one `<-` clause is present alongside an `else` branch, `with` adds no value over a plain `case`. Credo flags this as:
9+
10+
```
11+
[R] → `with` contains only one <- clause and an `else` branch, consider using `case` instead
12+
```
13+
14+
Always prefer `case` in this situation.
15+
16+
## When to Use
17+
18+
- When a `with` expression has exactly one `<-` clause and one or more `else` arms.
19+
- When refactoring code to address the Credo `Credo.Check.Refactor.WithClauses` warning.
20+
21+
## Anti-Pattern (Avoid)
22+
23+
```elixir
24+
# ❌ BAD: single-clause with/else — should be a case
25+
with {:ok, response} <- json_rpc(params, opts) do
26+
process(response)
27+
else
28+
{:error, reason} ->
29+
Logger.error("RPC failed: #{inspect(reason)}")
30+
:error
31+
end
32+
```
33+
34+
```elixir
35+
# ❌ BAD: single-clause with/else wrapping a nested case
36+
with {:ok, response} <- json_rpc(params, opts) do
37+
case parse(response) do
38+
{:ok, value} -> value
39+
_ -> :error
40+
end
41+
else
42+
{:error, reason} ->
43+
Logger.error("RPC failed: #{inspect(reason)}")
44+
:error
45+
end
46+
```
47+
48+
## Best Practice (Use Instead)
49+
50+
```elixir
51+
# ✅ GOOD: flat case replaces with/else
52+
case json_rpc(params, opts) do
53+
{:ok, response} ->
54+
process(response)
55+
56+
{:error, reason} ->
57+
Logger.error("RPC failed: #{inspect(reason)}")
58+
:error
59+
end
60+
```
61+
62+
```elixir
63+
# ✅ GOOD: nested case is fine when the outer with is replaced
64+
case json_rpc(params, opts) do
65+
{:ok, response} ->
66+
case parse(response) do
67+
{:ok, value} -> value
68+
_ -> :error
69+
end
70+
71+
{:error, reason} ->
72+
Logger.error("RPC failed: #{inspect(reason)}")
73+
:error
74+
end
75+
```
76+
77+
## Transformation Rules
78+
79+
1. Move the expression on the right-hand side of `<-` to become the subject of `case`.
80+
2. Turn the left-hand side of `<-` into the matching branch of `case`.
81+
3. Move the body of the `with` block as the body of that `case` branch.
82+
4. Move each arm of the `else` block as additional `case` branches.
83+
5. Remove the `with`/`else`/`end` wrapper.
84+
85+
## Real-World Example (from this codebase)
86+
87+
### Before
88+
89+
```elixir
90+
with {:ok, response} <-
91+
params
92+
|> Map.merge(%{id: 0})
93+
|> Nonce.request()
94+
|> json_rpc(json_rpc_named_arguments) do
95+
case Nonce.from_response(%{id: 0, result: response}, id_to_params) do
96+
{:ok, %{nonce: 0}} -> handle_zero_nonce(...)
97+
{:ok, %{nonce: nonce}} when nonce > 0 -> handle_nonzero_nonce(...)
98+
_ -> retry(...)
99+
end
100+
else
101+
{:error, reason} ->
102+
Logger.error("Error: #{inspect(reason)}")
103+
retry(...)
104+
end
105+
```
106+
107+
### After
108+
109+
```elixir
110+
case params
111+
|> Map.merge(%{id: 0})
112+
|> Nonce.request()
113+
|> json_rpc(json_rpc_named_arguments) do
114+
{:ok, response} ->
115+
case Nonce.from_response(%{id: 0, result: response}, id_to_params) do
116+
{:ok, %{nonce: 0}} -> handle_zero_nonce(...)
117+
{:ok, %{nonce: nonce}} when nonce > 0 -> handle_nonzero_nonce(...)
118+
_ -> retry(...)
119+
end
120+
121+
{:error, reason} ->
122+
Logger.error("Error: #{inspect(reason)}")
123+
retry(...)
124+
end
125+
```
126+
127+
## Notes
128+
129+
- If the `with` has **two or more** `<-` clauses, keep it as `with`; this refactor only applies to the single-clause case.
130+
- If there is no `else` branch at all, `with` is also acceptable for a single clause — but a `case` is still clearer and preferred.
131+
- After refactoring, run `mix format` to ensure correct indentation.

0 commit comments

Comments
 (0)