**JSON que executa código.**
npm install omni-astJSON que executa código.
Transformação bidirecional AST ↔ JSON ↔ Código com compactação inteligente.
---
AST ESTree é verboso. Um simples { name: "João" } vira:
``json`
{
"type": "ObjectExpression",
"start": 0, "end": 19,
"properties": [{
"type": "Property",
"start": 2, "end": 17,
"key": { "type": "Identifier", "start": 2, "end": 6, "name": "name" },
"value": { "type": "Literal", "start": 8, "end": 17, "value": "João", "raw": "\"João\"" },
"kind": "init", "method": false, "shorthand": false, "computed": false
}]
}
58 linhas de ruído para representar 1 propriedade.
omni-ast compacta para:
`json`
{
"type": "JsonExpression",
"body": { "name": "João" }
}
JSON puro. Sem ruído. Serializável. E o melhor: reconstruível de volta para código JS.
---
O diferencial. Quando há lógica dinâmica, ela é isolada com #ast:
`javascript`
// Código original
{ name: "João", createdAt: Date.now() }
`json`
{
"type": "JsonExpression",
"body": {
"name": "João",
"createdAt": {
"#ast": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "Date" },
"property": { "type": "Identifier", "name": "now" }
},
"arguments": []
}
}
}
- #ast = porta de entrada pro mundo AST (valor é o type do nó)
- Dentro do AST = usa type normal (ESTree)
- Só expressões são permitidas (coisas que retornam valor)
`json
// ✅ Válido - CallExpression é uma expressão
{ "#ast": "CallExpression", "callee": {...}, "arguments": [] }
// ❌ Inválido - IfStatement não retorna valor
{ "#ast": "IfStatement", "test": {...} }
`
- Dados estáticos → JSON direto (zero overhead)
- Lógica executável → marcada com #ast
- 100% serializável → salva em banco, envia por API, armazena em arquivo
- 100% reconstruível → volta para código JavaScript
---
`bash`
pnpm add omni-ast
---
`typescript
import { simplify, parseAST, generate, clearAST } from 'omni-ast';
import { parseScript } from 'meriyah'; // ou acorn, esprima, babel...
// 1. Código JavaScript
const code = '({ user: "Maria", total: calcular(items) })';
// 2. Parse + Normaliza + Simplifica
const json = simplify(clearAST(parseScript(code).body[0].expression));
// {
// type: "JsonExpression",
// body: {
// user: "Maria",
// total: { "#ast": "current", body: { type: "CallExpression", ... } }
// }
// }
// 3. Serializa (banco, API, arquivo...)
const stored = JSON.stringify(json);
// 4. Reconstrói
const ast = parseAST(JSON.parse(stored));
// 5. Gera código
generate(ast); // '{ user: "Maria", total: calcular(items) }'
`
---
``
clearAST() simplify()
Código JavaScript ──────────────► AST Limpo ────────────► JSON Compacto
▲ │
│ parseAST() │
│ ◄─────────────────────────────────────────┘
│
│ generate()
└─────────────── AST
| Função | Descrição |
|--------|-----------|
| normalize(ast) | Normaliza AST (remove metadados de parser) |simplify(ast)
| | Compacta para JSON com #ast |restore(json)
| | Reconstrói AST completo |generate(ast)
| | Gera código JavaScript |
> Aliases deprecated: clearAST → normalize, parseAST → restore
---
`typescript
import { json, ast, serialize } from 'omni-ast';
import * as b from 'omni-ast/builders';
const config = json({
apiUrl: "https://api.exemplo.com",
timeout: 5000,
getHeaders: ast(b.arrowFunctionExpression(
[],
b.objectExpression([
b.property(
b.identifier('Authorization'),
b.callExpression(b.identifier('getToken'), [])
)
])
))
});
serialize(config.body);
// '{ apiUrl: "https://api.exemplo.com", timeout: 5000, getHeaders: () => ({ Authorization: getToken() }) }'
`
`typescript
// Editor no-code salva definição como JSON
const definition = {
type: "JsonExpression",
body: {
action: "sendEmail",
to: { "#ast": "current", body: { type: "Identifier", name: "userEmail" } },
subject: "Bem-vindo!"
}
};
// Export para código executável
const code = generate(parseAST(definition));
// '{ action: "sendEmail", to: userEmail, subject: "Bem-vindo!" }'
`
`typescript`
const workflow = json({
steps: [
{ action: "fetch", url: "https://api.com/data" },
{
action: "transform",
handler: ast(b.arrowFunctionExpression(
[b.identifier('data')],
b.callExpression(
b.memberExpression(b.identifier('data'), b.identifier('filter')),
[b.arrowFunctionExpression(
[b.identifier('x')],
b.memberExpression(b.identifier('x'), b.identifier('active'))
)]
)
))
},
{ action: "save", target: "database" }
]
});
`typescript
import { walk, find, guards } from 'omni-ast';
// Encontra todas as chamadas de função
walk(ast, {
CallExpression(node) {
console.log('Chamada:', generate(node.callee));
}
});
// Busca específica
const asyncFn = find(ast, n =>
guards.isArrowFunctionExpression(n) && n.async
);
`
---
`typescript
import { normalize, simplify, restore, generate, serialize } from 'omni-ast';
normalize(ast) // Normaliza (remove start, end, loc, range, raw, method)
simplify(ast) // AST → JSON compacto
restore(json) // JSON → AST completo
generate(ast) // AST → código JavaScript
serialize(json) // JSON híbrido → código (via #ast)
`
47+ builders para construção programática:
`typescript
import { builders as b } from 'omni-ast';
// const x = getValue()
b.variableDeclaration('const', [
b.variableDeclarator(
b.identifier('x'),
b.callExpression(b.identifier('getValue'), [])
)
]);
`
| Categoria | Builders |
|-----------|----------|
| Básicos | identifier, literal |callExpression
| Expressões | , memberExpression, binaryExpression, conditionalExpression, arrowFunctionExpression, newExpression, awaitExpression |arrayExpression
| Coleções | , objectExpression, property, spreadElement, restElement |blockStatement
| Statements | , returnStatement, ifStatement, forStatement, whileStatement, tryStatement |variableDeclaration
| Declarações | , functionDeclaration |ast
| Especiais | , json, jsonExpression |
50+ guards com inferência TypeScript:
`typescript
import { guards } from 'omni-ast';
if (guards.isCallExpression(node)) {
// TypeScript sabe: node é CallExpression
console.log(node.callee, node.arguments);
}
`
`typescript
import { walk, find, mutate } from 'omni-ast';
// Traversal
walk(ast, {
Identifier(node) { / ... / },
"": (node) => { / catch-all */ }
});
// Busca
const fn = find(ast, guards.isFunctionExpression);
// Transformação imutável
const newAst = mutate(ast,
n => n.type === 'Identifier' && n.name === 'foo',
n => ({ ...n, name: 'bar' })
);
`
---
omni-ast funciona com qualquer parser ESTree. normalize() normaliza as diferenças:
`typescript
import { parseScript } from 'meriyah'; // ⚡ Recomendado (menor, mais rápido)
import * as acorn from 'acorn';
import * as esprima from 'esprima';
// Todos funcionam:
normalize(parseScript(code));
normalize(acorn.parse(code, { ecmaVersion: 2022 }));
normalize(esprima.parseScript(code));
`
| Parser | Bundle | Performance |
|--------|--------|-------------|
| Meriyah | ~12kb | ⚡ Mais rápido |
| Acorn | ~13kb | Boa |
| Esprima | ~20kb | Média |
| Babel | Grande | TypeScript/JSX |
---
omni-ast não tem dependências de runtime. Funciona standalone.
---
O generate() faz algumas escolhas intencionais para evitar ambiguidade no código gerado:
Arrow functions com body BinaryExpression, AssignmentExpression ou ObjectExpression recebem parênteses:
`typescript
// Builder
b.arrowFunctionExpression([b.identifier('x')], b.binaryExpression('*', b.identifier('x'), b.literal(2)))
// Gerado: (x) => (x * 2)
// Não: (x) => x * 2
`
Por quê? Evita ambiguidade em casos como:
`javascript
// Sem parênteses, pode confundir
x => x + y z // É (x + y) z ou x + (y * z)?
// Com parênteses, fica claro
x => (x + y * z)
`
Quando uma arrow function é usada como callee de uma chamada (IIFE), ela é envolta em parênteses:
`typescript
// Builder
b.callExpression(b.arrowFunctionExpression([], b.literal(42)), [])
// Gerado: (() => 42)()
// Não: () => 42() (que seria arrow retornando 42())
`
O generator suporta optional chaining em chamadas:
`typescript
// Builder
b.callExpression(b.identifier('fn'), [b.literal(1)], true)
// Gerado: fn?.(1)
``
---
- Arquitetura
- Builders
- Parsers
- Generators
- Guards
- Utils
---
MIT