split inference logic into separate folder
This commit is contained in:
@@ -33,7 +33,7 @@ var memberExpressionOptimisationVisitor = {
|
||||
// if we know that this member expression is referencing a number then we can safely
|
||||
// optimise it
|
||||
var prop = this.parentPath.get("property");
|
||||
if (prop.isGenericType("Number")) {
|
||||
if (prop.isBaseType("number")) {
|
||||
state.candidates.push(this);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ function crawl(path) {
|
||||
if (path.is("_templateLiteralProduced")) {
|
||||
crawl(path.get("left"));
|
||||
crawl(path.get("right"));
|
||||
} else if (!path.isGenericType("String") && !path.isGenericType("Number")) {
|
||||
} else if (!path.isBaseType("string") && !path.isBaseType("number")) {
|
||||
path.replaceWith(t.callExpression(t.identifier("String"), [path.node]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
- `context` - Methods responsible for maintaing TraversalContexts.
|
||||
- `conversion` - Methods that convert the path node into another node or some other type of data.
|
||||
- `evaluation` - Methods responsible for statically evaluating code.
|
||||
- `inference` - Methods responsible for type inferrence etc.
|
||||
- `modification` - Methods that modify the path/node in some ways.
|
||||
- `resolution` - Methods responsible for type inferrence etc.
|
||||
- `replacement` - Methods responsible for replacing a node with another.
|
||||
- `removal` - Methods responsible for removing a node.
|
||||
- `family` - Methods responsible for dealing with/retrieving children or siblings.
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class NodePath {
|
||||
}
|
||||
|
||||
assign(NodePath.prototype, require("./ancestry"));
|
||||
assign(NodePath.prototype, require("./resolution"));
|
||||
assign(NodePath.prototype, require("./inference"));
|
||||
assign(NodePath.prototype, require("./replacement"));
|
||||
assign(NodePath.prototype, require("./evaluation"));
|
||||
assign(NodePath.prototype, require("./conversion"));
|
||||
|
||||
89
src/babel/traversal/path/inference/index.js
Normal file
89
src/babel/traversal/path/inference/index.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as inferers from "./inferers";
|
||||
import * as t from "../../../types";
|
||||
|
||||
/**
|
||||
* Infer the type of the current `NodePath`.
|
||||
*/
|
||||
|
||||
export function getTypeAnnotation() {
|
||||
if (this.typeAnnotation) return this.typeAnnotation;
|
||||
|
||||
var type = this._getTypeAnnotation() || t.anyTypeAnnotation();
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
return this.typeAnnotation = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: split up this method
|
||||
*/
|
||||
|
||||
export function _getTypeAnnotation(): ?Object {
|
||||
var node = this.node;
|
||||
|
||||
if (!node) {
|
||||
// handle initializerless variables, add in checks for loop initializers too
|
||||
if (this.key === "init" && this.parentPath.isVariableDeclarator()) {
|
||||
var declar = this.parentPath.parentPath;
|
||||
var declarParent = declar.parentPath;
|
||||
|
||||
// for (var NODE in bar) {}
|
||||
if (declar.key === "left" && declarParent.isForInStatement()) {
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
// for (var NODE of bar) {}
|
||||
if (declar.key === "left" && declarParent.isForOfStatement()) {
|
||||
return t.anyTypeAnnotation();
|
||||
}
|
||||
|
||||
return t.voidTypeAnnotation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.typeAnnotation) {
|
||||
return node.typeAnnotation;
|
||||
}
|
||||
|
||||
var inferer = inferers[node.type];
|
||||
if (inferer) {
|
||||
return inferer.call(this, node);
|
||||
}
|
||||
|
||||
inferer = inferer[this.parentPath.type];
|
||||
if (inferer && inferer.validParent) {
|
||||
return this.parentPath.getTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function isBaseType(baseName: string): boolean {
|
||||
var type = this.getTypeAnnotation();
|
||||
|
||||
if (baseName === "string") {
|
||||
return t.isStringTypeAnnotation(type);
|
||||
} else if (baseName === "number") {
|
||||
return t.isNumberTypeAnnotation(type);
|
||||
} else if (baseName === "boolean") {
|
||||
return t.isBooleanTypeAnnotation(type);
|
||||
} else if (baseName === "any") {
|
||||
return t.isAnyTypeAnnotation(type);
|
||||
} else if (baseName === "mixed") {
|
||||
return t.isMixedTypeAnnotation(type);
|
||||
} else {
|
||||
throw new Error(`Unknown base type ${baseName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function isGenericType(genericName: string): boolean {
|
||||
var type = this.getTypeAnnotation();
|
||||
return t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName });
|
||||
}
|
||||
180
src/babel/traversal/path/inference/inferer-reference.js
Normal file
180
src/babel/traversal/path/inference/inferer-reference.js
Normal file
@@ -0,0 +1,180 @@
|
||||
import * as t from "../../../types";
|
||||
|
||||
export default function (node) {
|
||||
if (!this.isReferenced()) return;
|
||||
|
||||
// check if a binding exists of this value and if so then return a union type of all
|
||||
// possible types that the binding could be
|
||||
var binding = this.scope.getBinding(node.name);
|
||||
if (binding) {
|
||||
if (binding.identifier.typeAnnotation) {
|
||||
return binding.identifier.typeAnnotation;
|
||||
} else {
|
||||
return getTypeAnnotationBindingConstantViolations(this, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
// built-in values
|
||||
if (node.name === "undefined") {
|
||||
return t.voidTypeAnnotation();
|
||||
} else if (node.name === "NaN" || node.name === "Infinity") {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (node.name === "arguments") {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotationBindingConstantViolations(path, name) {
|
||||
var binding = path.scope.getBinding(name);
|
||||
|
||||
var types = [];
|
||||
path.typeAnnotation = t.unionTypeAnnotation(types);
|
||||
|
||||
var functionConstantViolations = [];
|
||||
var constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
|
||||
|
||||
var testType = getConditionalAnnotation(path, name);
|
||||
if (testType) {
|
||||
var testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
|
||||
|
||||
// remove constant violations observed before the IfStatement
|
||||
constantViolations = constantViolations.filter((path) => testConstantViolations.indexOf(path) < 0);
|
||||
|
||||
// clear current types and add in observed test type
|
||||
types.push(testType.typeAnnotation);
|
||||
}
|
||||
|
||||
if (constantViolations.length) {
|
||||
// pick one constant from each scope which will represent the last possible
|
||||
// control flow path that it could've taken/been
|
||||
var rawConstantViolations = constantViolations.reverse();
|
||||
var visitedScopes = [];
|
||||
constantViolations = [];
|
||||
for (let violation of (rawConstantViolations: Array)) {
|
||||
if (visitedScopes.indexOf(violation.scope) >= 0) continue;
|
||||
visitedScopes.push(violation.scope);
|
||||
constantViolations.push(violation);
|
||||
}
|
||||
|
||||
// add back on function constant violations since we can't track calls
|
||||
constantViolations = constantViolations.concat(functionConstantViolations);
|
||||
|
||||
// push on inferred types of violated paths
|
||||
for (let violation of (constantViolations: Array)) {
|
||||
types.push(violation.getTypeAnnotation());
|
||||
}
|
||||
}
|
||||
|
||||
if (types.length) {
|
||||
return t.createUnionTypeAnnotation(types);
|
||||
}
|
||||
}
|
||||
|
||||
function getConstantViolationsBefore(binding, path, functions) {
|
||||
var violations = binding.constantViolations.slice();
|
||||
violations.unshift(binding.path);
|
||||
return violations.filter((violation) => {
|
||||
violation = violation.resolve();
|
||||
var status = violation._guessExecutionStatusRelativeTo(path);
|
||||
if (functions && status === "function") functions.push(violation);
|
||||
return status === "before";
|
||||
});
|
||||
}
|
||||
|
||||
function inferAnnotationFromBinaryExpression(name, path) {
|
||||
var operator = path.node.operator;
|
||||
|
||||
var right = path.get("right").resolve();
|
||||
var left = path.get("left").resolve();
|
||||
|
||||
var target;
|
||||
if (left.isIdentifier({ name })) {
|
||||
target = right;
|
||||
} else if (right.isIdentifier({ name })) {
|
||||
target = left;
|
||||
}
|
||||
if (target) {
|
||||
if (operator === "===") {
|
||||
return target.getTypeAnnotation();
|
||||
} else if (t.BOOLEAN_NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.numberTypeAnnotation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (operator !== "===") return;
|
||||
}
|
||||
|
||||
//
|
||||
var typeofPath;
|
||||
var typePath;
|
||||
if (left.isUnaryExpression({ operator: "typeof" })) {
|
||||
typeofPath = left;
|
||||
typePath = right;
|
||||
} else if (right.isUnaryExpression({ operator: "typeof" })) {
|
||||
typeofPath = right;
|
||||
typePath = left;
|
||||
}
|
||||
if (!typePath && !typeofPath) return;
|
||||
|
||||
// ensure that the type path is a Literal
|
||||
typePath = typePath.resolve();
|
||||
if (!typePath.isLiteral()) return;
|
||||
|
||||
// and that it's a string so we can infer it
|
||||
var typeValue = typePath.node.value;
|
||||
if (typeof typeValue !== "string") return;
|
||||
|
||||
// and that the argument of the typeof path references us!
|
||||
if (!typeofPath.get("argument").isIdentifier({ name })) return;
|
||||
|
||||
// turn type value into a type annotation
|
||||
return t.createTypeAnnotationBasedOnTypeof(typePath.node.value);
|
||||
}
|
||||
|
||||
function getParentConditionalPath(path) {
|
||||
var parentPath;
|
||||
while (parentPath = path.parentPath) {
|
||||
if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) {
|
||||
if (path.key === "test") {
|
||||
return;
|
||||
} else {
|
||||
return parentPath;
|
||||
}
|
||||
} else {
|
||||
path = parentPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConditionalAnnotation(path, name) {
|
||||
var ifStatement = getParentConditionalPath(path);
|
||||
if (!ifStatement) return;
|
||||
|
||||
var test = ifStatement.get("test");
|
||||
var paths = [test];
|
||||
var types = [];
|
||||
|
||||
do {
|
||||
let path = paths.shift().resolve();
|
||||
|
||||
if (path.isLogicalExpression()) {
|
||||
paths.push(path.get("left"));
|
||||
paths.push(path.get("right"));
|
||||
}
|
||||
|
||||
if (path.isBinaryExpression()) {
|
||||
var type = inferAnnotationFromBinaryExpression(name, path);
|
||||
if (type) types.push(type);
|
||||
}
|
||||
} while(paths.length);
|
||||
|
||||
if (types.length) {
|
||||
return {
|
||||
typeAnnotation: t.createUnionTypeAnnotation(types),
|
||||
ifStatement
|
||||
};
|
||||
} else {
|
||||
return getConditionalAnnotation(ifStatement, name);
|
||||
}
|
||||
}
|
||||
157
src/babel/traversal/path/inference/inferers.js
Normal file
157
src/babel/traversal/path/inference/inferers.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import * as t from "../../../types";
|
||||
|
||||
export { default as Identifier } from "./inferer-reference";
|
||||
|
||||
export function VariableDeclarator() {
|
||||
var id = this.get("id");
|
||||
|
||||
if (id.isIdentifier()) {
|
||||
return this.get("init").getTypeAnnotation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function TypeCastExpression(node) {
|
||||
return node.typeAnnotation;
|
||||
}
|
||||
|
||||
TypeCastExpression.validParent = true;
|
||||
|
||||
export function NewExpression(node) {
|
||||
if (this.get("callee").isIdentifier()) {
|
||||
// only resolve identifier callee
|
||||
return t.genericTypeAnnotation(node.callee);
|
||||
}
|
||||
}
|
||||
|
||||
export function TemplateLiteral() {
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
export function UnaryExpression(node) {
|
||||
let operator = node.operator;
|
||||
|
||||
if (operator === "void") {
|
||||
return t.voidTypeAnnotation();
|
||||
} else if (t.NUMBER_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (t.STRING_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.stringTypeAnnotation();
|
||||
} else if (t.BOOLEAN_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.booleanTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
export function BinaryExpression(node) {
|
||||
let operator = node.operator;
|
||||
|
||||
if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.booleanTypeAnnotation();
|
||||
} else if (operator === "+") {
|
||||
var right = this.get("right");
|
||||
var left = this.get("left");
|
||||
|
||||
if (left.isBaseType("number") && right.isBaseType("number")) {
|
||||
// both numbers so this will be a number
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (left.isBaseType("string") || right.isBaseType("string")) {
|
||||
// one is a string so the result will be a string
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
// unsure if left and right are strings or numbers so stay on the safe side
|
||||
return t.unionTypeAnnotation([
|
||||
t.stringTypeAnnotation(),
|
||||
t.numberTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export function LogicalExpression() {
|
||||
return t.createUnionTypeAnnotation([
|
||||
this.get("left").getTypeAnnotation(),
|
||||
this.get("right").getTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
|
||||
export function ConditionalExpression() {
|
||||
return t.createUnionTypeAnnotation([
|
||||
this.get("consequent").getTypeAnnotation(),
|
||||
this.get("alternate").getTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
|
||||
export function SequenceExpression(node) {
|
||||
return this.get("expressions").pop().getTypeAnnotation();
|
||||
}
|
||||
|
||||
export function AssignmentExpression(node) {
|
||||
return this.get("right").getTypeAnnotation();
|
||||
}
|
||||
|
||||
export function UpdateExpression(node) {
|
||||
let operator = node.operator;
|
||||
if (operator === "++" || operator === "--") {
|
||||
return t.numberTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
export function Literal(node) {
|
||||
var value = node.value;
|
||||
if (typeof value === "string") return t.stringTypeAnnotation();
|
||||
if (typeof value === "number") return t.numberTypeAnnotation();
|
||||
if (typeof value === "boolean") return t.booleanTypeAnnotation();
|
||||
if (value === null) return t.voidTypeAnnotation();
|
||||
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
|
||||
}
|
||||
|
||||
export function ObjectExpression() {
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
|
||||
export function ArrayExpression () {
|
||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||
}
|
||||
|
||||
export function RestElement() {
|
||||
return ArrayExpression();
|
||||
}
|
||||
|
||||
RestElement.validParent = true;
|
||||
|
||||
export function Func() {
|
||||
return t.genericTypeAnnotation(t.identifier("Function"));
|
||||
}
|
||||
|
||||
export { Func as Function, Func as Class };
|
||||
|
||||
export function CallExpression() {
|
||||
return resolveCall(this.get("callee"));
|
||||
}
|
||||
|
||||
export function TaggedTemplateExpression() {
|
||||
return resolveCall(this.get("tag"));
|
||||
}
|
||||
|
||||
function resolveCall(callee) {
|
||||
callee = callee.resolve();
|
||||
|
||||
if (callee.isFunction()) {
|
||||
if (callee.is("async")) {
|
||||
if (callee.is("generator")) {
|
||||
return t.genericTypeAnnotation(t.identifier("AsyncIterator"));
|
||||
} else {
|
||||
return t.genericTypeAnnotation(t.identifier("Promise"));
|
||||
}
|
||||
} else {
|
||||
if (callee.node.returnType) {
|
||||
return callee.node.returnType;
|
||||
} else {
|
||||
// todo: get union type of all return arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type NodePath from "./index";
|
||||
import includes from "lodash/collection/includes";
|
||||
import * as t from "../../types";
|
||||
|
||||
@@ -232,21 +233,105 @@ export function willIMaybeExecutesBefore(target) {
|
||||
return this._guessExecutionStatusRelativeTo(target) !== "after";
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `target` check the execution status of it relative to the current path.
|
||||
*
|
||||
* "Execution status" simply refers to where or not we **think** this will execuete
|
||||
* before or after the input `target` element.
|
||||
*/
|
||||
|
||||
export function _guessExecutionStatusRelativeTo(target) {
|
||||
var self = this.getStatementParent();
|
||||
target = target.getStatementParent();
|
||||
if (target === self) return "before";
|
||||
|
||||
// check if the two paths are in different functions, we can't track execution of these
|
||||
var targetFuncParent = target.scope.getFunctionParent();
|
||||
var selfFuncParent = self.scope.getFunctionParent();
|
||||
if (targetFuncParent !== selfFuncParent) {
|
||||
return "function";
|
||||
}
|
||||
|
||||
// crawl up the targets parents until we hit the container we're in and check keys
|
||||
do {
|
||||
// todo: if (target === self) return "after";
|
||||
|
||||
if (target.container === self.container) {
|
||||
return target.key > self.key ? "before" : "after";
|
||||
return target.key >= self.key ? "before" : "after";
|
||||
}
|
||||
} while(self = self.parentPath);
|
||||
|
||||
return "before";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a "pointer" `NodePath` to it's absolute path.
|
||||
*/
|
||||
|
||||
export function resolve(dangerous, resolved) {
|
||||
return this._resolve(dangerous, resolved) || this;
|
||||
}
|
||||
|
||||
export function _resolve(dangerous?, resolved?): ?NodePath {
|
||||
// detect infinite recursion
|
||||
// todo: possibly have a max length on this just to be safe
|
||||
if (resolved && resolved.indexOf(this) >= 0) return;
|
||||
|
||||
// we store all the paths we've "resolved" in this array to prevent infinite recursion
|
||||
resolved = resolved || [];
|
||||
resolved.push(this);
|
||||
|
||||
if (this.isVariableDeclarator()) {
|
||||
if (this.get("id").isIdentifier()) {
|
||||
return this.get("init").resolve(dangerous, resolved);
|
||||
} else {
|
||||
// otherwise it's a request for a pattern and that's a bit more tricky
|
||||
}
|
||||
} else if (this.isReferencedIdentifier()) {
|
||||
var binding = this.scope.getBinding(this.node.name);
|
||||
if (!binding) return;
|
||||
|
||||
// reassigned so we can't really resolve it
|
||||
if (!binding.constant) return;
|
||||
|
||||
// todo - lookup module in dependency graph
|
||||
if (binding.kind === "module") return;
|
||||
|
||||
if (binding.path !== this) {
|
||||
return binding.path.resolve(dangerous, resolved);
|
||||
}
|
||||
} else if (this.isTypeCastExpression()) {
|
||||
return this.get("expression").resolve(dangerous, resolved);
|
||||
} else if (dangerous && 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(dangerous, resolved);
|
||||
|
||||
if (target.isObjectExpression()) {
|
||||
var props = target.get("properties");
|
||||
for (var prop of (props: Array)) {
|
||||
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 = match || key.isLiteral({ value: targetName });
|
||||
|
||||
if (match) return prop.get("value").resolve(dangerous, resolved);
|
||||
}
|
||||
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
|
||||
var elems = target.get("elements");
|
||||
var elem = elems[targetName];
|
||||
if (elem) return elem.resolve(dangerous, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
import type NodePath from "./index";
|
||||
import * as t from "../../types";
|
||||
|
||||
/**
|
||||
* Resolve a "pointer" `NodePath` to it's absolute path.
|
||||
*/
|
||||
|
||||
export function resolve(dangerous, resolved) {
|
||||
return this._resolve(dangerous, resolved) || this;
|
||||
}
|
||||
|
||||
export function _resolve(dangerous?, resolved?): ?NodePath {
|
||||
// detect infinite recursion
|
||||
// todo: possibly have a max length on this just to be safe
|
||||
if (resolved && resolved.indexOf(this) >= 0) return;
|
||||
|
||||
// we store all the paths we've "resolved" in this array to prevent infinite recursion
|
||||
resolved = resolved || [];
|
||||
resolved.push(this);
|
||||
|
||||
if (this.isVariableDeclarator()) {
|
||||
if (this.get("id").isIdentifier()) {
|
||||
return this.get("init").resolve(dangerous, resolved);
|
||||
} else {
|
||||
// otherwise it's a request for a pattern and that's a bit more tricky
|
||||
}
|
||||
} else if (this.isReferencedIdentifier()) {
|
||||
var binding = this.scope.getBinding(this.node.name);
|
||||
if (!binding) return;
|
||||
|
||||
// reassigned so we can't really resolve it
|
||||
if (!binding.constant) return;
|
||||
|
||||
// todo - lookup module in dependency graph
|
||||
if (binding.kind === "module") return;
|
||||
|
||||
if (binding.path !== this) {
|
||||
return binding.path.resolve(dangerous, resolved);
|
||||
}
|
||||
} else if (this.isTypeCastExpression()) {
|
||||
return this.get("expression").resolve(dangerous, resolved);
|
||||
} else if (dangerous && 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(dangerous, resolved);
|
||||
|
||||
if (target.isObjectExpression()) {
|
||||
var props = target.get("properties");
|
||||
for (var prop of (props: Array)) {
|
||||
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 = match || key.isLiteral({ value: targetName });
|
||||
|
||||
if (match) return prop.get("value").resolve(dangerous, resolved);
|
||||
}
|
||||
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
|
||||
var elems = target.get("elements");
|
||||
var elem = elems[targetName];
|
||||
if (elem) return elem.resolve(dangerous, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Infer the type of the current `NodePath`.
|
||||
*/
|
||||
|
||||
export function getTypeAnnotation(force) {
|
||||
if (this.typeAnnotation) return this.typeAnnotation;
|
||||
|
||||
var type = this._getTypeAnnotation(force) || t.anyTypeAnnotation();
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
return this.typeAnnotation = type;
|
||||
}
|
||||
|
||||
export function _getTypeAnnotationBindingConstantViolations(path, name) {
|
||||
var binding = this.scope.getBinding(name);
|
||||
|
||||
var types = [];
|
||||
this.typeAnnotation = t.unionTypeAnnotation(types);
|
||||
|
||||
var functionConstantViolations = [];
|
||||
var constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations);
|
||||
|
||||
var testType = getTypeAnnotationBasedOnConditional(path, name);
|
||||
if (testType) {
|
||||
var testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement);
|
||||
|
||||
// remove constant violations observed before the IfStatement
|
||||
constantViolations = constantViolations.filter((path) => testConstantViolations.indexOf(path) < 0);
|
||||
|
||||
// clear current types and add in observed test type
|
||||
types.push(testType.typeAnnotation);
|
||||
}
|
||||
|
||||
if (constantViolations.length) {
|
||||
// pick one constant from each scope which will represent the last possible
|
||||
// control flow path that it could've taken/been
|
||||
var rawConstantViolations = constantViolations.reverse();
|
||||
var visitedScopes = [];
|
||||
constantViolations = [];
|
||||
for (let violation of (rawConstantViolations: Array)) {
|
||||
if (visitedScopes.indexOf(violation.scope) >= 0) continue;
|
||||
visitedScopes.push(violation.scope);
|
||||
constantViolations.push(violation);
|
||||
}
|
||||
|
||||
// add back on function constant violations since we can't track calls
|
||||
constantViolations = constantViolations.concat(functionConstantViolations);
|
||||
|
||||
// push on inferred types of violated paths
|
||||
for (let violation of (constantViolations: Array)) {
|
||||
types.push(violation.getTypeAnnotation());
|
||||
}
|
||||
}
|
||||
|
||||
if (types.length) {
|
||||
return t.createUnionTypeAnnotation(types);
|
||||
}
|
||||
}
|
||||
|
||||
function getConstantViolationsBefore(binding, path, functions) {
|
||||
var violations = binding.constantViolations.slice();
|
||||
violations.unshift(binding.path);
|
||||
return violations.filter((violation) => {
|
||||
violation = violation.resolve();
|
||||
var status = violation._guessExecutionStatusRelativeTo(path);
|
||||
if (functions && status === "function") functions.push(violation);
|
||||
return status === "before";
|
||||
});
|
||||
}
|
||||
|
||||
function checkBinary(name, path) {
|
||||
var right = path.get("right").resolve();
|
||||
var left = path.get("left").resolve();
|
||||
|
||||
if (left.isIdentifier({ name })) {
|
||||
return right.getTypeAnnotation();
|
||||
} else if (right.isIdentifier({ name })) {
|
||||
return left.getTypeAnnotation();
|
||||
}
|
||||
|
||||
//
|
||||
var typeofPath;
|
||||
var typePath;
|
||||
if (left.isUnaryExpression({ operator: "typeof" })) {
|
||||
typeofPath = left;
|
||||
typePath = right;
|
||||
} else if (right.isUnaryExpression({ operator: "typeof" })) {
|
||||
typeofPath = right;
|
||||
typePath = left;
|
||||
}
|
||||
if (!typePath && !typeofPath) return;
|
||||
|
||||
// ensure that the type path is a Literal
|
||||
typePath = typePath.resolve();
|
||||
if (!typePath.isLiteral()) return;
|
||||
|
||||
// and that it's a string so we can infer it
|
||||
var typeValue = typePath.node.value;
|
||||
if (typeof typeValue !== "string") return;
|
||||
|
||||
// and that the argument of the typeof path references us!
|
||||
if (!typeofPath.get("argument").isIdentifier({ name })) return;
|
||||
|
||||
// turn type value into a type annotation
|
||||
return t.createTypeAnnotationBasedOnTypeof(typePath.node.value);
|
||||
}
|
||||
|
||||
function getParentConditional(path) {
|
||||
var parentPath;
|
||||
while (parentPath = path.parentPath) {
|
||||
if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) {
|
||||
if (path.key === "test") {
|
||||
return;
|
||||
} else {
|
||||
return parentPath;
|
||||
}
|
||||
} else {
|
||||
path = parentPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotationBasedOnConditional(path, name) {
|
||||
var ifStatement = getParentConditional(path);
|
||||
if (!ifStatement) return;
|
||||
|
||||
var test = ifStatement.get("test");
|
||||
var paths = [test];
|
||||
var types = [];
|
||||
|
||||
do {
|
||||
let path = paths.shift().resolve();
|
||||
|
||||
if (path.isLogicalExpression()) {
|
||||
paths.push(path.get("left"));
|
||||
paths.push(path.get("right"));
|
||||
}
|
||||
|
||||
if (path.isBinaryExpression({ operator: "===" })) {
|
||||
// todo: add in cases where operators imply a number
|
||||
var type = checkBinary(name, path);
|
||||
if (type) types.push(type);
|
||||
}
|
||||
} while(paths.length);
|
||||
|
||||
if (types.length) {
|
||||
return {
|
||||
typeAnnotation: t.createUnionTypeAnnotation(types),
|
||||
ifStatement
|
||||
};
|
||||
} else {
|
||||
return getTypeAnnotationBasedOnConditional(ifStatement, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: split up this method
|
||||
*/
|
||||
|
||||
export function _getTypeAnnotation(force?: boolean): ?Object {
|
||||
var node = this.node;
|
||||
|
||||
if (!node) {
|
||||
// handle initializerless variables, add in checks for loop initializers too
|
||||
if (this.key === "init" && this.parentPath.isVariableDeclarator()) {
|
||||
var declar = this.parentPath.parentPath;
|
||||
var declarParent = declar.parentPath;
|
||||
|
||||
// for (var NODE in bar) {}
|
||||
if (declar.key === "left" && declarParent.isForInStatement()) {
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
// for (var NODE of bar) {}
|
||||
if (declar.key === "left" && declarParent.isForOfStatement()) {
|
||||
return t.anyTypeAnnotation();
|
||||
}
|
||||
|
||||
return t.voidTypeAnnotation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.typeAnnotation) {
|
||||
return node.typeAnnotation;
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isVariableDeclarator()) {
|
||||
var id = this.get("id");
|
||||
|
||||
if (id.isIdentifier()) {
|
||||
return this.get("init").getTypeAnnotation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.parentPath.isTypeCastExpression()) {
|
||||
return this.parentPath.getTypeAnnotation();
|
||||
}
|
||||
|
||||
if (this.isTypeCastExpression()) {
|
||||
return node.typeAnnotation;
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isRestElement() || this.parentPath.isRestElement() || this.isArrayExpression()) {
|
||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||
}
|
||||
|
||||
//
|
||||
if (!force && this.parentPath.isReturnStatement()) {
|
||||
return this.parentPath.getTypeAnnotation();
|
||||
}
|
||||
|
||||
if (this.isReturnStatement()) {
|
||||
var funcPath = this.findParent((path) => path.isFunction());
|
||||
if (!funcPath) return;
|
||||
|
||||
var returnType = funcPath.node.returnType;
|
||||
if (returnType) {
|
||||
return returnType;
|
||||
} else {
|
||||
return this.get("argument").getTypeAnnotation(true);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isNewExpression() && this.get("callee").isIdentifier()) {
|
||||
// only resolve identifier callee
|
||||
return t.genericTypeAnnotation(node.callee);
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isReferencedIdentifier()) {
|
||||
// check if a binding exists of this value and if so then return a union type of all
|
||||
// possible types that the binding could be
|
||||
var binding = this.scope.getBinding(node.name);
|
||||
if (binding) {
|
||||
if (binding.identifier.typeAnnotation) {
|
||||
return binding.identifier.typeAnnotation;
|
||||
} else {
|
||||
return this._getTypeAnnotationBindingConstantViolations(this, node.name);
|
||||
}
|
||||
}
|
||||
|
||||
// built-in values
|
||||
if (node.name === "undefined") {
|
||||
return t.voidTypeAnnotation();
|
||||
} else if (node.name === "NaN" || node.name === "Infinity") {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (node.name === "arguments") {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isObjectExpression()) {
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isFunction() || this.isClass()) {
|
||||
return t.genericTypeAnnotation(t.identifier("Function"));
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isTemplateLiteral()) {
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isUnaryExpression()) {
|
||||
let operator = node.operator;
|
||||
|
||||
if (operator === "void") {
|
||||
return t.voidTypeAnnotation();
|
||||
} else if (t.NUMBER_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (t.STRING_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.stringTypeAnnotation();
|
||||
} else if (t.BOOLEAN_UNARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.booleanTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isBinaryExpression()) {
|
||||
let operator = node.operator;
|
||||
|
||||
if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.booleanTypeAnnotation();
|
||||
} else if (operator === "+") {
|
||||
var right = this.get("right");
|
||||
var left = this.get("left");
|
||||
|
||||
if (left.isGenericType("Number") && right.isGenericType("Number")) {
|
||||
// both numbers so this will be a number
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (left.isGenericType("String") || right.isGenericType("String")) {
|
||||
// one is a string so the result will be a string
|
||||
return t.stringTypeAnnotation();
|
||||
}
|
||||
|
||||
// unsure if left and right are strings or numbers so stay on the safe side
|
||||
return t.unionTypeAnnotation([
|
||||
t.stringTypeAnnotation(),
|
||||
t.numberTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isLogicalExpression()) {
|
||||
return t.createUnionTypeAnnotation([
|
||||
this.get("left").getTypeAnnotation(),
|
||||
this.get("right").getTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isConditionalExpression()) {
|
||||
return t.createUnionTypeAnnotation([
|
||||
this.get("consequent").getTypeAnnotation(),
|
||||
this.get("alternate").getTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isSequenceExpression()) {
|
||||
return this.get("expressions").pop().getTypeAnnotation(force);
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isAssignmentExpression()) {
|
||||
return this.get("right").getTypeAnnotation(force);
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isUpdateExpression()) {
|
||||
let operator = node.operator;
|
||||
if (operator === "++" || operator === "--") {
|
||||
return t.numberTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (this.isLiteral()) {
|
||||
var value = node.value;
|
||||
if (typeof value === "string") return t.stringTypeAnnotation();
|
||||
if (typeof value === "number") return t.numberTypeAnnotation();
|
||||
if (typeof value === "boolean") return t.booleanTypeAnnotation();
|
||||
if (value === null) return t.voidTypeAnnotation();
|
||||
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
|
||||
}
|
||||
|
||||
//
|
||||
var callPath;
|
||||
if (this.isCallExpression()) callPath = this.get("callee");
|
||||
if (this.isTaggedTemplateExpression()) callPath = this.get("tag");
|
||||
if (callPath) {
|
||||
var callee = callPath.resolve();
|
||||
// todo: read typescript/flow interfaces
|
||||
|
||||
if (callee.isFunction()) {
|
||||
if (callee.is("async")) {
|
||||
if (callee.is("generator")) {
|
||||
return t.genericTypeAnnotation(t.identifier("AsyncIterator"));
|
||||
} else {
|
||||
return t.genericTypeAnnotation(t.identifier("Promise"));
|
||||
}
|
||||
} else {
|
||||
if (callee.node.returnType) {
|
||||
return callee.node.returnType;
|
||||
} else {
|
||||
// todo: get union type of all return arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function isGenericType(genericName: string): boolean {
|
||||
var type = this.getTypeAnnotation();
|
||||
|
||||
if (t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName })) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericName === "String") {
|
||||
return t.isStringTypeAnnotation(type);
|
||||
} else if (genericName === "Number") {
|
||||
return t.isNumberTypeAnnotation(type);
|
||||
} else if (genericName === "Boolean") {
|
||||
return t.isBooleanTypeAnnotation(type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -109,8 +109,8 @@ export function createTypeAnnotationBasedOnTypeof(type) {
|
||||
} else if (type === "boolean") {
|
||||
return t.booleanTypeAnnotation();
|
||||
} else if (type === "function") {
|
||||
// todo
|
||||
return t.genericTypeAnnotation(t.identifier("Function"));
|
||||
} else if (type === "object") {
|
||||
// todo
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,9 @@ export const FLATTENABLE_KEYS = ["body", "expressions"];
|
||||
export const FOR_INIT_KEYS = ["left", "init"];
|
||||
export const COMMENT_KEYS = ["leadingComments", "trailingComments"];
|
||||
|
||||
export const BOOLEAN_BINARY_OPERATORS = ["==", "===", "!=", "!==", ">", "<", ">=", "<=", "in", "instanceof"];
|
||||
export const NUMBER_BINARY_OPERATORS = ["-", "/", "*", "**", "&", "|", ">>", ">>>", "<<", "^"];
|
||||
export const BOOLEAN_NUMBER_BINARY_OPERATORS = [">", "<", ">=", "<="];
|
||||
export const BOOLEAN_BINARY_OPERATORS = ["==", "===", "!=", "!==", "in", "instanceof"].concat(BOOLEAN_NUMBER_BINARY_OPERATORS);
|
||||
export const NUMBER_BINARY_OPERATORS = ["-", "/", "*", "**", "&", "|", ">>", ">>>", "<<", "^"];
|
||||
|
||||
export const BOOLEAN_UNARY_OPERATORS = ["delete", "!"];
|
||||
export const NUMBER_UNARY_OPERATORS = ["+", "-", "++", "--", "~"];
|
||||
|
||||
Reference in New Issue
Block a user