Skip to content

Commit 3e28978

Browse files
feat: let babel guess the source type
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
1 parent 672b81b commit 3e28978

File tree

7 files changed

+140
-21
lines changed

7 files changed

+140
-21
lines changed

src/AllSyntaxPlugin.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Babel from '@babel/core';
22
import { ParserPlugin } from '@babel/parser';
33
import { extname } from 'path';
4-
import { PluginObj } from './BabelPluginTypes';
4+
import { BabelPlugin, PluginObj } from './BabelPluginTypes';
55
import { TypeScriptExtensions } from './extensions';
66

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

29-
export default function(babel: typeof Babel): PluginObj {
30-
return {
31-
manipulateOptions(
32-
opts: Babel.TransformOptions,
33-
parserOpts: Babel.ParserOptions
34-
): void {
35-
parserOpts.sourceType = 'module';
36-
parserOpts.allowImportExportEverywhere = true;
37-
parserOpts.allowReturnOutsideFunction = true;
38-
parserOpts.allowSuperOutsideMethod = true;
39-
// Cast this because @babel/types typings don't allow plugin options.
40-
parserOpts.plugins = [
41-
...(parserOpts.plugins || []),
42-
...pluginsForFilename(opts.filename as string)
43-
] as Array<ParserPlugin>;
44-
}
29+
export default function buildPlugin(
30+
sourceType: Babel.ParserOptions['sourceType']
31+
): BabelPlugin {
32+
return function(babel: typeof Babel): PluginObj {
33+
return {
34+
manipulateOptions(
35+
opts: Babel.TransformOptions,
36+
parserOpts: Babel.ParserOptions
37+
): void {
38+
parserOpts.sourceType = sourceType;
39+
parserOpts.allowImportExportEverywhere = true;
40+
parserOpts.allowReturnOutsideFunction = true;
41+
parserOpts.allowSuperOutsideMethod = true;
42+
// Cast this because @babel/types typings don't allow plugin options.
43+
parserOpts.plugins = [
44+
...(parserOpts.plugins || []),
45+
...pluginsForFilename(opts.filename as string)
46+
] as Array<ParserPlugin>;
47+
}
48+
};
4549
};
4650
}

src/Config.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as Babel from '@babel/core';
2+
import { ParserOptions } from '@babel/parser';
23
import { basename, extname } from 'path';
34
import { install } from 'source-map-support';
4-
import AllSyntaxPlugin from './AllSyntaxPlugin';
5+
import buildAllSyntaxPlugin from './AllSyntaxPlugin';
56
import { BabelPlugin, RawBabelPlugin } from './BabelPluginTypes';
67
import BabelPrinterPlugin from './BabelPrinterPlugin';
78
import { TransformableExtensions } from './extensions';
@@ -50,6 +51,7 @@ export default class Config {
5051
readonly pluginOptions: Map<string, object> = new Map<string, object>(),
5152
readonly printer: Printer = Printer.Recast,
5253
readonly extensions: Set<string> = TransformableExtensions,
54+
readonly sourceType: ParserOptions['sourceType'] = 'unambiguous',
5355
readonly requires: Array<string> = [],
5456
readonly transpilePlugins: boolean = true,
5557
readonly findBabelConfig: boolean = false,
@@ -132,7 +134,7 @@ export default class Config {
132134
}
133135

134136
async getBabelPlugins(): Promise<Array<BabelPlugin>> {
135-
let result: Array<BabelPlugin> = [AllSyntaxPlugin];
137+
let result: Array<BabelPlugin> = [buildAllSyntaxPlugin(this.sourceType)];
136138

137139
switch (this.printer) {
138140
case Printer.Recast:
@@ -187,6 +189,7 @@ export class ConfigBuilder {
187189
private _pluginOptions?: Map<string, object>;
188190
private _printer?: Printer;
189191
private _extensions: Set<string> = new Set(TransformableExtensions);
192+
private _sourceType: ParserOptions['sourceType'] = 'module';
190193
private _requires?: Array<string>;
191194
private _transpilePlugins?: boolean;
192195
private _findBabelConfig?: boolean;
@@ -271,6 +274,11 @@ export class ConfigBuilder {
271274
return this;
272275
}
273276

277+
sourceType(value: ParserOptions['sourceType']): this {
278+
this._sourceType = value;
279+
return this;
280+
}
281+
274282
requires(value: Array<string>): this {
275283
this._requires = value;
276284
return this;
@@ -317,6 +325,7 @@ export class ConfigBuilder {
317325
this._pluginOptions,
318326
this._printer,
319327
this._extensions,
328+
this._sourceType,
320329
this._requires,
321330
this._transpilePlugins,
322331
this._findBabelConfig,

src/InlineTransformer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { transformAsync } from '@babel/core';
22
import { BabelPlugin } from './BabelPluginTypes';
3+
import Config from './Config';
34
import Transformer from './Transformer';
45

56
export default class InlineTransformer implements Transformer {

src/Options.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ export default class Options {
111111
config.addExtension(this.args[i]);
112112
break;
113113

114+
case '--source-type': {
115+
i++;
116+
let sourceType = this.args[i];
117+
if (
118+
sourceType === 'module' ||
119+
sourceType === 'script' ||
120+
sourceType === 'unambiguous'
121+
) {
122+
config.sourceType(sourceType);
123+
} else {
124+
throw new Error(
125+
`expected '--source-type' to be one of "module", "script", ` +
126+
`or "unambiguous" but got: "${sourceType}"`
127+
);
128+
}
129+
break;
130+
}
131+
114132
case '-s':
115133
case '--stdio':
116134
config.stdio(true);

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ OPTIONS
5454
--extensions EXTS Comma-separated extensions to process (default: "${Array.from(
5555
defaults.extensions
5656
).join(',')}").
57+
--source-type Parse as "module", "script", or "unambiguous" (meaning babel
58+
will try to guess, default: "${
59+
defaults.sourceType
60+
}").
5761
--[no-]transpile-plugins Transpile plugins to enable future syntax${optionAnnotation(
5862
defaults.transpilePlugins
5963
)}.

src/transpile-requires.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { transformSync, TransformOptions } from '@babel/core';
22
import { extname } from 'path';
33
import { addHook } from 'pirates';
4-
import AllSyntaxPlugin from './AllSyntaxPlugin';
4+
import buildAllSyntaxPlugin from './AllSyntaxPlugin';
55
import { PluginExtensions, TypeScriptExtensions } from './extensions';
66

77
let useBabelrc = false;
@@ -19,7 +19,7 @@ export function hook(code: string, filename: string): string {
1919
filename,
2020
babelrc: useBabelrc,
2121
presets: presets,
22-
plugins: [AllSyntaxPlugin],
22+
plugins: [buildAllSyntaxPlugin('module')],
2323
sourceMaps: 'inline'
2424
};
2525

test/cli/CLITest.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,87 @@ describe('CLI', function() {
474474
'type A = any;\nlet a = {} as any;\n'
475475
);
476476
});
477+
478+
it('can specify the source type as "script"', async function() {
479+
let afile = await createTemporaryFile(
480+
'a-file.js',
481+
'with (a) { b; }' // `with` statements aren't allowed in modules
482+
);
483+
let { status, stdout, stderr } = await runCodemodCLI([
484+
afile,
485+
'--source-type',
486+
'script'
487+
]);
488+
489+
deepEqual(
490+
{ status, stdout, stderr },
491+
{
492+
status: 0,
493+
stdout: `${afile}\n1 file(s), 0 modified, 0 errors\n`,
494+
stderr: ''
495+
}
496+
);
497+
});
498+
499+
it('can specify the source type as "module"', async function() {
500+
let afile = await createTemporaryFile(
501+
'a-file.js',
502+
'import "./b-file"' // `import` statements aren't allowed in scripts
503+
);
504+
let { status, stdout, stderr } = await runCodemodCLI([
505+
afile,
506+
'--source-type',
507+
'module'
508+
]);
509+
510+
deepEqual(
511+
{ status, stdout, stderr },
512+
{
513+
status: 0,
514+
stdout: `${afile}\n1 file(s), 0 modified, 0 errors\n`,
515+
stderr: ''
516+
}
517+
);
518+
});
519+
520+
it('can specify the source type as "unambiguous"', async function() {
521+
let afile = await createTemporaryFile(
522+
'a-file.js',
523+
'with (a) { b; }' // `with` statements aren't allowed in modules
524+
);
525+
let bfile = await createTemporaryFile(
526+
'b-file.js',
527+
'import "./a-file"' // `import` statements aren't allowed in scripts
528+
);
529+
let { status, stdout, stderr } = await runCodemodCLI([
530+
afile,
531+
bfile,
532+
'--source-type',
533+
'unambiguous'
534+
]);
535+
536+
deepEqual(
537+
{ status, stdout, stderr },
538+
{
539+
status: 0,
540+
stdout: `${afile}\n${bfile}\n2 file(s), 0 modified, 0 errors\n`,
541+
stderr: ''
542+
}
543+
);
544+
});
545+
546+
it('fails when given an invalid source type', async function() {
547+
let { status, stdout, stderr } = await runCodemodCLI([
548+
'--source-type',
549+
'hypercard'
550+
]);
551+
let expectedPrefix = `ERROR: expected '--source-type' to be one of "module", "script", or "unambiguous" but got: "hypercard"`;
552+
553+
ok(
554+
stderr.startsWith(expectedPrefix),
555+
`expected stderr to start with error but got:\n${stderr}`
556+
);
557+
558+
deepEqual({ status, stdout }, { status: 1, stdout: '' });
559+
});
477560
});

0 commit comments

Comments
 (0)