babel-plugin-transform-flow-to-gen
Advanced tools
Comparing version 0.0.13 to 0.0.14-0
@@ -17,9 +17,4 @@ 'use strict'; | ||
var isTopLevelExport = function isTopLevelExport(path) { | ||
return !!(t.isExportDefaultDeclaration(path.parentPath) || t.isExportNamedDeclaration(path.parentPath) || t.isVariableDeclarator(path.parentPath) && t.isVariableDeclaration(path.parentPath.parentPath) && isTopLevelExport(path.parentPath.parentPath)); | ||
}; | ||
var walkToRoot = function walkToRoot(path) { | ||
while (!t.isProgram(path.parentPath)) { | ||
// eslint-disable-next-line no-param-reassign | ||
var walkToScope = function walkToScope(path) { | ||
while (!t.SCOPABLE_TYPES.includes(path.parentPath.type)) { | ||
path = path.parentPath; | ||
@@ -31,2 +26,8 @@ } | ||
var nameCount = 0; | ||
var nextName = function nextName() { | ||
nameCount += 1; | ||
return _GEN_ID2.default + '_EXP_' + nameCount + '__'; | ||
}; | ||
return { | ||
@@ -37,16 +38,17 @@ inherits: require('babel-plugin-syntax-flow'), | ||
ImportDeclaration: function ImportDeclaration(path) { | ||
if (path.node.importKind === 'type') { | ||
// eslint-disable-next-line no-param-reassign | ||
path.node.importKind = 'value'; | ||
var node = path.node; | ||
if (node.importKind === 'type') { | ||
node.importKind = 'value'; | ||
} | ||
}, | ||
ExportNamedDeclaration: function ExportNamedDeclaration(path) { | ||
if (path.node.exportKind === 'type') { | ||
var _path$node = path.node, | ||
declaration = _path$node.declaration, | ||
specifiers = _path$node.specifiers; | ||
var node = path.node; | ||
if (node.exportKind === 'type') { | ||
var declaration = node.declaration; | ||
path.node.exportKind = 'value'; | ||
node.exportKind = 'value'; | ||
if (declaration) { | ||
@@ -65,3 +67,2 @@ var namedExport = { | ||
var node = path.node; | ||
var ast = (0, _transformType2.default)(node.id.name, node.right, node.typeParameters); | ||
@@ -71,25 +72,58 @@ path.replaceWithMultiple(ast); | ||
FunctionDeclaration: function FunctionDeclaration(path) { | ||
if (allParamsAreTyped(path.node) && isTopLevelExport(path)) { | ||
var name = path.node.id.name; | ||
var fn = (0, _transformFunction2.default)(name, path.node.params, path.node.typeParameters); | ||
var root = walkToRoot(path); | ||
root.insertAfter(fn); | ||
var node = path.node; | ||
if (!allParamsAreTyped(node)) { | ||
return; | ||
} | ||
var name = node.id.name; | ||
var fn = (0, _transformFunction2.default)(name, node.params, node.typeParameters); | ||
var root = walkToScope(path); | ||
var nodes = [root.node].concat(fn); | ||
root.replaceWithMultiple(nodes); | ||
}, | ||
FunctionExpression: function FunctionExpression(path) { | ||
if (allParamsAreTyped(path.node) && isTopLevelExport(path) && t.isVariableDeclarator(path.parentPath)) { | ||
var node = path.node; | ||
if (!allParamsAreTyped(node)) { | ||
return; | ||
} | ||
if (t.isVariableDeclarator(path.parentPath)) { | ||
var name = path.parentPath.node.id.name; | ||
var fn = (0, _transformFunction2.default)(name, path.node.params, path.node.typeParameters); | ||
var root = walkToRoot(path); | ||
root.insertAfter(fn); | ||
var fn = (0, _transformFunction2.default)(name, node.params, node.typeParameters); | ||
var root = walkToScope(path); | ||
var nodes = [root.node].concat(fn); | ||
root.replaceWithMultiple(nodes); | ||
} | ||
if (t.isReturnStatement(path.parentPath)) { | ||
node.id = node.id || t.identifier(nextName()); | ||
var _name = node.id.name; | ||
var _fn = (0, _transformFunction2.default)(_name, node.params, node.typeParameters); | ||
var _root = path.parentPath; | ||
var _nodes = [node].concat(_fn).concat(t.returnStatement(t.identifier(_name))); | ||
_root.replaceWithMultiple(_nodes); | ||
} | ||
}, | ||
// just transform to function expression to avoid dealing with more AST semantics | ||
ArrowFunctionExpression: function ArrowFunctionExpression(path) { | ||
if (allParamsAreTyped(path.node) && isTopLevelExport(path) && !t.isCallExpression(path.parentPath)) { | ||
var name = path.parentPath.node.id.name; | ||
var fn = (0, _transformFunction2.default)(name, path.node.params, path.node.typeParameters); | ||
var root = walkToRoot(path); | ||
root.insertAfter(fn); | ||
var node = path.node; | ||
var id = t.identifier(nextName()); | ||
var params = node.params; | ||
var body = node.body; | ||
if (!t.isBlockStatement(body)) { | ||
body = t.blockStatement([t.returnStatement(body)]); | ||
} | ||
var exp = t.functionExpression(id, params, body); | ||
exp.typeParameters = node.typeParameters; | ||
path.replaceWith(exp); | ||
} | ||
@@ -100,2 +134,6 @@ } | ||
var _GEN_ID = require('./GEN_ID'); | ||
var _GEN_ID2 = _interopRequireDefault(_GEN_ID); | ||
var _transformType = require('./transformType'); | ||
@@ -102,0 +140,0 @@ |
@@ -1,2 +0,2 @@ | ||
'use strict'; | ||
"use strict"; | ||
@@ -17,15 +17,15 @@ Object.defineProperty(exports, "__esModule", { | ||
Array: function Array(params) { | ||
return { type: 'array', elementType: params[0] }; | ||
return { type: "array", elementType: params[0] }; | ||
}, | ||
Object: function Object(params) { | ||
return { type: 'object', members: {}, indexers: [] }; | ||
Object: function Object() { | ||
return { type: "object", members: {}, indexers: [] }; | ||
}, | ||
$Gen: function $Gen(params) { | ||
return { type: 'generator', typeAlias: params[0], caller: params[1].name }; | ||
return { type: "generator", typeAlias: params[0], caller: params[1].name }; | ||
}, | ||
$Keys: function $Keys(params) { | ||
return { type: 'typeAliasKeys', typeAlias: params[0] }; | ||
return { type: "typeAliasKeys", typeAlias: params[0] }; | ||
}, | ||
$Shape: function $Shape(params) { | ||
return { type: 'typeAliasShape', typeAlias: params[0] }; | ||
return { type: "typeAliasShape", typeAlias: params[0] }; | ||
}, | ||
@@ -40,7 +40,7 @@ $Subtype: function $Subtype(params) { | ||
var type = path.type.replace('TypeAnnotation', '').toLowerCase(); | ||
var type = path.type.replace("TypeAnnotation", "").toLowerCase(); | ||
var base = { type: type, optional: optional }; | ||
switch (type) { | ||
case 'generic': | ||
case "generic": | ||
{ | ||
@@ -55,9 +55,9 @@ var typeParameters = path.typeParameters; | ||
if (Boolean(SPECIAL_GENERICS[name])) { | ||
if (SPECIAL_GENERICS[name]) { | ||
return _extends({}, SPECIAL_GENERICS[name](args), { optional: optional }); | ||
} else { | ||
return { type: 'typeAlias', optional: optional, name: name, args: args }; | ||
} | ||
return { type: "typeAlias", optional: optional, name: name, args: args }; | ||
} | ||
case 'object': | ||
case "object": | ||
{ | ||
@@ -72,3 +72,3 @@ var members = path.properties.reduce(function (acc, prop) { | ||
var indexers = path.indexers.reduce(function (acc, index) { | ||
if (index.id.name !== 'key') { | ||
if (index.id.name !== "key") { | ||
return acc; | ||
@@ -90,21 +90,21 @@ } | ||
} | ||
case 'array': | ||
case "array": | ||
return _extends({}, base, { elementType: typeAST(path.elementType) }); | ||
case 'nullliteral': | ||
return { type: 'literal', optional: optional, value: null }; | ||
case 'booleanliteral': | ||
case 'numericliteral': | ||
case 'stringliteral': | ||
return { type: 'literal', optional: optional, value: path.value }; | ||
case 'intersection': | ||
case 'tuple': | ||
case 'union': | ||
case "nullliteral": | ||
return { type: "literal", optional: optional, value: null }; | ||
case "booleanliteral": | ||
case "numericliteral": | ||
case "stringliteral": | ||
return { type: "literal", optional: optional, value: path.value }; | ||
case "intersection": | ||
case "tuple": | ||
case "union": | ||
return _extends({}, base, { entries: path.types.map(function (p) { | ||
return typeAST(p); | ||
}) }); | ||
case 'nullable': | ||
case "nullable": | ||
return _extends({}, base, { value: typeAST(path.typeAnnotation) }); | ||
case 'any': | ||
case 'mixed': | ||
return { type: 'garbage', optional: optional }; | ||
case "any": | ||
case "mixed": | ||
return { type: "garbage", optional: optional }; | ||
// case `void`: | ||
@@ -111,0 +111,0 @@ // case `function`: |
{ | ||
"name": "babel-plugin-transform-flow-to-gen", | ||
"version": "0.0.13", | ||
"version": "0.0.14-0", | ||
"description": "Turn flow definitions into generator functions", | ||
@@ -10,3 +10,5 @@ "main": "lib/index.js", | ||
"test": "jest", | ||
"test:watch": "DEBUG=1 npm run test -- --watchAll --noCache", | ||
"lint": "eslint --fix --ext .js src/", | ||
"precommit": "npm run test && npm run lint", | ||
"prepublish": "rm -rf lib && babel src --out-dir lib --ignore __test__,test.js", | ||
@@ -18,3 +20,2 @@ "postpublish": "rm -rf lib", | ||
"babel-plugin-syntax-flow": "^6.18.0", | ||
"eslint-plugin-flowtype": "^2.29.2", | ||
"testcheck": "^0.1.4" | ||
@@ -34,5 +35,9 @@ }, | ||
"eslint-config-airbnb-base": "^11.0.0", | ||
"eslint-plugin-flowtype": "^2.29.2", | ||
"eslint-plugin-import": "^2.2.0", | ||
"husky": "^0.13.2", | ||
"jest": "^18.1.0", | ||
"np": "^2.12.0" | ||
"np": "^2.12.0", | ||
"prettier": "^0.22.0", | ||
"prettier-eslint": "^4.3.2" | ||
}, | ||
@@ -59,5 +64,14 @@ "babel": { | ||
"env": { | ||
"node": true, | ||
"browser": true, | ||
"jest": true | ||
}, | ||
"prettierOptions": { | ||
"singleQuote": true, | ||
"brackSpacing": false, | ||
"trailingComma": "all", | ||
"parser": "flow", | ||
"printWidth": 100, | ||
"write": "**/*.js" | ||
}, | ||
"rules": { | ||
@@ -64,0 +78,0 @@ "arrow-parens": [ |
# Flow to gen | ||
_Transforms flow type aliases into generators for property based testing_ | ||
_Transforms Flow annotations into TestCheck generators for randomized testing_ | ||
## Motivation | ||
This Babel plugin attempts to alleviate the manual task of creating and maintaining fixtures/mocks by transforming all Flowtype type aliases | ||
into generator functions for mock data. Additionally, it provides a framework for automatically generating random input for | ||
typed functions and React components. If you're unfamiliar with generative or property based testing, please check out an implementation of | ||
[Quickcheck](https://en.wikipedia.org/wiki/QuickCheck) in your language of choice. Also look at | ||
[testcheck.js](https://github.com/leebyron/testcheck-js) which is 100% compatible and wrapped by the runtime of this library. | ||
Read [this blog post](https://medium.com/@gabescholz/randomized-testing-in-javascript-without-lifting-a-finger-8d616d7048af) | ||
By running this Babel transform on your code: | ||
- all type aliases are transformed in testcheck.js generators | ||
- all typed functions can immediately retrieve randomly generated inputs | ||
- all typed React components can immediately retrieve randomly generated props | ||
TODO: | ||
- handle TypeofTypeAnnotation | ||
- handle ExistentialTypeAnnotation | ||
- handle recursive types | ||
- handle Flow globally defined types | ||
- handle React components | ||
## Demo? | ||
@@ -29,65 +13,27 @@ | ||
## Getting Started | ||
## Installing | ||
`babel-plugin-transform-flow-to-gen` transforms your type aliases into functions | ||
that create testcheck.js generators. | ||
## Usage | ||
```js | ||
import {sample, types} from 'babel-plugin-transform-flow-to-gen/api'; | ||
yarn add babel-plugin-transform-flow-to-gen | ||
``` | ||
type Person<T> = { | ||
firstName: string, | ||
lastName: string, | ||
age: T | ||
} | ||
and add it to your `.babelrc` | ||
// use the generator static member to create samples | ||
const personGen = Person(types.number()); | ||
sample(personGen, 20); | ||
// [{ | ||
// "firstName": "9OY3o", | ||
// "lastName": "fB", | ||
// "age": 0 | ||
// }, | ||
// { | ||
// "firstName": "8Hft5LwfK", | ||
// "lastName": "51Vnn54vb9xHO", | ||
// "age": 2 | ||
// }, | ||
// { | ||
// "firstName": "7i59Sr35GAJiv626uiV", | ||
// "lastName": "s7GIgEf", | ||
// "age": 3 | ||
// }, | ||
// { | ||
// "firstName": "Mys89F65i36n921", | ||
// "lastName": "", | ||
// "age": 1 | ||
// }, ...] | ||
function setFirstName(person: Person<number>, firstName: string) { | ||
// ... | ||
```json | ||
{ | ||
"presets": ["es-2015"], | ||
"plugins": ["syntax-flow"], | ||
"env": { | ||
"development": { | ||
"plugins": ["strip-flow-types"] | ||
}, | ||
"test": { | ||
"plugins": ["flow-to-gen", "strip-flow-types"] | ||
} | ||
} | ||
} | ||
// returns an array of args for setFirstName | ||
sample(setFirstName.asGenerator()); | ||
// [ | ||
// [{ | ||
// "firstName": "3o", | ||
// "lastName": "j467DA", | ||
// "age": 0 | ||
// }, "f02j"] | ||
// , ...] | ||
``` | ||
## Installing | ||
```js | ||
yarn add babel-plugin-transform-flow-to-gen | ||
``` | ||
## License | ||
MIT |
2
634
30477
18
19
39
- Removedeslint-plugin-flowtype@^2.29.2
- Removed@eslint-community/eslint-utils@4.4.1(transitive)
- Removed@eslint-community/regexpp@4.12.1(transitive)
- Removed@eslint/config-array@0.19.1(transitive)
- Removed@eslint/core@0.10.0(transitive)
- Removed@eslint/eslintrc@3.2.0(transitive)
- Removed@eslint/js@9.18.0(transitive)
- Removed@eslint/object-schema@2.1.5(transitive)
- Removed@eslint/plugin-kit@0.2.5(transitive)
- Removed@humanfs/core@0.19.1(transitive)
- Removed@humanfs/node@0.16.6(transitive)
- Removed@humanwhocodes/module-importer@1.0.1(transitive)
- Removed@humanwhocodes/retry@0.3.10.4.1(transitive)
- Removed@types/estree@1.0.6(transitive)
- Removed@types/json-schema@7.0.15(transitive)
- Removedacorn@8.14.0(transitive)
- Removedacorn-jsx@5.3.2(transitive)
- Removedajv@6.12.6(transitive)
- Removedansi-styles@4.3.0(transitive)
- Removedargparse@2.0.1(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedcallsites@3.1.0(transitive)
- Removedchalk@4.1.2(transitive)
- Removedcolor-convert@2.0.1(transitive)
- Removedcolor-name@1.1.4(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedcross-spawn@7.0.6(transitive)
- Removeddebug@4.4.0(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removedescape-string-regexp@4.0.0(transitive)
- Removedeslint@9.18.0(transitive)
- Removedeslint-plugin-flowtype@2.50.3(transitive)
- Removedeslint-scope@8.2.0(transitive)
- Removedeslint-visitor-keys@3.4.34.2.0(transitive)
- Removedespree@10.3.0(transitive)
- Removedesquery@1.6.0(transitive)
- Removedesrecurse@4.3.0(transitive)
- Removedestraverse@5.3.0(transitive)
- Removedesutils@2.0.3(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedfile-entry-cache@8.0.0(transitive)
- Removedfind-up@5.0.0(transitive)
- Removedflat-cache@4.0.1(transitive)
- Removedflatted@3.3.2(transitive)
- Removedglob-parent@6.0.2(transitive)
- Removedglobals@14.0.0(transitive)
- Removedhas-flag@4.0.0(transitive)
- Removedignore@5.3.2(transitive)
- Removedimport-fresh@3.3.0(transitive)
- Removedimurmurhash@0.1.4(transitive)
- Removedis-extglob@2.1.1(transitive)
- Removedis-glob@4.0.3(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjs-yaml@4.1.0(transitive)
- Removedjson-buffer@3.0.1(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stable-stringify-without-jsonify@1.0.1(transitive)
- Removedkeyv@4.5.4(transitive)
- Removedlevn@0.4.1(transitive)
- Removedlocate-path@6.0.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedlodash.merge@4.6.2(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedms@2.1.3(transitive)
- Removednatural-compare@1.4.0(transitive)
- Removedoptionator@0.9.4(transitive)
- Removedp-limit@3.1.0(transitive)
- Removedp-locate@5.0.0(transitive)
- Removedparent-module@1.0.1(transitive)
- Removedpath-exists@4.0.0(transitive)
- Removedpath-key@3.1.1(transitive)
- Removedprelude-ls@1.2.1(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedresolve-from@4.0.0(transitive)
- Removedshebang-command@2.0.0(transitive)
- Removedshebang-regex@3.0.0(transitive)
- Removedstrip-json-comments@3.1.1(transitive)
- Removedsupports-color@7.2.0(transitive)
- Removedtype-check@0.4.0(transitive)
- Removeduri-js@4.4.1(transitive)
- Removedwhich@2.0.2(transitive)
- Removedword-wrap@1.2.5(transitive)
- Removedyocto-queue@0.1.0(transitive)