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

cloneElement passed into new MemberExpression fails with cryptic message #163

Open
Smolations opened this issue Jul 20, 2017 · 2 comments
Open

Comments

@Smolations
Copy link

Smolations commented Jul 20, 2017

CST version: 0.4.9

I am working on some angular generators, and my current work is around re-ordering an angular module definition. The goal is to keep the module definition as a clone so I don't have to rebuild from scratch (which I may have to try if all else fails) but, after cloning it, I am unable to add it to a new MemberExpression.

I wrote my own methods for inspecting the module tree so I could see the types of children contained in elements. First, I find the big CallExpression containing the definition. Below is what you see after calling .getSourceCode() on that expression:

angular.module('module.services', [
  ngDep,
])

  .service('svcOne', SvcOne)
  .service('svcTwo', SvcTwo)

Here is a small snippet of output as I inspected all of the children, so I could narrow down which element was the module definition (this is at the very end of the output since the module def is one of the last children):

-------------------------------------------------------
[Inspecting MemberExpression]:
angular.module('module.services', [
  ngDep,
])   [CallExpression]


     [Whitespace, Token]
.   [Punctuator, Token]
service   [Identifier]
-------------------------------------------------------

Once I reach that deep, I grab the CallExpression and save a clone. Then I try to create a new MemberExpression using the clone:

// i have verified that I am cloning the correct output by calling `.getSourceCode()` on it
const moduleDefCallExp = el.cloneElement();

// i created my own util service to help me, which is why i use `types.MemberExpression`
// to access the constructor
let growingMemberExpression = new types.MemberExpression([moduleDefCallExp]);

An error is triggered on that line, showing the following stack trace:

/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:37
            throw new Error(`Token expected but "${type}" found`);
                  ^
Error: Token expected but "undefined" found
    at ElementAssert.assertToken (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:37:19)
    at ElementAssert.passToken (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:214:9)
    at MemberExpression._acceptChildren (/home/vagrant/myproj/node_modules/cst/src/elements/types/MemberExpression.js:22:22)
    at MemberExpression._setChildren (/home/vagrant/myproj/node_modules/cst/src/elements/Element.js:638:14)
    at MemberExpression.Element (/home/vagrant/myproj/node_modules/cst/src/elements/Element.js:50:18)
    at MemberExpression.Node (/home/vagrant/myproj/node_modules/cst/src/elements/Node.js:11:33)
    at MemberExpression.Expression (/home/vagrant/myproj/node_modules/cst/src/elements/Expression.js:13:33)
    at new MemberExpression (/home/vagrant/myproj/node_modules/cst/src/elements/types/MemberExpression.js:4:29)
    at Object.formatNgModule (/home/vagrant/myproj/generator-component/lib/cst-util.es6.js:247:35)

Am I doing something wrong here? I followed the source code and it seems strange that a token error, or any other error for that matter, would be thrown given that I passed in a clone from an element that the parser itself created. Given the source (for ElementAssert) around the error...

    assertToken(tokenType, tokenValue)       {
        let {isToken, type, value} = this.currentElement || {};
 
        if (!isToken) {
            throw new Error(`Token expected but "${type}" found`);
        }
    // ...

...it seems that this.currentElement is not set, even though the constructor should call this._navigate(0) which sets that property:

    constructor(elements) {
        this._elements = elements;
 
        if (elements.length > 0) {
            this._navigate(0);
        }
    }

Thoughts?

@Smolations
Copy link
Author

I also attempted to create an empty MemberExpression and then append the clone to it, but it appears that you cannot create an empty MemberExpression as I receive an error, both with
passing an empty array to the constructor:

/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:303
        while (this.currentElement.type === 'Punctuator' && this.currentElement.value === '(') {
               ^
TypeError: Cannot read property 'type' of undefined
    at ElementAssert._passExpressionInParens (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:303:16)
    at ElementAssert.passExpressionOrSuper (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:278:21)

...and when passing no arguments at all:

/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:37
            throw new Error(`Token expected but "${type}" found`);
                  ^
Error: Token expected but "undefined" found
    at ElementAssert.assertToken (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:37:19)
    at ElementAssert.passToken (/home/vagrant/myproj/node_modules/cst/src/elements/ElementAssert.js:214:9)

@Smolations
Copy link
Author

Smolations commented Jul 20, 2017

Doing more digging, when the MemberExpression is instantiated, super calls bubble up until Element's constructor eventually calls this._setChildren(children);, which eventually calls MemberExpression's override of this._acceptChildren(). Then, it appears that that override immediately will look for a token, which the CallExpression I passed is not:

  _acceptChildren(children) {
      // ...
      if (children.isToken('Punctuator', '.')) {
          // ...
      } else {
          children.passToken('Punctuator', '[');  // the error occurs here
          // ...
      }
      // ...
  }

So I'm wondering, how did the MemberExpression I inspected ever get a CallExpression as its first child?

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

No branches or pull requests

1 participant