Skip to content

Commit

Permalink
rework internals to check children of JSXElement
Browse files Browse the repository at this point in the history
  • Loading branch information
TSMMark committed Oct 27, 2017
1 parent 799dfa6 commit 7bdc856
Show file tree
Hide file tree
Showing 2 changed files with 559 additions and 96 deletions.
158 changes: 93 additions & 65 deletions lib/rules/jsx-one-element-per-line.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,83 +23,111 @@ module.exports = {
create: function (context) {
const sourceCode = context.getSourceCode();

function generateFixFunction(elementInLine) {
const nodeText = sourceCode.getText(elementInLine);

return function(fixer) {
return fixer.replaceText(elementInLine, `\n${nodeText}`);
};
}

function elementDoesOpenOrCloseOnLine (element, line) {
const reportableLines = [element.openingElement, element.closingElement].reduce((lines, el) => {
if (!el) {
return lines;
}

return lines.concat([el.loc.start.line, el.loc.end.line]);
}, []);


return reportableLines.indexOf(line) !== -1;
}

function anyElementDoesOpenOrCloseOnLine (elements, line) {
return elements.some(element => elementDoesOpenOrCloseOnLine(element, line));
}

return {
JSXOpeningElement: function (node) {
if (!node.parent) {
JSXElement: function (node) {
const children = node.children;

if (!children || !children.length) {
return;
}

const openingStartLine = node.loc.start.line;
const openingElement = node.openingElement;
const closingElement = node.closingElement;
const openingElementEndLine = openingElement.loc.end.line;
const closingElementStartLine = closingElement.loc.start.line;

// Parent
if (node.parent.parent && node.parent.parent.openingElement) {
const parentOpeningStartLine = node.parent.parent.openingElement.loc.end.line;
const childrenGroupedByLine = {};

if (parentOpeningStartLine === openingStartLine) {
context.report({
node: node,
message: `Opening tag for Element \`${node.name.name}\` must be placed on a new line`,
fix: generateFixFunction(node)
});
}
}
children.forEach(child => {
let countNewLinesBeforeContent = 0;
let countNewLinesAfterContent = 0;

// Siblings
if (node.parent.parent && node.parent.parent.children && node.parent.parent.children.length) {
const firstSiblingOnLine = node.parent.parent.children.find(sibling => (
sibling.type === 'JSXElement' && elementDoesOpenOrCloseOnLine(sibling, openingStartLine)
));
if (child.type === 'Literal') {
if (child.value.match(/^\s*$/)) {
return;
}

if (firstSiblingOnLine !== node.parent) {
context.report({
node: node,
message: `Opening tag for Element \`${node.name.name}\` must be placed on a new line`,
fix: generateFixFunction(node)
});
countNewLinesBeforeContent = (child.raw.match(/^ *\n/g) || []).length;
countNewLinesAfterContent = (child.raw.match(/\n *$/g) || []).length;
}
}
},

JSXClosingElement: function (node) {
if (!node.parent || !node.parent.children.length) {
return;
}

const closingElementStartLine = node.loc.end.line;
const startLine = child.loc.start.line + countNewLinesBeforeContent;
const endLine = child.loc.end.line - countNewLinesAfterContent;

if (startLine === endLine) {
if (!childrenGroupedByLine[startLine]) {
childrenGroupedByLine[startLine] = [];
}
childrenGroupedByLine[startLine].push(child);
} else {
if (!childrenGroupedByLine[startLine]) {
childrenGroupedByLine[startLine] = [];
}
childrenGroupedByLine[startLine].push(child);
if (!childrenGroupedByLine[endLine]) {
childrenGroupedByLine[endLine] = [];
}
childrenGroupedByLine[endLine].push(child);
}
});

if (!anyElementDoesOpenOrCloseOnLine(node.parent.children, closingElementStartLine)) {
return;
}
Object.keys(childrenGroupedByLine).forEach(line => {
line = parseInt(line, 10);
const firstIndex = 0;
const lastIndex = childrenGroupedByLine[line].length - 1;

childrenGroupedByLine[line].forEach((child, i) => {
let prevChild;
if (i === firstIndex) {
if (line === openingElementEndLine) {
prevChild = openingElement;
}
} else {
prevChild = childrenGroupedByLine[line][i - 1];
}
let nextChild;
if (i === lastIndex) {
if (line === closingElementStartLine) {
nextChild = closingElement;
}
} else {
// We don't need to append a trailing because the next child will prepend a leading.
// nextChild = childrenGroupedByLine[line][i + 1];
}

function spaceBetweenPrev () {
return (prevChild.type === 'Literal' && prevChild.raw.match(/ $/)) ||
(child.type === 'Literal' && child.raw.match(/^ /)) ||
sourceCode.isSpaceBetweenTokens(prevChild, child);
}
function spaceBetweenNext () {
return (nextChild.type === 'Literal' && nextChild.raw.match(/^ /)) ||
(child.type === 'Literal' && child.raw.match(/ $/)) ||
sourceCode.isSpaceBetweenTokens(child, nextChild);
}
const leadingSpace = prevChild && spaceBetweenPrev() ? '\n{\' \'}' : '';
const trailingSpace = nextChild && spaceBetweenNext() ? '{\' \'}\n' : '';
const leadingNewLine = prevChild ? '\n' : '';
const trailingNewLine = nextChild ? '\n' : '';

if (!prevChild && !nextChild) {
return;
}

const source = sourceCode.getText(child);

function nodeDescriptor (n) {
return n.openingElement ? n.openingElement.name.name : source.replace(/\n/g, '');
}

context.report({
node: node,
message: `Closing tag for Element \`${node.name.name}\` must be placed on a new line`,
fix: generateFixFunction(node)
context.report({
node: child,
message: `\`${nodeDescriptor(child)}\` must be placed on a new line`,
fix: function (fixer) {
return fixer.replaceText(child, `${leadingSpace}${leadingNewLine}${source}${trailingNewLine}${trailingSpace}`);
}
});
});
});
}
};
Expand Down

0 comments on commit 7bdc856

Please sign in to comment.