no-unexpected-multiline.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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('../shared/types').Rule} */
  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/rules/no-unexpected-multiline"
  21. },
  22. schema: [],
  23. messages: {
  24. function: "Unexpected newline between function and ( of function call.",
  25. property: "Unexpected newline between object and [ of property access.",
  26. taggedTemplate: "Unexpected newline between template tag and template literal.",
  27. division: "Unexpected newline between numerator and division operator."
  28. }
  29. },
  30. create(context) {
  31. const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u;
  32. const sourceCode = context.getSourceCode();
  33. /**
  34. * Check to see if there is a newline between the node and the following open bracket
  35. * line's expression
  36. * @param {ASTNode} node The node to check.
  37. * @param {string} messageId The error messageId to use.
  38. * @returns {void}
  39. * @private
  40. */
  41. function checkForBreakAfter(node, messageId) {
  42. const openParen = sourceCode.getTokenAfter(node, astUtils.isNotClosingParenToken);
  43. const nodeExpressionEnd = sourceCode.getTokenBefore(openParen);
  44. if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) {
  45. context.report({
  46. node,
  47. loc: openParen.loc,
  48. messageId
  49. });
  50. }
  51. }
  52. //--------------------------------------------------------------------------
  53. // Public API
  54. //--------------------------------------------------------------------------
  55. return {
  56. MemberExpression(node) {
  57. if (!node.computed || node.optional) {
  58. return;
  59. }
  60. checkForBreakAfter(node.object, "property");
  61. },
  62. TaggedTemplateExpression(node) {
  63. const { quasi } = node;
  64. // handles common tags, parenthesized tags, and typescript's generic type arguments
  65. const tokenBefore = sourceCode.getTokenBefore(quasi);
  66. if (tokenBefore.loc.end.line !== quasi.loc.start.line) {
  67. context.report({
  68. node,
  69. loc: {
  70. start: quasi.loc.start,
  71. end: {
  72. line: quasi.loc.start.line,
  73. column: quasi.loc.start.column + 1
  74. }
  75. },
  76. messageId: "taggedTemplate"
  77. });
  78. }
  79. },
  80. CallExpression(node) {
  81. if (node.arguments.length === 0 || node.optional) {
  82. return;
  83. }
  84. checkForBreakAfter(node.callee, "function");
  85. },
  86. "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) {
  87. const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/");
  88. const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash);
  89. if (
  90. tokenAfterOperator.type === "Identifier" &&
  91. REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
  92. secondSlash.range[1] === tokenAfterOperator.range[0]
  93. ) {
  94. checkForBreakAfter(node.left, "division");
  95. }
  96. }
  97. };
  98. }
  99. };