• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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