no-unexpected-multiline.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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. module.exports = {
  14. meta: {
  15. type: "problem",
  16. docs: {
  17. description: "disallow confusing multiline expressions",
  18. category: "Possible Errors",
  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({ node, loc: openParen.loc.start, messageId, data: { char: openParen.value } });
  46. }
  47. }
  48. //--------------------------------------------------------------------------
  49. // Public API
  50. //--------------------------------------------------------------------------
  51. return {
  52. MemberExpression(node) {
  53. if (!node.computed) {
  54. return;
  55. }
  56. checkForBreakAfter(node.object, "property");
  57. },
  58. TaggedTemplateExpression(node) {
  59. if (node.tag.loc.end.line === node.quasi.loc.start.line) {
  60. return;
  61. }
  62. // handle generics type parameters on template tags
  63. const tokenBefore = sourceCode.getTokenBefore(node.quasi);
  64. if (tokenBefore.loc.end.line === node.quasi.loc.start.line) {
  65. return;
  66. }
  67. context.report({ node, loc: node.loc.start, messageId: "taggedTemplate" });
  68. },
  69. CallExpression(node) {
  70. if (node.arguments.length === 0) {
  71. return;
  72. }
  73. checkForBreakAfter(node.callee, "function");
  74. },
  75. "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) {
  76. const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/");
  77. const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash);
  78. if (
  79. tokenAfterOperator.type === "Identifier" &&
  80. REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
  81. secondSlash.range[1] === tokenAfterOperator.range[0]
  82. ) {
  83. checkForBreakAfter(node.left, "division");
  84. }
  85. }
  86. };
  87. }
  88. };