Skip to content

Commit

Permalink
feat: let babel guess the source type
Browse files Browse the repository at this point in the history
Previously babel-codemod hardcoded a value of `module` for the source type when parsing. This meant that any code not valid in strict mode could not be processed. Now babel-codemod uses a default source type of `unambiguous`, which means that babel will try to infer the source type. Additionally, this source type can be overridden with the `--source-type` option.

Closes #175
  • Loading branch information
eventualbuddha committed Nov 24, 2018
1 parent 672b81b commit 3e28978
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 21 deletions.
38 changes: 21 additions & 17 deletions src/AllSyntaxPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Babel from '@babel/core';
import { ParserPlugin } from '@babel/parser';
import { extname } from 'path';
import { PluginObj } from './BabelPluginTypes';
import { BabelPlugin, PluginObj } from './BabelPluginTypes';
import { TypeScriptExtensions } from './extensions';

const BASIC_PLUGINS: Array<ParserPlugin | [ParserPlugin, object]> = [
Expand All @@ -26,21 +26,25 @@ function pluginsForFilename(
: [...BASIC_PLUGINS, 'flow'];
}

export default function(babel: typeof Babel): PluginObj {
return {
manipulateOptions(
opts: Babel.TransformOptions,
parserOpts: Babel.ParserOptions
): void {
parserOpts.sourceType = 'module';
parserOpts.allowImportExportEverywhere = true;
parserOpts.allowReturnOutsideFunction = true;
parserOpts.allowSuperOutsideMethod = true;
// Cast this because @babel/types typings don't allow plugin options.
parserOpts.plugins = [
...(parserOpts.plugins || []),
...pluginsForFilename(opts.filename as string)
] as Array<ParserPlugin>;
}
export default function buildPlugin(
sourceType: Babel.ParserOptions['sourceType']
): BabelPlugin {
return function(babel: typeof Babel): PluginObj {
return {
manipulateOptions(
opts: Babel.TransformOptions,
parserOpts: Babel.ParserOptions
): void {
parserOpts.sourceType = sourceType;
parserOpts.allowImportExportEverywhere = true;
parserOpts.allowReturnOutsideFunction = true;
parserOpts.allowSuperOutsideMethod = true;
// Cast this because @babel/types typings don't allow plugin options.
parserOpts.plugins = [
...(parserOpts.plugins || []),
...pluginsForFilename(opts.filename as string)
] as Array<ParserPlugin>;
}
};
};
}
13 changes: 11 additions & 2 deletions src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Babel from '@babel/core';
import { ParserOptions } from '@babel/parser';
import { basename, extname } from 'path';
import { install } from 'source-map-support';
import AllSyntaxPlugin from './AllSyntaxPlugin';
import buildAllSyntaxPlugin from './AllSyntaxPlugin';
import { BabelPlugin, RawBabelPlugin } from './BabelPluginTypes';
import BabelPrinterPlugin from './BabelPrinterPlugin';
import { TransformableExtensions } from './extensions';
Expand Down Expand Up @@ -50,6 +51,7 @@ export default class Config {
readonly pluginOptions: Map<string, object> = new Map<string, object>(),
readonly printer: Printer = Printer.Recast,
readonly extensions: Set<string> = TransformableExtensions,
readonly sourceType: ParserOptions['sourceType'] = 'unambiguous',
readonly requires: Array<string> = [],
readonly transpilePlugins: boolean = true,
readonly findBabelConfig: boolean = false,
Expand Down Expand Up @@ -132,7 +134,7 @@ export default class Config {
}

async getBabelPlugins(): Promise<Array<BabelPlugin>> {
let result: Array<BabelPlugin> = [AllSyntaxPlugin];
let result: Array<BabelPlugin> = [buildAllSyntaxPlugin(this.sourceType)];

switch (this.printer) {
case Printer.Recast:
Expand Down Expand Up @@ -187,6 +189,7 @@ export class ConfigBuilder {
private _pluginOptions?: Map<string, object>;
private _printer?: Printer;
private _extensions: Set<string> = new Set(TransformableExtensions);
private _sourceType: ParserOptions['sourceType'] = 'module';
private _requires?: Array<string>;
private _transpilePlugins?: boolean;
private _findBabelConfig?: boolean;
Expand Down Expand Up @@ -271,6 +274,11 @@ export class ConfigBuilder {
return this;
}

sourceType(value: ParserOptions['sourceType']): this {
this._sourceType = value;
return this;
}

requires(value: Array<string>): this {
this._requires = value;
return this;
Expand Down Expand Up @@ -317,6 +325,7 @@ export class ConfigBuilder {
this._pluginOptions,
this._printer,
this._extensions,
this._sourceType,
this._requires,
this._transpilePlugins,
this._findBabelConfig,
Expand Down
1 change: 1 addition & 0 deletions src/InlineTransformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { transformAsync } from '@babel/core';
import { BabelPlugin } from './BabelPluginTypes';
import Config from './Config';
import Transformer from './Transformer';

export default class InlineTransformer implements Transformer {
Expand Down
18 changes: 18 additions & 0 deletions src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ export default class Options {
config.addExtension(this.args[i]);
break;

case '--source-type': {
i++;
let sourceType = this.args[i];
if (
sourceType === 'module' ||
sourceType === 'script' ||
sourceType === 'unambiguous'
) {
config.sourceType(sourceType);
} else {
throw new Error(
`expected '--source-type' to be one of "module", "script", ` +
`or "unambiguous" but got: "${sourceType}"`
);
}
break;
}

case '-s':
case '--stdio':
config.stdio(true);
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ OPTIONS
--extensions EXTS Comma-separated extensions to process (default: "${Array.from(
defaults.extensions
).join(',')}").
--source-type Parse as "module", "script", or "unambiguous" (meaning babel
will try to guess, default: "${
defaults.sourceType
}").
--[no-]transpile-plugins Transpile plugins to enable future syntax${optionAnnotation(
defaults.transpilePlugins
)}.
Expand Down
4 changes: 2 additions & 2 deletions src/transpile-requires.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { transformSync, TransformOptions } from '@babel/core';
import { extname } from 'path';
import { addHook } from 'pirates';
import AllSyntaxPlugin from './AllSyntaxPlugin';
import buildAllSyntaxPlugin from './AllSyntaxPlugin';
import { PluginExtensions, TypeScriptExtensions } from './extensions';

let useBabelrc = false;
Expand All @@ -19,7 +19,7 @@ export function hook(code: string, filename: string): string {
filename,
babelrc: useBabelrc,
presets: presets,
plugins: [AllSyntaxPlugin],
plugins: [buildAllSyntaxPlugin('module')],
sourceMaps: 'inline'
};

Expand Down
83 changes: 83 additions & 0 deletions test/cli/CLITest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,4 +474,87 @@ describe('CLI', function() {
'type A = any;\nlet a = {} as any;\n'
);
});

it('can specify the source type as "script"', async function() {
let afile = await createTemporaryFile(
'a-file.js',
'with (a) { b; }' // `with` statements aren't allowed in modules
);
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'--source-type',
'script'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 0 modified, 0 errors\n`,
stderr: ''
}
);
});

it('can specify the source type as "module"', async function() {
let afile = await createTemporaryFile(
'a-file.js',
'import "./b-file"' // `import` statements aren't allowed in scripts
);
let { status, stdout, stderr } = await runCodemodCLI([
afile,
'--source-type',
'module'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n1 file(s), 0 modified, 0 errors\n`,
stderr: ''
}
);
});

it('can specify the source type as "unambiguous"', async function() {
let afile = await createTemporaryFile(
'a-file.js',
'with (a) { b; }' // `with` statements aren't allowed in modules
);
let bfile = await createTemporaryFile(
'b-file.js',
'import "./a-file"' // `import` statements aren't allowed in scripts
);
let { status, stdout, stderr } = await runCodemodCLI([
afile,
bfile,
'--source-type',
'unambiguous'
]);

deepEqual(
{ status, stdout, stderr },
{
status: 0,
stdout: `${afile}\n${bfile}\n2 file(s), 0 modified, 0 errors\n`,
stderr: ''
}
);
});

it('fails when given an invalid source type', async function() {
let { status, stdout, stderr } = await runCodemodCLI([
'--source-type',
'hypercard'
]);
let expectedPrefix = `ERROR: expected '--source-type' to be one of "module", "script", or "unambiguous" but got: "hypercard"`;

ok(
stderr.startsWith(expectedPrefix),
`expected stderr to start with error but got:\n${stderr}`
);

deepEqual({ status, stdout }, { status: 1, stdout: '' });
});
});

0 comments on commit 3e28978

Please sign in to comment.