Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] Support shorthand fragment syntax #1956

Merged
merged 16 commits into from Sep 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 34 additions & 31 deletions lib/rules/jsx-child-element-spacing.js
Expand Up @@ -56,6 +56,9 @@ module.exports = {
]
},
create: function (context) {
const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;

const elementName = node => (
node.openingElement &&
node.openingElement.name &&
Expand All @@ -68,39 +71,39 @@ module.exports = {
INLINE_ELEMENTS.has(elementName(node))
);

const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
const handleJSX = node => {
let lastChild = null;
let child = null;
(node.children.concat([null])).forEach(nextChild => {
if (
(lastChild || nextChild) &&
(!lastChild || isInlineElement(lastChild)) &&
(child && (child.type === 'Literal' || child.type === 'JSXText')) &&
(!nextChild || isInlineElement(nextChild)) &&
true
) {
if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
context.report({
node: lastChild,
loc: lastChild.loc.end,
message: `Ambiguous spacing after previous element ${elementName(lastChild)}`
});
} else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
context.report({
node: nextChild,
loc: nextChild.loc.start,
message: `Ambiguous spacing before next element ${elementName(nextChild)}`
});
}
}
lastChild = child;
child = nextChild;
});
};

return {
JSXElement: function(node) {
let lastChild = null;
let child = null;
(node.children.concat([null])).forEach(nextChild => {
if (
(lastChild || nextChild) &&
(!lastChild || isInlineElement(lastChild)) &&
(child && (child.type === 'Literal' || child.type === 'JSXText')) &&
(!nextChild || isInlineElement(nextChild)) &&
true
) {
if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
context.report({
node: lastChild,
loc: lastChild.loc.end,
message: `Ambiguous spacing after previous element ${elementName(lastChild)}`
});
} else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
context.report({
node: nextChild,
loc: nextChild.loc.start,
message: `Ambiguous spacing before next element ${elementName(nextChild)}`
});
}
}
lastChild = child;
child = nextChild;
});
}
JSXElement: handleJSX,
JSXFragment: handleJSX
};
}
};
71 changes: 37 additions & 34 deletions lib/rules/jsx-closing-tag-location.js
Expand Up @@ -22,45 +22,48 @@ module.exports = {
},

