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

Correct global _ destruction via meteor shell. #9406

Merged
merged 10 commits into from Dec 2, 2017

Conversation

abernix
Copy link
Contributor

@abernix abernix commented Nov 21, 2017

(PR body is the commit message body from 15b24ff.)

A truthy useGlobal option when calling Node's repl.start() will use the global context, which will allow global Meteor variables (such as the global _ provided by the underscore) to be available in the context of the REPL shell. This sounds desirable, and in fact, the original
implementation set it as such, only to be later changed to false in 7c7e52f, specifically to avoid the undesirable behavior of Node REPL stomping on the global _ variable with its own special-meaning _ variable which is set to the value of the most recently eval'd expression.

This useGlobal was once again changed to true to fix the lost tab-completion functionality, thanks to 2443d83, which also implelented special mutator on _ to avoid breaking it. As it's probably clear now, this has been a struggle, and because of Node using its own Object.defineProperty('_', ...) as of Node.js 6 (in node/node#5535), this problem has surfaced again, as reported in #9276.

This changes the behavior to another implementation which starts with useGlobal set to false, but then sets the context immediately to global, which seems to avoid the problem.

This does maintain the existing behavior of our special double-underscore variable, in order to get the value of the last REPL expression, in the same manner as _ would in a normal REPL, by
accessing the internal last property. Since this is also undocumented (on our end), this seemed reasonable.


I'd recommend reviewing this PR at one time, in it's entirety, but keep in mind that the individual commits provide additional commentary in their own commit messages (and changes).

I attempted to make tests for this, but since the self tests seem to invoke the evaluateAndExit functionality within meteor shell (due to their lack of a full TTY), the context is not affected in any way.

Fixes #9276.

In order to catch so-called "recoverable" REPL errors (for example,
commands in the process of being inputted which are syntax errors until
they are completed/recovered), it had previously been necessary to
intentionally cause such an error and save the error's prototype for
future use.

Node 6 changed this by exporting the Error directly from the `repl`
module, and this commit represents the changes necessary to remove that
(now unnecessary) behavior.

This also takes the opportunity to update the `isRecoverableError` (and
related) functions which had previously been borrowed from the `repl`
module source, to their newer versions.
As Node.js 8 natively supports ECMAScript classes, this code shouldn't
be necessary anymore.
A truthy `useGlobal` option when calling Node's `repl.start()` will use
the `global` context, which will allow global Meteor variables (such as
the global `_` provided by the `underscore`) to be available in the context
of the REPL shell.  This sounds desirable, and in fact, the original
implementation set it as such, only to be later changed to `false` in
7c7e52f2d2, specifically to
avoid the undesirable behavior of Node REPL stomping on the global `_`
variable with its own special-meaning `_` variable which is set to the
value of the most recently eval'd expression.

This was once again changed to `true` to fix the lost tab-completion
functionality, thanks to 2443d83226,
which also implelented special mutator on `_` to avoid breaking
it.  As it's probably clear now, this has been a struggle, and because
of Node using its own `Object.defineProperty('_', ...)` as of Node.js 6
(in https://github.com/nodejs/node/pull/5535/files), this problem has
surfaced again, as reported in #9276.

This changes the behavior to another implementation which starts with
`useGlobal` set to `false`, but then sets the context immediately to
`global`, which seems to avoid the problem.

It's possible that another option might be to set explicitly set the
`underscoreAssigned` attribute on the `repl` after starting it, however
since that's an internal, undocumented property, it might be best to avoid.

This does maintain the existing behavior of our special
double-underscore variable, in order to get the value of the last
REPL expression, in the same manner as `_` would in a normal REPL, by
accessing the internal `last` property.  Since this is also undocumented
(on our end), this seemed reasonable.
Copy link
Contributor

@hwillson hwillson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me @abernix! I've played around with these changes a bit, and I can confirm this fixes #9276. Actually, these changes also fix #5415. 2 for the price of 1 - Black Friday on Wednesday! 🙂

@hwillson
Copy link
Contributor

Interesting - I didn't realize adding an issue link in a review comment doesn't register that link in the linked to issue thread. So, recapping here to fix the issue thread - these changes also fix #5415.

// https://github.com/meteor/meteor/commit/2443d832265c7d1c
Object.defineProperty(repl.context, "__", {
get: () => repl.last,
set: (val) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is repl.context.__ ever set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't, but I copied the behavior that Node.js uses in the native repl code located here:

https://github.com/nodejs/node/blob/ad8257fa5b9795d3d79fa4a91d0f18c43f024ab3/lib/repl.js#L575-L587

...in which they also allow setting of _, which gets stored on the repl instance's this.last property (in this code repl.last)

}

return stringLiteral ? lastChar === '\\' : isBlockComment;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there was any way to avoid copying this code here, I think that would make our REPL implementation much more maintainable in the future!

Addresses feedback from @benjamn.

Rather than copying the `IsRecoverableError` and `isCodeRecoverable`
methods from the Node.js `repl` module source (in order to capture
so-called "Recoverable" errors), wrap the default "eval" function with
our relatively thin logic, thus avoiding the need to continually update
the definition of what's "recoverable" as Node's implementation evolves.
// of catching so-called "Recoverable Errors" (https://git.io/vbvbl),
// we will wrap the default eval, run it in a Fiber (via a Promise), and
// give it the opportunity to decide if the user is mid-code-block.
const defaultEval = repl.eval;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

This is superfluous residue that I inadvertently created when splitting the
existing `startREPL` function into `setupREPL` and `enableInteractiveMode`.

The context is already set in `setupREPL` (to the exact same value as
here) by the time that this occurrence in `enableInteractiveMode` is called.
I previously had thought that a duplicate call to `setRequireAndModule`
encountered in code-path would no longer be necessary after some
consolidation in previous steps of this re-factor, but the test failure
seen here made it clear what was happening:

https://circleci.com/gh/meteor/meteor/12445

Specifically, if a module was imported in a piped command (that is to say,
when no TTY is present and the `evaluateAndExit` code-path is taken), as so:

    echo 'import { Meteor } from "meteor/meteor"' | meteor shell

...the `module` and `require` symbols were not set.  Conveniently, this is
the environment in effect when the `meteor self-test` suite is ran since
they do not have a TTY.

This moves the `setAndRequire` from the "interactive-only" function into
the general REPL setup and further harmonizes the code.
@benjamn benjamn added this to the Package Patches milestone Nov 29, 2017
As demonstrated in #9276.

This test wouldn't have caught the regression in the previous solution
since the lack of a TTY in the `self-test` test harness caused the tests
themselves to take the path through `shell-server`'s `evaluateAndExit`
logic, which didn't use the `global` scope in the same way as the
interactive shell.  That is no longer the case as of e0682c5.
It was previously necessary to have more from the `repl` module, but
it's sufficient to just have `start` now since we wrap the default
`eval`.
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

Successfully merging this pull request may close these issues.

[1.6] Starting meteor shell overwrite _ in the main server
3 participants