You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
5.8 KiB

2 years ago
/**
* @fileoverview enforce consistent line breaks inside jsx curly
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function getNormalizedOption(context) {
const rawOption = context.options[0] || 'consistent';
if (rawOption === 'consistent') {
return {
multiline: 'consistent',
singleline: 'consistent',
};
}
if (rawOption === 'never') {
return {
multiline: 'forbid',
singleline: 'forbid',
};
}
return {
multiline: rawOption.multiline || 'consistent',
singleline: rawOption.singleline || 'consistent',
};
}
const messages = {
expectedBefore: 'Expected newline before \'}\'.',
expectedAfter: 'Expected newline after \'{\'.',
unexpectedBefore: 'Unexpected newline before \'}\'.',
unexpectedAfter: 'Unexpected newline after \'{\'.',
};
module.exports = {
meta: {
type: 'layout',
docs: {
description: 'Enforce consistent line breaks inside jsx curly',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('jsx-curly-newline'),
},
fixable: 'whitespace',
schema: [
{
oneOf: [
{
enum: ['consistent', 'never'],
},
{
type: 'object',
properties: {
singleline: { enum: ['consistent', 'require', 'forbid'] },
multiline: { enum: ['consistent', 'require', 'forbid'] },
},
additionalProperties: false,
},
],
},
],
messages,
},
create(context) {
const sourceCode = context.getSourceCode();
const option = getNormalizedOption(context);
// ----------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are on the same line.
* @param {Object} left - The left token object.
* @param {Object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isTokenOnSameLine(left, right) {
return left.loc.end.line === right.loc.start.line;
}
/**
* Determines whether there should be newlines inside curlys
* @param {ASTNode} expression The expression contained in the curlys
* @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code.
* @returns {boolean} `true` if there should be newlines inside the function curlys
*/
function shouldHaveNewlines(expression, hasLeftNewline) {
const isMultiline = expression.loc.start.line !== expression.loc.end.line;
switch (isMultiline ? option.multiline : option.singleline) {
case 'forbid': return false;
case 'require': return true;
case 'consistent':
default: return hasLeftNewline;
}
}
/**
* Validates curlys
* @param {Object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
* @param {ASTNode} expression The expression inside the curly
* @returns {void}
*/
function validateCurlys(curlys, expression) {
const leftCurly = curlys.leftCurly;
const rightCurly = curlys.rightCurly;
const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly);
const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly);
const hasLeftNewline = !isTokenOnSameLine(leftCurly, tokenAfterLeftCurly);
const hasRightNewline = !isTokenOnSameLine(tokenBeforeRightCurly, rightCurly);
const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
if (hasLeftNewline && !needsNewlines) {
report(context, messages.unexpectedAfter, 'unexpectedAfter', {
node: leftCurly,
fix(fixer) {
return sourceCode
.getText()
.slice(leftCurly.range[1], tokenAfterLeftCurly.range[0])
.trim()
? null // If there is a comment between the { and the first element, don't do a fix.
: fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
},
});
} else if (!hasLeftNewline && needsNewlines) {
report(context, messages.expectedAfter, 'expectedAfter', {
node: leftCurly,
fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n'),
});
}
if (hasRightNewline && !needsNewlines) {
report(context, messages.unexpectedBefore, 'unexpectedBefore', {
node: rightCurly,
fix(fixer) {
return sourceCode
.getText()
.slice(tokenBeforeRightCurly.range[1], rightCurly.range[0])
.trim()
? null // If there is a comment between the last element and the }, don't do a fix.
: fixer.removeRange([
tokenBeforeRightCurly.range[1],
rightCurly.range[0],
]);
},
});
} else if (!hasRightNewline && needsNewlines) {
report(context, messages.expectedBefore, 'expectedBefore', {
node: rightCurly,
fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n'),
});
}
}
// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------
return {
JSXExpressionContainer(node) {
const curlyTokens = {
leftCurly: sourceCode.getFirstToken(node),
rightCurly: sourceCode.getLastToken(node),
};
validateCurlys(curlyTokens, node.expression);
},
};
},
};