UNPKG

7.72 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforce newlines between operands of ternary expressions
3 * @author Kai Cataldo
4 */
5
6"use strict";
7
8const astUtils = require("./utils/ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15 meta: {
16 type: "layout",
17
18 docs: {
19 description: "enforce newlines between operands of ternary expressions",
20 category: "Stylistic Issues",
21 recommended: false,
22 url: "https://eslint.org/docs/rules/multiline-ternary"
23 },
24
25 schema: [
26 {
27 enum: ["always", "always-multiline", "never"]
28 }
29 ],
30
31 messages: {
32 expectedTestCons: "Expected newline between test and consequent of ternary expression.",
33 expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.",
34 unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.",
35 unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression."
36 },
37
38 fixable: "whitespace"
39 },
40
41 create(context) {
42 const sourceCode = context.getSourceCode();
43 const option = context.options[0];
44 const multiline = option !== "never";
45 const allowSingleLine = option === "always-multiline";
46
47 //--------------------------------------------------------------------------
48 // Public
49 //--------------------------------------------------------------------------
50
51 return {
52 ConditionalExpression(node) {
53 const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
54 const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
55
56 const firstTokenOfTest = sourceCode.getFirstToken(node);
57 const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
58 const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
59 const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
60 const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
61
62 const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
63 const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
64
65 const hasComments = !!sourceCode.getCommentsInside(node).length;
66
67 if (!multiline) {
68 if (!areTestAndConsequentOnSameLine) {
69 context.report({
70 node: node.test,
71 loc: {
72 start: firstTokenOfTest.loc.start,
73 end: lastTokenOfTest.loc.end
74 },
75 messageId: "unexpectedTestCons",
76 fix: fixer => {
77 if (hasComments) {
78 return null;
79 }
80 const fixers = [];
81 const areTestAndQuestionOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, questionToken);
82 const areQuestionAndConsOnSameLine = astUtils.isTokenOnSameLine(questionToken, firstTokenOfConsequent);
83
84 if (!areTestAndQuestionOnSameLine) {
85 fixers.push(fixer.removeRange([lastTokenOfTest.range[1], questionToken.range[0]]));
86 }
87 if (!areQuestionAndConsOnSameLine) {
88 fixers.push(fixer.removeRange([questionToken.range[1], firstTokenOfConsequent.range[0]]));
89 }
90
91 return fixers;
92 }
93 });
94 }
95
96 if (!areConsequentAndAlternateOnSameLine) {
97 context.report({
98 node: node.consequent,
99 loc: {
100 start: firstTokenOfConsequent.loc.start,
101 end: lastTokenOfConsequent.loc.end
102 },
103 messageId: "unexpectedConsAlt",
104 fix: fixer => {
105 if (hasComments) {
106 return null;
107 }
108 const fixers = [];
109 const areConsAndColonOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, colonToken);
110 const areColonAndAltOnSameLine = astUtils.isTokenOnSameLine(colonToken, firstTokenOfAlternate);
111
112 if (!areConsAndColonOnSameLine) {
113 fixers.push(fixer.removeRange([lastTokenOfConsequent.range[1], colonToken.range[0]]));
114 }
115 if (!areColonAndAltOnSameLine) {
116 fixers.push(fixer.removeRange([colonToken.range[1], firstTokenOfAlternate.range[0]]));
117 }
118
119 return fixers;
120 }
121 });
122 }
123 } else {
124 if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
125 return;
126 }
127
128 if (areTestAndConsequentOnSameLine) {
129 context.report({
130 node: node.test,
131 loc: {
132 start: firstTokenOfTest.loc.start,
133 end: lastTokenOfTest.loc.end
134 },
135 messageId: "expectedTestCons",
136 fix: fixer => (hasComments ? null : (
137 fixer.replaceTextRange(
138 [
139 lastTokenOfTest.range[1],
140 questionToken.range[0]
141 ],
142 "\n"
143 )
144 ))
145 });
146 }
147
148 if (areConsequentAndAlternateOnSameLine) {
149 context.report({
150 node: node.consequent,
151 loc: {
152 start: firstTokenOfConsequent.loc.start,
153 end: lastTokenOfConsequent.loc.end
154 },
155 messageId: "expectedConsAlt",
156 fix: (fixer => (hasComments ? null : (
157 fixer.replaceTextRange(
158 [
159 lastTokenOfConsequent.range[1],
160 colonToken.range[0]
161 ],
162 "\n"
163 )
164 )))
165 });
166 }
167 }
168 }
169 };
170 }
171};