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 {
|
||||
arrayRef = this.scope.generateUidBasedOnNode(arrayRef);
|
||||
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 function ForOfStatement(node, parent, scope, file) {
|
||||
if (this.get("right").isTypeGeneric("Array")) {
|
||||
return array(node, scope, file);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var callback = spec;
|
||||
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 left = node.left;
|
||||
var declar, id;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -142,7 +142,7 @@ 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);
|
||||
var ownerIdInfo = this.scope.getBinding(this.ownerId.name);
|
||||
return ownerIdInfo && ownerIdInfo.reassigned;
|
||||
}
|
||||
|
||||
|
||||
@ -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"),
|
||||
@ -110,5 +109,6 @@ export default {
|
||||
"utility.inlineExpressions": require("./utility/inline-expressions"),
|
||||
"utility.deadCodeElimination": require("./utility/dead-code-elimination"),
|
||||
|
||||
flow: require("./other/flow"),
|
||||
_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) {
|
||||
node._declarations = null;
|
||||
node.extendedRange = null;
|
||||
node._scopeInfo = null;
|
||||
node._paths = null;
|
||||
node.tokens = 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 includes from "lodash/collection/includes";
|
||||
import Scope from "./scope";
|
||||
@ -200,7 +204,104 @@ export default class TraversalPath {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -215,13 +316,40 @@ export default class TraversalPath {
|
||||
return t.isReferenced(this.node, this.parent);
|
||||
}
|
||||
|
||||
isBlockScoped() {
|
||||
return t.isBlockScoped(this.node);
|
||||
}
|
||||
|
||||
isVar() {
|
||||
return t.isVar(this.node);
|
||||
}
|
||||
|
||||
isScope() {
|
||||
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() {
|
||||
return t.getBindingIdentifiers(this.node);
|
||||
}
|
||||
|
||||
traverse(opts, state) {
|
||||
traverse(this.node, opts, this.scope, state);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < t.TYPES.length; i++) {
|
||||
|
||||
@ -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.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 = {
|
||||
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();
|
||||
}
|
||||
@ -112,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,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.uids[uid] = true;
|
||||
return uid;
|
||||
}
|
||||
|
||||
@ -139,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
|
||||
*/
|
||||
@ -217,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;
|
||||
@ -246,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
|
||||
*/
|
||||
@ -349,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")) return node;
|
||||
}
|
||||
|
||||
if (t.isArrayExpression(node)) {
|
||||
@ -376,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,18 +320,16 @@ export default class Scope {
|
||||
* Description
|
||||
*/
|
||||
|
||||
registerBindingReassignment(node: Object) {
|
||||
var ids = t.getBindingIdentifiers(node);
|
||||
registerBindingReassignment(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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,91 +392,92 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
crawl() {
|
||||
var block = this.block;
|
||||
var path = this.path;
|
||||
var i;
|
||||
|
||||
//
|
||||
|
||||
var info = this.path.getData("scopeInfo");
|
||||
var info = path.getData("scopeInfo");
|
||||
if (info) {
|
||||
extend(this, info);
|
||||
return;
|
||||
}
|
||||
|
||||
info = this.path.setData("scopeInfo", {
|
||||
info = path.setData("scopeInfo", {
|
||||
bindings: object(),
|
||||
globals: object()
|
||||
globals: object(),
|
||||
uids: object()
|
||||
});
|
||||
|
||||
extend(this, info);
|
||||
|
||||
// ForStatement - left, init
|
||||
|
||||
if (t.isLoop(block)) {
|
||||
if (path.isLoop()) {
|
||||
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);
|
||||
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 (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
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -674,7 +578,7 @@ export default class Scope {
|
||||
* Description
|
||||
*/
|
||||
|
||||
getBindingInfo(name: string) {
|
||||
getBinding(name: string) {
|
||||
var scope = this;
|
||||
|
||||
do {
|
||||
@ -696,7 +600,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getBindingIdentifier(name: string) {
|
||||
var info = this.getBindingInfo(name);
|
||||
var info = this.getBinding(name);
|
||||
return info && info.identifier;
|
||||
}
|
||||
|
||||
@ -722,29 +626,11 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
getImmutableBindingValue(name: string) {
|
||||
return this._immutableBindingInfoToValue(this.getBindingInfo(name));
|
||||
return this._immutableBindingInfoToValue(this.getBinding(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;
|
||||
}
|
||||
_immutableBindingInfoToValue(binding) {
|
||||
if (binding) return binding.getValueIfImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -789,7 +675,7 @@ export default class Scope {
|
||||
*/
|
||||
|
||||
removeBinding(name: string) {
|
||||
var info = this.getBindingInfo(name);
|
||||
var info = this.getBinding(name);
|
||||
if (info) info.scope.removeOwnBinding(name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user