Skip to content

Commit 63ef07e

Browse files
jseminckyannickcr
authored andcommitted
Add jsx-first-prop-new-line rule (fixes #410)
1 parent 7b27c53 commit 63ef07e

File tree

5 files changed

+272
-1
lines changed

5 files changed

+272
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ The plugin has a [recommended configuration](#user-content-recommended-configura
102102
* [jsx-closing-bracket-location](docs/rules/jsx-closing-bracket-location.md): Validate closing bracket location in JSX (fixable)
103103
* [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes (fixable)
104104
* [jsx-equals-spacing](docs/rules/jsx-equals-spacing.md): Enforce or disallow spaces around equal signs in JSX attributes (fixable)
105+
* [jsx-first-prop-new-line](docs/rules/jsx-first-prop-new-line.md): Enforce position of the first prop in JSX
105106
* [jsx-handler-names](docs/rules/jsx-handler-names.md): Enforce event handler naming conventions in JSX
106107
* [jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX (fixable)
107108
* [jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation

docs/rules/jsx-first-prop-new-line.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Configure the position of the first property (jsx-first-prop-new-line)
2+
3+
Ensure correct position of the first property.
4+
5+
## Rule Details
6+
7+
This rule checks whether the first property of all JSX elements is correctly placed. There are three possible configurations:
8+
* `always`: The first property should always be placed on a new line.
9+
* `never` : The first property should never be placed on a new line, e.g. should always be on the same line as the Component opening tag.
10+
* `multiline`: The first property should always be placed on a new line when the properties are spread over multiple lines.
11+
12+
The following patterns are considered warnings when configured `"always"`:
13+
14+
```js
15+
<Hello personal={true} />
16+
17+
<Hello personal={true}
18+
foo="bar"
19+
/>
20+
```
21+
22+
The following patterns are not considered warnings when configured `"always"`:
23+
24+
```js
25+
<Hello
26+
personal />
27+
28+
<Hello
29+
personal
30+
/>
31+
```
32+
33+
The following patterns are considered warnings when configured `"never"`:
34+
35+
```js
36+
<Hello
37+
personal />
38+
39+
<Hello
40+
personal
41+
/>
42+
```
43+
44+
The following patterns are not considered warnings when configured `"never"`:
45+
46+
```js
47+
<Hello personal={true} />
48+
49+
<Hello personal={true}
50+
foo="bar"
51+
/>
52+
```
53+
54+
The following patterns are considered warnings when configured `"multiline"`:
55+
56+
```js
57+
<Hello personal
58+
prop />
59+
```
60+
61+
The following patterns are not considered warnings when configured `"multiline"`:
62+
63+
```js
64+
<Hello personal={true} />
65+
66+
<Hello
67+
personal={true}
68+
foo="bar"
69+
/>
70+
```
71+
72+
## When not to use
73+
74+
If you are not using JSX then you can disable this rule.

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ module.exports = {
4242
'jsx-key': require('./lib/rules/jsx-key'),
4343
'no-string-refs': require('./lib/rules/no-string-refs'),
4444
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
45-
'require-render-return': require('./lib/rules/require-render-return')
45+
'require-render-return': require('./lib/rules/require-render-return'),
46+
'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line')
4647
},
4748
configs: {
4849
recommended: {

lib/rules/jsx-first-prop-new-line.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @fileoverview Ensure proper position of the first property in JSX
3+
* @author Joachim Seminck
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Rule Definition
9+
// ------------------------------------------------------------------------------
10+
11+
module.exports = function (context) {
12+
var configuration = context.options[0];
13+
14+
function isMultilineJSX(jsxNode) {
15+
return jsxNode.loc.start.line < jsxNode.loc.end.line;
16+
}
17+
18+
return {
19+
JSXOpeningElement: function (node) {
20+
if ((configuration === 'multiline' && isMultilineJSX(node)) || (configuration === 'always')) {
21+
node.attributes.forEach(function(decl) {
22+
if (decl.loc.start.line === node.loc.start.line) {
23+
context.report({
24+
node: decl,
25+
message: 'Property should be placed on a new line'
26+
});
27+
}
28+
});
29+
} else if (configuration === 'never' && node.attributes.length > 0) {
30+
var firstNode = node.attributes[0];
31+
if (node.loc.start.line < firstNode.loc.start.line) {
32+
context.report({
33+
node: firstNode,
34+
message: 'Property should be placed on the same line as the component declaration'
35+
});
36+
return;
37+
}
38+
}
39+
return;
40+
}
41+
};
42+
};
43+
44+
module.exports.schema = [{
45+
enum: ['always', 'never', 'multiline']
46+
}];
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* @fileoverview Ensure proper position of the first property in JSX
3+
* @author Joachim Seminck
4+
*/
5+
'use strict';
6+
7+
// -----------------------------------------------------------------------------
8+
// Requirements
9+
// -----------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/jsx-first-prop-new-line');
12+
var RuleTester = require('eslint').RuleTester;
13+
14+
var parserOptions = 'babel-eslint';
15+
16+
// -----------------------------------------------------------------------------
17+
// Tests
18+
// -----------------------------------------------------------------------------
19+
20+
var ruleTester = new RuleTester();
21+
ruleTester.run('jsx-first-prop-new-line', rule, {
22+
23+
valid: [
24+
{
25+
code: '<Foo />',
26+
options: ['never'],
27+
parser: parserOptions
28+
},
29+
{
30+
code: '<Foo prop="bar" />',
31+
options: ['never'],
32+
parser: parserOptions
33+
},
34+
{
35+
code: '<Foo {...this.props} />',
36+
options: ['never'],
37+
parser: parserOptions
38+
},
39+
{
40+
code: '<Foo a a a />',
41+
options: ['never'],
42+
parser: parserOptions
43+
},
44+
{
45+
code: [
46+
'<Foo a',
47+
' b ',
48+
'/>'
49+
].join('\n'),
50+
options: ['never'],
51+
parser: parserOptions
52+
},
53+
{
54+
code: '<Foo />',
55+
options: ['multiline'],
56+
parser: parserOptions
57+
},
58+
{
59+
code: '<Foo prop="one" />',
60+
options: ['multiline'],
61+
parser: parserOptions
62+
},
63+
{
64+
code: '<Foo {...this.props} />',
65+
options: ['multiline'],
66+
parser: parserOptions
67+
},
68+
{
69+
code: '<Foo a a a />',
70+
options: ['multiline'],
71+
parser: parserOptions
72+
},
73+
{
74+
code: [
75+
'<Foo',
76+
' propOne="one"',
77+
' propTwo="two"',
78+
'/>'
79+
].join('\n'),
80+
options: ['multiline'],
81+
parser: parserOptions
82+
},
83+
{
84+
code: [
85+
'<Foo',
86+
' {...this.props}',
87+
' propTwo="two"',
88+
'/>'
89+
].join('\n'),
90+
options: ['multiline'],
91+
parser: parserOptions
92+
},
93+
{
94+
code: '<Foo />',
95+
options: ['always'],
96+
parser: parserOptions
97+
},
98+
{
99+
code: [
100+
'<Foo',
101+
' propOne="one"',
102+
' propTwo="two"',
103+
'/>'
104+
].join('\n'),
105+
options: ['always'],
106+
parser: parserOptions
107+
},
108+
{
109+
code: [
110+
'<Foo',
111+
' {...this.props}',
112+
' propTwo="two"',
113+
'/>'
114+
].join('\n'),
115+
options: ['always'],
116+
parser: parserOptions
117+
}
118+
],
119+
120+
invalid: [
121+
{
122+
code: '<Foo prop="one" />',
123+
options: ['always'],
124+
errors: [{message: 'Property should be placed on a new line'}],
125+
parser: parserOptions
126+
},
127+
{
128+
code: [
129+
'<Foo propOne="one"',
130+
' propTwo="two"',
131+
'/>'
132+
].join('\n'),
133+
options: ['always'],
134+
errors: [{message: 'Property should be placed on a new line'}],
135+
parser: parserOptions
136+
},
137+
{
138+
code: [
139+
'<Foo',
140+
' propOne="one"',
141+
' propTwo="two"',
142+
'/>'
143+
].join('\n'),
144+
options: ['never'],
145+
errors: [{message: 'Property should be placed on the same line as the component declaration'}],
146+
parser: parserOptions
147+
}
148+
]
149+
});

0 commit comments

Comments
 (0)