Skip to content

Commit

Permalink
feat: add require-types-at-top rule (closes #319, #168, #285)
Browse files Browse the repository at this point in the history
  • Loading branch information
pnevyk authored and gajus committed Feb 18, 2018
1 parent c10b0d9 commit 74bcb8c
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions .README/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
{"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-types-at-top.md"}
{"gitdown": "include", "file": "./rules/require-valid-file-annotation.md"}
{"gitdown": "include", "file": "./rules/require-variable-type.md"}
{"gitdown": "include", "file": "./rules/semi.md"}
Expand Down
14 changes: 14 additions & 0 deletions .README/rules/require-types-at-top.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### `require-types-at-top`

Requires all type declarations to be at the top of the file, after any import declarations.

#### Options

The rule has a string option:

* `"never"`
* `"always"`

The default value is `"always"`.

<!-- assertions require-types-at-top -->
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import objectTypeDelimiter from './rules/objectTypeDelimiter';
import requireExactType from './rules/requireExactType';
import requireParameterType from './rules/requireParameterType';
import requireReturnType from './rules/requireReturnType';
import requireTypesAtTop from './rules/requireTypesAtTop';
import requireValidFileAnnotation from './rules/requireValidFileAnnotation';
import requireVariableType from './rules/requireVariableType';
import semi from './rules/semi';
Expand Down Expand Up @@ -48,6 +49,7 @@ const rules = {
'require-exact-type': requireExactType,
'require-parameter-type': requireParameterType,
'require-return-type': requireReturnType,
'require-types-at-top': requireTypesAtTop,
'require-valid-file-annotation': requireValidFileAnnotation,
'require-variable-type': requireVariableType,
semi,
Expand Down
73 changes: 73 additions & 0 deletions src/rules/requireTypesAtTop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import _ from 'lodash';

const schema = [
{
enum: ['always', 'never'],
type: 'string'
}
];

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

if (always) {
const sourceCode = context.getSourceCode();

// nodes representing type and import declarations
const ignoredNodes = [
// import ...
(node) => { return node.type === 'ImportDeclaration'; },
// export type Foo = ...
// export opaque type Foo = ...
// export type Foo from ...
// export opaque type Foo from ...
(node) => { return node.type === 'ExportNamedDeclaration' && node.exportKind === 'type'; },
// type Foo = ...
(node) => { return node.type === 'TypeAlias'; },
// opaque type Foo = ...
(node) => { return node.type === 'OpaqueType'; }
];

const isIgnoredNode = (node) => {
for (const predicate of ignoredNodes) {
if (predicate(node)) {
return true;
}
}

return false;
};

let regularCodeStartRange;

for (const node of sourceCode.ast.body) {
if (!isIgnoredNode(node)) {
regularCodeStartRange = node.range;
break;
}
}

if (!_.isArray(regularCodeStartRange)) {
// a source with only ignored nodes
return {};
}

return {
'TypeAlias, OpaqueType' (node) {
if (node.range[0] > regularCodeStartRange[0]) {
context.report({
message: 'All type declaration should be at the top of the file, after any import declarations.',
node
});
}
}
};
} else {
return {};
}
};

export default {
create,
schema
};
86 changes: 86 additions & 0 deletions tests/rules/assertions/requireTypesAtTop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export default {
invalid: [
{
code: 'const foo = 3;\ntype Foo = number;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
},
{
code: 'const foo = 3;\nopaque type Foo = number;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
},
{
code: 'const foo = 3;\nexport type Foo = number;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
},
{
code: 'const foo = 3;\nexport opaque type Foo = number;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
},
{
code: 'const foo = 3;\ntype Foo = number | string;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
},
{
code: 'import bar from "./bar";\nconst foo = 3;\ntype Foo = number;',
errors: [{message: 'All type declaration should be at the top of the file, after any import declarations.'}]
}
],
misconfigured: [
{
errors: [
{
data: 'sometimes',
dataPath: '[0]',
keyword: 'enum',
message: 'should be equal to one of the allowed values',
params: {
allowedValues: [
'always',
'never'
]
},
parentSchema: {
enum: [
'always',
'never'
],
type: 'string'
},
schema: [
'always',
'never'
],
schemaPath: '#/items/0/enum'
}
],
options: ['sometimes']
}
],
valid: [
{
code: 'type Foo = number;\nconst foo = 3;'
},
{
code: 'opaque type Foo = number;\nconst foo = 3;'
},
{
code: 'export type Foo = number;\nconst foo = 3;'
},
{
code: 'export opaque type Foo = number;\nconst foo = 3;'
},
{
code: 'type Foo = number;\nconst foo = 3;'
},
{
code: 'import bar from "./bar";\ntype Foo = number;'
},
{
code: 'type Foo = number;\nimport bar from "./bar";'
},
{
code: 'const foo = 3;\ntype Foo = number;',
options: ['never']
}
]
};
1 change: 1 addition & 0 deletions tests/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const reportingRules = [
'require-exact-type',
'require-parameter-type',
'require-return-type',
'require-types-at-top',
'require-valid-file-annotation',
'require-variable-type',
'semi',
Expand Down

0 comments on commit 74bcb8c

Please sign in to comment.