Infer type for variable declarations and class properties (#540)

This infers the type from Flow type annotations for variable
declarations and class properties.

This also moves the logic for setting the `type` for typedefs
to the same type inferrer.

Also infer the type for statements like:

```js
const x = 42;
```

same as:

```js
const x: number = 42;
```
This commit is contained in:
Erik Arvidsson 2016-09-13 12:40:24 -07:00 committed by GitHub
parent a71bb03681
commit 2eff668edf
8 changed files with 217 additions and 101 deletions

View File

@ -20,7 +20,7 @@ var fs = require('fs'),
inferMembership = require('./lib/infer/membership'),
inferReturn = require('./lib/infer/return'),
inferAccess = require('./lib/infer/access'),
inferTypedefType = require('./lib/infer/typedef_type'),
inferType = require('./lib/infer/type'),
formatLint = require('./lib/lint').formatLint,
garbageCollect = require('./lib/garbage_collect'),
lintComments = require('./lib/lint').lintComments,
@ -175,7 +175,7 @@ function buildSync(indexes, options) {
inferProperties(),
inferReturn(),
inferMembership(),
inferTypedefType(),
inferType(),
nest,
options.github && github,
garbageCollect);
@ -245,7 +245,7 @@ function lint(indexes, options, callback) {
inferProperties(),
inferReturn(),
inferMembership(),
inferTypedefType(),
inferType(),
nest);
return expandInputs(indexes, options, function (error, inputs) {

View File

@ -22,9 +22,9 @@ function findTarget(path) {
path = path.declaration;
}
// var x = TARGET;
// var x = init;
if (t.isVariableDeclaration(path)) {
return path.declarations[0].init;
return path.declarations[0];
}
// foo.x = TARGET

View File

@ -162,6 +162,12 @@ function inferParams() {
return shouldSkipInference(function inferParams(comment) {
var node = finders.findTarget(comment.context.ast);
// In case of `/** */ var x = function () {}` findTarget returns
// the declarator.
if (t.isVariableDeclarator(node)) {
node = node.init;
}
if (!t.isFunction(node)) {
return comment;
}

55
lib/infer/type.js Normal file
View File

@ -0,0 +1,55 @@
'use strict';
var finders = require('./finders'),
shouldSkipInference = require('./should_skip_inference'),
flowDoctrine = require('../flow_doctrine'),
t = require('babel-types');
var constTypeMapping = {
'BooleanLiteral': {type: 'BooleanTypeAnnotation'},
'NumericLiteral': {type: 'NumberTypeAnnotation'},
'StringLiteral': {type: 'StringTypeAnnotation'}
};
/**
* Infers type tags by using Flow type annotations
*
* @name inferType
* @param {Object} comment parsed comment
* @returns {Object} comment with type tag inferred
*/
module.exports = function () {
return shouldSkipInference(function inferType(comment) {
if (comment.type) {
return comment;
}
var n = finders.findTarget(comment.context.ast);
if (!n) {
return comment;
}
var type;
switch (n.type) {
case 'VariableDeclarator':
type = n.id.typeAnnotation;
if (!type && comment.kind === 'constant') {
type = constTypeMapping[n.init.type];
}
break;
case 'ClassProperty':
type = n.typeAnnotation;
break;
case 'TypeAlias':
type = n.right;
break;
}
if (type) {
if (t.isTypeAnnotation(type)) {
type = type.typeAnnotation;
}
comment.type = flowDoctrine(type);
}
return comment;
});
};

View File

@ -1,27 +0,0 @@
'use strict';
var shouldSkipInference = require('./should_skip_inference'),
flowDoctrine = require('../flow_doctrine'),
finders = require('./finders');
/**
* Infers the type of typedefs defined by Flow type aliases
*
* @name inferTypedefType
* @param {Object} comment parsed comment
* @returns {Object} comment with type tag inferred
*/
module.exports = function () {
return shouldSkipInference(function inferTypedefType(comment) {
if (comment.kind !== 'typedef') {
return comment;
}
var flowAlias = finders.findTarget(comment.context.ast);
if (flowAlias && flowAlias.type === 'TypeAlias') {
comment.type = flowDoctrine(flowAlias.right);
}
return comment;
});
};

View File

@ -21,6 +21,22 @@ test('inferParams', function (t) {
function f(x) {}
}).params, [{lineNumber: 3, name: 'x', title: 'param'}]);
t.deepEqual(evaluate(function () {
/** Test */
var f = function (x) {};
}).params, [{lineNumber: 3, name: 'x', title: 'param'}]);
t.deepEqual(evaluate('/** Test */ var f = (x) => {}').params,
[{lineNumber: 1, name: 'x', title: 'param'}]);
t.deepEqual(evaluate(function () {
var x = 1,
g = function (y) {},
/** Test */
f = function (x) {};
}).params, [{lineNumber: 5, name: 'x', title: 'param'}]);
t.deepEqual(evaluate('/** Test */ export function f(x) {}').params,
[{lineNumber: 1, name: 'x', title: 'param'}]);

135
test/lib/infer/type.js Normal file
View File

@ -0,0 +1,135 @@
'use strict';
var test = require('tap').test,
parse = require('../../../lib/parsers/javascript'),
inferKind = require('../../../lib/infer/kind')(),
inferType = require('../../../lib/infer/type')();
function toComment(code) {
return parse({
source: code
})[0];
}
function evaluate(code) {
return inferType(inferKind(toComment(code)));
}
test('inferType', function (t) {
t.deepEqual(evaluate(
'/** @typedef {T} V */'
).type, {
name: 'T',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'type V = T'
).type, {
name: 'T',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** @typedef {Array<T>} V */'
).type, {
applications: [
{
name: 'T',
type: 'NameExpression'
}
],
expression: {
name: 'Array',
type: 'NameExpression'
},
type: 'TypeApplication'
});
t.deepEqual(evaluate(
'/** */' +
'type V = Array<T>'
).type, {
applications: [
{
name: 'T',
type: 'NameExpression'
}
],
expression: {
name: 'Array',
type: 'NameExpression'
},
type: 'TypeApplication'
});
t.deepEqual(evaluate(
'/** */' +
'var x: number'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'let x: number'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'const x: number = 42;'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'let x,' +
'/** */' +
'y: number'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'class C {' +
'/** */' +
'x: number;' +
'}'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'const x = 42;'
).type, {
name: 'number',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'const x = "abc";'
).type, {
name: 'string',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'const x = true;'
).type, {
name: 'boolean',
type: 'NameExpression'
});
t.end();
});

View File

@ -1,69 +0,0 @@
'use strict';
var test = require('tap').test,
parse = require('../../../lib/parsers/javascript'),
inferKind = require('../../../lib/infer/kind')(),
inferTypedefType = require('../../../lib/infer/typedef_type')();
function toComment(code) {
return parse({
source: code
})[0];
}
function evaluate(code) {
return inferTypedefType(inferKind(toComment(code)));
}
test('inferTypedefType', function (t) {
t.deepEqual(evaluate(
'/** @typedef {T} V */'
).type, {
name: 'T',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** */' +
'type V = T'
).type, {
name: 'T',
type: 'NameExpression'
});
t.deepEqual(evaluate(
'/** @typedef {Array<T>} V */'
).type, {
applications: [
{
name: 'T',
type: 'NameExpression'
}
],
expression: {
name: 'Array',
type: 'NameExpression'
},
type: 'TypeApplication'
});
t.deepEqual(evaluate(
'/** */' +
'type V = Array<T>'
).type, {
applications: [
{
name: 'T',
type: 'NameExpression'
}
],
expression: {
name: 'Array',
type: 'NameExpression'
},
type: 'TypeApplication'
});
t.end();
});