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