Repository: brigand/babel-plugin-flow-react-proptypes
Pull request #175 (2018-01-31 14:31:42)

Interface support
Hi.

This is an unfinished pull request addressing #173 
Added unit test seems to work fine (I've just delegated `InterfaceDeclaration` to `ObjectTypeAnnotation`.
What I haven't been able to update yet is creating the export...

coveralls: 
[![Coverage Status](https://coveralls.io/builds/15307337/badge)](https://coveralls.io/builds/15307337)

Coverage increased (+0.04%) to 91.876% when pulling **42bec0c1a4b6cf5779a7733084c7c7775e3dcc83 on kubijo:interfaceSupport** into **769565f040aa4b3caa32d8cfb88c539b46c90830 on brigand:master**.

brigand: Just a few minutes ago I extracted the code to make export statements into a function `addExportTypeDecl`. Usage: `addExportTypeDecl(path, 'bpfrpt_proptype_Something')`. You create the name with `getExportNameForType` like `getExportNameForType('Something')`, which you also use to create the local variable which will be exported.
kubijo: I see.
I've rebased my changes and now I'd like to finish this up with that export stuff.

kubijo: Sorry, for the force pushes ... now it seems to work fine even when I test it on my project.
However now there are some failing tests ...

Patch:
diff --git a/src/convertToPropTypes.js b/src/convertToPropTypes.js
index f109751..f51d0ba 100644
--- a/src/convertToPropTypes.js
+++ b/src/convertToPropTypes.js
@@ -89,7 +89,6 @@ export default function convertToPropTypes(node, importedTypes, internalTypes) {
       for (let i = 0; i < typeParams.length; i++) {
         typeParamMapping[typeParams[i]] = types[i];
       }
-      // console.log(node.right, typeParamMapping);
       return convertGenericToPropTypes(node.right, typeParamMapping, importedTypes, internalTypes);
     };
   }
@@ -97,6 +96,10 @@ export default function convertToPropTypes(node, importedTypes, internalTypes) {
     node = node.right;
   }
 
+  if (node.type === 'InterfaceDeclaration') {
+    node = node.body;
+  }
+
   if (node.type === 'ObjectTypeAnnotation') {
     const properties = [];
 
@@ -187,7 +190,7 @@ export default function convertToPropTypes(node, importedTypes, internalTypes) {
       resultPropType = {type: 'any'};
     }
     else if (requiresRuntimeMerge.length === 0) {
-      resultPropType = {'type': 'shape', properties: mergedProperties};
+      resultPropType = {type: 'shape', properties: mergedProperties};
     }
     else {
       // TODO: properties may be a misnomer - that probably means a list of object
diff --git a/src/index.js b/src/index.js
index e44c0b8..6250c6c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -331,14 +331,14 @@ module.exports = function flowReactPropTypes(babel) {
     }
   };
 
-    /**
-     * Visitor for functions.
-     *
-     * Determines if a function is a functional react component and
-     * inserts the proptypes and contexttypes AST via `annotate`.
-     *
-     * @param path
-     */
+  /**
+   * Visitor for functions.
+   *
+   * Determines if a function is a functional react component and
+   * inserts the proptypes and contexttypes AST via `annotate`.
+   *
+   * @param path
+   */
   const functionVisitor = path => {
     if (!isFunctionalReactComponent(path)) {
       return;
@@ -398,7 +398,7 @@ module.exports = function flowReactPropTypes(babel) {
         const directives = path.node.directives;
         if(directives && directives.length)  {
           const directive = directives[0];
-          if (directive.value && directive.value.value == SUPPRESS_STRING) {
+          if (directive.value && directive.value.value === SUPPRESS_STRING) {
             suppress = true;
           }
         }
@@ -410,9 +410,9 @@ module.exports = function flowReactPropTypes(babel) {
           }
         }
       },
-      TypeAlias(path) {
+      "TypeAlias|InterfaceDeclaration"(path) {
         if (suppress) return;
-        $debug('TypeAlias found');
+        $debug('TypeAlias/InterfaceDeclaration found');
 
         const typeAliasName = path.node.id.name;
         if (!typeAliasName) {
@@ -520,14 +520,14 @@ module.exports = function flowReactPropTypes(babel) {
 
       // See issue:
       /**
-         * Processes exported type aliases.
-         *
-         * This function also adds something to the AST directly, instead
-         * of invoking annotate.
-         *
-         * @param path
-         * @constructor
-         */
+       * Processes exported type aliases.
+       *
+       * This function also adds something to the AST directly, instead
+       * of invoking annotate.
+       *
+       * @param path
+       * @constructor
+       */
       ExportNamedDeclaration(path) {
         if (suppress) return;
         const {node} = path;
@@ -546,12 +546,16 @@ module.exports = function flowReactPropTypes(babel) {
           return;
         }
 
-
-        if (!node.declaration || node.declaration.type !== 'TypeAlias') {
-          return;
+        let declarationObject = null;
+        if (!node.declaration) return;
+        if (node.declaration.type === 'TypeAlias') {
+          declarationObject = node.declaration.right;
+        }
+        if (node.declaration.type === 'InterfaceDeclaration') {
+          declarationObject = node.declaration.body;
         }
 
-        const declarationObject = node.declaration.right;
+        if (!declarationObject) return;
 
         const name = node.declaration.id.name;
         const propTypes = convertNodeToPropTypes(declarationObject);
@@ -573,7 +577,6 @@ module.exports = function flowReactPropTypes(babel) {
         );
         path.insertBefore(variableDeclarationAst);
 
-        
         if (!omitRuntimeTypeExport) {
           if (path.node[SKIP]) return;
           addExportTypeDecl(path, exportName);
diff --git a/src/makePropTypesAst.js b/src/makePropTypesAst.js
index 704ba0c..004f83b 100644
--- a/src/makePropTypesAst.js
+++ b/src/makePropTypesAst.js
@@ -69,7 +69,7 @@ export function makePropTypesAstForPropTypesAssignment(propTypeData) {
   }
 
   return node;
-};
+}
 
 
 /**
@@ -95,7 +95,7 @@ export function makePropTypesAstForExport(propTypeData) {
     ast = makePropType(propTypeData);
   }
   return ast;
-};
+}
 
 
 function makeAnyPropTypeAST() {
diff --git a/src/util.js b/src/util.js
index a7a62d3..d56a25d 100644
--- a/src/util.js
+++ b/src/util.js
@@ -43,7 +43,7 @@ export function containsReactElement(node) {
       if (matched) {
         path.stop();
         return;
-      };
+      }
 
       const {node} = path;
       const {callee} = node;
@@ -79,4 +79,4 @@ export function hasReactElementTypeAnnotationReturn(node) {
   }
 
   return false;
-};
+}


Test patch:
diff --git a/src/__tests__/__snapshots__/interface-export.js.snap b/src/__tests__/__snapshots__/interface-export.js.snap
new file mode 100644
index 0000000..1d35c82
--- /dev/null
+++ b/src/__tests__/__snapshots__/interface-export.js.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`interface-export 1`] = `
+"\\"use strict\\";
+
+Object.defineProperty(exports, \\"__esModule\\", {
+    value: true
+});
+exports.bpfrpt_proptype_Pager = undefined;
+
+var _propTypes = require(\\"prop-types\\");
+
+var _propTypes2 = _interopRequireDefault(_propTypes);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var bpfrpt_proptype_Pager = {
+    next: _propTypes2.default.func.isRequired,
+    prev: _propTypes2.default.func.isRequired,
+    hasNext: _propTypes2.default.bool.isRequired,
+    hasPrev: _propTypes2.default.bool.isRequired
+};
+exports.bpfrpt_proptype_Pager = bpfrpt_proptype_Pager;"
+`;
diff --git a/src/__tests__/__snapshots__/interface.js.snap b/src/__tests__/__snapshots__/interface.js.snap
new file mode 100644
index 0000000..36fca4b
--- /dev/null
+++ b/src/__tests__/__snapshots__/interface.js.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`import-object 1`] = `
+"\\"use strict\\";
+
+var _propTypes = require(\\"prop-types\\");
+
+var _propTypes2 = _interopRequireDefault(_propTypes);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\\"Cannot call a class as a function\\"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\\"this hasn't been initialised - super() hasn't been called\\"); } return call && (typeof call === \\"object\\" || typeof call === \\"function\\") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== \\"function\\" && superClass !== null) { throw new TypeError(\\"Super expression must either be null or a function, not \\" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var C = function (_React$Component) {
+    _inherits(C, _React$Component);
+
+    function C() {
+        _classCallCheck(this, C);
+
+        return _possibleConstructorReturn(this, (C.__proto__ || Object.getPrototypeOf(C)).apply(this, arguments));
+    }
+
+    return C;
+}(React.Component);
+
+C.propTypes = {
+    pager: _propTypes2.default.shape({
+        next: _propTypes2.default.func.isRequired,
+        prev: _propTypes2.default.func.isRequired,
+        hasNext: _propTypes2.default.bool.isRequired,
+        hasPrev: _propTypes2.default.bool.isRequired
+    }).isRequired
+};"
+`;
diff --git a/src/__tests__/interface-export.js b/src/__tests__/interface-export.js
new file mode 100644
index 0000000..1f6d1eb
--- /dev/null
+++ b/src/__tests__/interface-export.js
@@ -0,0 +1,18 @@
+const babel = require('babel-core');
+const content = `
+export interface Pager {
+    next(number): void,
+    prev(number): void,
+    hasNext: boolean,
+    hasPrev: boolean,
+}
+`;
+
+it('interface-export', () => {
+  const res = babel.transform(content, {
+    babelrc: false,
+    presets: ['es2015', 'stage-1', 'react'],
+    plugins: ['syntax-flow', require('../')],
+  }).code;
+  expect(res).toMatchSnapshot();
+});
diff --git a/src/__tests__/interface.js b/src/__tests__/interface.js
new file mode 100644
index 0000000..73b1966
--- /dev/null
+++ b/src/__tests__/interface.js
@@ -0,0 +1,30 @@
+const babel = require('babel-core');
+const content = `
+type ObjType = {
+    next: null,
+};
+
+interface Pager {
+    next(number): void,
+    prev(number): void,
+    hasNext: boolean,
+    hasPrev: boolean,
+}
+
+type FooProps = {
+    pager: Pager,
+};
+
+class C extends React.Component {
+    props: FooProps
+}
+`;
+
+it('import-object', () => {
+  const res = babel.transform(content, {
+    babelrc: false,
+    presets: ['es2015', 'stage-1', 'react'],
+    plugins: ['syntax-flow', require('../')],
+  }).code;
+  expect(res).toMatchSnapshot();
+});