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

Adds option for running plugin through babel-preset-env pipeline #31

Merged
merged 1 commit into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,29 @@ For more detailed options, run `codemod --help`.

There are [many, many existing plugins](https://yarnpkg.com/en/packages?q=babel-plugin) that you can use. However, if you need to write your own you should consult the [babel handbook](https://github.com/thejameskyle/babel-handbook). If you publish a plugin intended specifically as a codemod, consider using both the [`babel-plugin`](https://yarnpkg.com/en/packages?q=babel-plugin) and [`babel-codemod`](https://yarnpkg.com/en/packages?q=babel-codemod) keywords.

While testing out your plugin, you may find it useful to use the `--require` option when running `codemod` if your plugin is written using JavaScript syntax not supported by the current version of node. For example:
### Transpiling using babel plugins

`babel-codemod` also supports non-standard/future language features that are not currently supported by the latest version of node. It does this by leveraging `babel-preset-env` which loads the [latest babel plugins](https://github.com/babel/babel/tree/master/packages/babel-preset-env#support-all-plugins-in-babel-that-are-considered-latest). This feature is on by default.

This feature should support most use cases when writing plugins in advanced JavaScript syntax. However, if you are writing plugins with syntax that is beyond "latest", or you would like to use your own set of plugins and presets, you can pass in the `--find-babel-config` switch in combination with a local `.babelrc` file that lists the presets/plugins you want applied to your plugin code.

```
# Run a local plugin written with newer JavaScript syntax.
$ codemod --require babel-register --plugin ./my-plugin.js src/
# Run a local plugin that is passed through locally installed babel plugins
$ codemod --find-babel-config --plugin ./my-plugin.js src/
```

This requires that all babel plugins and presets be installed locally and are listed in your `.babelrc` file. `babel-codemod` uses `babel-register` under the hood too accomplish this and all `.babelrc` [lookup rules apply](https://babeljs.io/docs/usage/babelrc/#lookup-behavior).

### Transpiling using TypeScript
There is currently an [open issue](https://github.com/square/babel-codemod/issues/51) for supporting plugins written in typescript. In the interim, you can take the same approach using `--require` along with `ts-node/register`.

For example:

```
# Run a local plugin written with TypeScript.
$ codemod --require ts-node/register --plugin ./my-plugin.ts src/
```

Note: You'll need to [setup `babel-register`](https://github.com/square/babel-codemod/pull/25#issuecomment-309607661) if you're using `--require`.

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md) for information on setting up the project for development and on contributing to the project.
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
"typescript": "^2.2.1"
},
"dependencies": {
"babel-core": "^6.23.1",
"glob": "^7.1.1",
"mz": "^2.6.0",
"babel-core": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-register": "^6.26.0",
"glob": "^7.1.2",
"mz": "^2.7.0",
"recast": "^0.13.0",
"resolve": "^1.3.2"
"resolve": "^1.5.0"
},
"engines": {
"node": ">=6.0.0"
Expand Down
38 changes: 36 additions & 2 deletions src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Plugin {
readonly rawPlugin: RawBabelPlugin,
readonly inferredName: string,
readonly path?: string,
readonly resolvedPath?: string,
) {
let instance = rawPlugin(Babel);

Expand All @@ -25,6 +26,7 @@ export class Plugin {
}

static load(path: string, inferredName: string) {
let resolvedPath = require.resolve(path);
let exports = require(path);
let plugin;

Expand All @@ -39,7 +41,8 @@ export class Plugin {
return new Plugin(
rawPlugin,
inferredName,
path
path,
resolvedPath
);
}
}
Expand Down Expand Up @@ -67,14 +70,21 @@ export default class Options {
readonly pluginOptions: Map<string, object>,
readonly extensions: Set<string>,
readonly requires: Array<string>,
readonly transpilePlugins: boolean,
readonly findBabelConfig: boolean,
readonly ignore: PathPredicate,
readonly stdio: boolean,
readonly help: boolean,
readonly dry: boolean
) {}

private _pluginCache?: Array<Plugin>;

getPlugins(): Array<Plugin> {
return this.plugins.map(plugin => plugin.load());
if (!this._pluginCache) {
this._pluginCache = this.plugins.map(plugin => plugin.load());
}
return this._pluginCache;
}

loadRequires() {
Expand All @@ -83,6 +93,16 @@ export default class Options {
}
}

loadBabelTranspile() {
let pluginOptions;
if (!this.findBabelConfig) {
pluginOptions = require('babel-preset-env').default();
pluginOptions.babelrc = false; // ignore babelrc file if present
}

require('babel-register')(pluginOptions);
}

getPlugin(name: string): Plugin | null {
for (let plugin of this.getPlugins()) {
if (plugin.declaredName === name || plugin.inferredName === name) {
Expand Down Expand Up @@ -134,6 +154,8 @@ export default class Options {
let extensions = DEFAULT_EXTENSIONS;
let ignore = (path: string, basename: string, root: string) => basename[0] === '.';
let requires: Array<string> = [];
let findBabelConfig = false;
let transpilePlugins = true;
let stdio = false;
let help = false;
let dry = false;
Expand Down Expand Up @@ -177,6 +199,16 @@ export default class Options {
requires.push(getRequirableModulePath(args[i]));
break;

case '--transpile-plugins':
case '--no-transpile-plugins':
transpilePlugins = arg === '--transpile-plugins';
break;

case '--find-babel-config':
case '--no-find-babel-config':
findBabelConfig = arg === '--find-babel-config';
break;

case '--extensions':
i++;
extensions = new Set(
Expand Down Expand Up @@ -221,6 +253,8 @@ export default class Options {
pluginOptions,
extensions,
requires,
transpilePlugins,
findBabelConfig,
ignore,
stdio,
help,
Expand Down
8 changes: 5 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ OPTIONS
-o, --plugin-options PLUGIN=OPTS JSON-encoded OPTS for PLUGIN (allows multiple).
-r, --require PATH Require PATH before transform (allows multiple).
--extensions EXTS Comma-separated extensions to process (default: "${Array.from(DEFAULT_EXTENSIONS).join(',')}").
--[no-]transpile-plugins Transpile plugins to enable future syntax (default: on).
--[no-]find-babel-config Run plugins through babel plugins/presets specified in local
.babelrc file instead of babel-preset-env (default: off).
-s, --stdio Read source from stdin and print to stdout.
-h, --help Show this help message.
-d, --dry Run plugins without modifying files on disk.
Expand All @@ -39,9 +42,6 @@ EXAMPLES
# Pass options from a config file to a plugin.
$ ${$0} -p ./a.js -o [email protected] src/

# Run with a plugin which itself is transpiled using babel.
$ ${$0} -r babel-register -p ./some-plugin.js src/

# Run with a plugin written in TypeScript.
$ ${$0} -r ts-node/register -p ./some-plugin.ts src/
`.trim());
Expand All @@ -68,6 +68,8 @@ export default async function run(
return 0;
}

options.loadBabelTranspile();

options.loadRequires();

let plugins = options.getBabelPlugins();
Expand Down
49 changes: 49 additions & 0 deletions test/CLITest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,53 @@ describe('CLI', function() {
'3 + 4;'
);
});

it('can load plugins written with ES modules by default', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([afile, '-p', plugin('increment-export-default')]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(
await readFile(afile, 'utf8'),
'4 + 5;'
);
});

it('can load plugins with multiple files with ES modules by default`', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let pluginFile = join(__dirname, `fixtures/plugin/increment-export-default-multiple/increment-export-default.js`);
let { status, stdout, stderr } = await runCodemodCLI([afile, '-p', pluginFile]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 1 modified, 0 errors\n`,
stderr: ''
}
);
strictEqual(
await readFile(afile, 'utf8'),
'4 + 5;'
);
});

it('fails when specifying --find-babel-config as there are no plugins loaded', async function() {
let afile = await createTemporaryFile('a-file.js', '3 + 4;');
let { status, stdout, stderr } = await runCodemodCLI([afile, '-p', plugin('increment-export-default'), '--find-babel-config']);

ok(
/SyntaxError: Unexpected token export/.test(stderr),
`error should reference invalid syntax: ${stderr}`
);
strictEqual(stdout, '');
strictEqual(status, 255);
});
});
5 changes: 5 additions & 0 deletions test/OptionsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ describe('Options', function() {
strictEqual(options.dry, true);
});

it('should set useLocalBabel', function() {
let options = assertOptionsParsed(Options.parse(['--find-babel-config']));
strictEqual(options.findBabelConfig, true);
});

function assertOptionsParsed(result: ParseOptionsResult): Options {
if (result instanceof Options) {
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import incrementValue from './increment-value'

export default function() {
return {
visitor: {
NumericLiteral(path) {
path.node.value = incrementValue(path.node.value);
}
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import path from 'path'

export default function incrementValue(value) {
return value + 1
}
9 changes: 9 additions & 0 deletions test/fixtures/plugin/increment-export-default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function() {
return {
visitor: {
NumericLiteral(path) {
path.node.value += 1;
}
}
}
};
Loading