1 | 'use strict';
|
2 |
|
3 | const docsUrl = require('../util/docsUrl');
|
4 |
|
5 |
|
6 | const INLINE_ELEMENTS = new Set([
|
7 | 'a',
|
8 | 'abbr',
|
9 | 'acronym',
|
10 | 'b',
|
11 | 'bdo',
|
12 | 'big',
|
13 | 'br',
|
14 | 'button',
|
15 | 'cite',
|
16 | 'code',
|
17 | 'dfn',
|
18 | 'em',
|
19 | 'i',
|
20 | 'img',
|
21 | 'input',
|
22 | 'kbd',
|
23 | 'label',
|
24 | 'map',
|
25 | 'object',
|
26 | 'q',
|
27 | 'samp',
|
28 | 'script',
|
29 | 'select',
|
30 | 'small',
|
31 | 'span',
|
32 | 'strong',
|
33 | 'sub',
|
34 | 'sup',
|
35 | 'textarea',
|
36 | 'tt',
|
37 | 'var'
|
38 | ]);
|
39 |
|
40 | module.exports = {
|
41 | meta: {
|
42 | docs: {
|
43 | description: 'Ensures inline tags are not rendered without spaces between them',
|
44 | category: 'Stylistic Issues',
|
45 | recommended: false,
|
46 | url: docsUrl('jsx-child-element-spacing')
|
47 | },
|
48 | fixable: null,
|
49 | schema: [
|
50 | {
|
51 | type: 'object',
|
52 | properties: {},
|
53 | default: {},
|
54 | additionalProperties: false
|
55 | }
|
56 | ]
|
57 | },
|
58 | create(context) {
|
59 | const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
|
60 | const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
|
61 |
|
62 | const elementName = (node) => (
|
63 | node.openingElement
|
64 | && node.openingElement.name
|
65 | && node.openingElement.name.type === 'JSXIdentifier'
|
66 | && node.openingElement.name.name
|
67 | );
|
68 |
|
69 | const isInlineElement = (node) => (
|
70 | node.type === 'JSXElement'
|
71 | && INLINE_ELEMENTS.has(elementName(node))
|
72 | );
|
73 |
|
74 | const handleJSX = (node) => {
|
75 | let lastChild = null;
|
76 | let child = null;
|
77 | (node.children.concat([null])).forEach((nextChild) => {
|
78 | if (
|
79 | (lastChild || nextChild)
|
80 | && (!lastChild || isInlineElement(lastChild))
|
81 | && (child && (child.type === 'Literal' || child.type === 'JSXText'))
|
82 | && (!nextChild || isInlineElement(nextChild))
|
83 | && true
|
84 | ) {
|
85 | if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
|
86 | context.report({
|
87 | node: lastChild,
|
88 | loc: lastChild.loc.end,
|
89 | message: `Ambiguous spacing after previous element ${elementName(lastChild)}`
|
90 | });
|
91 | } else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
|
92 | context.report({
|
93 | node: nextChild,
|
94 | loc: nextChild.loc.start,
|
95 | message: `Ambiguous spacing before next element ${elementName(nextChild)}`
|
96 | });
|
97 | }
|
98 | }
|
99 | lastChild = child;
|
100 | child = nextChild;
|
101 | });
|
102 | };
|
103 |
|
104 | return {
|
105 | JSXElement: handleJSX,
|
106 | JSXFragment: handleJSX
|
107 | };
|
108 | }
|
109 | };
|