Skip to content

Commit

Permalink
feat: add require-exact-type rule (fixes #304) (#305)
Browse files Browse the repository at this point in the history
* feat: add require-exact-type rule (fixes #304)

* docs: update README
  • Loading branch information
geraintwhite authored and gajus committed Jan 25, 2018
1 parent c898dec commit 2008687
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 1 deletion.
1 change: 1 addition & 0 deletions .README/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
{"gitdown": "include", "file": "./rules/no-unused-expressions.md"}
{"gitdown": "include", "file": "./rules/no-weak-types.md"}
{"gitdown": "include", "file": "./rules/object-type-delimiter.md"}
{"gitdown": "include", "file": "./rules/require-exact-type.md"}
{"gitdown": "include", "file": "./rules/require-parameter-type.md"}
{"gitdown": "include", "file": "./rules/require-return-type.md"}
{"gitdown": "include", "file": "./rules/require-valid-file-annotation.md"}
Expand Down
32 changes: 32 additions & 0 deletions .README/rules/require-exact-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
### `require-exact-type`

This rule enforces [exact object types](https://flow.org/en/docs/types/objects/#toc-exact-object-types).

#### Options

The rule has one string option:

* `"always"` (default): Report all object type definitions that aren't exact.
* `"never"`: Report all object type definitions that are exact.

```js
{
"rules": {
"flowtype/require-exact-type": [
2,
"always"
]
}
}

{
"rules": {
"flowtype/require-exact-type": [
2,
"never"
]
}
}
```

<!-- assertions requireExactType -->
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Run with `npm run lint`.

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

Expand Down
159 changes: 159 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
* [`delimiter-dangle`](#eslint-plugin-flowtype-rules-delimiter-dangle)
* [`generic-spacing`](#eslint-plugin-flowtype-rules-generic-spacing)
* [`no-dupe-keys`](#eslint-plugin-flowtype-rules-no-dupe-keys)
* [`no-flow-fix-me-comments`](#eslint-plugin-flowtype-rules-no-flow-fix-me-comments)
* [`no-mutable-array`](#eslint-plugin-flowtype-rules-no-mutable-array)
* [`no-primitive-constructor-types`](#eslint-plugin-flowtype-rules-no-primitive-constructor-types)
* [`no-types-missing-file-annotation`](#eslint-plugin-flowtype-rules-no-types-missing-file-annotation)
* [`no-unused-expressions`](#eslint-plugin-flowtype-rules-no-unused-expressions)
* [`no-weak-types`](#eslint-plugin-flowtype-rules-no-weak-types)
* [`object-type-delimiter`](#eslint-plugin-flowtype-rules-object-type-delimiter)
* [`require-exact-type`](#eslint-plugin-flowtype-rules-require-exact-type)
* [`require-parameter-type`](#eslint-plugin-flowtype-rules-require-parameter-type)
* [`require-return-type`](#eslint-plugin-flowtype-rules-require-return-type)
* [`require-valid-file-annotation`](#eslint-plugin-flowtype-rules-require-valid-file-annotation)
Expand Down Expand Up @@ -252,6 +254,9 @@ var a: AType<BType>
type A = AType
// Additional rules: {"no-undef":2}

declare type A = number
// Additional rules: {"no-undef":2}

opaque type A = AType
// Additional rules: {"no-undef":2}

Expand Down Expand Up @@ -285,6 +290,9 @@ class C implements AType {}
interface AType {}
// Additional rules: {"no-undef":2}

declare interface A {}
// Additional rules: {"no-undef":2}

({ a: ({b() {}}: AType) })
// Additional rules: {"no-undef":2}

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

declare type A = number
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}

opaque type A = AType
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}

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

declare interface A {}
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}

({ a: ({b() {}}: AType) })
// Additional rules: {"no-undef":2,"no-use-before-define":[2,"nofunc"]}

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



<a name="eslint-plugin-flowtype-rules-no-flow-fix-me-comments"></a>
### <code>no-flow-fix-me-comments</code>

Disallows `$FlowFixMe` comment suppressions.

This is especially useful as a warning to ensure instances of `$FlowFixMe` in your codebase get fixed over time.

<a name="eslint-plugin-flowtype-rules-no-flow-fix-me-comments-options"></a>
#### Options

This rule takes an optional RegExp that comments a text RegExp that makes the supression valid.

```js
{
"rules": {
"flowtype/no-flow-fix-me-comments": [
1,
"TODO\s+[0-9]+"
]
}
}
```

<!-- assertions no-flow-fix-me-comments -->

<a name="eslint-plugin-flowtype-rules-no-mutable-array"></a>
### <code>no-mutable-array</code>

Expand Down Expand Up @@ -1500,6 +1539,95 @@ type Foo = { a: Foo, b: Bar }
<a name="eslint-plugin-flowtype-rules-require-exact-type"></a>
### <code>require-exact-type</code>
This rule enforces [exact object types](https://flow.org/en/docs/types/objects/#toc-exact-object-types).
<a name="eslint-plugin-flowtype-rules-require-exact-type-options"></a>
#### Options
The rule has one string option:
* `"always"` (default): Report all object type definitions that aren't exact.
* `"never"`: Report all object type definitions that are exact.
```js
{
"rules": {
"flowtype/require-exact-type": [
2,
"always"
]
}
}

{
"rules": {
"flowtype/require-exact-type": [
2,
"never"
]
}
}
```
The following patterns are considered problems:
```js
type foo = {};
// Message: Type identifier 'foo' must be exact.

type foo = { bar: string };
// Message: Type identifier 'foo' must be exact.

// Options: ["always"]
type foo = {};
// Message: Type identifier 'foo' must be exact.

// Options: ["always"]
type foo = { bar: string };
// Message: Type identifier 'foo' must be exact.

// Options: ["never"]
type foo = {| |};
// Message: Type identifier 'foo' must not be exact.

// Options: ["never"]
type foo = {| bar: string |};
// Message: Type identifier 'foo' must not be exact.
```
The following patterns are not considered problems:
```js
type foo = {| |};

type foo = {| bar: string |};

type foo = number;

// Options: ["always"]
type foo = {| |};

// Options: ["always"]
type foo = {| bar: string |};

// Options: ["always"]
type foo = number;

// Options: ["never"]
type foo = { };

// Options: ["never"]
type foo = { bar: string };

// Options: ["never"]
type foo = number;
```
<a name="eslint-plugin-flowtype-rules-require-parameter-type"></a>
### <code>require-parameter-type</code>
Expand Down Expand Up @@ -1789,6 +1917,19 @@ async () => {}
async function x() {}
// Message: Missing return type annotation.

// Options: ["always",{"annotateUndefined":"always"}]
class Test { constructor() { } }
// Message: Must annotate undefined return type.

class Test { foo() { return 42; } }
// Message: Missing return type annotation.

class Test { foo = () => { return 42; } }
// Message: Missing return type annotation.

class Test { foo = () => 42; }
// Message: Missing return type annotation.

// Options: ["always"]
async () => { return; }
// Message: Missing return type annotation.
Expand Down Expand Up @@ -1869,6 +2010,24 @@ async function doThing(): Promise<void> {}
// Options: ["always",{"annotateUndefined":"always"}]
function* doThing(): Generator<number, void, void> { yield 2; }

// Options: ["always",{"annotateUndefined":"always","excludeMatching":["constructor"]}]
class Test { constructor() { } }

class Test { constructor() { } }

// Options: ["always",{"excludeMatching":["foo"]}]
class Test { foo() { return 42; } }

// Options: ["always",{"excludeMatching":["foo"]}]
class Test { foo = () => { return 42; } }

// Options: ["always",{"excludeMatching":["foo"]}]
class Test { foo = () => 42; }

class Test { foo = (): number => { return 42; } }

class Test { foo = (): number => 42; }

async (foo): Promise<number> => { return 3; }

// Options: ["always",{"excludeArrowFunctions":true}]
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import noTypesMissingFileAnnotation from './rules/noTypesMissingFileAnnotation';
import noUnusedExpressions from './rules/noUnusedExpressions';
import noWeakTypes from './rules/noWeakTypes';
import objectTypeDelimiter from './rules/objectTypeDelimiter';
import requireExactType from './rules/requireExactType';
import requireParameterType from './rules/requireParameterType';
import requireReturnType from './rules/requireReturnType';
import requireValidFileAnnotation from './rules/requireValidFileAnnotation';
Expand Down Expand Up @@ -40,6 +41,7 @@ const rules = {
'no-unused-expressions': noUnusedExpressions,
'no-weak-types': noWeakTypes,
'object-type-delimiter': objectTypeDelimiter,
'require-exact-type': requireExactType,
'require-parameter-type': requireParameterType,
'require-return-type': requireReturnType,
'require-valid-file-annotation': requireValidFileAnnotation,
Expand Down Expand Up @@ -79,6 +81,7 @@ export default {
'no-mutable-array': 0,
'no-weak-types': 0,
'object-type-delimiter': 0,
'require-exact-type': 0,
'require-parameter-type': 0,
'require-return-type': 0,
'require-variable-type': 0,
Expand Down
39 changes: 39 additions & 0 deletions src/rules/requireExactType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const schema = [
{
enum: ['always', 'never'],
type: 'string'
}
];

const create = (context) => {
const always = (context.options[0] || 'always') === 'always';

return {
TypeAlias (node) {
const {id: {name}, right: {type, exact}} = node;

if (type === 'ObjectTypeAnnotation') {
if (always && !exact) {
context.report({
data: {name},
message: 'Type identifier \'{{name}}\' must be exact.',
node
});
}

if (!always && exact) {
context.report({
data: {name},
message: 'Type identifier \'{{name}}\' must not be exact.',
node
});
}
}
}
};
};

export default {
create,
schema
};
Loading

0 comments on commit 2008687

Please sign in to comment.