multiline-ternary.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /**
  2. * @fileoverview Enforce newlines between operands of ternary expressions
  3. * @author Kai Cataldo
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. const astUtils = require("./utils/ast-utils");
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. /** @type {import('../types').Rule.RuleModule} */
  12. module.exports = {
  13. meta: {
  14. deprecated: {
  15. message: "Formatting rules are being moved out of ESLint core.",
  16. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  17. deprecatedSince: "8.53.0",
  18. availableUntil: "11.0.0",
  19. replacedBy: [
  20. {
  21. message:
  22. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  23. url: "https://eslint.style/guide/migration",
  24. plugin: {
  25. name: "@stylistic/eslint-plugin",
  26. url: "https://eslint.style",
  27. },
  28. rule: {
  29. name: "multiline-ternary",
  30. url: "https://eslint.style/rules/multiline-ternary",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description:
  38. "Enforce newlines between operands of ternary expressions",
  39. recommended: false,
  40. url: "https://eslint.org/docs/latest/rules/multiline-ternary",
  41. },
  42. schema: [
  43. {
  44. enum: ["always", "always-multiline", "never"],
  45. },
  46. ],
  47. messages: {
  48. expectedTestCons:
  49. "Expected newline between test and consequent of ternary expression.",
  50. expectedConsAlt:
  51. "Expected newline between consequent and alternate of ternary expression.",
  52. unexpectedTestCons:
  53. "Unexpected newline between test and consequent of ternary expression.",
  54. unexpectedConsAlt:
  55. "Unexpected newline between consequent and alternate of ternary expression.",
  56. },
  57. fixable: "whitespace",
  58. },
  59. create(context) {
  60. const sourceCode = context.sourceCode;
  61. const option = context.options[0];
  62. const multiline = option !== "never";
  63. const allowSingleLine = option === "always-multiline";
  64. //--------------------------------------------------------------------------
  65. // Public
  66. //--------------------------------------------------------------------------
  67. return {
  68. ConditionalExpression(node) {
  69. const questionToken = sourceCode.getTokenAfter(
  70. node.test,
  71. astUtils.isNotClosingParenToken,
  72. );
  73. const colonToken = sourceCode.getTokenAfter(
  74. node.consequent,
  75. astUtils.isNotClosingParenToken,
  76. );
  77. const firstTokenOfTest = sourceCode.getFirstToken(node);
  78. const lastTokenOfTest =
  79. sourceCode.getTokenBefore(questionToken);
  80. const firstTokenOfConsequent =
  81. sourceCode.getTokenAfter(questionToken);
  82. const lastTokenOfConsequent =
  83. sourceCode.getTokenBefore(colonToken);
  84. const firstTokenOfAlternate =
  85. sourceCode.getTokenAfter(colonToken);
  86. const areTestAndConsequentOnSameLine =
  87. astUtils.isTokenOnSameLine(
  88. lastTokenOfTest,
  89. firstTokenOfConsequent,
  90. );
  91. const areConsequentAndAlternateOnSameLine =
  92. astUtils.isTokenOnSameLine(
  93. lastTokenOfConsequent,
  94. firstTokenOfAlternate,
  95. );
  96. const hasComments = !!sourceCode.getCommentsInside(node).length;
  97. if (!multiline) {
  98. if (!areTestAndConsequentOnSameLine) {
  99. context.report({
  100. node: node.test,
  101. loc: {
  102. start: firstTokenOfTest.loc.start,
  103. end: lastTokenOfTest.loc.end,
  104. },
  105. messageId: "unexpectedTestCons",
  106. fix(fixer) {
  107. if (hasComments) {
  108. return null;
  109. }
  110. const fixers = [];
  111. const areTestAndQuestionOnSameLine =
  112. astUtils.isTokenOnSameLine(
  113. lastTokenOfTest,
  114. questionToken,
  115. );
  116. const areQuestionAndConsOnSameLine =
  117. astUtils.isTokenOnSameLine(
  118. questionToken,
  119. firstTokenOfConsequent,
  120. );
  121. if (!areTestAndQuestionOnSameLine) {
  122. fixers.push(
  123. fixer.removeRange([
  124. lastTokenOfTest.range[1],
  125. questionToken.range[0],
  126. ]),
  127. );
  128. }
  129. if (!areQuestionAndConsOnSameLine) {
  130. fixers.push(
  131. fixer.removeRange([
  132. questionToken.range[1],
  133. firstTokenOfConsequent.range[0],
  134. ]),
  135. );
  136. }
  137. return fixers;
  138. },
  139. });
  140. }
  141. if (!areConsequentAndAlternateOnSameLine) {
  142. context.report({
  143. node: node.consequent,
  144. loc: {
  145. start: firstTokenOfConsequent.loc.start,
  146. end: lastTokenOfConsequent.loc.end,
  147. },
  148. messageId: "unexpectedConsAlt",
  149. fix(fixer) {
  150. if (hasComments) {
  151. return null;
  152. }
  153. const fixers = [];
  154. const areConsAndColonOnSameLine =
  155. astUtils.isTokenOnSameLine(
  156. lastTokenOfConsequent,
  157. colonToken,
  158. );
  159. const areColonAndAltOnSameLine =
  160. astUtils.isTokenOnSameLine(
  161. colonToken,
  162. firstTokenOfAlternate,
  163. );
  164. if (!areConsAndColonOnSameLine) {
  165. fixers.push(
  166. fixer.removeRange([
  167. lastTokenOfConsequent.range[1],
  168. colonToken.range[0],
  169. ]),
  170. );
  171. }
  172. if (!areColonAndAltOnSameLine) {
  173. fixers.push(
  174. fixer.removeRange([
  175. colonToken.range[1],
  176. firstTokenOfAlternate.range[0],
  177. ]),
  178. );
  179. }
  180. return fixers;
  181. },
  182. });
  183. }
  184. } else {
  185. if (
  186. allowSingleLine &&
  187. node.loc.start.line === node.loc.end.line
  188. ) {
  189. return;
  190. }
  191. if (areTestAndConsequentOnSameLine) {
  192. context.report({
  193. node: node.test,
  194. loc: {
  195. start: firstTokenOfTest.loc.start,
  196. end: lastTokenOfTest.loc.end,
  197. },
  198. messageId: "expectedTestCons",
  199. fix: fixer =>
  200. hasComments
  201. ? null
  202. : fixer.replaceTextRange(
  203. [
  204. lastTokenOfTest.range[1],
  205. questionToken.range[0],
  206. ],
  207. "\n",
  208. ),
  209. });
  210. }
  211. if (areConsequentAndAlternateOnSameLine) {
  212. context.report({
  213. node: node.consequent,
  214. loc: {
  215. start: firstTokenOfConsequent.loc.start,
  216. end: lastTokenOfConsequent.loc.end,
  217. },
  218. messageId: "expectedConsAlt",
  219. fix: fixer =>
  220. hasComments
  221. ? null
  222. : fixer.replaceTextRange(
  223. [
  224. lastTokenOfConsequent.range[1],
  225. colonToken.range[0],
  226. ],
  227. "\n",
  228. ),
  229. });
  230. }
  231. }
  232. },
  233. };
  234. },
  235. };