1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
|
# 🚀 Iku
> Grammar-Aware Code Formatter: Structure through separation
Let your code breathe!
Iku is a grammar-based formatter that enforces consistent blank-line placement by statement and declaration type. It supports Go, JavaScript, TypeScript, JSX, and TSX.
## Philosophy
Code structure should be visually apparent from its formatting. Iku groups statements by grammatical type and separates them with blank lines, making the code flow easier to read at a glance.
## Rules
1. **Same type means no blank line**: Consecutive statements of the same type stay together
2. **Different type means blank line**: Transitions between statement types get visual separation
3. **Scoped constructs get blank lines**: `if`, `for`, `switch`, `select`, `func`, `type struct`, `type interface` always have blank lines around them
4. **Declarations use token types**: `var`, `const`, `type`, `func`, `import` are distinguished by their keyword, not grouped as generic declarations
## How It Works
For Go files, Iku applies standard Go formatting (via [go/format](https://pkg.go.dev/go/format)) first, then adds its grammar-based blank-line rules on top. Your code gets `go fmt` output plus structural separation.
For JavaScript and TypeScript files (`.js`, `.ts`, `.jsx`, `.tsx`), Iku uses a heuristic line-based analyser that classifies statements by keyword (`function`, `class`, `if`, `for`, `try`, etc.) and applies the same blank-line rules.
## Installation
```bash
go install github.com/Fuwn/iku@latest
```
Or run with Nix:
```bash
nix run github:Fuwn/iku
```
## Usage
```bash
# Format stdin
echo 'package main ...' | iku
# Format and print to stdout
iku file.go
iku component.tsx
# Format in-place
iku -w file.go
iku -w src/
# Format entire directory (Go, JS, TS, JSX, TSX)
iku -w .
# List files that need formatting
iku -l .
# Show diff
iku -d file.go
```
### Flags
| Flag | Description |
|------|-------------|
| `-w` | Write result to file instead of stdout |
| `-l` | List files whose formatting differs |
| `-d` | Display diffs instead of rewriting |
| `--version` | Print version |
## Configuration
Iku looks for `.iku.json` or `iku.json` in the current working directory.
```json
{
"comment_mode": "follow",
"group_single_line_functions": false
}
```
All fields are optional. Omitted fields use their defaults.
### `comment_mode`
Controls how comments interact with blank-line insertion. Default: `"follow"`.
| Mode | Behaviour |
|------|-----------|
| `follow` | Comments attach to the **next** statement. The blank line goes **before** the comment. |
| `precede` | Comments attach to the **previous** statement. The blank line goes **after** the comment. |
| `standalone` | Comments are independent. Blank lines are placed strictly by statement rules. |
### `group_single_line_functions`
When `true`, consecutive single-line function declarations of the same type are kept together without blank lines. Default: `false`.
```go
// group_single_line_functions = true
func Base() string { return baseDirectory }
func Config() string { return configFile }
// group_single_line_functions = false (default)
func Base() string { return baseDirectory }
func Config() string { return configFile }
```
## Examples
### Before
```go
package main
func main() {
x := 1
y := 2
var config = loadConfig()
defer cleanup()
defer closeDB()
if err != nil {
return err
}
if x > 0 {
process(x)
}
go worker()
return nil
}
```
### After
```go
package main
func main() {
x := 1
y := 2
var config = loadConfig()
defer cleanup()
defer closeDB()
if err != nil {
return err
}
if x > 0 {
process(x)
}
go worker()
return nil
}
```
Notice how:
- `x := 1` and `y := 2` (both `AssignStmt`) stay together
- `var config` (`DeclStmt`) gets separated from assignments
- `defer` statements stay grouped together
- Each `if` statement gets a blank line before it (scoped statement)
- `go worker()` (`GoStmt`) is separated from the `if` above
- `return` (`ReturnStmt`) is separated from the `go` statement
### Top-Level Declarations
```go
// Before
package main
type Config struct {
Name string
}
type ID int
type Name string
var defaultConfig = Config{}
var x = 1
func main() {
run()
}
func run() {
process()
}
// After
package main
type Config struct {
Name string
}
type ID int
type Name string
var defaultConfig = Config{}
var x = 1
func main() {
run()
}
func run() {
process()
}
```
Notice how:
- `type Config struct` is scoped (has braces), so it gets a blank line
- `type ID int` and `type Name string` are unscoped type aliases, so they group together
- `var defaultConfig` and `var x` are unscoped, so they group together
- `func main()` and `func run()` are scoped, so each gets a blank line
### Switch Statements
```go
// Before
func process(x int) {
result := compute(x)
switch result {
case 1:
handleOne()
if needsExtra {
doExtra()
}
case 2:
handleTwo()
}
cleanup()
}
// After
func process(x int) {
result := compute(x)
switch result {
case 1:
handleOne()
if needsExtra {
doExtra()
}
case 2:
handleTwo()
}
cleanup()
}
```
## AST Node Types
For reference, here are common Go statement types that Iku distinguishes:
| Type | Examples |
|------|----------|
| `*ast.AssignStmt` | `x := 1`, `x = 2` |
| `*ast.DeclStmt` | `var x = 1` |
| `*ast.ExprStmt` | `fmt.Println()`, `doSomething()` |
| `*ast.ReturnStmt` | `return x` |
| `*ast.IfStmt` | `if x > 0 { }` |
| `*ast.ForStmt` | `for i := 0; i < n; i++ { }` |
| `*ast.RangeStmt` | `for k, v := range m { }` |
| `*ast.SwitchStmt` | `switch x { }` |
| `*ast.SelectStmt` | `select { }` |
| `*ast.DeferStmt` | `defer f()` |
| `*ast.GoStmt` | `go f()` |
| `*ast.SendStmt` | `ch <- x` |
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
|