Repository: brigand/babel-plugin-flow-react-proptypes
Pull request #195 (2018-04-10 05:43:05)

Add support for opaque type aliases
This change supports using opaque type aliases as if they were regular
types, i.e. the generated propTypes code is based on the underlying
type information.

brigand: Thanks for the PR!

Could you add a test for exporting an opaque type? That's where I see this being useful.
coveralls: 
[![Coverage Status](https://coveralls.io/builds/16430748/badge)](https://coveralls.io/builds/16430748)

Coverage increased (+0.03%) to 91.948% when pulling **5c216dc3e9a699422a441b0811d8b61c2fb4a685 on marcind:opaqueType** into **1bab06133d62f3ca27f6319228e4a819783ec35c on brigand:master**.

marcind: Just tested the new version in my app and no more warnings. Release would be awesome.

Patch:
diff --git a/src/convertToPropTypes.js b/src/convertToPropTypes.js
index f4654fb..df190c1 100644
--- a/src/convertToPropTypes.js
+++ b/src/convertToPropTypes.js
@@ -96,6 +96,10 @@ export default function convertToPropTypes(node, importedTypes, internalTypes) {
     node = node.right;
   }
 
+  if (node.type === 'OpaqueType') {
+    node = node.impltype;
+  }
+
   if (node.type === 'InterfaceDeclaration') {
     node = node.body;
   }
diff --git a/src/index.js b/src/index.js
index a2e8e29..efe2fcf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -428,9 +428,9 @@ module.exports = function flowReactPropTypes(babel) {
           }
         }
       },
-      "TypeAlias|InterfaceDeclaration"(path) {
+      "TypeAlias|InterfaceDeclaration|OpaqueType"(path) {
         if (suppress) return;
-        $debug('TypeAlias/InterfaceDeclaration found');
+        $debug('TypeAlias/InterfaceDeclaration/OpaqueType found');
 
         const typeAliasName = path.node.id.name;
         if (!typeAliasName) {
@@ -587,6 +587,9 @@ module.exports = function flowReactPropTypes(babel) {
         if (node.declaration.type === 'TypeAlias') {
           declarationObject = node.declaration.right;
         }
+        if (node.declaration.type === 'OpaqueType') {
+          declarationObject = node.declaration.impltype;
+        }
         if (node.declaration.type === 'InterfaceDeclaration') {
           declarationObject = node.declaration.body;
         }


Test patch:
diff --git a/src/__tests__/__snapshots__/opaque-type-alias-test.js.snap b/src/__tests__/__snapshots__/opaque-type-alias-test.js.snap
new file mode 100644
index 0000000..5b07ae0
--- /dev/null
+++ b/src/__tests__/__snapshots__/opaque-type-alias-test.js.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`opaque-type-alias 1`] = `
+"'use strict';
+
+Object.defineProperty(exports, \\"__esModule\\", {
+  value: true
+});
+exports.bpfrpt_proptype_ExportedStringAlias = undefined;
+
+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 React = require('react');
+
+var bpfrpt_proptype_ExportedStringAlias = _propTypes2.default.string;
+
+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 = {
+  stringAlias: _propTypes2.default.string.isRequired,
+  exportedStringAlias: _propTypes2.default.string.isRequired,
+  optionalStringAlias: _propTypes2.default.string,
+  stringAliasConstraint: _propTypes2.default.string.isRequired,
+  numberAlias: _propTypes2.default.number.isRequired,
+  typeAlias: _propTypes2.default.shape({
+    a: _propTypes2.default.string.isRequired,
+    b: _propTypes2.default.bool
+  }).isRequired,
+  typeReferenceAlias: _propTypes2.default.shape({
+    b: _propTypes2.default.number.isRequired
+  }).isRequired,
+  interfaceReferenceAlias: _propTypes2.default.shape({
+    x: _propTypes2.default.number.isRequired,
+    y: _propTypes2.default.string
+  }).isRequired
+};
+exports.default = C;
+exports.bpfrpt_proptype_ExportedStringAlias = bpfrpt_proptype_ExportedStringAlias;"
+`;
diff --git a/src/__tests__/opaque-type-alias-test.js b/src/__tests__/opaque-type-alias-test.js
new file mode 100644
index 0000000..7b54df0
--- /dev/null
+++ b/src/__tests__/opaque-type-alias-test.js
@@ -0,0 +1,42 @@
+const babel = require('babel-core');
+const content = `
+var React = require('react');
+
+type TypeReference = { b: number };
+interface Interface {
+  x: number,
+  y?: string,
+}
+
+opaque type StringAlias = string;
+export opaque type ExportedStringAlias = string;
+opaque type StringConstraintAlias: string = string;
+opaque type NumberAlias = number;
+opaque type TypeAlias = { a: string, b?: boolean };
+opaque type TypeReferenceAlias = TypeReference;
+opaque type InterfaceReferenceAlias = Interface;
+
+type T = {
+  stringAlias: StringAlias,
+  exportedStringAlias: ExportedStringAlias,
+  optionalStringAlias?: StringAlias,
+  stringAliasConstraint: StringConstraintAlias,
+  numberAlias: NumberAlias,
+  typeAlias: TypeAlias,
+  typeReferenceAlias: TypeReference,
+  interfaceReferenceAlias: InterfaceReferenceAlias,
+}
+
+export default class C extends React.Component {
+  props: T
+}
+`;
+
+it('opaque-type-alias', () => {
+  const res = babel.transform(content, {
+    babelrc: false,
+    presets: ['es2015', 'stage-1', 'react'],
+    plugins: ['syntax-flow', require('../')],
+  }).code;
+  expect(res).toMatchSnapshot();
+});