diff --git a/bin/babel/index.js b/bin/babel/index.js index da34ba6fe2..99ab67d1f7 100755 --- a/bin/babel/index.js +++ b/bin/babel/index.js @@ -2,32 +2,38 @@ var commander = require("commander"); var transform = require("../../lib/babel/transformation"); +var kebabCase = require("lodash/string/kebabCase"); +var File = require("../../lib/babel/transformation/file"); var util = require("../../lib/babel/util"); -var fs = require("fs"); var each = require("lodash/collection/each"); var keys = require("lodash/object/keys"); +var fs = require("fs"); + +each(File.options, function (option, key) { + if (option.hidden) return; + + var arg = kebabCase(key); + + if (option.type !== "boolean") { + arg += ` [${option.type || "string"}]`; + } + + if (option.type === "boolean" && option.default === true) { + arg = `no-${key}`; + } + + arg = `--${arg}`; + + if (option.shorthand) { + arg = `-${option.shorthand}, ${arg}`; + } + + commander.option(arg, option.description); +}) -commander.option("-t, --source-maps-inline", "Append sourceMappingURL comment to bottom of code"); -commander.option("-s, --source-maps", "Save source map alongside the compiled code"); -commander.option("-f, --filename [filename]", "Filename to use when reading from stdin - this will be used in source-maps, errors etc [stdin]", "stdin"); commander.option("-w, --watch", "Recompile files on changes"); -commander.option("-r, --external-helpers", "Replace helpers with references to a `babelHelpers` global"); -commander.option("-e, --experimental", "Enable experimental support for proposed ES7 features"); -commander.option("-p, --playground", "Enable playground support"); - -commander.option("-c, --compact [mode]", "When set to \"auto\" compact is `true` when the input size exceeds 100KB. (auto|true|false)", "auto"); -commander.option("-m, --modules [modules]", "Module formatter type to use [common]", "common"); -commander.option("-l, --whitelist [whitelist]", "Whitelist of transformers to ONLY use", util.list); -commander.option("-b, --blacklist [blacklist]", "Blacklist of transformers to NOT use", util.list); -commander.option("-i, --optional [list]", "List of optional transformers to enable", util.list); -commander.option("-L, --loose [list]", "List of transformers to enable loose mode ON", util.list); commander.option("-o, --out-file [out]", "Compile all input files into a single file"); commander.option("-d, --out-dir [out]", "Compile an input directory of modules into an output directory"); -commander.option("-c, --remove-comments", "Remove comments from the compiled code", false); -commander.option("-M, --module-ids", "Insert module id in modules", false); -commander.option("-R, --react-compat", "Makes the react transformer produce pre-v0.12 code"); -commander.option("--keep-module-id-extensions", "Keep extensions when generating module ids", false); -commander.option("-a, --auxiliary-comment [comment]", "Comment text to prepend to all auxiliary code"); commander.option("-D, --copy-files", "When compiling a directory copy over non-compilable files"); commander.on("--help", function () { @@ -38,9 +44,7 @@ commander.on("--help", function () { each(keys(obj).sort(), function (key) { if (key[0] === "_") return; - if (obj[key].optional) { - key = "[" + key + "]"; - } + if (obj[key].optional) key = `[${key}]`; console.log(" - " + key); }); @@ -100,24 +104,11 @@ if (errors.length) { // -exports.opts = { - keepModuleIdExtensions: commander.keepModuleIdExtensions, - auxiliaryComment: commander.auxiliaryComment, - externalHelpers: commander.externalHelpers, - sourceMapName: commander.outFile, - experimental: commander.experimental, - reactCompat: commander.reactCompat, - playground: commander.playground, - moduleIds: commander.moduleIds, - blacklist: commander.blacklist, - whitelist: commander.whitelist, - sourceMap: commander.sourceMaps || commander.sourceMapsInline, - optional: commander.optional, - comments: !commander.removeComments, - modules: commander.modules, - compact: commander.compact, - loose: commander.loose -}; +exports.opts = {}; + +each(File.options, function (opt, key) { + exports.opts[key] = commander[key]; +}); var fn; diff --git a/bin/babel/util.js b/bin/babel/util.js index 49c43de4e8..b353e153a8 100644 --- a/bin/babel/util.js +++ b/bin/babel/util.js @@ -1,11 +1,10 @@ -var resolveRc = require("../../lib/babel/api/register/resolve-rc"); -var readdir = require("fs-readdir-recursive"); -var index = require("./index"); -var babel = require("../../lib/babel/api/node"); -var util = require("../../lib/babel/util"); -var path = require("path"); -var fs = require("fs"); -var _ = require("lodash"); +var readdir = require("fs-readdir-recursive"); +var index = require("./index"); +var babel = require("../../lib/babel/api/node"); +var util = require("../../lib/babel/util"); +var path = require("path"); +var fs = require("fs"); +var _ = require("lodash"); exports.readdirFilter = function (filename) { return readdir(filename).filter(function (filename) { @@ -24,7 +23,6 @@ exports.addSourceMappingUrl = function (code, loc) { exports.transform = function (filename, code, opts) { opts = _.defaults(opts || {}, index.opts); opts.filename = filename; - resolveRc(filename, opts); var result = babel.transform(code, opts); result.filename = filename; diff --git a/package.json b/package.json index 086793830b..a21f8e800b 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "leven": "^1.0.1", "line-numbers": "0.2.0", "lodash": "^3.2.0", + "minimatch": "^2.0.3", "output-file-sync": "^1.1.0", "path-is-absolute": "^1.0.0", "private": "^0.1.6", diff --git a/src/babel/api/node.js b/src/babel/api/node.js index df49f9e21d..fa2a7a7818 100644 --- a/src/babel/api/node.js +++ b/src/babel/api/node.js @@ -1,12 +1,13 @@ import isFunction from "lodash/lang/isFunction"; import transform from "../transformation"; +import * as util from "../util"; import fs from "fs"; -import * as util from "../util"; -export { util as _util }; +export { util }; export { canCompile } from "../util"; export { default as acorn } from "acorn-babel"; +export { default as Transformer } from "../transformation/transformer"; export { default as transform } from "../transformation"; export { default as traverse } from "../traversal"; export { default as buildExternalHelpers } from "../tools/build-external-helpers"; @@ -15,7 +16,7 @@ export { version } from "../../../package"; import * as t from "../types"; export { t as types }; -export function register(opts) { +export function register(opts?: Object) { var callback = require("./register/node"); if (opts != null) callback(opts); return callback; @@ -25,7 +26,7 @@ export function polyfill() { require("../polyfill"); } -export function transformFile(filename, opts, callback) { +export function transformFile(filename: string, opts?: Object, callback: Function) { if (isFunction(opts)) { callback = opts; opts = {}; @@ -48,7 +49,7 @@ export function transformFile(filename, opts, callback) { }); } -export function transformFileSync(filename, opts = {}) { +export function transformFileSync(filename: string, opts?: Object = {}) { opts.filename = filename; return transform(fs.readFileSync(filename), opts); } diff --git a/src/babel/api/register/cache.js b/src/babel/api/register/cache.js index 6f7bc54029..dd724ac74a 100644 --- a/src/babel/api/register/cache.js +++ b/src/babel/api/register/cache.js @@ -2,7 +2,7 @@ import path from "path"; import os from "os"; import fs from "fs"; -var FILENAME = process.env.BABEL_CACHE_PATH || path.join(os.tmpdir(), "babel.json"); +const FILENAME = process.env.BABEL_CACHE_PATH || path.join(os.tmpdir(), "babel.json"); var data = {}; export function save() { diff --git a/src/babel/api/register/node.js b/src/babel/api/register/node.js index f2c044af0c..beb06ebdb0 100644 --- a/src/babel/api/register/node.js +++ b/src/babel/api/register/node.js @@ -1,7 +1,7 @@ import "../../polyfill"; import sourceMapSupport from "source-map-support"; import * as registerCache from "./cache"; -import resolveRc from "./resolve-rc"; +import resolveRc from "../../tools/resolve-rc"; import extend from "lodash/object/extend"; import * as babel from "../node"; import each from "lodash/collection/each"; @@ -44,6 +44,9 @@ var compile = function (filename) { var result; var opts = extend({}, transformOpts); + + // this will be done when the file is transformed anyway but we need all + // the options so we can generate the cache key resolveRc(filename, opts); var cacheKey = `${filename}:${JSON.stringify(opts)}:${babel.version}`; diff --git a/src/babel/generation/generators/modules.js b/src/babel/generation/generators/modules.js index b7241adb3a..2218e90486 100644 --- a/src/babel/generation/generators/modules.js +++ b/src/babel/generation/generators/modules.js @@ -66,8 +66,10 @@ export function ImportDeclaration(node, print) { if (specfiers && specfiers.length) { var foundImportSpecifier = false; - each(node.specifiers, (spec, i) => { - if (+i > 0) { + for (var i = 0; i < node.specifiers.length; i++) { + var spec = node.specifiers[i]; + + if (i > 0) { this.push(", "); } @@ -79,7 +81,7 @@ export function ImportDeclaration(node, print) { } print(spec); - }); + } if (foundImportSpecifier) { this.push(" }"); diff --git a/src/babel/generation/node/parentheses.js b/src/babel/generation/node/parentheses.js index e429dca474..d3b4302f2d 100644 --- a/src/babel/generation/node/parentheses.js +++ b/src/babel/generation/node/parentheses.js @@ -1,7 +1,7 @@ import each from "lodash/collection/each"; import * as t from "../../types"; -var PRECEDENCE = {}; +const PRECEDENCE = {}; each([ ["||"], diff --git a/src/babel/helpers/code-frame.js b/src/babel/helpers/code-frame.js index e1bb415681..8f24c61502 100644 --- a/src/babel/helpers/code-frame.js +++ b/src/babel/helpers/code-frame.js @@ -55,10 +55,10 @@ var highlight = function (text) { }); }; -export default function (lines, lineNumber, colNumber) { +export default function (lines: number, lineNumber: number, colNumber: number, color?): string { colNumber = Math.max(colNumber, 0); - if (chalk.supportsColor) { + if (color && chalk.supportsColor) { lines = highlight(lines); } diff --git a/src/babel/helpers/parse.js b/src/babel/helpers/parse.js index b93361c638..124407c96f 100644 --- a/src/babel/helpers/parse.js +++ b/src/babel/helpers/parse.js @@ -32,12 +32,12 @@ export default function (opts, code, callback) { } catch (err) { if (!err._babel) { err._babel = true; + var message = `${opts.filename}: ${err.message}`; var loc = err.loc; if (loc) { - var frame = codeFrame(code, loc.line, loc.column + 1); - message += frame; + message += codeFrame(code, loc.line, loc.column + 1, opts.highlightErrors); } if (err.stack) { diff --git a/src/babel/messages.js b/src/babel/messages.js index f97e84c969..6a4952dcf3 100644 --- a/src/babel/messages.js +++ b/src/babel/messages.js @@ -1,6 +1,6 @@ import * as util from "util"; -export var messages = { +export const MESSAGES = { tailCallReassignmentDeopt: "Function reference has been reassigned so it's probably be dereferenced so we can't optimise this with confidence", JSXNamespacedTags: "Namespace tags are not supported. ReactJSX is not XML.", classesIllegalBareSuper: "Illegal use of bare super", @@ -25,7 +25,7 @@ export var messages = { }; export function get(key: String, ...args) { - var msg = messages[key]; + var msg = MESSAGES[key]; if (!msg) throw new ReferenceError(`Unknown message ${JSON.stringify(key)}`); args = parseArgs(args); diff --git a/src/babel/api/register/resolve-rc.js b/src/babel/tools/resolve-rc.js similarity index 100% rename from src/babel/api/register/resolve-rc.js rename to src/babel/tools/resolve-rc.js diff --git a/src/babel/transformation/file.js b/src/babel/transformation/file/index.js similarity index 74% rename from src/babel/transformation/file.js rename to src/babel/transformation/file/index.js index ee40919b2f..22559262c1 100644 --- a/src/babel/transformation/file.js +++ b/src/babel/transformation/file/index.js @@ -1,20 +1,25 @@ import convertSourceMap from "convert-source-map"; +import * as optionParsers from "./option-parsers"; import shebangRegex from "shebang-regex"; +import TraversalPath from "../../traversal/path"; import isFunction from "lodash/lang/isFunction"; +import isAbsolute from "path-is-absolute"; +import resolveRc from "../../tools/resolve-rc"; import sourceMap from "source-map"; -import transform from "./index"; -import generate from "../generation"; +import transform from "./../index"; +import generate from "../../generation"; import defaults from "lodash/object/defaults"; import includes from "lodash/collection/includes"; +import traverse from "../../traversal"; import assign from "lodash/object/assign"; import Logger from "./logger"; -import parse from "../helpers/parse"; -import Scope from "../traversal/scope"; +import parse from "../../helpers/parse"; +import Scope from "../../traversal/scope"; import slash from "slash"; -import * as util from "../util"; +import * as util from "../../util"; import path from "path"; import each from "lodash/collection/each"; -import * as t from "../types"; +import * as t from "../../types"; var checkTransformerVisitor = { enter(node, parent, scope, state) { @@ -78,84 +83,37 @@ export default class File { "self-global" ]; - static validOptions = [ - "filename", - "filenameRelative", - - "blacklist", - "whitelist", - "optional", - - "loose", - "playground", - "experimental", - - "modules", - "moduleIds", - "moduleId", - "resolveModuleSource", - "keepModuleIdExtensions", - - "code", - "ast", - - "comments", - "compact", - - "auxiliaryComment", - "externalHelpers", - "returnUsedHelpers", - - "inputSourceMap", - "sourceMap", - "sourceMapName", - "sourceFileName", - "sourceRoot", - "moduleRoot", - - // legacy - "format", - "reactCompat", - - // these are used by plugins - "ignore", - "only", - "extensions", - "accept" - ]; + static options = require("./options"); normalizeOptions(opts: Object) { opts = assign({}, opts); - for (var key in opts) { - if (key[0] !== "_" && File.validOptions.indexOf(key) < 0) { - throw new ReferenceError(`Unknown option: ${key}`); - } + if (opts.filename && isAbsolute(opts.filename)) { + opts = resolveRc(opts.filename, opts); } - defaults(opts, { - keepModuleIdExtensions: false, - resolveModuleSource: null, - returnUsedHelpers: false, - externalHelpers: false, - auxilaryComment: "", - inputSourceMap: null, - experimental: false, - reactCompat: false, - playground: false, - moduleIds: false, - blacklist: [], - whitelist: [], - sourceMap: false, - optional: [], - comments: true, - filename: "unknown", - modules: "common", - compact: "auto", - loose: [], - code: true, - ast: true - }); + // + + for (let key in opts) { + if (key[0] === "_") continue; + + let option = File.options[key]; + if (!option) throw new ReferenceError(`Unknown option: ${key}`); + } + + for (let key in File.options) { + let option = File.options[key]; + + var val = opts[key]; + if (val == null) val = option.default || null; + + var optionParser = optionParsers[option.type]; + if (optionParser) { + val = optionParser(key, val); + } + + opts[key] = val; + } if (opts.inputSourceMap) { opts.sourceMap = true; @@ -173,15 +131,8 @@ export default class File { opts.basename = path.basename(opts.filename, path.extname(opts.filename)); - opts.blacklist = util.arrayify(opts.blacklist); - opts.whitelist = util.arrayify(opts.whitelist); - opts.optional = util.arrayify(opts.optional); - opts.compact = util.booleanify(opts.compact); - opts.loose = util.arrayify(opts.loose); - - if (includes(opts.loose, "all") || includes(opts.loose, true)) { - opts.loose = Object.keys(transform.transformers); - } + opts.ignore = util.arrayify(opts.ignore, util.regexify); + opts.only = util.arrayify(opts.only, util.regexify); defaults(opts, { moduleRoot: opts.sourceRoot @@ -200,33 +151,12 @@ export default class File { sourceMapName: opts.filenameRelative }); - if (opts.playground) { - opts.experimental = true; - } + // if (opts.externalHelpers) { this.set("helpersNamespace", t.identifier("babelHelpers")); } - opts.blacklist = transform._ensureTransformerNames("blacklist", opts.blacklist); - opts.whitelist = transform._ensureTransformerNames("whitelist", opts.whitelist); - opts.optional = transform._ensureTransformerNames("optional", opts.optional); - opts.loose = transform._ensureTransformerNames("loose", opts.loose); - - if (opts.reactCompat) { - opts.optional.push("reactCompat"); - console.error("The reactCompat option has been moved into the optional transformer `reactCompat`"); - } - - var ensureEnabled = function (key) { - var namespace = transform.transformerNamespaces[key]; - if (namespace === "es7") opts.experimental = true; - if (namespace === "playground") opts.playground = true; - }; - - each(opts.whitelist, ensureEnabled); - each(opts.optional, ensureEnabled); - return opts; }; @@ -234,6 +164,10 @@ export default class File { return includes(this.opts.loose, key); } + buildPlugins(stack) { + + } + buildTransformers() { var file = this; @@ -242,6 +176,8 @@ export default class File { var secondaryStack = []; var stack = []; + this.buildPlugins(stack); + each(transform.transformers, function (transformer, key) { var pass = transformers[key] = transformer.buildPass(file); @@ -377,7 +313,7 @@ export default class File { this.usedHelpers[name] = true; var generator = this.get("helperGenerator"); - var runtime = this.get("helpersNamespace"); + var runtime = this.get("helpersNamespace"); if (generator) { return generator(name); } else if (runtime) { @@ -410,7 +346,36 @@ export default class File { return this.parseShebang(code); } + shouldIgnore() { + var opts = this.opts; + + var filename = opts.filename; + var ignore = opts.ignore; + var only = opts.only; + + if (only.length) { + for (var i = 0; i < only.length; i++) { + if (only[i].test(filename)) return false; + } + return true; + } else if (ignore.length) { + for (var i = 0; i < ignore.length; i++) { + if (ignore[i].test(filename)) return true; + } + } + + return false; + } + parse(code: string) { + if (this.shouldIgnore()) { + return { + code: code, + map: null, + ast: null + }; + } + code = this.addCode(code); var opts = this.opts; @@ -424,12 +389,28 @@ export default class File { }); } + setAst(ast) { + this.path = TraversalPath.get(null, null, ast, ast, "program", this); + this.scope = this.path.scope; + this.ast = ast; + + this.path.traverse({ + enter(node, parent, scope) { + if (this.isScope()) { + for (var key in scope.bindings) { + scope.bindings[key].setTypeAnnotation(); + } + } + } + }); + } + transform(ast) { this.log.debug(); - this.ast = ast; + this.setAst(ast); + this.lastStatements = t.getLastStatements(ast.program); - this.scope = new Scope(ast.program, ast, null, this); var modFormatter = this.moduleFormatter = this.getModuleFormatter(this.opts.modules); if (modFormatter.init && this.transformers["es6.modules"].canRun()) { @@ -483,14 +464,14 @@ export default class File { if (inputMap) { map.sources[0] = inputMap.file; - var inputMapConsumer = new sourceMap.SourceMapConsumer(inputMap); - var outputMapConsumer = new sourceMap.SourceMapConsumer(map); + var inputMapConsumer = new sourceMap.SourceMapConsumer(inputMap); + var outputMapConsumer = new sourceMap.SourceMapConsumer(map); var outputMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(outputMapConsumer); outputMapGenerator.applySourceMap(inputMapConsumer); var mergedMap = outputMapGenerator.toJSON(); mergedMap.sources = inputMap.sources - mergedMap.file = inputMap.file; + mergedMap.file = inputMap.file; return mergedMap; } @@ -498,6 +479,7 @@ export default class File { } generate(): { + usedHelpers?: Array; code: string; map?: Object; ast?: Object; @@ -507,8 +489,8 @@ export default class File { var result = { code: "", - map: null, - ast: null + map: null, + ast: null }; if (this.opts.returnUsedHelpers) { diff --git a/src/babel/transformation/logger.js b/src/babel/transformation/file/logger.js similarity index 74% rename from src/babel/transformation/logger.js rename to src/babel/transformation/file/logger.js index 29e185af9d..109019c810 100644 --- a/src/babel/transformation/logger.js +++ b/src/babel/transformation/file/logger.js @@ -1,4 +1,4 @@ -import * as util from "../util"; +import * as util from "../../util"; export default class Logger { constructor(file: File) { @@ -12,6 +12,12 @@ export default class Logger { return parts; } + deprecate(msg) { + if (!file.opts.suppressDeprecationMessages) { + console.error(msg); + } + } + debug(msg: string) { util.debug(this._buildMessage(msg)); } diff --git a/src/babel/transformation/file/option-parsers.js b/src/babel/transformation/file/option-parsers.js new file mode 100644 index 0000000000..a0a2cc33d2 --- /dev/null +++ b/src/babel/transformation/file/option-parsers.js @@ -0,0 +1,20 @@ +import transform from "./../index"; +import * as util from "../../util"; + +export function transformerList(key, val) { + val = util.arrayify(val); + + if (val.indexOf("all") >= 0 || val.indexOf(true) >= 0) { + val = Object.keys(transform.transformers); + } + + return transform._ensureTransformerNames(key, val); +} + +export function boolean(key, val) { + return util.booleanify(val); +} + +export function list(key, val) { + return util.list(val); +} diff --git a/src/babel/transformation/file/options.json b/src/babel/transformation/file/options.json new file mode 100644 index 0000000000..0482ff9576 --- /dev/null +++ b/src/babel/transformation/file/options.json @@ -0,0 +1,164 @@ +{ + "filename": { + "type": "string", + "description": "Filename to use when reading from stdin - this will be used in source-maps, errors etc", + "default": "unknown", + "shorthand": "f" + }, + + "filenameRelative": { + "hidden": true, + "type": "string" + }, + + "inputSourceMap": { + "hidden": true + }, + + "moduleId": { + + }, + + "highlightErrors": { + "description": "ANSI syntax highlight error messages" + "type": "boolean", + "default": true + }, + + "suppressDeprecationMessages": { + "type": "boolean", + "default": false, + "hidden": true + }, + + "resolveModuleSource": { + "hidden": true + }, + + "experimental": { + "description": "Enable all ES7+ transformers", + "shorthand": "e", + "type": "boolean", + "default": false + }, + + "playground": { + "description": "Enable all playground transformers", + "shorthand": "p", + "type": "boolean", + "default": false + }, + + "blacklist": { + "type": "transformerList", + "description": "Blacklist of transformers to NOT use", + "shorthand": "b" + }, + + "whitelist": { + "type": "transformerList", + "description": "Whitelist of transformers to ONLY use", + "shorthand": "l" + }, + + "optional": { + "type": "transformerList", + "description": "List of optional transformers to enable" + }, + + "modules": { + "type": "string", + "description": "Module formatter type to use [common]", + "default": "common", + "shorthand": "m" + }, + + "moduleIds": { + "type": "boolean", + "default": false, + "shorthand": "M" + }, + + "loose": { + "type": "transformerList", + "description": "List of transformers to enable loose mode ON", + "shorthand": "L" + }, + + "ignore": { + "type": "list" + }, + + "only": { + "type": "list" + }, + + "code": { + "hidden": true, + "default": true, + "type": "boolean" + }, + + "ast": { + "hidden": true, + "default": true, + "type": "boolean" + }, + + "comments": { + "type": "boolean", + "default": true + }, + + "compact": { + "type": "string", + "default": "auto" + }, + + "keepModuleIdExtensions": { + "type": "boolean", + "description": "Keep extensions when generating module ids", + "default": false, + "shorthand": "k" + }, + + "auxiliaryComment": { + "type": "string", + "default": "", + "shorthand": "a" + }, + + "externalHelpers": { + "type": "string", + "default": false, + "shorthand": "r" + }, + + "returnUsedHelpers": { + "type": "boolean", + "default": false, + "hidden": true + }, + + "sourceMap": { + "type": "string", + "default": false, + "shorthand": "s" + }, + + "sourceMapName": { + "type": "string" + }, + + "sourceFileName": { + "type": "string" + }, + + "sourceRoot": { + "type": "string" + }, + + "moduleRoot": { + "type": "string" + } +} diff --git a/src/babel/transformation/helpers/build-react-transformer.js b/src/babel/transformation/helpers/build-react-transformer.js index 02b9ba1ffe..ccef9d819b 100644 --- a/src/babel/transformation/helpers/build-react-transformer.js +++ b/src/babel/transformation/helpers/build-react-transformer.js @@ -27,7 +27,7 @@ export default function (exports, opts) { }; exports.JSXNamespacedName = function (node, parent, scope, file) { - throw file.errorWithNode(node, messages.get("JSXNamespacedTags")); + throw this.errorWithNode(messages.get("JSXNamespacedTags")); }; exports.JSXMemberExpression = { diff --git a/src/babel/transformation/helpers/replace-supers.js b/src/babel/transformation/helpers/replace-supers.js index e60cbabcb4..aa80001ff6 100644 --- a/src/babel/transformation/helpers/replace-supers.js +++ b/src/babel/transformation/helpers/replace-supers.js @@ -39,7 +39,7 @@ var visitor = { var callback = self.specHandle; if (self.isLoose) callback = self.looseHandle; - return callback.call(self, getThisReference, node, parent); + return callback.call(self, this, getThisReference); } }; @@ -191,11 +191,12 @@ export default class ReplaceSupers { * Description */ - looseHandle(getThisReference: Function, node: Object, parent: Object) { - if (t.isIdentifier(node, { name: "super" })) { + looseHandle(path: TraversalPath, getThisReference: Function) { + var node = path.node; + if (path.isIdentifier({ name: "super" })) { this.hasSuper = true; - return this.getLooseSuperProperty(node, parent); - } else if (t.isCallExpression(node)) { + return this.getLooseSuperProperty(node, path.parent); + } else if (path.isCallExpression()) { var callee = node.callee; if (!t.isMemberExpression(callee)) return; if (callee.object.name !== "super") return; @@ -211,15 +212,18 @@ export default class ReplaceSupers { * Description */ - specHandle(getThisReference: Function, node: Object, parent: Object) { + specHandle(path: TraversalPath, getThisReference: Function) { var methodNode = this.methodNode; var property; var computed; var args; var thisReference; + var parent = path.parent; + var node = path.node; + if (isIllegalBareSuper(node, parent)) { - throw this.file.errorWithNode(node, messages.get("classesIllegalBareSuper")); + throw path.errorWithNode(messages.get("classesIllegalBareSuper")); } if (t.isCallExpression(node)) { diff --git a/src/babel/transformation/index.js b/src/babel/transformation/index.js index 1c02c4ee2a..8d244aa51a 100644 --- a/src/babel/transformation/index.js +++ b/src/babel/transformation/index.js @@ -4,7 +4,7 @@ import object from "../helpers/object"; import File from "./file"; import each from "lodash/collection/each"; -export default function transform(code: code, opts?: Object) { +export default function transform(code: string, opts?: Object) { var file = new File(opts); return file.parse(code); } diff --git a/src/babel/transformation/modules/_default.js b/src/babel/transformation/modules/_default.js index a60d3b7507..2158b8525f 100644 --- a/src/babel/transformation/modules/_default.js +++ b/src/babel/transformation/modules/_default.js @@ -45,7 +45,7 @@ var importsVisitor = { ImportDeclaration: { enter(node, parent, scope, formatter) { formatter.hasLocalImports = true; - extend(formatter.localImports, t.getBindingIdentifiers(node)); + extend(formatter.localImports, this.getBindingIdentifiers()); formatter.bumpImportOccurences(node); } } @@ -54,11 +54,11 @@ var importsVisitor = { var exportsVisitor = { ExportDeclaration: { enter(node, parent, scope, formatter) { - var declar = node.declaration; + var declar = this.get("declaration"); formatter.hasLocalImports = true; - if (declar && t.isStatement(declar)) { - extend(formatter.localExports, t.getBindingIdentifiers(declar)); + if (declar.isStatement()) { + extend(formatter.localExports, declar.getBindingIdentifiers()); } if (!node.default) { @@ -105,16 +105,16 @@ export default class DefaultFormatter { } getLocalExports() { - this.file.scope.traverse(this.file.ast, exportsVisitor, this); + this.file.path.traverse(exportsVisitor, this); } getLocalImports() { - this.file.scope.traverse(this.file.ast, importsVisitor, this); + this.file.path.traverse(importsVisitor, this); } remapAssignments() { if (this.hasLocalImports) { - this.file.scope.traverse(this.file.ast, remapVisitor, this); + this.file.path.traverse(remapVisitor, this); } } diff --git a/src/babel/transformation/templates/for-of-array.js b/src/babel/transformation/templates/for-of-array.js new file mode 100644 index 0000000000..7f27900c15 --- /dev/null +++ b/src/babel/transformation/templates/for-of-array.js @@ -0,0 +1 @@ +for (var KEY = 0; KEY < ARR.length; KEY++) BODY; diff --git a/src/babel/transformation/transformer-pass.js b/src/babel/transformation/transformer-pass.js index 3beac40bc8..0d9897f259 100644 --- a/src/babel/transformation/transformer-pass.js +++ b/src/babel/transformation/transformer-pass.js @@ -1,4 +1,5 @@ import includes from "lodash/collection/includes"; +import traverse from "../traversal"; /** * This class is responsible for traversing over the provided `File`s @@ -31,14 +32,14 @@ export default class TransformerPass { var whitelist = opts.whitelist; if (whitelist.length) return includes(whitelist, key); - // optional - if (transformer.optional && !includes(opts.optional, key)) return false; - // experimental - if (transformer.experimental && !opts.experimental) return false; + if (transformer.experimental && opts.experimental) return true; // playground - if (transformer.playground && !opts.playground) return false; + if (transformer.playground && opts.playground) return true; + + // optional + if (transformer.optional && !includes(opts.optional, key)) return false; return true; } @@ -59,7 +60,7 @@ export default class TransformerPass { file.log.debug(`Running transformer ${this.transformer.key}`); - file.scope.traverse(file.ast, this.handlers, file); + traverse(file.ast, this.handlers, file.scope, file); this.ran = true; } diff --git a/src/babel/transformation/transformer.js b/src/babel/transformation/transformer.js index fddb3ae2a1..6a05af07c1 100644 --- a/src/babel/transformation/transformer.js +++ b/src/babel/transformation/transformer.js @@ -3,6 +3,7 @@ import isFunction from "lodash/lang/isFunction"; import traverse from "../traversal"; import isObject from "lodash/lang/isObject"; import assign from "lodash/object/assign"; +import File from "./file"; import each from "lodash/collection/each"; /** @@ -66,6 +67,10 @@ export default class Transformer { } buildPass(file: File): TransformerPass { + if (!(file instanceof File)) { + throw new Error("Multiple versions of babel are interacting, this is either due to a version mismatch in a plugin or it was resolved incorrectly"); + } + return new TransformerPass(file, this); } } diff --git a/src/babel/transformation/transformers/es6/block-scoping-tdz.js b/src/babel/transformation/transformers/es6/block-scoping-tdz.js index d18333a192..0eee6bc324 100644 --- a/src/babel/transformation/transformers/es6/block-scoping-tdz.js +++ b/src/babel/transformation/transformers/es6/block-scoping-tdz.js @@ -32,7 +32,7 @@ export function BlockStatement(node, parent, scope, file) { var letRefs = node._letReferences; if (!letRefs) return; - scope.traverse(node, visitor, { + this.traverse(visitor, { letRefs: letRefs, file: file }); diff --git a/src/babel/transformation/transformers/es6/block-scoping.js b/src/babel/transformation/transformers/es6/block-scoping.js index 79eb0380c7..ee7e87402f 100644 --- a/src/babel/transformation/transformers/es6/block-scoping.js +++ b/src/babel/transformation/transformers/es6/block-scoping.js @@ -111,7 +111,7 @@ function traverseReplace(node, parent, scope, remaps) { var letReferenceBlockVisitor = { enter(node, parent, scope, state) { if (this.isFunction()) { - scope.traverse(node, letReferenceFunctionVisitor, state); + this.traverse(letReferenceFunctionVisitor, state); return this.skip(); } } @@ -173,7 +173,7 @@ var loopVisitor = { if (this.isLoop()) { state.ignoreLabeless = true; - scope.traverse(node, loopVisitor, state); + this.traverse(loopVisitor, state); state.ignoreLabeless = false; } diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index f0ccc38c5f..add480ac59 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -15,7 +15,7 @@ export function ClassDeclaration(node, parent, scope, file) { } export function ClassExpression(node, parent, scope, file) { - return new ClassTransformer(node, parent, scope, file).run(); + return new ClassTransformer(this, file).run(); } var verifyConstructorVisitor = traverse.explode({ @@ -33,11 +33,11 @@ var verifyConstructorVisitor = traverse.explode({ CallExpression: { enter(node, parent, scope, state) { - if (t.isIdentifier(node.callee, { name: "super" })) { + if (this.get("callee").isIdentifier({ name: "super" })) { state.hasBareSuper = true; if (!state.hasSuper) { - throw state.file.errorWithNode(node, "super call is only allowed in derived constructor"); + throw this.errorWithNode("super call is only allowed in derived constructor"); } } } @@ -46,7 +46,7 @@ var verifyConstructorVisitor = traverse.explode({ ThisExpression: { enter(node, parent, scope, state) { if (state.hasSuper && !state.hasBareSuper) { - throw state.file.errorWithNode(node, "'this' is not allowed before super()"); + throw this.errorWithNode("'this' is not allowed before super()"); } } } @@ -58,10 +58,11 @@ class ClassTransformer { * Description */ - constructor(node: Object, parent: Object, scope: Scope, file: File) { - this.parent = parent; - this.scope = scope; - this.node = node; + constructor(path: TraversalPath, file: File) { + this.parent = path.parent; + this.scope = path.scope; + this.node = path.node; + this.path = path; this.file = file; this.hasInstanceMutators = false; @@ -71,11 +72,11 @@ class ClassTransformer { this.staticMutatorMap = {}; this.hasConstructor = false; - this.className = node.id; - this.classRef = node.id || scope.generateUidIdentifier("class"); + this.className = this.node.id; + this.classRef = this.node.id || this.scope.generateUidIdentifier("class"); - this.superName = node.superClass || t.identifier("Function"); - this.hasSuper = !!node.superClass; + this.superName = this.node.superClass || t.identifier("Function"); + this.hasSuper = !!this.node.superClass; this.isLoose = file.isLoose("es6.classes"); } @@ -174,11 +175,13 @@ class ClassTransformer { var classBody = this.node.body.body; var body = this.body; + var classBodyPaths = this.path.get("body").get("body"); + for (var i = 0; i < classBody.length; i++) { var node = classBody[i]; if (t.isMethodDefinition(node)) { var isConstructor = (!node.computed && t.isIdentifier(node.key, { name: "constructor" })) || t.isLiteral(node.key, { value: "constructor" }); - if (isConstructor) this.verifyConstructor(node); + if (isConstructor) this.verifyConstructor(classBodyPaths[i]); var replaceSupers = new ReplaceSupers({ methodNode: node, @@ -205,8 +208,8 @@ class ClassTransformer { } } - // we have no constructor, we have a super, and the super doesn't appear to be falsy - if (!this.hasConstructor && this.hasSuper && t.evaluateTruthy(superName, this.scope) !== false) { + // we have no constructor, but we're a derived class + if (!this.hasConstructor && this.hasSuper) { var helperName = "class-super-constructor-call"; if (this.isLoose) helperName += "-loose"; constructor.body.body.push(util.template(helperName, { @@ -249,19 +252,17 @@ class ClassTransformer { * Description */ - verifyConstructor(node: Object) { - return; // enable this for the next major - + verifyConstructor(path: TraversalPath) { var state = { hasBareSuper: false, hasSuper: this.hasSuper, file: this.file }; - traverse(node, verifyConstructorVisitor, this.scope, state); + path.traverse(verifyConstructorVisitor, state); if (!state.hasBareSuper && this.hasSuper) { - throw this.file.errorWithNode(node, "Derived constructor must call super()"); + throw path.errorWithNode("Derived constructor must call super()"); } } diff --git a/src/babel/transformation/transformers/es6/constants.js b/src/babel/transformation/transformers/es6/constants.js index fc29e8c916..4a4e5c95d2 100644 --- a/src/babel/transformation/transformers/es6/constants.js +++ b/src/babel/transformation/transformers/es6/constants.js @@ -37,7 +37,7 @@ var visitor = { }; export function Scopable(node, parent, scope, file) { - scope.traverse(node, visitor, { + this.traverse(visitor, { constants: scope.getAllBindingsOfKind("const"), file: file }); diff --git a/src/babel/transformation/transformers/es6/destructuring.js b/src/babel/transformation/transformers/es6/destructuring.js index ea9e3bdaf3..a27e70c907 100644 --- a/src/babel/transformation/transformers/es6/destructuring.js +++ b/src/babel/transformation/transformers/es6/destructuring.js @@ -432,7 +432,7 @@ class DestructuringTransformer { } else { arrayRef = this.scope.generateUidBasedOnNode(arrayRef); this.nodes.push(this.buildVariableDeclaration(arrayRef, toArray)); - this.scope.assignTypeGeneric(arrayRef.name, "Array"); + //this.getBinding(arrayRef.name).assignTypeGeneric("Array"); } // diff --git a/src/babel/transformation/transformers/es6/for-of.js b/src/babel/transformation/transformers/es6/for-of.js index 7c6674ab2d..5d6e380dc0 100644 --- a/src/babel/transformation/transformers/es6/for-of.js +++ b/src/babel/transformation/transformers/es6/for-of.js @@ -5,6 +5,10 @@ import * as t from "../../../types"; export var check = t.isForOfStatement; export function ForOfStatement(node, parent, scope, file) { + if (this.get("right").isArrayExpression()) { + return _ForOfStatementArray.call(this, node, scope, file); + } + var callback = spec; if (file.isLoose("es6.forOf")) callback = loose; @@ -29,13 +33,48 @@ export function ForOfStatement(node, parent, scope, file) { t.inherits(loop, node); - // todo: find out why this is necessary? #538 - loop._scopeInfo = node._scopeInfo; - if (build.replaceParent) this.parentPath.node = build.node; return build.node; } +export function _ForOfStatementArray(node, scope, file) { + var nodes = []; + var right = node.right; + + if (!t.isIdentifier(right) || !scope.hasBinding(right.name)) { + var uid = scope.generateUidIdentifier("arr"); + nodes.push(t.variableDeclaration("var", [ + t.variableDeclarator(uid, right) + ])); + right = uid; + } + + var iterationKey = scope.generateUidIdentifier("i"); + + var loop = util.template("for-of-array", { + BODY: node.body, + KEY: iterationKey, + ARR: right + }); + + t.inherits(loop, node); + t.ensureBlock(loop); + + var iterationValue = t.memberExpression(right, iterationKey, true); + + var left = node.left; + if (t.isVariableDeclaration(left)) { + left.declarations[0].init = iterationValue; + loop.body.body.unshift(left); + } else { + loop.body.body.unshift(t.expressionStatement(t.assignmentExpression("=", left, iterationValue))); + } + + nodes.push(loop); + + return nodes; +} + var loose = function (node, parent, scope, file) { var left = node.left; var declar, id; diff --git a/src/babel/transformation/transformers/es6/modules.js b/src/babel/transformation/transformers/es6/modules.js index 0eab992dfe..cb583622d6 100644 --- a/src/babel/transformation/transformers/es6/modules.js +++ b/src/babel/transformation/transformers/es6/modules.js @@ -26,7 +26,7 @@ export function ImportDeclaration(node, parent, scope, file) { export function ExportDeclaration(node, parent, scope, file) { // flow type - if (t.isTypeAlias(node.declaration)) return; + if (this.get("declaration").isTypeAlias()) return; var nodes = []; var i; diff --git a/src/babel/transformation/transformers/es6/parameters.default.js b/src/babel/transformation/transformers/es6/parameters.default.js index 9e525c87d3..82dd26e20e 100644 --- a/src/babel/transformation/transformers/es6/parameters.default.js +++ b/src/babel/transformation/transformers/es6/parameters.default.js @@ -49,41 +49,42 @@ exports.Function = function (node, parent, scope, file) { body.push(defNode); }; - for (var i = 0; i < node.params.length; i++) { - var param = node.params[i]; + var params = this.get("params"); + for (var i = 0; i < params.length; i++) { + var param = params[i]; - if (!t.isAssignmentPattern(param)) { - if (!t.isRestElement(param)) { + if (!param.isAssignmentPattern()) { + if (!param.isRestElement()) { lastNonDefaultParam = i + 1; } - if (!t.isIdentifier(param)) { - scope.traverse(param, iifeVisitor, state); + if (!param.isIdentifier()) { + param.traverse(iifeVisitor, state); } - if (file.transformers["es6.blockScopingTDZ"].canRun() && t.isIdentifier(param)) { - pushDefNode(param, t.identifier("undefined"), i); + if (file.transformers["es6.blockScopingTDZ"].canRun() && param.isIdentifier()) { + pushDefNode(param.node, t.identifier("undefined"), i); } continue; } - var left = param.left; - var right = param.right; + var left = param.get("left"); + var right = param.get("right"); var placeholder = scope.generateUidIdentifier("x"); placeholder._isDefaultPlaceholder = true; node.params[i] = placeholder; if (!state.iife) { - if (t.isIdentifier(right) && scope.hasOwnBinding(right.name)) { + if (right.isIdentifier() && scope.hasOwnBinding(right.node.name)) { state.iife = true; } else { - scope.traverse(right, iifeVisitor, state); + right.traverse(iifeVisitor, state); } } - pushDefNode(left, right, i); + pushDefNode(left.node, right.node, i); } // we need to cut off all trailing default parameters diff --git a/src/babel/transformation/transformers/es6/parameters.rest.js b/src/babel/transformation/transformers/es6/parameters.rest.js index 14098fa0e3..d0f967a8f7 100644 --- a/src/babel/transformation/transformers/es6/parameters.rest.js +++ b/src/babel/transformation/transformers/es6/parameters.rest.js @@ -15,7 +15,7 @@ var memberExpressionOptimisationVisitor = { // to the wrong function if (this.isFunctionDeclaration() || this.isFunctionExpression()) { state.noOptimise = true; - scope.traverse(node, memberExpressionOptimisationVisitor, state); + this.traverse(memberExpressionOptimisationVisitor, state); state.noOptimise = false; return this.skip(); } @@ -88,7 +88,7 @@ exports.Function = function (node, parent, scope, file) { name: rest.name }; - scope.traverse(node, memberExpressionOptimisationVisitor, state); + this.traverse(memberExpressionOptimisationVisitor, state); // we only have shorthands and there's no other references if (state.canOptimise && state.candidates.length) { @@ -127,16 +127,14 @@ exports.Function = function (node, parent, scope, file) { ); } - scope.assignTypeGeneric(rest.name, "Array"); - var loop = util.template("rest", { ARGUMENTS: argsId, ARRAY_KEY: arrKey, ARRAY_LEN: arrLen, - START: start, - ARRAY: rest, - KEY: key, - LEN: len, + START: start, + ARRAY: rest, + KEY: key, + LEN: len }); loop._blockHoist = node.params.length + 1; node.body.body.unshift(loop); diff --git a/src/babel/transformation/transformers/es6/spread.js b/src/babel/transformation/transformers/es6/spread.js index 4fe2b7d238..693553ded9 100644 --- a/src/babel/transformation/transformers/es6/spread.js +++ b/src/babel/transformation/transformers/es6/spread.js @@ -81,7 +81,7 @@ export function CallExpression(node, parent, scope) { var callee = node.callee; - if (t.isMemberExpression(callee)) { + if (this.get("callee").isMemberExpression()) { var temp = scope.generateTempBasedOnNode(callee.object); if (temp) { callee.object = t.assignmentExpression("=", temp, callee.object); @@ -101,7 +101,7 @@ export function NewExpression(node, parent, scope, file) { var args = node.arguments; if (!hasSpread(args)) return; - var nativeType = t.isIdentifier(node.callee) && includes(t.NATIVE_TYPE_NAMES, node.callee.name); + var nativeType = this.get("callee").isIdentifier() && includes(t.NATIVE_TYPE_NAMES, node.callee.name); var nodes = build(args, scope); diff --git a/src/babel/transformation/transformers/es6/symbols.js b/src/babel/transformation/transformers/es6/symbols.js index fd3fe51b7e..03f7ab491f 100644 --- a/src/babel/transformation/transformers/es6/symbols.js +++ b/src/babel/transformation/transformers/es6/symbols.js @@ -7,7 +7,7 @@ export function UnaryExpression(node, parent, scope, file) { if (node.operator === "typeof") { var call = t.callExpression(file.addHelper("typeof"), [node.argument]); - if (t.isIdentifier(node.argument)) { + if (this.get("argument").isIdentifier()) { var undefLiteral = t.literal("undefined"); return t.conditionalExpression( t.binaryExpression("===", t.unaryExpression("typeof", node.argument), undefLiteral), diff --git a/src/babel/transformation/transformers/es6/tail-call.js b/src/babel/transformation/transformers/es6/tail-call.js index e7439cdf5a..c0efe08ec5 100644 --- a/src/babel/transformation/transformers/es6/tail-call.js +++ b/src/babel/transformation/transformers/es6/tail-call.js @@ -6,7 +6,7 @@ import map from "lodash/collection/map"; import * as t from "../../../types"; exports.Function = function (node, parent, scope, file) { - var tailCall = new TailCallTransformer(node, scope, file); + var tailCall = new TailCallTransformer(this, scope, file); tailCall.run(); }; @@ -18,11 +18,11 @@ function returnBlock(expr) { var firstPass = { enter(node, parent, scope, state) { if (this.isIfStatement()) { - if (t.isReturnStatement(node.alternate)) { + if (this.get("alternate").isReturnStatement()) { t.ensureBlock(node, "alternate"); } - if (t.isReturnStatement(node.consequent)) { + if (this.get("consequent").isReturnStatement()) { t.ensureBlock(node, "consequent"); } } else if (this.isReturnStatement()) { @@ -85,17 +85,18 @@ var thirdPass = { }; class TailCallTransformer { - constructor(node, scope, file) { + constructor(path, scope, file) { this.hasTailRecursion = false; this.needsArguments = false; this.setsArguments = false; this.needsThis = false; - this.ownerId = node.id; + this.ownerId = path.node.id; this.vars = []; this.scope = scope; + this.path = path; this.file = file; - this.node = node; + this.node = path.node; } getArgumentsId() { @@ -142,8 +143,8 @@ class TailCallTransformer { hasDeopt() { // check if the ownerId has been reassigned, if it has then it's not safe to // perform optimisations - var ownerIdInfo = this.scope.getBindingInfo(this.ownerId.name); - return ownerIdInfo && ownerIdInfo.reassigned; + var ownerIdInfo = this.scope.getBinding(this.ownerId.name); + return ownerIdInfo && !ownerIdInfo.constant; } run() { @@ -156,7 +157,7 @@ class TailCallTransformer { if (!ownerId) return; // traverse the function and look for tail recursion - scope.traverse(node, firstPass, this); + this.path.traverse(firstPass, this); if (!this.hasTailRecursion) return; @@ -167,10 +168,10 @@ class TailCallTransformer { // - scope.traverse(node, secondPass, this); + this.path.traverse(secondPass, this); if (!this.needsThis || !this.needsArguments) { - scope.traverse(node, thirdPass, this); + this.path.traverse(thirdPass, this); } var body = t.ensureBlock(node).body; diff --git a/src/babel/transformation/transformers/es6/template-literals.js b/src/babel/transformation/transformers/es6/template-literals.js index 9bee577561..537f57bde1 100644 --- a/src/babel/transformation/transformers/es6/template-literals.js +++ b/src/babel/transformation/transformers/es6/template-literals.js @@ -9,11 +9,11 @@ export function check(node) { } export function TaggedTemplateExpression(node, parent, scope, file) { - var args = []; var quasi = node.quasi; + var args = []; var strings = []; - var raw = []; + var raw = []; for (var i = 0; i < quasi.quasis.length; i++) { var elem = quasi.quasis[i]; diff --git a/src/babel/transformation/transformers/es7/abstract-references.js b/src/babel/transformation/transformers/es7/abstract-references.js index 7713b41444..79f6415fad 100644 --- a/src/babel/transformation/transformers/es7/abstract-references.js +++ b/src/babel/transformation/transformers/es7/abstract-references.js @@ -4,6 +4,7 @@ import * as util from "../../../util"; import * as t from "../../../types"; export var experimental = true; +export var optional = true; var container = function (parent, call, ret, file) { if (t.isExpressionStatement(parent) && !file.isConsequenceExpressionStatement(parent)) { diff --git a/src/babel/transformation/transformers/es7/comprehensions.js b/src/babel/transformation/transformers/es7/comprehensions.js index 98484701b0..901f42e7e2 100644 --- a/src/babel/transformation/transformers/es7/comprehensions.js +++ b/src/babel/transformation/transformers/es7/comprehensions.js @@ -4,6 +4,7 @@ import * as util from "../../../util"; import * as t from "../../../types"; export var experimental = true; +export var optional = true; export function ComprehensionExpression(node, parent, scope, file) { var callback = array; diff --git a/src/babel/transformation/transformers/es7/exponentiation-operator.js b/src/babel/transformation/transformers/es7/exponentiation-operator.js index da988d07dd..e5106b938c 100644 --- a/src/babel/transformation/transformers/es7/exponentiation-operator.js +++ b/src/babel/transformation/transformers/es7/exponentiation-operator.js @@ -4,6 +4,7 @@ import build from "../../helpers/build-binary-assignment-operator-transformer"; import * as t from "../../../types"; export var experimental = true; +export var optional = true; var MATH_POW = t.memberExpression(t.identifier("Math"), t.identifier("pow")); diff --git a/src/babel/transformation/transformers/es7/object-rest-spread.js b/src/babel/transformation/transformers/es7/object-rest-spread.js index c5d7b08c3d..257064dbcd 100644 --- a/src/babel/transformation/transformers/es7/object-rest-spread.js +++ b/src/babel/transformation/transformers/es7/object-rest-spread.js @@ -3,6 +3,7 @@ import * as t from "../../../types"; export var experimental = true; +export var optional = true; export function manipulateOptions(opts) { if (opts.whitelist.length) opts.whitelist.push("es6.destructuring"); diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index e318ff6902..9b008d23c6 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -20,7 +20,6 @@ export default { "playground.objectGetterMemoization": require("./playground/object-getter-memoization"), reactCompat: require("./other/react-compat"), - flow: require("./other/flow"), react: require("./other/react"), _modules: require("./internal/modules"), @@ -45,6 +44,7 @@ export default { // needs to be before `_aliasFunction` due to define property closure "es6.properties.computed": require("./es6/properties.computed"), + "optimisation.es6.forOf": require("./optimisation/flow.for-of"), "es6.forOf": require("./es6/for-of"), "es6.regex.sticky": require("./es6/regex.sticky"), @@ -110,5 +110,6 @@ export default { "utility.inlineExpressions": require("./utility/inline-expressions"), "utility.deadCodeElimination": require("./utility/dead-code-elimination"), + flow: require("./other/flow"), _cleanUp: require("./internal/cleanup") }; diff --git a/src/babel/transformation/transformers/internal/alias-functions.js b/src/babel/transformation/transformers/internal/alias-functions.js index b76e19ae50..649edd0c38 100644 --- a/src/babel/transformation/transformers/internal/alias-functions.js +++ b/src/babel/transformation/transformers/internal/alias-functions.js @@ -34,13 +34,13 @@ var functionVisitor = { } // traverse all child nodes of this function and find `arguments` and `this` - scope.traverse(node, functionChildrenVisitor, state); + this.traverse(functionChildrenVisitor, state); return this.skip(); } }; -var go = function (getBody, node, scope) { +function aliasFunction(getBody, path, scope) { var argumentsId; var thisId; @@ -56,7 +56,7 @@ var go = function (getBody, node, scope) { // traverse the function and find all alias functions so we can alias // `arguments` and `this` if necessary - scope.traverse(node, functionVisitor, state); + path.traverse(functionVisitor, state); var body; @@ -77,16 +77,16 @@ var go = function (getBody, node, scope) { }; export function Program(node, parent, scope) { - go(function () { + aliasFunction(function () { return node.body; - }, node, scope); + }, this, scope); }; export function FunctionDeclaration(node, parent, scope) { - go(function () { + aliasFunction(function () { t.ensureBlock(node); return node.body.body; - }, node, scope); + }, this, scope); } export { FunctionDeclaration as FunctionExpression }; diff --git a/src/babel/transformation/transformers/internal/modules.js b/src/babel/transformation/transformers/internal/modules.js index 7ef5bc5302..50ecd3a7a8 100644 --- a/src/babel/transformation/transformers/internal/modules.js +++ b/src/babel/transformation/transformers/internal/modules.js @@ -50,7 +50,12 @@ export function ExportDeclaration(node, parent, scope) { return [getDeclar(), node]; } } else { - if (t.isFunctionDeclaration(declar)) { + if (t.isClassDeclaration(declar)) { + // export class Foo {} + node.specifiers = [t.importSpecifier(declar.id, declar.id)]; + node.declaration = null; + return [getDeclar(), node]; + } else if (t.isFunctionDeclaration(declar)) { // export function Foo() {} node.specifiers = [t.importSpecifier(declar.id, declar.id)]; node.declaration = null; @@ -59,7 +64,7 @@ export function ExportDeclaration(node, parent, scope) { } else if (t.isVariableDeclaration(declar)) { // export var foo = "bar"; var specifiers = []; - var bindings = t.getBindingIdentifiers(declar); + var bindings = this.get("declaration").getBindingIdentifiers(); for (var key in bindings) { var id = bindings[key]; specifiers.push(t.exportSpecifier(id, id)); diff --git a/src/babel/transformation/transformers/optimisation/flow.for-of.js b/src/babel/transformation/transformers/optimisation/flow.for-of.js new file mode 100644 index 0000000000..5e0237d700 --- /dev/null +++ b/src/babel/transformation/transformers/optimisation/flow.for-of.js @@ -0,0 +1,11 @@ +import { _ForOfStatementArray } from "../es6/for-of"; +import * as t from "../../../types"; + +export var check = t.isForOfStatement; +export var optional = true; + +export function ForOfStatement(node, parent, scope, file) { + if (this.get("right").isTypeGeneric("Array")) { + return _ForOfStatementArray.call(this, node, scope, file); + } +} diff --git a/src/babel/transformation/transformers/other/flow.js b/src/babel/transformation/transformers/other/flow.js index 4f8126f9f9..bb6bf4c393 100644 --- a/src/babel/transformation/transformers/other/flow.js +++ b/src/babel/transformation/transformers/other/flow.js @@ -29,5 +29,5 @@ export function ImportDeclaration(node) { } export function ExportDeclaration(node) { - if (t.isTypeAlias(node.declaration)) this.remove(); + if (this.get("declaration").isTypeAlias()) this.remove(); } diff --git a/src/babel/transformation/transformers/other/runtime.js b/src/babel/transformation/transformers/other/runtime.js index a137603e8c..ec2de99541 100644 --- a/src/babel/transformation/transformers/other/runtime.js +++ b/src/babel/transformation/transformers/other/runtime.js @@ -4,7 +4,7 @@ import core from "core-js/library"; import has from "lodash/object/has"; import * as t from "../../../types"; -var isSymboliterator = t.buildMatchMemberExpression("Symbol.iterator"); +var isSymbolIterator = t.buildMatchMemberExpression("Symbol.iterator"); var coreHas = function (node) { return node.name !== "_" && has(core, node.name); @@ -47,7 +47,7 @@ var astVisitor = { if (!callee.computed) return false; prop = callee.property; - if (!isSymboliterator(prop)) return false; + if (!isSymbolIterator(prop)) return false; return util.template("corejs-iterator", { CORE_ID: file.get("coreIdentifier"), @@ -59,7 +59,7 @@ var astVisitor = { if (node.operator !== "in") return; var left = node.left; - if (!isSymboliterator(left)) return; + if (!isSymbolIterator(left)) return; return util.template("corejs-is-iterator", { CORE_ID: file.get("coreIdentifier"), @@ -76,7 +76,7 @@ export function manipulateOptions(opts) { } export function Program(node, parent, scope, file) { - scope.traverse(node, astVisitor, file); + this.traverse(astVisitor, file); } export function pre(file) { diff --git a/src/babel/transformation/transformers/playground/mallet-operator.js b/src/babel/transformation/transformers/playground/mallet-operator.js index 25e6896a45..1d51716fe0 100644 --- a/src/babel/transformation/transformers/playground/mallet-operator.js +++ b/src/babel/transformation/transformers/playground/mallet-operator.js @@ -3,6 +3,7 @@ import build from "../../helpers/build-conditional-assignment-operator-transform import * as t from "../../../types"; export var playground = true; +export var optional = true; build(exports, { is(node, file) { diff --git a/src/babel/transformation/transformers/playground/memoization-operator.js b/src/babel/transformation/transformers/playground/memoization-operator.js index 7c63ad102a..c399166eef 100644 --- a/src/babel/transformation/transformers/playground/memoization-operator.js +++ b/src/babel/transformation/transformers/playground/memoization-operator.js @@ -2,6 +2,7 @@ import build from "../../helpers/build-conditional-assignment-operator-transform import * as t from "../../../types"; export var playground = true; +export var optional = true; build(exports, { is(node) { diff --git a/src/babel/transformation/transformers/playground/method-binding.js b/src/babel/transformation/transformers/playground/method-binding.js index 3bea826a5a..86ec1312ae 100644 --- a/src/babel/transformation/transformers/playground/method-binding.js +++ b/src/babel/transformation/transformers/playground/method-binding.js @@ -1,9 +1,10 @@ import * as t from "../../../types"; export var playground = true; +export var optional = true; -export function BindMemberExpression(node, parent, scope) { - console.error("Method binding is deprecated and will be removed in 5.0.0"); +export function BindMemberExpression(node, parent, scope, file) { + file.log.deprecate("Method binding is deprecated and will be removed in 5.0.0"); var object = node.object; var prop = node.property; @@ -26,8 +27,8 @@ export function BindMemberExpression(node, parent, scope) { } } -export function BindFunctionExpression(node, parent, scope) { - console.error("Method binding is deprecated and will be removed in 5.0.0"); +export function BindFunctionExpression(node, parent, scope, file) { + file.log.deprecate("Method binding is deprecated and will be removed in 5.0.0"); var buildCall = function (args) { var param = scope.generateUidIdentifier("val"); diff --git a/src/babel/transformation/transformers/playground/object-getter-memoization.js b/src/babel/transformation/transformers/playground/object-getter-memoization.js index e2f0ac6487..8c117811a7 100644 --- a/src/babel/transformation/transformers/playground/object-getter-memoization.js +++ b/src/babel/transformation/transformers/playground/object-getter-memoization.js @@ -1,6 +1,7 @@ import * as t from "../../../types"; export var playground = true; +export var optional = true; var visitor = { enter(node, parent, scope, state) { @@ -20,10 +21,9 @@ export function MethodDefinition(node, parent, scope, file) { if (node.kind !== "memo") return; node.kind = "get"; - console.error("Object getter memoization is deprecated and will be removed in 5.0.0"); + file.log.deprecate("Object getter memoization is deprecated and will be removed in 5.0.0"); - var value = node.value; - t.ensureBlock(value); + t.ensureBlock(node.value); var key = node.key; @@ -36,7 +36,7 @@ export function MethodDefinition(node, parent, scope, file) { file: file }; - scope.traverse(value, visitor, state); + this.get("value").traverse(visitor, state); return node; } diff --git a/src/babel/transformation/transformers/utility/dead-code-elimination.js b/src/babel/transformation/transformers/utility/dead-code-elimination.js index 3dd7252ae3..bda19f668d 100644 --- a/src/babel/transformation/transformers/utility/dead-code-elimination.js +++ b/src/babel/transformation/transformers/utility/dead-code-elimination.js @@ -20,7 +20,7 @@ function toStatements(node) { export var optional = true; export function ConditionalExpression(node, parent, scope) { - var evaluateTest = t.evaluateTruthy(node.test, scope); + var evaluateTest = this.get("test").evaluateTruthy(); if (evaluateTest === true) { return node.consequent; } else if (evaluateTest === false) { @@ -32,9 +32,8 @@ export var IfStatement = { exit(node, parent, scope) { var consequent = node.consequent; var alternate = node.alternate; - var test = node.test; - var evaluateTest = t.evaluateTruthy(test, scope); + var evaluateTest = this.get("test").evaluateTruthy(); // we can check if a test will be truthy 100% and if so then we can inline // the consequent and completely ignore the alternate diff --git a/src/babel/transformation/transformers/utility/inline-environment-variables.js b/src/babel/transformation/transformers/utility/inline-environment-variables.js index 5c62f53a6e..5e2cabb55c 100644 --- a/src/babel/transformation/transformers/utility/inline-environment-variables.js +++ b/src/babel/transformation/transformers/utility/inline-environment-variables.js @@ -6,7 +6,7 @@ var match = t.buildMatchMemberExpression("process.env"); export function MemberExpression(node) { if (match(node.object)) { - var key = t.toComputedKey(node, node.property); + var key = this.toComputedKey(); if (t.isLiteral(key)) { return t.valueToNode(process.env[key.value]); } diff --git a/src/babel/transformation/transformers/utility/inline-expressions.js b/src/babel/transformation/transformers/utility/inline-expressions.js index 9b77422502..f1be106014 100644 --- a/src/babel/transformation/transformers/utility/inline-expressions.js +++ b/src/babel/transformation/transformers/utility/inline-expressions.js @@ -3,7 +3,7 @@ import * as t from "../../../types"; export var optional = true; export function Expression(node, parent, scope) { - var res = t.evaluate(node, scope); + var res = this.evaluate(); if (res.confident) return t.valueToNode(res.value); } diff --git a/src/babel/transformation/transformers/utility/remove-console.js b/src/babel/transformation/transformers/utility/remove-console.js index 791b17c9f4..9fe1f491a9 100644 --- a/src/babel/transformation/transformers/utility/remove-console.js +++ b/src/babel/transformation/transformers/utility/remove-console.js @@ -1,11 +1,9 @@ import * as t from "../../../types"; -var isConsole = t.buildMatchMemberExpression("console", true); - export var optional = true; export function CallExpression(node, parent) { - if (isConsole(node.callee)) { + if (this.get("callee").matchesPattern("console", true)) { if (t.isExpressionStatement(parent)) { this.parentPath.remove(); } else { diff --git a/src/babel/transformation/transformers/validation/react.js b/src/babel/transformation/transformers/validation/react.js index f8cbc58ffb..536aff1768 100644 --- a/src/babel/transformation/transformers/validation/react.js +++ b/src/babel/transformation/transformers/validation/react.js @@ -14,7 +14,7 @@ function check(source, file) { } export function CallExpression(node, parent, scope, file) { - if (t.isIdentifier(node.callee, { name: "require" }) && node.arguments.length === 1) { + if (this.get("callee").isIdentifier({ name: "require" }) && node.arguments.length === 1) { check(node.arguments[0], file); } } diff --git a/src/babel/traversal/binding.js b/src/babel/traversal/binding.js new file mode 100644 index 0000000000..ee2cdddf0a --- /dev/null +++ b/src/babel/traversal/binding.js @@ -0,0 +1,68 @@ +import * as t from "../types"; + +export default class Binding { + constructor({ identifier, scope, path, kind }) { + this.identifier = identifier; + this.constant = true; + this.scope = scope; + this.path = path; + this.kind = kind; + } + + /** + * Description + */ + + setTypeAnnotation() { + var typeInfo = this.path.getTypeAnnotation(); + this.typeAnnotationInferred = typeInfo.inferred; + this.typeAnnotation = typeInfo.annotation; + } + + /** + * Description + */ + + isTypeGeneric(): boolean { + return this.path.isTypeGeneric(...arguments); + } + + /** + * Description + */ + + assignTypeGeneric(type: Object, params?) { + var typeParams = null; + if (params) params = t.typeParameterInstantiation(params); + this.assignType(t.genericTypeAnnotation(t.identifier(type), typeParams)); + } + + /** + * Description + */ + + assignType(type: Object) { + this.typeAnnotation = type; + } + + /** + * Description + */ + + reassign() { + this.constant = false; + + if (this.typeAnnotationInferred) { + // destroy the inferred typeAnnotation + this.typeAnnotation = null; + } + } + + /** + * Description + */ + + isCompatibleWithType(newType): boolean { + return false; + } +} diff --git a/src/babel/traversal/index.js b/src/babel/traversal/index.js index 86c94d9fbe..c36162f7df 100644 --- a/src/babel/traversal/index.js +++ b/src/babel/traversal/index.js @@ -1,10 +1,8 @@ -module.exports = traverse; - import TraversalContext from "./context"; import includes from "lodash/collection/includes"; import * as t from "../types"; -function traverse(parent, opts, scope, state) { +export default function traverse(parent, opts, scope, state, parentPath) { if (!parent) return; if (!opts.noScope && !scope) { @@ -20,10 +18,10 @@ function traverse(parent, opts, scope, state) { // array of nodes if (Array.isArray(parent)) { for (var i = 0; i < parent.length; i++) { - traverse.node(parent[i], opts, scope, state); + traverse.node(parent[i], opts, scope, state, parentPath); } } else { - traverse.node(parent, opts, scope, state); + traverse.node(parent, opts, scope, state, parentPath); } } @@ -42,7 +40,6 @@ traverse.node = function (node, opts, scope, state, parentPath) { function clearNode(node) { node._declarations = null; node.extendedRange = null; - node._scopeInfo = null; node._paths = null; node.tokens = null; node.range = null; diff --git a/src/babel/traversal/path.js b/src/babel/traversal/path.js deleted file mode 100644 index fb5c204050..0000000000 --- a/src/babel/traversal/path.js +++ /dev/null @@ -1,226 +0,0 @@ -import traverse from "./index"; -import includes from "lodash/collection/includes"; -import Scope from "./scope"; -import * as t from "../types"; - -export default class TraversalPath { - constructor(parent, container) { - this.container = container; - this.parent = parent; - this.data = {}; - } - - static get(parentPath, context, parent, container, key) { - var targetNode = container[key]; - var paths = container._paths ||= []; - var path; - - for (var i = 0; i < paths.length; i++) { - var pathCheck = paths[i]; - if (pathCheck.node === targetNode) { - path = pathCheck; - break; - } - } - - if (!path) { - path = new TraversalPath(parent, container); - paths.push(path); - } - - path.setContext(parentPath, context, key); - - return path; - } - - static getScope(node, parent, scope) { - var ourScope = scope; - - // we're entering a new scope so let's construct it! - if (t.isScope(node, parent)) { - ourScope = new Scope(node, parent, scope); - } - - return ourScope; - } - - insertBefore(node) { - - } - - insertAfter(node) { - - } - - setData(key, val) { - return this.data[key] = val; - } - - getData(key) { - return this.data[key]; - } - - setScope() { - this.scope = TraversalPath.getScope(this.node, this.parent, this.context.scope); - } - - setContext(parentPath, context, key) { - this.shouldSkip = false; - this.shouldStop = false; - - this.parentPath = parentPath || this.parentPath; - this.context = context; - this.state = context.state; - this.opts = context.opts; - this.key = key; - - this.setScope(); - } - - remove() { - this._refresh(this.node, []); - this.container[this.key] = null; - this.flatten(); - } - - skip() { - this.shouldSkip = true; - } - - stop() { - this.shouldStop = true; - this.shouldSkip = true; - } - - flatten() { - this.context.flatten(); - } - - _refresh(oldNode, newNodes) { - // todo - } - - refresh() { - var node = this.node; - this._refresh(node, [node]); - } - - get node() { - return this.container[this.key]; - } - - set node(replacement) { - if (!replacement) return this.remove(); - - var oldNode = this.node; - var isArray = Array.isArray(replacement); - var replacements = isArray ? replacement : [replacement]; - - // inherit comments from original node to the first replacement node - var inheritTo = replacements[0]; - if (inheritTo) t.inheritsComments(inheritTo, oldNode); - - // replace the node - this.container[this.key] = replacement; - - // potentially create new scope - this.setScope(); - - // refresh scope with new/removed bindings - this._refresh(oldNode, replacements); - - var file = this.scope && this.scope.file; - if (file) { - for (var i = 0; i < replacements.length; i++) { - file.checkNode(replacements[i], this.scope); - } - } - - // we're replacing a statement or block node with an array of statements so we better - // ensure that it's a block - if (isArray) { - if (includes(t.STATEMENT_OR_BLOCK_KEYS, this.key) && !t.isBlockStatement(this.container)) { - t.ensureBlock(this.container, this.key); - } - - this.flatten(); - // TODO: duplicate internal path metadata across the new node paths - } - } - - call(key) { - var node = this.node; - if (!node) return; - - var opts = this.opts; - var fn = opts[key] || opts; - if (opts[node.type]) fn = opts[node.type][key] || fn; - - var replacement = fn.call(this, node, this.parent, this.scope, this.state); - - if (replacement) { - this.node = replacement; - } - } - - isBlacklisted() { - var blacklist = this.opts.blacklist; - return blacklist && blacklist.indexOf(this.node.type) > -1; - } - - visit() { - if (this.isBlacklisted()) return false; - - this.call("enter"); - - if (this.shouldSkip) { - return this.shouldStop; - } - - var node = this.node; - var opts = this.opts; - - if (node) { - if (Array.isArray(node)) { - // traverse over these replacement nodes we purposely don't call exitNode - // as the original node has been destroyed - for (var i = 0; i < node.length; i++) { - traverse.node(node[i], opts, this.scope, this.state, this); - } - } else { - traverse.node(node, opts, this.scope, this.state, this); - this.call("exit"); - } - } - - return this.shouldStop; - } - - get(key) { - return TraversalPath.get(this, this.context, this.node, this.node, key); - } - - isReferencedIdentifier(opts) { - return t.isReferencedIdentifier(this.node, this.parent, opts); - } - - isReferenced() { - return t.isReferenced(this.node, this.parent); - } - - isScope() { - return t.isScope(this.node, this.parent); - } - - getBindingIdentifiers() { - return t.getBindingIdentifiers(this.node); - } -} - -for (var i = 0; i < t.TYPES.length; i++) { - let type = t.TYPES[i]; - let typeKey = `is${type}`; - TraversalPath.prototype[typeKey] = function (opts) { - return t[typeKey](this.node, opts); - }; -} diff --git a/src/babel/traversal/path/conversion.js b/src/babel/traversal/path/conversion.js new file mode 100644 index 0000000000..24293c7d8b --- /dev/null +++ b/src/babel/traversal/path/conversion.js @@ -0,0 +1,24 @@ +import * as t from "../../types"; + +/** + * Description + */ + +export function toComputedKey(): Object { + var node = this.node; + + var key; + if (this.isMemberExpression()) { + key = node.property; + } else if (this.isProperty()) { + key = node.key; + } else { + throw new ReferenceError("todo"); + } + + if (!node.computed) { + if (t.isIdentifier(key)) key = t.literal(key.name); + } + + return key; +} diff --git a/src/babel/types/evaluators.js b/src/babel/traversal/path/evaluation.js similarity index 65% rename from src/babel/types/evaluators.js rename to src/babel/traversal/path/evaluation.js index 163de2581e..2070ab5d2b 100644 --- a/src/babel/types/evaluators.js +++ b/src/babel/traversal/path/evaluation.js @@ -1,5 +1,3 @@ -import * as t from "./index"; - /** * Walk the input `node` and statically evaluate if it's truthy. * @@ -18,15 +16,15 @@ import * as t from "./index"; * */ -export function evaluateTruthy(node: Object, scope: Scope): boolean { - var res = evaluate(node, scope); +export function evaluateTruthy(): boolean { + var res = this.evaluate(); if (res.confident) return !!res.value; } /** * Walk the input `node` and statically evaluate it. * - * Returns an pbject in the form `{ confident, value }`. `confident` indicates + * Returns an object in the form `{ confident, value }`. `confident` indicates * whether or not we had to drop out of evaluating the expression because of * hitting an unknown node that we couldn't confidently find the value of. * @@ -38,24 +36,27 @@ export function evaluateTruthy(node: Object, scope: Scope): boolean { * */ -export function evaluate(node: Object, scope: Scope): { confident: boolean; value: any } { +export function evaluate(): { confident: boolean; value: any } { var confident = true; - var value = evaluate(node); + var value = evaluate(this); if (!confident) value = undefined; return { confident: confident, value: value }; - function evaluate(node) { + function evaluate(path) { if (!confident) return; - if (t.isSequenceExpression(node)) { - return evaluate(node.expressions[node.expressions.length - 1]); + var node = path.node; + + if (path.isSequenceExpression()) { + var exprs = path.get("expressions"); + return evaluate(exprs[exprs.length - 1]); } - if (t.isLiteral(node)) { + if (path.isLiteral()) { if (node.regex && node.value === null) { // we have a regex and we can't represent it natively } else { @@ -63,24 +64,29 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isConditionalExpression(node)) { - if (evaluate(node.test)) { - return evaluate(node.consequent); + if (path.isConditionalExpression()) { + if (evaluate(path.get("test"))) { + return evaluate(path.get("consequent")); } else { - return evaluate(node.alternate); + return evaluate(path.get("alternate")); } } - if (t.isIdentifier(node)) { - if (node.name === "undefined") { - return undefined; + if (path.isIdentifier({ name: "undefined" })) { + return undefined; + } + + if (path.isIdentifier() || path.isMemberExpression()) { + path = path.resolve(); + if (path) { + return evaluate(path); } else { - return evaluate(scope.getImmutableBindingValue(node.name)); + return confident = false; } } - if (t.isUnaryExpression(node, { prefix: true })) { - var arg = evaluate(node.argument); + if (path.isUnaryExpression({ prefix: true })) { + var arg = evaluate(path.get("argument")); switch (node.operator) { case "void": return undefined; case "!": return !arg; @@ -89,13 +95,13 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isArrayExpression(node) || t.isObjectExpression(node)) { + if (path.isArrayExpression() || path.isObjectExpression()) { // we could evaluate these but it's probably impractical and not very useful } - if (t.isLogicalExpression(node)) { - let left = evaluate(node.left); - let right = evaluate(node.right); + if (path.isLogicalExpression()) { + let left = evaluate(path.get("left")); + let right = evaluate(path.get("right")); switch (node.operator) { case "||": return left || right; @@ -103,9 +109,9 @@ export function evaluate(node: Object, scope: Scope): { confident: boolean; valu } } - if (t.isBinaryExpression(node)) { - let left = evaluate(node.left); - let right = evaluate(node.right); + if (path.isBinaryExpression()) { + let left = evaluate(path.get("left")); + let right = evaluate(path.get("right")); switch (node.operator) { case "-": return left - right; diff --git a/src/babel/traversal/path/index.js b/src/babel/traversal/path/index.js new file mode 100644 index 0000000000..64fed848f0 --- /dev/null +++ b/src/babel/traversal/path/index.js @@ -0,0 +1,464 @@ +import isBoolean from "lodash/lang/isBoolean"; +import isNumber from "lodash/lang/isNumber"; +import isRegExp from "lodash/lang/isRegExp"; +import isString from "lodash/lang/isString"; +import traverse from "../index"; +import includes from "lodash/collection/includes"; +import assign from "lodash/object/assign"; +import Scope from "../scope"; +import * as t from "../../types"; + +export default class TraversalPath { + constructor(parent, container) { + this.container = container; + this.parent = parent; + this.data = {}; + } + + static get(parentPath: TraversalPath, context?: TraversalContext, parent, container, key, file?: File) { + var targetNode = container[key]; + var paths = container._paths ||= []; + var path; + + for (var i = 0; i < paths.length; i++) { + var pathCheck = paths[i]; + if (pathCheck.node === targetNode) { + path = pathCheck; + break; + } + } + + if (!path) { + path = new TraversalPath(parent, container); + paths.push(path); + } + + path.setContext(parentPath, context, key, file); + + return path; + } + + static getScope(path: TraversalPath, scope: Scope, file?: File) { + var ourScope = scope; + + // we're entering a new scope so let's construct it! + if (path.isScope()) { + ourScope = new Scope(path, scope, file); + } + + return ourScope; + } + + insertBefore(node) { + + } + + insertAfter(node) { + + } + + setData(key, val) { + return this.data[key] = val; + } + + getData(key, def) { + var val = this.data[key]; + if (!val && def) val = this.data[key] = def; + return val; + } + + setScope(file?) { + this.scope = TraversalPath.getScope(this, this.context && this.context.scope, file); + } + + setContext(parentPath, context, key, file?) { + this.shouldSkip = false; + this.shouldStop = false; + + this.parentPath = parentPath || this.parentPath; + this.key = key; + + if (context) { + this.context = context; + this.state = context.state; + this.opts = context.opts; + } + + this.setScope(file); + } + + remove() { + this._refresh(this.node, []); + this.container[this.key] = null; + this.flatten(); + } + + skip() { + this.shouldSkip = true; + } + + stop() { + this.shouldStop = true; + this.shouldSkip = true; + } + + flatten() { + this.context.flatten(); + } + + _refresh(oldNode, newNodes) { + // todo + } + + refresh() { + var node = this.node; + this._refresh(node, [node]); + } + + errorWithNode(msg, Error = SyntaxError) { + var loc = this.node.loc.start; + var err = new Error(`Line ${loc.line}: ${msg}`); + err.loc = loc; + return err; + } + + get node() { + return this.container[this.key]; + } + + set node(replacement) { + if (!replacement) return this.remove(); + + var oldNode = this.node; + var isArray = Array.isArray(replacement); + var replacements = isArray ? replacement : [replacement]; + + // inherit comments from original node to the first replacement node + var inheritTo = replacements[0]; + if (inheritTo) t.inheritsComments(inheritTo, oldNode); + + // replace the node + this.container[this.key] = replacement; + + // potentially create new scope + this.setScope(); + + var file = this.scope && this.scope.file; + if (file) { + for (var i = 0; i < replacements.length; i++) { + file.checkNode(replacements[i], this.scope); + } + } + + // we're replacing a statement or block node with an array of statements so we better + // ensure that it's a block + if (isArray) { + if (includes(t.STATEMENT_OR_BLOCK_KEYS, this.key) && !t.isBlockStatement(this.container)) { + t.ensureBlock(this.container, this.key); + } + + this.flatten(); + // TODO: duplicate internal path metadata across the new node paths + } + } + + call(key) { + var node = this.node; + if (!node) return; + + var opts = this.opts; + var fn = opts[key] || opts; + if (opts[node.type]) fn = opts[node.type][key] || fn; + + var replacement = fn.call(this, node, this.parent, this.scope, this.state); + + if (replacement) { + this.node = replacement; + } + } + + isBlacklisted(): boolean { + var blacklist = this.opts.blacklist; + return blacklist && blacklist.indexOf(this.node.type) > -1; + } + + visit(): boolean { + if (this.isBlacklisted()) return false; + + this.call("enter"); + + if (this.shouldSkip) { + return this.shouldStop; + } + + var node = this.node; + var opts = this.opts; + + if (node) { + if (Array.isArray(node)) { + // traverse over these replacement nodes we purposely don't call exitNode + // as the original node has been destroyed + for (var i = 0; i < node.length; i++) { + traverse.node(node[i], opts, this.scope, this.state, this); + } + } else { + traverse.node(node, opts, this.scope, this.state, this); + this.call("exit"); + } + } + + return this.shouldStop; + } + + get(key) { + var node = this.node; + var container = node[key]; + if (Array.isArray(container)) { + return container.map((_, i) => { + return TraversalPath.get(this, this.context, node, container, i); + }); + } else { + return TraversalPath.get(this, this.context, node, node, key); + } + } + + has(key): boolean { + return !!this.node[key]; + } + + is(key): boolean { + return this.has(key); + } + + isnt(key): boolean { + return !this.has(key); + } + + getTypeAnnotation(): { + inferred: boolean; + annotation: ?Object; + } { + if (this.typeInfo) { + return this.typeInfo; + } + + var info = this.typeInfo = { + inferred: false, + annotation: null + }; + + var type = this.node.typeAnnotation; + + if (!type) { + info.inferred = true; + type = this.inferType(this); + } + + if (type) { + if (t.isTypeAnnotation(type)) type = type.typeAnnotation; + info.annotation = type; + } + + return info; + } + + resolve(): ?TraversalPath { + if (this.isVariableDeclarator()) { + if (this.get("id").isIdentifier()) { + return this.get("init").resolve(); + } else { + // otherwise it's a request for a destructuring declarator and i'm not + // ready to resolve those just yet + } + } else if (this.isIdentifier()) { + var binding = this.scope.getBinding(this.node.name); + if (!binding || !binding.constant) return; + + if (binding.path === this) { + return this; + } else { + return binding.path.resolve(); + } + } else if (this.isMemberExpression()) { + // this is dangerous, as non-direct target assignments will mutate it's state + // making this resolution inaccurate + + var targetKey = this.toComputedKey(); + if (!t.isLiteral(targetKey)) return; + var targetName = targetKey.value; + + var target = this.get("object").resolve(); + if (!target || !target.isObjectExpression()) return; + + var props = target.get("properties"); + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (!prop.isProperty()) continue; + + var key = prop.get("key"); + + // { foo: obj } + var match = prop.isnt("computed") && key.isIdentifier({ name: targetName }); + + // { "foo": "obj" } or { ["foo"]: "obj" } + match ||= key.isLiteral({ value: targetName }); + + if (match) return prop.get("value"); + } + } else { + return this; + } + } + + inferType(path: TraversalPath): ?Object { + path = path.resolve(); + if (!path) return; + + if (path.isRestElement() || path.parentPath.isRestElement() || path.isArrayExpression()) { + return t.genericTypeAnnotation(t.identifier("Array")); + } + + if (path.parentPath.isTypeCastExpression()) { + return path.parentPath.node.typeAnnotation; + } + + if (path.isTypeCastExpression()) { + return path.node.typeAnnotation; + } + + if (path.isObjectExpression()) { + return t.genericTypeAnnotation(t.identifier("Object")); + } + + if (path.isFunction()) { + return t.identifier("Function"); + } + + if (path.isLiteral()) { + var value = path.node.value; + if (isString(value)) return t.stringTypeAnnotation(); + if (isNumber(value)) return t.numberTypeAnnotation(); + if (isBoolean(value)) return t.booleanTypeAnnotation(); + } + + if (path.isCallExpression()) { + var callee = path.get("callee").resolve(); + if (callee && callee.isFunction()) return callee.node.returnType; + } + } + + isScope(): boolean { + return t.isScope(this.node, this.parent); + } + + isReferencedIdentifier(opts): boolean { + return t.isReferencedIdentifier(this.node, this.parent, opts); + } + + isReferenced(): boolean { + return t.isReferenced(this.node, this.parent); + } + + isBlockScoped(): boolean { + return t.isBlockScoped(this.node); + } + + isVar(): boolean { + return t.isVar(this.node); + } + + isScope(): boolean { + return t.isScope(this.node, this.parent); + } + + isTypeGeneric(genericName: string, opts = {}): boolean { + var typeInfo = this.getTypeAnnotation(); + var type = typeInfo.annotation; + if (!type) return false; + + if (type.inferred && opts.inference === false) { + return false; + } + + if (!t.isGenericTypeAnnotation(type) || !t.isIdentifier(type.id, { name: genericName })) { + return false; + } + + if (opts.requireTypeParameters && !type.typeParameters) { + return false; + } + + return true; + } + + getBindingIdentifiers() { + return t.getBindingIdentifiers(this.node); + } + + traverse(opts, state) { + traverse(this.node, opts, this.scope, state, this); + } + + /** + * Match the current node if it matches the provided `pattern`. + * + * For example, given the match `React.createClass` it would match the + * parsed nodes of `React.createClass` and `React["createClass"]`. + */ + + matchesPattern(pattern: string, allowPartial?: boolean): boolean { + var parts = pattern.split("."); + + // not a member expression + if (!this.isMemberExpression()) return false; + + var search = [this.node]; + var i = 0; + + while (search.length) { + var node = search.shift(); + + if (allowPartial && i === parts.length) { + return true; + } + + if (t.isIdentifier(node)) { + // this part doesn't match + if (parts[i] !== node.name) return false; + } else if (t.isLiteral(node)) { + // this part doesn't match + if (parts[i] !== node.value) return false; + } else if (t.isMemberExpression(node)) { + if (node.computed && !t.isLiteral(node.property)) { + // we can't deal with this + return false; + } else { + search.push(node.object); + search.push(node.property); + continue; + } + } else { + // we can't deal with this + return false; + } + + // too many parts + if (++i > parts.length) { + return false; + } + } + + return true; + } +} + +assign(TraversalPath.prototype, require("./evaluation")); +assign(TraversalPath.prototype, require("./conversion")); + +for (var i = 0; i < t.TYPES.length; i++) { + let type = t.TYPES[i]; + let typeKey = `is${type}`; + TraversalPath.prototype[typeKey] = function (opts) { + return t[typeKey](this.node, opts); + }; +} diff --git a/src/babel/traversal/scope.js b/src/babel/traversal/scope.js index 5c006703b8..e7f8ea1791 100644 --- a/src/babel/traversal/scope.js +++ b/src/babel/traversal/scope.js @@ -2,6 +2,7 @@ import includes from "lodash/collection/includes"; import traverse from "./index"; import defaults from "lodash/object/defaults"; import * as messages from "../messages"; +import Binding from "./binding"; import globals from "globals"; import flatten from "lodash/array/flatten"; import extend from "lodash/object/extend"; @@ -12,27 +13,27 @@ import * as t from "../types"; var functionVariableVisitor = { enter(node, parent, scope, state) { if (t.isFor(node)) { - each(t.FOR_INIT_KEYS, function (key) { - var declar = node[key]; - if (t.isVar(declar)) state.scope.registerBinding("var", declar); + each(t.FOR_INIT_KEYS, (key) => { + var declar = this.get(key); + if (declar.isVar()) state.scope.registerBinding("var", declar); }); } // this block is a function so we'll stop since none of the variables // declared within are accessible - if (t.isFunction(node)) return this.skip(); + if (this.isFunction()) return this.skip(); // function identifier doesn't belong to this scope if (state.blockId && node === state.blockId) return; // delegate block scope handling to the `blockVariableVisitor` - if (t.isBlockScoped(node)) return; + if (this.isBlockScoped()) return; // this will be hit again once we traverse into it after this iteration - if (t.isExportDeclaration(node) && t.isDeclaration(node.declaration)) return; + if (this.isExportDeclaration() && t.isDeclaration(node.declaration)) return; // we've ran into a declaration! - if (t.isDeclaration(node)) state.scope.registerDeclaration(node); + if (this.isDeclaration()) state.scope.registerDeclaration(this); } }; @@ -42,16 +43,20 @@ var programReferenceVisitor = { state.addGlobal(node); } else if (t.isLabeledStatement(node)) { state.addGlobal(node); - } else if (t.isAssignmentExpression(node) || t.isUpdateExpression(node) || (t.isUnaryExpression(node) && node.operator === "delete")) { - scope.registerBindingReassignment(node); + } else if (t.isAssignmentExpression(node)) { + scope.registerConstantViolation(this.get("left"), this.get("right")); + } else if (t.isUpdateExpression(node)) { + scope.registerConstantViolation(this.get("argument"), null); + } else if (t.isUnaryExpression(node) && node.operator === "delete") { + scope.registerConstantViolation(this.get("left"), null); } } }; var blockVariableVisitor = { enter(node, parent, scope, state) { - if (t.isFunctionDeclaration(node) || t.isBlockScoped(node)) { - state.registerDeclaration(node); + if (this.isFunctionDeclaration() || this.isBlockScoped()) { + state.registerDeclaration(this); } else if (t.isScope(node, parent)) { this.skip(); } @@ -65,12 +70,20 @@ export default class Scope { * within. */ - constructor(block: Object, parentBlock: Object, parent?: Scope, file?: File) { + constructor(path: TraversalPath, parent?: Scope, file?: File) { + var cached = path.getData("scope"); + if (cached) { + return cached; + } else { + //path.setData("scope", this); + } + this.parent = parent; this.file = parent ? parent.file : file; - this.parentBlock = parentBlock; - this.block = block; + this.parentBlock = path.parent; + this.block = path.node; + this.path = path; this.crawl(); } @@ -104,9 +117,7 @@ export default class Scope { */ generateUidIdentifier(name: string) { - var id = t.identifier(this.generateUid(name)); - this.getFunctionParent().registerBinding("uid", id); - return id; + return t.identifier(this.generateUid(name)); } /** @@ -121,7 +132,8 @@ export default class Scope { do { uid = this._generateUid(name, i); i++; - } while (this.hasBinding(uid) || this.hasGlobal(uid)); + } while (this.hasBinding(uid) || this.hasGlobal(uid) || this.hasUid(uid)); + this.getFunctionParent().uids[uid] = true; return uid; } @@ -131,6 +143,19 @@ export default class Scope { return `_${id}`; } + /** + * Description + */ + + hasUid(name): boolean { + var scope = this; + do { + if (scope.uids[name]) return true; + scope = scope.parent; + } while (scope); + return false; + } + /* * Description */ @@ -209,7 +234,7 @@ export default class Scope { rename(oldName: string, newName: string) { newName ||= this.generateUidIdentifier(oldName).name; - var info = this.getBindingInfo(oldName); + var info = this.getBinding(oldName); if (!info) return; var binding = info.identifier; @@ -220,7 +245,7 @@ export default class Scope { if (t.isReferencedIdentifier(node, parent) && node.name === oldName) { node.name = newName; } else if (t.isDeclaration(node)) { - var ids = t.getBindingIdentifiers(node); + var ids = this.getBindingIdentifiers(); for (var name in ids) { if (name === oldName) ids[name].name = newName; } @@ -238,102 +263,6 @@ export default class Scope { binding.name = newName; } - /** - * Description - */ - - inferType(node: Object) { - var target; - - if (t.isVariableDeclarator(node)) { - target = node.init; - } - - if (t.isArrayExpression(target)) { - return t.genericTypeAnnotation(t.identifier("Array")); - } - - if (t.isObjectExpression(target)) { - return; - } - - if (t.isLiteral(target)) { - return; - } - - if (t.isCallExpression(target) && t.isIdentifier(target.callee)) { - var funcInfo = this.getBindingInfo(target.callee.name); - if (funcInfo) { - var funcNode = funcInfo.node; - return !funcInfo.reassigned && t.isFunction(funcNode) && node.returnType; - } - } - - if (t.isIdentifier(target)) { - return; - } - } - - /** - * Description - */ - - isTypeGeneric(name: string, genericName: string) { - var info = this.getBindingInfo(name); - if (!info) return false; - - var type = info.typeAnnotation; - return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName }); - } - - /** - * Description - */ - - assignTypeGeneric(name: string, type: Object) { - this.assignType(name, t.genericTypeAnnotation(t.identifier(type))); - } - - /** - * Description - */ - - assignType(name: string, type: Object) { - var info = this.getBindingInfo(name); - if (!info) return; - - info.typeAnnotation = type; - } - - /** - * Description - */ - - getTypeAnnotation(id: Object, node: Object): Object { - var info = { - annotation: null, - inferred: false - }; - - var type; - - if (id.typeAnnotation) { - type = id.typeAnnotation; - } - - if (!type) { - info.inferred = true; - type = this.inferType(node); - } - - if (type) { - if (t.isTypeAnnotation(type)) type = type.typeAnnotation; - info.annotation = type; - } - - return info; - } - /** * Description */ @@ -341,8 +270,9 @@ export default class Scope { toArray(node: Object, i?: number) { var file = this.file; - if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) { - return node; + if (t.isIdentifier(node)) { + var binding = this.getBinding(node.name); + if (binding && binding.isTypeGeneric("Array", { inference: false })) return node; } if (t.isArrayExpression(node)) { @@ -368,33 +298,21 @@ export default class Scope { * Description */ - refreshDeclaration(node: Object) { - if (t.isBlockScoped(node)) { - this.getBlockParent().registerDeclaration(node); - } else if (t.isVariableDeclaration(node, { kind: "var" })) { - this.getFunctionParent().registerDeclaration(node); - } else if (node === this.block) { - this.recrawl(); - } - } - - /** - * Description - */ - - registerDeclaration(node: Object) { + registerDeclaration(path: TraversalPath) { + var node = path.node; if (t.isFunctionDeclaration(node)) { - this.registerBinding("hoisted", node); + this.registerBinding("hoisted", path); } else if (t.isVariableDeclaration(node)) { - for (var i = 0; i < node.declarations.length; i++) { - this.registerBinding(node.kind, node.declarations[i]); + var declarations = path.get("declarations"); + for (var i = 0; i < declarations.length; i++) { + this.registerBinding(node.kind, declarations[i]); } } else if (t.isClassDeclaration(node)) { - this.registerBinding("let", node); + this.registerBinding("let", path); } else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) { - this.registerBinding("module", node); + this.registerBinding("module", path); } else { - this.registerBinding("unknown", node); + this.registerBinding("unknown", path); } } @@ -402,18 +320,16 @@ export default class Scope { * Description */ - registerBindingReassignment(node: Object) { - var ids = t.getBindingIdentifiers(node); + registerConstantViolation(left: TraversalPath, right: TraversalPath) { + var ids = left.getBindingIdentifiers(); for (var name in ids) { - var info = this.getBindingInfo(name); - if (info) { - info.reassigned = true; - - if (info.typeAnnotationInferred) { - // destroy the inferred typeAnnotation - info.typeAnnotation = null; - } + var binding = this.getBinding(name); + if (!binding) continue; + if (right) { + var rightType = right.typeAnnotation; + if (rightType && binding.isCompatibleWithType(rightType)) continue; } + binding.reassign(); } } @@ -421,27 +337,22 @@ export default class Scope { * Description */ - registerBinding(kind: string, node: Object) { + registerBinding(kind: string, path: TraversalPath) { if (!kind) throw new ReferenceError("no `kind`"); - var ids = t.getBindingIdentifiers(node); + var ids = path.getBindingIdentifiers(); for (var name in ids) { var id = ids[name]; this.checkBlockScopedCollisions(kind, name, id); - var typeInfo = this.getTypeAnnotation(id, node); - - this.bindings[name] = { - typeAnnotationInferred: typeInfo.inferred, - typeAnnotation: typeInfo.annotation, - reassigned: false, - identifier: id, - scope: this, - node: node, - kind: kind - }; + this.bindings[name] = new Binding({ + identifier: id, + scope: this, + path: path, + kind: kind + }); } } @@ -472,7 +383,7 @@ export default class Scope { */ recrawl() { - this.block._scopeInfo = null; + this.path.setData("scopeInfo", null); this.crawl(); } @@ -481,91 +392,88 @@ export default class Scope { */ crawl() { - var block = this.block; - var i; + var path = this.path; // - var info = block._scopeInfo; - if (info) { - extend(this, info); - return; - } + var info = path.getData("scopeInfo"); + if (info) return extend(this, info); - info = block._scopeInfo = { + info = path.setData("scopeInfo", { bindings: object(), - globals: object() - }; + globals: object(), + uids: object() + }); extend(this, info); // ForStatement - left, init - if (t.isLoop(block)) { - for (i = 0; i < t.FOR_INIT_KEYS.length; i++) { - var node = block[t.FOR_INIT_KEYS[i]]; - if (t.isBlockScoped(node)) this.registerBinding("let", node); + if (path.isLoop()) { + for (let i = 0; i < t.FOR_INIT_KEYS.length; i++) { + var node = path.get(t.FOR_INIT_KEYS[i]); + if (node.isBlockScoped()) this.registerBinding("let", node); } - if (t.isBlockStatement(block.body)) { - block = block.body; - } + var body = path.get("body"); + if (body.isBlockStatement()) path = path.get("body"); } // FunctionExpression - id - if (t.isFunctionExpression(block) && block.id) { - if (!t.isProperty(this.parentBlock, { method: true })) { - this.registerBinding("var", block.id); + if (path.isFunctionExpression() && path.has("id")) { + if (!t.isProperty(path.parent, { method: true })) { + this.registerBinding("var", path.get("id")); } } // Class - if (t.isClass(block) && block.id) { - this.registerBinding("var", block.id); + if (path.isClass() && path.has("id")) { + this.registerBinding("var", path.get("id")); } // Function - params, rest - if (t.isFunction(block)) { - for (i = 0; i < block.params.length; i++) { - this.registerBinding("param", block.params[i]); + if (path.isFunction()) { + var params = path.get("params"); + for (let i = 0; i < params.length; i++) { + this.registerBinding("param", params[i]); } - this.traverse(block.body, blockVariableVisitor, this); + this.traverse(path.get("body").node, blockVariableVisitor, this); } // Program, BlockStatement, Function - let variables - if (t.isBlockStatement(block) || t.isProgram(block)) { - this.traverse(block, blockVariableVisitor, this); + if (path.isBlockStatement() || path.isProgram()) { + this.traverse(path.node, blockVariableVisitor, this); } // CatchClause - param - if (t.isCatchClause(block)) { - this.registerBinding("let", block.param); + if (path.isCatchClause()) { + this.registerBinding("let", path.get("param")); } // ComprehensionExpression - blocks - if (t.isComprehensionExpression(block)) { - this.registerBinding("let", block); + if (path.isComprehensionExpression()) { + this.registerBinding("let", path); } // Program, Function - var variables - if (t.isProgram(block) || t.isFunction(block)) { - this.traverse(block, functionVariableVisitor, { - blockId: block.id, + if (path.isProgram() || path.isFunction()) { + this.traverse(path.node, functionVariableVisitor, { + blockId: path.get("id").node, scope: this }); } // Program - if (t.isProgram(block)) { - this.traverse(block, programReferenceVisitor, this); + if (path.isProgram()) { + this.traverse(path.node, programReferenceVisitor, this); } } @@ -666,7 +574,7 @@ export default class Scope { * Description */ - getBindingInfo(name: string) { + getBinding(name: string) { var scope = this; do { @@ -688,7 +596,7 @@ export default class Scope { */ getBindingIdentifier(name: string) { - var info = this.getBindingInfo(name); + var info = this.getBinding(name); return info && info.identifier; } @@ -701,44 +609,6 @@ export default class Scope { return binding && binding.identifier; } - /** - * Description - */ - - getOwnImmutableBindingValue(name: string) { - return this._immutableBindingInfoToValue(this.getOwnBindingInfo(name)); - } - - /** - * Description - */ - - getImmutableBindingValue(name: string) { - return this._immutableBindingInfoToValue(this.getBindingInfo(name)); - } - - _immutableBindingInfoToValue(info) { - if (!info) return; - - // can't guarantee this value is the same - if (info.reassigned) return; - - var node = info.node; - if (t.isVariableDeclarator(node)) { - if (t.isIdentifier(node.id)) { - node = node.init; - } else { - // otherwise it's probably a destructuring like: - // var { foo } = "foo"; - return; - } - } - - if (t.isImmutable(node)) { - return node; - } - } - /** * Description */ @@ -781,7 +651,7 @@ export default class Scope { */ removeBinding(name: string) { - var info = this.getBindingInfo(name); + var info = this.getBinding(name); if (info) info.scope.removeOwnBinding(name); } } diff --git a/src/babel/types/converters.js b/src/babel/types/converters.js index ea3a240ff3..429e9c45dd 100644 --- a/src/babel/types/converters.js +++ b/src/babel/types/converters.js @@ -10,7 +10,7 @@ import * as t from "./index"; * Description */ -export function toComputedKey(node: Object, key: Object = node.key): Object { +export function toComputedKey(node: Object, key: Object = node.key || node.property): Object { if (!node.computed) { if (t.isIdentifier(key)) key = t.literal(key.name); } diff --git a/src/babel/types/index.js b/src/babel/types/index.js index dfbdf9b07e..a82b86cfc3 100644 --- a/src/babel/types/index.js +++ b/src/babel/types/index.js @@ -24,15 +24,15 @@ function registerType(type: string, skipAliasCheck?: boolean) { }; } -export var STATEMENT_OR_BLOCK_KEYS = ["consequent", "body", "alternate"]; -export var NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array", "String"]; -export var FLATTENABLE_KEYS = ["body", "expressions"]; -export var FOR_INIT_KEYS = ["left", "init"]; -export var COMMENT_KEYS = ["leadingComments", "trailingComments"]; +export const STATEMENT_OR_BLOCK_KEYS = ["consequent", "body", "alternate"]; +export const NATIVE_TYPE_NAMES = ["Array", "Object", "Number", "Boolean", "Date", "Array", "String"]; +export const FLATTENABLE_KEYS = ["body", "expressions"]; +export const FOR_INIT_KEYS = ["left", "init"]; +export const COMMENT_KEYS = ["leadingComments", "trailingComments"]; -export var VISITOR_KEYS = require("./visitor-keys"); -export var BUILDER_KEYS = require("./builder-keys"); -export var ALIAS_KEYS = require("./alias-keys"); +export const VISITOR_KEYS = require("./visitor-keys"); +export const BUILDER_KEYS = require("./builder-keys"); +export const ALIAS_KEYS = require("./alias-keys"); t.FLIPPED_ALIAS_KEYS = {}; @@ -52,7 +52,7 @@ each(t.FLIPPED_ALIAS_KEYS, function (types, type) { registerType(type, false); }); -export var TYPES = Object.keys(t.VISITOR_KEYS).concat(Object.keys(t.FLIPPED_ALIAS_KEYS)); +export const TYPES = Object.keys(t.VISITOR_KEYS).concat(Object.keys(t.FLIPPED_ALIAS_KEYS)); /** * Returns whether `node` is of given `type`. @@ -281,7 +281,6 @@ export function inheritsComments(child: Object, parent: Object): Object { export function inherits(child: Object, parent: Object): Object { child._declarations = parent._declarations; - child._scopeInfo = parent._scopeInfo; child.range = parent.range; child.start = parent.start; child.loc = parent.loc; @@ -298,7 +297,6 @@ toFastProperties(t); toFastProperties(t.VISITOR_KEYS); exports.__esModule = true; -assign(t, require("./evaluators")); assign(t, require("./retrievers")); assign(t, require("./validators")); assign(t, require("./converters")); diff --git a/src/babel/types/visitor-keys.json b/src/babel/types/visitor-keys.json index e3ad4a629e..b30d9f0106 100644 --- a/src/babel/types/visitor-keys.json +++ b/src/babel/types/visitor-keys.json @@ -93,7 +93,7 @@ "TypeofTypeAnnotation": ["argument"], "TypeAlias": ["id", "typeParameters", "right"], "TypeAnnotation": ["typeAnnotation"], - "TypeCastExpression": ["expression"], + "TypeCastExpression": ["expression", "typeAnnotation"], "TypeParameterDeclaration": ["params"], "TypeParameterInstantiation": ["params"], "ObjectTypeAnnotation": ["key", "value"], diff --git a/src/babel/util.js b/src/babel/util.js index f9a8184639..d3b271ecc1 100644 --- a/src/babel/util.js +++ b/src/babel/util.js @@ -4,6 +4,7 @@ import buildDebug from "debug/node"; import cloneDeep from "lodash/lang/cloneDeep"; import isBoolean from "lodash/lang/isBoolean"; import * as messages from "./messages"; +import minimatch from "minimatch"; import contains from "lodash/collection/contains"; import traverse from "./traversal"; import isString from "lodash/lang/isString"; @@ -43,16 +44,21 @@ export function list(val: string): Array { export function regexify(val: any): RegExp { if (!val) return new RegExp(/.^/); if (Array.isArray(val)) val = val.join("|"); - if (isString(val)) return new RegExp(val); + if (isString(val)) return minimatch.makeRe(val, { nocase: true }); if (isRegExp(val)) return val; throw new TypeError("illegal type for regexify"); } -export function arrayify(val: any): Array { +export function arrayify(val: any, mapFn?: Function): Array { if (!val) return []; - if (isBoolean(val)) return [val]; - if (isString(val)) return list(val); - if (Array.isArray(val)) return val; + if (isBoolean(val)) return arrayify([val], mapFn); + if (isString(val)) return arrayify(list(val), mapFn); + + if (Array.isArray(val)) { + if (mapFn) val = val.map(mapFn); + return val; + } + throw new TypeError("illegal type for arrayify"); } diff --git a/test/_transformation-helper.js b/test/_transformation-helper.js index bcad6fdba8..14f0146983 100644 --- a/test/_transformation-helper.js +++ b/test/_transformation-helper.js @@ -55,6 +55,7 @@ var run = function (task, done) { var getOpts = function (self) { return _.merge({ + suppressDeprecationMessages: true, filename: self.loc }, opts); }; diff --git a/test/fixtures/transformation/es6-block-scoping/hoisting/actual.js b/test/fixtures/transformation/es6-block-scoping/hoisting/actual.js index 8baf9f54a2..03757cd57d 100644 --- a/test/fixtures/transformation/es6-block-scoping/hoisting/actual.js +++ b/test/fixtures/transformation/es6-block-scoping/hoisting/actual.js @@ -1,4 +1,4 @@ -for (let i of [1, 2, 3]) { +for (let i of nums) { var x = 5; fns.push(function () { return i * x; diff --git a/test/fixtures/transformation/es6-block-scoping/hoisting/expected.js b/test/fixtures/transformation/es6-block-scoping/hoisting/expected.js index f8cec2e342..661e28888b 100644 --- a/test/fixtures/transformation/es6-block-scoping/hoisting/expected.js +++ b/test/fixtures/transformation/es6-block-scoping/hoisting/expected.js @@ -5,7 +5,7 @@ var _didIteratorError = false; var _iteratorError = undefined; try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var x; (function () { @@ -30,4 +30,4 @@ try { throw _iteratorError; } } -} \ No newline at end of file +} diff --git a/test/fixtures/transformation/es6-modules-amd/exports-variable/expected.js b/test/fixtures/transformation/es6-modules-amd/exports-variable/expected.js index bd6d12e3f6..b5f5c1538f 100644 --- a/test/fixtures/transformation/es6-modules-amd/exports-variable/expected.js +++ b/test/fixtures/transformation/es6-modules-amd/exports-variable/expected.js @@ -26,7 +26,9 @@ define(["exports"], function (exports) { function foo7() {} - var foo8 = exports.foo8 = function foo8() { + var foo8 = function foo8() { _classCallCheck(this, foo8); }; -}); \ No newline at end of file + + exports.foo8 = foo8; +}); diff --git a/test/fixtures/transformation/es6-modules-common/exports-variable/expected.js b/test/fixtures/transformation/es6-modules-common/exports-variable/expected.js index 0b2cbf18e7..b468b9bc5d 100644 --- a/test/fixtures/transformation/es6-modules-common/exports-variable/expected.js +++ b/test/fixtures/transformation/es6-modules-common/exports-variable/expected.js @@ -25,6 +25,8 @@ exports.foo6 = foo6; function foo7() {} -var foo8 = exports.foo8 = function foo8() { +var foo8 = function foo8() { _classCallCheck(this, foo8); -}; \ No newline at end of file +}; + +exports.foo8 = foo8; diff --git a/test/fixtures/transformation/es6-modules-system/exports-variable/expected.js b/test/fixtures/transformation/es6-modules-system/exports-variable/expected.js index 8626db3270..5006e12bbf 100644 --- a/test/fixtures/transformation/es6-modules-system/exports-variable/expected.js +++ b/test/fixtures/transformation/es6-modules-system/exports-variable/expected.js @@ -34,11 +34,13 @@ System.register([], function (_export) { _export("foo6", foo6); - foo8 = _export("foo8", function foo8() { + foo8 = function foo8() { _classCallCheck(this, foo8); - }); + }; + + _export("foo8", foo8); _export("foo3", foo3 = 5); } }; -}); \ No newline at end of file +}); diff --git a/test/fixtures/transformation/es6-modules-umd/exports-variable/expected.js b/test/fixtures/transformation/es6-modules-umd/exports-variable/expected.js index 49eca85aba..0f1a05c14d 100644 --- a/test/fixtures/transformation/es6-modules-umd/exports-variable/expected.js +++ b/test/fixtures/transformation/es6-modules-umd/exports-variable/expected.js @@ -32,7 +32,9 @@ function foo7() {} - var foo8 = exports.foo8 = function foo8() { + var foo8 = function foo8() { _classCallCheck(this, foo8); }; -}); \ No newline at end of file + + exports.foo8 = foo8; +}); diff --git a/test/fixtures/transformation/es6-spread/known-array/actual.js b/test/fixtures/transformation/es6-spread/known-array/actual.js deleted file mode 100644 index af3c9258a8..0000000000 --- a/test/fixtures/transformation/es6-spread/known-array/actual.js +++ /dev/null @@ -1,2 +0,0 @@ -var arr: Array = bar; -[...arr]; diff --git a/test/fixtures/transformation/es6-spread/known-array/expected.js b/test/fixtures/transformation/es6-spread/known-array/expected.js deleted file mode 100644 index cba7214bfd..0000000000 --- a/test/fixtures/transformation/es6-spread/known-array/expected.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; - -var arr = bar; -[].concat(arr); diff --git a/test/fixtures/transformation/es7-comprehensions/arguments/actual.js b/test/fixtures/transformation/es7-comprehensions/arguments/actual.js index 95f9a4df57..5b07664e61 100644 --- a/test/fixtures/transformation/es7-comprehensions/arguments/actual.js +++ b/test/fixtures/transformation/es7-comprehensions/arguments/actual.js @@ -1,5 +1,5 @@ function add() { - return [for (i of [1, 2, 3]) i * arguments[0]]; + return [for (i of nums) i * arguments[0]]; } add(5); diff --git a/test/fixtures/transformation/es7-comprehensions/arguments/expected.js b/test/fixtures/transformation/es7-comprehensions/arguments/expected.js index 610d5ef84b..ea21a12ae3 100644 --- a/test/fixtures/transformation/es7-comprehensions/arguments/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/arguments/expected.js @@ -10,7 +10,7 @@ function add() { var _iteratorError = undefined; try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var i = _step.value; _ref.push(i * _arguments[0]); @@ -34,4 +34,4 @@ function add() { })(); } -add(5); \ No newline at end of file +add(5); diff --git a/test/fixtures/transformation/es7-comprehensions/array-expression-single-if/expected.js b/test/fixtures/transformation/es7-comprehensions/array-expression-single-if/expected.js index e0815299e2..f36d1c6137 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-expression-single-if/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/array-expression-single-if/expected.js @@ -2,32 +2,15 @@ var arr = (function () { var _arr = []; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + var _arr2 = [1, 2, 3]; - try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var i = _step.value; + for (var _i = 0; _i < _arr2.length; _i++) { + var i = _arr2[_i]; - if (i > 1) { - _arr.push(i * i); - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator["return"]) { - _iterator["return"](); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } + if (i > 1) { + _arr.push(i * i); } } return _arr; -})(); \ No newline at end of file +})(); diff --git a/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js b/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js index 2ee858e713..0758f1ede6 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/array-expression-single/expected.js @@ -2,30 +2,13 @@ var arr = (function () { var _arr = []; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; + var _arr2 = [1, 2, 3]; - try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var i = _step.value; + for (var _i = 0; _i < _arr2.length; _i++) { + var i = _arr2[_i]; - _arr.push(i * i); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator["return"]) { - _iterator["return"](); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } + _arr.push(i * i); } return _arr; -})(); \ No newline at end of file +})(); diff --git a/test/fixtures/transformation/es7-comprehensions/array-this/actual.js b/test/fixtures/transformation/es7-comprehensions/array-this/actual.js index 355ed48188..5ae580c2d3 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-this/actual.js +++ b/test/fixtures/transformation/es7-comprehensions/array-this/actual.js @@ -1,5 +1,5 @@ function add() { - return [for (i of [1, 2, 3]) i * this.multiplier]; + return [for (i of nums) i * this.multiplier]; } add.call({ multiplier: 5 }); diff --git a/test/fixtures/transformation/es7-comprehensions/array-this/expected.js b/test/fixtures/transformation/es7-comprehensions/array-this/expected.js index 5f7f52ab97..4ca4eeaee8 100644 --- a/test/fixtures/transformation/es7-comprehensions/array-this/expected.js +++ b/test/fixtures/transformation/es7-comprehensions/array-this/expected.js @@ -10,7 +10,7 @@ function add() { var _iteratorError = undefined; try { - for (var _iterator = [1, 2, 3][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + for (var _iterator = nums[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var i = _step.value; _ref.push(i * _this.multiplier); @@ -34,4 +34,4 @@ function add() { })(); } -add.call({ multiplier: 5 }); \ No newline at end of file +add.call({ multiplier: 5 }); diff --git a/vendor/.babelrc b/vendor/.babelrc new file mode 100644 index 0000000000..3a8f8eb911 --- /dev/null +++ b/vendor/.babelrc @@ -0,0 +1,3 @@ +{ + "breakConfig": true +}