Skip to content

Commit

Permalink
feat: Add newline-after-flow-annotation rule (#312)
Browse files Browse the repository at this point in the history
  • Loading branch information
danwang authored and gajus committed Feb 9, 2018
1 parent 2008687 commit ad4cf82
Show file tree
Hide file tree
Showing 6 changed files with 148 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 @@ -146,6 +146,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
{"gitdown": "include", "file": "./rules/define-flow-type.md"}
{"gitdown": "include", "file": "./rules/delimiter-dangle.md"}
{"gitdown": "include", "file": "./rules/generic-spacing.md"}
{"gitdown": "include", "file": "./rules/newline-after-flow-annotation"}
{"gitdown": "include", "file": "./rules/no-dupe-keys.md"}
{"gitdown": "include", "file": "./rules/no-flow-fix-me-comments.md"}
{"gitdown": "include", "file": "./rules/no-mutable-array.md"}
Expand Down
25 changes: 25 additions & 0 deletions .README/rules/newline-after-flow-annotation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
### `newline-after-flow-annotation`

This rule requires an empty line after the Flow annotation.

#### Options

The rule has a string option:

* `"always"` (default): Enforces that `@flow` annotations be followed by an empty line, separated by newline (LF)
* `"always-windows"`: Identical to "always", but will use a CRLF when autofixing
* `"never"`: Enforces that `@flow` annotations are not followed by empty lines

```js
{
"rules": {
"flowtype/newline-after-flow-annotation": [
2,
"always"
]
}
}
```


<!-- assertions newlineAfterFlowAnnotation -->
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import booleanStyle from './rules/booleanStyle';
import defineFlowType from './rules/defineFlowType';
import delimiterDangle from './rules/delimiterDangle';
import genericSpacing from './rules/genericSpacing';
import newlineAfterFlowAnnotation from './rules/newlineAfterFlowAnnotation';
import noDupeKeys from './rules/noDupeKeys';
import noFlowFixMeComments from './rules/noFlowFixMeComments';
import noMutableArray from './rules/noMutableArray';
Expand Down Expand Up @@ -33,6 +34,7 @@ const rules = {
'define-flow-type': defineFlowType,
'delimiter-dangle': delimiterDangle,
'generic-spacing': genericSpacing,
'newline-after-flow-annotation': newlineAfterFlowAnnotation,
'no-dupe-keys': noDupeKeys,
'no-flow-fix-me-comments': noFlowFixMeComments,
'no-mutable-array': noMutableArray,
Expand Down Expand Up @@ -76,6 +78,7 @@ export default {
'define-flow-type': 0,
'delimiter-dangle': 0,
'generic-spacing': 0,
'newline-after-flow-annotation': 0,
'no-dupe-keys': 0,
'no-flow-fix-me-comments': 0,
'no-mutable-array': 0,
Expand Down
76 changes: 76 additions & 0 deletions src/rules/newlineAfterFlowAnnotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import _ from 'lodash';

const looksLikeFlowFileAnnotation = (comment) => {
return /@(?:no)?flo/i.test(comment);
};

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

const create = (context) => {
const mode = context.options[0];
const never = mode === 'never';

const newline = mode === 'always-windows' ? '\r\n' : '\n';

return {
Program (node) {
const sourceCode = context.getSourceCode();

const potentialFlowFileAnnotation = _.find(
context.getAllComments(),
(comment) => {
return looksLikeFlowFileAnnotation(comment.value);
}
);

if (potentialFlowFileAnnotation) {
const line = potentialFlowFileAnnotation.loc.end.line;
const nextLineIsEmpty = sourceCode.lines[line] === '';

if (!never && !nextLineIsEmpty) {
context.report({
fix: (fixer) => {
return fixer.insertTextAfter(
potentialFlowFileAnnotation,
newline
);
},
message: 'Expected newline after flow annotation',
node
});
}

if (never && nextLineIsEmpty) {
context.report({
fix: (fixer) => {
const lineBreak = sourceCode.text[potentialFlowFileAnnotation.end];

return fixer.replaceTextRange(
[
potentialFlowFileAnnotation.end,
potentialFlowFileAnnotation.end + (
lineBreak === '\r' ? 2 : 1
)
],
''
);
},
message: 'Expected no newline after flow annotation',
node
});
}
}
}
};
};

export default {
create,
schema
};

42 changes: 42 additions & 0 deletions tests/rules/assertions/newlineAfterFlowAnnotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export default {
invalid: [
{
code: '// @flow\nimport Foo from \'./foo\';',
errors: [{message: 'Expected newline after flow annotation'}],
output: '// @flow\n\nimport Foo from \'./foo\';'
},
{
code: '// @flow\nimport Foo from \'./foo\';',
errors: [{message: 'Expected newline after flow annotation'}],
options: ['always'],
output: '// @flow\n\nimport Foo from \'./foo\';'
},
{
code: '// @flow\r\nimport Foo from \'./foo\';',
errors: [{message: 'Expected newline after flow annotation'}],
options: ['always-windows'],
output: '// @flow\r\n\r\nimport Foo from \'./foo\';'
},
{
code: '// @flow\n\n',
errors: [{message: 'Expected no newline after flow annotation'}],
options: ['never'],
output: '// @flow\n'
}
],
valid: [
{
code: '// @flow\n\nimport Foo from \'./foo\';',
options: ['always']
},
{
code: '// @flow\r\n\r\nimport Foo from \'./foo\';',
options: ['always-windows']
},
{
code: '// @flow\nimport Foo from \'./foo\';',
options: ['never']
}
]
};

1 change: 1 addition & 0 deletions tests/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const reportingRules = [
'define-flow-type',
'delimiter-dangle',
'generic-spacing',
'newline-after-flow-annotation',
'no-dupe-keys',
'no-flow-fix-me-comments',
'no-mutable-array',
Expand Down

0 comments on commit ad4cf82

Please sign in to comment.