From e2915dc19cf4b9dca42865f712b0b41f1304b21a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 10 Apr 2017 13:14:04 -0400 Subject: [PATCH] feat(core): Support Flow interface declarations * Add support for interface declarations * Support exported interfaces/type aliases * Update test fixtures * Add interface type, fix test * Fix comment style and typo --- declarations/comment.js | 3 +- lib/infer/augments.js | 45 +++++-- lib/infer/finders.js | 16 --- lib/infer/kind.js | 2 + lib/infer/properties.js | 14 ++- test/fixture/interface.input.js | 7 ++ test/fixture/interface.output.json | 132 +++++++++++++++++++++ test/fixture/interface.output.md | 16 +++ test/fixture/interface.output.md.json | 163 ++++++++++++++++++++++++++ 9 files changed, 364 insertions(+), 34 deletions(-) create mode 100644 test/fixture/interface.input.js create mode 100644 test/fixture/interface.output.json create mode 100644 test/fixture/interface.output.md create mode 100644 test/fixture/interface.output.md.json diff --git a/declarations/comment.js b/declarations/comment.js index 1f8924f4b..a876bd6e0 100644 --- a/declarations/comment.js +++ b/declarations/comment.js @@ -98,7 +98,8 @@ declare type Kind = 'class' | 'mixin' | 'module' | 'namespace' | - 'typedef'; + 'typedef' | + 'interface'; declare type Comment = { errors: Array, diff --git a/lib/infer/augments.js b/lib/infer/augments.js index 0d4920b29..474ca1fb8 100644 --- a/lib/infer/augments.js +++ b/lib/infer/augments.js @@ -3,7 +3,7 @@ /* @flow */ var generate = require('babel-generator').default, - findClass = require('./finders').findClass; + findTarget = require('./finders').findTarget; /** * Infers an `augments` tag from an ES6 class declaration @@ -16,18 +16,37 @@ function inferAugments(comment/*: Comment */) { return comment; } - var path = findClass(comment.context.ast); - - /* - * A superclass can be a single name, like React, - * or a MemberExpression like React.Component, - * so we generate code from the AST rather than assuming - * we can access a name like `path.node.superClass.name` - */ - if (path && path.node.superClass) { - comment.augments.push({ - title: 'augments', - name: generate(path.node.superClass).code + var path = findTarget(comment.context.ast); + + if (!path) { + return comment; + } + + if (path.isClass()) { + /* + * A superclass can be a single name, like React, + * or a MemberExpression like React.Component, + * so we generate code from the AST rather than assuming + * we can access a name like `path.node.superClass.name` + */ + if (path.node.superClass) { + comment.augments.push({ + title: 'augments', + name: generate(path.node.superClass).code + }); + } + } else if (path.isInterfaceDeclaration()) { + /* + * extends is an array of interface identifiers or + * qualified type identifiers, so we generate code + * from the AST rather than assuming we can acces + * a name. + */ + path.node.extends.forEach(node => { + comment.augments.push({ + title: 'extends', + name: generate(node).code + }); }); } diff --git a/lib/infer/finders.js b/lib/infer/finders.js index 8fd63f55d..6fd56fb5d 100644 --- a/lib/infer/finders.js +++ b/lib/infer/finders.js @@ -38,20 +38,4 @@ function findTarget(path/*: Object */) { return path.node && path; } -/** - * Try to find a JavaScript class that this comment refers to, - * whether an expression in an assignment, or a declaration. - * - * @param {Object} path abstract syntax tree path - * @returns {?Object} ast path, if one is found. - * @private - */ -function findClass(path/*: Object*/) { - var target = findTarget(path); - if (target && (target.isClassDeclaration() || target.isClassExpression())) { - return target; - } -} - module.exports.findTarget = findTarget; -module.exports.findClass = findClass; diff --git a/lib/infer/kind.js b/lib/infer/kind.js index d058899fc..8bd45f876 100644 --- a/lib/infer/kind.js +++ b/lib/infer/kind.js @@ -32,6 +32,8 @@ function inferKind(comment/*: Comment*/) { } } else if (t.isTypeAlias(node)) { comment.kind = 'typedef'; + } else if (t.isInterfaceDeclaration(node)) { + comment.kind = 'interface'; } else if (t.isVariableDeclaration(node)) { if (node.kind === 'const') { comment.kind = 'constant'; diff --git a/lib/infer/properties.js b/lib/infer/properties.js index 4b6112d15..baed1fac5 100644 --- a/lib/infer/properties.js +++ b/lib/infer/properties.js @@ -2,8 +2,8 @@ 'use strict'; /* @flow */ -var t = require('babel-types'), - flowDoctrine = require('../flow_doctrine'); +var flowDoctrine = require('../flow_doctrine'), + findTarget = require('./finders').findTarget; function prefixedName(name, prefix) { if (prefix.length) { @@ -56,8 +56,14 @@ function inferProperties(comment/*: Comment */)/*: Comment */ { } } - if (t.isTypeAlias(comment.context.ast)) { - inferProperties(comment.context.ast.node.right, []); + var path = findTarget(comment.context.ast); + + if (path) { + if (path.isTypeAlias()) { + inferProperties(path.node.right, []); + } else if (path.isInterfaceDeclaration()) { + inferProperties(path.node.body, []); + } } return comment; diff --git a/test/fixture/interface.input.js b/test/fixture/interface.input.js new file mode 100644 index 000000000..5389c6b24 --- /dev/null +++ b/test/fixture/interface.input.js @@ -0,0 +1,7 @@ +/** + * This is my interface. + */ +interface Foo extends Bar, Baz { + prop1: number; + prop2: string; +} diff --git a/test/fixture/interface.output.json b/test/fixture/interface.output.json new file mode 100644 index 000000000..013e347e5 --- /dev/null +++ b/test/fixture/interface.output.json @@ -0,0 +1,132 @@ +[ + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This is my interface.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 7, + "column": 1 + } + } + }, + "augments": [ + { + "title": "extends", + "name": "Bar" + }, + { + "title": "extends", + "name": "Baz" + } + ], + "errors": [], + "examples": [], + "params": [], + "properties": [ + { + "title": "property", + "name": "prop1", + "lineNumber": 5, + "type": { + "type": "NameExpression", + "name": "number" + } + }, + { + "title": "property", + "name": "prop2", + "lineNumber": 6, + "type": { + "type": "NameExpression", + "name": "string" + } + } + ], + "returns": [], + "sees": [], + "throws": [], + "todos": [], + "name": "Foo", + "kind": "interface", + "members": { + "global": [], + "inner": [], + "instance": [], + "events": [], + "static": [] + }, + "path": [ + { + "name": "Foo", + "kind": "interface" + } + ], + "namespace": "Foo" + } +] \ No newline at end of file diff --git a/test/fixture/interface.output.md b/test/fixture/interface.output.md new file mode 100644 index 000000000..7ef9cf99d --- /dev/null +++ b/test/fixture/interface.output.md @@ -0,0 +1,16 @@ + + +### Table of Contents + +- [Foo](#foo) + +## Foo + +**Extends Bar, Baz** + +This is my interface. + +**Properties** + +- `prop1` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** +- `prop2` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** diff --git a/test/fixture/interface.output.md.json b/test/fixture/interface.output.md.json new file mode 100644 index 000000000..96610ee59 --- /dev/null +++ b/test/fixture/interface.output.md.json @@ -0,0 +1,163 @@ +{ + "type": "root", + "children": [ + { + "type": "html", + "value": "" + }, + { + "depth": 2, + "type": "heading", + "children": [ + { + "type": "text", + "value": "Foo" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "Extends " + }, + { + "type": "text", + "value": "Bar, Baz" + } + ] + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "This is my interface.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 22, + "offset": 21 + }, + "indent": [] + } + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "Properties" + } + ] + }, + { + "ordered": false, + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "prop1" + }, + { + "type": "text", + "value": " " + }, + { + "type": "strong", + "children": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number", + "type": "link", + "children": [ + { + "type": "text", + "value": "number" + } + ] + } + ] + }, + { + "type": "text", + "value": " " + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "prop2" + }, + { + "type": "text", + "value": " " + }, + { + "type": "strong", + "children": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String", + "type": "link", + "children": [ + { + "type": "text", + "value": "string" + } + ] + } + ] + }, + { + "type": "text", + "value": " " + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file