Skip to content

Commit

Permalink
New rule: spacing around generic type annotation parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
danharper committed Aug 20, 2016
1 parent 31f6b1c commit a925a97
Show file tree
Hide file tree
Showing 7 changed files with 296 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 @@ -93,6 +93,7 @@ When `true`, only checks files with a [`@flow` annotation](http://flowtype.org/d
{"gitdown": "include", "file": "./rules/require-valid-file-annotation.md"}
{"gitdown": "include", "file": "./rules/space-after-type-colon.md"}
{"gitdown": "include", "file": "./rules/space-before-type-colon.md"}
{"gitdown": "include", "file": "./rules/generic-spacing.md"}
{"gitdown": "include", "file": "./rules/type-id-match.md"}
{"gitdown": "include", "file": "./rules/use-flow-type.md"}
{"gitdown": "include", "file": "./rules/valid-syntax.md"}
11 changes: 11 additions & 0 deletions .README/rules/generic-spacing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### `generic-spacing`

_The `--fix` option on the command line automatically fixes problems reported by this rule._

Enforces consistent spacing within generic type annotation parameters.

This rule takes one argument. If it is `'never'` then a problem is raised when there is a space surrounding the generic type parameters. If it is `'always'` then a problem is raised when there is no space surrounding the generic type parameters.

The default value is `'never'`.

<!-- assertions genericSpacing -->
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* [`require-valid-file-annotation`](#eslint-plugin-flowtype-rules-require-valid-file-annotation)
* [`space-after-type-colon`](#eslint-plugin-flowtype-rules-space-after-type-colon)
* [`space-before-type-colon`](#eslint-plugin-flowtype-rules-space-before-type-colon)
* [`generic-spacing`](#eslint-plugin-flowtype-rules-generic-spacing)
* [`type-id-match`](#eslint-plugin-flowtype-rules-type-id-match)
* [`use-flow-type`](#eslint-plugin-flowtype-rules-use-flow-type)
* [`valid-syntax`](#eslint-plugin-flowtype-rules-valid-syntax)
Expand Down Expand Up @@ -1193,6 +1194,90 @@ declare class Foo { static bar : number; }
<h3 id="eslint-plugin-flowtype-rules-generic-spacing"><code>generic-spacing</code></h3>
_The `--fix` option on the command line automatically fixes problems reported by this rule._
Enforces consistent spacing within generic type annotation parameters.
This rule takes one argument. If it is `'never'` then a problem is raised when there is a space surrounding the generic type parameters. If it is `'always'` then a problem is raised when there is no space surrounding the generic type parameters.
The default value is `'never'`.
The following patterns are considered problems:
```js
type X = Promise< string>
// Message: There must be no space at start of "Promise" generic type annotation

// Options: ["never"]
type X = Promise< string>
// Message: There must be no space at start of "Promise" generic type annotation

type X = FooBar<string >
// Message: There must be no space at end of "FooBar" generic type annotation

type X = Promise< string >
// Message: There must be no space at start of "Promise" generic type annotation
// Message: There must be no space at end of "Promise" generic type annotation

type X = Promise< (foo), bar, (((baz))) >
// Message: There must be no space at start of "Promise" generic type annotation
// Message: There must be no space at end of "Promise" generic type annotation

// Options: ["always"]
type X = Promise<string >
// Message: There must be a space at start of "Promise" generic type annotation

// Options: ["always"]
type X = FooBar< string>
// Message: There must be a space at end of "FooBar" generic type annotation

// Options: ["always"]
type X = Promise<string>
// Message: There must be a space at start of "Promise" generic type annotation
// Message: There must be a space at end of "Promise" generic type annotation

// Options: ["always"]
type X = Promise<(foo), bar, (((baz)))>
// Message: There must be a space at start of "Promise" generic type annotation
// Message: There must be a space at end of "Promise" generic type annotation

// Options: ["always"]
type X = FooBar< string >
// Message: There must be one space at start of "FooBar" generic type annotation

// Options: ["always"]
type X = FooBar< string >
// Message: There must be one space at end of "FooBar" generic type annotation

// Options: ["always"]
type X = Promise< (foo), bar, (((baz))) >
// Message: There must be one space at start of "Promise" generic type annotation
// Message: There must be one space at end of "Promise" generic type annotation
```
The following patterns are not considered problems:
```js
type X = Promise<string>

type X = Promise<(string)>

type X = Promise<(foo), bar, (((baz)))>

// Options: ["always"]
type X = Promise< string >

// Options: ["always"]
type X = Promise< (string) >

// Options: ["always"]
type X = Promise< (foo), bar, (((baz))) >
```
<h3 id="eslint-plugin-flowtype-rules-type-id-match"><code>type-id-match</code></h3>
Enforces a consistent naming pattern for type aliases.
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import defineFlowType from './rules/defineFlowType';
import genericSpacing from './rules/genericSpacing';
import requireParameterType from './rules/requireParameterType';
import requireReturnType from './rules/requireReturnType';
import requireValidFileAnnotation from './rules/requireValidFileAnnotation';
Expand All @@ -11,6 +12,7 @@ import validSyntax from './rules/validSyntax';
export default {
rules: {
'define-flow-type': defineFlowType,
'generic-spacing': genericSpacing,
'require-parameter-type': requireParameterType,
'require-return-type': requireReturnType,
'require-valid-file-annotation': requireValidFileAnnotation,
Expand All @@ -22,6 +24,7 @@ export default {
},
rulesConfig: {
'define-flow-type': 0,
'generic-spacing': 0,
'require-parameter-type': 0,
'require-return-type': 0,
'space-after-type-colon': 0,
Expand Down
78 changes: 78 additions & 0 deletions src/rules/genericSpacing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {spacingFixers} from './../utilities';

export default (context) => {
const sourceCode = context.getSourceCode();

const never = (context.options[0] || 'never') === 'never';

return {
GenericTypeAnnotation (node) {
const types = node.typeParameters;

// Promise<foo>
// ^^^^^^^^^^^^ GenericTypeAnnotation (with typeParameters)
// ^^^ GenericTypeAnnotation (without typeParameters)
if (!types) {
return;
}

const [opener, firstInnerToken] = sourceCode.getFirstTokens(types, 2);
const [lastInnerToken, closer] = sourceCode.getLastTokens(types, 2);

const spacesBefore = firstInnerToken.start - opener.end;
const spacesAfter = closer.start - lastInnerToken.end;

if (never) {
if (spacesBefore) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.stripSpacesAfter(opener, spacesBefore),
message: 'There must be no space at start of "{{name}}" generic type annotation',
node: types
});
}

if (spacesAfter) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.stripSpacesAfter(lastInnerToken, spacesAfter),
message: 'There must be no space at end of "{{name}}" generic type annotation',
node: types
});
}
} else {
if (spacesBefore > 1) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.stripSpacesAfter(opener, spacesBefore - 1),
message: 'There must be one space at start of "{{name}}" generic type annotation',
node: types
});
} else if (spacesBefore === 0) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.addSpaceAfter(opener),
message: 'There must be a space at start of "{{name}}" generic type annotation',
node: types
});
}

if (spacesAfter > 1) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.stripSpacesAfter(lastInnerToken, spacesAfter - 1),
message: 'There must be one space at end of "{{name}}" generic type annotation',
node: types
});
} else if (spacesAfter === 0) {
context.report({
data: {name: node.id.name},
fix: spacingFixers.addSpaceAfter(lastInnerToken),
message: 'There must be a space at end of "{{name}}" generic type annotation',
node: types
});
}
}
}
};
};
117 changes: 117 additions & 0 deletions tests/rules/assertions/genericSpacing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
export default {
invalid: [
// Never

{
code: 'type X = Promise< string>',
errors: [{message: 'There must be no space at start of "Promise" generic type annotation'}],
output: 'type X = Promise<string>'
},
{
code: 'type X = Promise< string>',
errors: [{message: 'There must be no space at start of "Promise" generic type annotation'}],
options: ['never'],
output: 'type X = Promise<string>'
},
{
code: 'type X = FooBar<string >',
errors: [{message: 'There must be no space at end of "FooBar" generic type annotation'}],
output: 'type X = FooBar<string>'
},
{
code: 'type X = Promise< string >',
errors: [
{message: 'There must be no space at start of "Promise" generic type annotation'},
{message: 'There must be no space at end of "Promise" generic type annotation'}
],
output: 'type X = Promise<string>'
},
{
code: 'type X = Promise< (foo), bar, (((baz))) >',
errors: [
{message: 'There must be no space at start of "Promise" generic type annotation'},
{message: 'There must be no space at end of "Promise" generic type annotation'}
],
output: 'type X = Promise<(foo), bar, (((baz)))>'
},

// Always (given no space)

{
code: 'type X = Promise<string >',
errors: [{message: 'There must be a space at start of "Promise" generic type annotation'}],
options: ['always'],
output: 'type X = Promise< string >'
},
{
code: 'type X = FooBar< string>',
errors: [{message: 'There must be a space at end of "FooBar" generic type annotation'}],
options: ['always'],
output: 'type X = FooBar< string >'
},
{
code: 'type X = Promise<string>',
errors: [
{message: 'There must be a space at start of "Promise" generic type annotation'},
{message: 'There must be a space at end of "Promise" generic type annotation'}
],
options: ['always'],
output: 'type X = Promise< string >'
},
{
code: 'type X = Promise<(foo), bar, (((baz)))>',
errors: [
{message: 'There must be a space at start of "Promise" generic type annotation'},
{message: 'There must be a space at end of "Promise" generic type annotation'}
],
options: ['always'],
output: 'type X = Promise< (foo), bar, (((baz))) >'
},

// Always (given too many spaces)

{
code: 'type X = FooBar< string >',
errors: [{message: 'There must be one space at start of "FooBar" generic type annotation'}],
options: ['always'],
output: 'type X = FooBar< string >'
},
{
code: 'type X = FooBar< string >',
errors: [{message: 'There must be one space at end of "FooBar" generic type annotation'}],
options: ['always'],
output: 'type X = FooBar< string >'
},
{
code: 'type X = Promise< (foo), bar, (((baz))) >',
errors: [
{message: 'There must be one space at start of "Promise" generic type annotation'},
{message: 'There must be one space at end of "Promise" generic type annotation'}
],
options: ['always'],
output: 'type X = Promise< (foo), bar, (((baz))) >'
}
],
valid: [
// Never

{code: 'type X = Promise<string>'},
{code: 'type X = Promise<(string)>'},
{code: 'type X = Promise<(foo), bar, (((baz)))>'},

// Always

{
code: 'type X = Promise< string >',
options: ['always']
},
{
code: 'type X = Promise< (string) >',
options: ['always']
},
{
code: 'type X = Promise< (foo), bar, (((baz))) >',
options: ['always']
}
]
};
1 change: 1 addition & 0 deletions tests/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ruleTester = new RuleTester();

const reportingRules = [
'define-flow-type',
'generic-spacing',
'require-parameter-type',
'require-return-type',
'require-valid-file-annotation',
Expand Down

0 comments on commit a925a97

Please sign in to comment.