Skip to content

Commit 4ecbc0e

Browse files
committed
Add usage-rules
1 parent 16cfd91 commit 4ecbc0e

File tree

4 files changed

+692
-1
lines changed

4 files changed

+692
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ erl_crash.dump
2828
*.o
2929
*.beam
3030
*.plt
31+
*.coverdata
3132
erl_crash.dump
3233
.DS_Store
3334
._*
@@ -37,4 +38,3 @@ erl_crash.dump
3738
/priv
3839
.sobelow*
3940
/config
40-
Elixir*
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Third-Party Licenses
2+
3+
This directory contains usage rules sourced from the following open-source projects.
4+
5+
---
6+
7+
## UsageRules
8+
9+
**Source**: https://github.com/ash-project/usage_rules
10+
11+
**Files affected**: `elixir.md` (lines 1-69)
12+
13+
**License**: MIT License
14+
15+
```
16+
MIT License
17+
18+
Copyright (c) 2025 usage_rules contributors
19+
20+
Permission is hereby granted, free of charge, to any person obtaining a copy
21+
of this software and associated documentation files (the "Software"), to deal
22+
in the Software without restriction, including without limitation the rights
23+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24+
copies of the Software, and to permit persons to whom the Software is
25+
furnished to do so, subject to the following conditions:
26+
27+
The above copyright notice and this permission notice shall be included in all
28+
copies or substantial portions of the Software.
29+
30+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
SOFTWARE.
37+
```
38+
39+
---
40+
41+
## Phoenix Framework
42+
43+
**Source**: https://github.com/phoenixframework/phoenix
44+
45+
**Files affected**: `elixir.md` (lines 71-184)
46+
47+
**License**: MIT License
48+
49+
```
50+
MIT License
51+
52+
Copyright (c) 2014 Chris McCord
53+
54+
Permission is hereby granted, free of charge, to any person obtaining a copy
55+
of this software and associated documentation files (the "Software"), to deal
56+
in the Software without restriction, including without limitation the rights
57+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
58+
copies of the Software, and to permit persons to whom the Software is
59+
furnished to do so, subject to the following conditions:
60+
61+
The above copyright notice and this permission notice shall be included in all
62+
copies or substantial portions of the Software.
63+
64+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
65+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
66+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
67+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
68+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
69+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
70+
SOFTWARE.
71+
```

