goahead

command module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 21, 2026 License: MIT Imports: 6 Imported by: 0

README

GoAhead → Compile-time Code Generation for Go

GoAhead is a compile-time code generation tool for Go. It replaces placeholder comments with computed values at build time.

CodeQL Advanced Go Report Card GitHub Issues or Pull Requests GitHub last commit GitHub License

GoAhead

Highlights

  • Works with Go commands via -toolexec (build, test, run, generate)
  • Placeholder replacement keeps surrounding expressions intact
  • Supports parameterised helpers, raw Go expressions, and simple type inference
  • Supports standard-library packages directly (strings, strconv, os, http, etc.)
  • Hierarchical function inheritance: subdirectories can shadow parent functions
  • Supports variadic functions, constants, type definitions, and complex expressions
  • Cross-platform compatible (Linux, macOS, Windows)
  • Includes examples and an integration test suite

Installation

go install github.com/AeonDave/goahead@latest

Quick Start

1. Declare build-time helpers

Helpers live in files marked with both build tags. They are excluded from normal compilation but executed by GoAhead.

//go:build exclude
//go:ahead functions

package helpers

import "strings"

func welcome(name string) string {
    return "Hello, " + strings.ToUpper(name)
}
2. Reference helpers + build
package main

//:welcome:"gopher"
var greeting = ""
go build -toolexec="goahead" ./...

Placeholder Grammar Reference

Basic Syntax
//:functionName[:arg1[:arg2[:argN]]]

The placeholder comment must appear on the line immediately before the target statement or expression.

Argument Types
Type Syntax Examples
String Double quotes "hello", "production", ""
Raw String Backticks `raw\nstring`
Integer Unquoted number 42, -17, 0xFF, 0o755, 0b1010
Float Decimal or scientific 3.14, -2.5, 1.5e10
Boolean Unquoted true, false, True, False
Raw Expression Prefix with = =strings.TrimSpace(input)
Number Formats

GoAhead supports all Go number literal formats:

//:process:42          // Decimal
//:process:0xFF        // Hexadecimal  
//:process:0o755       // Octal
//:process:0b1010      // Binary
//:process:-3.14       // Negative float
//:process:1.5e10      // Scientific notation
Raw Expressions

Prefix an argument with = to pass a raw Go expression that will be evaluated at build time:

//:hash:=strings.TrimSpace(input)
//:detect:=[]byte{0x89, 0x50, 0x4E, 0x47}
//:process:=map[string]int{"a": 1, "b": 2}

Raw expressions can contain colons (e.g., in map literals, struct literals, slice expressions) - GoAhead correctly parses nested brackets.

Variadic Functions

GoAhead fully supports variadic helper functions:

// Helper definition
func joinAll(sep string, parts ...string) string {
    return strings.Join(parts, sep)
}

func sum(nums ...int) int {
    total := 0
    for _, n := range nums { total += n }
    return total
}
// Usage - pass any number of arguments
//:joinAll:"-":"a":"b":"c":"d"
result = ""  // → "a-b-c-d"

//:sum:1:2:3:4:5
total = 0    // → 15
Placeholder Targets

The placeholder replaces the first matching literal in the following statement:

Return Type Placeholder Example Target
string "" or ` ` msg = ""
int 0 count = 0
float64 0.0 rate = 0.0
bool false enabled = false
uint 0xff (hex) flags = 0xff

Helper File Features

Constants and Variables

Helper files can define constants and variables at package level:

//go:build exclude
//go:ahead functions

package helpers

const (
    Prefix    = "APP_"
    Separator = "::"
)

var defaultTimeout = 30

func prefixed(key string) string {
    return Prefix + key
}
Custom Types

Define and use custom types within helper files:

type Status int

const (
    StatusPending Status = iota
    StatusActive
    StatusCompleted
)

func getDefaultStatus() Status {
    return StatusActive
}

func statusName(s Status) string {
    names := []string{"Pending", "Active", "Completed"}
    return names[s]
}
Multiple Helper Files

GoAhead scans the entire directory tree and resolves functions using hierarchical inheritance:

  • Functions resolve bottom-up from source file's directory to root
  • A source file "sees" functions from its own directory + all ancestors
  • Local shadows parent: A subdirectory's function overrides the parent's function of the same name
  • Siblings are isolated: pkg1/ cannot see pkg2/'s functions (only common ancestors)
project/
├── helpers.go        # version() -> "1.0.0", common() -> "shared"
├── main.go           # uses version() → "1.0.0", common() → "shared"
├── pkg1/
│   ├── helpers.go    # version() -> "2.0.0-pkg1"  ← shadows root
│   └── main.go       # uses version() → "2.0.0-pkg1", common() → "shared"
└── pkg2/
    └── main.go       # uses version() → "1.0.0", common() → "shared"

Console output shows which helper file was used:

