Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: require-exact-type detects nested objects #441

Merged
merged 2 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .README/rules/require-exact-type.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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",
Copy link
Contributor Author

@Marcosld Marcosld Mar 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dependency was causing an error because in this commit import-js/eslint-plugin-import@1a3a128 one of the dependent rules was updated.

"gitdown": "^3.1.1",
"glob": "^7.1.4",
"husky": "^3.0.3",
Expand Down
48 changes: 31 additions & 17 deletions src/rules/requireExactType.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const generateOrderedList = (context, sort, properties) => {
const beforePunctuator = source.getTokenBefore(nextPunctuator, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reported by rules when I updated the plugin (prefer slice to substring)

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
Loading