no-unexpected-multiline.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /**
  2. * @fileoverview Rule to spot scenarios where a newline looks like it is ending a statement, but is not.
  3. * @author Glen Mailer
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. /** @type {import('../types').Rule.RuleModule} */
  14. module.exports = {
  15. meta: {
  16. type: "problem",
  17. docs: {
  18. description: "Disallow confusing multiline expressions",
  19. recommended: true,
  20. url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline",
  21. },
  22. schema: [],
  23. messages: {
  24. function:
  25. "Unexpected newline between function and ( of function call.",
  26. property:
  27. "Unexpected newline between object and [ of property access.",
  28. taggedTemplate:
  29. "Unexpected newline between template tag and template literal.",
  30. division:
  31. "Unexpected newline between numerator and division operator.",
  32. },
  33. },
  34. create(context) {
  35. const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u;
  36. const sourceCode = context.sourceCode;
  37. /**
  38. * Check to see if there is a newline between the node and the following open bracket
  39. * line's expression
  40. * @param {ASTNode} node The node to check.
  41. * @param {string} messageId The error messageId to use.
  42. * @returns {void}
  43. * @private
  44. */
  45. function checkForBreakAfter(node, messageId) {
  46. const openParen = sourceCode.getTokenAfter(
  47. node,
  48. astUtils.isNotClosingParenToken,
  49. );
  50. const nodeExpressionEnd = sourceCode.getTokenBefore(openParen);
  51. if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {
  52. context.report({
  53. node,
  54. loc: openParen.loc,
  55. messageId,
  56. });
  57. }
  58. }
  59. //--------------------------------------------------------------------------
  60. // Public API
  61. //--------------------------------------------------------------------------
  62. return {
  63. MemberExpression(node) {
  64. if (!node.computed || node.optional) {
  65. return;
  66. }
  67. checkForBreakAfter(node.object, "property");
  68. },
  69. TaggedTemplateExpression(node) {
  70. const { quasi } = node;
  71. // handles common tags, parenthesized tags, and typescript's generic type arguments
  72. const tokenBefore = sourceCode.getTokenBefore(quasi);
  73. if (tokenBefore.loc.end.line !== quasi.loc.start.line) {
  74. context.report({
  75. node,
  76. loc: {
  77. start: quasi.loc.start,
  78. end: {
  79. line: quasi.loc.start.line,
  80. column: quasi.loc.start.column + 1,
  81. },
  82. },
  83. messageId: "taggedTemplate",
  84. });
  85. }
  86. },
  87. CallExpression(node) {
  88. if (node.arguments.length === 0 || node.optional) {
  89. return;
  90. }
  91. checkForBreakAfter(node.callee, "function");
  92. },
  93. "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(
  94. node,
  95. ) {
  96. const secondSlash = sourceCode.getTokenAfter(
  97. node,
  98. token => token.value === "/",
  99. );
  100. const tokenAfterOperator =
  101. sourceCode.getTokenAfter(secondSlash);
  102. if (
  103. tokenAfterOperator.type === "Identifier" &&
  104. REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
  105. secondSlash.range[1] === tokenAfterOperator.range[0]
  106. ) {
  107. checkForBreakAfter(node.left, "division");
  108. }
  109. },
  110. };
  111. },
  112. };