[goahead] Replaced in pkg1/main.go: version() -> "2.0.0-pkg1" (from pkg1/helpers.go)
[goahead] WARNING: Function 'version' in pkg1/helpers.go shadows function in helpers.go

⚠️ Duplicate function names in the same directory cause a fatal error - GoAhead will exit with an error showing both file locations.


Standard Library Integration

Auto-detected Packages

GoAhead automatically resolves common standard library packages:

//:strings.ToUpper:"hello"
upper = ""  // → "HELLO"

//:os.Getenv:"HOME"
home = ""   // → "/home/user"

//:strconv.Itoa:42
str = ""    // → "42"

//:http.DetectContentType:=[]byte("PNG data")
mime = ""   // → "application/octet-stream"

Function Injection

GoAhead can inject entire functions from helper files into your source code. This is useful for obfuscation scenarios where you need both an encoding function (executed at build time) and a decoding function (included in runtime).

Syntax

Inject markers must appear above an interface declaration and reference methods that exist in that interface:

//:inject:MethodName1
//:inject:MethodName2
type MyInterface interface {
    MethodName1(args) returnType
    MethodName2(args) returnType
}

GoAhead will:

  1. Validate that each method exists in the interface
  2. Find the implementation in helper files
  3. Inject the function code at the end of the file
  4. Remove the markers
Example: String Obfuscation

Helper file (helpers.go):

//go:build exclude
//go:ahead functions

package main

const xorKey byte = 0x42

func Shadow(s string) string {
    result := ""
    for _, c := range s {
        result += string(byte(c) ^ xorKey)
    }
    return result
}

func Unshadow(s string) string {
    result := ""
    for _, c := range s {
        result += string(byte(c) ^ xorKey)
    }
    return result
}

Source file (main.go):

package main

import "fmt"

//:Shadow:"secret password"
var encrypted = ""

//:inject:Unshadow
type Decoder interface {
    Unshadow(s string) string
}

func main() {
    fmt.Println(Unshadow(encrypted))
}

After processing:

package main

import "fmt"

var encrypted = "1'!0'6r2#115/0&"

type Decoder interface {
    Unshadow(s string) string
}

func main() {
    fmt.Println(Unshadow(encrypted))
}

const xorKey byte = 0x42

func Unshadow(s string) string {
    result := ""
    for _, c := range s {
        result += string(byte(c) ^ xorKey)
    }
    return result
}
What Gets Injected

When you use //:inject:FunctionName above an interface:

  1. Validates that the method exists in the interface (error if not)
  2. Removes any existing function with the same name (to allow updates)
  3. Copies the function from the helper file to the end of the source file
  4. Adds required imports used by the function
  5. Includes dependencies (constants, variables, types) that the function uses
  6. Includes helper-to-helper dependencies (other helper functions called by the injected function)
  7. Respects hierarchy - uses the function from the nearest helper file
  8. Preserves the marker - the //:inject: comment stays in the source file
Subsequent Builds

Unlike placeholder comments which remain static after first replacement, inject markers work differently:

  • Marker stays: The //:inject: comment is never removed
  • Function is replaced: On each build, the existing injected code block is removed and re-injected
  • Updates propagate: If you change the helper file, the next build will update the injected function
  • Generated code marker: Injected code is wrapped in standard Go generated code comments

This makes it easy to iterate on implementations (e.g., changing encryption algorithms) without manual editing.

Error Conditions
  • Method not in interface: If //:inject:Foo is used but Foo is not a method in the interface, GoAhead exits with an error
  • Markers not followed by interface: If inject markers are not immediately followed by an interface declaration, GoAhead exits with an error
  • Implementation not found: If no helper file contains the function, GoAhead exits with an error

CGO Compatibility

GoAhead works correctly with CGO files:

package main

/*
#include <stdio.h>
#cgo LDFLAGS: -lm

// C comments with colons: like:this are preserved
*/
import "C"

var (
    //:getLibVersion
    libVersion = ""
)

CGO preambles, directives, and comments are fully preserved.


Command-line Reference

goahead -dir=<path> [-verbose] [-version]
Flag Description
-dir Directory to process (default .)
-verbose Print detailed progress information
-version Print the tool version and exit
-help Show the extended usage banner
Environment Variables
GOAHEAD_VERBOSE=1   # Force verbose output when invoked via toolexec

  • Ensure helper file has both //go:build exclude and //go:ahead functions
  • Check function name matches exactly (case-sensitive)
  • Verify argument count matches function signature
Type mismatch
  • Ensure the literal placeholder matches the return type
  • Use 0 for int, 0.0 for float, "" for string, false for bool
Colon in argument
  • Wrap strings in quotes: "http://localhost:8080"
  • For raw expressions, use =: //:process:=map[string]int{"a": 1}
CGO errors
  • GoAhead preserves CGO preambles - check C code syntax separately
  • Ensure import "C" appears alone after the preamble

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL