Skip to content

Commit

Permalink
feat(guess): split guess to esdoc-type-inference-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
h13i32maru committed May 7, 2017
1 parent 8e1c929 commit 2a5710a
Show file tree
Hide file tree
Showing 16 changed files with 14 additions and 583 deletions.
3 changes: 1 addition & 2 deletions .eslintignore
@@ -1,4 +1,3 @@
**/Publisher/Builder/template
**/test/fixture
**/test.new
**/test

20 changes: 0 additions & 20 deletions src/Doc/FunctionDoc.js
@@ -1,6 +1,5 @@
import babelGenerator from 'babel-generator';
import AbstractDoc from './AbstractDoc.js';
import ParamParser from '../Parser/ParamParser.js';
import NamingUtil from '../Util/NamingUtil.js';

/**
Expand Down Expand Up @@ -49,23 +48,4 @@ export default class FunctionDoc extends AbstractDoc {
super._$async();
this._value.async = this._node.async;
}

/** if @param is not exists, guess type of param by using self node. */
_$param() {
super._$param();
if (this._value.params) return;

this._value.params = ParamParser.guessParams(this._node.params);
}

/** if @return is not exists, guess type of return by using self node. */
_$return() {
super._$return();
if (this._value.return) return;

const result = ParamParser.guessReturnParam(this._node.body);
if (result) {
this._value.return = result;
}
}
}
9 changes: 0 additions & 9 deletions src/Doc/MemberDoc.js
@@ -1,6 +1,5 @@
import AbstractDoc from './AbstractDoc.js';
import MethodDoc from './MethodDoc.js';
import ParamParser from '../Parser/ParamParser.js';
import babelGenerator from 'babel-generator';

/**
Expand Down Expand Up @@ -53,12 +52,4 @@ export default class MemberDoc extends AbstractDoc {
_$memberof() {
Reflect.apply(MethodDoc.prototype._$memberof, this, []);
}

/** if @type is not exists, guess type by using self node */
_$type() {
super._$type();
if (this._value.type) return;

this._value.type = ParamParser.guessType(this._node.right);
}
}
45 changes: 0 additions & 45 deletions src/Doc/MethodDoc.js
@@ -1,5 +1,4 @@
import AbstractDoc from './AbstractDoc.js';
import ParamParser from '../Parser/ParamParser.js';
import babelGenerator from 'babel-generator';

/**
Expand Down Expand Up @@ -52,50 +51,6 @@ export default class MethodDoc extends AbstractDoc {
}
}

/** if @param is not exists, guess type of param by using self node. but ``get`` and ``set`` are not guessed. */
_$param() {
super._$param();
if (this._value.params) return;

if (['set', 'get'].includes(this._value.kind)) return;

this._value.params = ParamParser.guessParams(this._node.params);
}

/** if @type is not exists, guess type by using self node. only ``get`` and ``set`` are guess. */
_$type() {
super._$type();
if (this._value.type) return;

/* eslint-disable default-case */
switch (this._value.kind) {
case 'set':
this._value.type = ParamParser.guessType(this._node.right);
break;
case 'get': {
const result = ParamParser.guessReturnParam(this._node.body);
if (result) this._value.type = result;
break;
}
}
}

/**
* if @return is not exists, guess type of return by using self node.
* but ``constructor``, ``get`` and ``set``are not guessed.
*/
_$return() {
super._$return();
if (this._value.return) return;

if (['constructor', 'set', 'get'].includes(this._value.kind)) return;

const result = ParamParser.guessReturnParam(this._node.body);
if (result) {
this._value.return = result;
}
}

/** use generator property of self node. */
_$generator() {
super._$generator();
Expand Down
16 changes: 0 additions & 16 deletions src/Doc/VariableDoc.js
@@ -1,5 +1,4 @@
import AbstractDoc from './AbstractDoc.js';
import ParamParser from '../Parser/ParamParser.js';

/**
* Doc Class from Variable Declaration AST node.
Expand Down Expand Up @@ -40,20 +39,5 @@ export default class VariableDoc extends AbstractDoc {
super._$memberof();
this._value.memberof = this._pathResolver.filePath;
}

/** if @type is not exists, guess type by using self node. */
_$type() {
super._$type();
if (this._value.type) return;

if (this._node.declarations[0].init.type === 'NewExpression') {
const className = this._node.declarations[0].init.callee.name;
let longname = this._findClassLongname(className);
if (!longname) longname = '*';
this._value.type = {types: [longname]};
} else {
this._value.type = ParamParser.guessType(this._node.declarations[0].init);
}
}
}

240 changes: 0 additions & 240 deletions src/Parser/ParamParser.js
@@ -1,8 +1,4 @@
import Logger from 'color-logger';
import assert from 'assert';
import ASTUtil from '../Util/ASTUtil.js';

const logger = new Logger('ParamParser');

