Skip to content

Commit 83b48d5

Browse files
goodmindgajus
authored andcommitted
feat: add require-indexer-name rule (#425)
* Add require-indexer-name rule * Add require-indexer-name docs
1 parent 091fbf3 commit 83b48d5

File tree

7 files changed

+110
-0
lines changed

7 files changed

+110
-0
lines changed

.README/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
174174
{"gitdown": "include", "file": "./rules/object-type-delimiter.md"}
175175
{"gitdown": "include", "file": "./rules/require-compound-type-alias.md"}
176176
{"gitdown": "include", "file": "./rules/require-exact-type.md"}
177+
{"gitdown": "include", "file": "./rules/require-indexer-name.md"}
177178
{"gitdown": "include", "file": "./rules/require-inexact-type.md"}
178179
{"gitdown": "include", "file": "./rules/require-parameter-type.md"}
179180
{"gitdown": "include", "file": "./rules/require-readonly-react-props.md"}

.README/rules/require-indexer-name.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
### `require-indexer-name`
2+
3+
This rule validates Flow object indexer name.
4+
5+
#### Options
6+
7+
The rule has a string option:
8+
9+
* `"never"` (default): Never report files that are missing an indexer key name.
10+
* `"always"`: Always report files that are missing an indexer key name.
11+
12+
```js
13+
{
14+
"rules": {
15+
"flowtype/require-indexer-name": [
16+
2,
17+
"always"
18+
]
19+
}
20+
}
21+
```
22+
23+
<!-- assertions requireIndexerName -->

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import noUnusedExpressions from './rules/noUnusedExpressions';
1717
import noWeakTypes from './rules/noWeakTypes';
1818
import noMixed from './rules/noMixed';
1919
import objectTypeDelimiter from './rules/objectTypeDelimiter';
20+
import requireIndexerName from './rules/requireIndexerName';
2021
import requireCompoundTypeAlias from './rules/requireCompoundTypeAlias';
2122
import requireInexactType from './rules/requireInexactType';
2223
import requireExactType from './rules/requireExactType';
@@ -61,6 +62,7 @@ const rules = {
6162
'object-type-delimiter': objectTypeDelimiter,
6263
'require-compound-type-alias': requireCompoundTypeAlias,
6364
'require-exact-type': requireExactType,
65+
'require-indexer-name': requireIndexerName,
6466
'require-inexact-type': requireInexactType,
6567
'require-parameter-type': requireParameterType,
6668
'require-readonly-react-props': requireReadonlyReactProps,

src/rules/requireIndexerName.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {getParameterName} from '../utilities';
2+
3+
const schema = [
4+
{
5+
enum: ['always', 'never'],
6+
type: 'string',
7+
},
8+
];
9+
10+
const create = (context) => {
11+
const always = (context.options[0] || 'always') === 'always';
12+
13+
if (always) {
14+
return {
15+
ObjectTypeIndexer (node) {
16+
const id = getParameterName(node, context);
17+
if (id === null) {
18+
context.report({
19+
message: 'All indexers must be declared with key name.',
20+
node,
21+
});
22+
}
23+
},
24+
};
25+
} else {
26+
return {};
27+
}
28+
};
29+
30+
export default {
31+
create,
32+
schema,
33+
};

src/utilities/getParameterName.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@ export default (identifierNode, context) => {
3333
return context.getSourceCode().getFirstToken(identifierNode, tokenIndex).value;
3434
}
3535

36+
if (identifierNode.type === 'ObjectTypeIndexer') {
37+
let tokenIndex;
38+
39+
tokenIndex = 0;
40+
41+
if (identifierNode.static) {
42+
tokenIndex++;
43+
}
44+
45+
if (identifierNode.variance) {
46+
tokenIndex++;
47+
}
48+
49+
tokenIndex++;
50+
51+
const id = context.getSourceCode().getFirstToken(identifierNode, tokenIndex);
52+
const colonOrBrace = context.getSourceCode().getTokenAfter(id);
53+
if (colonOrBrace.value === ':') {
54+
return id.value;
55+
} else {
56+
return null;
57+
}
58+
}
59+
3660
if (identifierNode.type === 'FunctionTypeParam') {
3761
return context.getSourceCode().getFirstToken(identifierNode).value;
3862
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export default {
2+
invalid: [
3+
{
4+
code: 'type foo = { [string]: number };',
5+
errors: [
6+
{message: 'All indexers must be declared with key name.'},
7+
],
8+
},
9+
],
10+
valid: [
11+
{
12+
code: 'type foo = { [key: string]: number };',
13+
errors: [],
14+
},
15+
{
16+
code: 'type foo = { [key: string]: number };',
17+
errors: [],
18+
options: ['never'],
19+
},
20+
{
21+
code: 'type foo = { [string]: number };',
22+
errors: [],
23+
options: ['never'],
24+
},
25+
],
26+
};

tests/rules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const reportingRules = [
3131
'object-type-delimiter',
3232
'require-compound-type-alias',
3333
'require-inexact-type',
34+
'require-indexer-name',
3435
'require-exact-type',
3536
'require-parameter-type',
3637
'require-readonly-react-props',

0 commit comments

Comments
 (0)