UNPKG

5.87 kBJavaScriptView Raw
1/**
2 * @fileoverview enforce consistent line breaks inside jsx curly
3 */
4
5'use strict';
6
7const docsUrl = require('../util/docsUrl');
8
9// ------------------------------------------------------------------------------
10// Rule Definition
11// ------------------------------------------------------------------------------
12
13function getNormalizedOption(context) {
14 const rawOption = context.options[0] || 'consistent';
15
16 if (rawOption === 'consistent') {
17 return {
18 multiline: 'consistent',
19 singleline: 'consistent'
20 };
21 }
22
23 if (rawOption === 'never') {
24 return {
25 multiline: 'forbid',
26 singleline: 'forbid'
27 };
28 }
29
30 return {
31 multiline: rawOption.multiline || 'consistent',
32 singleline: rawOption.singleline || 'consistent'
33 };
34}
35
36module.exports = {
37 meta: {
38 type: 'layout',
39
40 docs: {
41 description: 'Enforce consistent line breaks inside jsx curly',
42 category: 'Stylistic Issues',
43 recommended: false,
44 url: docsUrl('jsx-curly-newline')
45 },
46
47 fixable: 'whitespace',
48
49 schema: [
50 {
51 oneOf: [
52 {
53 enum: ['consistent', 'never']
54 },
55 {
56 type: 'object',
57 properties: {
58 singleline: {enum: ['consistent', 'require', 'forbid']},
59 multiline: {enum: ['consistent', 'require', 'forbid']}
60 },
61 additionalProperties: false
62 }
63 ]
64 }
65 ],
66
67 messages: {
68 expectedBefore: 'Expected newline before \'}\'.',
69 expectedAfter: 'Expected newline after \'{\'.',
70 unexpectedBefore: 'Unexpected newline before \'{\'.',
71 unexpectedAfter: 'Unexpected newline after \'}\'.'
72 }
73 },
74
75 create(context) {
76 const sourceCode = context.getSourceCode();
77 const option = getNormalizedOption(context);
78
79 // ----------------------------------------------------------------------
80 // Helpers
81 // ----------------------------------------------------------------------
82
83 /**
84 * Determines whether two adjacent tokens are on the same line.
85 * @param {Object} left - The left token object.
86 * @param {Object} right - The right token object.
87 * @returns {boolean} Whether or not the tokens are on the same line.
88 */
89 function isTokenOnSameLine(left, right) {
90 return left.loc.end.line === right.loc.start.line;
91 }
92
93 /**
94 * Determines whether there should be newlines inside curlys
95 * @param {ASTNode} expression The expression contained in the curlys
96 * @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code.
97 * @returns {boolean} `true` if there should be newlines inside the function curlys
98 */
99 function shouldHaveNewlines(expression, hasLeftNewline) {
100 const isMultiline = expression.loc.start.line !== expression.loc.end.line;
101
102 switch (isMultiline ? option.multiline : option.singleline) {
103 case 'forbid': return false;
104 case 'require': return true;
105 case 'consistent':
106 default: return hasLeftNewline;
107 }
108 }
109
110 /**
111 * Validates curlys
112 * @param {Object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
113 * @param {ASTNode} expression The expression inside the curly
114 * @returns {void}
115 */
116 function validateCurlys(curlys, expression) {
117 const leftCurly = curlys.leftCurly;
118 const rightCurly = curlys.rightCurly;
119 const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly);
120 const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly);
121 const hasLeftNewline = !isTokenOnSameLine(leftCurly, tokenAfterLeftCurly);
122 const hasRightNewline = !isTokenOnSameLine(tokenBeforeRightCurly, rightCurly);
123 const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
124
125 if (hasLeftNewline && !needsNewlines) {
126 context.report({
127 node: leftCurly,
128 messageId: 'unexpectedAfter',
129 fix(fixer) {
130 return sourceCode
131 .getText()
132 .slice(leftCurly.range[1], tokenAfterLeftCurly.range[0])
133 .trim()
134 ? null // If there is a comment between the { and the first element, don't do a fix.
135 : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
136 }
137 });
138 } else if (!hasLeftNewline && needsNewlines) {
139 context.report({
140 node: leftCurly,
141 messageId: 'expectedAfter',
142 fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n')
143 });
144 }
145
146 if (hasRightNewline && !needsNewlines) {
147 context.report({
148 node: rightCurly,
149 messageId: 'unexpectedBefore',
150 fix(fixer) {
151 return sourceCode
152 .getText()
153 .slice(tokenBeforeRightCurly.range[1], rightCurly.range[0])
154 .trim()
155 ? null // If there is a comment between the last element and the }, don't do a fix.
156 : fixer.removeRange([
157 tokenBeforeRightCurly.range[1],
158 rightCurly.range[0]
159 ]);
160 }
161 });
162 } else if (!hasRightNewline && needsNewlines) {
163 context.report({
164 node: rightCurly,
165 messageId: 'expectedBefore',
166 fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n')
167 });
168 }
169 }
170
171 // ----------------------------------------------------------------------
172 // Public
173 // ----------------------------------------------------------------------
174
175 return {
176 JSXExpressionContainer(node) {
177 const curlyTokens = {
178 leftCurly: sourceCode.getFirstToken(node),
179 rightCurly: sourceCode.getLastToken(node)
180 };
181 validateCurlys(curlyTokens, node.expression);
182 }
183 };
184 }
185};