completely rework type inferrence, support coercing to union types and be more reliable in the inferrence and always be cautious
This commit is contained in:
parent
64f4209119
commit
3d3cb4be4f
@ -70,6 +70,10 @@ export function _ForOfStatementArray(node, scope, file) {
|
|||||||
loop.body.body.unshift(t.expressionStatement(t.assignmentExpression("=", left, iterationValue)));
|
loop.body.body.unshift(t.expressionStatement(t.assignmentExpression("=", left, iterationValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.parentPath.isLabeledStatement()) {
|
||||||
|
loop = t.labeledStatement(this.parentPath.node.label, loop)
|
||||||
|
}
|
||||||
|
|
||||||
nodes.push(loop);
|
nodes.push(loop);
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
|
|||||||
@ -3,12 +3,14 @@ import * as util from "../../../util";
|
|||||||
import * as t from "../../../types";
|
import * as t from "../../../types";
|
||||||
|
|
||||||
var memberExpressionOptimisationVisitor = {
|
var memberExpressionOptimisationVisitor = {
|
||||||
enter(node, parent, scope, state) {
|
Scope(node, parent, scope, state) {
|
||||||
// check if this scope has a local binding that will shadow the rest parameter
|
// check if this scope has a local binding that will shadow the rest parameter
|
||||||
if (this.isScope() && !scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
|
if (!scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
|
||||||
return this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enter(node, parent, scope, state) {
|
||||||
var stop = () => {
|
var stop = () => {
|
||||||
state.canOptimise = false;
|
state.canOptimise = false;
|
||||||
this.stop();
|
this.stop();
|
||||||
@ -31,8 +33,8 @@ var memberExpressionOptimisationVisitor = {
|
|||||||
if (!state.noOptimise && t.isMemberExpression(parent) && parent.computed) {
|
if (!state.noOptimise && t.isMemberExpression(parent) && parent.computed) {
|
||||||
// if we know that this member expression is referencing a number then we can safely
|
// if we know that this member expression is referencing a number then we can safely
|
||||||
// optimise it
|
// optimise it
|
||||||
var prop = parent.property;
|
var prop = this.parentPath.get("property");
|
||||||
if (isNumber(prop.value) || t.isUnaryExpression(prop) || t.isBinaryExpression(prop)) {
|
if (prop.isTypeAnnotationGeneric("Number")) {
|
||||||
state.candidates.push(this);
|
state.candidates.push(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,6 +45,8 @@ var memberExpressionOptimisationVisitor = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function optimizeMemberExpression(parent, offset) {
|
function optimizeMemberExpression(parent, offset) {
|
||||||
|
if (offset === 0) return;
|
||||||
|
|
||||||
var newExpr;
|
var newExpr;
|
||||||
var prop = parent.property;
|
var prop = parent.property;
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,10 @@ export function AssignmentExpression() {
|
|||||||
|
|
||||||
export function IfStatement() {
|
export function IfStatement() {
|
||||||
var evaluated = this.get("test").evaluate();
|
var evaluated = this.get("test").evaluate();
|
||||||
if (!evaluated.confident) return this.skip();
|
if (!evaluated.confident) {
|
||||||
|
// todo: deopt binding values for constant violations inside
|
||||||
|
return this.skip();
|
||||||
|
}
|
||||||
|
|
||||||
if (evaluated.value) {
|
if (evaluated.value) {
|
||||||
this.skipKey("alternate");
|
this.skipKey("alternate");
|
||||||
|
|||||||
@ -102,15 +102,15 @@ export function evaluate(): { confident: boolean; value: any } {
|
|||||||
|
|
||||||
if (path.isReferencedIdentifier()) {
|
if (path.isReferencedIdentifier()) {
|
||||||
var binding = path.scope.getBinding(node.name);
|
var binding = path.scope.getBinding(node.name);
|
||||||
if (binding && binding.hasValue) return binding.value;
|
if (binding && binding.hasValue) {
|
||||||
}
|
return binding.value;
|
||||||
|
|
||||||
if ((path.isIdentifier() || path.isMemberExpression()) && path.isReferenced()) {
|
|
||||||
var resolved = path.resolve();
|
|
||||||
if (resolved === path) {
|
|
||||||
return confident = false;
|
|
||||||
} else {
|
} else {
|
||||||
return evaluate(resolved);
|
var resolved = path.resolve();
|
||||||
|
if (resolved === path) {
|
||||||
|
return confident = false;
|
||||||
|
} else {
|
||||||
|
return evaluate(resolved);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,17 @@ export var ReferencedIdentifier = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export var Expression = {
|
||||||
|
types: ["Expression"],
|
||||||
|
checkPath(path) {
|
||||||
|
if (path.isIdentifier()) {
|
||||||
|
return path.isReferencedIdentifier();
|
||||||
|
} else {
|
||||||
|
return t.isExpression(path.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export var Scope = {
|
export var Scope = {
|
||||||
types: ["Scopable"],
|
types: ["Scopable"],
|
||||||
checkPath(path) {
|
checkPath(path) {
|
||||||
|
|||||||
@ -2,53 +2,14 @@ import type NodePath from "./index";
|
|||||||
import * as t from "../../types";
|
import * as t from "../../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description
|
* Resolve a "pointer" `NodePath` to it's absolute path.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getTypeAnnotation() {
|
export function resolve(dangerous, resolved) {
|
||||||
return this.getTypeAnnotationInfo().annotation;
|
return this._resolve(dangerous, resolved) || this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function _resolve(dangerous?, resolved?): ?NodePath {
|
||||||
* Description
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function getTypeAnnotationInfo(): {
|
|
||||||
inferred: boolean;
|
|
||||||
annotation: ?Object;
|
|
||||||
} {
|
|
||||||
if (this.typeInfo) {
|
|
||||||
return this.typeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = this.typeInfo = {
|
|
||||||
inferred: false,
|
|
||||||
annotation: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var type = this.node && this.node.typeAnnotation;
|
|
||||||
|
|
||||||
if (!type) {
|
|
||||||
info.inferred = true;
|
|
||||||
type = this.inferTypeAnnotation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.isTypeAnnotation(type)) type = type.typeAnnotation;
|
|
||||||
info.annotation = type;
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
return this._resolve(resolved) || this;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _resolve(resolved?): ?NodePath {
|
|
||||||
// detect infinite recursion
|
// detect infinite recursion
|
||||||
// todo: possibly have a max length on this just to be safe
|
// todo: possibly have a max length on this just to be safe
|
||||||
if (resolved && resolved.indexOf(this) >= 0) return;
|
if (resolved && resolved.indexOf(this) >= 0) return;
|
||||||
@ -59,7 +20,7 @@ export function _resolve(resolved?): ?NodePath {
|
|||||||
|
|
||||||
if (this.isVariableDeclarator()) {
|
if (this.isVariableDeclarator()) {
|
||||||
if (this.get("id").isIdentifier()) {
|
if (this.get("id").isIdentifier()) {
|
||||||
return this.get("init").resolve(resolved);
|
return this.get("init").resolve(dangerous, resolved);
|
||||||
} else {
|
} else {
|
||||||
// otherwise it's a request for a pattern and that's a bit more tricky
|
// otherwise it's a request for a pattern and that's a bit more tricky
|
||||||
}
|
}
|
||||||
@ -74,9 +35,9 @@ export function _resolve(resolved?): ?NodePath {
|
|||||||
if (binding.kind === "module") return;
|
if (binding.kind === "module") return;
|
||||||
|
|
||||||
if (binding.path !== this) {
|
if (binding.path !== this) {
|
||||||
return binding.path.resolve(resolved);
|
return binding.path.resolve(dangerous, resolved);
|
||||||
}
|
}
|
||||||
} else if (this.isMemberExpression()) {
|
} else if (dangerous && this.isMemberExpression()) {
|
||||||
// this is dangerous, as non-direct target assignments will mutate it's state
|
// this is dangerous, as non-direct target assignments will mutate it's state
|
||||||
// making this resolution inaccurate
|
// making this resolution inaccurate
|
||||||
|
|
||||||
@ -85,7 +46,7 @@ export function _resolve(resolved?): ?NodePath {
|
|||||||
|
|
||||||
var targetName = targetKey.value;
|
var targetName = targetKey.value;
|
||||||
|
|
||||||
var target = this.get("object").resolve(resolved);
|
var target = this.get("object").resolve(dangerous, resolved);
|
||||||
|
|
||||||
if (target.isObjectExpression()) {
|
if (target.isObjectExpression()) {
|
||||||
var props = target.get("properties");
|
var props = target.get("properties");
|
||||||
@ -100,58 +61,105 @@ export function _resolve(resolved?): ?NodePath {
|
|||||||
// { "foo": "obj" } or { ["foo"]: "obj" }
|
// { "foo": "obj" } or { ["foo"]: "obj" }
|
||||||
match = match || key.isLiteral({ value: targetName });
|
match = match || key.isLiteral({ value: targetName });
|
||||||
|
|
||||||
if (match) return prop.get("value").resolve(resolved);
|
if (match) return prop.get("value").resolve(dangerous, resolved);
|
||||||
}
|
}
|
||||||
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
|
} else if (target.isArrayExpression() && !isNaN(+targetName)) {
|
||||||
var elems = target.get("elements");
|
var elems = target.get("elements");
|
||||||
var elem = elems[targetName];
|
var elem = elems[targetName];
|
||||||
if (elem) return elem.resolve(resolved);
|
if (elem) return elem.resolve(dangerous, resolved);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infer the type of the current `NodePath`.
|
* Infer the type of the current `NodePath`.
|
||||||
*
|
*
|
||||||
* NOTE: This is not cached. Use `getTypeAnnotation()` which is cached.
|
* NOTE: This is not cached. Use `getTypeAnnotation()` which is cached.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function inferTypeAnnotation(force) {
|
export function getTypeAnnotation(force) {
|
||||||
return this._inferTypeAnnotation(force) || t.anyTypeAnnotation();
|
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 _inferTypeAnnotation(force?: boolean): ?Object {
|
export function _getTypeAnnotationBindingConstantViolations(name, types = []) {
|
||||||
var path = this.resolve();
|
var binding = this.scope.getBinding(name);
|
||||||
|
|
||||||
var node = path.node;
|
for (var constantViolation of (binding.constantViolations: Array)) {
|
||||||
|
types.push(constantViolation.getTypeAnnotation());
|
||||||
if (!node && path.key === "init" && path.parentPath.isVariableDeclarator()) {
|
|
||||||
return t.voidTypeAnnotation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node) return;
|
if (types.length) {
|
||||||
|
return t.createUnionTypeAnnotation(types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if (node.typeAnnotation) {
|
||||||
return node.typeAnnotation;
|
return node.typeAnnotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isRestElement() || path.parentPath.isRestElement() || path.isArrayExpression()) {
|
//
|
||||||
return t.genericTypeAnnotation(t.identifier("Array"));
|
if (this.isVariableDeclarator()) {
|
||||||
|
var id = this.get("id");
|
||||||
|
|
||||||
|
if (id.isIdentifier()) {
|
||||||
|
return this._getTypeAnnotationBindingConstantViolations(id.node.name, [
|
||||||
|
this.get("init").getTypeAnnotation()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.parentPath.isTypeCastExpression()) {
|
//
|
||||||
return path.parentPath.inferTypeAnnotation();
|
if (this.parentPath.isTypeCastExpression()) {
|
||||||
|
return this.parentPath.getTypeAnnotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isTypeCastExpression()) {
|
if (this.isTypeCastExpression()) {
|
||||||
return node.typeAnnotation;
|
return node.typeAnnotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.parentPath.isReturnStatement() && !force) {
|
//
|
||||||
return path.parentPath.inferTypeAnnotation();
|
if (this.isRestElement() || this.parentPath.isRestElement() || this.isArrayExpression()) {
|
||||||
|
return t.genericTypeAnnotation(t.identifier("Array"), t.typeParameterInstantiation([t.anyTypeAnnotation()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isReturnStatement()) {
|
//
|
||||||
|
if (!force && this.parentPath.isReturnStatement()) {
|
||||||
|
return this.parentPath.getTypeAnnotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isReturnStatement()) {
|
||||||
var funcPath = this.findParent((node, path) => path.isFunction());
|
var funcPath = this.findParent((node, path) => path.isFunction());
|
||||||
if (!funcPath) return;
|
if (!funcPath) return;
|
||||||
|
|
||||||
@ -159,40 +167,58 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
if (returnType) {
|
if (returnType) {
|
||||||
return returnType;
|
return returnType;
|
||||||
} else {
|
} else {
|
||||||
return this.get("argument").inferTypeAnnotation(true);
|
return this.get("argument").getTypeAnnotation(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isNewExpression() && path.get("callee").isIdentifier()) {
|
//
|
||||||
|
if (this.isNewExpression() && this.get("callee").isIdentifier()) {
|
||||||
// only resolve identifier callee
|
// only resolve identifier callee
|
||||||
return t.genericTypeAnnotation(node.callee);
|
return t.genericTypeAnnotation(node.callee);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isReferencedIdentifier()) {
|
//
|
||||||
|
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(node.name, [
|
||||||
|
binding.path.getTypeAnnotation()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// built-in values
|
||||||
if (node.name === "undefined") {
|
if (node.name === "undefined") {
|
||||||
return t.voidTypeAnnotation();
|
return t.voidTypeAnnotation();
|
||||||
} else if (node.name === "NaN") {
|
} else if (node.name === "NaN" || node.name === "Infinity") {
|
||||||
return t.numberTypeAnnotation();
|
return t.numberTypeAnnotation();
|
||||||
|
} else if (node.name === "arguments") {
|
||||||
|
// todo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isObjectExpression()) {
|
//
|
||||||
|
if (this.isObjectExpression()) {
|
||||||
return t.genericTypeAnnotation(t.identifier("Object"));
|
return t.genericTypeAnnotation(t.identifier("Object"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isFunction() && path.parentPath.isProperty({ kind: "get" })) {
|
//
|
||||||
return node.returnType;
|
if (this.isFunction() || this.isClass()) {
|
||||||
}
|
|
||||||
|
|
||||||
if (path.isFunction() || path.isClass()) {
|
|
||||||
return t.genericTypeAnnotation(t.identifier("Function"));
|
return t.genericTypeAnnotation(t.identifier("Function"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isTemplateLiteral()) {
|
//
|
||||||
|
if (this.isTemplateLiteral()) {
|
||||||
return t.stringTypeAnnotation();
|
return t.stringTypeAnnotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isUnaryExpression()) {
|
//
|
||||||
|
if (this.isUnaryExpression()) {
|
||||||
let operator = node.operator;
|
let operator = node.operator;
|
||||||
|
|
||||||
if (operator === "void") {
|
if (operator === "void") {
|
||||||
@ -206,7 +232,8 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isBinaryExpression()) {
|
//
|
||||||
|
if (this.isBinaryExpression()) {
|
||||||
let operator = node.operator;
|
let operator = node.operator;
|
||||||
|
|
||||||
if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
if (t.NUMBER_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||||
@ -214,17 +241,15 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
} else if (t.BOOLEAN_BINARY_OPERATORS.indexOf(operator) >= 0) {
|
||||||
return t.booleanTypeAnnotation();
|
return t.booleanTypeAnnotation();
|
||||||
} else if (operator === "+") {
|
} else if (operator === "+") {
|
||||||
var right = path.get("right").resolve();
|
var right = this.get("right");
|
||||||
var left = path.get("left").resolve();
|
var left = this.get("left");
|
||||||
|
|
||||||
if (left || right) {
|
if (left.isTypeAnnotationGeneric("Number") && right.isTypeAnnotationGeneric("Number")) {
|
||||||
if (left.isTypeAnnotationGeneric("Number") && right.isTypeAnnotationGeneric("Number")) {
|
// both numbers so this will be a number
|
||||||
// both numbers so this will be a number
|
return t.numberTypeAnnotation();
|
||||||
return t.numberTypeAnnotation();
|
} else if (left.isTypeAnnotationGeneric("String") || right.isTypeAnnotationGeneric("String")) {
|
||||||
} else if (left.isTypeAnnotationGeneric("String") || right.isTypeAnnotationGeneric("String")) {
|
// one is a string so the result will be a string
|
||||||
// one is a string so the result will be a string
|
return t.stringTypeAnnotation();
|
||||||
return t.stringTypeAnnotation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsure if left and right are strings or numbers so stay on the safe side
|
// unsure if left and right are strings or numbers so stay on the safe side
|
||||||
@ -235,30 +260,42 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isLogicalExpression()) {
|
//
|
||||||
// todo: create UnionType of left and right annotations
|
if (this.isLogicalExpression()) {
|
||||||
|
return t.createUnionTypeAnnotation([
|
||||||
|
this.get("left").getTypeAnnotation(),
|
||||||
|
this.get("right").getTypeAnnotation()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isConditionalExpression()) {
|
//
|
||||||
// todo: create UnionType of consequent and alternate annotations
|
if (this.isConditionalExpression()) {
|
||||||
|
return t.createUnionTypeAnnotation([
|
||||||
|
this.get("consequent").getTypeAnnotation(),
|
||||||
|
this.get("alternate").getTypeAnnotation()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isSequenceExpression()) {
|
//
|
||||||
return this.get("expressions").pop().inferTypeAnnotation(force);
|
if (this.isSequenceExpression()) {
|
||||||
|
return this.get("expressions").pop().getTypeAnnotation(force);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isAssignmentExpression()) {
|
//
|
||||||
return this.get("right").inferTypeAnnotation(force);
|
if (this.isAssignmentExpression()) {
|
||||||
|
return this.get("right").getTypeAnnotation(force);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isUpdateExpression()) {
|
//
|
||||||
|
if (this.isUpdateExpression()) {
|
||||||
let operator = node.operator;
|
let operator = node.operator;
|
||||||
if (operator === "++" || operator === "--") {
|
if (operator === "++" || operator === "--") {
|
||||||
return t.numberTypeAnnotation();
|
return t.numberTypeAnnotation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.isLiteral()) {
|
//
|
||||||
|
if (this.isLiteral()) {
|
||||||
var value = node.value;
|
var value = node.value;
|
||||||
if (typeof value === "string") return t.stringTypeAnnotation();
|
if (typeof value === "string") return t.stringTypeAnnotation();
|
||||||
if (typeof value === "number") return t.numberTypeAnnotation();
|
if (typeof value === "number") return t.numberTypeAnnotation();
|
||||||
@ -266,12 +303,14 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
|
if (node.regex) return t.genericTypeAnnotation(t.identifier("RegExp"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
var callPath;
|
var callPath;
|
||||||
if (path.isCallExpression()) callPath = path.get("callee");
|
if (this.isCallExpression()) callPath = this.get("callee");
|
||||||
if (path.isTaggedTemplateExpression()) callPath = path.get("tag");
|
if (this.isTaggedTemplateExpression()) callPath = this.get("tag");
|
||||||
if (callPath) {
|
if (callPath) {
|
||||||
var callee = callPath.resolve();
|
var callee = callPath.resolve();
|
||||||
// todo: read typescript/flow interfaces
|
// todo: read typescript/flow interfaces
|
||||||
|
|
||||||
if (callee.isFunction()) {
|
if (callee.isFunction()) {
|
||||||
if (callee.is("async")) {
|
if (callee.is("async")) {
|
||||||
if (callee.is("generator")) {
|
if (callee.is("generator")) {
|
||||||
@ -280,7 +319,11 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
return t.genericTypeAnnotation(t.identifier("Promise"));
|
return t.genericTypeAnnotation(t.identifier("Promise"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return callee.node.returnType;
|
if (callee.node.returnType) {
|
||||||
|
return callee.node.returnType;
|
||||||
|
} else {
|
||||||
|
// todo: get union type of all return arguments
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,21 +333,11 @@ export function _inferTypeAnnotation(force?: boolean): ?Object {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function isTypeAnnotationGeneric(genericName: string, opts = {}): boolean {
|
export function isTypeAnnotationGeneric(genericName: string): boolean {
|
||||||
var typeInfo = this.getTypeAnnotationInfo();
|
var type = this.getTypeAnnotation();
|
||||||
var type = typeInfo.annotation;
|
|
||||||
if (!type) return false;
|
|
||||||
|
|
||||||
if (typeInfo.inferred && opts.inference === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName })) {
|
if (t.isGenericTypeAnnotation(type) && t.isIdentifier(type.id, { name: genericName })) {
|
||||||
if (opts.requireTypeParameters && !type.typeParameters) {
|
return true;
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (genericName === "String") {
|
if (genericName === "String") {
|
||||||
|
|||||||
@ -43,7 +43,7 @@ var collectorVisitor = {
|
|||||||
ForXStatement() {
|
ForXStatement() {
|
||||||
var left = this.get("left");
|
var left = this.get("left");
|
||||||
if (left.isPattern() || left.isIdentifier()) {
|
if (left.isPattern() || left.isIdentifier()) {
|
||||||
this.scope.registerConstantViolation(left);
|
this.scope.registerConstantViolation(left, left);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -79,15 +79,15 @@ var collectorVisitor = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register as constant violation
|
// register as constant violation
|
||||||
this.scope.registerConstantViolation(this.get("left"), this.get("right"));
|
this.scope.registerConstantViolation(this, this.get("left"), this.get("right"));
|
||||||
},
|
},
|
||||||
|
|
||||||
UpdateExpression(node, parent, scope) {
|
UpdateExpression(node, parent, scope) {
|
||||||
scope.registerConstantViolation(this.get("argument"), null);
|
scope.registerConstantViolation(this, this.get("argument"), null);
|
||||||
},
|
},
|
||||||
|
|
||||||
UnaryExpression(node, parent, scope) {
|
UnaryExpression(node, parent, scope) {
|
||||||
if (node.operator === "delete") scope.registerConstantViolation(this.get("left"), null);
|
if (node.operator === "delete") scope.registerConstantViolation(this, this.get("left"), null);
|
||||||
},
|
},
|
||||||
|
|
||||||
BlockScoped(node, parent, scope) {
|
BlockScoped(node, parent, scope) {
|
||||||
@ -456,7 +456,7 @@ export default class Scope {
|
|||||||
* Description
|
* Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
registerConstantViolation(left: NodePath, right: NodePath) {
|
registerConstantViolation(root: NodePath, left: NodePath, right: NodePath) {
|
||||||
var ids = left.getBindingIdentifiers();
|
var ids = left.getBindingIdentifiers();
|
||||||
for (var name in ids) {
|
for (var name in ids) {
|
||||||
var binding = this.getBinding(name);
|
var binding = this.getBinding(name);
|
||||||
@ -467,7 +467,7 @@ export default class Scope {
|
|||||||
if (rightType && binding.isCompatibleWithType(rightType)) continue;
|
if (rightType && binding.isCompatibleWithType(rightType)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.reassign(left, right);
|
binding.reassign(root, left, right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -79,9 +79,9 @@
|
|||||||
"JSXEmptyExpression": ["Expression"],
|
"JSXEmptyExpression": ["Expression"],
|
||||||
"JSXMemberExpression": ["Expression"],
|
"JSXMemberExpression": ["Expression"],
|
||||||
|
|
||||||
"AnyTypeAnnotation": ["Flow"],
|
"AnyTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
"ArrayTypeAnnotation": ["Flow"],
|
"ArrayTypeAnnotation": ["Flow"],
|
||||||
"BooleanTypeAnnotation": ["Flow"],
|
"BooleanTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
"ClassImplements": ["Flow"],
|
"ClassImplements": ["Flow"],
|
||||||
"DeclareClass": ["Flow", "Statement"],
|
"DeclareClass": ["Flow", "Statement"],
|
||||||
"DeclareFunction": ["Flow", "Statement"],
|
"DeclareFunction": ["Flow", "Statement"],
|
||||||
@ -93,11 +93,11 @@
|
|||||||
"InterfaceExtends": ["Flow"],
|
"InterfaceExtends": ["Flow"],
|
||||||
"InterfaceDeclaration": ["Flow", "Statement", "Declaration"],
|
"InterfaceDeclaration": ["Flow", "Statement", "Declaration"],
|
||||||
"IntersectionTypeAnnotation": ["Flow"],
|
"IntersectionTypeAnnotation": ["Flow"],
|
||||||
"MixedTypeAnnotation": ["Flow"],
|
"MixedTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
"NullableTypeAnnotation": ["Flow"],
|
"NullableTypeAnnotation": ["Flow"],
|
||||||
"NumberTypeAnnotation": ["Flow"],
|
"NumberTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
"StringLiteralTypeAnnotation": ["Flow"],
|
"StringLiteralTypeAnnotation": ["Flow"],
|
||||||
"StringTypeAnnotation": ["Flow"],
|
"StringTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
"TupleTypeAnnotation": ["Flow"],
|
"TupleTypeAnnotation": ["Flow"],
|
||||||
"TypeofTypeAnnotation": ["Flow"],
|
"TypeofTypeAnnotation": ["Flow"],
|
||||||
"TypeAlias": ["Flow", "Statement"],
|
"TypeAlias": ["Flow", "Statement"],
|
||||||
@ -111,7 +111,7 @@
|
|||||||
"ObjectTypeProperty": ["Flow", "UserWhitespacable"],
|
"ObjectTypeProperty": ["Flow", "UserWhitespacable"],
|
||||||
"QualifiedTypeIdentifier": ["Flow"],
|
"QualifiedTypeIdentifier": ["Flow"],
|
||||||
"UnionTypeAnnotation": ["Flow"],
|
"UnionTypeAnnotation": ["Flow"],
|
||||||
"VoidTypeAnnotation": ["Flow"],
|
"VoidTypeAnnotation": ["Flow", "FlowBaseAnnotation"],
|
||||||
|
|
||||||
"JSXAttribute": ["JSX", "Immutable"],
|
"JSXAttribute": ["JSX", "Immutable"],
|
||||||
"JSXClosingElement": ["JSX", "Immutable"],
|
"JSXClosingElement": ["JSX", "Immutable"],
|
||||||
|
|||||||
82
src/babel/types/flow.js
Normal file
82
src/babel/types/flow.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import * as t from "./index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an array of `types` and flattens them, removing duplicates and
|
||||||
|
* returns a `UnionTypeAnnotation` node containg them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function createUnionTypeAnnotation(types) {
|
||||||
|
var flattened = removeTypeDuplicates(types);
|
||||||
|
|
||||||
|
if (flattened.length === 1) {
|
||||||
|
return flattened[0];
|
||||||
|
} else {
|
||||||
|
return t.unionTypeAnnotation(flattened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTypeDuplicates(nodes) {
|
||||||
|
var generics = {};
|
||||||
|
var bases = {};
|
||||||
|
|
||||||
|
var flattened = [];
|
||||||
|
var types = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
var node = nodes[i];
|
||||||
|
if (!node) continue;
|
||||||
|
|
||||||
|
// this type matches anything
|
||||||
|
if (t.isAnyTypeAnnotation(node)) {
|
||||||
|
return [node];
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (t.isFlowBaseAnnotation(node)) {
|
||||||
|
bases[node.type] = node;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (t.isUnionTypeAnnotation(node)) {
|
||||||
|
nodes = nodes.concat(node.types);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a matching generic type and merge and deduplicate the type parameters
|
||||||
|
if (t.isGenericTypeAnnotation(node)) {
|
||||||
|
var name = node.id.name;
|
||||||
|
|
||||||
|
if (generics[name]) {
|
||||||
|
var existing = generics[name];
|
||||||
|
if (existing.typeParameters) {
|
||||||
|
if (node.typeParameters) {
|
||||||
|
existing.typeParameters.params = removeTypeDuplicates(
|
||||||
|
existing.typeParameters.params.concat(node.typeParameters.params)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
existing = node.typeParameters;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generics[name] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
types.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add back in bases
|
||||||
|
for (var type in bases) {
|
||||||
|
types.push(bases[type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add back in generics
|
||||||
|
for (var name in generics) {
|
||||||
|
types.push(generics[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
@ -314,3 +314,4 @@ exports.__esModule = true;
|
|||||||
assign(t, require("./retrievers"));
|
assign(t, require("./retrievers"));
|
||||||
assign(t, require("./validators"));
|
assign(t, require("./validators"));
|
||||||
assign(t, require("./converters"));
|
assign(t, require("./converters"));
|
||||||
|
assign(t, require("./flow"));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user