From 2eff668edfcd852223694faabf26991f73cd167f Mon Sep 17 00:00:00 2001 From: Erik Arvidsson Date: Tue, 13 Sep 2016 12:40:24 -0700 Subject: [PATCH] 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; ``` --- index.js | 6 +- lib/infer/finders.js | 4 +- lib/infer/params.js | 6 ++ lib/infer/type.js | 55 ++++++++++++++ lib/infer/typedef_type.js | 27 ------- test/lib/infer/params.js | 16 ++++ test/lib/infer/type.js | 135 +++++++++++++++++++++++++++++++++ test/lib/infer/typedef_type.js | 69 ----------------- 8 files changed, 217 insertions(+), 101 deletions(-) create mode 100644 lib/infer/type.js delete mode 100644 lib/infer/typedef_type.js create mode 100644 test/lib/infer/type.js delete mode 100644 test/lib/infer/typedef_type.js diff --git a/index.js b/index.js index 180f3d9..570a5a9 100644 --- a/index.js +++ b/index.js @@ -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) { diff --git a/lib/infer/finders.js b/lib/infer/finders.js index 791cb7a..394f24c 100644 --- a/lib/infer/finders.js +++ b/lib/infer/finders.js @@ -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 diff --git a/lib/infer/params.js b/lib/infer/params.js index 31278ac..511a3a9 100644 --- a/lib/infer/params.js +++ b/lib/infer/params.js @@ -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; } diff --git a/lib/infer/type.js b/lib/infer/type.js new file mode 100644 index 0000000..d9b0004 --- /dev/null +++ b/lib/infer/type.js @@ -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; + }); +}; diff --git a/lib/infer/typedef_type.js b/lib/infer/typedef_type.js deleted file mode 100644 index b4cb105..0000000 --- a/lib/infer/typedef_type.js +++ /dev/null @@ -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; - }); -}; diff --git a/test/lib/infer/params.js b/test/lib/infer/params.js index 4f48dae..b318918 100644 --- a/test/lib/infer/params.js +++ b/test/lib/infer/params.js @@ -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'}]); diff --git a/test/lib/infer/type.js b/test/lib/infer/type.js new file mode 100644 index 0000000..6777d79 --- /dev/null +++ b/test/lib/infer/type.js @@ -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} V */' + ).type, { + applications: [ + { + name: 'T', + type: 'NameExpression' + } + ], + expression: { + name: 'Array', + type: 'NameExpression' + }, + type: 'TypeApplication' + }); + + t.deepEqual(evaluate( + '/** */' + + 'type V = Array' + ).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(); +}); diff --git a/test/lib/infer/typedef_type.js b/test/lib/infer/typedef_type.js deleted file mode 100644 index 070ec5e..0000000 --- a/test/lib/infer/typedef_type.js +++ /dev/null @@ -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} V */' - ).type, { - applications: [ - { - name: 'T', - type: 'NameExpression' - } - ], - expression: { - name: 'Array', - type: 'NameExpression' - }, - type: 'TypeApplication' - }); - - t.deepEqual(evaluate( - '/** */' + - 'type V = Array' - ).type, { - applications: [ - { - name: 'T', - type: 'NameExpression' - } - ], - expression: { - name: 'Array', - type: 'NameExpression' - }, - type: 'TypeApplication' - }); - - - t.end(); -});