Skip to content

Commit 6d5362b

Browse files
authored
feat: add support for implicit exact Flow types (#471)
This commit adds a support for implicit exact Flow types. This is especially useful when using `exact_by_default=true` Flow option and migrating towards `{ }` and `{ ... }` syntax (Flow standard is now to use `{ }` for strict objects and `{ key: value, ... }` for open objects). Closes: #467
1 parent 8fd60d6 commit 6d5362b

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
lines changed

.README/rules/require-readonly-react-props.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,21 @@ class Bar extends React.Component<Props> { }
7979
```
8080

8181

82+
Optionally, you can enable support for [implicit exact Flow types](https://medium.com/flow-type/on-the-roadmap-exact-objects-by-default-16b72933c5cf) (useful when using `exact_by_default=true` Flow option):
83+
84+
85+
```js
86+
{
87+
"rules": {
88+
"flowtype/require-readonly-react-props": [
89+
2,
90+
{
91+
"useImplicitExactTypes": true
92+
}
93+
]
94+
}
95+
}
96+
```
97+
98+
8299
<!-- assertions requireReadonlyReactProps -->

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,6 +3461,23 @@ class Bar extends React.Component<Props> { }
34613461
```
34623462
34633463
3464+
Optionally, you can enable support for [implicit exact Flow types](https://medium.com/flow-type/on-the-roadmap-exact-objects-by-default-16b72933c5cf) (useful when using `exact_by_default=true` Flow option):
3465+
3466+
3467+
```js
3468+
{
3469+
"rules": {
3470+
"flowtype/require-readonly-react-props": [
3471+
2,
3472+
{
3473+
"useImplicitExactTypes": true
3474+
}
3475+
]
3476+
}
3477+
}
3478+
```
3479+
3480+
34643481
The following patterns are considered problems:
34653482
34663483
```js
@@ -3540,8 +3557,20 @@ type Props = $FlowFixMe; class Foo extends Component<Props> { }
35403557

35413558
type Props = {||}; class Foo extends Component<Props> { }
35423559

3560+
// Options: [{"useImplicitExactTypes":true}]
3561+
type Props = {||}; class Foo extends Component<Props> { }
3562+
3563+
// Options: [{"useImplicitExactTypes":true}]
3564+
type Props = {}; class Foo extends Component<Props> { }
3565+
3566+
class Foo extends Component<{||}> { }
3567+
3568+
// Options: [{"useImplicitExactTypes":true}]
35433569
class Foo extends Component<{||}> { }
35443570

3571+
// Options: [{"useImplicitExactTypes":true}]
3572+
class Foo extends Component<{}> { }
3573+
35453574
class Foo extends React.Component<UnknownProps> { }
35463575

35473576
import { type Props } from "file"; class Foo extends React.Component<Props> { }
@@ -3557,6 +3586,12 @@ function Foo() { return <p /> }
35573586
function Foo(props: $FlowFixMe) { return <p /> }
35583587

35593588
function Foo(props: {||}) { return <p /> }
3589+
3590+
// Options: [{"useImplicitExactTypes":true}]
3591+
function Foo(props: {||}) { return <p /> }
3592+
3593+
// Options: [{"useImplicitExactTypes":true}]
3594+
function Foo(props: {}) { return <p /> }
35603595
```
35613596
35623597

src/rules/requireReadonlyReactProps.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
const schema = [];
1+
import _ from 'lodash';
2+
3+
const schema = [
4+
{
5+
additionalProperties: false,
6+
properties: {
7+
useImplicitExactTypes: {
8+
type: 'boolean',
9+
},
10+
},
11+
type: 'object',
12+
},
13+
];
214

315
const reComponentName = /^(Pure)?Component$/;
416
const reReadOnly = /^\$(ReadOnly|FlowFixMe)$/;
@@ -21,14 +33,19 @@ const isReactComponent = (node) => {
2133
);
2234
};
2335

24-
const isReadOnlyObjectType = (node) => {
36+
const isReadOnlyObjectType = (node, {useImplicitExactTypes}) => {
2537
if (!node || node.type !== 'ObjectTypeAnnotation') {
2638
return false;
2739
}
2840

29-
// we consider `{||}` to be ReadOnly since it's exact AND has no props
30-
if (node.exact && node.properties.length === 0) {
31-
return true;
41+
if (node.properties.length === 0) {
42+
// we consider `{}` to be ReadOnly since it's exact AND has no props (when `implicitExactTypes=true`)
43+
// we consider `{||}` to be ReadOnly since it's exact AND has no props (when `implicitExactTypes=false`)
44+
if (useImplicitExactTypes === true && node.exact === false) {
45+
return true;
46+
} else if (node.exact === true) {
47+
return true;
48+
}
3249
}
3350

3451
// { +foo: ..., +bar: ..., ... }
@@ -38,11 +55,14 @@ const isReadOnlyObjectType = (node) => {
3855
});
3956
};
4057

41-
const isReadOnlyType = (node) => {
42-
return node.right.id && reReadOnly.test(node.right.id.name) || isReadOnlyObjectType(node.right);
58+
const isReadOnlyType = (node, options) => {
59+
return node.right.id && reReadOnly.test(node.right.id.name) || isReadOnlyObjectType(node.right, options);
4360
};
4461

4562
const create = (context) => {
63+
const useImplicitExactTypes = _.get(context, ['options', 0, 'useImplicitExactTypes'], false);
64+
const options = {useImplicitExactTypes};
65+
4666
const readOnlyTypes = [];
4767
const foundTypes = [];
4868
const reportedFunctionalComponents = [];
@@ -72,7 +92,7 @@ const create = (context) => {
7292

7393
if (idName) {
7494
foundTypes.push(idName);
75-
if (isReadOnlyType(typeNode)) {
95+
if (isReadOnlyType(typeNode, options)) {
7696
readOnlyTypes.push(idName);
7797
}
7898
}
@@ -89,7 +109,7 @@ const create = (context) => {
89109
});
90110
} else if (node.superTypeParameters &&
91111
node.superTypeParameters.params[0].type === 'ObjectTypeAnnotation' &&
92-
!isReadOnlyObjectType(node.superTypeParameters.params[0])) {
112+
!isReadOnlyObjectType(node.superTypeParameters.params[0], options)) {
93113
context.report({
94114
message: node.id.name + ' class props must be $ReadOnly',
95115
node,
@@ -133,7 +153,7 @@ const create = (context) => {
133153
}
134154

135155
if (typeAnnotation.typeAnnotation.type === 'ObjectTypeAnnotation' &&
136-
!isReadOnlyObjectType(typeAnnotation.typeAnnotation)) {
156+
!isReadOnlyObjectType(typeAnnotation.typeAnnotation, options)) {
137157
context.report({
138158
message: currentNode.id.name + ' component props must be $ReadOnly',
139159
node,

tests/rules/assertions/requireReadonlyReactProps.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,40 @@ export default {
165165
{
166166
code: 'type Props = {||}; class Foo extends Component<Props> { }',
167167
},
168+
{
169+
code: 'type Props = {||}; class Foo extends Component<Props> { }',
170+
options: [
171+
{
172+
useImplicitExactTypes: true,
173+
},
174+
],
175+
},
176+
{
177+
code: 'type Props = {}; class Foo extends Component<Props> { }',
178+
options: [
179+
{
180+
useImplicitExactTypes: true,
181+
},
182+
],
183+
},
184+
{
185+
code: 'class Foo extends Component<{||}> { }',
186+
},
168187
{
169188
code: 'class Foo extends Component<{||}> { }',
189+
options: [
190+
{
191+
useImplicitExactTypes: true,
192+
},
193+
],
194+
},
195+
{
196+
code: 'class Foo extends Component<{}> { }',
197+
options: [
198+
{
199+
useImplicitExactTypes: true,
200+
},
201+
],
170202
},
171203
{
172204
code: 'class Foo extends React.Component<UnknownProps> { }',
@@ -194,5 +226,21 @@ export default {
194226
{
195227
code: 'function Foo(props: {||}) { return <p /> }',
196228
},
229+
{
230+
code: 'function Foo(props: {||}) { return <p /> }',
231+
options: [
232+
{
233+
useImplicitExactTypes: true,
234+
},
235+
],
236+
},
237+
{
238+
code: 'function Foo(props: {}) { return <p /> }',
239+
options: [
240+
{
241+
useImplicitExactTypes: true,
242+
},
243+
],
244+
},
197245
],
198246
};

0 commit comments

Comments
 (0)