Open source · MIT · Pure Go
go-fAST
A super-fast JavaScript parser, transformer and code generator for Go.
Turn JavaScript into a typed AST, rewrite it in plain Go, and print it straight back to source. No Node process, no engine to embed - it compiles into your binary and runs anywhere Go does. It is the engine we reach for when we take apart obfuscated anti-bot scripts, and it is free for anyone to use.
$ go get github.com/t14raptor/go-fast
Benchmarks
Rust-class speed, in pure Go
On a full parse, transform and generate cycle, go-fAST runs in the same league as the fastest Rust toolchains, and comfortably more than 10x ahead of Babel or the next Go parser. Lower is better.
| Tool | Lang | Parse | Traverse | Codegen | Full pipeline |
|---|---|---|---|---|---|
| OXC | Rust | 4.18ms | 0.59ms | 1.55ms | 6.47ms |
| go-fAST (PGO) | Go | 4.30ms | 1.13ms | 2.05ms | 8.00ms |
| SWC | Rust | 7.85ms | 0.71ms | 3.90ms | 12.50ms |
| @babel | JS | 40.5ms | 9.9ms | 22.6ms | 79.7ms |
| tdewolff/parse | Go | 95.04ms | 1.92ms | 5.70ms | 104.82ms |
Full parse, transform and code generation over a representative JavaScript file; the bar chart is scaled to the slowest tool. go-fAST is built with profile-guided optimisation (PGO). Times vary with input and hardware.
Three things, done fast
Parse. Transform. Generate.
A single, cohesive toolkit for the whole round-trip from JavaScript source to a typed tree and back again.
Source to AST
One call - parser.ParseFile - turns a source string into a fully typed *ast.Program, ready to inspect or rewrite. It copes with the messy, modern JavaScript you actually meet in the wild.
Walk and rewrite
Embed ast.NoopVisitor, override the handful of node types you care about, and mutate the tree in place. Everything else is walked for you, so a transform stays as small as the change it makes.
AST back to source
Print any node back to JavaScript: readable with generator.Generate, compact with GenerateMinified, or shaped to your own rules through GenerateWithOptions.
In practice
The full round-trip in one file
Parse a snippet, walk the tree with a visitor that renames an identifier, then print the result. That is the entire shape of a go-fAST program.
package main
import (
"fmt"
"github.com/t14raptor/go-fast/ast"
"github.com/t14raptor/go-fast/generator"
"github.com/t14raptor/go-fast/parser"
)
// renamer rewrites every identifier named "a" to "answer".
type renamer struct {
ast.NoopVisitor
}
func (r *renamer) VisitIdentifier(n *ast.Identifier) {
if n.Name == "a" {
n.Name = "answer"
}
n.VisitChildrenWith(r.V)
}
func main() {
// 1. Parse JavaScript into a typed AST.
program, err := parser.ParseFile(`const a = 6 * 7; console.log(a);`)
if err != nil {
panic(err)
}
// 2. Walk the tree with a visitor and rewrite it.
v := &renamer{}
v.V = v
program.VisitWith(v)
// 3. Print the transformed source back out.
fmt.Println(generator.Generate(program))
// const answer = 6 * 7;
// console.log(answer);
}
Why go-fAST
Reasons it earns its keep
Not a wrapper around someone else's runtime. A focused, native-Go toolkit with a few things going for it.
No runtime to babysit
Pure Go, no cgo and no JavaScript engine to embed. It compiles into your binary, so you ship one static executable to a CLI, a service or a Lambda - there is no Node process to install or keep alive beside it.
Arena-backed parsing
The parser bump-allocates nodes into pre-sized arenas instead of scattering them across the heap, so building even a large tree stays light on allocations and easy on the garbage collector.
An in-built scope resolver
One call to resolver.Resolve walks the tree and stamps every identifier with a scope context, binding each reference to the exact declaration it belongs to across function, block and class scopes, hoisting and all. Two different variables both named a are never confused, so you can rename and fold code without breaking it - the safety net real deobfuscation depends on.
The compiler has your back
Every node is a concrete Go struct with typed fields. Your editor autocompletes the tree, and a wrong transform fails to compile instead of blowing up at runtime.
Learn it in a sitting
Three entry points: parse, a visitor, generate. There is no config object to memorise and no plugin graph to assemble before you can get useful work done.
Battle-tested daily
It is the engine behind our own anti-bot work, run every day against live, hostile, obfuscated JavaScript. What you build on is what we depend on.
What it is for
Built for taking JavaScript apart
If your Go program needs to understand, rewrite or emit JavaScript, go-fAST is the layer underneath it.
FAQ
Questions
What is go-fAST?+
An open-source Go library for parsing, transforming and generating JavaScript Abstract Syntax Trees. You parse source into a typed AST, walk and rewrite it in plain Go, then print it back to JavaScript. It runs no JavaScript itself, and is built for code analysis and (de)obfuscation.
Is it free and open source?+
Yes. go-fAST is MIT licensed and developed in the open at github.com/T14Raptor/go-fAST.
Do I need Node.js or a JavaScript engine?+
No. go-fAST is pure Go with no cgo and nothing to embed. go get it and it compiles straight into your binary, so there is no runtime to install or separate process to manage.
Does it run the JavaScript, like goja or otto?+
No. goja and otto are interpreters that execute JavaScript inside Go. go-fAST never runs the code; it hands you the syntax tree to inspect, rewrite and print. Reach for it when the goal is to analyse or transform JavaScript rather than execute it.
What can I build with it?+
Minifiers, deobfuscators, static analysers, codemods, linters and transpilation passes. It is the parser we reach for when reverse-engineering anti-bot scripts, which is what the DISASM API is built on.
How do I install it?+
Run go get github.com/t14raptor/go-fast. It requires Go 1.24 or newer.