consolidate the concept of "virtual types"

This commit is contained in:
Sebastian McKenzie 2015-05-05 02:33:49 +01:00
parent 6a4e93bf0f
commit 0112c63779
11 changed files with 188 additions and 166 deletions

View File

@ -17,7 +17,7 @@ export default function (exports, opts) {
};
exports.JSXIdentifier = function (node, parent) {
if (node.name === "this" && t.isReferenced(node, parent)) {
if (node.name === "this" && this.isReferenced()) {
return t.thisExpression();
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
node.type = "Identifier";

View File

@ -19,7 +19,7 @@ var awaitVisitor = {
var referenceVisitor = {
enter(node, parent, scope, state) {
var name = state.id.name;
if (t.isReferencedIdentifier(node, parent, { name: name }) && scope.bindingIdentifierEquals(name, state.id)) {
if (this.isReferencedIdentifier({ name: name }) && scope.bindingIdentifierEquals(name, state.id)) {
return state.ref = state.ref || scope.generateUidIdentifier(name);
}
}

View File

@ -1,5 +1,6 @@
import callDelegate from "../../helpers/call-delegate";
import * as util from "../../../util";
import traverse from "../../../traversal";
import * as t from "../../../types";
export function shouldVisit(node) {
@ -13,16 +14,15 @@ var hasDefaults = function (node) {
return false;
};
var iifeVisitor = {
enter(node, parent, scope, state) {
if (!this.isReferencedIdentifier()) return;
var iifeVisitor = traverse.explode({
ReferencedIdentifier(node, parent, scope, state) {
if (!state.scope.hasOwnBinding(node.name)) return;
if (state.scope.bindingIdentifierEquals(node.name, node)) return;
state.iife = true;
this.stop();
}
};
});
exports.Function = function (node, parent, scope, file) {
if (!hasDefaults(node)) return;

View File

@ -1,8 +1,8 @@
import traverse from "../../../traversal";
import * as t from "../../../types";
var visitor = {
enter(node, parent, scope, state) {
if (!this.isReferencedIdentifier()) return;
var visitor = traverse.explode({
ReferencedIdentifier(node, parent, scope, state) {
if (t.isFor(parent) && parent.left === node) return;
var declared = state.letRefs[node.name];
@ -25,7 +25,7 @@ var visitor = {
return t.logicalExpression("&&", assert, node);
}
}
};
});
export var metadata = {
optional: true

View File

@ -1,6 +1,7 @@
import reduceRight from "lodash/collection/reduceRight";
import * as messages from "../../../messages";
import flatten from "lodash/array/flatten";
import traverse from "../../../traversal";
import * as util from "../../../util";
import map from "lodash/collection/map";
import * as t from "../../../types";
@ -16,54 +17,61 @@ function returnBlock(expr) {
}
// looks for and replaces tail recursion calls
var firstPass = {
var firstPass = traverse.explode({
enter(node, parent, scope, state) {
if (this.isReturnStatement()) {
this.skip();
return state.subTransform(node.argument);
} else if (t.isTryStatement(parent)) {
if (t.isTryStatement(parent)) {
if (node === parent.block) {
this.skip();
} else if (parent.finalizer && node !== parent.finalizer) {
this.skip();
}
} else if (this.isFunction()) {
this.skip();
} else if (this.isVariableDeclaration()) {
this.skip();
state.vars.push(node);
}
},
ReturnStatement(node, parent, scope, state) {
this.skip();
return state.subTransform(node.argument);
},
Function(node, parent, scope, state) {
this.skip();
},
VariableDeclaration(node, parent, scope, state) {
this.skip();
state.vars.push(node);
}
};
});
// hoists up function declarations, replaces `this` and `arguments` and marks
// them as needed
var secondPass = {
enter(node, parent, scope, state) {
if (this.isThisExpression()) {
state.needsThis = true;
return state.getThisId();
} else if (this.isReferencedIdentifier({ name: "arguments" })) {
state.needsArguments = true;
return state.getArgumentsId();
} else if (this.isFunction()) {
this.skip();
if (this.isFunctionDeclaration()) {
node = t.variableDeclaration("var", [
t.variableDeclarator(node.id, t.toExpression(node))
]);
node._blockHoist = 2;
return node;
}
var secondPass = traverse.explode({
ThisExpression(node, parent, scope, state) {
state.needsThis = true;
return state.getThisId();
},
ReferencedIdentifier(node, parent, scope, state) {
if (node.name !== "arguments") return;
state.needsArguments = true;
return state.getArgumentsId();
},
Function(node, parent, scope, state) {
this.skip();
if (this.isFunctionDeclaration()) {
node = t.variableDeclaration("var", [
t.variableDeclarator(node.id, t.toExpression(node))
]);
node._blockHoist = 2;
return node;
}
}
};
});
// optimizes recursion by removing `this` and `arguments` if they aren't used
var thirdPass = {
enter(node, parent, scope, state) {
if (!this.isExpressionStatement()) return;
var thirdPass = traverse.explode({
ExpressionStatement(node, parent, scope, state) {
var expr = node.expression;
if (!t.isAssignmentExpression(expr)) return;
@ -75,7 +83,7 @@ var thirdPass = {
});
}
}
};
});
class TailCallTransformer {
constructor(path, scope, file) {

View File

@ -15,7 +15,7 @@ export default class Binding {
* Description
*/
setTypeAnnotation() {
setTypeAnnotation() {
var typeInfo = this.path.getTypeAnnotation();
this.typeAnnotationInferred = typeInfo.inferred;
this.typeAnnotation = typeInfo.annotation;

View File

@ -1,10 +1,12 @@
import PathHoister from "./hoister";
import * as virtualTypes from "./virtual-types";
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 codeFrame from "../../helpers/code-frame";
import parse from "../../helpers/parse";
import { explode } from "../visitors";
import traverse from "../index";
import includes from "lodash/collection/includes";
import assign from "lodash/object/assign";
@ -12,33 +14,33 @@ import extend from "lodash/object/extend";
import Scope from "../scope";
import * as t from "../../types";
var hoistVariablesVisitor = {
enter(node, parent, scope) {
if (this.isFunction()) {
return this.skip();
}
var hoistVariablesVisitor = explode({
Function() {
this.skip();
},
VariableDeclaration(node, parent, scope) {
if (node.kind !== "var") return;
if (this.isVariableDeclaration() && node.kind === "var") {
var bindings = this.getBindingIdentifiers();
for (var key in bindings) {
scope.push({ id: bindings[key] });
}
var exprs = [];
for (var i = 0; i < node.declarations.length; i++) {
var declar = node.declarations[i];
if (declar.init) {
exprs.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
));
}
}
return exprs;
for (var key in bindings) {
scope.push({ id: bindings[key] });
}
var exprs = [];
for (var i = 0; i < node.declarations.length; i++) {
var declar = node.declarations[i];
if (declar.init) {
exprs.push(t.expressionStatement(
t.assignmentExpression("=", declar.id, declar.init)
));
}
}
return exprs;
}
};
});
export default class TraversalPath {
constructor(parent, container) {
@ -938,46 +940,6 @@ export default class TraversalPath {
}
}
/**
* Description
*/
isScope(): boolean {
return t.isScope(this.node, this.parent);
}
/**
* Description
*/
isReferencedIdentifier(opts): boolean {
return t.isReferencedIdentifier(this.node, this.parent, opts);
}
/**
* Description
*/
isReferenced(): boolean {
return t.isReferenced(this.node, this.parent);
}
/**
* Description
*/
isBlockScoped(): boolean {
return t.isBlockScoped(this.node);
}
/**
* Description
*/
isVar(): boolean {
return t.isVar(this.node);
}
/**
* Description
*/
@ -1096,8 +1058,15 @@ export default class TraversalPath {
assign(TraversalPath.prototype, require("./evaluation"));
assign(TraversalPath.prototype, require("./conversion"));
for (var i = 0; i < t.TYPES.length; i++) {
let type = t.TYPES[i];
for (let type in virtualTypes) {
if (type[0] === "_") continue;
TraversalPath.prototype[`is${type}`] = function (opts) {
return virtualTypes[type].checkPath(this, opts);
};
}
for (let type of (t.TYPES: Array)) {
let typeKey = `is${type}`;
TraversalPath.prototype[typeKey] = function (opts) {
return t[typeKey](this.node, opts);

View File

@ -0,0 +1,34 @@
import * as t from "../../types";
export var ReferencedIdentifier = {
type: "Identifier",
checkPath(path, opts) {
return t.isReferencedIdentifier(path.node, path.parent, opts);
}
};
export var Scope = {
type: "Scopable",
checkPath(path) {
return t.isScope(path.node, path.parent);
}
};
export var Referenced = {
checkPath(path) {
return t.isReferenced(path.node, path.parent);
}
};
export var BlockScoped = {
checkPath(path) {
return t.isBlockScoped(path.node);
}
};
export var Var = {
type: "VariableDeclaration",
checkPath(path) {
return t.isVar(path.node);
}
};

View File

@ -65,33 +65,34 @@ var programReferenceVisitor = explode({
}
});
var blockVariableVisitor = {
var blockVariableVisitor = explode({
Scope() {
this.skip();
},
enter(node, parent, scope, state) {
if (this.isFunctionDeclaration() || this.isBlockScoped()) {
state.registerDeclaration(this);
}
if (this.isScope()) {
this.skip();
}
}
};
});
var renameVisitor = explode({
Identifier(node, parent, scope, state) {
if (this.isReferenced() && node.name === state.oldName) {
if (this.parentPath.isProperty() && this.key === "key" && parent.shorthand) {
var value = t.identifier(state.newName);;
ReferencedIdentifier(node, parent, scope, state) {
if (node.name !== state.oldName) return;
if (parent.value === state.binding) {
state.info.identifier = state.binding = value;
}
if (this.parentPath.isProperty() && this.key === "key" && parent.shorthand) {
var value = t.identifier(state.newName);;
parent.shorthand = false;
parent.value = value;
parent.key = t.identifier(state.oldName);
} else {
node.name = state.newName;
if (parent.value === state.binding) {
state.info.identifier = state.binding = value;
}
parent.shorthand = false;
parent.value = value;
parent.key = t.identifier(state.oldName);
} else {
node.name = state.newName;
}
},

View File

@ -1,20 +1,56 @@
import * as typeWrappers from "./wrappers";
import * as messages from "../../messages";
import * as t from "../../types";
import * as virtualTypes from "./path/virtual-types";
import * as messages from "../messages";
import * as t from "../types";
export function explode(visitor, mergeConflicts) {
// make sure there's no __esModule type since this is because we're using loose mode
// and it sets __esModule to be enumerable on all modules :(
delete visitor.__esModule;
// ensure visitors are objects
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
var fns = visitor[nodeType];
if (typeof fns === "function") {
visitor[nodeType] = fns = { enter: fns };
visitor[nodeType] = { enter: fns };
}
}
// add type wrappers
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
var wrapper = virtualTypes[nodeType];
if (!wrapper) continue;
// wrap all the functions
var fns = visitor[nodeType];
for (var type in fns) {
fns[type] = wrapCheck(wrapper, fns[type]);
}
// clear it from the visitor
delete visitor[nodeType];
if (wrapper.type) {
// merge the visitor if necessary or just put it back in
if (visitor[wrapper.type]) {
merge(visitor[wrapper.type], fns);
} else {
visitor[wrapper.type] = fns;
}
} else {
merge(visitor, fns);
}
}
// add aliases
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
var fns = visitor[nodeType];
var aliases = t.FLIPPED_ALIAS_KEYS[nodeType];
if (!aliases) continue;
@ -34,30 +70,6 @@ export function explode(visitor, mergeConflicts) {
}
}
// handle type wrappers
for (let nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
var wrapper = typeWrappers[nodeType];
if (!wrapper) continue;
// wrap all the functions
var fns = visitor[nodeType];
for (var type in fns) {
fns[type] = wrapper.wrap(fns[type]);
}
// clear it from the visitor
delete visitor[nodeType];
// merge the visitor if necessary or just put it back in
if (visitor[wrapper.type]) {
merge(visitor[wrapper.type], fns);
} else {
visitor[wrapper.type] = fns;
}
}
return visitor;
}
@ -75,7 +87,7 @@ export function verify(visitor) {
for (var nodeType in visitor) {
if (shouldIgnoreKey(nodeType)) continue;
if (!t.VISITOR_KEYS[nodeType]) {
if (t.TYPES.indexOf(nodeType) < 0) {
throw new Error(messages.get("traverseVerifyNodeType", nodeType));
}
@ -94,6 +106,14 @@ export function verify(visitor) {
visitor._verified = true;
}
function wrapCheck(wrapper, fn) {
return function () {
if (wrapper.checkPath(this)) {
return fn.apply(this, arguments);
}
};
}
function shouldIgnoreKey(key) {
// internal/hidden key
if (key[0] === "_") return true;

View File

@ -1,10 +0,0 @@
export var ReferencedIdentifier = {
type: "Identifier",
wrap(fn) {
return function () {
if (this.isReferencedIdentifier()) {
return fn.apply(this, arguments);
}
};
}
}