AST editing
How Empryo edits TypeScript and JavaScript by symbol name instead of by matching text, so edits don't fail on whitespace or drift with line numbers.
Copy & share
On this page6 sections
When Empryo edits a TypeScript or JavaScript file, it changes the code by symbol, not by searching for text. It says "set the return type of fetchUser" instead of "find these exact three lines and replace them". Small changes stay small, and an edit can't fail because of whitespace, quotes or line endings.
The tool that does this is called ast_edit. AST stands for abstract syntax tree, the structured form of your code that the TypeScript compiler builds. You never call ast_edit yourself. Empryo uses it automatically for TypeScript and JavaScript files, and this page is a reference for what it can do.
In a project with no TypeScript or JavaScript files, such as a Go or Rust repository, Empryo loads ast_edit on demand instead of sending it with every request. The model can still pull it in when it meets a stray .ts file. Empryo decides when a tab starts, from the files git tracks plus new files that are not ignored, or from a scan of the folder when it is not a git repository. The decision is saved with the session, so a resumed session keeps the same tools and its prompt cache.
Where the idea comes fromLink to this section
The approach grew out of a Master's thesis in Computer Science, *Typed vs Untyped Programming Languages* (Ouail Bni and Artur Matusiak, 2022), and its reference implementation JS Typer. The thesis showed that converting JavaScript to TypeScript could be done entirely by editing the syntax tree rather than the text. ast_edit applies the same idea to AI coding.
ExamplesLink to this section
Change a function's return type:
ast_edit({
path: "src/api.ts",
action: "set_return_type",
target: "function",
name: "fetchUser",
value: "Promise",
})Make a function async, add a parameter and add an import, as one batch:
ast_edit({
path: "src/api.ts",
operations: [
{ action: "set_async", target: "function", name: "fetchUser", value: "true" },
{ action: "add_parameter", target: "function", name: "fetchUser", value: "cache: boolean" },
{ action: "add_named_import", value: "./types", newCode: "User" },
],
})Rename a class method:
ast_edit({
path: "src/store.ts",
action: "rename",
target: "method",
name: "UserStore.fetch",
value: "load",
})Replace a whole function body:
ast_edit({
path: "src/util.ts",
action: "set_body",
target: "function",
name: "parseInput",
newCode: "return JSON.parse(raw);",
})A batch is all-or-nothing. If one operation fails, none of them are applied.
TargetsLink to this section
You point an edit at a symbol by its kind and name.
| Target | Addressed by |
|---|---|
function, class, interface, type, enum, variable, constant | name |
method, property | "ClassName.member" or "member" |
arrow_function | name (for const fn = () => {}) |
constructor | ClassName |
Anonymous code, such as an inline callback, can't be targeted directly. Empryo edits the named symbol that contains it instead.
Action referenceLink to this section
Small edits
| Action | Effect |
|---|---|
set_type | Type annotation of a variable, property or parameter |
set_return_type | Function return type |
set_initializer / remove_initializer | Variable initializer |
set_value | Enum member value |
set_async / set_generator | Toggle async / function* |
set_export / set_default_export | Toggle export modifiers |
set_abstract / set_static / set_readonly / set_scope / set_optional / set_overrides / set_ambient | Class member modifiers |
set_const_enum / set_declaration_kind | Other modifiers |
rename | Rename the declaration only |
remove | Delete the declaration |
add_parameter / remove_parameter | Function and method parameters |
Body and member edits
| Action | Effect |
|---|---|
set_body | Replace a function or method body |
add_statement / insert_statement / remove_statement | Individual statements |
add_property / remove_property | Interface and class properties |
add_method / remove_method / add_constructor / add_getter / add_setter | Class members |
add_member / remove_member | Enum and type-literal members |
add_decorator / remove_decorator | Decorators |
add_overload | Function overload signatures |
set_extends / add_extends / remove_extends / add_implements / remove_implements | Inheritance |
add_type_parameter | Generic type parameters |
add_jsdoc / remove_jsdoc | JSDoc comments |
unwrap, set_structure, extract_interface | Structural rewrites |
Whole-symbol replacement
| Action | Effect |
|---|---|
replace | Replace a whole symbol |
replace_in_body | Replace text inside a named symbol, found by a unique anchor or a start/end anchor pair |
File-level
| Action | Effect |
|---|---|
create_file | Create a new file (newCode is the content) |
add_imports / add_import / remove_import / add_named_import / remove_named_import | Imports |
set_module_specifier | Change an import path |
add_export_declaration / add_named_reexport | Re-exports |
add_namespace | Namespace declarations |
organize_imports / fix_missing_imports / fix_unused | Cleanup |
add_function / add_class / add_interface / add_type_alias / add_enum / add_variable | Add top-level declarations |
insert_text | Insert text at an anchor ("after-imports", "before-exports", or an index) |
rename_global | Rename a symbol across the project |
Supported filesLink to this section
.ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs. For every other file type, and for files with a syntax error, Empryo uses its regular text-based edit tools.
SafetyLink to this section
- Batches are atomic: all operations apply, or none do.
undo_editreverses anast_editchange the same way it reverses any other edit.- After an edit, new type errors are reported back to the agent so it can fix them in the same turn.
- Files blocked by your
/privacypatterns can't be edited.