Skip to content

Commit 06e1667

Browse files
authored
Merge pull request #1519 from pfhayes/spaces
New: jsx-child-element-spacing proposal Fixes #1515.
2 parents b801624 + 7def79f commit 06e1667

File tree

3 files changed

+302
-2
lines changed

3 files changed

+302
-2
lines changed

index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ const allRules = {
1010
'forbid-component-props': require('./lib/rules/forbid-component-props'),
1111
'forbid-dom-props': require('./lib/rules/forbid-dom-props'),
1212
'forbid-elements': require('./lib/rules/forbid-elements'),
13-
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
1413
'forbid-foreign-prop-types': require('./lib/rules/forbid-foreign-prop-types'),
14+
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
1515
'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
16+
'jsx-child-element-spacing': require('./lib/rules/jsx-child-element-spacing'),
1617
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
1718
'jsx-closing-tag-location': require('./lib/rules/jsx-closing-tag-location'),
1819
'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'),
@@ -24,12 +25,12 @@ const allRules = {
2425
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
2526
'jsx-key': require('./lib/rules/jsx-key'),
2627
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
27-
'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'),
2828
'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
2929
'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
3030
'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
3131
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
3232
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
33+
'jsx-one-expression-per-line': require('./lib/rules/jsx-one-expression-per-line'),
3334
'button-has-type': require('./lib/rules/button-has-type'),
3435
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
3536
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'),
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict';
2+
3+
// This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
4+
const INLINE_ELEMENTS = new Set([
5+
'a',
6+
'abbr',
7+
'acronym',
8+
'b',
9+
'bdo',
10+
'big',
11+
'br',
12+
'button',
13+
'cite',
14+
'code',
15+
'dfn',
16+
'em',
17+
'i',
18+
'img',
19+
'input',
20+
'kbd',
21+
'label',
22+
'map',
23+
'object',
24+
'q',
25+
'samp',
26+
'script',
27+
'select',
28+
'small',
29+
'span',
30+
'strong',
31+
'sub',
32+
'sup',
33+
'textarea',
34+
'tt',
35+
'var'
36+
]);
37+
38+
module.exports = {
39+
meta: {
40+
docs: {
41+
description: 'Ensures inline tags are not rendered without spaces between them',
42+
category: 'Stylistic Issues',
43+
recommended: false
44+
},
45+
fixable: false,
46+
schema: [
47+
{
48+
type: 'object',
49+
properties: {},
50+
default: {},
51+
additionalProperties: false
52+
}
53+
]
54+
},
55+
create: function (context) {
56+
const elementName = node => (
57+
node.openingElement &&
58+
node.openingElement.name &&
59+
node.openingElement.name.type === 'JSXIdentifier' &&
60+
node.openingElement.name.name
61+
);
62+
63+
const isInlineElement = node => (
64+
node.type === 'JSXElement' &&
65+
INLINE_ELEMENTS.has(elementName(node))
66+
);
67+
68+
const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
69+
const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
70+
71+
return {
72+
JSXElement: function(node) {
73+
let lastChild = null;
74+
let child = null;
75+
(node.children.concat([null])).forEach(nextChild => {
76+
if (
77+
(lastChild || nextChild) &&
78+
(!lastChild || isInlineElement(lastChild)) &&
79+
(child && child.type === 'Literal') &&
80+
(!nextChild || isInlineElement(nextChild)) &&
81+
true
82+
) {
83+
if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
84+
context.report({
85+
node: child,
86+
loc: child.loc,
87+
message: `Ambiguous spacing after previous element ${elementName(lastChild)}`
88+
});
89+
} else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
90+
context.report({
91+
node: child,
92+
loc: child.loc,
93+
message: `Ambiguous spacing before next element ${elementName(nextChild)}`
94+
});
95+
}
96+
}
97+
lastChild = child;
98+
child = nextChild;
99+
});
100+
}
101+
};
102+
}
103+
};
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
'use strict';
2+
3+
const rule = require('../../../lib/rules/jsx-child-element-spacing');
4+
const RuleTester = require('eslint').RuleTester;
5+
const parserOptions = {
6+
sourceType: 'module',
7+
ecmaFeatures: {
8+
jsx: true
9+
}
10+
};
11+
12+
const ruleTester = new RuleTester({parserOptions});
13+
ruleTester.run('jsx-child-element-spacing', rule, {
14+
valid: [{
15+
code: `
16+
<App>
17+
foo
18+
</App>
19+
`
20+
}, {
21+
code: `
22+
<App>
23+
<a>bar</a>
24+
</App>
25+
`
26+
}, {
27+
code: `
28+
<App>
29+
<a>
30+
<b>nested</b>
31+
</a>
32+
</App>
33+
`
34+
}, {
35+
code: `
36+
<App>
37+
foo
38+
bar
39+
</App>
40+
`
41+
}, {
42+
code: `
43+
<App>
44+
foo<a>bar</a>baz
45+
</App>
46+
`
47+
}, {
48+
code: `
49+
<App>
50+
foo
51+
{' '}
52+
<a>bar</a>
53+
{' '}
54+
baz
55+
</App>
56+
`
57+
}, {
58+
code: `
59+
<App>
60+
foo
61+
{' '}<a>bar</a>{' '}
62+
baz
63+
</App>
64+
`
65+
}, {
66+
code: `
67+
<App>
68+
foo{' '}
69+
<a>bar</a>
70+
{' '}baz
71+
</App>
72+
`
73+
}, {
74+
code: `
75+
<App>
76+
foo{/*
77+
*/}<a>bar</a>{/*
78+
*/}baz
79+
</App>
80+
`
81+
}, {
82+
code: `
83+
<App>
84+
Please take a look at <a href="https://js.org">this link</a>.
85+
</App>
86+
`
87+
}, {
88+
code: `
89+
<App>
90+
Please take a look at
91+
{' '}
92+
<a href="https://js.org">this link</a>.
93+
</App>
94+
`
95+
}, {
96+
code: `
97+
<App>
98+
<p>A</p>
99+
<p>B</p>
100+
</App>
101+
`
102+
}, {
103+
code: `
104+
<App>
105+
<p>A</p><p>B</p>
106+
</App>
107+
`
108+
}, {
109+
code: `
110+
<App>
111+
<a>foo</a>
112+
<a>bar</a>
113+
</App>
114+
`
115+
}, {
116+
code: `
117+
<App>
118+
<a>
119+
<b>nested1</b>
120+
<b>nested2</b>
121+
</a>
122+
</App>
123+
`
124+
}, {
125+
code: `
126+
<App>
127+
A
128+
B
129+
</App>
130+
`
131+
}],
132+
133+
invalid: [{
134+
code: `
135+
<App>
136+
foo
137+
<a>bar</a>
138+
</App>
139+
`,
140+
errors: [
141+
{message: 'Ambiguous spacing before next element a'}
142+
]
143+
}, {
144+
code: `
145+
<App>
146+
<a>bar</a>
147+
baz
148+
</App>
149+
`,
150+
errors: [
151+
{message: 'Ambiguous spacing after previous element a'}
152+
]
153+
}, {
154+
code: `
155+
<App>
156+
{' '}<a>bar</a>
157+
baz
158+
</App>
159+
`,
160+
errors: [
161+
{message: 'Ambiguous spacing after previous element a'}
162+
]
163+
}, {
164+
code: `
165+
<App>
166+
Please take a look at
167+
<a href="https://js.org">this link</a>.
168+
</App>
169+
`,
170+
errors: [
171+
{message: 'Ambiguous spacing before next element a'}
172+
]
173+
}, {
174+
code: `
175+
<App>
176+
Some <code>loops</code> and some
177+
<code>if</code> statements.
178+
</App>
179+
`,
180+
errors: [
181+
{message: 'Ambiguous spacing before next element code'}
182+
]
183+
}, {
184+
code: `
185+
<App>
186+
Here is
187+
<a href="https://js.org">a link</a> and here is
188+
<a href="https://js.org">another</a>
189+
</App>
190+
`,
191+
errors: [
192+
{message: 'Ambiguous spacing before next element a'},
193+
{message: 'Ambiguous spacing before next element a'}
194+
]
195+
}]
196+
});

0 commit comments

Comments
 (0)