split inference logic into separate folder

This commit is contained in:
Sebastian McKenzie
2015-06-09 14:02:57 +01:00
parent eaaa279aa5
commit f4d7cc55c1
11 changed files with 521 additions and 492 deletions

View File

@@ -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;
}

View File

@@ -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]));
}
}

View File

@@ -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.

View File

@@ -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"));

View 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 });
}

View 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);
}
}

View 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
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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"));
}
}

View File

@@ -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 = ["+", "-", "++", "--", "~"];