/**
* Param Type Parser class.
Expand Down Expand Up @@ -173,240 +169,4 @@ export default class ParamParser {

return result;
}

/* eslint-disable complexity */
/* eslint-disable max-statements */
/**
* guess param type by using param default arguments.
* @param {Object} params - node of callable AST node.
* @returns {ParsedParam[]} guess param results.
*
* @example
* // with method
* let results = ParamParser.guessParams(node.value.params);
*
* // with function
* let results = ParamParser.guessParams(node.params);
*/
static guessParams(params) {
const _params = [];
for (let i = 0; i < params.length; i++) {
const param = params[i];
const result = {};

switch (param.type) {
case 'Identifier':
// e.g. func(a){}
result.name = param.name;
result.types = ['*'];
break;

case 'AssignmentPattern':
if (param.left.type === 'Identifier') {
result.name = param.left.name;
} else if (param.left.type === 'ObjectPattern') {
result.name = `objectPattern${i === 0 ? '' : i}`;
} else if (param.left.type === 'ArrayPattern') {
result.name = `arrayPattern${i === 0 ? '' : i}`;
}

result.optional = true;

if (param.right.type.includes('Literal')) {
// e.g. func(a = 10){}
result.types = param.right.value === null ? ['*'] : [typeof param.right.value];
result.defaultRaw = param.right.value;
result.defaultValue = `${result.defaultRaw}`;
} else if (param.right.type === 'ArrayExpression') {
// e.g. func(a = [123]){}
result.types = param.right.elements.length ? [`${typeof param.right.elements[0].value}[]`] : ['*[]'];
result.defaultRaw = param.right.elements.map((elm)=> elm.value);
result.defaultValue = `${JSON.stringify(result.defaultRaw)}`;
} else if (param.right.type === 'ObjectExpression') {
const typeMap = {};
for (const prop of param.left.properties || []) {
typeMap[prop.key.name] = '*';
}

// e.g. func(a = {key: 123}){}
const obj = {};
for (const prop of param.right.properties) {
obj[prop.key.name] = prop.value.value;
typeMap[prop.key.name] = typeof prop.value.value;
}

const types = [];
for (const key of Object.keys(typeMap)) {
types.push(`"${key}": ${typeMap[key]}`);
}

result.types = [`{${types.join(', ')}}`];
result.defaultRaw = obj;
result.defaultValue = `${JSON.stringify(result.defaultRaw)}`;
} else if (param.right.type === 'Identifier') {
// e.g. func(a = value){}
result.types = ['*'];
result.defaultRaw = param.right.name;
result.defaultValue = `${param.right.name}`;
} else {
// e.g. func(a = new Foo()){}, func(a = foo()){}
// CallExpression, NewExpression
result.types = ['*'];
}
break;
case 'RestElement':
// e.g. func(...a){}
result.name = `${param.argument.name}`;
result.types = ['...*'];
result.spread = true;
break;
case 'ObjectPattern': {
const objectPattern = [];
const raw = {};
for (const property of param.properties) {
if (property.type === 'ObjectProperty') {
objectPattern.push(`"${property.key.name}": *`);
raw[property.key.name] = null;
} else if (property.type === 'RestProperty') {
objectPattern.push(`...${property.argument.name}: Object`);
raw[property.argument.name] = {};
}
}
result.name = `objectPattern${i === 0 ? '' : i}`;
result.types = [`{${objectPattern.join(', ')}}`];
result.defaultRaw = raw;
result.defaultValue = `${JSON.stringify(result.defaultRaw)}`;
break;
}
case 'ArrayPattern': {
// e.g. func([a, b = 10]){}
let arrayType = null;
const raw = [];

for (const element of param.elements) {
if (element.type === 'Identifier') {
raw.push('null');
} else if (element.type === 'AssignmentPattern') {
if ('value' in element.right) {
if (!arrayType && element.right.value !== null) arrayType = typeof element.right.value;
raw.push(JSON.stringify(element.right.value));
} else {
raw.push('*');
}
}
}

if (!arrayType) arrayType = '*';
result.name = `arrayPattern${i === 0 ? '' : i}`;
result.types = [`${arrayType}[]`];
result.defaultRaw = raw;
result.defaultValue = `[${raw.join(', ')}]`;
break;
}
default:
logger.w('unknown param.type', param);
}

_params.push(result);
}

return _params;
}

/**
* guess return type by using return node.
* @param {ASTNode} body - callable body node.
* @returns {ParsedParam|null}
*/
static guessReturnParam(body) {
const result = {};
const guessType = this.guessType.bind(this);

ASTUtil.traverse(body, (node, parent, path)=>{
// `return` in Function is not the body's `return`
if (node.type.includes('Function')) {
path.skip();
return;
}

if (node.type !== 'ReturnStatement') return;

if (!node.argument) return;

result.types = guessType(node.argument).types;
});

if (result.types) {
return result;
}

return null;
}

/**
* guess self type by using assignment node.
* @param {ASTNode} right - assignment right node.
* @returns {ParsedParam}
*/
static guessType(right) {
if (!right) {
return {types: ['*']};
}

if (right.type === 'TemplateLiteral') {
return {types: ['string']};
}

if (right.type === 'NullLiteral') {
return {types: ['*']};
}

if (right.type.includes('Literal')) {
return {types: [typeof right.value]};
}

if (right.type === 'ArrayExpression') {
if (right.elements.length) {
return {types: [`${typeof right.elements[0].value}[]`]};
} else {
return {types: ['*[]']};
}
}

if (right.type === 'ObjectExpression') {
const typeMap = {};
for (const prop of right.properties) {
switch (prop.type) {
case 'ObjectProperty': {
const name = `"${prop.key.name || prop.key.value}"`;
typeMap[name] = prop.value.value ? typeof prop.value.value : '*';
break;
}
case 'ObjectMethod': {
const name = `"${prop.key.name || prop.key.value}"`;
typeMap[name] = 'function';
break;
}
case 'SpreadProperty': {
const name = `...${prop.argument.name}`;
typeMap[name] = 'Object';
break;
}
default: {
const name = `"${prop.key.name || prop.key.value}"`;
typeMap[name] = '*';
}
}
}

const types = [];
for (const key of Object.keys(typeMap)) {
types.push(`${key}: ${typeMap[key]}`);
}

return {types: [`{${types.join(', ')}}`]};
}

return {types: ['*']};
}
}

0 comments on commit 2a5710a

Please sign in to comment.