usage-rules/elixir.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<!--
2+
The following rules are sourced from [UsageRules](https://github.com/ash-project/usage_rules),
3+
with modifications and additions.
4+
5+
SPDX-FileCopyrightText: 2025 usage_rules contributors <https://github.com/ash-project/usage_rules/graphs.contributors>
6+
7+
SPDX-License-Identifier: MIT
8+
-->
9+
# Elixir Core Usage Rules
10+
11+
## Pattern Matching
12+
13+
- Use pattern matching over conditional logic when possible
14+
- Prefer to match on function heads instead of using `if`/`else` or `case` in function bodies
15+
- `%{}` matches ANY map, not just empty maps. Use `map_size(map) == 0` guard to check for truly empty maps
16+
17+
## Error Handling
18+
19+
- Use `{:ok, result}` and `{:error, reason}` tuples for operations that can fail
20+
- Avoid raising exceptions for control flow
21+
- Use `with` for chaining operations that return `{:ok, _}` or `{:error, _}`
22+
- Bang functions (`!`) that explicitly raise exceptions on failure are acceptable (e.g., `File.read!/1`, `String.to_integer!/1`)
23+
- Avoid rescuing exceptions unless for a very specific case (e.g., cleaning up resources, logging critical errors)
24+
25+
## Common Mistakes to Avoid
26+
27+
- Elixir has no `return` statement, nor early returns. The last expression in a block is always returned.
28+
- Don't use `Enum` functions on large collections when `Stream` is more appropriate
29+
- Avoid nested `case` statements - refactor to a single `case`, `with` or separate functions
30+
- Don't use `String.to_atom/1` on user input (memory leak risk)
31+
- Lists and enumerables cannot be indexed with brackets. Use pattern matching or `Enum` functions
32+
- Prefer `Enum` functions like `Enum.reduce` over recursion
33+
- When recursion is necessary, prefer to use pattern matching in function heads for base case detection
34+
- Using the process dictionary is typically a sign of unidiomatic code
35+
- Only use macros if explicitly requested
36+
- There are many useful standard library functions, prefer to use them where possible
37+
38+
## Function Design
39+
40+
- Use guard clauses: `when is_binary(name) and byte_size(name) > 0`
41+
- Prefer multiple function clauses over complex conditional logic
42+
- Name functions descriptively: `calculate_total_price/2` not `calc/2`
43+
- Predicate function names should not start with `is` and should end in a question mark.
44+
- Names like `is_thing` should be reserved for guards
45+
46+
## Data Structures
47+
48+
- Use structs over maps when the shape is known: `defstruct [:name, :age]`
49+
- Prefer keyword lists for options: `[timeout: 5000, retries: 3]`
50+
- Use maps for dynamic key-value data
51+
- Prefer to prepend to lists `[new | list]` not `list ++ [new]`
52+
53+
## Mix Tasks
54+
55+
- Use `mix help` to list available mix tasks
56+
- Use `mix help task_name` to get docs for an individual task
57+
- Read the docs and options fully before using tasks
58+
59+
## Testing
60+
61+
- Run tests in a specific file with `mix test test/my_test.exs` and a specific test with the line number `mix test path/to/test.exs:123`
62+
- Limit the number of failed tests with `mix test --max-failures n`
63+
- Use `@tag` to tag specific tests, and `mix test --only tag` to run only those tests
64+
- Use `assert_raise` for testing expected exceptions: `assert_raise ArgumentError, fn -> invalid_function() end`
65+
- Use `mix help test` to for full documentation on running tests
66+
67+
## Debugging
68+
69+
- Use `dbg/1` to print values while debugging. This will display the formatted value and other relevant information in the console.
70+
71+
<!--
72+
The following rules are sourced from [Phoenix Framework](https://github.com/phoenixframework/phoenix),
73+
with modifications and additions.
74+
75+
Copyright (c) 2014 Chris McCord, licensed under the MIT License.
76+
-->
77+
## Elixir guidelines
78+
79+
- Elixir lists **do not support index based access via the access syntax**
80+
81+
**Never do this (invalid)**:
82+
83+
i = 0
84+
mylist = ["blue", "green"]
85+
mylist[i]
86+
87+
Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie:
88+
89+
i = 0
90+
mylist = ["blue", "green"]
91+
Enum.at(mylist, i)
92+
93+
- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc
94+
you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:
95+
96+
# INVALID: we are rebinding inside the `if` and the result never gets assigned
97+
if connected?(socket) do
98+
socket = assign(socket, :val, val)
99+
end
100+
101+
# VALID: we rebind the result of the `if` to a new variable
102+
socket =
103+
if connected?(socket) do
104+
assign(socket, :val, val)
105+
end
106+
107+
- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors
108+
- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets
109+
- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package)
110+
- Don't use `String.to_atom/1` on user input (memory leak risk)
111+
- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards
112+
- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)`
113+
- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option
114+
115+
- The `in` operator in guards requires a compile-time known value on the right side (literal list or range)
116+
117+
**Never do this (invalid)**: using a variable which is unknown at compile time
118+
119+
def t(x, y) when x in y, do: {x, y}
120+
121+
This will raise `ArgumentError: invalid right argument for operator "in", it expects a compile-time proper list or compile-time range on the right side when used in guard expressions`
122+
123+
**Valid**: use a known value for the list or range
124+
125+
def t(x, y) when x in [1, 2, 3], do: {x, y}
126+
def t(x, y) when x in 1..10, do: {x, y}
127+
128+
- In tests, avoid using `assert` with pattern matching when the expected value is fully known. Use direct equality comparison instead for clearer test failures
129+
130+
**Avoid**:
131+
132+
assert {:ok, ^value} = testing()
133+
assert {:error, :not_found} = fetch()
134+
135+
**Prefer**:
136+
137+
assert testing() == {:ok, value}
138+
assert fetch() == {:error, :not_found}
139+
140+
**Exception**: Pattern matching is acceptable when you only want to assert part of a complex structure
141+
142+
# OK: asserting only specific fields of a large struct/map
143+
assert {:ok, %{id: ^id}} = get_order()
144+
145+
- In tests, avoid duplicating test data across multiple tests. Use constants, fixture files, or private fixture functions instead
146+
147+
**Avoid**: Duplicating test data
148+
149+
test "validates user email" do
150+
assert valid_email?("user@example.com")
151+
end
152+
153+
test "creates user" do
154+
assert create_user("user@example.com")
155+
end
156+
157+
**Prefer**: Use module attributes for constants or fixture functions
158+
159+
@valid_email "user@example.com"
160+
161+
test "validates user email" do
162+
assert valid_email?(@valid_email)
163+
end
164+
165+
test "creates user" do
166+
assert create_user(@valid_email)
167+
end
168+
169+
For complex data structures, create fixture functions:
170+
171+
defp user_fixture(attrs \\ %{}) do
172+
%User{
173+
name: "John Doe",
174+
email: "john@example.com",
175+
age: 30
176+
}
177+
|> Map.merge(attrs)
178+
end
179+
180+
## Mix guidelines
181+
182+
- Read the docs and options before using tasks (by using `mix help task_name`)
183+
- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed`
184+
- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason

0 commit comments

Comments
 (0)