diff --git a/src/AllSyntaxPlugin.ts b/src/AllSyntaxPlugin.ts index 3ff4e837..c012136b 100644 --- a/src/AllSyntaxPlugin.ts +++ b/src/AllSyntaxPlugin.ts @@ -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 = [ @@ -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; - } +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; + } + }; }; } diff --git a/src/Config.ts b/src/Config.ts index 4bd3a6a3..40b4f1d0 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -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'; @@ -50,6 +51,7 @@ export default class Config { readonly pluginOptions: Map = new Map(), readonly printer: Printer = Printer.Recast, readonly extensions: Set = TransformableExtensions, + readonly sourceType: ParserOptions['sourceType'] = 'unambiguous', readonly requires: Array = [], readonly transpilePlugins: boolean = true, readonly findBabelConfig: boolean = false, @@ -132,7 +134,7 @@ export default class Config { } async getBabelPlugins(): Promise> { - let result: Array = [AllSyntaxPlugin]; + let result: Array = [buildAllSyntaxPlugin(this.sourceType)]; switch (this.printer) { case Printer.Recast: @@ -187,6 +189,7 @@ export class ConfigBuilder { private _pluginOptions?: Map; private _printer?: Printer; private _extensions: Set = new Set(TransformableExtensions); + private _sourceType: ParserOptions['sourceType'] = 'module'; private _requires?: Array; private _transpilePlugins?: boolean; private _findBabelConfig?: boolean; @@ -271,6 +274,11 @@ export class ConfigBuilder { return this; } + sourceType(value: ParserOptions['sourceType']): this { + this._sourceType = value; + return this; + } + requires(value: Array): this { this._requires = value; return this; @@ -317,6 +325,7 @@ export class ConfigBuilder { this._pluginOptions, this._printer, this._extensions, + this._sourceType, this._requires, this._transpilePlugins, this._findBabelConfig, diff --git a/src/InlineTransformer.ts b/src/InlineTransformer.ts index b075ccbf..70952731 100644 --- a/src/InlineTransformer.ts +++ b/src/InlineTransformer.ts @@ -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 { diff --git a/src/Options.ts b/src/Options.ts index c4b3ca77..738035cc 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -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); diff --git a/src/index.ts b/src/index.ts index dbf4c760..fb8229da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 )}. diff --git a/src/transpile-requires.ts b/src/transpile-requires.ts index 310d0d43..8c223d40 100644 --- a/src/transpile-requires.ts +++ b/src/transpile-requires.ts @@ -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; @@ -19,7 +19,7 @@ export function hook(code: string, filename: string): string { filename, babelrc: useBabelrc, presets: presets, - plugins: [AllSyntaxPlugin], + plugins: [buildAllSyntaxPlugin('module')], sourceMaps: 'inline' }; diff --git a/test/cli/CLITest.ts b/test/cli/CLITest.ts index 15cb31ea..b58cbb54 100644 --- a/test/cli/CLITest.ts +++ b/test/cli/CLITest.ts @@ -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: '' }); + }); });