Skip to content

Commit 2008687

Browse files
geraintwhitegajus
authored andcommitted
feat: add require-exact-type rule (fixes #304) (#305)
* feat: add require-exact-type rule (fixes #304) * docs: update README
1 parent c898dec commit 2008687

File tree

8 files changed

+339
-1
lines changed

8 files changed

+339
-1
lines changed

.README/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
154154
{"gitdown": "include", "file": "./rules/no-unused-expressions.md"}
155155
{"gitdown": "include", "file": "./rules/no-weak-types.md"}
156156
{"gitdown": "include", "file": "./rules/object-type-delimiter.md"}
157+
{"gitdown": "include", "file": "./rules/require-exact-type.md"}
157158
{"gitdown": "include", "file": "./rules/require-parameter-type.md"}
158159
{"gitdown": "include", "file": "./rules/require-return-type.md"}
159160
{"gitdown": "include", "file": "./rules/require-valid-file-annotation.md"}

.README/rules/require-exact-type.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
### `require-exact-type`
2+
3+
This rule enforces [exact object types](https://flow.org/en/docs/types/objects/#toc-exact-object-types).
4+
5+
#### Options
6+
7+
The rule has one string option:
8+
9+
* `"always"` (default): Report all object type definitions that aren't exact.
10+
* `"never"`: Report all object type definitions that are exact.
11+
12+
```js
13+
{
14+
"rules": {
15+
"flowtype/require-exact-type": [
16+
2,
17+
"always"
18+
]
19+
}
20+
}
21+
22+
{
23+
"rules": {
24+
"flowtype/require-exact-type": [
25+
2,
26+
"never"
27+
]
28+
}
29+
}
30+
```
31+
32+
<!-- assertions requireExactType -->

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Run with `npm run lint`.
3636

3737
1. Create a file in `tests/rules/assertions` named the `camelCase` version of your rule name with the following template:
3838
* `export default { invalid: [], valid: [] }`
39-
2. Add your test file to `tests/index.js`
39+
2. Add your test file to `tests/rules/index.js`
4040
3. Create a file in `src/rules` named the `camelCase` version of your rule name
4141
4. Add your rule file to `src/index.js`
4242

README.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
* [`delimiter-dangle`](#eslint-plugin-flowtype-rules-delimiter-dangle)
2020
* [`generic-spacing`](#eslint-plugin-flowtype-rules-generic-spacing)
2121
* [`no-dupe-keys`](#eslint-plugin-flowtype-rules-no-dupe-keys)
22+
* [`no-flow-fix-me-comments`](#eslint-plugin-flowtype-rules-no-flow-fix-me-comments)
2223
* [`no-mutable-array`](#eslint-plugin-flowtype-rules-no-mutable-array)
2324
* [`no-primitive-constructor-types`](#eslint-plugin-flowtype-rules-no-primitive-constructor-types)
2425
* [`no-types-missing-file-annotation`](#eslint-plugin-flowtype-rules-no-types-missing-file-annotation)
2526
* [`no-unused-expressions`](#eslint-plugin-flowtype-rules-no-unused-expressions)
2627
* [`no-weak-types`](#eslint-plugin-flowtype-rules-no-weak-types)
2728
* [`object-type-delimiter`](#eslint-plugin-flowtype-rules-object-type-delimiter)
29+
* [`require-exact-type`](#eslint-plugin-flowtype-rules-require-exact-type)
2830
* [`require-parameter-type`](#eslint-plugin-flowtype-rules-require-parameter-type)
2931
* [`require-return-type`](#eslint-plugin-flowtype-rules-require-return-type)
3032
* [`require-valid-file-annotation`](#eslint-plugin-flowtype-rules-require-valid-file-annotation)
@@ -252,6 +254,9 @@ var a: AType<BType>
252254
type A = AType
253255
// Additional rules: {"no-undef":2}
254256

257+
declare type A = number
258+
// Additional rules: {"no-undef":2}
259+
255260
opaque type A = AType
256261
// Additional rules: {"no-undef":2}
257262

@@ -285,6 +290,9 @@ class C implements AType {}
285290
interface AType {}
286291
// Additional rules: {"no-undef":2}
287292

293+
declare interface A {}
294+
// Additional rules: {"no-undef":2}
295+
288296
({ a: ({b() {}}: AType) })
289297
// Additional rules: {"no-undef":2}
290298

@@ -309,6 +317,9 @@ var a: AType<BType>
309317
type A = AType
310318
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
311319

320+
declare type A = number
321+
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
322+
312323
opaque type A = AType
313324
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
314325

@@ -342,6 +353,9 @@ class C implements AType {}
342353
interface AType {}
343354
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
344355

356+
declare interface A {}
357+
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
358+
345359
({ a: ({b() {}}: AType) })
346360
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}
347361

@@ -968,6 +982,31 @@ var a = 1; var b = 1; type f = { get(key: a): string, get(key: b): string }
968982

969983

970984

985+
<a name="eslint-plugin-flowtype-rules-no-flow-fix-me-comments"></a>
986+
### <code>no-flow-fix-me-comments</code>
987+
988+
Disallows `$FlowFixMe` comment suppressions.
989+
990+
This is especially useful as a warning to ensure instances of `$FlowFixMe` in your codebase get fixed over time.
991+
992+
<a name="eslint-plugin-flowtype-rules-no-flow-fix-me-comments-options"></a>
993+
#### Options
994+
995+
This rule takes an optional RegExp that comments a text RegExp that makes the supression valid.
996+
997+
```js
998+
{
999+
"rules": {
1000+
"flowtype/no-flow-fix-me-comments": [
1001+
1,
1002+
"TODO\s+[0-9]+"
1003+
]
1004+
}
1005+
}
1006+
```
1007+
1008+
<!-- assertions no-flow-fix-me-comments -->
1009+
9711010
<a name="eslint-plugin-flowtype-rules-no-mutable-array"></a>
9721011
### <code>no-mutable-array</code>
9731012

@@ -1500,6 +1539,95 @@ type Foo = { a: Foo, b: Bar }
15001539
15011540
15021541
1542+
<a name="eslint-plugin-flowtype-rules-require-exact-type"></a>
1543+
### <code>require-exact-type</code>
1544+
1545+
This rule enforces [exact object types](https://flow.org/en/docs/types/objects/#toc-exact-object-types).
1546+
1547+
<a name="eslint-plugin-flowtype-rules-require-exact-type-options"></a>
1548+
#### Options
1549+
1550+
The rule has one string option:
1551+
1552+
* `"always"` (default): Report all object type definitions that aren't exact.
1553+
* `"never"`: Report all object type definitions that are exact.
1554+
1555+
```js
1556+
{
1557+
"rules": {
1558+
"flowtype/require-exact-type": [
1559+
2,
1560+
"always"
1561+
]
1562+
}
1563+
}
1564+
1565+
{
1566+
"rules": {
1567+
"flowtype/require-exact-type": [
1568+
2,
1569+
"never"
1570+
]
1571+
}
1572+
}
1573+
```
1574+
1575+
The following patterns are considered problems:
1576+
1577+
```js
1578+
type foo = {};
1579+
// Message: Type identifier 'foo' must be exact.
1580+
1581+
type foo = { bar: string };
1582+
// Message: Type identifier 'foo' must be exact.
1583+
1584+
// Options: ["always"]
1585+
type foo = {};
1586+
// Message: Type identifier 'foo' must be exact.
1587+
1588+
// Options: ["always"]
1589+
type foo = { bar: string };
1590+
// Message: Type identifier 'foo' must be exact.
1591+
1592+
// Options: ["never"]
1593+
type foo = {| |};
1594+
// Message: Type identifier 'foo' must not be exact.
1595+
1596+
// Options: ["never"]
1597+
type foo = {| bar: string |};
1598+
// Message: Type identifier 'foo' must not be exact.
1599+
```
1600+
1601+
The following patterns are not considered problems:
1602+
1603+
```js
1604+
type foo = {| |};
1605+
1606+
type foo = {| bar: string |};
1607+
1608+
type foo = number;
1609+
1610+
// Options: ["always"]
1611+
type foo = {| |};
1612+
1613+
// Options: ["always"]
1614+
type foo = {| bar: string |};
1615+
1616+
// Options: ["always"]
1617+
type foo = number;
1618+
1619+
// Options: ["never"]
1620+
type foo = { };
1621+
1622+
// Options: ["never"]
1623+
type foo = { bar: string };
1624+
1625+
// Options: ["never"]
1626+
type foo = number;
1627+
```
1628+
1629+
1630+
15031631
<a name="eslint-plugin-flowtype-rules-require-parameter-type"></a>
15041632
### <code>require-parameter-type</code>
15051633
@@ -1789,6 +1917,19 @@ async () => {}
17891917
async function x() {}
17901918
// Message: Missing return type annotation.
17911919

1920+
// Options: ["always",{"annotateUndefined":"always"}]
1921+
class Test { constructor() { } }
1922+
// Message: Must annotate undefined return type.
1923+
1924+
class Test { foo() { return 42; } }
1925+
// Message: Missing return type annotation.
1926+
1927+
class Test { foo = () => { return 42; } }
1928+
// Message: Missing return type annotation.
1929+
1930+
class Test { foo = () => 42; }
1931+
// Message: Missing return type annotation.
1932+
17921933
// Options: ["always"]
17931934
async () => { return; }
17941935
// Message: Missing return type annotation.
@@ -1869,6 +2010,24 @@ async function doThing(): Promise<void> {}
18692010
// Options: ["always",{"annotateUndefined":"always"}]
18702011
function* doThing(): Generator<number, void, void> { yield 2; }
18712012

2013+
// Options: ["always",{"annotateUndefined":"always","excludeMatching":["constructor"]}]
2014+
class Test { constructor() { } }
2015+
2016+
class Test { constructor() { } }
2017+
2018+
// Options: ["always",{"excludeMatching":["foo"]}]
2019+
class Test { foo() { return 42; } }
2020+
2021+
// Options: ["always",{"excludeMatching":["foo"]}]
2022+
class Test { foo = () => { return 42; } }
2023+
2024+
// Options: ["always",{"excludeMatching":["foo"]}]
2025+
class Test { foo = () => 42; }
2026+
2027+
class Test { foo = (): number => { return 42; } }
2028+
2029+
class Test { foo = (): number => 42; }
2030+
18722031
async (foo): Promise<number> => { return 3; }
18732032

18742033
// Options: ["always",{"excludeArrowFunctions":true}]

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import noTypesMissingFileAnnotation from './rules/noTypesMissingFileAnnotation';
1212
import noUnusedExpressions from './rules/noUnusedExpressions';
1313
import noWeakTypes from './rules/noWeakTypes';
1414
import objectTypeDelimiter from './rules/objectTypeDelimiter';
15+
import requireExactType from './rules/requireExactType';
1516
import requireParameterType from './rules/requireParameterType';
1617
import requireReturnType from './rules/requireReturnType';
1718
import requireValidFileAnnotation from './rules/requireValidFileAnnotation';
@@ -40,6 +41,7 @@ const rules = {
4041
'no-unused-expressions': noUnusedExpressions,
4142
'no-weak-types': noWeakTypes,
4243
'object-type-delimiter': objectTypeDelimiter,
44+
'require-exact-type': requireExactType,
4345
'require-parameter-type': requireParameterType,
4446
'require-return-type': requireReturnType,
4547
'require-valid-file-annotation': requireValidFileAnnotation,
@@ -79,6 +81,7 @@ export default {
7981
'no-mutable-array': 0,
8082
'no-weak-types': 0,
8183
'object-type-delimiter': 0,
84+
'require-exact-type': 0,
8285
'require-parameter-type': 0,
8386
'require-return-type': 0,
8487
'require-variable-type': 0,

src/rules/requireExactType.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const schema = [
2+
{
3+
enum: ['always', 'never'],
4+
type: 'string'
5+
}
6+
];
7+
8+
const create = (context) => {
9+
const always = (context.options[0] || 'always') === 'always';
10+
11+
return {
12+
TypeAlias (node) {
13+
const {id: {name}, right: {type, exact}} = node;
14+
15+
if (type === 'ObjectTypeAnnotation') {
16+
if (always && !exact) {
17+
context.report({
18+
data: {name},
19+
message: 'Type identifier \'{{name}}\' must be exact.',
20+
node
21+
});
22+
}
23+
24+
if (!always && exact) {
25+
context.report({
26+
data: {name},
27+
message: 'Type identifier \'{{name}}\' must not be exact.',
28+
node
29+
});
30+
}
31+
}
32+
}
33+
};
34+
};
35+
36+
export default {
37+
create,
38+
schema
39+
};

0 commit comments

Comments
 (0)