UNPKG

4.28 kBJavaScriptView Raw
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};