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 |
|
31 | ;
|
32 |
|
33 | const astUtil = require('../util/ast');
|
34 | const docsUrl = require('../util/docsUrl');
|
35 |
|
36 | // ------------------------------------------------------------------------------
|
37 | // Rule Definition
|
38 | // ------------------------------------------------------------------------------
|
39 | module.exports = {
|
40 | meta: {
|
41 | docs: {
|
42 | description: 'Validate props indentation in JSX',
|
43 | category: 'Stylistic Issues',
|
44 | recommended: false,
|
45 | url: docsUrl('jsx-indent-props')
|
46 | },
|
47 | fixable: 'code',
|
48 |
|
49 | schema: [{
|
50 | oneOf: [{
|
51 | enum: ['tab', 'first']
|
52 | }, {
|
53 | type: 'integer'
|
54 | }]
|
55 | }]
|
56 | },
|
57 |
|
58 | create(context) {
|
59 | const MESSAGE = 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.';
|
60 |
|
61 | const extraColumnStart = 0;
|
62 | let indentType = 'space';
|
63 | /** @type {number|'first'} */
|
64 | let indentSize = 4;
|
65 |
|
66 | if (context.options.length) {
|
67 | if (context.options[0] === 'first') {
|
68 | indentSize = 'first';
|
69 | indentType = 'space';
|
70 | } else if (context.options[0] === 'tab') {
|
71 | indentSize = 1;
|
72 | indentType = 'tab';
|
73 | } else if (typeof context.options[0] === 'number') {
|
74 | indentSize = context.options[0];
|
75 | indentType = 'space';
|
76 | }
|
77 | }
|
78 |
|
79 | /**
|
80 | * Reports a given indent violation and properly pluralizes the message
|
81 | * @param {ASTNode} node Node violating the indent rule
|
82 | * @param {Number} needed Expected indentation character count
|
83 | * @param {Number} gotten Indentation character count in the actual node/code
|
84 | */
|
85 | function report(node, needed, gotten) {
|
86 | const msgContext = {
|
87 | needed,
|
88 | type: indentType,
|
89 | characters: needed === 1 ? 'character' : 'characters',
|
90 | gotten
|
91 | };
|
92 |
|
93 | context.report({
|
94 | node,
|
95 | message: MESSAGE,
|
96 | data: msgContext,
|
97 | fix(fixer) {
|
98 | return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]],
|
99 | Array(needed + 1).join(indentType === 'space' ? ' ' : '\t'));
|
100 | }
|
101 | });
|
102 | }
|
103 |
|
104 | /**
|
105 | * Get node indent
|
106 | * @param {ASTNode} node Node to examine
|
107 | * @return {Number} Indent
|
108 | */
|
109 | function getNodeIndent(node) {
|
110 | let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart);
|
111 | const lines = src.split('\n');
|
112 | src = lines[0];
|
113 |
|
114 | let regExp;
|
115 | if (indentType === 'space') {
|
116 | regExp = /^[ ]+/;
|
117 | } else {
|
118 | regExp = /^[\t]+/;
|
119 | }
|
120 |
|
121 | const indent = regExp.exec(src);
|
122 | return indent ? indent[0].length : 0;
|
123 | }
|
124 |
|
125 | /**
|
126 | * Check indent for nodes list
|
127 | * @param {ASTNode[]} nodes list of node objects
|
128 | * @param {Number} indent needed indent
|
129 | */
|
130 | function checkNodesIndent(nodes, indent) {
|
131 | nodes.forEach((node) => {
|
132 | const nodeIndent = getNodeIndent(node);
|
133 | if (
|
134 | node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression'
|
135 | && nodeIndent !== indent && astUtil.isNodeFirstInLine(context, node)
|
136 | ) {
|
137 | report(node, indent, nodeIndent);
|
138 | }
|
139 | });
|
140 | }
|
141 |
|
142 | return {
|
143 | JSXOpeningElement(node) {
|
144 | if (!node.attributes.length) {
|
145 | return;
|
146 | }
|
147 | let propIndent;
|
148 | if (indentSize === 'first') {
|
149 | const firstPropNode = node.attributes[0];
|
150 | propIndent = firstPropNode.loc.start.column;
|
151 | } else {
|
152 | const elementIndent = getNodeIndent(node);
|
153 | propIndent = elementIndent + indentSize;
|
154 | }
|
155 | checkNodesIndent(node.attributes, propIndent);
|
156 | }
|
157 | };
|
158 | }
|
159 | };
|