From 0a97e0eccc4c4e6868a866b391dad1bd5f701852 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 1 Sep 2016 16:32:47 -0400 Subject: [PATCH] Continue to close the gap with flow (#520) * Adds test support for flow difference * New support for FunctionTypeAnnotation * New support for ObjectTypeAnnotation * New support for MixedTypeAnnotation --- lib/flow_doctrine.js | 37 +- test/fixture/sync/flow-types.input.js | 8 + test/fixture/sync/flow-types.output.json | 370 +++++++++++++++++++- test/fixture/sync/flow-types.output.md | 22 +- test/fixture/sync/flow-types.output.md.json | 287 ++++++++++++++- test/lib/flow_doctrine.js | 63 +++- 6 files changed, 759 insertions(+), 28 deletions(-) diff --git a/lib/flow_doctrine.js b/lib/flow_doctrine.js index 9dec9c319..f19e0881c 100644 --- a/lib/flow_doctrine.js +++ b/lib/flow_doctrine.js @@ -1,12 +1,12 @@ var namedTypes = { 'NumberTypeAnnotation': 'number', 'BooleanTypeAnnotation': 'boolean', - 'ObjectTypeAnnotation': 'Object', 'StringTypeAnnotation': 'string' }; var oneToOne = { 'AnyTypeAnnotation': 'AllLiteral', + 'MixedTypeAnnotation': 'AllLiteral', 'NullLiteralTypeAnnotation': 'NullLiteral', 'VoidTypeAnnotation': 'VoidLiteral' }; @@ -17,6 +17,14 @@ var literalTypes = { 'StringLiteralTypeAnnotation': 'StringLiteral' }; +function propertyToField(property) { + return { + type: 'FieldType', + key: property.key.name, + value: flowDoctrine(property.value) + }; +} + /** * Babel parses Flow annotations in JavaScript into AST nodes. documentation.js uses * Babel to parse JavaScript. This method restructures those Babel-generated @@ -71,6 +79,20 @@ function flowDoctrine(type) { applications: [flowDoctrine(type.elementType)] }; + // (y: number) => bool + case 'FunctionTypeAnnotation': + return { + type: 'FunctionType', + params: type.params.map(function (param) { + return { + type: 'ParameterType', + name: param.name.name, + expression: flowDoctrine(param.typeAnnotation) + }; + }), + result: flowDoctrine(type.returnType) + }; + case 'GenericTypeAnnotation': if (type.typeParameters) { return { @@ -83,6 +105,19 @@ function flowDoctrine(type) { }; } + return { + type: 'NameExpression', + name: type.id.name + }; + + case 'ObjectTypeAnnotation': + if (type.properties) { + return { + type: 'RecordType', + fields: type.properties.map(propertyToField) + }; + } + return { type: 'NameExpression', name: type.id.name diff --git a/test/fixture/sync/flow-types.input.js b/test/fixture/sync/flow-types.input.js index 08d73c4d5..cfcc9cb4a 100644 --- a/test/fixture/sync/flow-types.input.js +++ b/test/fixture/sync/flow-types.input.js @@ -52,3 +52,11 @@ function veryImportantTransform( * Function with optional parameter. */ function optionalFunc(x: number = 42) {} + +/** + * Function with object parameter. + */ +function objectParamFn(x: { a: number }) {} + +/** hi */ +function objectParamFn(x: (y:Foo) => Bar) {} diff --git a/test/fixture/sync/flow-types.output.json b/test/fixture/sync/flow-types.output.json index 2cc038d33..35304499f 100644 --- a/test/fixture/sync/flow-types.output.json +++ b/test/fixture/sync/flow-types.output.json @@ -329,8 +329,17 @@ "name": "rgb", "lineNumber": 16, "type": { - "type": "NameExpression", - "name": "Object" + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "hex", + "value": { + "type": "NameExpression", + "name": "string" + } + } + ] }, "properties": [ { @@ -349,8 +358,26 @@ "name": "props", "lineNumber": 19, "type": { - "type": "NameExpression", - "name": "Object" + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "radius", + "value": { + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "x", + "value": { + "type": "NameExpression", + "name": "number" + } + } + ] + } + } + ] }, "properties": [ { @@ -358,8 +385,17 @@ "name": "props.radius", "lineNumber": 20, "type": { - "type": "NameExpression", - "name": "Object" + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "x", + "value": { + "type": "NameExpression", + "name": "number" + } + } + ] }, "properties": [ { @@ -379,8 +415,68 @@ "name": "Point", "kind": "typedef", "type": { - "type": "NameExpression", - "name": "Object" + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "x", + "value": { + "type": "NameExpression", + "name": "number" + } + }, + { + "type": "FieldType", + "key": "y", + "value": { + "type": "NameExpression", + "name": "number" + } + }, + { + "type": "FieldType", + "key": "rgb", + "value": { + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "hex", + "value": { + "type": "NameExpression", + "name": "string" + } + } + ] + } + }, + { + "type": "FieldType", + "key": "props", + "value": { + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "radius", + "value": { + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "x", + "value": { + "type": "NameExpression", + "name": "number" + } + } + ] + } + } + ] + } + } + ] }, "members": { "instance": [], @@ -506,8 +602,36 @@ } ], "type": { - "type": "NameExpression", - "name": "Object" + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "x", + "value": { + "type": "NameExpression", + "name": "number" + } + }, + { + "type": "FieldType", + "key": "y", + "value": { + "type": "NameExpression", + "name": "number" + } + }, + { + "type": "FieldType", + "key": "z", + "value": { + "type": "NullableType", + "expression": { + "type": "NameExpression", + "name": "number" + } + } + } + ] }, "members": { "instance": [], @@ -860,5 +984,231 @@ } ], "namespace": "optionalFunc" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Function with object parameter.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 56, + "column": 0 + }, + "end": { + "line": 58, + "column": 3 + } + }, + "context": { + "loc": { + "start": { + "line": 59, + "column": 0 + }, + "end": { + "line": 59, + "column": 43 + } + } + }, + "errors": [], + "name": "objectParamFn", + "kind": "function", + "params": [ + { + "title": "param", + "name": "x", + "lineNumber": 59, + "type": { + "type": "RecordType", + "fields": [ + { + "type": "FieldType", + "key": "a", + "value": { + "type": "NameExpression", + "name": "number" + } + } + ] + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "objectParamFn", + "kind": "function" + } + ], + "namespace": "objectParamFn" + }, + { + "description": { + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "hi", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + } + } + }, + "tags": [], + "loc": { + "start": { + "line": 61, + "column": 0 + }, + "end": { + "line": 61, + "column": 9 + } + }, + "context": { + "loc": { + "start": { + "line": 62, + "column": 0 + }, + "end": { + "line": 62, + "column": 44 + } + } + }, + "errors": [], + "name": "objectParamFn", + "kind": "function", + "params": [ + { + "title": "param", + "name": "x", + "lineNumber": 62, + "type": { + "type": "FunctionType", + "params": [ + { + "type": "ParameterType", + "name": "y", + "expression": { + "type": "NameExpression", + "name": "Foo" + } + } + ], + "result": { + "type": "NameExpression", + "name": "Bar" + } + } + } + ], + "members": { + "instance": [], + "static": [] + }, + "path": [ + { + "name": "objectParamFn", + "kind": "function" + } + ], + "namespace": "objectParamFn" } ] \ No newline at end of file diff --git a/test/fixture/sync/flow-types.output.md b/test/fixture/sync/flow-types.output.md index 9ea16ca48..49e6256d7 100644 --- a/test/fixture/sync/flow-types.output.md +++ b/test/fixture/sync/flow-types.output.md @@ -23,10 +23,10 @@ A 2D point. - `x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** this is a prop - `y` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** -- `rgb` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** +- `rgb` **{hex: [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)}** - `rgb.hex` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** -- `props` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** - - `props.radius` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** +- `props` **{radius: {x: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)}}** + - `props.radius` **{x: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)}** - `props.radius.x` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** # Two @@ -61,3 +61,19 @@ Function with optional parameter. **Parameters** - `x` **\[[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** (optional, default `42`) + +# objectParamFn + +Function with object parameter. + +**Parameters** + +- `x` **{a: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)}** + +# objectParamFn + +hi + +**Parameters** + +- `x` **function (y: Foo): Bar** diff --git a/test/fixture/sync/flow-types.output.md.json b/test/fixture/sync/flow-types.output.md.json index 1a8303f19..c1a045090 100644 --- a/test/fixture/sync/flow-types.output.md.json +++ b/test/fixture/sync/flow-types.output.md.json @@ -525,15 +525,27 @@ "type": "strong", "children": [ { - "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "text", + "value": "{" + }, + { + "type": "text", + "value": "hex: " + }, + { + "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": "Object" + "value": "string" } ] + }, + { + "type": "text", + "value": "}" } ] }, @@ -607,15 +619,39 @@ "type": "strong", "children": [ { - "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "text", + "value": "{" + }, + { + "type": "text", + "value": "radius: " + }, + { + "type": "text", + "value": "{" + }, + { + "type": "text", + "value": "x: " + }, + { + "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": "Object" + "value": "number" } ] + }, + { + "type": "text", + "value": "}" + }, + { + "type": "text", + "value": "}" } ] }, @@ -647,15 +683,27 @@ "type": "strong", "children": [ { - "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object", + "type": "text", + "value": "{" + }, + { + "type": "text", + "value": "x: " + }, + { + "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": "Object" + "value": "number" } ] + }, + { + "type": "text", + "value": "}" } ] }, @@ -1271,6 +1319,227 @@ ] } ] + }, + { + "depth": 1, + "type": "heading", + "children": [ + { + "type": "text", + "value": "objectParamFn" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Function with object parameter.", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 32, + "offset": 31 + }, + "indent": [] + } + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "Parameters" + } + ] + }, + { + "ordered": false, + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "x" + }, + { + "type": "text", + "value": " " + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "{" + }, + { + "type": "text", + "value": "a: " + }, + { + "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": "text", + "value": " " + } + ] + } + ] + } + ] + }, + { + "depth": 1, + "type": "heading", + "children": [ + { + "type": "text", + "value": "objectParamFn" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "hi", + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1, + "offset": 0 + }, + "end": { + "line": 1, + "column": 3, + "offset": 2 + }, + "indent": [] + } + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "Parameters" + } + ] + }, + { + "ordered": false, + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "inlineCode", + "value": "x" + }, + { + "type": "text", + "value": " " + }, + { + "type": "strong", + "children": [ + { + "type": "text", + "value": "function (" + }, + { + "type": "text", + "value": "y: " + }, + { + "type": "text", + "value": "Foo" + }, + { + "type": "text", + "value": ")" + }, + { + "type": "text", + "value": ": " + }, + { + "type": "text", + "value": "Bar" + } + ] + }, + { + "type": "text", + "value": " " + } + ] + } + ] + } + ] } ] } \ No newline at end of file diff --git a/test/lib/flow_doctrine.js b/test/lib/flow_doctrine.js index 340343e6c..9239ed3c3 100644 --- a/test/lib/flow_doctrine.js +++ b/test/lib/flow_doctrine.js @@ -2,6 +2,7 @@ var flowDoctrine = require('../../lib/flow_doctrine.js'), parse = require('../../lib/parsers/javascript'), + FLOW_TYPES = require('babel-types').FLOW_TYPES, test = require('tap').test; function toComment(fn, filename) { @@ -11,14 +12,24 @@ function toComment(fn, filename) { })[0]; } -function toDoctrineType(flowType) { - return flowDoctrine(toComment( - '/** add */function add(a: ' + flowType + ' ) { }' - ).context.ast.node.params[0].typeAnnotation.typeAnnotation); -} + test('flowDoctrine', function (t) { + var types = FLOW_TYPES.filter(function (type) { + return type.match(/\wTypeAnnotation$/); + }); + + function toDoctrineType(flowType) { + var annotation = toComment( + '/** add */function add(a: ' + flowType + ' ) { }' + ).context.ast.node.params[0].typeAnnotation.typeAnnotation; + if (types.indexOf(annotation.type) !== -1) { + types.splice(types.indexOf(annotation.type), 1); + } + return flowDoctrine(annotation); + } + t.deepEqual(toDoctrineType('number'), { type: 'NameExpression', @@ -36,6 +47,23 @@ test('flowDoctrine', function (t) { type: 'AllLiteral' }, 'all'); + t.deepEqual(toDoctrineType('(y:Foo) => Bar'), + { + type: 'FunctionType', + params: [{ + type: 'ParameterType', + name: 'y', + expression: { + type: 'NameExpression', + name: 'Foo' + } + }], + result: { + type: 'NameExpression', + name: 'Bar' + } + }, 'function type'); + t.deepEqual(toDoctrineType('?number'), { type: 'NullableType', @@ -66,6 +94,24 @@ test('flowDoctrine', function (t) { name: 'Object' }, 'object'); + t.deepEqual(toDoctrineType('{ a: 1 }'), + { + type: 'RecordType', + fields: [{ + type: 'FieldType', + key: 'a', + value: { + type: 'NumberLiteral', + name: 1 + } + }] + }, 'object with properties'); + + t.deepEqual(toDoctrineType('mixed'), + { + type: 'AllLiteral' + }, 'alias mixed to any for now'); + t.deepEqual(toDoctrineType('Array'), { type: 'NameExpression', @@ -176,5 +222,12 @@ test('flowDoctrine', function (t) { type: 'VoidLiteral', }, 'VoidLiteral'); + // TODO: remove all these types + t.deepEqual(types, [ + 'IntersectionTypeAnnotation', + 'ThisTypeAnnotation', + 'TypeofTypeAnnotation' + ], 'Type coverage'); + t.end(); });