Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error while replacing identifier of a PropertyAccessExpression #365

Closed
gregjacobs opened this issue Jul 16, 2018 · 5 comments
Closed

Error while replacing identifier of a PropertyAccessExpression #365

gregjacobs opened this issue Jul 16, 2018 · 5 comments
Labels

Comments

@gregjacobs
Copy link

gregjacobs commented Jul 16, 2018

Hey @dsherret, long time no talk! How's it going?

I'm back doing ts transforms (yay!), but ran into an issue while trying to replace the object name (expression) in a PropertyAccessExpression.

I'm basically trying to convert that.something to this.something, but if the there are 4 chained PropertyAccessExpressions, I get an error. Try running this code:

import Project, { Identifier, PropertyAccessExpression, SyntaxKind, TypeGuards } from 'ts-simple-ast';

const project = new Project();
const sourceFile = project.createSourceFile( 'my-file.ts', `
	that.something.something2.something3;   // <-- too nested!
` );


const thatDotSomething: PropertyAccessExpression = sourceFile
	.getDescendantsOfKind( SyntaxKind.PropertyAccessExpression )
	.find( propAccess => TypeGuards.isIdentifier( propAccess.getExpression() ) )!;

const thatIdentifier = thatDotSomething.getExpression() as Identifier;
thatIdentifier.replaceWithText( 'this' );

console.log( sourceFile.getFullText() );

I get the error: Error: Error replacing tree: Should not have more children left over.

Oddly enough, when I remove one property and change that.something.something2.something3 to that.something.something2, the code works fine.

Can you take a look into this? Thanks!

@cancerberoSgx
Copy link
Contributor

I think is an issue indeed. First of all I suggest you use rename() instead o replaceWithText(). Is safer, wont make the nodes obsolete and also it will rename all the references to that symbol in the whole project (just like TypeScript rename feature). Nevertheless rename() has the same problem.

Fortunately you can workaround it by renaming the Identifier definition node:

play with the code

import Project, { Identifier, PropertyAccessExpression, SyntaxKind, TypeGuards, Node, DeclarationNamedNode } from 'ts-simple-ast'

function canBeRenamed(node: Node): node is Node & { rename: (name: string)=> Node; } {
  return typeof (node as any).rename === 'function'
}

const project = new Project({useVirtualFileSystem: true});
const sourceFile = project.createSourceFile( 'my-file2.ts', `
let a, x
a.b.c.d = 3;   // <-- too nested!
x=1
` );

const aInExpression = sourceFile.getDescendants().find(d => TypeGuards.isIdentifier(d) && 
  TypeGuards.isExpression(d.getParent()) && d.getText()==='a') as Identifier

// renaming the id in the expression doesn't work: 
// aInExpression.rename('RENAMED1'); 

// however renaming the definitino node does:
// aInExpression.getDefinitionNodes().filter(canBeRenamed).forEach(n => n.rename('RENAMED2')) // n is VariableDeclaration

// also renaming the id of the variable declaration (and not the variable declaration itseof) works:
sourceFile.getDescendants().filter(d => TypeGuards.isIdentifier(d) && 
  TypeGuards.isVariableDeclaration(d.getParent()) && d.getText()==='a').
  filter(canBeRenamed).forEach(n=>n.rename('RENAMED3')); 
log(sourceFile.getFullText());

@gregjacobs
Copy link
Author

gregjacobs commented Jul 16, 2018

Interesting, I didn't realize there was a rename method. Thanks for that!

Although an interesting point in this case might be that I'm "renaming" from an identifier node to a ThisKeyword node. Will that still work? (I haven't checked the code to see if there's a special case for the string "this")

And of course as you pointed out, the renaming still doesn't work on its own with this level of nested property accesses :/

@cancerberoSgx
Copy link
Contributor

I think is weird to anIdentifier.rename('this') conceptually since what you really want to do is replace the Identifier for a ThisKeyword node - (not renaming an Id). Perhaps replaceText would be more appropriate but I think the ideal tool for what you want to accomplish is ts.transformations. In the playground you have three transformation examples in typescript native compiler API. The library doesn't have that API wrapped so you will need to do it in the compilerNode (sourceFile.compilerNode) save the file and reload the ts-simple-ast project to get the changes. My two cents

@dsherret
Copy link
Owner

dsherret commented Jul 16, 2018

Hey @gregjacobs! I've been doing well thanks. I hope you have been too. Nice to see you back, but sorry that it's because of a bug!

Thanks for reporting this. I've fixed it now in 12.6.1 (available now). Just use the return value of replaceWithText to get the new ThisKeyword as the previous Identifier will be forgotten.

const thatIdentifier = ...;
const thisKeyword = thatIdentifier.replaceWithText("this");

thatIdentifier.getText(); // throws because node was forgotten since the kind changed
thisKeyword.getText();    // ok

@gregjacobs
Copy link
Author

@dsherret Thanks for the quick fix! Definitely good to be back - I really love this stuff and I missed it :)

Very good to know about the return value from replaceWithText() - didn't realize that it did that. Many thanks, and keep up the great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants