flesh out type inferrence some more, rename some of the NodePath methods to be less ambiguous, remove dead Binding type methods
This commit is contained in:
parent
de652dc747
commit
7d2c6525d8
@ -5,7 +5,7 @@ export var metadata = {
|
||||
};
|
||||
|
||||
export function ForOfStatement(node, parent, scope, file) {
|
||||
if (this.get("right").isTypeGeneric("Array")) {
|
||||
if (this.get("right").isTypeAnnotationGeneric("Array")) {
|
||||
return _ForOfStatementArray.call(this, node, scope, file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +11,10 @@ export function insertBefore(nodes) {
|
||||
|
||||
if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) {
|
||||
return this.parentPath.insertBefore(nodes);
|
||||
} else if (this.isType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
|
||||
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
|
||||
if (this.node) nodes.push(this.node);
|
||||
this.replaceExpressionWithStatements(nodes);
|
||||
} else if (this.isType("Statement") || !this.type) {
|
||||
} else if (this.isNodeType("Statement") || !this.type) {
|
||||
this._maybePopFromStatements(nodes);
|
||||
if (Array.isArray(this.container)) {
|
||||
return this._containerInsertBefore(nodes);
|
||||
@ -82,14 +82,14 @@ export function insertAfter(nodes) {
|
||||
|
||||
if (this.parentPath.isExpressionStatement() || this.parentPath.isLabeledStatement()) {
|
||||
return this.parentPath.insertAfter(nodes);
|
||||
} else if (this.isType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
|
||||
} else if (this.isNodeType("Expression") || (this.parentPath.isForStatement() && this.key === "init")) {
|
||||
if (this.node) {
|
||||
var temp = this.scope.generateDeclaredUidIdentifier();
|
||||
nodes.unshift(t.expressionStatement(t.assignmentExpression("=", temp, this.node)));
|
||||
nodes.push(t.expressionStatement(temp));
|
||||
}
|
||||
this.replaceExpressionWithStatements(nodes);
|
||||
} else if (this.isType("Statement") || !this.type) {
|
||||
} else if (this.isNodeType("Statement") || !this.type) {
|
||||
this._maybePopFromStatements(nodes);
|
||||
if (Array.isArray(this.container)) {
|
||||
return this._containerInsertAfter(nodes);
|
||||
|
||||
@ -110,12 +110,12 @@ export function replaceWith(replacement, whateverAllowed) {
|
||||
}
|
||||
|
||||
// replacing a statement with an expression so wrap it in an expression statement
|
||||
if (this.isType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
|
||||
if (this.isNodeType("Statement") && t.isExpression(replacement) && !this.canHaveVariableDeclarationOrExpression()) {
|
||||
replacement = t.expressionStatement(replacement);
|
||||
}
|
||||
|
||||
// replacing an expression with a statement so let's explode it
|
||||
if (this.isType("Expression") && t.isStatement(replacement)) {
|
||||
if (this.isNodeType("Expression") && t.isStatement(replacement)) {
|
||||
return this.replaceExpressionWithStatements([replacement]);
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,22 @@ import isNumber from "lodash/lang/isNumber";
|
||||
import isString from "lodash/lang/isString";
|
||||
import * as t from "../../types";
|
||||
|
||||
const BOOLEAN_BINARY_OPERATORS = ["==", "===", "!=", "!==", ">", "<", ">=", "<="];
|
||||
const NUMBER_BINARY_OPERATORS = ["-", "/", "*", "**", "&", "|"];
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function getTypeAnnotation(): {
|
||||
export function getTypeAnnotation() {
|
||||
return this.getTypeAnnotationInfo().annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function getTypeAnnotationInfo(): {
|
||||
inferred: boolean;
|
||||
annotation: ?Object;
|
||||
} {
|
||||
@ -24,23 +35,23 @@ export function getTypeAnnotation(): {
|
||||
|
||||
if (!type) {
|
||||
info.inferred = true;
|
||||
type = this.inferType(this);
|
||||
type = this.inferTypeAnnotation();
|
||||
}
|
||||
|
||||
if (type) {
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
info.annotation = type;
|
||||
}
|
||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
||||
info.annotation = type;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
* Resolves `NodePath` pointers until it resolves to an absolute path. ie. a data type instead of a
|
||||
* call etc. If a data type can't be resolved then the last path we were at is returned.
|
||||
*/
|
||||
|
||||
export function resolve(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
|
||||
@ -53,9 +64,15 @@ export function resolve(resolved?): ?NodePath {
|
||||
} else {
|
||||
// otherwise it's a request for a pattern and that's a bit more tricky
|
||||
}
|
||||
} else if (this.isIdentifier()) {
|
||||
} else if (this.isReferencedIdentifier()) {
|
||||
var binding = this.scope.getBinding(this.node.name);
|
||||
if (!binding || !binding.constant) return;
|
||||
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 this;
|
||||
@ -94,43 +111,121 @@ export function resolve(resolved?): ?NodePath {
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
* Infer the type of the current `NodePath`.
|
||||
*
|
||||
* NOTE: This is not cached. Use `getTypeAnnotation()` which is cached.
|
||||
*/
|
||||
|
||||
export function inferType(path: NodePath): ?Object {
|
||||
path = path.resolve();
|
||||
export function inferTypeAnnotation(force) {
|
||||
return this._inferTypeAnnotation(force) || t.anyTypeAnnotation();
|
||||
}
|
||||
|
||||
export function _inferTypeAnnotation(force?: boolean): ?Object {
|
||||
var path = this.resolve();
|
||||
if (!path) return;
|
||||
|
||||
if (path.isType("RestElement") || path.parentPath.isType("RestElement") || path.isType("ArrayExpression")) {
|
||||
if (path.isNodeType("RestElement") || path.parentPath.isNodeType("RestElement") || path.isNodeType("ArrayExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
||||
}
|
||||
|
||||
if (path.parentPath.isType("TypeCastExpression")) {
|
||||
if (path.parentPath.isNodeType("TypeCastExpression")) {
|
||||
return path.parentPath.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isType("TypeCastExpression")) {
|
||||
if (path.parentPath.isNodeType("ReturnStatement") && !force) {
|
||||
return path.parentPath.inferTypeAnnotation();
|
||||
}
|
||||
|
||||
if (path.isNodeType("ReturnStatement")) {
|
||||
var funcPath = this.findParent((node, path) => path.isFunction());
|
||||
if (!funcPath) return;
|
||||
|
||||
var returnType = funcPath.node.returnType;
|
||||
if (returnType) {
|
||||
return returnType;
|
||||
} else {
|
||||
return this.get("argument").inferTypeAnnotation(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isNodeType("NewExpression")) {
|
||||
// todo
|
||||
}
|
||||
|
||||
if (path.isNodeType("Identifier") && path.node.name === "undefined") {
|
||||
return t.voidTypeAnnotation();
|
||||
}
|
||||
|
||||
if (path.isNodeType("TypeCastExpression")) {
|
||||
return path.node.typeAnnotation;
|
||||
}
|
||||
|
||||
if (path.isType("ObjectExpression")) {
|
||||
if (path.isNodeType("ObjectExpression")) {
|
||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||
}
|
||||
|
||||
if (path.isType("Function")) {
|
||||
if (path.isNodeType("Function")) {
|
||||
return t.identifier("Function");
|
||||
}
|
||||
|
||||
if (path.isType("Literal")) {
|
||||
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.isNodeType("BinaryExpression")) {
|
||||
var operator = path.node.operator;
|
||||
if (NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
// these operators always result in numbers
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||
return t.booleanTypeAnnotation();
|
||||
} else if (operator === "+") {
|
||||
var right = this.get("right").getTypeAnnotation();
|
||||
var left = this.get("left").getTypeAnnotation();
|
||||
|
||||
if (t.isNumberTypeAnnotation(left) && t.isNumberTypeAnnotation(right)) {
|
||||
// both numbers so this will be a number
|
||||
return t.numberTypeAnnotation();
|
||||
} else if (t.isStringTypeAnnotation(left) && t.isStringTypeAnnotation(right)) {
|
||||
// both strings so this will be a string
|
||||
return t.stringTypeAnnotation();
|
||||
} else {
|
||||
// unsure if left and right are both strings or numbers so stay on the safe side
|
||||
return t.unionTypeAnnotation([
|
||||
t.stringTypeAnnotation(),
|
||||
t.numberTypeAnnotation()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isType("CallExpression")) {
|
||||
if (path.isNodeType("LogicalExpression")) {
|
||||
// todo: create UnionType of left and right annotations
|
||||
}
|
||||
|
||||
if (path.isNodeType("UpdateExpression")) {
|
||||
var operator = path.node.operator;
|
||||
if (operator === "++" || operator === "--") {
|
||||
return t.numberTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isNodeType("UnaryExpression") && path.node.prefix) {
|
||||
var operator = path.node.operator;
|
||||
if (operator === "!") {
|
||||
return t.booleanTypeAnnotation();
|
||||
} else if (operator === "+" || operator === "-") {
|
||||
return t.numberTypeAnnotation();
|
||||
}
|
||||
}
|
||||
|
||||
if (path.isNodeType("Literal")) {
|
||||
var value = path.node.value;
|
||||
if (typeof value === "string") return t.stringTypeAnnotation();
|
||||
if (typeof value === "number") return t.numberTypeAnnotation();
|
||||
if (typeof value === "boolean") return t.booleanTypeAnnotation();
|
||||
if (path.node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
|
||||
}
|
||||
|
||||
if (path.isNodeType("CallExpression")) {
|
||||
var callee = path.get("callee").resolve();
|
||||
if (callee && callee.isType("Function")) return callee.node.returnType;
|
||||
if (callee && callee.isNodeType("Function")) return callee.node.returnType;
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,8 +233,8 @@ export function inferType(path: NodePath): ?Object {
|
||||
* Description
|
||||
*/
|
||||
|
||||
export function isTypeGeneric(genericName: string, opts = {}): boolean {
|
||||
var typeInfo = this.getTypeAnnotation();
|
||||
export function isTypeAnnotationGeneric(genericName: string, opts = {}): boolean {
|
||||
var typeInfo = this.getTypeAnnotationInfo();
|
||||
var type = typeInfo.annotation;
|
||||
if (!type) return false;
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ export function equals(key, value): boolean {
|
||||
* been removed yet we still internally know the type and need it to calculate node replacement.
|
||||
*/
|
||||
|
||||
export function isType(type: string): boolean {
|
||||
export function isNodeType(type: string): boolean {
|
||||
return t.isType(this.type, type);
|
||||
}
|
||||
|
||||
|
||||
@ -14,42 +14,6 @@ export default class Binding {
|
||||
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
|
||||
*/
|
||||
@ -57,11 +21,6 @@ export default class Binding {
|
||||
reassign(path) {
|
||||
this.constant = false;
|
||||
this.constantViolations.push(path);
|
||||
|
||||
if (this.typeAnnotationInferred) {
|
||||
// destroy the inferred typeAnnotation
|
||||
this.typeAnnotation = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -380,7 +380,7 @@ export default class Scope {
|
||||
|
||||
if (t.isIdentifier(node)) {
|
||||
var binding = this.getBinding(node.name);
|
||||
if (binding && binding.constant && binding.isTypeGeneric("Array")) return node;
|
||||
if (binding && binding.constant && binding.path.isTypeAnnotationGeneric("Array")) return node;
|
||||
}
|
||||
|
||||
if (t.isArrayExpression(node)) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user