diff --git a/.flowconfig b/.flowconfig index ba9239f778..9097683673 100644 --- a/.flowconfig +++ b/.flowconfig @@ -15,3 +15,4 @@ lib/third-party-libs.js.flow [options] suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe suppress_comment= \\(.\\|\n\\)*\\$FlowIssue +esproposal.export_star_as=enable diff --git a/lib/third-party-libs.js.flow b/lib/third-party-libs.js.flow index 1aefc40618..97d19dfda2 100644 --- a/lib/third-party-libs.js.flow +++ b/lib/third-party-libs.js.flow @@ -31,3 +31,36 @@ declare module "lodash/clone" { declare module "lodash/merge" { declare export default (T, Object) => T; } + +declare module "convert-source-map" { + declare export type SourceMap = { + version: 3, + file: ?string, + sources: [?string], + sourcesContent: [?string], + names: [?string], + mappings: string, + }; + + declare class Converter { + toJSON(): string; + toBase64(): string; + toComment(): string; + toObject(): SourceMap + } + + declare module.exports: { + SourceMap: SourceMap, + Converter: Converter, + fromObject(obj: SourceMap): Converter, + fromJSON(str: string): Converter, + fromBase64(str: string): Converter, + fromComment(str: string): Converter, + fromMapFileComment(str: string): Converter, + fromSource(str: string): Converter, + fromMapFileSource(str: string): Converter, + removeComments(str: string): string, + removeMapFileComments(str: string): string, + generateMapFileComment(path: string, options?: ?{ multiline: boolean }): string, + }; +} diff --git a/packages/babel-core/src/config/index.js b/packages/babel-core/src/config/index.js index f00fd0e924..61684308e3 100644 --- a/packages/babel-core/src/config/index.js +++ b/packages/babel-core/src/config/index.js @@ -5,9 +5,13 @@ import manageOptions from "./option-manager"; export type ResolvedConfig = { options: Object, - passes: Array>, + passes: PluginPasses, }; +export type { Plugin }; +export type PluginPassList = Array<[Plugin, ?{}]>; +export type PluginPasses = Array; + /** * Standard API for loading Babel configuration data. Not for public consumption. */ diff --git a/packages/babel-core/src/config/plugin.js b/packages/babel-core/src/config/plugin.js index 84b3701061..58a768ffa2 100644 --- a/packages/babel-core/src/config/plugin.js +++ b/packages/babel-core/src/config/plugin.js @@ -1,6 +1,12 @@ // @flow export default class Plugin { + key: ?string; + manipulateOptions: ?Function; + post: ?Function; + pre: ?Function; + visitor: ?{}; + constructor(plugin: {}, key?: string) { if (plugin.name != null && typeof plugin.name !== "string") { throw new Error("Plugin .name must be a string, null, or undefined"); @@ -30,10 +36,4 @@ export default class Plugin { this.pre = plugin.pre; this.visitor = plugin.visitor; } - - key: ?string; - manipulateOptions: ?Function; - post: ?Function; - pre: ?Function; - visitor: ?{}; } diff --git a/packages/babel-core/src/index.js b/packages/babel-core/src/index.js index 998bbd835f..4281a15976 100644 --- a/packages/babel-core/src/index.js +++ b/packages/babel-core/src/index.js @@ -1,17 +1,20 @@ -export File from "./transformation/file"; -export buildExternalHelpers from "./tools/build-external-helpers"; +// @flow + +export { + default as buildExternalHelpers, +} from "./tools/build-external-helpers"; export { resolvePlugin, resolvePreset } from "./config/loading/files"; export { version } from "../package"; export { getEnv } from "./config/helpers/environment"; export * as types from "babel-types"; -export traverse from "babel-traverse"; -export template from "babel-template"; +export { default as traverse } from "babel-traverse"; +export { default as template } from "babel-template"; import loadConfig from "./config"; -export function loadOptions(opts): Object | null { +export function loadOptions(opts: {}): Object | null { const config = loadConfig(opts); return config ? config.options : null; @@ -19,21 +22,21 @@ export function loadOptions(opts): Object | null { // For easier backward-compatibility, provide an API like the one we exposed in Babel 6. export class OptionManager { - init(opts) { + init(opts: {}) { return loadOptions(opts); } } -export function Plugin(alias) { - throw new Error(`The (${alias}) Babel 5 plugin is being run with Babel 6.`); +export function Plugin(alias: string) { + throw new Error( + `The (${alias}) Babel 5 plugin is being run with an unsupported Babel version.`, + ); } -export { - transform, - transformFromAst, - transformFile, - transformFileSync, -} from "./transformation"; +export { default as transform } from "./transform"; +export { default as transformFromAst } from "./transform-ast"; +export { default as transformFile } from "./transform-file"; +export { default as transformFileSync } from "./transform-file-sync"; /** * Recommended set of compilable extensions. Not used in babel-core directly, but meant as diff --git a/packages/babel-core/src/transform-ast.js b/packages/babel-core/src/transform-ast.js new file mode 100644 index 0000000000..85b94063b8 --- /dev/null +++ b/packages/babel-core/src/transform-ast.js @@ -0,0 +1,21 @@ +// @flow +import * as t from "babel-types"; +import loadConfig from "./config"; +import runTransform, { type FileResult } from "./transformation"; + +export default function transformFromAst( + ast: Object, + code: string, + opts: Object, +): FileResult | null { + const config = loadConfig(opts); + if (config === null) return null; + + if (ast && ast.type === "Program") { + ast = t.file(ast, [], []); + } else if (!ast || ast.type !== "File") { + throw new Error("Not a valid ast?"); + } + + return runTransform(config, code, ast); +} diff --git a/packages/babel-core/src/transform-file-sync.js b/packages/babel-core/src/transform-file-sync.js new file mode 100644 index 0000000000..d0a2d2823b --- /dev/null +++ b/packages/babel-core/src/transform-file-sync.js @@ -0,0 +1,16 @@ +// @flow +import fs from "fs"; + +import loadConfig from "./config"; +import runTransform, { type FileResult } from "./transformation"; + +export default function transformFileSync( + filename: string, + opts?: Object = {}, +): FileResult | null { + opts.filename = filename; + const config = loadConfig(opts); + if (config === null) return null; + + return runTransform(config, fs.readFileSync(filename, "utf8")); +} diff --git a/packages/babel-core/src/transform-file.js b/packages/babel-core/src/transform-file.js new file mode 100644 index 0000000000..71f576be09 --- /dev/null +++ b/packages/babel-core/src/transform-file.js @@ -0,0 +1,32 @@ +// @flow +import fs from "fs"; + +import loadConfig from "./config"; +import runTransform, { type FileResult } from "./transformation"; + +export default function transformFile( + filename: string, + opts?: Object = {}, + callback: (?Error, FileResult | null) => void, +) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + + opts.filename = filename; + const config = loadConfig(opts); + if (config === null) return callback(null, null); + + fs.readFile(filename, "utf8", function(err, code: string) { + if (err) return callback(err, null); + + let result; + try { + result = runTransform(config, code); + } catch (_err) { + return callback(err, null); + } + callback(null, result); + }); +} diff --git a/packages/babel-core/src/transform.js b/packages/babel-core/src/transform.js new file mode 100644 index 0000000000..cfea9983c7 --- /dev/null +++ b/packages/babel-core/src/transform.js @@ -0,0 +1,13 @@ +// @flow +import loadConfig from "./config"; +import runTransform, { type FileResult } from "./transformation"; + +export default function transform( + code: string, + opts?: Object, +): FileResult | null { + const config = loadConfig(opts); + if (config === null) return null; + + return runTransform(config, code); +} diff --git a/packages/babel-core/src/transformation/internal-plugins/block-hoist.js b/packages/babel-core/src/transformation/block-hoist-plugin.js similarity index 60% rename from packages/babel-core/src/transformation/internal-plugins/block-hoist.js rename to packages/babel-core/src/transformation/block-hoist-plugin.js index 1682670314..f307c09e89 100644 --- a/packages/babel-core/src/transformation/internal-plugins/block-hoist.js +++ b/packages/babel-core/src/transformation/block-hoist-plugin.js @@ -1,6 +1,28 @@ +// @flow + import sortBy from "lodash/sortBy"; -export default { +import loadConfig, { type Plugin } from "../config"; + +let LOADED_PLUGIN: Plugin | void; + +export default function loadBlockHoistPlugin(): [Plugin, void] { + if (!LOADED_PLUGIN) { + // Lazy-init the internal plugin to remove the init-time circular + // dependency between plugins being passed babel-core's export object, + // which loads this file, and this 'loadConfig' loading plugins. + const config = loadConfig({ + babelrc: false, + plugins: [blockHoistPlugin], + }); + LOADED_PLUGIN = config ? config.passes[0][0][0] : undefined; + if (!LOADED_PLUGIN) throw new Error("Assertion failure"); + } + + return [LOADED_PLUGIN, undefined]; +} + +const blockHoistPlugin = { /** * [Please add a description.] * diff --git a/packages/babel-core/src/transformation/file/file.js b/packages/babel-core/src/transformation/file/file.js new file mode 100644 index 0000000000..aec4db3c04 --- /dev/null +++ b/packages/babel-core/src/transformation/file/file.js @@ -0,0 +1,212 @@ +// @flow + +import getHelper from "babel-helpers"; +import { NodePath, Hub, Scope } from "babel-traverse"; +import { codeFrameColumns } from "babel-code-frame"; +import traverse from "babel-traverse"; +import * as t from "babel-types"; + +import type { NormalizedFile } from "../normalize-file"; + +const errorVisitor = { + enter(path, state) { + const loc = path.node.loc; + if (loc) { + state.loc = loc; + path.stop(); + } + }, +}; + +export default class File { + _map: Map = new Map(); + opts: Object; + declarations: Object = {}; + path: NodePath = null; + ast: Object = {}; + scope: Scope; + metadata: {} = {}; + hub: Hub = new Hub(this); + code: string = ""; + shebang: string | null = ""; + inputMap: Object | null = null; + + constructor(options: {}, { code, ast, shebang, inputMap }: NormalizedFile) { + this.opts = options; + this.code = code; + this.ast = ast; + this.shebang = shebang; + this.inputMap = inputMap; + + this.path = NodePath.get({ + hub: this.hub, + parentPath: null, + parent: this.ast, + container: this.ast, + key: "program", + }).setContext(); + this.scope = this.path.scope; + } + + set(key: mixed, val: mixed) { + this._map.set(key, val); + } + + get(key: mixed): any { + return this._map.get(key); + } + + getModuleName(): ?string { + const opts = this.opts; + if (!opts.moduleIds) { + return null; + } + + // moduleId is n/a if a `getModuleId()` is provided + if (opts.moduleId != null && !opts.getModuleId) { + return opts.moduleId; + } + + let filenameRelative = opts.filenameRelative; + let moduleName = ""; + + if (opts.moduleRoot != null) { + moduleName = opts.moduleRoot + "/"; + } + + if (!opts.filenameRelative) { + return moduleName + opts.filename.replace(/^\//, ""); + } + + if (opts.sourceRoot != null) { + // remove sourceRoot from filename + const sourceRootRegEx = new RegExp("^" + opts.sourceRoot + "/?"); + filenameRelative = filenameRelative.replace(sourceRootRegEx, ""); + } + + // remove extension + filenameRelative = filenameRelative.replace(/\.(\w*?)$/, ""); + + moduleName += filenameRelative; + + // normalize path separators + moduleName = moduleName.replace(/\\/g, "/"); + + if (opts.getModuleId) { + // If return is falsy, assume they want us to use our generated default name + return opts.getModuleId(moduleName) || moduleName; + } else { + return moduleName; + } + } + + // TODO: Remove this before 7.x's official release. Leaving it in for now to + // prevent unnecessary breakage between beta versions. + resolveModuleSource(source: string): string { + return source; + } + + addImport() { + throw new Error( + "This API has been removed. If you're looking for this " + + "functionality in Babel 7, you should import the " + + "'babel-helper-module-imports' module and use the functions exposed " + + " from that module, such as 'addNamed' or 'addDefault'.", + ); + } + + addHelper(name: string): Object { + const declar = this.declarations[name]; + if (declar) return declar; + + const generator = this.get("helperGenerator"); + const runtime = this.get("helpersNamespace"); + if (generator) { + const res = generator(name); + if (res) return res; + } else if (runtime) { + return t.memberExpression(runtime, t.identifier(name)); + } + + const uid = (this.declarations[name] = this.scope.generateUidIdentifier( + name, + )); + + const { nodes, globals } = getHelper( + name, + name => this.addHelper(name), + uid, + () => Object.keys(this.scope.getAllBindings()), + ); + + globals.forEach(name => { + if (this.path.scope.hasBinding(name, true /* noGlobals */)) { + this.path.scope.rename(name); + } + }); + + nodes.forEach(node => { + node._compact = true; + }); + + this.path.unshiftContainer("body", nodes); + // TODO: NodePath#unshiftContainer should automatically register new + // bindings. + this.path.get("body").forEach(path => { + if (nodes.indexOf(path.node) === -1) return; + if (path.isVariableDeclaration()) this.scope.registerDeclaration(path); + }); + + return uid; + } + + addTemplateObject() { + throw new Error( + "This function has been moved into the template literal transform itself.", + ); + } + + buildCodeFrameError( + node: ?{ + loc?: { line: number, column: number }, + _loc?: { line: number, column: number }, + }, + msg: string, + Error: typeof Error = SyntaxError, + ): Error { + let loc = node && (node.loc || node._loc); + + msg = `${this.opts.filename}: ${msg}`; + + if (!loc && node) { + const state = { + loc: null, + }; + traverse(node, errorVisitor, this.scope, state); + loc = state.loc; + + let txt = + "This is an error on an internal node. Probably an internal error."; + if (loc) txt += " Location has been estimated."; + + msg += ` (${txt})`; + } + + if (loc) { + msg += + "\n" + + codeFrameColumns( + this.code, + { + start: { + line: loc.line, + column: loc.column + 1, + }, + }, + this.opts, + ); + } + + return new Error(msg); + } +} diff --git a/packages/babel-core/src/transformation/file/generate.js b/packages/babel-core/src/transformation/file/generate.js new file mode 100644 index 0000000000..4601106561 --- /dev/null +++ b/packages/babel-core/src/transformation/file/generate.js @@ -0,0 +1,89 @@ +// @flow + +import convertSourceMap, { type SourceMap } from "convert-source-map"; +import sourceMap from "source-map"; +import generate from "babel-generator"; + +import type File from "./file"; + +export default function generateCode( + file: File, +): { + outputCode: string, + outputMap: SourceMap | null, +} { + const { opts, ast, shebang, code, inputMap } = file; + + let gen = generate; + if (opts.generatorOpts && opts.generatorOpts.generator) { + gen = opts.generatorOpts.generator; + } + + let { code: outputCode, map: outputMap } = gen( + ast, + opts.generatorOpts ? Object.assign(opts, opts.generatorOpts) : opts, + code, + ); + + if (shebang) { + // add back shebang + outputCode = `${shebang}\n${outputCode}`; + } + + if (outputMap && inputMap) { + outputMap = mergeSourceMap(inputMap.toObject(), outputMap); + } + + if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") { + outputCode += "\n" + convertSourceMap.fromObject(outputMap).toComment(); + } + + if (opts.sourceMaps === "inline") { + outputMap = null; + } + + return { outputCode, outputMap }; +} + +function mergeSourceMap(inputMap: SourceMap, map: SourceMap): SourceMap { + const inputMapConsumer = new sourceMap.SourceMapConsumer(inputMap); + const outputMapConsumer = new sourceMap.SourceMapConsumer(map); + + const mergedGenerator = new sourceMap.SourceMapGenerator({ + file: inputMapConsumer.file, + sourceRoot: inputMapConsumer.sourceRoot, + }); + + // This assumes the output map always has a single source, since Babel always compiles a + // single source file to a single output file. + const source = outputMapConsumer.sources[0]; + + inputMapConsumer.eachMapping(function(mapping) { + const generatedPosition = outputMapConsumer.generatedPositionFor({ + line: mapping.generatedLine, + column: mapping.generatedColumn, + source: source, + }); + if (generatedPosition.column != null) { + mergedGenerator.addMapping({ + source: mapping.source, + + original: + mapping.source == null + ? null + : { + line: mapping.originalLine, + column: mapping.originalColumn, + }, + + generated: generatedPosition, + + name: mapping.name, + }); + } + }); + + const mergedMap = mergedGenerator.toJSON(); + inputMap.mappings = mergedMap.mappings; + return inputMap; +} diff --git a/packages/babel-core/src/transformation/file/index.js b/packages/babel-core/src/transformation/file/index.js deleted file mode 100644 index c0538464f4..0000000000 --- a/packages/babel-core/src/transformation/file/index.js +++ /dev/null @@ -1,501 +0,0 @@ -/* global BabelFileResult, BabelParserOptions, BabelFileMetadata */ - -import getHelper from "babel-helpers"; -import convertSourceMap from "convert-source-map"; -import PluginPass from "../plugin-pass"; -import { NodePath, Hub, Scope } from "babel-traverse"; -import sourceMap from "source-map"; -import generate from "babel-generator"; -import { codeFrameColumns } from "babel-code-frame"; -import traverse from "babel-traverse"; -import { parse } from "babylon"; -import * as t from "babel-types"; -import buildDebug from "debug"; - -import loadConfig, { type ResolvedConfig } from "../../config"; - -import blockHoistPlugin from "../internal-plugins/block-hoist"; - -const babelDebug = buildDebug("babel:file"); - -export function debug(opts: Object, msg: string) { - babelDebug(`${opts.filename || "unknown"}: ${msg}`); -} - -const shebangRegex = /^#!.*/; - -let INTERNAL_PLUGINS; - -const errorVisitor = { - enter(path, state) { - const loc = path.node.loc; - if (loc) { - state.loc = loc; - path.stop(); - } - }, -}; - -export default class File { - constructor({ options, passes }: ResolvedConfig) { - if (!INTERNAL_PLUGINS) { - // Lazy-init the internal plugin to remove the init-time circular dependency between plugins being - // passed babel-core's export object, which loads this file, and this 'loadConfig' loading plugins. - INTERNAL_PLUGINS = loadConfig({ - babelrc: false, - plugins: [blockHoistPlugin], - }).passes[0]; - } - - this._map = new Map(); - this.pluginPasses = passes; - this.opts = options; - - this.parserOpts = { - sourceType: this.opts.sourceType, - sourceFileName: this.opts.filename, - plugins: [], - }; - - for (const pluginPairs of passes) { - for (const [plugin] of pluginPairs) { - if (plugin.manipulateOptions) { - plugin.manipulateOptions(this.opts, this.parserOpts, this); - } - } - } - - this.metadata = {}; - this.declarations = {}; - - this.path = null; - this.ast = {}; - - this.code = ""; - this.shebang = ""; - - this.hub = new Hub(this); - } - - pluginPasses: Array>; - parserOpts: BabelParserOptions; - opts: Object; - declarations: Object; - path: NodePath; - ast: Object; - scope: Scope; - metadata: BabelFileMetadata; - hub: Hub; - code: string; - shebang: string; - - set(key: string, val) { - this._map.set(key, val); - } - - get(key: string): any { - return this._map.get(key); - } - - getModuleName(): ?string { - const opts = this.opts; - if (!opts.moduleIds) { - return null; - } - - // moduleId is n/a if a `getModuleId()` is provided - if (opts.moduleId != null && !opts.getModuleId) { - return opts.moduleId; - } - - let filenameRelative = opts.filenameRelative; - let moduleName = ""; - - if (opts.moduleRoot != null) { - moduleName = opts.moduleRoot + "/"; - } - - if (!opts.filenameRelative) { - return moduleName + opts.filename.replace(/^\//, ""); - } - - if (opts.sourceRoot != null) { - // remove sourceRoot from filename - const sourceRootRegEx = new RegExp("^" + opts.sourceRoot + "/?"); - filenameRelative = filenameRelative.replace(sourceRootRegEx, ""); - } - - // remove extension - filenameRelative = filenameRelative.replace(/\.(\w*?)$/, ""); - - moduleName += filenameRelative; - - // normalize path separators - moduleName = moduleName.replace(/\\/g, "/"); - - if (opts.getModuleId) { - // If return is falsy, assume they want us to use our generated default name - return opts.getModuleId(moduleName) || moduleName; - } else { - return moduleName; - } - } - - // TODO: Remove this before 7.x's official release. Leaving it in for now to - // prevent unnecessary breakage between beta versions. - resolveModuleSource(source: string): string { - return source; - } - - addImport() { - throw new Error( - "This API has been removed. If you're looking for this " + - "functionality in Babel 7, you should import the " + - "'babel-helper-module-imports' module and use the functions exposed " + - " from that module, such as 'addNamed' or 'addDefault'.", - ); - } - - addHelper(name: string): Object { - const declar = this.declarations[name]; - if (declar) return declar; - - const generator = this.get("helperGenerator"); - const runtime = this.get("helpersNamespace"); - if (generator) { - const res = generator(name); - if (res) return res; - } else if (runtime) { - return t.memberExpression(runtime, t.identifier(name)); - } - - const uid = (this.declarations[name] = this.scope.generateUidIdentifier( - name, - )); - - const { nodes, globals } = getHelper( - name, - name => this.addHelper(name), - uid, - () => Object.keys(this.scope.getAllBindings()), - ); - - globals.forEach(name => { - if (this.path.scope.hasBinding(name, true /* noGlobals */)) { - this.path.scope.rename(name); - } - }); - - nodes.forEach(node => { - node._compact = true; - }); - - this.path.unshiftContainer("body", nodes); - // TODO: NodePath#unshiftContainer should automatically register new - // bindings. - this.path.get("body").forEach(path => { - if (nodes.indexOf(path.node) === -1) return; - if (path.isVariableDeclaration()) this.scope.registerDeclaration(path); - }); - - return uid; - } - - addTemplateObject() { - throw new Error( - "This function has been moved into the template literal transform itself.", - ); - } - - buildCodeFrameError( - node: Object, - msg: string, - Error: typeof Error = SyntaxError, - ): Error { - let loc = node && (node.loc || node._loc); - - msg = `${this.opts.filename}: ${msg}`; - - if (!loc && node) { - const state = { - loc: null, - }; - traverse(node, errorVisitor, this.scope, state); - loc = state.loc; - - let txt = - "This is an error on an internal node. Probably an internal error."; - if (loc) txt += " Location has been estimated."; - - msg += ` (${txt})`; - } - - msg += - "\n" + - codeFrameColumns( - this.code, - { - start: { - line: loc.line, - column: loc.column + 1, - }, - }, - this.opts, - ); - - return new Error(msg); - } - - mergeSourceMap(map: Object) { - const inputMap = this.opts.inputSourceMap; - - if (inputMap) { - const inputMapConsumer = new sourceMap.SourceMapConsumer(inputMap); - const outputMapConsumer = new sourceMap.SourceMapConsumer(map); - - const mergedGenerator = new sourceMap.SourceMapGenerator({ - file: inputMapConsumer.file, - sourceRoot: inputMapConsumer.sourceRoot, - }); - - // This assumes the output map always has a single source, since Babel always compiles a - // single source file to a single output file. - const source = outputMapConsumer.sources[0]; - - inputMapConsumer.eachMapping(function(mapping) { - const generatedPosition = outputMapConsumer.generatedPositionFor({ - line: mapping.generatedLine, - column: mapping.generatedColumn, - source: source, - }); - if (generatedPosition.column != null) { - mergedGenerator.addMapping({ - source: mapping.source, - - original: - mapping.source == null - ? null - : { - line: mapping.originalLine, - column: mapping.originalColumn, - }, - - generated: generatedPosition, - - name: mapping.name, - }); - } - }); - - const mergedMap = mergedGenerator.toJSON(); - inputMap.mappings = mergedMap.mappings; - return inputMap; - } else { - return map; - } - } - - parse(code: string) { - let parseCode = parse; - let parserOpts = this.opts.parserOpts; - - if (parserOpts) { - parserOpts = Object.assign({}, this.parserOpts, parserOpts); - - if (parserOpts.parser) { - parseCode = parserOpts.parser; - - parserOpts.parser = { - parse(source) { - return parse(source, parserOpts); - }, - }; - } - } - - debug(this.opts, "Parse start"); - let ast; - try { - ast = parseCode(code, parserOpts || this.parserOpts); - } catch (err) { - const loc = err.loc; - if (loc) { - err.loc = null; - err.message = - `${this.opts.filename}: ${err.message}\n` + - codeFrameColumns( - this.code, - { - start: { - line: loc.line, - column: loc.column + 1, - }, - }, - this.opts, - ); - } - throw err; - } - debug(this.opts, "Parse stop"); - return ast; - } - - _addAst(ast) { - this.path = NodePath.get({ - hub: this.hub, - parentPath: null, - parent: ast, - container: ast, - key: "program", - }).setContext(); - this.scope = this.path.scope; - this.ast = ast; - } - - addAst(ast) { - debug(this.opts, "Start set AST"); - this._addAst(ast); - debug(this.opts, "End set AST"); - } - - transform(): BabelFileResult { - for (const pluginPairs of this.pluginPasses) { - const passPairs = []; - const passes = []; - const visitors = []; - - for (const [plugin, pluginOpts] of pluginPairs.concat(INTERNAL_PLUGINS)) { - const pass = new PluginPass(this, plugin.key, pluginOpts); - - passPairs.push([plugin, pass]); - passes.push(pass); - visitors.push(plugin.visitor); - } - - for (const [plugin, pass] of passPairs) { - const fn = plugin.pre; - if (fn) fn.call(pass, this); - } - - debug(this.opts, "Start transform traverse"); - - // merge all plugin visitors into a single visitor - const visitor = traverse.visitors.merge( - visitors, - passes, - this.opts.wrapPluginVisitorMethod, - ); - traverse(this.ast, visitor, this.scope); - - debug(this.opts, "End transform traverse"); - - for (const [plugin, pass] of passPairs) { - const fn = plugin.post; - if (fn) fn.call(pass, this); - } - } - - return this.generate(); - } - - addCode(code: string) { - code = (code || "") + ""; - code = this.parseInputSourceMap(code); - this.code = code; - } - - parseCode() { - this.parseShebang(); - const ast = this.parse(this.code); - this.addAst(ast); - } - - parseInputSourceMap(code: string): string { - const opts = this.opts; - - if (opts.inputSourceMap !== false) { - const inputMap = convertSourceMap.fromSource(code); - if (inputMap) { - opts.inputSourceMap = inputMap.toObject(); - code = convertSourceMap.removeComments(code); - } - } - - return code; - } - - parseShebang() { - const shebangMatch = shebangRegex.exec(this.code); - if (shebangMatch) { - this.shebang = shebangMatch[0]; - this.code = this.code.replace(shebangRegex, ""); - } - } - - makeResult({ code, map, ast, ignored }: BabelFileResult): BabelFileResult { - const result = { - metadata: this.metadata, - options: this.opts, - ignored: !!ignored, - code: null, - ast: null, - map: map || null, - }; - - if (this.opts.code) { - result.code = code; - } - - if (this.opts.ast) { - result.ast = ast; - } - - return result; - } - - generate(): BabelFileResult { - const opts = this.opts; - const ast = this.ast; - - const result: BabelFileResult = { ast }; - if (!opts.code) return this.makeResult(result); - - let gen = generate; - if (opts.generatorOpts && opts.generatorOpts.generator) { - gen = opts.generatorOpts.generator; - } - - debug(this.opts, "Generation start"); - - const _result = gen( - ast, - opts.generatorOpts ? Object.assign(opts, opts.generatorOpts) : opts, - this.code, - ); - result.code = _result.code; - result.map = _result.map; - - debug(this.opts, "Generation end"); - - if (this.shebang) { - // add back shebang - result.code = `${this.shebang}\n${result.code}`; - } - - if (result.map) { - result.map = this.mergeSourceMap(result.map); - } - - if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") { - result.code += "\n" + convertSourceMap.fromObject(result.map).toComment(); - } - - if (opts.sourceMaps === "inline") { - result.map = null; - } - - return this.makeResult(result); - } -} - -export { File }; diff --git a/packages/babel-core/src/transformation/index.js b/packages/babel-core/src/transformation/index.js index cea1c32c53..44c281d7ff 100644 --- a/packages/babel-core/src/transformation/index.js +++ b/packages/babel-core/src/transformation/index.js @@ -1,88 +1,80 @@ -/* global BabelFileResult */ -import fs from "fs"; +// @flow +import traverse from "babel-traverse"; +import type { SourceMap } from "convert-source-map"; -import * as t from "babel-types"; -import File from "./file"; -import loadConfig from "../config"; +import type { ResolvedConfig, PluginPasses } from "../config"; -export function transform(code: string, opts?: Object): BabelFileResult { - const config = loadConfig(opts); - if (config === null) return null; +import PluginPass from "./plugin-pass"; +import loadBlockHoistPlugin from "./block-hoist-plugin"; +import normalizeOptions from "./normalize-opts"; +import normalizeFile from "./normalize-file"; - const file = new File(config); - file.addCode(code); - file.parseCode(code); - return file.transform(); -} +import generateCode from "./file/generate"; +import File from "./file/file"; -export function transformFromAst( - ast: Object, +export type FileResult = { + metadata: {}, + options: {}, + ast: {} | null, + code: string | null, + map: SourceMap | null, +}; + +export default function runTransform( + config: ResolvedConfig, code: string, - opts: Object, -): BabelFileResult { - const config = loadConfig(opts); - if (config === null) return null; + ast?: {}, +): FileResult { + const options = normalizeOptions(config); + const input = normalizeFile(options, code, ast); - if (ast && ast.type === "Program") { - ast = t.file(ast, [], []); - } else if (!ast || ast.type !== "File") { - throw new Error("Not a valid ast?"); - } + const file = new File(options, input); - const file = new File(config); - file.addCode(code); - file.addAst(ast); - return file.transform(); + transformFile(file, config.passes); + + const { outputCode, outputMap } = options.code ? generateCode(file) : {}; + + return { + metadata: file.metadata, + options: options, + ast: options.ast ? file.ast : null, + code: outputCode === undefined ? null : outputCode, + map: outputMap === undefined ? null : outputMap, + }; } -export function transformFile( - filename: string, - opts?: Object, - callback: Function, -) { - if (typeof opts === "function") { - callback = opts; - opts = {}; - } +function transformFile(file: File, pluginPasses: PluginPasses): void { + for (const pluginPairs of pluginPasses) { + const passPairs = []; + const passes = []; + const visitors = []; - opts.filename = filename; - const config = loadConfig(opts); - if (config === null) return callback(null, null); + for (const [plugin, pluginOpts] of pluginPairs.concat([ + loadBlockHoistPlugin(), + ])) { + const pass = new PluginPass(file, plugin.key, pluginOpts); - fs.readFile(filename, function(err, code) { - let result; - - if (!err) { - try { - const file = new File(config); - file.addCode(code); - file.parseCode(code); - result = file.transform(); - } catch (_err) { - err = _err; - } + passPairs.push([plugin, pass]); + passes.push(pass); + visitors.push(plugin.visitor); } - if (err) { - callback(err); - } else { - callback(null, result); + for (const [plugin, pass] of passPairs) { + const fn = plugin.pre; + if (fn) fn.call(pass, file); } - }); -} - -export function transformFileSync( - filename: string, - opts?: Object = {}, -): string { - opts.filename = filename; - const config = loadConfig(opts); - if (config === null) return null; - - const code = fs.readFileSync(filename, "utf8"); - const file = new File(config); - - file.addCode(code); - file.parseCode(code); - return file.transform(); + + // merge all plugin visitors into a single visitor + const visitor = traverse.visitors.merge( + visitors, + passes, + file.opts.wrapPluginVisitorMethod, + ); + traverse(file.ast, visitor, file.scope); + + for (const [plugin, pass] of passPairs) { + const fn = plugin.post; + if (fn) fn.call(pass, file); + } + } } diff --git a/packages/babel-core/src/transformation/normalize-file.js b/packages/babel-core/src/transformation/normalize-file.js new file mode 100644 index 0000000000..7c60c2aa27 --- /dev/null +++ b/packages/babel-core/src/transformation/normalize-file.js @@ -0,0 +1,87 @@ +// @flow + +import convertSourceMap, { typeof Converter } from "convert-source-map"; +import { parse } from "babylon"; +import { codeFrameColumns } from "babel-code-frame"; + +const shebangRegex = /^#!.*/; + +export type NormalizedFile = { + code: string, + ast: {}, + shebang: string | null, + inputMap: Converter | null, +}; + +export default function normalizeFile( + options: Object, + code: string, + ast?: {}, +): NormalizedFile { + code = `${code || ""}`; + + let shebang = null; + let inputMap = null; + if (options.inputSourceMap !== false) { + inputMap = convertSourceMap.fromSource(code); + if (inputMap) { + code = convertSourceMap.removeComments(code); + } else if (typeof options.inputSourceMap === "object") { + inputMap = convertSourceMap.fromObject(options.inputSourceMap); + } + } + + const shebangMatch = shebangRegex.exec(code); + if (shebangMatch) { + shebang = shebangMatch[0]; + code = code.replace(shebangRegex, ""); + } + + if (!ast) ast = parser(options, code); + + return { + code, + ast, + shebang, + inputMap, + }; +} + +function parser(options, code) { + let parseCode = parse; + + let { parserOpts } = options; + if (parserOpts.parser) { + parseCode = parserOpts.parser; + + parserOpts = Object.assign({}, parserOpts, { + parser: { + parse(source) { + return parse(source, parserOpts); + }, + }, + }); + } + + try { + return parseCode(code, parserOpts); + } catch (err) { + const loc = err.loc; + if (loc) { + err.loc = null; + err.message = + `${options.filename}: ${err.message}\n` + + codeFrameColumns( + code, + { + start: { + line: loc.line, + column: loc.column + 1, + }, + }, + options, + ); + } + throw err; + } +} diff --git a/packages/babel-core/src/transformation/normalize-opts.js b/packages/babel-core/src/transformation/normalize-opts.js new file mode 100644 index 0000000000..1746ce4d27 --- /dev/null +++ b/packages/babel-core/src/transformation/normalize-opts.js @@ -0,0 +1,26 @@ +// @flow + +import type { ResolvedConfig } from "../config"; + +export default function normalizeOptions(config: ResolvedConfig): {} { + const options = Object.assign({}, config.options, { + parserOpts: Object.assign( + { + sourceType: config.options.sourceType, + sourceFileName: config.options.filename, + plugins: [], + }, + config.options.parserOpts, + ), + }); + + for (const pluginPairs of config.passes) { + for (const [plugin] of pluginPairs) { + if (plugin.manipulateOptions) { + plugin.manipulateOptions(options, options.parserOpts); + } + } + } + + return options; +} diff --git a/packages/babel-core/src/transformation/plugin-pass.js b/packages/babel-core/src/transformation/plugin-pass.js index edde9acd07..000f7e4c62 100644 --- a/packages/babel-core/src/transformation/plugin-pass.js +++ b/packages/babel-core/src/transformation/plugin-pass.js @@ -1,39 +1,47 @@ -import File from "./file"; +// @flow + +import type File from "./file/file"; export default class PluginPass { - constructor(file: File, key: string, options: Object = {}) { - this._map = new Map(); - - this.key = key; - this.file = file; - this.opts = options; - } - - key: string; + _map: Map = new Map(); + key: ?string; file: File; opts: Object; - set(key: string, val) { + constructor(file: File, key: ?string, options: ?Object) { + this.key = key; + this.file = file; + this.opts = options || {}; + } + + set(key: mixed, val: mixed) { this._map.set(key, val); } - get(key: string): any { + get(key: mixed): any { return this._map.get(key); } - addHelper(...args) { - return this.file.addHelper(...args); + addHelper(name: string) { + return this.file.addHelper(name); } - addImport(...args) { - return this.file.addImport(...args); + addImport() { + return this.file.addImport(); } - getModuleName(...args) { - return this.file.getModuleName(...args); + getModuleName(): ?string { + return this.file.getModuleName(); } - buildCodeFrameError(...args) { - return this.file.buildCodeFrameError(...args); + buildCodeFrameError( + node: ?{ + loc?: { line: number, column: number }, + _loc?: { line: number, column: number }, + }, + msg: string, + Error?: typeof Error, + ) { + return this.file.buildCodeFrameError(node, msg, Error); } } diff --git a/packages/babel-core/test/option-manager.js b/packages/babel-core/test/option-manager.js index 5358b2f2f2..7e233a2a67 100644 --- a/packages/babel-core/test/option-manager.js +++ b/packages/babel-core/test/option-manager.js @@ -8,7 +8,7 @@ describe("option-manager", () => { manageOptions({ plugins: [({ Plugin }) => new Plugin("object-assign", {})], }); - }, /Babel 5 plugin is being run with Babel 6/); + }, /Babel 5 plugin is being run with an unsupported Babel/); }); describe("mergeOptions", () => { diff --git a/packages/babel-traverse/src/hub.js b/packages/babel-traverse/src/hub.js index f05eaa8136..69f9406fc9 100644 --- a/packages/babel-traverse/src/hub.js +++ b/packages/babel-traverse/src/hub.js @@ -1,6 +1,5 @@ export default class Hub { - constructor(file, options) { + constructor(file) { this.file = file; - this.options = options; } }