<< All versions

Skill v1.0.0

currentLLM-judged scan90/100
yanacuti1121/yana-ai/ast-codegen-patterns
──Details
PublishedOctober 24, 2026 at 12:14 AM
Content Hashsha256:c6eaac5b02aeade0...
Git SHA5b39442837f1
──Files
Files (1 file, 4.1 KB)
SKILL.md4.1 KBactive
SKILL.md · 154 lines · 4.1 KB

name: ast-codegen-patterns description: Code generation from AST back to source text. esotope-style compact code output, format-preserving reprint, and round-trip AST → source pipelines for codemods and refactoring tools. Sources: glaynge/esotope (BSD-2-Clause). origin: yana-ai — synthesized from glaynge/esotope (BSD-2-Clause), escodegen (BSD-2-Clause) license: Apache-2.0 version: 1.0.0 compatibility: yana-ai >= 1.3.49


/ast-codegen-patterns

When to Use

  • Regenerate source from a modified AST (after [[babel-ast-transform]] or [[estree-ast-spec]] walker)
  • Format-preserving reprints: preserve whitespace/comments in unmodified subtrees
  • Round-trip pipelines: parse → mutate → generate → diff
  • Custom code generation: emit domain-specific patterns from data models

Do NOT use for

  • Full Babel transform pipelines (use [[babel-ast-transform]] which includes generate)
  • TypeScript codegen from scratch (use [[ts-morph-refactor]] for type-safe generation)

ESTree → source (escodegen)

javascript
import escodegen from 'escodegen'
import { parse } from 'acorn'
const source = `
const arr = [1, 2, 3]
const doubled = arr.map(x => x * 2)
`
const ast = parse(source, { ecmaVersion: 2022, sourceType: 'module' })
// Mutate AST: rename 'doubled' → 'multiplied'
function rename(node, from, to) {
if (!node || typeof node !== 'object') return
if (node.type === 'Identifier' && node.name === from) node.name = to
for (const key of Object.keys(node)) rename(node[key], from, to)
}
rename(ast, 'doubled', 'multiplied')
const output = escodegen.generate(ast, {
format: {
indent: { style: ' ' },
quotes: 'single',
semicolons: true,
},
comment: true, // preserve comments from original
})
console.log(output)
// const arr = [1, 2, 3];
// const multiplied = arr.map(x => x * 2);

Source map–aware generation

javascript
import escodegen from 'escodegen'
import { SourceMapConsumer } from 'source-map'
const { code, map } = escodegen.generate(ast, {
sourceMap: 'input.js',
sourceMapWithCode: true,
format: { compact: false },
})
// map is a SourceMapGenerator — serialize for output
const sourceMapString = map.toString()
const codeWithMap = code + '\n//# sourceMappingURL=data:application/json;base64,'
+ Buffer.from(sourceMapString).toString('base64')

Manual AST builder (no parser needed)

javascript
// Build `export const VERSION = "1.0.0"` from scratch
function buildVersionExport(version: string) {
return {
type: 'ExportNamedDeclaration',
declaration: {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{
type: 'VariableDeclarator',
id: { type: 'Identifier', name: 'VERSION' },
init: { type: 'Literal', value: version, raw: JSON.stringify(version) },
}],
},
specifiers: [],
source: null,
}
}
const program = {
type: 'Program',
body: [buildVersionExport('1.0.0')],
sourceType: 'module',
}
console.log(escodegen.generate(program))
// export const VERSION = '1.0.0';

Round-trip codemod helper

typescript
import { parse } from 'acorn'
import escodegen from 'escodegen'
export function codemod(
source: string,
mutate: (ast: any) => void,
options: escodegen.GenerateOptions = {}
): string {
const ast = parse(source, {
ecmaVersion: 2022,
sourceType: 'module',
onComment: (block, text, start, end) => {
// attach comments to nearest node for escodegen to reprint
},
})
mutate(ast)
return escodegen.generate(ast, {
comment: true,
format: { indent: { style: ' ' }, ...options },
})
}

Anti-Fake-Pass Checklist

❌ escodegen.generate on Babel AST → Babel adds non-ESTree nodes; strip them first
❌ comment: false → all comments lost in round-trip
❌ Mutating raw node.loc values → code generation uses loc for source maps; corruption
❌ compact: true in a codemod → diffs become unreadable one-liners
❌ Manual AST missing 'raw' field on Literal → some generators produce undefined
❌ sourceMap: 'filename' must match the original filename exactly or stack traces break
All versions