123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- /**
- * @fileoverview enforce consistent line breaks inside jsx curly
- */
- 'use strict';
- const docsUrl = require('../util/docsUrl');
- // ------------------------------------------------------------------------------
- // 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'
- };
- }
- 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: {
- expectedBefore: 'Expected newline before \'}\'.',
- expectedAfter: 'Expected newline after \'{\'.',
- unexpectedBefore: 'Unexpected newline before \'}\'.',
- unexpectedAfter: 'Unexpected newline after \'{\'.'
- }
- },
- 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) {
- context.report({
- node: leftCurly,
- messageId: 'unexpectedAfter',
- 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) {
- context.report({
- node: leftCurly,
- messageId: 'expectedAfter',
- fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n')
- });
- }
- if (hasRightNewline && !needsNewlines) {
- context.report({
- node: rightCurly,
- messageId: 'unexpectedBefore',
- 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) {
- context.report({
- node: rightCurly,
- messageId: 'expectedBefore',
- fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n')
- });
- }
- }
- // ----------------------------------------------------------------------
- // Public
- // ----------------------------------------------------------------------
- return {
- JSXExpressionContainer(node) {
- const curlyTokens = {
- leftCurly: sourceCode.getFirstToken(node),
- rightCurly: sourceCode.getLastToken(node)
- };
- validateCurlys(curlyTokens, node.expression);
- }
- };
- }
- };
|