Skip to content

Commit 7bdc856

Browse files
committed
rework internals to check children of JSXElement
1 parent 799dfa6 commit 7bdc856

File tree

2 files changed

+559
-96
lines changed

2 files changed

+559
-96
lines changed

lib/rules/jsx-one-element-per-line.js

Lines changed: 93 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,83 +23,111 @@ module.exports = {
2323
create: function (context) {
2424
const sourceCode = context.getSourceCode();
2525

26-
function generateFixFunction(elementInLine) {
27-
const nodeText = sourceCode.getText(elementInLine);
28-
29-
return function(fixer) {
30-
return fixer.replaceText(elementInLine, `\n${nodeText}`);
31-
};
32-
}
33-
34-
function elementDoesOpenOrCloseOnLine (element, line) {
35-
const reportableLines = [element.openingElement, element.closingElement].reduce((lines, el) => {
36-
if (!el) {
37-
return lines;
38-
}
39-
40-
return lines.concat([el.loc.start.line, el.loc.end.line]);
41-
}, []);
42-
43-
44-
return reportableLines.indexOf(line) !== -1;
45-
}
46-
47-
function anyElementDoesOpenOrCloseOnLine (elements, line) {
48-
return elements.some(element => elementDoesOpenOrCloseOnLine(element, line));
49-
}
50-
5126
return {
52-
JSXOpeningElement: function (node) {
53-
if (!node.parent) {
27+
JSXElement: function (node) {
28+
const children = node.children;
29+
30+
if (!children || !children.length) {
5431
return;
5532
}
5633

57-
const openingStartLine = node.loc.start.line;
34+
const openingElement = node.openingElement;
35+
const closingElement = node.closingElement;
36+
const openingElementEndLine = openingElement.loc.end.line;
37+
const closingElementStartLine = closingElement.loc.start.line;
5838

59-
// Parent
60-
if (node.parent.parent && node.parent.parent.openingElement) {
61-
const parentOpeningStartLine = node.parent.parent.openingElement.loc.end.line;
39+
const childrenGroupedByLine = {};
6240

63-
if (parentOpeningStartLine === openingStartLine) {
64-
context.report({
65-
node: node,
66-
message: `Opening tag for Element \`${node.name.name}\` must be placed on a new line`,
67-
fix: generateFixFunction(node)
68-
});
69-
}
70-
}
41+
children.forEach(child => {
42+
let countNewLinesBeforeContent = 0;
43+
let countNewLinesAfterContent = 0;
7144

72-
// Siblings
73-
if (node.parent.parent && node.parent.parent.children && node.parent.parent.children.length) {
74-
const firstSiblingOnLine = node.parent.parent.children.find(sibling => (
75-
sibling.type === 'JSXElement' && elementDoesOpenOrCloseOnLine(sibling, openingStartLine)
76-
));
45+
if (child.type === 'Literal') {
46+
if (child.value.match(/^\s*$/)) {
47+
return;
48+
}
7749

78-
if (firstSiblingOnLine !== node.parent) {
79-
context.report({
80-
node: node,
81-
message: `Opening tag for Element \`${node.name.name}\` must be placed on a new line`,
82-
fix: generateFixFunction(node)
83-
});
50+
countNewLinesBeforeContent = (child.raw.match(/^ *\n/g) || []).length;
51+
countNewLinesAfterContent = (child.raw.match(/\n *$/g) || []).length;
8452
}
85-
}
86-
},
87-
88-
JSXClosingElement: function (node) {
89-
if (!node.parent || !node.parent.children.length) {
90-
return;
91-
}
9253

93-
const closingElementStartLine = node.loc.end.line;
54+
const startLine = child.loc.start.line + countNewLinesBeforeContent;
55+
const endLine = child.loc.end.line - countNewLinesAfterContent;
56+
57+
if (startLine === endLine) {
58+
if (!childrenGroupedByLine[startLine]) {
59+
childrenGroupedByLine[startLine] = [];
60+
}
61+
childrenGroupedByLine[startLine].push(child);
62+
} else {
63+
if (!childrenGroupedByLine[startLine]) {
64+
childrenGroupedByLine[startLine] = [];
65+
}
66+
childrenGroupedByLine[startLine].push(child);
67+
if (!childrenGroupedByLine[endLine]) {
68+
childrenGroupedByLine[endLine] = [];
69+
}
70+
childrenGroupedByLine[endLine].push(child);
71+
}
72+
});
9473

95-
if (!anyElementDoesOpenOrCloseOnLine(node.parent.children, closingElementStartLine)) {
96-
return;
97-
}
74+
Object.keys(childrenGroupedByLine).forEach(line => {
75+
line = parseInt(line, 10);
76+
const firstIndex = 0;
77+
const lastIndex = childrenGroupedByLine[line].length - 1;
78+
79+
childrenGroupedByLine[line].forEach((child, i) => {
80+
let prevChild;
81+
if (i === firstIndex) {
82+
if (line === openingElementEndLine) {
83+
prevChild = openingElement;
84+
}
85+
} else {
86+
prevChild = childrenGroupedByLine[line][i - 1];
87+
}
88+
let nextChild;
89+
if (i === lastIndex) {
90+
if (line === closingElementStartLine) {
91+
nextChild = closingElement;
92+
}
93+
} else {
94+
// We don't need to append a trailing because the next child will prepend a leading.
95+
// nextChild = childrenGroupedByLine[line][i + 1];
96+
}
97+
98+
function spaceBetweenPrev () {
99+
return (prevChild.type === 'Literal' && prevChild.raw.match(/ $/)) ||
100+
(child.type === 'Literal' && child.raw.match(/^ /)) ||
101+
sourceCode.isSpaceBetweenTokens(prevChild, child);
102+
}
103+
function spaceBetweenNext () {
104+
return (nextChild.type === 'Literal' && nextChild.raw.match(/^ /)) ||
105+
(child.type === 'Literal' && child.raw.match(/ $/)) ||
106+
sourceCode.isSpaceBetweenTokens(child, nextChild);
107+
}
108+
const leadingSpace = prevChild && spaceBetweenPrev() ? '\n{\' \'}' : '';
109+
const trailingSpace = nextChild && spaceBetweenNext() ? '{\' \'}\n' : '';
110+
const leadingNewLine = prevChild ? '\n' : '';
111+
const trailingNewLine = nextChild ? '\n' : '';
112+
113+
if (!prevChild && !nextChild) {
114+
return;
115+
}
116+
117+
const source = sourceCode.getText(child);
118+
119+
function nodeDescriptor (n) {
120+
return n.openingElement ? n.openingElement.name.name : source.replace(/\n/g, '');
121+
}
98122

99-
context.report({
100-
node: node,
101-
message: `Closing tag for Element \`${node.name.name}\` must be placed on a new line`,
102-
fix: generateFixFunction(node)
123+
context.report({
124+
node: child,
125+
message: `\`${nodeDescriptor(child)}\` must be placed on a new line`,
126+
fix: function (fixer) {
127+
return fixer.replaceText(child, `${leadingSpace}${leadingNewLine}${source}${trailingNewLine}${trailingSpace}`);
128+
}
129+
});
130+
});
103131
});
104132
}
105133
};

0 commit comments

Comments
 (0)