Skip to content

Commit

Permalink
feat: require-exact-type detects nested objects (#441)
Browse files Browse the repository at this point in the history
* feat: require-exact-type should now detect all object type annotations, including nested ones. Also added a fixer.

* fix: updated dependency causing errors, fixed linting errors
  • Loading branch information
Marcosld committed Mar 24, 2020
1 parent 97a230b commit 463b74e
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 86 deletions.
2 changes: 2 additions & 0 deletions .README/rules/require-exact-type.md
@@ -1,5 +1,7 @@
### `require-exact-type`

_The `--fix` option on the command line automatically fixes problems reported by this rule._

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

#### Options
Expand Down
144 changes: 136 additions & 8 deletions README.md
Expand Up @@ -2695,6 +2695,8 @@ function foo(bar: { n: number } & { s: string }) {}
<a name="eslint-plugin-flowtype-rules-require-exact-type"></a>
### <code>require-exact-type</code>
_The `--fix` option on the command line automatically fixes problems reported by this rule._
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-3"></a>
Expand Down Expand Up @@ -2729,26 +2731,38 @@ The following patterns are considered problems:
```js
type foo = {};
// Message: Type identifier 'foo' must be exact.
// Message: Object type must be exact.

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

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

// Options: ["always"]
type foo = { bar: string };
// Message: Type identifier 'foo' must be exact.
(foo: Array<{bar: string}>) => {};
// Message: Object type must be exact.

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

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

// Options: ["never"]
type foo = { bar: {| baz: string |} };
// Message: Object type must not be exact.

// Options: ["never"]
type foo = Array<{| bar: string |}>;
// Message: Object type must not be exact.

// Options: ["never"]
(foo: Array<{| bar: string |}>) => {};
// Message: Object type must not be exact.
```
The following patterns are not considered problems:
Expand All @@ -2768,6 +2782,12 @@ type foo = {| |};
// Options: ["always"]
type foo = {| bar: string |};

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

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

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

Expand All @@ -2777,6 +2797,12 @@ type foo = { };
// Options: ["never"]
type foo = { bar: string };

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

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

// Options: ["never"]
type foo = number;
```
Expand Down Expand Up @@ -3812,6 +3838,18 @@ a;
a;
b;
// Message: Strict Flow file annotation is required, should be `// @flow strict`

// Options: ["never",{"annotationStyle":"line"}]
/* @flow */
a;
b;
// Message: Flow file annotation style must be `// @flow`

// Options: ["never",{"annotationStyle":"line"}]
/* @flow strict */
a;
b;
// Message: Flow file annotation style must be `// @flow strict`
```
The following patterns are not considered problems:
Expand Down Expand Up @@ -4100,6 +4138,96 @@ type FooType = { a: number, c: number, b: string }
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
type FooType = {
a: $ReadOnlyArray<number>,
c: $ReadOnlyMap<string, number>,
b: Map<string, Array<Map<string, number>>>,
}
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
type FooType = {
...ErrorsInRecursiveGenericTypeArgsButDoesNotFix<{
y: boolean,
x: string,
z: {
j: string,
l: number,
k: boolean,
},
}>,
a: number,
c: string,
b: Map<string, Array<ErrorsInRecursiveGenericTypeArgsButDoesNotFix<{
y: boolean,
x: string,
z: {
j: string,
l: number,
k: boolean,
},
}>>>,
}
// Message: Expected type annotations to be in ascending order. "x" should be before "y".
// Message: Expected type annotations to be in ascending order. "k" should be before "l".
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
// Message: Expected type annotations to be in ascending order. "x" should be before "y".
// Message: Expected type annotations to be in ascending order. "k" should be before "l".
type FooType = {
...BPreservesSpreadOrder,
...APreservesSpreadOrder,
c: string,
b: number,
}
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
type FooType = {
...BPreservesSpreadSpans,
...APreservesSpreadSpans,
c: string,
b: number,
...CPreservesSpreadSpans,
e: string,
d: number,
}
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
// Message: Expected type annotations to be in ascending order. "d" should be before "e".
type FooType = {
...BPreservesSpreadOrderAndTypeArgs<string, number>,
...APreservesSpreadOrderAndTypeArgs<number>,
c: string,
b: number,
}
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
type FooType = {
/* preserves block comment before spread BType */
// preserves line comment before spread BType
... /* preserves comment in spread BType */ BType<Generic> /* preserves trailing comment in spread AType */,
/* preserves block comment before spread AType */
// preserves line comment before spread AType
... /* preserves comment in spread AType */ AType /* preserves trailing comment in spread AType */,
/* preserves block comment before reordered key "c" */
// preserves line comment before reordered key "c"
c:/* preserves comment and white space or lack of it */string/* preserves trailing comment for key "c" */,
b: number,
dWithoutComma: boolean
}
// Message: Expected type annotations to be in ascending order. "b" should be before "c".
type FooType = {
+a: number,
c: number,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -19,7 +19,7 @@
"chai": "^4.2.0",
"eclint": "^2.8.1",
"eslint": "^5.13.0",
"eslint-config-canonical": "^17.3.4",
"eslint-config-canonical": "^18.1.1",
"gitdown": "^3.1.1",
"glob": "^7.1.4",
"husky": "^3.0.3",
Expand Down
48 changes: 31 additions & 17 deletions src/rules/requireExactType.js
Expand Up @@ -5,35 +5,49 @@ const schema = [
},
];

const meta = {
fixable: 'code',
};

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

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

if (type === 'ObjectTypeAnnotation') {
if (always && !exact && indexers.length === 0) {
context.report({
data: {name},
message: 'Type identifier \'{{name}}\' must be exact.',
node,
});
}
if (always && !exact && indexers.length === 0) {
context.report({
fix: (fixer) => {
return [
fixer.replaceText(sourceCode.getFirstToken(node), '{|'),
fixer.replaceText(sourceCode.getLastToken(node), '|}'),
];
},
message: 'Object type must be exact.',
node,
});
}

if (!always && exact) {
context.report({
data: {name},
message: 'Type identifier \'{{name}}\' must not be exact.',
node,
});
}
if (!always && exact) {
context.report({
fix: (fixer) => {
return [
fixer.replaceText(sourceCode.getFirstToken(node), '{'),
fixer.replaceText(sourceCode.getLastToken(node), '}'),
];
},
message: 'Object type must not be exact.',
node,
});
}
},
};
};

export default {
create,
meta,
schema,
};
8 changes: 4 additions & 4 deletions src/rules/sortKeys.js
Expand Up @@ -88,7 +88,7 @@ const generateOrderedList = (context, sort, properties) => {
const beforePunctuator = source.getTokenBefore(nextPunctuator, {
includeComments: true,
});
const text = source.getText().substring(startIndex, beforePunctuator.end);
const text = source.getText().slice(startIndex, beforePunctuator.end);

return [property, text];
}
Expand All @@ -100,7 +100,7 @@ const generateOrderedList = (context, sort, properties) => {
});

// Preserve all code until the colon verbatim:
const key = source.getText().substring(startIndex, colonToken.start);
const key = source.getText().slice(startIndex, colonToken.start);
let value;

if (property.value.type === 'ObjectTypeAnnotation') {
Expand All @@ -118,7 +118,7 @@ const generateOrderedList = (context, sort, properties) => {
const beforePunctuator = source.getTokenBefore(nextPunctuator, {
includeComments: true,
});
const text = source.getText().substring(colonToken.end, beforePunctuator.end);
const text = source.getText().slice(colonToken.end, beforePunctuator.end);

value = text;
}
Expand Down Expand Up @@ -184,7 +184,7 @@ const generateFix = (node, context, sort) => {
const startIndex = commentsBefore.length > 0 ?
commentsBefore[0].start :
property.start;
const subString = source.getText().substring(
const subString = source.getText().slice(
startIndex,
beforePunctuator.end
);
Expand Down

0 comments on commit 463b74e

Please sign in to comment.