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

Obtaining the string representation of a single Node #17

Closed
lelit opened this issue Jul 16, 2018 · 4 comments
Closed

Obtaining the string representation of a single Node #17

lelit opened this issue Jul 16, 2018 · 4 comments
Labels

Comments

@lelit
Copy link

lelit commented Jul 16, 2018

Hi, I spent some time trying to upgrade a script I wrote on top of slimit replacing it with calmjs.parse, but I failed to find an equivalent of slimit's Node.to_ecma() method.

The script in question basically analyzes the arguments passed to every call of particular functions, to extract some info. It uses an ASTVisitor (this class) and when it finds a call to (say) Ext.define it extracts some info from the parameters.

I tried several approaches around calmjs.parse.unparsers.es5 to obtain the complete name of a FunctionCall identifier, but I always end up with the whole subtree (that is, not just the name, but the whole function, body included) stringification...

Can you give me an hint on how can I achieve that?

Thanks in advance!

@metatoaster
Copy link
Member

Ah, what you want is still there, just cast the identifier to str (or unicode if Python 2) and it should work. I also took a look at your code and see that a number of changes will be need; while I did say I want to make it a drop-in replacement, it really end up only for the highest level API and when it really was 0.9.0. When I made the 1.0.0 release there were just too many things that needed fixing at the fundamental level I took the liberty to reorganize everything to make it more to my liking. So that also mean that there's more to this, and it is a good idea to take a look at the asttypes class definition for the attributes needed, as that will become handy.

Based on the base example on extracting specific nodes on the readme, you might want to modify your base class to the following:

class ClassDependencies(object): 
    # note the subclass change
    # ... skipped the other unchanged methods for the mean time
    def process(self, node):
        # using the `u''` to ensure unicode in Python 2
        fname = u'%s' % node.identifier
        if fname == u'Ext.define':
            args = node.args.items  # argument lists are now classes
            if not isinstance(args[0], String):
                # to simplify following demo, I have this
                print('Ignoring exotic definition: %s' % args[0])
                # logging.debug('Ignoring exotic definition: %s', args[0])
            else:
                name = self.get_string(args[0])
                print('calling handle_define', name, args[1])

Now the example

>>> from calmjs.parse import es5
>>> from calmjs.parse.walkers import Walker
>>> from calmjs.parse.asttypes import FunctionCall
>>> from calmjs.parse.asttypes import String 
>>> source = '''Ext.define('My.sample.Person', {
...     name: 'Unknown',
... 
...     constructor: function(name) {
...         if (name) {
...             this.name = name;
...         }
...     },
... 
...     eat: function(foodType) {
...         alert(this.name + " is eating: " + foodType);
...     }
... });
... 
... Ext.define(some.exotic.thing, {})
... '''
>>> tree = es5(source)
>>> walker = Walker()
>>> dependencies = ClassDependencies()
>>> 
>>> for node in walker.filter(tree, lambda node: isinstance(node, FunctionCall)):
...     dependencies.process(node)
... 
calling handle_define My.sample.Person {
  name: 'Unknown',
  constructor: function(name) {
    if (name) {
      this.name = name;
    }
  },
  eat: function(foodType) {
    alert(this.name + " is eating: " + foodType);
  }
}
Ignoring exotic definition: some.exotic.thing

Another benefit: ClassDependencies class for your library is decoupled from any specific framework (now subclassed directly from object). Also note using a calling convention with a simple walker like so allows straightforward composition of different things needed to be handled/extracted into the same loop, potentially saving extra "visitations" and in my opinion it is a lot less unwieldy than trying to subclass the visitor class (or multiple classes to do two things at once), but now I digress.

@metatoaster
Copy link
Member

Oh, one more thing: for the string conversion (ClassDependencies.get_string method), do note that I made changes to the lexer also (although 1.1.0 is not released yet), such that escape notations (such as the line continuation mark) are no longer converted and stored in the ast node element. It might be worth it to borrow from Python's ast.literal_eval (like how I've done it here; also more examples on using calmjs.parse) to turn that into the real string value due to the similarities with string notations between Python and JavaScript.

@lelit
Copy link
Author

lelit commented Jul 17, 2018

Doh, “missing the obvious”, as it's called! 😄

Thanks a lot, I will try to apply your hints and report back!

@lelit
Copy link
Author

lelit commented Jul 19, 2018

Thanks again, I got it working, with a few other minor adaptation!

@lelit lelit closed this as completed Jul 19, 2018
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

2 participants