-
Notifications
You must be signed in to change notification settings - Fork 18
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
Run from AST Explorer #53
Conversation
Awesome! Sounds good to me, we should add this to Babel maybe as well. Actually wish it would just be a part of babel-cli really to keep in sync (eventually) |
I refactored the internals a little bit in order to support loading network plugins. It's partially broken at the moment but should be fairly easy to fix. The way I did it means we could just use e.g. Also, since we're now using What do you think, @hzoo? |
c1085cc
to
87b9952
Compare
7184613
to
86558fa
Compare
Okay, this now has both |
(I can't actually make either of you reviewers, but I'd like to have both @hzoo and @mcMickJuice take a look.) |
For sure @eventualbuddha 👍 Just to make it easier on me, could you provide a high level overview of the changes you made? Looks like there were some significant changes |
I’ll provide some inline comments. I had to rebase this branch after doing the prettier stuff and ended up just squashing everything together so I lost all my nicely-separated commits. Also looks like I’m relying on |
README.md
Outdated
$ codemod --plugin transform-module-name \ | ||
path/to/file.js \ | ||
another/file.js | ||
``` | ||
|
||
This will re-write the files `path/to/file.js` and `another/file.js` by transforming them with the babel plugin `transform-module-name`. Multiple plugins may be specified, and multiple file or directories may be re-written at once. | ||
|
||
Plugins may also be loaded from [AST Explorer](https://astexplorer.net/) using `--remote-plugin`. This feature should only be used as a convenience to load code that you or someone you trust wrote. It will run with your full user privileges, so please exercise caution! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I wrote it you can actually load things from any URL that contains a babel plugin, but there’s special handling for astexplorer.net. I should probably document that here.
@@ -8,7 +8,7 @@ | |||
"lint": "tslint --project .", | |||
"lint-fix": "tslint --project . --fix", | |||
"pretest": "yarn lint && tsc --project .", | |||
"test": "mocha", | |||
"test": "mocha 'test/**/*Test.js'", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to avoid this, but I didn’t see a good way to do so. I wanted to make running from both the command line and editors (like WebStorm) work as expected with minimal configuration and duplication, and I can’t put globs into mocha.opts
without forcing those files to always run.
"typescript": "^2.2.1" | ||
}, | ||
"dependencies": { | ||
"babel-core": "^6.26.0", | ||
"babel-preset-env": "^1.6.1", | ||
"babel-register": "^6.26.0", | ||
"glob": "^7.1.2", | ||
"got": "^8.0.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got
is added for fetching data from URLs.
package.json
Outdated
"mz": "^2.7.0", | ||
"recast": "^0.13.0", | ||
"resolve": "^1.5.0" | ||
"resolve": "^1.5.0", | ||
"tmp": "^0.0.33" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tmp
is added to create a temporary file for downloaded plugins.
@@ -4,6 +4,11 @@ import { hasMagic as hasGlob, sync as globSync } from 'glob'; | |||
import { basename, extname, resolve } from 'path'; | |||
import { sync as resolveSync } from 'resolve'; | |||
import { PathPredicate } from './iterateSources'; | |||
import PluginLoader from './PluginLoader'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here’s where the big changes start. Rather than making loading the responsibility of Plugin
, I created a PluginLoader
class that takes a series of resolvers that can resolve things that are given on the command line (i.e. my-plugin
from --plugin my-plugin
) and turn them into file paths to be loaded. Once the file paths are resolved, PluginLoader
does a require
and extracts the babel plugin.
|
||
private remotePluginLoader = new PluginLoader([ | ||
new AstExplorerResolver(), | ||
new NetworkResolver() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually these lists might be configurable, but for now local plugins are loaded from either a direct file system reference or by package name and remote plugins are loaded either from astexplorer.net editor URLs or just a raw URL.
private _pluginCache?: Array<Plugin>; | ||
|
||
getPlugins(): Array<Plugin> { | ||
async getPlugins(): Promise<Array<Plugin>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made all these APIs async.
*/ | ||
export default class AstExplorerResolver extends NetworkResolver { | ||
constructor( | ||
private readonly baseURL: URL = new URL('https://astexplorer.net/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter is only used in tests so we can test against a local server instead of the real astexplorer.net.
test/cli/CLITest.ts
Outdated
@@ -5,7 +5,7 @@ import { basename, dirname, join } from 'path'; | |||
import { sync as rimraf } from 'rimraf'; | |||
|
|||
function plugin(name: string): string { | |||
return join(__dirname, `fixtures/plugin/${name}.js`); | |||
return join(__dirname, `../fixtures/plugin/${name}.js`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these sort of changes were just moving CLI tests into test/cli
and everything else into test/unit
to enable running the unit tests by themselves more quickly. That’s somewhat nullified by the required tsc -p .
step, but such is life. I’m open to suggestions for ways to better speed up the tests (@mcMickJuice mentioned Jest).
@@ -0,0 +1 @@ | |||
--require source-map-support/register |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were already building sourcemaps, but we need this to make stack traces actually use them.
test/unit/TestServer.ts
Outdated
export class RealTestServer { | ||
private server: Server; | ||
|
||
readonly protocol: string = 'http'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this actually should be http:
.
res: ServerResponse | ||
) => void; | ||
|
||
export class RealTestServer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn’t look very hard for another package that implements something like this. I found mocking libraries for http but those always felt messy to me, and so I chose to have a real local server that could be hit for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
import AstExplorerResolver from '../../../src/resolvers/AstExplorerResolver'; | ||
import { startServer } from '../TestServer'; | ||
|
||
describe('AstExplorerResolver', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should probably add some failure cases here.
import { join } from 'path'; | ||
import FileSystemResolver from '../../../src/resolvers/FileSystemResolver'; | ||
|
||
describe('FileSystemResolver', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should probably add some failure cases here.
@@ -1,37 +1,21 @@ | |||
{ | |||
"rules": { | |||
"extends": [ | |||
"tslint-config-prettier" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up having to do what you were suggesting on #56 to make sure tslint and prettier didn’t conflict, @mcMickJuice.
Okay, hopefully that’s enough context for you to take a look @mcMickJuice. Also, @fkling, does this look sane from your perspective? I don’t know whether there’s some obvious cases where using astexplorer.net in this way will fail. |
86558fa
to
80aef03
Compare
}) | ||
); | ||
|
||
this._pluginCache = [...(await localPlugins), ...(await remotePlugins)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isnt async/await the best?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the approach is solid! I haven't run the code yet but just looking through changes and making minor comments and tweaks, it looks awesome.
*/ | ||
export default class FileSystemResolver implements Resolver { | ||
constructor( | ||
private readonly optionalExtensions: Set<string> = DEFAULT_PLUGIN_EXTENSIONS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would the user pass in additional extensions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They wouldn't at this point. We might be able to use this for #57.
import { resolve } from 'path'; | ||
import Resolver from './Resolver'; | ||
|
||
const DEFAULT_PLUGIN_EXTENSIONS = new Set(['.js']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we support .ts
by default as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe? But we don't actually support them out of the box.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. then disregard my comment
throw new NetworkLoadError(response); | ||
} | ||
|
||
let filename = tmp(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We create temporary files in the CLITests. Maybe make a note to use this package for temporary tests files as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I added a comment.
src/index.ts
Outdated
@@ -13,6 +13,7 @@ ${$0} [OPTIONS] [PATH … | --stdio] | |||
|
|||
OPTIONS | |||
-p, --plugin PLUGIN Transform sources with PLUGIN (allows multiple). | |||
---remote-plugin URL Fetch a plugin from URL (allows multiple). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra -
res: ServerResponse | ||
) => void; | ||
|
||
export class RealTestServer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
) {} | ||
|
||
private *enumerateCandidateSources(source: string): IterableIterator<string> { | ||
yield resolve(source); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I misunderstood the flow in this function. optionalExtensions
is used here so that the user can provide a path path/to/filename.js
or path/to/filename
, where currently if extension is omitted, we will try to resolve using this extension array. Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right.
80aef03
to
b9eccd3
Compare
@@ -17,6 +17,10 @@ | |||
"*.ts": [ | |||
"prettier --parser typescript --write", | |||
"git add" | |||
], | |||
"*.md": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remembered that prettier can reformat Markdown, so since I was editing the README anyway I figured I'd add that here.
"resolve": "^1.5.0" | ||
"resolve": "^1.5.0", | ||
"tmp": "^0.0.33", | ||
"whatwg-url": "^6.4.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using this instead of the builtin url
since node v6.x doesn't have url.URL
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests pass now!
import { URL } from 'whatwg-url'; | ||
import NetworkResolver from './NetworkResolver'; | ||
|
||
const EDITOR_HASH_PATTERN = /^#\/gist\/(\w+)(?:\/(\w+))?$/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just changed this to not include the hostname, since we're supposed to be able to customize it in the constructor argument.
b9eccd3
to
05131ea
Compare
Seems fine from my side. You could consider using the github API directly instead of going through astexplorer (this is what astexplorer itself is doing), but probably not a big deal. Note that the structure of the data in the gists could change in the future (in other words, I can't promise that I will remember to notify you about such changes, but I'll try 😉 ) |
Thanks @fkling! I'll probably leave it as-is for now. In the future it might be nice to allow more flexibly pulling files out of gists, something like this: $ codemod --remote-plugin gist:68827467161b95ee48048ff906fab6d8#transform.js Exactly what syntax to use for that I'm not sure. |
Alright, this is now available in v1.4.0! |
Begins work on #33.
@hzoo, what do you think of the proposed
--remote-plugin
option in this PR's README? I made it different from--plugin
out of concern for security. I also think the implementation should probably make you confirm somehow before actually loading it.TODO:
--remote-plugin
to help output--remote-plugin
AstExplorerResolver
andFileSystemResolver
TestServer#protocol
behttp: