Grammar-Aware Go Formatter: Structure through separation
Let your code breathe!
Iku is a grammar-based Go formatter that enforces consistent blank-line placement by statement and declaration type.
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
Iku applies standard Go formatting (via go/format) first (formatter.go#29), then adds its grammar-based blank-line rules on top. Your code gets go fmt output plus structural separation.
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
# Format in-place
iku -w file.go
# Format entire directory
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 |
--comments |
Comment attachment mode: follow, precede, standalone |
--version |
Print version |
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.