Skip to content

Commit 45e86d8

Browse files
committed
Merge branch 'pnevyk-feat/array-style'
2 parents 8f86c4b + 1d664d7 commit 45e86d8

12 files changed

+434
-0
lines changed

.README/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
148148

149149
<!-- Rules are sorted alphabetically. -->
150150

151+
{"gitdown": "include", "file": "./rules/array-style-complex-type.md"}
152+
{"gitdown": "include", "file": "./rules/array-style-simple-type.md"}
151153
{"gitdown": "include", "file": "./rules/boolean-style.md"}
152154
{"gitdown": "include", "file": "./rules/define-flow-type.md"}
153155
{"gitdown": "include", "file": "./rules/delimiter-dangle.md"}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
### `array-style-complex-type`
2+
3+
_The `--fix` option on the command line automatically fixes problems reported by this rule._
4+
5+
Enforces a particular annotation style of complex types.
6+
7+
Type is considered complex in these cases:
8+
9+
* [Maybe type](https://flow.org/en/docs/types/maybe/)
10+
* [Function type](https://flow.org/en/docs/types/functions/)
11+
* [Object type](https://flow.org/en/docs/types/objects/)
12+
* [Tuple type](https://flow.org/en/docs/types/tuples/)
13+
* [Union type](https://flow.org/en/docs/types/unions/)
14+
* [Intersection type](https://flow.org/en/docs/types/intersections/)
15+
16+
This rule takes one argument.
17+
18+
If it is `'verbose'` then a problem is raised when using `Type[]` instead of `Array<Type>`.
19+
20+
If it is `'shorthand'` then a problem is raised when using `Array<Type>` instead of `Type[]`.
21+
22+
The default value is `'verbose'`.
23+
24+
<!-- assertions arrayStyleComplexType -->
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
### `array-style-simple-type`
2+
3+
_The `--fix` option on the command line automatically fixes problems reported by this rule._
4+
5+
Enforces a particular array type annotation style of simple types.
6+
7+
Type is considered simple in these cases:
8+
9+
* [Primitive types](https://flow.org/en/docs/types/primitives/)
10+
* [Literal types](https://flow.org/en/docs/types/literals/)
11+
* [Mixed type](https://flow.org/en/docs/types/mixed/)
12+
* [Any type](https://flow.org/en/docs/types/any/)
13+
* [Class type](https://flow.org/en/docs/types/classes/)
14+
* [Generic type](https://flow.org/en/docs/types/generics/)
15+
* Array type [shorthand notation](https://flow.org/en/docs/types/arrays/#toc-array-type-shorthand-syntax)
16+
17+
This rule takes one argument.
18+
19+
If it is `'verbose'` then a problem is raised when using `Type[]` instead of `Array<Type>`.
20+
21+
If it is `'shorthand'` then a problem is raised when using `Array<Type>` instead of `Type[]`.
22+
23+
The default value is `'verbose'`.
24+
25+
<!-- assertions arrayStyleSimpleType -->

src/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import _ from 'lodash';
22
import recommended from './configs/recommended.json';
3+
import arrayStyleComplexType from './rules/arrayStyleComplexType';
4+
import arrayStyleSimpleType from './rules/arrayStyleSimpleType';
35
import booleanStyle from './rules/booleanStyle';
46
import defineFlowType from './rules/defineFlowType';
57
import delimiterDangle from './rules/delimiterDangle';
@@ -33,6 +35,8 @@ import validSyntax from './rules/validSyntax';
3335
import {checkFlowFileAnnotation} from './utilities';
3436

3537
const rules = {
38+
'array-style-complex-type': arrayStyleComplexType,
39+
'array-style-simple-type': arrayStyleSimpleType,
3640
'boolean-style': booleanStyle,
3741
'define-flow-type': defineFlowType,
3842
'delimiter-dangle': delimiterDangle,

src/rules/arrayStyle/index.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import isSimpleType from './isSimpleType';
2+
import needWrap from './needWrap';
3+
4+
const schema = [
5+
{
6+
enum: ['verbose', 'shorthand'],
7+
type: 'string'
8+
}
9+
];
10+
11+
const inlineType = (type) => {
12+
const inlined = type.replace(/\s+/g, ' ');
13+
14+
if (inlined.length <= 50) {
15+
return inlined;
16+
} else {
17+
return 'Type';
18+
}
19+
};
20+
21+
export default (defaultConfig, simpleType) => {
22+
const create = (context) => {
23+
const verbose = (context.options[0] || defaultConfig) === 'verbose';
24+
25+
return {
26+
// shorthand
27+
ArrayTypeAnnotation (node) {
28+
const rawElementType = context.getSourceCode().getText(node.elementType);
29+
const inlinedType = inlineType(rawElementType);
30+
const wrappedInlinedType = needWrap(node.elementType) ? '(' + inlinedType + ')' : inlinedType;
31+
32+
if (isSimpleType(node.elementType) === simpleType && verbose) {
33+
context.report({
34+
data: {
35+
type: inlinedType,
36+
wrappedType: wrappedInlinedType
37+
},
38+
fix (fixer) {
39+
return fixer.replaceText(node, 'Array<' + rawElementType + '>');
40+
},
41+
message: 'Use "Array<{{ type }}>", not "{{ wrappedType }}[]"',
42+
node
43+
});
44+
}
45+
},
46+
// verbose
47+
GenericTypeAnnotation (node) {
48+
if (node.id.name === 'Array') {
49+
if (node.typeParameters.params.length === 1) {
50+
const elementTypeNode = node.typeParameters.params[0];
51+
const rawElementType = context.getSourceCode().getText(elementTypeNode);
52+
const inlinedType = inlineType(rawElementType);
53+
const wrappedInlinedType = needWrap(elementTypeNode) ? '(' + inlinedType + ')' : inlinedType;
54+
55+
if (isSimpleType(elementTypeNode) === simpleType && !verbose) {
56+
context.report({
57+
data: {
58+
type: inlinedType,
59+
wrappedType: wrappedInlinedType
60+
},
61+
fix (fixer) {
62+
if (needWrap(elementTypeNode)) {
63+
return fixer.replaceText(node, '(' + rawElementType + ')[]');
64+
} else {
65+
return fixer.replaceText(node, rawElementType + '[]');
66+
}
67+
},
68+
message: 'Use "{{ wrappedType }}[]", not "Array<{{ type }}>"',
69+
node
70+
});
71+
}
72+
}
73+
}
74+
}
75+
};
76+
};
77+
78+
return {
79+
create,
80+
schema
81+
};
82+
};

src/rules/arrayStyle/isSimpleType.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Types considered simple:
3+
*
4+
* - primitive types
5+
* - literal types
6+
* - mixed and any types
7+
* - generic types (such as Date, Promise<string>, $Keys<T>, etc.)
8+
* - array type written in shorthand notation
9+
*
10+
* Types not considered simple:
11+
*
12+
* - maybe type
13+
* - function type
14+
* - object type
15+
* - tuple type
16+
* - union and intersection types
17+
*
18+
* Reminder: if you change these semantics, don't forget to modify documentation of `array-style-...` rules
19+
*/
20+
21+
const simpleTypePatterns = [
22+
/^(?:Any|Array|Boolean|Generic|Mixed|Number|String|Void)TypeAnnotation$/,
23+
/.+LiteralTypeAnnotation$/
24+
];
25+
26+
export default (node) => {
27+
return simpleTypePatterns.some((pattern) => {
28+
return pattern.test(node.type);
29+
});
30+
};

src/rules/arrayStyle/needWrap.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import isSimpleType from './isSimpleType';
2+
3+
const complexTypesWithoutWrap = ['TupleTypeAnnotation', 'ObjectTypeAnnotation'];
4+
5+
export default (node) => {
6+
return !isSimpleType(node) && complexTypesWithoutWrap.indexOf(node.type) === -1;
7+
};

src/rules/arrayStyleComplexType.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import makeArrayStyleRule from './arrayStyle';
2+
3+
export default makeArrayStyleRule('verbose', false);

src/rules/arrayStyleSimpleType.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import makeArrayStyleRule from './arrayStyle';
2+
3+
export default makeArrayStyleRule('verbose', true);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
export default {
2+
invalid: [
3+
{
4+
code: 'type X = (?string)[]',
5+
errors: [{message: 'Use "Array<?string>", not "(?string)[]"'}],
6+
output: 'type X = Array<?string>'
7+
},
8+
{
9+
code: 'type X = (?string)[]',
10+
errors: [{message: 'Use "Array<?string>", not "(?string)[]"'}],
11+
options: ['verbose'],
12+
output: 'type X = Array<?string>'
13+
},
14+
{
15+
code: 'type X = Array<?string>',
16+
errors: [{message: 'Use "(?string)[]", not "Array<?string>"'}],
17+
options: ['shorthand'],
18+
output: 'type X = (?string)[]'
19+
},
20+
{
21+
code: 'type X = Array<{foo: string}>',
22+
errors: [{message: 'Use "{foo: string}[]", not "Array<{foo: string}>"'}],
23+
options: ['shorthand'],
24+
output: 'type X = {foo: string}[]'
25+
},
26+
{
27+
code: 'type X = (string | number)[]',
28+
errors: [{message: 'Use "Array<string | number>", not "(string | number)[]"'}],
29+
output: 'type X = Array<string | number>'
30+
},
31+
{
32+
code: 'type X = (string & number)[]',
33+
errors: [{message: 'Use "Array<string & number>", not "(string & number)[]"'}],
34+
output: 'type X = Array<string & number>'
35+
},
36+
{
37+
code: 'type X = [string, number][]',
38+
errors: [{message: 'Use "Array<[string, number]>", not "[string, number][]"'}],
39+
output: 'type X = Array<[string, number]>'
40+
},
41+
{
42+
code: 'type X = {foo: string}[]',
43+
errors: [{message: 'Use "Array<{foo: string}>", not "{foo: string}[]"'}],
44+
output: 'type X = Array<{foo: string}>'
45+
},
46+
{
47+
code: 'type X = (string => number)[]',
48+
errors: [{message: 'Use "Array<string => number>", not "(string => number)[]"'}],
49+
output: 'type X = Array<string => number>'
50+
},
51+
{
52+
code: 'type X = {\n foo: string,\n bar: number\n}[]',
53+
errors: [{message: 'Use "Array<{ foo: string, bar: number }>", not "{ foo: string, bar: number }[]"'}],
54+
output: 'type X = Array<{\n foo: string,\n bar: number\n}>'
55+
},
56+
{
57+
code: 'type X = {\n foo: string,\n bar: number,\n quo: boolean,\n hey: Date\n}[]',
58+
errors: [{message: 'Use "Array<Type>", not "Type[]"'}],
59+
output: 'type X = Array<{\n foo: string,\n bar: number,\n quo: boolean,\n hey: Date\n}>'
60+
}
61+
],
62+
misconfigured: [
63+
{
64+
errors: [
65+
{
66+
data: 'normal',
67+
dataPath: '[0]',
68+
keyword: 'enum',
69+
message: 'should be equal to one of the allowed values',
70+
params: {
71+
allowedValues: [
72+
'verbose',
73+
'shorthand'
74+
]
75+
},
76+
parentSchema: {
77+
enum: [
78+
'verbose',
79+
'shorthand'
80+
],
81+
type: 'string'
82+
},
83+
schema: [
84+
'verbose',
85+
'shorthand'
86+
],
87+
schemaPath: '#/items/0/enum'
88+
}
89+
],
90+
options: ['normal']
91+
}
92+
],
93+
valid: [
94+
{
95+
code: 'type X = Array<?string>'
96+
},
97+
{
98+
code: 'type X = Array<?string>',
99+
options: ['verbose']
100+
},
101+
{
102+
code: 'type X = (?string)[]',
103+
options: ['shorthand']
104+
},
105+
{
106+
code: 'type X = Array<string>',
107+
options: ['shorthand']
108+
},
109+
{
110+
code: 'type X = Array<?string>',
111+
options: ['shorthand'],
112+
settings: {
113+
flowtype: {
114+
onlyFilesWithFlowAnnotation: true
115+
}
116+
}
117+
}
118+
]
119+
};

0 commit comments

Comments
 (0)