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.
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.
- Same type means no blank line: Consecutive statements of the same type stay together
- Different type means blank line: Transitions between statement types get visual separation
- Scoped constructs get blank lines:
if,for,switch,select,func,type struct,type interfacealways have blank lines around them - Declarations use token types:
var,const,type,func,importare distinguished by their keyword, not grouped as generic declarations
For Go files, Iku applies standard Go formatting (via 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.
go install github.com/Fuwn/iku@latestOr run with Nix:
nix run github:Fuwn/iku# 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| 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 |
Iku looks for .iku.json or iku.json in the current working directory.
{
"comment_mode": "follow",
"group_single_line_functions": false
}All fields are optional. Omitted fields use their defaults.
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. |
When true, consecutive single-line function declarations of the same type are kept together without blank lines. Default: false.
// 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 }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
}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 := 1andy := 2(bothAssignStmt) stay togethervar config(DeclStmt) gets separated from assignmentsdeferstatements stay grouped together- Each
ifstatement gets a blank line before it (scoped statement) go worker()(GoStmt) is separated from theifabovereturn(ReturnStmt) is separated from thegostatement
// 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 structis scoped (has braces), so it gets a blank linetype ID intandtype Name stringare unscoped type aliases, so they group togethervar defaultConfigandvar xare unscoped, so they group togetherfunc main()andfunc run()are scoped, so each gets a blank line
// 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()
}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 |
This project is licensed under the GNU General Public License v3.0.