-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
Copy pathjsx-props-no-multi-spaces.js
135 lines (114 loc) · 3.56 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
/**
* @fileoverview Disallow multiple spaces between inline JSX props
* @author Adrian Moennich
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”',
onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”',
};
module.exports = {
meta: {
type: 'layout',
docs: {
description: 'Disallow multiple spaces between inline JSX props',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('jsx-props-no-multi-spaces'),
},
fixable: 'whitespace',
messages,
schema: [],
},
create(context) {
const sourceCode = context.getSourceCode();
function getPropName(propNode) {
switch (propNode.type) {
case 'JSXSpreadAttribute':
return context.getSourceCode().getText(propNode.argument);
case 'JSXIdentifier':
return propNode.name;
case 'JSXMemberExpression':
return `${getPropName(propNode.object)}.${propNode.property.name}`;
default:
return propNode.name
? propNode.name.name
: `${context.getSourceCode().getText(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 = context.getSourceCode().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 containsTypeParams = typeof node.typeParameters !== 'undefined';
return containsTypeParams && node.typeParameters.type === 'TSTypeParameterInstantiation';
}
function getGenericNode(node) {
const name = node.name;
if (containsGenericType(node)) {
const type = node.typeParameters;
return Object.assign(
{},
node,
{
range: [
name.range[0],
type.range[1],
],
}
);
}
return name;
}
return {
JSXOpeningElement(node) {
node.attributes.reduce((prev, prop) => {
checkSpacing(prev, prop);
return prop;
}, getGenericNode(node));
},
};
},
};