Skip to content
/ iku Public

πŸš€ Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)

License

Notifications You must be signed in to change notification settings

Fuwn/iku

Repository files navigation

πŸš€ 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) 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

go install github.com/Fuwn/iku@latest

Or run with Nix:

nix run github:Fuwn/iku

Usage

# 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.

{
  "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.

// 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

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

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

// 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

// 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

This project is licensed under the GNU General Public License v3.0.

About

πŸš€ Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)

Topics

Resources

License

Stars

Watchers

Forks