-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
Copy pathjsx-props-no-multi-spaces.js
144 lines (121 loc) · 3.81 KB
/
jsx-props-no-multi-spaces.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* @fileoverview Disallow multiple spaces between inline JSX props
* @author Adrian Moennich
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const eslintUtil = require('../util/eslint');
const report = require('../util/report');
const propsUtil = require('../util/props');
const getSourceCode = eslintUtil.getSourceCode;
const getText = eslintUtil.getText;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”',
onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow multiple spaces between inline JSX props',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('jsx-props-no-multi-spaces'),
},
fixable: 'code',
messages,
schema: [],
},
create(context) {
const sourceCode = getSourceCode(context);
function getPropName(propNode) {
switch (propNode.type) {
case 'JSXSpreadAttribute':
return getText(context, propNode.argument);
case 'JSXIdentifier':
return propNode.name;
case 'JSXMemberExpression':
return `${getPropName(propNode.object)}.${propNode.property.name}`;
default:
return propNode.name
? propNode.name.name
: `${getText(context, propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser
}
}
// First and second must be adjacent nodes
function hasEmptyLines(first, second) {
const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : [];
const nodes = [].concat(first, comments, second);
for (let i = 1; i < nodes.length; i += 1) {
const prev = nodes[i - 1];
const curr = nodes[i];
if (curr.loc.start.line - prev.loc.end.line >= 2) {
return true;
}
}
return false;
}
function checkSpacing(prev, node) {
if (hasEmptyLines(prev, node)) {
report(context, messages.noLineGap, 'noLineGap', {
node,
data: {
prop1: getPropName(prev),
prop2: getPropName(node),
},
});
}
if (prev.loc.end.line !== node.loc.end.line) {
return;
}
const between = getSourceCode(context).text.slice(prev.range[1], node.range[0]);
if (between !== ' ') {
report(context, messages.onlyOneSpace, 'onlyOneSpace', {
node,
data: {
prop1: getPropName(prev),
prop2: getPropName(node),
},
fix(fixer) {
return fixer.replaceTextRange([prev.range[1], node.range[0]], ' ');
},
});
}
}
function containsGenericType(node) {
const nodeTypeArguments = propsUtil.getTypeArguments(node);
if (typeof nodeTypeArguments === 'undefined') {
return false;
}
return nodeTypeArguments.type === 'TSTypeParameterInstantiation';
}
function getGenericNode(node) {
const name = node.name;
if (containsGenericType(node)) {
const nodeTypeArguments = propsUtil.getTypeArguments(node);
return Object.assign(
{},
node,
{
range: [
name.range[0],
nodeTypeArguments.range[1],
],
}
);
}
return name;
}
return {
JSXOpeningElement(node) {
node.attributes.reduce((prev, prop) => {
checkSpacing(prev, prop);
return prop;
}, getGenericNode(node));
},
};
},
};