multiline-ternary.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. /**
  2. * @fileoverview Enforce newlines between operands of ternary expressions
  3. * @author Kai Cataldo
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. module.exports = {
  11. meta: {
  12. type: "layout",
  13. docs: {
  14. description: "enforce newlines between operands of ternary expressions",
  15. category: "Stylistic Issues",
  16. recommended: false,
  17. url: "https://eslint.org/docs/rules/multiline-ternary"
  18. },
  19. schema: [
  20. {
  21. enum: ["always", "always-multiline", "never"]
  22. }
  23. ],
  24. messages: {
  25. expectedTestCons: "Expected newline between test and consequent of ternary expression.",
  26. expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.",
  27. unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.",
  28. unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression."
  29. }
  30. },
  31. create(context) {
  32. const option = context.options[0];
  33. const multiline = option !== "never";
  34. const allowSingleLine = option === "always-multiline";
  35. const sourceCode = context.getSourceCode();
  36. //--------------------------------------------------------------------------
  37. // Public
  38. //--------------------------------------------------------------------------
  39. return {
  40. ConditionalExpression(node) {
  41. const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
  42. const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
  43. const firstTokenOfTest = sourceCode.getFirstToken(node);
  44. const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
  45. const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
  46. const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
  47. const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
  48. const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
  49. const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
  50. if (!multiline) {
  51. if (!areTestAndConsequentOnSameLine) {
  52. context.report({
  53. node: node.test,
  54. loc: {
  55. start: firstTokenOfTest.loc.start,
  56. end: lastTokenOfTest.loc.end
  57. },
  58. messageId: "unexpectedTestCons"
  59. });
  60. }
  61. if (!areConsequentAndAlternateOnSameLine) {
  62. context.report({
  63. node: node.consequent,
  64. loc: {
  65. start: firstTokenOfConsequent.loc.start,
  66. end: lastTokenOfConsequent.loc.end
  67. },
  68. messageId: "unexpectedConsAlt"
  69. });
  70. }
  71. } else {
  72. if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
  73. return;
  74. }
  75. if (areTestAndConsequentOnSameLine) {
  76. context.report({
  77. node: node.test,
  78. loc: {
  79. start: firstTokenOfTest.loc.start,
  80. end: lastTokenOfTest.loc.end
  81. },
  82. messageId: "expectedTestCons"
  83. });
  84. }
  85. if (areConsequentAndAlternateOnSameLine) {
  86. context.report({
  87. node: node.consequent,
  88. loc: {
  89. start: firstTokenOfConsequent.loc.start,
  90. end: lastTokenOfConsequent.loc.end
  91. },
  92. messageId: "expectedConsAlt"
  93. });
  94. }
  95. }
  96. }
  97. };
  98. }
  99. };