create: function(context) {
return {
JSXClosingElement: function(node) {
if (!node.parent) {
return;
}

const opening = node.parent.openingElement;
if (opening.loc.start.line === node.loc.start.line) {
return;
}
function handleClosingElement(node) {
if (!node.parent) {
return;
}

if (opening.loc.start.column === node.loc.start.column) {
return;
}
const opening = node.parent.openingElement || node.parent.openingFragment;
if (opening.loc.start.line === node.loc.start.line) {
return;
}

let message;
if (!astUtil.isNodeFirstInLine(context, node)) {
message = 'Closing tag of a multiline JSX expression must be on its own line.';
} else {
message = 'Expected closing tag to match indentation of opening.';
}
if (opening.loc.start.column === node.loc.start.column) {
return;
}

context.report({
node: node,
loc: node.loc,
message,
fix: function(fixer) {
const indent = Array(opening.loc.start.column + 1).join(' ');
if (astUtil.isNodeFirstInLine(context, node)) {
return fixer.replaceTextRange(
[node.range[0] - node.loc.start.column, node.range[0]],
indent
);
}
let message;
if (!astUtil.isNodeFirstInLine(context, node)) {
message = 'Closing tag of a multiline JSX expression must be on its own line.';
} else {
message = 'Expected closing tag to match indentation of opening.';
}

return fixer.insertTextBefore(node, `\n${indent}`);
context.report({
node: node,
loc: node.loc,
message,
fix: function(fixer) {
const indent = Array(opening.loc.start.column + 1).join(' ');
if (astUtil.isNodeFirstInLine(context, node)) {
return fixer.replaceTextRange(
[node.range[0] - node.loc.start.column, node.range[0]],
indent
);
}
});
}

return fixer.insertTextBefore(node, `\n${indent}`);
}
});
}

return {
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement
};
}
};
20 changes: 9 additions & 11 deletions lib/rules/jsx-curly-brace-presence.js
Expand Up @@ -6,6 +6,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const jsxUtil = require('../util/jsx');

// ------------------------------------------------------------------------------
// Constants
Expand Down Expand Up @@ -168,13 +169,12 @@ module.exports = {
function lintUnnecessaryCurly(JSXExpressionNode) {
const expression = JSXExpressionNode.expression;
const expressionType = expression.type;
const parentType = JSXExpressionNode.parent.type;

if (
(expressionType === 'Literal' || expressionType === 'JSXText') &&
typeof expression.value === 'string' &&
!needToEscapeCharacterForJSX(expression.raw) && (
parentType === 'JSXElement' ||
jsxUtil.isJSX(JSXExpressionNode.parent) ||
!containsQuoteCharacters(expression.value)
)
) {
Expand All @@ -183,32 +183,30 @@ module.exports = {
expressionType === 'TemplateLiteral' &&
expression.expressions.length === 0 &&
!needToEscapeCharacterForJSX(expression.quasis[0].value.raw) && (
parentType === 'JSXElement' ||
jsxUtil.isJSX(JSXExpressionNode.parent) ||
!containsQuoteCharacters(expression.quasis[0].value.cooked)
)
) {
reportUnnecessaryCurly(JSXExpressionNode);
}
}

function areRuleConditionsSatisfied(parentType, config, ruleCondition) {
function areRuleConditionsSatisfied(parent, config, ruleCondition) {
return (
parentType === 'JSXAttribute' &&
parent.type === 'JSXAttribute' &&
typeof config.props === 'string' &&
config.props === ruleCondition
) || (
parentType === 'JSXElement' &&
jsxUtil.isJSX(parent) &&
typeof config.children === 'string' &&
config.children === ruleCondition
);
}

function shouldCheckForUnnecessaryCurly(parent, config) {
const parentType = parent.type;

// If there are more than one JSX child, there is no need to check for
// unnecessary curly braces.
if (parentType === 'JSXElement' && parent.children.length !== 1) {
if (jsxUtil.isJSX(parent) && parent.children.length !== 1) {
return false;
}

Expand All @@ -220,7 +218,7 @@ module.exports = {
return false;
}

return areRuleConditionsSatisfied(parentType, config, OPTION_NEVER);
return areRuleConditionsSatisfied(parent, config, OPTION_NEVER);
}

function shouldCheckForMissingCurly(parent, config) {
Expand All @@ -232,7 +230,7 @@ module.exports = {
return false;
}

return areRuleConditionsSatisfied(parent.type, config, OPTION_ALWAYS);
return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS);
}

// --------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-curly-spacing.js
Expand Up @@ -331,6 +331,7 @@ module.exports = {
break;

case 'JSXElement':
case 'JSXFragment':
config = childrenConfig;
break;

Expand Down
47 changes: 25 additions & 22 deletions lib/rules/jsx-filename-extension.js
Expand Up @@ -43,38 +43,41 @@ module.exports = {
},

create: function(context) {
let invalidExtension;
let invalidNode;

function getExtensionsConfig() {
return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
}

let invalidExtension;
let invalidNode;
function handleJSX(node) {
const filename = context.getFilename();
if (filename === '<text>') {
return;
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
if (invalidNode) {
return;
}

return {
JSXElement: function(node) {
const filename = context.getFilename();
if (filename === '<text>') {
return;
}
const allowedExtensions = getExtensionsConfig();
const isAllowedExtension = allowedExtensions.some(extension => filename.slice(-extension.length) === extension);

if (invalidNode) {
return;
}
if (isAllowedExtension) {
return;
}

const allowedExtensions = getExtensionsConfig();
const isAllowedExtension = allowedExtensions.some(extension => filename.slice(-extension.length) === extension);
invalidNode = node;
invalidExtension = path.extname(filename);
}

if (isAllowedExtension) {
return;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

invalidNode = node;
invalidExtension = path.extname(filename);
},
return {
JSXElement: handleJSX,
JSXFragment: handleJSX,

'Program:exit': function() {
if (!invalidNode) {
Expand Down
77 changes: 41 additions & 36 deletions lib/rules/jsx-indent.js
Expand Up @@ -205,43 +205,48 @@ module.exports = {
}
}

return {
JSXOpeningElement: function(node) {
let prevToken = sourceCode.getTokenBefore(node);
if (!prevToken) {
return;
}
// Use the parent in a list or an array
if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
prevToken = prevToken.type === 'Literal' || prevToken.type === 'JSXText' ? prevToken.parent : prevToken;
// Use the first non-punctuator token in a conditional expression
} else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
do {
prevToken = sourceCode.getTokenBefore(prevToken);
} while (prevToken.type === 'Punctuator');
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
prevToken = prevToken.parent;
}
}
prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;

const parentElementIndent = getNodeIndent(prevToken);
const indent = (
prevToken.loc.start.line === node.loc.start.line ||
isRightInLogicalExp(node) ||
isAlternateInConditionalExp(node)
) ? 0 : indentSize;
checkNodesIndent(node, parentElementIndent + indent);
},
JSXClosingElement: function(node) {
if (!node.parent) {
return;
function handleOpeningElement(node) {
let prevToken = sourceCode.getTokenBefore(node);
if (!prevToken) {
return;
}
// Use the parent in a list or an array
if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
prevToken = prevToken.type === 'Literal' || prevToken.type === 'JSXText' ? prevToken.parent : prevToken;
// Use the first non-punctuator token in a conditional expression
} else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
do {
prevToken = sourceCode.getTokenBefore(prevToken);
} while (prevToken.type === 'Punctuator' && prevToken.value !== '/');
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
prevToken = prevToken.parent;
}
const peerElementIndent = getNodeIndent(node.parent.openingElement);
checkNodesIndent(node, peerElementIndent);
},
}
prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
const parentElementIndent = getNodeIndent(prevToken);
const indent = (
prevToken.loc.start.line === node.loc.start.line ||
isRightInLogicalExp(node) ||
isAlternateInConditionalExp(node)
) ? 0 : indentSize;
checkNodesIndent(node, parentElementIndent + indent);
}

function handleClosingElement(node) {
if (!node.parent) {
return;
}
const peerElementIndent = getNodeIndent(node.parent.openingElement || node.parent.openingFragment);
checkNodesIndent(node, peerElementIndent);
}

return {
JSXOpeningElement: handleOpeningElement,
JSXOpeningFragment: handleOpeningElement,
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement,
JSXExpressionContainer: function(node) {
if (!node.parent) {
return;
Expand Down