diff --git a/lib/rules/jsx-child-element-spacing.js b/lib/rules/jsx-child-element-spacing.js index d6c03c59bf..271423d970 100644 --- a/lib/rules/jsx-child-element-spacing.js +++ b/lib/rules/jsx-child-element-spacing.js @@ -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 && @@ -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 }; } }; diff --git a/lib/rules/jsx-closing-tag-location.js b/lib/rules/jsx-closing-tag-location.js index 81c1f77686..244d3072f5 100644 --- a/lib/rules/jsx-closing-tag-location.js +++ b/lib/rules/jsx-closing-tag-location.js @@ -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 }; } }; diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 88f6fa175a..b609ed2fb4 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -6,6 +6,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); // ------------------------------------------------------------------------------ // Constants @@ -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) ) ) { @@ -183,7 +183,7 @@ 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) ) ) { @@ -191,24 +191,22 @@ module.exports = { } } - 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; } @@ -220,7 +218,7 @@ module.exports = { return false; } - return areRuleConditionsSatisfied(parentType, config, OPTION_NEVER); + return areRuleConditionsSatisfied(parent, config, OPTION_NEVER); } function shouldCheckForMissingCurly(parent, config) { @@ -232,7 +230,7 @@ module.exports = { return false; } - return areRuleConditionsSatisfied(parent.type, config, OPTION_ALWAYS); + return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS); } // -------------------------------------------------------------------------- diff --git a/lib/rules/jsx-curly-spacing.js b/lib/rules/jsx-curly-spacing.js index 81f11c81b2..f1395e5d07 100644 --- a/lib/rules/jsx-curly-spacing.js +++ b/lib/rules/jsx-curly-spacing.js @@ -331,6 +331,7 @@ module.exports = { break; case 'JSXElement': + case 'JSXFragment': config = childrenConfig; break; diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js index fecfa88e64..7e7a7c1439 100644 --- a/lib/rules/jsx-filename-extension.js +++ b/lib/rules/jsx-filename-extension.js @@ -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 === '') { + return; + } - // -------------------------------------------------------------------------- - // Public - // -------------------------------------------------------------------------- + if (invalidNode) { + return; + } - return { - JSXElement: function(node) { - const filename = context.getFilename(); - if (filename === '') { - 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) { diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index cbb9dac84e..1ba0d75cdc 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -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; diff --git a/lib/rules/jsx-max-depth.js b/lib/rules/jsx-max-depth.js index 9341a6d970..02de21ef03 100644 --- a/lib/rules/jsx-max-depth.js +++ b/lib/rules/jsx-max-depth.js @@ -6,6 +6,7 @@ const has = require('has'); const variableUtil = require('../util/variable'); +const jsxUtil = require('../util/jsx'); const docsUrl = require('../util/docsUrl'); // ------------------------------------------------------------------------------ @@ -39,16 +40,12 @@ module.exports = { const option = context.options[0] || {}; const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH; - function isJSXElement(node) { - return node.type === 'JSXElement'; - } - function isExpression(node) { return node.type === 'JSXExpressionContainer'; } function hasJSX(node) { - return isJSXElement(node) || isExpression(node) && isJSXElement(node.expression); + return jsxUtil.isJSX(node) || isExpression(node) && jsxUtil.isJSX(node.expression); } function isLeaf(node) { @@ -60,9 +57,9 @@ module.exports = { function getDepth(node) { let count = 0; - while (isJSXElement(node.parent) || isExpression(node.parent)) { + while (jsxUtil.isJSX(node.parent) || isExpression(node.parent)) { node = node.parent; - if (isJSXElement(node)) { + if (jsxUtil.isJSX(node)) { count++; } } @@ -82,7 +79,7 @@ module.exports = { }); } - function findJSXElement(variables, name) { + function findJSXElementOrFragment(variables, name) { function find(refs) { let i = refs.length; @@ -90,10 +87,10 @@ module.exports = { if (has(refs[i], 'writeExpr')) { const writeExpr = refs[i].writeExpr; - return isJSXElement(writeExpr) + return jsxUtil.isJSX(writeExpr) && writeExpr || writeExpr.type === 'Identifier' - && findJSXElement(variables, writeExpr.name); + && findJSXElementOrFragment(variables, writeExpr.name); } } @@ -119,24 +116,28 @@ module.exports = { }); } + function handleJSX(node) { + if (!isLeaf(node)) { + return; + } + + const depth = getDepth(node); + if (depth > maxDepth) { + report(node, depth); + } + } + return { - JSXElement: function(node) { - if (!isLeaf(node)) { - return; - } + JSXElement: handleJSX, + JSXFragment: handleJSX, - const depth = getDepth(node); - if (depth > maxDepth) { - report(node, depth); - } - }, JSXExpressionContainer: function(node) { if (node.expression.type !== 'Identifier') { return; } const variables = variableUtil.variablesInScope(context); - const element = findJSXElement(variables, node.expression.name); + const element = findJSXElementOrFragment(variables, node.expression.name); if (element) { const baseDepth = getDepth(node); diff --git a/lib/rules/jsx-one-expression-per-line.js b/lib/rules/jsx-one-expression-per-line.js index cc14ad8323..b9bacdacee 100644 --- a/lib/rules/jsx-one-expression-per-line.js +++ b/lib/rules/jsx-one-expression-per-line.js @@ -50,171 +50,174 @@ module.exports = { return n.openingElement ? n.openingElement.name.name : sourceCode.getText(n).replace(/\n/g, ''); } - return { - JSXElement: function (node) { - const children = node.children; - - if (!children || !children.length) { - return; - } + function handleJSX(node) { + const children = node.children; - const openingElement = node.openingElement; - const closingElement = node.closingElement; - const openingElementStartLine = openingElement.loc.start.line; - const openingElementEndLine = openingElement.loc.end.line; - const closingElementStartLine = closingElement.loc.start.line; - const closingElementEndLine = closingElement.loc.end.line; + if (!children || !children.length) { + return; + } - if (children.length === 1) { - const child = children[0]; + const openingElement = node.openingElement || node.openingFragment; + const closingElement = node.closingElement || node.closingFragment; + const openingElementStartLine = openingElement.loc.start.line; + const openingElementEndLine = openingElement.loc.end.line; + const closingElementStartLine = closingElement.loc.start.line; + const closingElementEndLine = closingElement.loc.end.line; + + if (children.length === 1) { + const child = children[0]; + if ( + openingElementStartLine === openingElementEndLine && + openingElementEndLine === closingElementStartLine && + closingElementStartLine === closingElementEndLine && + closingElementEndLine === child.loc.start.line && + child.loc.start.line === child.loc.end.line + ) { if ( - openingElementStartLine === openingElementEndLine && - openingElementEndLine === closingElementStartLine && - closingElementStartLine === closingElementEndLine && - closingElementEndLine === child.loc.start.line && - child.loc.start.line === child.loc.end.line + options.allow === 'single-child' || + options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText') ) { - if ( - options.allow === 'single-child' || - options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText') - ) { - return; - } + return; } } + } - const childrenGroupedByLine = {}; - const fixDetailsByNode = {}; + const childrenGroupedByLine = {}; + const fixDetailsByNode = {}; - children.forEach(child => { - let countNewLinesBeforeContent = 0; - let countNewLinesAfterContent = 0; + children.forEach(child => { + let countNewLinesBeforeContent = 0; + let countNewLinesAfterContent = 0; - if (child.type === 'Literal' || child.type === 'JSXText') { - if (/^\s*$/.test(child.raw)) { - return; - } + if (child.type === 'Literal' || child.type === 'JSXText') { + if (/^\s*$/.test(child.raw)) { + return; + } + + countNewLinesBeforeContent = (child.raw.match(/^ *\n/g) || []).length; + countNewLinesAfterContent = (child.raw.match(/\n *$/g) || []).length; + } + + const startLine = child.loc.start.line + countNewLinesBeforeContent; + const endLine = child.loc.end.line - countNewLinesAfterContent; - countNewLinesBeforeContent = (child.raw.match(/^ *\n/g) || []).length; - countNewLinesAfterContent = (child.raw.match(/\n *$/g) || []).length; + 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); + } + }); - const startLine = child.loc.start.line + countNewLinesBeforeContent; - const endLine = child.loc.end.line - countNewLinesAfterContent; + Object.keys(childrenGroupedByLine).forEach(_line => { + const line = parseInt(_line, 10); + const firstIndex = 0; + const lastIndex = childrenGroupedByLine[line].length - 1; - if (startLine === endLine) { - if (!childrenGroupedByLine[startLine]) { - childrenGroupedByLine[startLine] = []; + childrenGroupedByLine[line].forEach((child, i) => { + let prevChild; + let nextChild; + + if (i === firstIndex) { + if (line === openingElementEndLine) { + prevChild = openingElement; } - childrenGroupedByLine[startLine].push(child); } else { - if (!childrenGroupedByLine[startLine]) { - childrenGroupedByLine[startLine] = []; - } - childrenGroupedByLine[startLine].push(child); - if (!childrenGroupedByLine[endLine]) { - childrenGroupedByLine[endLine] = []; - } - childrenGroupedByLine[endLine].push(child); + prevChild = childrenGroupedByLine[line][i - 1]; } - }); - - Object.keys(childrenGroupedByLine).forEach(_line => { - const line = parseInt(_line, 10); - const firstIndex = 0; - const lastIndex = childrenGroupedByLine[line].length - 1; - - childrenGroupedByLine[line].forEach((child, i) => { - let prevChild; - let nextChild; - - if (i === firstIndex) { - if (line === openingElementEndLine) { - prevChild = openingElement; - } - } else { - prevChild = childrenGroupedByLine[line][i - 1]; - } - 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]; + 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.type === 'JSXText') && / $/.test(prevChild.raw)) || - ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw)) || - sourceCode.isSpaceBetweenTokens(prevChild, child); - } + function spaceBetweenPrev () { + return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && / $/.test(prevChild.raw)) || + ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw)) || + sourceCode.isSpaceBetweenTokens(prevChild, child); + } - function spaceBetweenNext () { - return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && /^ /.test(nextChild.raw)) || - ((child.type === 'Literal' || child.type === 'JSXText') && / $/.test(child.raw)) || - sourceCode.isSpaceBetweenTokens(child, nextChild); - } + function spaceBetweenNext () { + return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && /^ /.test(nextChild.raw)) || + ((child.type === 'Literal' || child.type === 'JSXText') && / $/.test(child.raw)) || + sourceCode.isSpaceBetweenTokens(child, nextChild); + } - if (!prevChild && !nextChild) { - return; - } + if (!prevChild && !nextChild) { + return; + } - const source = sourceCode.getText(child); - const leadingSpace = !!(prevChild && spaceBetweenPrev()); - const trailingSpace = !!(nextChild && spaceBetweenNext()); - const leadingNewLine = !!prevChild; - const trailingNewLine = !!nextChild; + const source = sourceCode.getText(child); + const leadingSpace = !!(prevChild && spaceBetweenPrev()); + const trailingSpace = !!(nextChild && spaceBetweenNext()); + const leadingNewLine = !!prevChild; + const trailingNewLine = !!nextChild; - const key = nodeKey(child); + const key = nodeKey(child); - if (!fixDetailsByNode[key]) { - fixDetailsByNode[key] = { - node: child, - source: source, - descriptor: nodeDescriptor(child) - }; - } + if (!fixDetailsByNode[key]) { + fixDetailsByNode[key] = { + node: child, + source: source, + descriptor: nodeDescriptor(child) + }; + } - if (leadingSpace) { - fixDetailsByNode[key].leadingSpace = true; - } - if (leadingNewLine) { - fixDetailsByNode[key].leadingNewLine = true; - } - if (trailingNewLine) { - fixDetailsByNode[key].trailingNewLine = true; - } - if (trailingSpace) { - fixDetailsByNode[key].trailingSpace = true; - } - }); + if (leadingSpace) { + fixDetailsByNode[key].leadingSpace = true; + } + if (leadingNewLine) { + fixDetailsByNode[key].leadingNewLine = true; + } + if (trailingNewLine) { + fixDetailsByNode[key].trailingNewLine = true; + } + if (trailingSpace) { + fixDetailsByNode[key].trailingSpace = true; + } }); + }); - Object.keys(fixDetailsByNode).forEach(key => { - const details = fixDetailsByNode[key]; + Object.keys(fixDetailsByNode).forEach(key => { + const details = fixDetailsByNode[key]; - const nodeToReport = details.node; - const descriptor = details.descriptor; - const source = details.source.replace(/(^ +| +(?=\n)*$)/g, ''); + const nodeToReport = details.node; + const descriptor = details.descriptor; + const source = details.source.replace(/(^ +| +(?=\n)*$)/g, ''); - const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : ''; - const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : ''; - const leadingNewLineString = details.leadingNewLine ? '\n' : ''; - const trailingNewLineString = details.trailingNewLine ? '\n' : ''; + const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : ''; + const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : ''; + const leadingNewLineString = details.leadingNewLine ? '\n' : ''; + const trailingNewLineString = details.trailingNewLine ? '\n' : ''; - const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`; + const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`; - context.report({ - node: nodeToReport, - message: `\`${descriptor}\` must be placed on a new line`, - fix: function (fixer) { - return fixer.replaceText(nodeToReport, replaceText); - } - }); + context.report({ + node: nodeToReport, + message: `\`${descriptor}\` must be placed on a new line`, + fix: function (fixer) { + return fixer.replaceText(nodeToReport, replaceText); + } }); - } + }); + } + + return { + JSXElement: handleJSX, + JSXFragment: handleJSX }; } }; diff --git a/lib/rules/jsx-uses-react.js b/lib/rules/jsx-uses-react.js index 51ba2fc320..ffafc4049d 100644 --- a/lib/rules/jsx-uses-react.js +++ b/lib/rules/jsx-uses-react.js @@ -25,16 +25,16 @@ module.exports = { create: function(context) { const pragma = pragmaUtil.getFromContext(context); + function handleOpeningElement() { + context.markVariableAsUsed(pragma); + } // -------------------------------------------------------------------------- // Public // -------------------------------------------------------------------------- return { - - JSXOpeningElement: function() { - context.markVariableAsUsed(pragma); - } - + JSXOpeningElement: handleOpeningElement, + JSXOpeningFragment: handleOpeningElement }; } }; diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index ca9b980d62..32d3d08113 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -6,6 +6,7 @@ const has = require('has'); const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); // ------------------------------------------------------------------------------ // Constants @@ -122,7 +123,7 @@ module.exports = { } function check(node, type) { - if (!node || node.type !== 'JSXElement') { + if (!node || !jsxUtil.isJSX(node)) { return; } diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js index db945e859a..1fa88d1f5e 100644 --- a/lib/rules/no-unescaped-entities.js +++ b/lib/rules/no-unescaped-entities.js @@ -5,6 +5,7 @@ 'use strict'; const docsUrl = require('../util/docsUrl'); +const jsxUtil = require('../util/jsx'); // ------------------------------------------------------------------------------ // Rule Definition @@ -72,7 +73,7 @@ module.exports = { return { 'Literal, JSXText': function(node) { - if (node.parent.type === 'JSXElement') { + if (jsxUtil.isJSX(node.parent)) { reportInvalidEntity(node); } } diff --git a/lib/util/Components.js b/lib/util/Components.js index 1859600f35..5961eaae60 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -11,6 +11,7 @@ const variableUtil = require('./variable'); const pragmaUtil = require('./pragma'); const astUtil = require('./ast'); const propTypes = require('./propTypes'); +const jsxUtil = require('./jsx'); function getId(node) { return node && node.range.join(':'); @@ -349,12 +350,12 @@ function componentRule(rule, context) { const returnsConditionalJSXConsequent = node[property] && node[property].type === 'ConditionalExpression' && - node[property].consequent.type === 'JSXElement' + jsxUtil.isJSX(node[property].consequent) ; const returnsConditionalJSXAlternate = node[property] && node[property].type === 'ConditionalExpression' && - node[property].alternate.type === 'JSXElement' + jsxUtil.isJSX(node[property].alternate) ; const returnsConditionalJSX = strict ? @@ -363,7 +364,7 @@ function componentRule(rule, context) { const returnsJSX = node[property] && - node[property].type === 'JSXElement' + jsxUtil.isJSX(node[property]) ; const returnsReactCreateElement = this.isReactCreateElement(node[property]); diff --git a/lib/util/jsx.js b/lib/util/jsx.js index e6c3e007d6..a1484ef8bb 100644 --- a/lib/util/jsx.js +++ b/lib/util/jsx.js @@ -9,7 +9,7 @@ const COMPAT_TAG_REGEX = /^[a-z]|\-/; /** * Checks if a node represents a DOM element. - * @param {String} node - JSXOpeningElement to check. + * @param {object} node - JSXOpeningElement to check. * @returns {boolean} Whether or not the node corresponds to a DOM element. */ function isDOMComponent(node) { @@ -25,6 +25,16 @@ function isDOMComponent(node) { return COMPAT_TAG_REGEX.test(name); } +/** + * Checks if a node represents a JSX element or fragment. + * @param {object} node - node to check. + * @returns {boolean} Whether or not the node if a JSX element or fragment. + */ +function isJSX(node) { + return ['JSXElement', 'JSXFragment'].indexOf(node.type) >= 0; +} + module.exports = { - isDOMComponent: isDOMComponent + isDOMComponent: isDOMComponent, + isJSX: isJSX }; diff --git a/tests/lib/rules/jsx-child-element-spacing.js b/tests/lib/rules/jsx-child-element-spacing.js index fdd19149fb..51c837b18a 100644 --- a/tests/lib/rules/jsx-child-element-spacing.js +++ b/tests/lib/rules/jsx-child-element-spacing.js @@ -17,6 +17,13 @@ ruleTester.run('jsx-child-element-spacing', rule, { foo ` + }, { + code: ` + <> + foo + + `, + parser: 'babel-eslint' }, { code: ` @@ -146,6 +153,21 @@ ruleTester.run('jsx-child-element-spacing', rule, { ] }, { code: ` +<> + foo + bar + + `, + parser: 'babel-eslint', + errors: [ + { + message: 'Ambiguous spacing before next element a', + line: 4, + column: 3 + } + ] + }, { + code: ` bar baz diff --git a/tests/lib/rules/jsx-closing-tag-location.js b/tests/lib/rules/jsx-closing-tag-location.js index 5dda91f58f..6899bfd669 100644 --- a/tests/lib/rules/jsx-closing-tag-location.js +++ b/tests/lib/rules/jsx-closing-tag-location.js @@ -36,6 +36,18 @@ ruleTester.run('jsx-closing-tag-location', rule, { code: ` foo ` + }, { + code: ` + <> + foo + + `, + parser: 'babel-eslint' + }, { + code: ` + <>foo + `, + parser: 'babel-eslint' }], invalid: [{ @@ -61,5 +73,30 @@ ruleTester.run('jsx-closing-tag-location', rule, { `, errors: MESSAGE_OWN_LINE + }, { + code: ` + <> + foo + + `, + parser: 'babel-eslint', + output: ` + <> + foo + + `, + errors: MESSAGE_MATCH_INDENTATION + }, { + code: ` + <> + foo + `, + parser: 'babel-eslint', + output: ` + <> + foo + + `, + errors: MESSAGE_OWN_LINE }] }); diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 59d6563c7d..bfdf422831 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -32,6 +32,10 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo' }, + { + code: '<>foo', + parser: 'babel-eslint' + }, { code: 'foo', options: [{props: 'never'}] @@ -259,6 +263,13 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, + { + code: '<>{`foo`}', + output: '<>foo', + parser: 'babel-eslint', + options: [{children: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: `{'foo'}`, output: 'foo', diff --git a/tests/lib/rules/jsx-curly-spacing.js b/tests/lib/rules/jsx-curly-spacing.js index 1447b7a487..a76150672a 100644 --- a/tests/lib/rules/jsx-curly-spacing.js +++ b/tests/lib/rules/jsx-curly-spacing.js @@ -677,6 +677,9 @@ ruleTester.run('jsx-curly-spacing', rule, { '`}' ].join('\n'), options: [{children: {when: 'never', allowMultiline: false}}] + }, { + code: '<>{bar} {baz};', + parser: 'babel-eslint' }], invalid: [{ @@ -738,6 +741,16 @@ ruleTester.run('jsx-curly-spacing', rule, { }, { message: 'There should be no space before \'}\'' }] + }, { + code: '<>{ bar };', + output: '<>{bar};', + parser: 'babel-eslint', + options: [{children: true}], + errors: [{ + message: 'There should be no space after \'{\'' + }, { + message: 'There should be no space before \'}\'' + }] }, { code: '{ { bar: true, baz: true } };', output: '{{ bar: true, baz: true }};', diff --git a/tests/lib/rules/jsx-filename-extension.js b/tests/lib/rules/jsx-filename-extension.js index 02a2a616a3..485ddfc9c3 100644 --- a/tests/lib/rules/jsx-filename-extension.js +++ b/tests/lib/rules/jsx-filename-extension.js @@ -23,7 +23,8 @@ const parserOptions = { // Code Snippets // ------------------------------------------------------------------------------ -const withJSX = 'module.exports = function MyComponent() { return
\n
\n
; }'; +const withJSXElement = 'module.exports = function MyComponent() { return
\n
\n
; }'; +const withJSXFragment = 'module.exports = function MyComponent() { return <>\n; }'; const withoutJSX = 'module.exports = {}'; // ------------------------------------------------------------------------------ @@ -36,29 +37,54 @@ ruleTester.run('jsx-filename-extension', rule, { valid: [ { filename: '', - code: withJSX + code: withJSXElement }, { filename: 'MyComponent.jsx', - code: withJSX + code: withJSXElement }, { filename: 'MyComponent.js', options: [{extensions: ['.js', '.jsx']}], - code: withJSX + code: withJSXElement }, { filename: 'notAComponent.js', code: withoutJSX + }, { + filename: '', + code: withJSXFragment, + parser: 'babel-eslint' + }, + { + filename: 'MyComponent.jsx', + code: withJSXFragment, + parser: 'babel-eslint' + }, { + filename: 'MyComponent.js', + options: [{extensions: ['.js', '.jsx']}], + code: withJSXFragment, + parser: 'babel-eslint' } ], invalid: [ { filename: 'MyComponent.js', - code: withJSX, + code: withJSXElement, + errors: [{message: 'JSX not allowed in files with extension \'.js\''}] + }, { + filename: 'MyComponent.jsx', + code: withJSXElement, + options: [{extensions: ['.js']}], + errors: [{message: 'JSX not allowed in files with extension \'.jsx\''}] + }, { + filename: 'MyComponent.js', + code: withJSXFragment, + parser: 'babel-eslint', errors: [{message: 'JSX not allowed in files with extension \'.js\''}] }, { filename: 'MyComponent.jsx', - code: withJSX, + code: withJSXFragment, + parser: 'babel-eslint', options: [{extensions: ['.js']}], errors: [{message: 'JSX not allowed in files with extension \'.jsx\''}] } diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js index 520e2b7147..fc44dd4a5a 100644 --- a/tests/lib/rules/jsx-indent.js +++ b/tests/lib/rules/jsx-indent.js @@ -29,11 +29,22 @@ ruleTester.run('jsx-indent', rule, { code: [ '' ].join('\n') + }, { + code: [ + '<>' + ].join('\n'), + parser: 'babel-eslint' }, { code: [ '', '' ].join('\n') + }, { + code: [ + '<>', + '' + ].join('\n'), + parser: 'babel-eslint' }, { code: [ '', @@ -41,6 +52,22 @@ ruleTester.run('jsx-indent', rule, { '' ].join('\n'), options: [2] + }, { + code: [ + '', + ' <>', + '' + ].join('\n'), + parser: 'babel-eslint', + options: [2] + }, { + code: [ + '<>', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ '', @@ -71,6 +98,16 @@ ruleTester.run('jsx-indent', rule, { '}' ].join('\n'), options: [2] + }, { + code: [ + 'function App() {', + ' return ', + ' <>', + ' ;', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ 'function App() {', @@ -80,6 +117,16 @@ ruleTester.run('jsx-indent', rule, { '}' ].join('\n'), options: [2] + }, { + code: [ + 'function App() {', + ' return (', + ' <>', + ' );', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ 'function App() {', @@ -91,6 +138,18 @@ ruleTester.run('jsx-indent', rule, { '}' ].join('\n'), options: [2] + }, { + code: [ + 'function App() {', + ' return (', + ' ', + ' <>', + ' ', + ' );', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ 'it(', @@ -102,6 +161,18 @@ ruleTester.run('jsx-indent', rule, { ')' ].join('\n'), options: [2] + }, { + code: [ + 'it(', + ' (', + '
', + ' <>', + '
', + ' )', + ')' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ 'it(', @@ -132,6 +203,17 @@ ruleTester.run('jsx-indent', rule, { '}' ].join('\n'), options: [2] + }, { + code: [ + '{', + ' head.title &&', + ' <>', + ' {head.title}', + ' ', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ '{', @@ -171,6 +253,15 @@ ruleTester.run('jsx-indent', rule, { ']' ].join('\n'), options: [2] + }, { + code: [ + '[', + ' <>,', + ' <>', + ']' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: [ '
', @@ -193,6 +284,18 @@ ruleTester.run('jsx-indent', rule, { ' }', '
' ].join('\n') + }, { + code: [ + '
', + ' {foo &&', + ' [', + ' <>,', + ' <>', + ' ]', + ' }', + '
' + ].join('\n'), + parser: 'babel-eslint' }, { // Literals indentation is not touched code: [ @@ -203,6 +306,16 @@ ruleTester.run('jsx-indent', rule, { 'bar
', '
' ].join('\n') + }, { + code: [ + '<>', + 'bar <>', + ' bar', + ' bar {foo}', + 'bar ', + '' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the end of the first expression) @@ -211,6 +324,13 @@ ruleTester.run('jsx-indent', rule, { ' :', ' ' ].join('\n') + }, { + code: [ + 'foo ?', + ' <> :', + ' <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the start of the second expression) @@ -219,6 +339,13 @@ ruleTester.run('jsx-indent', rule, { ' ', ' : ' ].join('\n') + }, { + code: [ + 'foo ?', + ' <>', + ' : <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon on its own line) @@ -228,6 +355,14 @@ ruleTester.run('jsx-indent', rule, { ':', ' ' ].join('\n') + }, { + code: [ + 'foo ?', + ' <>', + ':', + ' <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (multiline JSX, colon on its own line) @@ -249,6 +384,12 @@ ruleTester.run('jsx-indent', rule, { 'foo ? :', '' ].join('\n') + }, { + code: [ + 'foo ? <> :', + '<>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (first expression on test line, colon at the start of the second expression) @@ -256,6 +397,12 @@ ruleTester.run('jsx-indent', rule, { 'foo ? ', ': ' ].join('\n') + }, { + code: [ + 'foo ? <>', + ': <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (first expression on test line, colon on its own line) @@ -264,6 +411,13 @@ ruleTester.run('jsx-indent', rule, { ':', '' ].join('\n') + }, { + code: [ + 'foo ? <>', + ':', + '<>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the end of the first expression, parenthesized first expression) @@ -273,6 +427,14 @@ ruleTester.run('jsx-indent', rule, { ') :', ' ' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ') :', + ' <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the start of the second expression, parenthesized first expression) @@ -282,6 +444,14 @@ ruleTester.run('jsx-indent', rule, { ')', ' : ' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ')', + ' : <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon on its own line, parenthesized first expression) @@ -292,6 +462,15 @@ ruleTester.run('jsx-indent', rule, { ':', ' ' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ')', + ':', + ' <>' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the end of the first expression, parenthesized second expression) @@ -301,6 +480,14 @@ ruleTester.run('jsx-indent', rule, { ' ', ' )' ].join('\n') + }, { + code: [ + 'foo ?', + ' <> : (', + ' <>', + ' )' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon on its own line, parenthesized second expression) @@ -311,6 +498,15 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ?', + ' <>', + ': (', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon indented on its own line, parenthesized second expression) @@ -321,6 +517,15 @@ ruleTester.run('jsx-indent', rule, { ' ', ' )' ].join('\n') + }, { + code: [ + 'foo ?', + ' <>', + ' : (', + ' <>', + ' )' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon at the end of the first expression, both expression parenthesized) @@ -331,6 +536,15 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ') : (', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon on its own line, both expression parenthesized) @@ -342,6 +556,16 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ')', + ': (', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (colon on its own line, both expression parenthesized) @@ -354,6 +578,17 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ? (', + ' <>', + ')', + ':', + '(', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (first expression on test line, colon at the end of the first expression, parenthesized second expression) @@ -362,6 +597,13 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ? <> : (', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (first expression on test line, colon at the start of the second expression, parenthesized second expression) @@ -369,6 +611,12 @@ ruleTester.run('jsx-indent', rule, { 'foo ? ', ': ()' ].join('\n') + }, { + code: [ + 'foo ? <>', + ': (<>)' + ].join('\n'), + parser: 'babel-eslint' }, { // Multiline ternary // (first expression on test line, colon on its own line, parenthesized second expression) @@ -378,6 +626,14 @@ ruleTester.run('jsx-indent', rule, { ' ', ')' ].join('\n') + }, { + code: [ + 'foo ? <>', + ': (', + ' <>', + ')' + ].join('\n'), + parser: 'babel-eslint' }, { code: [ '', @@ -416,6 +672,21 @@ ruleTester.run('jsx-indent', rule, { '}' ].join('\n'), options: [2] + }, { + code: [ + 'function foo() {', + ' ', + ' {condition ?', + ' :', + ' <>', + ' }', + ' ', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [2] }, { code: ` class Test extends React.Component { @@ -430,6 +701,21 @@ ruleTester.run('jsx-indent', rule, { } `, options: [2] + }, { + code: ` + class Test extends React.Component { + render() { + return ( + <> + <> + <> + + ); + } + } + `, + parser: 'babel-eslint', + options: [2] }], invalid: [{ @@ -444,6 +730,32 @@ ruleTester.run('jsx-indent', rule, { '' ].join('\n'), errors: [{message: 'Expected indentation of 4 space characters but found 2.'}] + }, { + code: [ + '', + ' <>', + '' + ].join('\n'), + parser: 'babel-eslint', + output: [ + '', + ' <>', + '' + ].join('\n'), + errors: [{message: 'Expected indentation of 4 space characters but found 2.'}] + }, { + code: [ + '<>', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint', + output: [ + '<>', + ' ', + '' + ].join('\n'), + errors: [{message: 'Expected indentation of 4 space characters but found 2.'}] }, { code: [ '', @@ -635,6 +947,24 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 2 space characters but found 4.'} ] + }, { + code: [ + '[', + '
,', + ' <>', + ']' + ].join('\n'), + parser: 'babel-eslint', + output: [ + '[', + '
,', + ' <>', + ']' + ].join('\n'), + options: [2], + errors: [ + {message: 'Expected indentation of 2 space characters but found 4.'} + ] }, { code: [ '\n', @@ -729,6 +1059,21 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ?', + ' :', + '<>' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ?', + ' :', + ' <>' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (colon on its own line) @@ -761,6 +1106,23 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 0 space characters but found 4.'} ] + }, { + code: [ + 'foo ?', + ' ', + ':', + '<>' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ?', + ' ', + ':', + ' <>' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (first expression on test line, colon on its own line) @@ -795,6 +1157,23 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ? (', + ' ', + ') :', + '<>' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ? (', + ' ', + ') :', + ' <>' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (colon on its own line, parenthesized first expression) @@ -833,6 +1212,23 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 8 space characters but found 4.'} ] + }, { + code: [ + 'foo ?', + ' : (', + ' <>', + ' )' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ?', + ' : (', + ' <>', + ' )' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 8 space characters but found 4.'} + ] }, { // Multiline ternary // (colon on its own line, parenthesized second expression) @@ -873,6 +1269,25 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 8 space characters but found 4.'} ] + }, { + code: [ + 'foo ?', + ' ', + ' : (', + ' <>', + ' )' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ?', + ' ', + ' : (', + ' <>', + ' )' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 8 space characters but found 4.'} + ] }, { // Multiline ternary // (colon at the end of the first expression, both expression parenthesized) @@ -894,6 +1309,26 @@ ruleTester.run('jsx-indent', rule, { {message: 'Expected indentation of 4 space characters but found 0.'}, {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ? (', + '<>', + ') : (', + '<>', + ')' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ? (', + ' <>', + ') : (', + ' <>', + ')' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'}, + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (colon on its own line, both expression parenthesized) @@ -942,6 +1377,30 @@ ruleTester.run('jsx-indent', rule, { {message: 'Expected indentation of 4 space characters but found 0.'}, {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ? (', + '<>', + ')', + ':', + '(', + '<>', + ')' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ? (', + ' <>', + ')', + ':', + '(', + ' <>', + ')' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'}, + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (first expression on test line, colon at the end of the first expression, parenthesized second expression) @@ -958,6 +1417,21 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ? : (', + '<>', + ')' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ? : (', + ' <>', + ')' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { // Multiline ternary // (first expression on test line, colon on its own line, parenthesized second expression) @@ -976,6 +1450,23 @@ ruleTester.run('jsx-indent', rule, { errors: [ {message: 'Expected indentation of 4 space characters but found 0.'} ] + }, { + code: [ + 'foo ? ', + ': (', + '<>', + ')' + ].join('\n'), + parser: 'babel-eslint', + output: [ + 'foo ? ', + ': (', + ' <>', + ')' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] }, { code: [ '

', diff --git a/tests/lib/rules/jsx-max-depth.js b/tests/lib/rules/jsx-max-depth.js index 0b2ceb1a9c..2519b54dbe 100644 --- a/tests/lib/rules/jsx-max-depth.js +++ b/tests/lib/rules/jsx-max-depth.js @@ -61,6 +61,26 @@ ruleTester.run('jsx-max-depth', rule, { }, { code: 'const foo = (x) =>

{x}
;', options: [{max: 2}] + }, { + code: [ + '<>' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + '<>', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint', + options: [{max: 1}] + }, { + code: [ + 'const x = <>x;', + '<>{x}' + ].join('\n'), + parser: 'babel-eslint', + options: [{max: 2}] }], invalid: [{ @@ -121,6 +141,39 @@ ruleTester.run('jsx-max-depth', rule, { '{
}', '
' ].join('\n'), + parser: 'babel-eslint', errors: [{message: 'Expected the depth of nested jsx elements to be <= 2, but found 3.'}] + }, { + code: [ + '<>', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint', + options: [{max: 0}], + errors: [{message: 'Expected the depth of nested jsx elements to be <= 0, but found 1.'}] + }, { + code: [ + '<>', + ' <>', + ' ', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint', + options: [{max: 1}], + errors: [{message: 'Expected the depth of nested jsx elements to be <= 1, but found 2.'}] + }, { + code: [ + 'const x = <>;', + 'let y = x;', + '<>{x}-{y}' + ].join('\n'), + parser: 'babel-eslint', + options: [{max: 1}], + errors: [ + {message: 'Expected the depth of nested jsx elements to be <= 1, but found 2.'}, + {message: 'Expected the depth of nested jsx elements to be <= 1, but found 2.'} + ] }] }); diff --git a/tests/lib/rules/jsx-no-comment-textnodes.js b/tests/lib/rules/jsx-no-comment-textnodes.js index 5691d1aaf1..5126038b8f 100644 --- a/tests/lib/rules/jsx-no-comment-textnodes.js +++ b/tests/lib/rules/jsx-no-comment-textnodes.js @@ -40,6 +40,19 @@ ruleTester.run('jsx-no-comment-textnodes', rule, { } `, parser: 'babel-eslint' + }, { + code: ` + class Comp1 extends Component { + render() { + return ( + <> + {/* valid */} + + ); + } + } + `, + parser: 'babel-eslint' }, { code: ` class Comp1 extends Component { @@ -125,6 +138,18 @@ ruleTester.run('jsx-no-comment-textnodes', rule, { `, parser: 'babel-eslint' }, + { + code: ` + + `, + parser: 'babel-eslint' + }, + { + code: ` + <> + `, + parser: 'babel-eslint' + }, { code: ` @@ -158,6 +183,16 @@ ruleTester.run('jsx-no-comment-textnodes', rule, { `, parser: 'babel-eslint', errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + }, { + code: ` + class Comp1 extends Component { + render() { + return (<>// invalid); + } + } + `, + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] }, { code: ` class Comp1 extends Component { diff --git a/tests/lib/rules/jsx-no-literals.js b/tests/lib/rules/jsx-no-literals.js index 869466c265..7edac90594 100644 --- a/tests/lib/rules/jsx-no-literals.js +++ b/tests/lib/rules/jsx-no-literals.js @@ -41,6 +41,19 @@ ruleTester.run('jsx-no-literals', rule, { } `, parser: 'babel-eslint' + }, { + code: ` + class Comp1 extends Component { + render() { + return ( + <> + {'asdjfl'} + + ); + } + } + `, + parser: 'babel-eslint' }, { code: ` class Comp1 extends Component { @@ -189,6 +202,16 @@ ruleTester.run('jsx-no-literals', rule, { `, parser: 'babel-eslint', errors: [{message: 'Missing JSX expression container around literal string'}] + }, { + code: ` + class Comp1 extends Component { + render() { + return (<>test); + } + } + `, + parser: 'babel-eslint', + errors: [{message: 'Missing JSX expression container around literal string'}] }, { code: ` class Comp1 extends Component { diff --git a/tests/lib/rules/jsx-one-expression-per-line.js b/tests/lib/rules/jsx-one-expression-per-line.js index 0ca57b8882..9e653e8542 100644 --- a/tests/lib/rules/jsx-one-expression-per-line.js +++ b/tests/lib/rules/jsx-one-expression-per-line.js @@ -102,6 +102,24 @@ ruleTester.run('jsx-one-expression-per-line', rule, { }, { code: '', options: [{allow: 'single-child'}] + }, { + code: '<>', + parser: 'babel-eslint' + }, { + code: [ + '<>', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint' + }, { + code: [ + '<>', + ' ', + ' ', + '' + ].join('\n'), + parser: 'babel-eslint' }], invalid: [{ @@ -949,5 +967,45 @@ ruleTester.run('jsx-one-expression-per-line', rule, { '' ].join('\n'), errors: [{message: '`foobar` must be placed on a new line'}] + }, { + code: '<>{"foo"}', + output: [ + '<>', + '{"foo"}', + '' + ].join('\n'), + errors: [{message: '`{"foo"}` must be placed on a new line'}], + parser: 'babel-eslint', + parserOptions: parserOptions + }, { + code: [ + '', + ' <>', + '' + ].join('\n'), + output: [ + '', + ' ', + '<>', + '' + ].join('\n'), + errors: [{message: '`<>` must be placed on a new line'}], + parser: 'babel-eslint', + parserOptions: parserOptions + }, { + code: [ + '<', + '>', + '' + ].join('\n'), + output: [ + '<', + '>', + '', + '' + ].join('\n'), + errors: [{message: '`Foo` must be placed on a new line'}], + parser: 'babel-eslint', + parserOptions: parserOptions }] }); diff --git a/tests/lib/rules/jsx-uses-react.js b/tests/lib/rules/jsx-uses-react.js index 1cb516ba18..d8874e91d6 100644 --- a/tests/lib/rules/jsx-uses-react.js +++ b/tests/lib/rules/jsx-uses-react.js @@ -39,7 +39,8 @@ ruleTester.run('no-unused-vars', rule, { {code: '/*eslint jsx-uses-react:1*/ var React;
;'}, {code: '/*eslint jsx-uses-react:1*/ var React; (function () {
})();'}, {code: '/*eslint jsx-uses-react:1*/ /** @jsx Foo */ var Foo;
;'}, - {code: '/*eslint jsx-uses-react:1*/ var Foo;
;', settings: settings} + {code: '/*eslint jsx-uses-react:1*/ var Foo;
;', settings: settings}, + {code: '/*eslint jsx-uses-react:1*/ var React; <>;', parser: 'babel-eslint'} ], invalid: [{ code: '/*eslint jsx-uses-react:1*/ var React;', @@ -49,6 +50,12 @@ ruleTester.run('no-unused-vars', rule, { errors: [{message: '\'React\' is defined but never used.'}] }, { code: '/*eslint jsx-uses-react:1*/ var React;
;', - errors: [{message: '\'React\' is defined but never used.'}], settings: settings + errors: [{message: '\'React\' is defined but never used.'}], + settings: settings + }, { + code: '/*eslint jsx-uses-react:1*/ var React; <>;', + parser: 'babel-eslint', + errors: [{message: '\'React\' is defined but never used.'}], + settings: settings }] }); diff --git a/tests/lib/rules/jsx-wrap-multilines.js b/tests/lib/rules/jsx-wrap-multilines.js index 450ee782e6..0351df1831 100644 --- a/tests/lib/rules/jsx-wrap-multilines.js +++ b/tests/lib/rules/jsx-wrap-multilines.js @@ -44,6 +44,16 @@ const RETURN_PAREN = ` }); `; +const RETURN_PAREN_FRAGMENT = ` + var Hello = createReactClass({ + render: function() { + return (<> +

Hello {this.props.name}

+ ); + } + }); +`; + const RETURN_NO_PAREN = ` var Hello = createReactClass({ render: function() { @@ -54,6 +64,16 @@ const RETURN_NO_PAREN = ` }); `; +const RETURN_NO_PAREN_FRAGMENT = ` + var Hello = createReactClass({ + render: function() { + return <> +

Hello {this.props.name}

+ ; + } + }); +`; + const RETURN_PAREN_NEW_LINE = ` var Hello = createReactClass({ render: function() { @@ -66,8 +86,30 @@ const RETURN_PAREN_NEW_LINE = ` }); `; +const RETURN_PAREN_NEW_LINE_FRAGMENT = ` + var Hello = createReactClass({ + render: function() { + return ( + <> +

Hello {this.props.name}

+ + ); + } + }); +`; + +const RETURN_SINGLE_LINE_FRAGMENT = ` + var Hello = createReactClass({ + render: function() { + return <>Hello {this.props.name}; + } + }); +`; + const DECLARATION_TERNARY_SINGLE_LINE = 'var hello = foo ?

Hello

:

Hi

;'; +const DECLARATION_TERNARY_SINGLE_LINE_FRAGMENT = 'var hello = foo ? <>Hello : <>Hi;'; + const DECLARATION_TERNARY_PAREN = ` var hello = foo ? (

Hello

@@ -76,6 +118,14 @@ const DECLARATION_TERNARY_PAREN = `
); `; +const DECLARATION_TERNARY_PAREN_FRAGMENT = ` + var hello = foo ? (<> +

Hello

+ ) : (<> +

Hi

+ ); +`; + const DECLARATION_TERNARY_NO_PAREN = ` var hello = foo ?

Hello

@@ -84,6 +134,14 @@ const DECLARATION_TERNARY_NO_PAREN = `
; `; +const DECLARATION_TERNARY_NO_PAREN_FRAGMENT = ` + var hello = foo ? <> +

Hello

+ : <> +

Hi

+ ; +`; + const DECLARATION_TERNARY_PAREN_NEW_LINE = ` var hello = foo ? (
@@ -107,6 +165,15 @@ const ASSIGNMENT_TERNARY_PAREN = `
); `; +const ASSIGNMENT_TERNARY_PAREN_FRAGMENT = ` + var hello; + hello = foo ? (<> +

Hello

+ ) : (<> +

Hi

+ ); +`; + const ASSIGNMENT_TERNARY_NO_PAREN = ` var hello; hello = foo ?
@@ -116,6 +183,15 @@ const ASSIGNMENT_TERNARY_NO_PAREN = `
; `; +const ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT = ` + var hello; + hello = foo ? <> +

Hello

+ : <> +

Hi

+ ; +`; + const ASSIGNMENT_TERNARY_PAREN_NEW_LINE = ` var hello; hello = foo ? ( @@ -137,12 +213,24 @@ const DECLARATION_PAREN = `
); `; +const DECLARATION_PAREN_FRAGMENT = ` + var hello = (<> +

Hello

+ ); +`; + const DECLARATION_NO_PAREN = ` var hello =

Hello

; `; +const DECLARATION_NO_PAREN_FRAGMENT = ` + var hello = <> +

Hello

+ ; +`; + const DECLARATION_PAREN_NEW_LINE = ` var hello = (
@@ -160,6 +248,13 @@ const ASSIGNMENT_PAREN = `
); `; +const ASSIGNMENT_PAREN_FRAGMENT = ` + var hello; + hello = (<> +

Hello

+ ); +`; + const ASSIGNMENT_NO_PAREN = ` var hello; hello =
@@ -167,6 +262,13 @@ const ASSIGNMENT_NO_PAREN = `
; `; +const ASSIGNMENT_NO_PAREN_FRAGMENT = ` + var hello; + hello = <> +

Hello

+ ; +`; + const ASSIGNMENT_PAREN_NEW_LINE = ` var hello; hello = ( @@ -184,12 +286,24 @@ const ARROW_PAREN = `
); `; +const ARROW_PAREN_FRAGMENT = ` + var hello = () => (<> +

Hello

+ ); +`; + const ARROW_NO_PAREN = ` var hello = () =>

Hello

; `; +const ARROW_NO_PAREN_FRAGMENT = ` + var hello = () => <> +

Hello

+ ; +`; + const ARROW_PAREN_NEW_LINE = ` var hello = () => (
@@ -208,6 +322,14 @@ const CONDITION_PAREN = `
`; +const CONDITION_PAREN_FRAGMENT = ` +
+ {foo ? (<> +

Hello

+ ) : null} +
+`; + const CONDITION_NO_PAREN = `
{foo ?
@@ -216,6 +338,14 @@ const CONDITION_NO_PAREN = `
`; +const CONDITION_NO_PAREN_FRAGMENT = ` +
+ {foo ? <> +

Hello

+ : null} +
+`; + const CONDITION_PAREN_NEW_LINE = `
{foo ? ( @@ -238,6 +368,16 @@ const LOGICAL_PAREN = `
`; +const LOGICAL_PAREN_FRAGMENT = ` +
+ {foo && + (<> +

Hello World

+ ) + } +
+`; + const LOGICAL_NO_PAREN = `
{foo && @@ -248,6 +388,16 @@ const LOGICAL_NO_PAREN = `
`; +const LOGICAL_NO_PAREN_FRAGMENT = ` +
+ {foo && + <> +

Hello World

+ + } +
+`; + const LOGICAL_PAREN_NEW_LINE_AUTOFIX = `
{foo && ( @@ -258,6 +408,16 @@ const LOGICAL_PAREN_NEW_LINE_AUTOFIX = `
`; +const LOGICAL_PAREN_NEW_LINE_AUTOFIX_FRAGMENT = ` +
+ {foo && ( +<> +

Hello World

+ +)} +
+`; + const LOGICAL_PAREN_NEW_LINE = `
{foo && ( @@ -280,6 +440,16 @@ const ATTR_PAREN = `
`; +const ATTR_PAREN_FRAGMENT = ` +
+

Hello

+ ) + }> +

Hello

+
+`; + const ATTR_NO_PAREN = `
@@ -290,6 +460,16 @@ const ATTR_NO_PAREN = `
`; +const ATTR_NO_PAREN_FRAGMENT = ` +
+

Hello

+ + }> +

Hello

+
+`; + const ATTR_PAREN_NEW_LINE = `
@@ -310,6 +490,16 @@ const ATTR_PAREN_NEW_LINE_AUTOFIX = `
`; +const ATTR_PAREN_NEW_LINE_AUTOFIX_FRAGMENT = ` +
+

Hello

+ +)}> +

Hello

+
+`; + function addNewLineSymbols(code) { return code.replace(/\(\)/g, '>\n)'); } @@ -324,11 +514,21 @@ ruleTester.run('jsx-wrap-multilines', rule, { valid: [ { code: RETURN_SINGLE_LINE + }, { + code: RETURN_SINGLE_LINE_FRAGMENT, + parser: 'babel-eslint' }, { code: RETURN_PAREN + }, { + code: RETURN_PAREN, + parser: 'babel-eslint' }, { code: RETURN_SINGLE_LINE, options: [{return: true}] + }, { + code: RETURN_SINGLE_LINE_FRAGMENT, + parser: 'babel-eslint', + options: [{return: true}] }, { code: RETURN_PAREN, options: [{return: true}] @@ -340,11 +540,18 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{return: false}] }, { code: DECLARATION_TERNARY_SINGLE_LINE + }, { + code: DECLARATION_TERNARY_SINGLE_LINE_FRAGMENT, + parser: 'babel-eslint' }, { code: DECLARATION_TERNARY_PAREN }, { code: DECLARATION_TERNARY_SINGLE_LINE, options: [{declaration: true}] + }, { + code: DECLARATION_TERNARY_SINGLE_LINE, + parser: 'babel-eslint', + options: [{declaration: true}] }, { code: DECLARATION_TERNARY_PAREN, options: [{declaration: true}] @@ -374,6 +581,9 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: DECLARATION_SINGLE_LINE }, { code: DECLARATION_PAREN + }, { + code: DECLARATION_PAREN_FRAGMENT, + parser: 'babel-eslint' }, { code: DECLARATION_SINGLE_LINE, options: [{declaration: true}] @@ -383,6 +593,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { }, { code: DECLARATION_NO_PAREN, options: [{declaration: 'ignore'}] + }, { + code: DECLARATION_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{declaration: 'ignore'}] }, { code: DECLARATION_NO_PAREN, options: [{declaration: false}] @@ -394,28 +608,46 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{declaration: false}] }, { code: ASSIGNMENT_PAREN + }, { + code: ASSIGNMENT_PAREN_FRAGMENT, + parser: 'babel-eslint' }, { code: ASSIGNMENT_PAREN, options: [{assignment: true}] }, { code: ASSIGNMENT_NO_PAREN, options: [{assignment: 'ignore'}] + }, { + code: ASSIGNMENT_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{assignment: 'ignore'}] }, { code: ASSIGNMENT_NO_PAREN, options: [{assignment: false}] }, { code: ARROW_PAREN + }, { + code: ARROW_PAREN_FRAGMENT, + parser: 'babel-eslint' }, { code: ARROW_SINGLE_LINE }, { code: ARROW_PAREN, options: [{arrow: true}] + }, { + code: ARROW_PAREN, + parser: 'babel-eslint', + options: [{arrow: true}] }, { code: ARROW_SINGLE_LINE, options: [{arrow: true}] }, { code: ARROW_NO_PAREN, options: [{arrow: 'ignore'}] + }, { + code: ARROW_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{arrow: 'ignore'}] }, { code: ARROW_NO_PAREN, options: [{arrow: false}] @@ -429,6 +661,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { }, { code: CONDITION_PAREN, options: [{condition: true}] + }, { + code: CONDITION_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{condition: true}] }, { code: LOGICAL_SINGLE_LINE }, { @@ -436,6 +672,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { }, { code: LOGICAL_PAREN, options: [{logical: true}] + }, { + code: LOGICAL_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{logical: true}] }, { code: ATTR_SINGLE_LINE }, { @@ -443,9 +683,17 @@ ruleTester.run('jsx-wrap-multilines', rule, { }, { code: ATTR_PAREN, options: [{prop: true}] + }, { + code: ATTR_PAREN_FRAGMENT, + parser: 'babel-eslint', + options: [{prop: true}] }, { code: RETURN_PAREN_NEW_LINE, options: [{return: 'parens-new-line'}] + }, { + code: RETURN_PAREN_NEW_LINE_FRAGMENT, + parser: 'babel-eslint', + options: [{return: 'parens-new-line'}] }, { code: DECLARATION_TERNARY_PAREN_NEW_LINE, options: [{declaration: 'parens-new-line'}] @@ -478,11 +726,22 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: RETURN_NO_PAREN, output: RETURN_PAREN, errors: [{message: MISSING_PARENS}] + }, { + code: RETURN_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: RETURN_PAREN_FRAGMENT, + errors: [{message: MISSING_PARENS}] }, { code: RETURN_NO_PAREN, output: RETURN_PAREN, options: [{return: true}], errors: [{message: MISSING_PARENS}] + }, { + code: RETURN_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: RETURN_PAREN_FRAGMENT, + options: [{return: true}], + errors: [{message: MISSING_PARENS}] }, { code: DECLARATION_TERNARY_NO_PAREN, output: DECLARATION_TERNARY_PAREN, @@ -490,6 +749,14 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: DECLARATION_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: DECLARATION_TERNARY_PAREN_FRAGMENT, + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] }, { code: DECLARATION_TERNARY_NO_PAREN, output: DECLARATION_TERNARY_PAREN, @@ -498,6 +765,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: DECLARATION_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: DECLARATION_TERNARY_PAREN_FRAGMENT, + options: [{declaration: true}], + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] }, { code: ASSIGNMENT_TERNARY_NO_PAREN, output: ASSIGNMENT_TERNARY_PAREN, @@ -505,6 +781,14 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ASSIGNMENT_TERNARY_PAREN_FRAGMENT, + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] }, { code: ASSIGNMENT_TERNARY_NO_PAREN, output: ASSIGNMENT_TERNARY_PAREN, @@ -513,10 +797,24 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ASSIGNMENT_TERNARY_PAREN_FRAGMENT, + options: [{assignment: true}], + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] }, { code: DECLARATION_NO_PAREN, output: DECLARATION_PAREN, errors: [{message: MISSING_PARENS}] + }, { + code: DECLARATION_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: DECLARATION_PAREN_FRAGMENT, + errors: [{message: MISSING_PARENS}] }, { code: DECLARATION_NO_PAREN, output: DECLARATION_PAREN, @@ -526,6 +824,11 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ASSIGNMENT_NO_PAREN, output: ASSIGNMENT_PAREN, errors: [{message: MISSING_PARENS}] + }, { + code: ASSIGNMENT_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ASSIGNMENT_PAREN_FRAGMENT, + errors: [{message: MISSING_PARENS}] }, { code: ASSIGNMENT_NO_PAREN, output: ASSIGNMENT_PAREN, @@ -535,6 +838,11 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ARROW_NO_PAREN, output: ARROW_PAREN, errors: [{message: MISSING_PARENS}] + }, { + code: ARROW_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ARROW_PAREN_FRAGMENT, + errors: [{message: MISSING_PARENS}] }, { code: ARROW_NO_PAREN, output: ARROW_PAREN, @@ -545,6 +853,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: CONDITION_PAREN, options: [{condition: 'parens'}], errors: [{message: MISSING_PARENS}] + }, { + code: CONDITION_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: CONDITION_PAREN_FRAGMENT, + options: [{condition: 'parens'}], + errors: [{message: MISSING_PARENS}] }, { code: CONDITION_NO_PAREN, output: CONDITION_PAREN, @@ -555,6 +869,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: LOGICAL_PAREN, options: [{logical: 'parens'}], errors: [{message: MISSING_PARENS}] + }, { + code: LOGICAL_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: LOGICAL_PAREN_FRAGMENT, + options: [{logical: 'parens'}], + errors: [{message: MISSING_PARENS}] }, { code: LOGICAL_NO_PAREN, output: LOGICAL_PAREN, @@ -565,6 +885,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: ATTR_PAREN, options: [{prop: 'parens'}], errors: [{message: MISSING_PARENS}] + }, { + code: ATTR_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ATTR_PAREN_FRAGMENT, + options: [{prop: 'parens'}], + errors: [{message: MISSING_PARENS}] }, { code: ATTR_NO_PAREN, output: ATTR_PAREN, @@ -575,11 +901,23 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: addNewLineSymbols(RETURN_PAREN), options: [{return: 'parens-new-line'}], errors: [{message: MISSING_PARENS}] + }, { + code: RETURN_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(RETURN_PAREN_FRAGMENT), + options: [{return: 'parens-new-line'}], + errors: [{message: MISSING_PARENS}] }, { code: RETURN_PAREN, output: addNewLineSymbols(RETURN_PAREN), options: [{return: 'parens-new-line'}], errors: [{message: PARENS_NEW_LINES}] + }, { + code: RETURN_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(RETURN_PAREN_FRAGMENT), + options: [{return: 'parens-new-line'}], + errors: [{message: PARENS_NEW_LINES}] }, { code: DECLARATION_TERNARY_NO_PAREN, output: addNewLineSymbols(DECLARATION_TERNARY_PAREN), @@ -588,6 +926,24 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: DECLARATION_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(DECLARATION_TERNARY_PAREN_FRAGMENT), + options: [{declaration: 'parens-new-line'}], + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] + }, { + code: DECLARATION_TERNARY_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(DECLARATION_TERNARY_PAREN_FRAGMENT), + options: [{declaration: 'parens-new-line'}], + errors: [ + {message: PARENS_NEW_LINES}, + {message: PARENS_NEW_LINES} + ] }, { code: DECLARATION_TERNARY_PAREN, output: addNewLineSymbols(DECLARATION_TERNARY_PAREN), @@ -596,6 +952,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: PARENS_NEW_LINES}, {message: PARENS_NEW_LINES} ] + }, { + code: DECLARATION_TERNARY_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(DECLARATION_TERNARY_PAREN_FRAGMENT), + options: [{declaration: 'parens-new-line'}], + errors: [ + {message: PARENS_NEW_LINES}, + {message: PARENS_NEW_LINES} + ] }, { code: ASSIGNMENT_TERNARY_NO_PAREN, output: addNewLineSymbols(ASSIGNMENT_TERNARY_PAREN), @@ -604,6 +969,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: MISSING_PARENS}, {message: MISSING_PARENS} ] + }, { + code: ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(ASSIGNMENT_TERNARY_PAREN_FRAGMENT), + options: [{assignment: 'parens-new-line'}], + errors: [ + {message: MISSING_PARENS}, + {message: MISSING_PARENS} + ] }, { code: ASSIGNMENT_TERNARY_PAREN, output: addNewLineSymbols(ASSIGNMENT_TERNARY_PAREN), @@ -612,6 +986,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { {message: PARENS_NEW_LINES}, {message: PARENS_NEW_LINES} ] + }, { + code: ASSIGNMENT_TERNARY_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(ASSIGNMENT_TERNARY_PAREN_FRAGMENT), + options: [{assignment: 'parens-new-line'}], + errors: [ + {message: PARENS_NEW_LINES}, + {message: PARENS_NEW_LINES} + ] }, { code: DECLARATION_NO_PAREN, output: addNewLineSymbols(DECLARATION_PAREN), @@ -637,21 +1020,45 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: addNewLineSymbols(ARROW_PAREN), options: [{arrow: 'parens-new-line'}], errors: [{message: PARENS_NEW_LINES}] + }, { + code: ARROW_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(ARROW_PAREN_FRAGMENT), + options: [{arrow: 'parens-new-line'}], + errors: [{message: PARENS_NEW_LINES}] }, { code: ARROW_NO_PAREN, output: addNewLineSymbols(ARROW_PAREN), options: [{arrow: 'parens-new-line'}], errors: [{message: MISSING_PARENS}] + }, { + code: ARROW_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(ARROW_PAREN_FRAGMENT), + options: [{arrow: 'parens-new-line'}], + errors: [{message: MISSING_PARENS}] }, { code: CONDITION_PAREN, output: addNewLineSymbols(CONDITION_PAREN), options: [{condition: 'parens-new-line'}], errors: [{message: PARENS_NEW_LINES}] + }, { + code: CONDITION_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(CONDITION_PAREN_FRAGMENT), + options: [{condition: 'parens-new-line'}], + errors: [{message: PARENS_NEW_LINES}] }, { code: CONDITION_NO_PAREN, output: addNewLineSymbols(CONDITION_PAREN), options: [{condition: 'parens-new-line'}], errors: [{message: MISSING_PARENS}] + }, { + code: CONDITION_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(CONDITION_PAREN_FRAGMENT), + options: [{condition: 'parens-new-line'}], + errors: [{message: MISSING_PARENS}] }, { code: LOGICAL_PAREN, output: addNewLineSymbols(LOGICAL_PAREN), @@ -662,15 +1069,33 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: LOGICAL_PAREN_NEW_LINE_AUTOFIX, options: [{logical: 'parens-new-line'}], errors: [{message: MISSING_PARENS}] + }, { + code: LOGICAL_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: LOGICAL_PAREN_NEW_LINE_AUTOFIX_FRAGMENT, + options: [{logical: 'parens-new-line'}], + errors: [{message: MISSING_PARENS}] }, { code: ATTR_PAREN, output: addNewLineSymbols(ATTR_PAREN), options: [{prop: 'parens-new-line'}], errors: [{message: PARENS_NEW_LINES}] + }, { + code: ATTR_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: addNewLineSymbols(ATTR_PAREN_FRAGMENT), + options: [{prop: 'parens-new-line'}], + errors: [{message: PARENS_NEW_LINES}] }, { code: ATTR_NO_PAREN, output: ATTR_PAREN_NEW_LINE_AUTOFIX, options: [{prop: 'parens-new-line'}], errors: [{message: MISSING_PARENS}] + }, { + code: ATTR_NO_PAREN_FRAGMENT, + parser: 'babel-eslint', + output: ATTR_PAREN_NEW_LINE_AUTOFIX_FRAGMENT, + options: [{prop: 'parens-new-line'}], + errors: [{message: MISSING_PARENS}] }] }); diff --git a/tests/lib/rules/no-unescaped-entities.js b/tests/lib/rules/no-unescaped-entities.js index 61e3a9e0a4..aff01dca5b 100644 --- a/tests/lib/rules/no-unescaped-entities.js +++ b/tests/lib/rules/no-unescaped-entities.js @@ -71,6 +71,35 @@ ruleTester.run('no-unescaped-entities', rule, { }, }); ` + }, + { + code: ` + var Hello = createReactClass({ + render: function() { + return <>Here is some text!; + } + }); + `, + parser: 'babel-eslint' + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return <>I’ve escaped some entities: > < &; + } + }); + `, + parser: 'babel-eslint' + }, + { + code: ` + var Hello = createReactClass({ + render: function() { + return <>{">" + "<" + "&" + '"'}; + }, + }); + `, + parser: 'babel-eslint' } ], @@ -84,6 +113,16 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, errors: [{message: 'HTML entities must be escaped.'}] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return <>>; + } + }); + `, + parser: 'babel-eslint', + errors: [{message: 'HTML entities must be escaped.'}] }, { code: ` var Hello = createReactClass({ @@ -95,6 +134,18 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, errors: [{message: 'HTML entities must be escaped.'}] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return <>first line is ok + so is second + and here are some bad entities: > + } + }); + `, + parser: 'babel-eslint', + errors: [{message: 'HTML entities must be escaped.'}] }, { code: ` var Hello = createReactClass({ @@ -126,6 +177,16 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, errors: [{message: 'HTML entities must be escaped.'}] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return <>{"Unbalanced braces"}}; + } + }); + `, + parser: 'babel-eslint', + errors: [{message: 'HTML entities must be escaped.'}] } ] }); diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index bdc492f262..4cbe55fa26 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -3852,6 +3852,21 @@ ruleTester.run('prop-types', rule, { errors: [{ message: '\'foo.baz\' is missing in props validation' }] + }, + { + code: ` + const ForAttendees = ({ page }) => ( + <> +
{page}
+ + ); + + export default ForAttendees; + `, + parser: 'babel-eslint', + errors: [{ + message: '\'page\' is missing in props validation' + }] } ] });