Skip to content

Commit be33738

Browse files
committed
Ad jsx-intent rule (fixes #342)
1 parent d28048f commit be33738

File tree

5 files changed

+411
-0
lines changed

5 files changed

+411
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Finally, enable all of the rules that you would like to use.
6767
"react/jsx-curly-spacing": 1,
6868
"react/jsx-handler-names": 1,
6969
"react/jsx-indent-props": 1,
70+
"react/jsx-indent": 1,
7071
"react/jsx-key": 1,
7172
"react/jsx-max-props-per-line": 1,
7273
"react/jsx-no-bind": 1,
@@ -109,6 +110,7 @@ Finally, enable all of the rules that you would like to use.
109110
* [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
110111
* [jsx-handler-names](docs/rules/jsx-handler-names.md): Enforce event handler naming conventions in JSX
111112
* [jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX
113+
* [jsx-indent](docs/rules/jsx-indent.md): Validate JSX indentation
112114
* [jsx-key](docs/rules/jsx-key.md): Validate JSX has key prop when in array or iterator
113115
* [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
114116
* [jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props

docs/rules/jsx-indent.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Validate JSX indentation (jsx-indent)
2+
3+
This option validates a specific indentation style for JSX.
4+
5+
## Rule Details
6+
7+
This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`.
8+
9+
The following patterns are considered warnings:
10+
11+
```jsx
12+
// 2 spaces indentation
13+
<App>
14+
<Hello />
15+
</App>
16+
17+
// no indentation
18+
<App>
19+
<Hello />
20+
</App>
21+
22+
// 1 tab indentation
23+
<App>
24+
<Hello />
25+
</App>
26+
```
27+
28+
## Rule Options
29+
30+
It takes an option as the second parameter which can be `"tab"` for tab-based indentation or a positive number for space indentations.
31+
32+
```js
33+
...
34+
"jsx-indent": [<enabled>, 'tab'|<number>]
35+
...
36+
```
37+
38+
The following patterns are considered warnings:
39+
40+
```jsx
41+
// 2 spaces indentation
42+
// [2, 2]
43+
<App>
44+
<Hello />
45+
</App>
46+
47+
// tab indentation
48+
// [2, 'tab']
49+
<App>
50+
<Hello />
51+
</App>
52+
```
53+
54+
The following patterns are not warnings:
55+
56+
```jsx
57+
58+
// 2 spaces indentation
59+
// [2, 2]
60+
<App>
61+
<Hello />
62+
</App>
63+
64+
// tab indentation
65+
// [2, 'tab']
66+
<App>
67+
<Hello />
68+
</App>
69+
70+
// no indentation
71+
// [2, 0]
72+
<App>
73+
<Hello />
74+
</App>
75+
```
76+
77+
## When not to use
78+
79+
If you are not using JSX then you can disable this rule.

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module.exports = {
3232
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
3333
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
3434
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
35+
'jsx-indent': require('./lib/rules/jsx-indent'),
3536
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
3637
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
3738
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
@@ -70,6 +71,7 @@ module.exports = {
7071
'jsx-max-props-per-line': 0,
7172
'jsx-no-literals': 0,
7273
'jsx-indent-props': 0,
74+
'jsx-indent': 0,
7375
'jsx-closing-bracket-location': 0,
7476
'no-direct-mutation-state': 0,
7577
'forbid-prop-types': 0,

lib/rules/jsx-indent.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* @fileoverview Validate props indentation in JSX
3+
* @author Yannick Croissant
4+
5+
* This rule has been ported and modified from eslint and nodeca.
6+
* @author Vitaly Puzrin
7+
* @author Gyandeep Singh
8+
* @copyright 2015 Vitaly Puzrin. All rights reserved.
9+
* @copyright 2015 Gyandeep Singh. All rights reserved.
10+
Copyright (C) 2014 by Vitaly Puzrin
11+
12+
Permission is hereby granted, free of charge, to any person obtaining a copy
13+
of this software and associated documentation files (the 'Software'), to deal
14+
in the Software without restriction, including without limitation the rights
15+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
copies of the Software, and to permit persons to whom the Software is
17+
furnished to do so, subject to the following conditions:
18+
19+
The above copyright notice and this permission notice shall be included in
20+
all copies or substantial portions of the Software.
21+
22+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28+
THE SOFTWARE.
29+
*/
30+
'use strict';
31+
32+
// ------------------------------------------------------------------------------
33+
// Rule Definition
34+
// ------------------------------------------------------------------------------
35+
module.exports = function(context) {
36+
37+
var MESSAGE = 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.';
38+
39+
var extraColumnStart = 0;
40+
var indentType = 'space';
41+
var indentSize = 4;
42+
43+
if (context.options.length) {
44+
if (context.options[0] === 'tab') {
45+
indentSize = 1;
46+
indentType = 'tab';
47+
} else if (typeof context.options[0] === 'number') {
48+
indentSize = context.options[0];
49+
indentType = 'space';
50+
}
51+
}
52+
53+
/**
54+
* Reports a given indent violation and properly pluralizes the message
55+
* @param {ASTNode} node Node violating the indent rule
56+
* @param {Number} needed Expected indentation character count
57+
* @param {Number} gotten Indentation character count in the actual node/code
58+
* @param {Object=} loc Error line and column location
59+
*/
60+
function report(node, needed, gotten, loc) {
61+
var msgContext = {
62+
needed: needed,
63+
type: indentType,
64+
characters: needed === 1 ? 'character' : 'characters',
65+
gotten: gotten
66+
};
67+
68+
if (loc) {
69+
context.report(node, loc, MESSAGE, msgContext);
70+
} else {
71+
context.report(node, MESSAGE, msgContext);
72+
}
73+
}
74+
75+
/**
76+
* Get node indent
77+
* @param {ASTNode} node Node to examine
78+
* @param {Boolean} byLastLine get indent of node's last line
79+
* @param {Boolean} excludeCommas skip comma on start of line
80+
* @return {Number} Indent
81+
*/
82+
function getNodeIndent(node, byLastLine, excludeCommas) {
83+
byLastLine = byLastLine || false;
84+
excludeCommas = excludeCommas || false;
85+
86+
var src = context.getSource(node, node.loc.start.column + extraColumnStart);
87+
var lines = src.split('\n');
88+
if (byLastLine) {
89+
src = lines[lines.length - 1];
90+
} else {
91+
src = lines[0];
92+
}
93+
94+
var skip = excludeCommas ? ',' : '';
95+
96+
var regExp;
97+
if (indentType === 'space') {
98+
regExp = new RegExp('^[ ' + skip + ']+');
99+
} else {
100+
regExp = new RegExp('^[\t' + skip + ']+');
101+
}
102+
103+
var indent = regExp.exec(src);
104+
return indent ? indent[0].length : 0;
105+
}
106+
107+
/**
108+
* Checks node is the first in its own start line. By default it looks by start line.
109+
* @param {ASTNode} node The node to check
110+
* @return {Boolean} true if its the first in the its start line
111+
*/
112+
function isNodeFirstInLine(node) {
113+
var token = node;
114+
do {
115+
token = context.getTokenBefore(token);
116+
} while (token.type === 'JSXText');
117+
var startLine = node.loc.start.line;
118+
var endLine = token ? token.loc.end.line : -1;
119+
120+
return startLine !== endLine;
121+
}
122+
123+
/**
124+
* Check indent for nodes list
125+
* @param {ASTNode[]} nodes list of node objects
126+
* @param {Number} indent needed indent
127+
* @param {Boolean} excludeCommas skip comma on start of line
128+
*/
129+
function checkNodesIndent(node, indent, excludeCommas) {
130+
var nodeIndent = getNodeIndent(node, false, excludeCommas);
131+
if (nodeIndent !== indent && isNodeFirstInLine(node)) {
132+
report(node, indent, nodeIndent);
133+
}
134+
}
135+
136+
return {
137+
JSXOpeningElement: function(node) {
138+
if (!node.parent || !node.parent.parent) {
139+
return;
140+
}
141+
var parentElementIndent = getNodeIndent(node.parent.parent);
142+
var indent = node.parent.parent.loc.start.line === node.loc.start.line ? 0 : indentSize;
143+
checkNodesIndent(node, parentElementIndent + indent);
144+
},
145+
JSXClosingElement: function(node) {
146+
if (!node.parent) {
147+
return;
148+
}
149+
var peerElementIndent = getNodeIndent(node.parent.openingElement);
150+
checkNodesIndent(node, peerElementIndent);
151+
}
152+
};
153+
154+
};
155+
156+
module.exports.schema = [{
157+
oneOf: [{
158+
enum: ['tab']
159+
}, {
160+
type: 'integer'
161+
}]
162+
}];

0 commit comments

Comments
 (0)