-
Notifications
You must be signed in to change notification settings - Fork 356
Open
Description
This issue contains two reproducible cases where recast fails to generate the correct output after modifying AST nodes.
Environment & Versions:
- recast: v0.23.11
- @babel/traverse: v7.27.0
- Node.js: v20.18.1
- macOS: 15.4 (Intel Chip)
Steps:
recast.parse(...)with thebabel-tsparser- AST traversal and mutation with
@babel/traverse - Code regeneration with
recast.print(...)
If this usage pattern is incorrect or unsupported, please advise. Otherwise, the behavior below seems to be a bug.
Case 1: ImportSpecifier alias not printed
Input:
import { foo } from 'my-package';Transformation:
I update the local.name of the ImportSpecifier to _fooLocalName, without changing imported.name.
They are not the same object, and their names are different.
importKind remains "value".
Expected output:
import { foo as _fooLocalName } from 'my-package';Actual output:
import { _fooLocalName } from 'my-package'; // ❌ incorrectCase 2: ObjectProperty shorthand not updated
Input:
const foo = 100;
console.log({ foo });Transformation:
I rename the reference foo to _fooValueName, but the parent ObjectProperty still has shorthand: true.
Expected output:
console.log({ foo: _fooValueName });Actual output:
console.log({ _fooValueName }); // ❌ incorrectReproduction Code
import * as assert from 'node:assert';
import { parse, print } from 'recast';
import * as babelTsParser from 'recast/parsers/babel-ts';
import traverse from '@babel/traverse';
function reproduceCase1() {
const source = `import { foo } from 'my-package'`;
const ast = parse(source, { parser: babelTsParser });
traverse(ast, {
ImportDeclaration(path) {
const fooSpecifier = path.node.specifiers[0];
fooSpecifier.local.name = '_fooLocalName';
assert.notEqual(fooSpecifier.imported.name, fooSpecifier.local.name);
assert.equal(fooSpecifier.importKind, 'value');
},
});
const result = print(ast).code;
assert.equal(result, `import { foo as _fooLocalName } from 'my-package'`);
}
function reproduceCase2() {
const source = `const foo = 100; console.log({ foo })`;
const ast = parse(source, { parser: babelTsParser });
traverse(ast, {
Program(path) {
const fooRef = path.scope.getBinding('foo')!.referencePaths[0];
const objProp = fooRef.parent;
fooRef.node.name = '_fooValueName';
assert.notEqual(objProp.key.name, objProp.value.name);
assert.equal(objProp.shorthand, true);
},
});
const result = print(ast).code;
assert.equal(result, `const foo = 100; console.log({ foo: _fooValueName })`);
}
reproduceCase1();
reproduceCase2();Here is how to fix this problem
function removeRange (path) {
delete path.node.start;
delete path.node.end;
}
traverse(ast, {
ImportSpecifier: removeRange,
ObjectProperty: removeRange,
});Metadata
Metadata
Assignees
Labels
No labels