separate binding logic from scope to a binding class, move binding type resolution to the path so it can be used on any expression - #653
This commit is contained in:
parent
40a111abbf
commit
b44ba25d11
1
src/babel/transformation/templates/for-of-array.js
Normal file
1
src/babel/transformation/templates/for-of-array.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
for (var KEY = 0; KEY < ARR.length; KEY++) BODY;
|
||||||
@ -432,7 +432,7 @@ class DestructuringTransformer {
|
|||||||
} else {
|
} else {
|
||||||
arrayRef = this.scope.generateUidBasedOnNode(arrayRef);
|
arrayRef = this.scope.generateUidBasedOnNode(arrayRef);
|
||||||
this.nodes.push(this.buildVariableDeclaration(arrayRef, toArray));
|
this.nodes.push(this.buildVariableDeclaration(arrayRef, toArray));
|
||||||
this.scope.assignTypeGeneric(arrayRef.name, "Array");
|
this.getBinding(arrayRef.name).assignTypeGeneric("Array");
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import * as t from "../../../types";
|
|||||||
export var check = t.isForOfStatement;
|
export var check = t.isForOfStatement;
|
||||||
|
|
||||||
export function ForOfStatement(node, parent, scope, file) {
|
export function ForOfStatement(node, parent, scope, file) {
|
||||||
|
if (this.get("right").isTypeGeneric("Array")) {
|
||||||
|
return array(node, scope, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
var callback = spec;
|
var callback = spec;
|
||||||
if (file.isLoose("es6.forOf")) callback = loose;
|
if (file.isLoose("es6.forOf")) callback = loose;
|
||||||
|
|
||||||
@ -36,6 +42,44 @@ export function ForOfStatement(node, parent, scope, file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var array = function (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 loose = function (node, parent, scope, file) {
|
||||||
var left = node.left;
|
var left = node.left;
|
||||||
var declar, id;
|
var declar, id;
|
||||||
|
|||||||
@ -127,16 +127,14 @@ exports.Function = function (node, parent, scope, file) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.assignTypeGeneric(rest.name, "Array");
|
|
||||||
|
|
||||||
var loop = util.template("rest", {
|
var loop = util.template("rest", {
|
||||||
ARGUMENTS: argsId,
|
ARGUMENTS: argsId,
|
||||||
ARRAY_KEY: arrKey,
|
ARRAY_KEY: arrKey,
|
||||||
ARRAY_LEN: arrLen,
|
ARRAY_LEN: arrLen,
|
||||||
START: start,
|
START: start,
|
||||||
ARRAY: rest,
|
ARRAY: rest,
|
||||||
KEY: key,
|
KEY: key,
|
||||||
LEN: len,
|
LEN: len
|
||||||
});
|
});
|
||||||
loop._blockHoist = node.params.length + 1;
|
loop._blockHoist = node.params.length + 1;
|
||||||
node.body.body.unshift(loop);
|
node.body.body.unshift(loop);
|
||||||
|
|||||||
@ -142,7 +142,7 @@ class TailCallTransformer {
|
|||||||
hasDeopt() {
|
hasDeopt() {
|
||||||
// check if the ownerId has been reassigned, if it has then it's not safe to
|
// check if the ownerId has been reassigned, if it has then it's not safe to
|
||||||
// perform optimisations
|
// perform optimisations
|
||||||
var ownerIdInfo = this.scope.getBindingInfo(this.ownerId.name);
|
var ownerIdInfo = this.scope.getBinding(this.ownerId.name);
|
||||||
return ownerIdInfo && ownerIdInfo.reassigned;
|
return ownerIdInfo && ownerIdInfo.reassigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ export default {
|
|||||||
"playground.objectGetterMemoization": require("./playground/object-getter-memoization"),
|
"playground.objectGetterMemoization": require("./playground/object-getter-memoization"),
|
||||||
|
|
||||||
reactCompat: require("./other/react-compat"),
|
reactCompat: require("./other/react-compat"),
|
||||||
flow: require("./other/flow"),
|
|
||||||
react: require("./other/react"),
|
react: require("./other/react"),
|
||||||
|
|
||||||
_modules: require("./internal/modules"),
|
_modules: require("./internal/modules"),
|
||||||
@ -110,5 +109,6 @@ export default {
|
|||||||
"utility.inlineExpressions": require("./utility/inline-expressions"),
|
"utility.inlineExpressions": require("./utility/inline-expressions"),
|
||||||
"utility.deadCodeElimination": require("./utility/dead-code-elimination"),
|
"utility.deadCodeElimination": require("./utility/dead-code-elimination"),
|
||||||
|
|
||||||
|
flow: require("./other/flow"),
|
||||||
_cleanUp: require("./internal/cleanup")
|
_cleanUp: require("./internal/cleanup")
|
||||||
};
|
};
|
||||||
|
|||||||
92
src/babel/traversal/binding.js
Normal file
92
src/babel/traversal/binding.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import * as t from "../types";
|
||||||
|
|
||||||
|
export default class Binding {
|
||||||
|
constructor({ identifier, scope, path, kind }) {
|
||||||
|
this.identifier = identifier;
|
||||||
|
this.reassigned = false;
|
||||||
|
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.reassigned = true;
|
||||||
|
|
||||||
|
if (this.typeAnnotationInferred) {
|
||||||
|
// destroy the inferred typeAnnotation
|
||||||
|
this.typeAnnotation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description
|
||||||
|
*/
|
||||||
|
|
||||||
|
getValueIfImmutable() {
|
||||||
|
// can't guarantee this value is the same
|
||||||
|
if (this.reassigned) return;
|
||||||
|
|
||||||
|
var node = this.path.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
|
||||||
|
*/
|
||||||
|
|
||||||
|
isCompatibleWithType(newType): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,7 +42,6 @@ traverse.node = function (node, opts, scope, state, parentPath) {
|
|||||||
function clearNode(node) {
|
function clearNode(node) {
|
||||||
node._declarations = null;
|
node._declarations = null;
|
||||||
node.extendedRange = null;
|
node.extendedRange = null;
|
||||||
node._scopeInfo = null;
|
|
||||||
node._paths = null;
|
node._paths = null;
|
||||||
node.tokens = null;
|
node.tokens = null;
|
||||||
node.range = null;
|
node.range = null;
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
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 traverse from "./index";
|
||||||
import includes from "lodash/collection/includes";
|
import includes from "lodash/collection/includes";
|
||||||
import Scope from "./scope";
|
import Scope from "./scope";
|
||||||
@ -200,7 +204,104 @@ export default class TraversalPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
return TraversalPath.get(this, this.context, this.node, this.node, 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) {
|
||||||
|
return !!this.node[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeAnnotation(): 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()) {
|
||||||
|
return this.get("init").resolve();
|
||||||
|
} else if (this.isIdentifier()) {
|
||||||
|
var binding = this.scope.getBinding(this.node.name);
|
||||||
|
if (!binding) return;
|
||||||
|
|
||||||
|
if (binding.path === this) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return binding.path.resolve();;
|
||||||
|
}
|
||||||
|
} else if (this.isMemberExpression()) {
|
||||||
|
var targetKey = t.toComputedKey(this.node);
|
||||||
|
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");
|
||||||
|
if (key.isIdentifier({ name: targetName }) || key.isLiteral({ value: targetName })) {
|
||||||
|
return prop.get("value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inferType(path: TraversalPath) {
|
||||||
|
path = path.resolve();
|
||||||
|
if (!path) return;
|
||||||
|
|
||||||
|
if (path.isRestElement() || path.isArrayExpression()) {
|
||||||
|
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.isObjectExpression()) {
|
||||||
|
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
isScope() {
|
||||||
@ -215,13 +316,40 @@ export default class TraversalPath {
|
|||||||
return t.isReferenced(this.node, this.parent);
|
return t.isReferenced(this.node, this.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isBlockScoped() {
|
||||||
|
return t.isBlockScoped(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
isVar() {
|
||||||
|
return t.isVar(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
isScope() {
|
isScope() {
|
||||||
return t.isScope(this.node, this.parent);
|
return t.isScope(this.node, this.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTypeGeneric(genericName: string, hasTypeParameters?): boolean {
|
||||||
|
var type = this.getTypeAnnotation().annotation;
|
||||||
|
if (!type) return false;
|
||||||
|
|
||||||
|
if (!t.isGenericTypeAnnotation(type) || !t.isIdentifier(type.id, { name: genericName })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTypeParameters && !type.typeParameters) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getBindingIdentifiers() {
|
getBindingIdentifiers() {
|
||||||
return t.getBindingIdentifiers(this.node);
|
return t.getBindingIdentifiers(this.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
traverse(opts, state) {
|
||||||
|
traverse(this.node, opts, this.scope, state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < t.TYPES.length; i++) {
|
for (var i = 0; i < t.TYPES.length; i++) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import includes from "lodash/collection/includes";
|
|||||||
import traverse from "./index";
|
import traverse from "./index";
|
||||||
import defaults from "lodash/object/defaults";
|
import defaults from "lodash/object/defaults";
|
||||||
import * as messages from "../messages";
|
import * as messages from "../messages";
|
||||||
|
import Binding from "./binding";
|
||||||
import globals from "globals";
|
import globals from "globals";
|
||||||
import flatten from "lodash/array/flatten";
|
import flatten from "lodash/array/flatten";
|
||||||
import extend from "lodash/object/extend";
|
import extend from "lodash/object/extend";
|
||||||
@ -12,27 +13,27 @@ import * as t from "../types";
|
|||||||
var functionVariableVisitor = {
|
var functionVariableVisitor = {
|
||||||
enter(node, parent, scope, state) {
|
enter(node, parent, scope, state) {
|
||||||
if (t.isFor(node)) {
|
if (t.isFor(node)) {
|
||||||
each(t.FOR_INIT_KEYS, function (key) {
|
each(t.FOR_INIT_KEYS, (key) => {
|
||||||
var declar = node[key];
|
var declar = this.get(key);
|
||||||
if (t.isVar(declar)) state.scope.registerBinding("var", declar);
|
if (declar.isVar()) state.scope.registerBinding("var", declar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// this block is a function so we'll stop since none of the variables
|
// this block is a function so we'll stop since none of the variables
|
||||||
// declared within are accessible
|
// 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
|
// function identifier doesn't belong to this scope
|
||||||
if (state.blockId && node === state.blockId) return;
|
if (state.blockId && node === state.blockId) return;
|
||||||
|
|
||||||
// delegate block scope handling to the `blockVariableVisitor`
|
// 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
|
// 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!
|
// 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);
|
state.addGlobal(node);
|
||||||
} else if (t.isLabeledStatement(node)) {
|
} else if (t.isLabeledStatement(node)) {
|
||||||
state.addGlobal(node);
|
state.addGlobal(node);
|
||||||
} else if (t.isAssignmentExpression(node) || t.isUpdateExpression(node) || (t.isUnaryExpression(node) && node.operator === "delete")) {
|
} else if (t.isAssignmentExpression(node)) {
|
||||||
scope.registerBindingReassignment(node);
|
scope.registerBindingReassignment(this.get("left"), this.get("right"));
|
||||||
|
} else if (t.isUpdateExpression(node)) {
|
||||||
|
// TODO
|
||||||
|
} else if (t.isUnaryExpression(node) && node.operator === "delete") {
|
||||||
|
scope.registerBindingReassignment(this.get("left"), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var blockVariableVisitor = {
|
var blockVariableVisitor = {
|
||||||
enter(node, parent, scope, state) {
|
enter(node, parent, scope, state) {
|
||||||
if (t.isFunctionDeclaration(node) || t.isBlockScoped(node)) {
|
if (this.isFunctionDeclaration() || this.isBlockScoped()) {
|
||||||
state.registerDeclaration(node);
|
state.registerDeclaration(this);
|
||||||
} else if (t.isScope(node, parent)) {
|
} else if (t.isScope(node, parent)) {
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
@ -112,9 +117,7 @@ export default class Scope {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
generateUidIdentifier(name: string) {
|
generateUidIdentifier(name: string) {
|
||||||
var id = t.identifier(this.generateUid(name));
|
return t.identifier(this.generateUid(name));
|
||||||
this.getFunctionParent().registerBinding("uid", id);
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,7 +132,8 @@ export default class Scope {
|
|||||||
do {
|
do {
|
||||||
uid = this._generateUid(name, i);
|
uid = this._generateUid(name, i);
|
||||||
i++;
|
i++;
|
||||||
} while (this.hasBinding(uid) || this.hasGlobal(uid));
|
} while (this.hasBinding(uid) || this.hasGlobal(uid) || this.hasUid(uid));
|
||||||
|
this.uids[uid] = true;
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +143,19 @@ export default class Scope {
|
|||||||
return `_${id}`;
|
return `_${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description
|
||||||
|
*/
|
||||||
|
|
||||||
|
hasUid(name): boolean {
|
||||||
|
var scope = this;
|
||||||
|
do {
|
||||||
|
if (scope.uids[name]) return true;
|
||||||
|
scope = scope.parent;
|
||||||
|
} while (scope);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
@ -217,7 +234,7 @@ export default class Scope {
|
|||||||
rename(oldName: string, newName: string) {
|
rename(oldName: string, newName: string) {
|
||||||
newName ||= this.generateUidIdentifier(oldName).name;
|
newName ||= this.generateUidIdentifier(oldName).name;
|
||||||
|
|
||||||
var info = this.getBindingInfo(oldName);
|
var info = this.getBinding(oldName);
|
||||||
if (!info) return;
|
if (!info) return;
|
||||||
|
|
||||||
var binding = info.identifier;
|
var binding = info.identifier;
|
||||||
@ -246,102 +263,6 @@ export default class Scope {
|
|||||||
binding.name = newName;
|
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
|
* Description
|
||||||
*/
|
*/
|
||||||
@ -349,8 +270,9 @@ export default class Scope {
|
|||||||
toArray(node: Object, i?: number) {
|
toArray(node: Object, i?: number) {
|
||||||
var file = this.file;
|
var file = this.file;
|
||||||
|
|
||||||
if (t.isIdentifier(node) && this.isTypeGeneric(node.name, "Array")) {
|
if (t.isIdentifier(node)) {
|
||||||
return node;
|
var binding = this.getBinding(node.name);
|
||||||
|
if (binding && binding.isTypeGeneric("Array")) return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.isArrayExpression(node)) {
|
if (t.isArrayExpression(node)) {
|
||||||
@ -376,33 +298,21 @@ export default class Scope {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
refreshDeclaration(node: Object) {
|
registerDeclaration(path: TraversalPath) {
|
||||||
if (t.isBlockScoped(node)) {
|
var node = path.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) {
|
|
||||||
if (t.isFunctionDeclaration(node)) {
|
if (t.isFunctionDeclaration(node)) {
|
||||||
this.registerBinding("hoisted", node);
|
this.registerBinding("hoisted", path);
|
||||||
} else if (t.isVariableDeclaration(node)) {
|
} else if (t.isVariableDeclaration(node)) {
|
||||||
for (var i = 0; i < node.declarations.length; i++) {
|
var declarations = path.get("declarations");
|
||||||
this.registerBinding(node.kind, node.declarations[i]);
|
for (var i = 0; i < declarations.length; i++) {
|
||||||
|
this.registerBinding(node.kind, declarations[i]);
|
||||||
}
|
}
|
||||||
} else if (t.isClassDeclaration(node)) {
|
} else if (t.isClassDeclaration(node)) {
|
||||||
this.registerBinding("let", node);
|
this.registerBinding("let", path);
|
||||||
} else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) {
|
} else if (t.isImportDeclaration(node) || t.isExportDeclaration(node)) {
|
||||||
this.registerBinding("module", node);
|
this.registerBinding("module", path);
|
||||||
} else {
|
} else {
|
||||||
this.registerBinding("unknown", node);
|
this.registerBinding("unknown", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,18 +320,16 @@ export default class Scope {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
registerBindingReassignment(node: Object) {
|
registerBindingReassignment(left: TraversalPath, right: TraversalPath) {
|
||||||
var ids = t.getBindingIdentifiers(node);
|
var ids = left.getBindingIdentifiers();
|
||||||
for (var name in ids) {
|
for (var name in ids) {
|
||||||
var info = this.getBindingInfo(name);
|
var binding = this.getBinding(name);
|
||||||
if (info) {
|
if (!binding) continue;
|
||||||
info.reassigned = true;
|
if (right) {
|
||||||
|
var rightType = right.typeAnnotation;
|
||||||
if (info.typeAnnotationInferred) {
|
if (rightType && binding.isCompatibleWithType(rightType)) continue;
|
||||||
// destroy the inferred typeAnnotation
|
|
||||||
info.typeAnnotation = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
binding.reassign();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,27 +337,22 @@ export default class Scope {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
registerBinding(kind: string, node: Object) {
|
registerBinding(kind: string, path: TraversalPath) {
|
||||||
if (!kind) throw new ReferenceError("no `kind`");
|
if (!kind) throw new ReferenceError("no `kind`");
|
||||||
|
|
||||||
var ids = t.getBindingIdentifiers(node);
|
var ids = path.getBindingIdentifiers();
|
||||||
|
|
||||||
for (var name in ids) {
|
for (var name in ids) {
|
||||||
var id = ids[name];
|
var id = ids[name];
|
||||||
|
|
||||||
this.checkBlockScopedCollisions(kind, name, id);
|
this.checkBlockScopedCollisions(kind, name, id);
|
||||||
|
|
||||||
var typeInfo = this.getTypeAnnotation(id, node);
|
this.bindings[name] = new Binding({
|
||||||
|
identifier: id,
|
||||||
this.bindings[name] = {
|
scope: this,
|
||||||
typeAnnotationInferred: typeInfo.inferred,
|
path: path,
|
||||||
typeAnnotation: typeInfo.annotation,
|
kind: kind
|
||||||
reassigned: false,
|
});
|
||||||
identifier: id,
|
|
||||||
scope: this,
|
|
||||||
node: node,
|
|
||||||
kind: kind
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,91 +392,92 @@ export default class Scope {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
crawl() {
|
crawl() {
|
||||||
var block = this.block;
|
var path = this.path;
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
var info = this.path.getData("scopeInfo");
|
var info = path.getData("scopeInfo");
|
||||||
if (info) {
|
if (info) {
|
||||||
extend(this, info);
|
extend(this, info);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
info = this.path.setData("scopeInfo", {
|
info = path.setData("scopeInfo", {
|
||||||
bindings: object(),
|
bindings: object(),
|
||||||
globals: object()
|
globals: object(),
|
||||||
|
uids: object()
|
||||||
});
|
});
|
||||||
|
|
||||||
extend(this, info);
|
extend(this, info);
|
||||||
|
|
||||||
// ForStatement - left, init
|
// ForStatement - left, init
|
||||||
|
|
||||||
if (t.isLoop(block)) {
|
if (path.isLoop()) {
|
||||||
for (i = 0; i < t.FOR_INIT_KEYS.length; i++) {
|
for (i = 0; i < t.FOR_INIT_KEYS.length; i++) {
|
||||||
var node = block[t.FOR_INIT_KEYS[i]];
|
var node = path.get(t.FOR_INIT_KEYS[i]);
|
||||||
if (t.isBlockScoped(node)) this.registerBinding("let", node);
|
if (node.isBlockScoped()) this.registerBinding("let", node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.isBlockStatement(block.body)) {
|
var body = path.get("body");
|
||||||
block = block.body;
|
if (body.isBlockStatement()) path = path.get("body");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionExpression - id
|
// FunctionExpression - id
|
||||||
|
|
||||||
if (t.isFunctionExpression(block) && block.id) {
|
if (path.isFunctionExpression() && path.has("id")) {
|
||||||
if (!t.isProperty(this.parentBlock, { method: true })) {
|
if (!t.isProperty(path.parent, { method: true })) {
|
||||||
this.registerBinding("var", block.id);
|
this.registerBinding("var", path.get("id"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class
|
// Class
|
||||||
|
|
||||||
if (t.isClass(block) && block.id) {
|
if (path.isClass() && path.has("id")) {
|
||||||
this.registerBinding("var", block.id);
|
this.registerBinding("var", path.get("id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function - params, rest
|
// Function - params, rest
|
||||||
|
|
||||||
if (t.isFunction(block)) {
|
if (path.isFunction()) {
|
||||||
for (i = 0; i < block.params.length; i++) {
|
var params = path.get("params");
|
||||||
this.registerBinding("param", block.params[i]);
|
for (i = 0; i < params.length; i++) {
|
||||||
|
this.registerBinding("param", params[i]);
|
||||||
}
|
}
|
||||||
this.traverse(block.body, blockVariableVisitor, this);
|
this.traverse(path.get("body"), blockVariableVisitor, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program, BlockStatement, Function - let variables
|
// Program, BlockStatement, Function - let variables
|
||||||
|
|
||||||
if (t.isBlockStatement(block) || t.isProgram(block)) {
|
if (path.isBlockStatement() || path.isProgram()) {
|
||||||
this.traverse(block, blockVariableVisitor, this);
|
this.traverse(path.node, blockVariableVisitor, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatchClause - param
|
// CatchClause - param
|
||||||
|
|
||||||
if (t.isCatchClause(block)) {
|
if (path.isCatchClause()) {
|
||||||
this.registerBinding("let", block.param);
|
this.registerBinding("let", path.get("param"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComprehensionExpression - blocks
|
// ComprehensionExpression - blocks
|
||||||
|
|
||||||
if (t.isComprehensionExpression(block)) {
|
if (path.isComprehensionExpression()) {
|
||||||
this.registerBinding("let", block);
|
this.registerBinding("let", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program, Function - var variables
|
// Program, Function - var variables
|
||||||
|
|
||||||
if (t.isProgram(block) || t.isFunction(block)) {
|
if (path.isProgram() || path.isFunction()) {
|
||||||
this.traverse(block, functionVariableVisitor, {
|
this.traverse(path.node, functionVariableVisitor, {
|
||||||
blockId: block.id,
|
blockId: path.get("id").node,
|
||||||
scope: this
|
scope: this
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program
|
// Program
|
||||||
|
|
||||||
if (t.isProgram(block)) {
|
if (path.isProgram()) {
|
||||||
this.traverse(block, programReferenceVisitor, this);
|
this.traverse(path.node, programReferenceVisitor, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,7 +578,7 @@ export default class Scope {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getBindingInfo(name: string) {
|
getBinding(name: string) {
|
||||||
var scope = this;
|
var scope = this;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -696,7 +600,7 @@ export default class Scope {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getBindingIdentifier(name: string) {
|
getBindingIdentifier(name: string) {
|
||||||
var info = this.getBindingInfo(name);
|
var info = this.getBinding(name);
|
||||||
return info && info.identifier;
|
return info && info.identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -722,29 +626,11 @@ export default class Scope {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getImmutableBindingValue(name: string) {
|
getImmutableBindingValue(name: string) {
|
||||||
return this._immutableBindingInfoToValue(this.getBindingInfo(name));
|
return this._immutableBindingInfoToValue(this.getBinding(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
_immutableBindingInfoToValue(info) {
|
_immutableBindingInfoToValue(binding) {
|
||||||
if (!info) return;
|
if (binding) return binding.getValueIfImmutable();
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -789,7 +675,7 @@ export default class Scope {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
removeBinding(name: string) {
|
removeBinding(name: string) {
|
||||||
var info = this.getBindingInfo(name);
|
var info = this.getBinding(name);
|
||||||
if (info) info.scope.removeOwnBinding(name);
|
if (info) info.scope.removeOwnBinding(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import * as t from "./index";
|
|||||||
* Description
|
* 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 (!node.computed) {
|
||||||
if (t.isIdentifier(key)) key = t.literal(key.name);
|
if (t.isIdentifier(key)) key = t.literal(key.name);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -281,7 +281,6 @@ export function inheritsComments(child: Object, parent: Object): Object {
|
|||||||
|
|
||||||
export function inherits(child: Object, parent: Object): Object {
|
export function inherits(child: Object, parent: Object): Object {
|
||||||
child._declarations = parent._declarations;
|
child._declarations = parent._declarations;
|
||||||
child._scopeInfo = parent._scopeInfo;
|
|
||||||
child.range = parent.range;
|
child.range = parent.range;
|
||||||
child.start = parent.start;
|
child.start = parent.start;
|
||||||
child.loc = parent.loc;
|
child.loc = parent.loc;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user