• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Require spaces around infix operators
3 * @author Michael Ficarra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = {
12    meta: {
13        type: "layout",
14
15        docs: {
16            description: "require spacing around infix operators",
17            category: "Stylistic Issues",
18            recommended: false,
19            url: "https://eslint.org/docs/rules/space-infix-ops"
20        },
21
22        fixable: "whitespace",
23
24        schema: [
25            {
26                type: "object",
27                properties: {
28                    int32Hint: {
29                        type: "boolean",
30                        default: false
31                    }
32                },
33                additionalProperties: false
34            }
35        ],
36
37        messages: {
38            missingSpace: "Operator '{{operator}}' must be spaced."
39        }
40    },
41
42    create(context) {
43        const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
44        const sourceCode = context.getSourceCode();
45
46        /**
47         * Returns the first token which violates the rule
48         * @param {ASTNode} left The left node of the main node
49         * @param {ASTNode} right The right node of the main node
50         * @param {string} op The operator of the main node
51         * @returns {Object} The violator token or null
52         * @private
53         */
54        function getFirstNonSpacedToken(left, right, op) {
55            const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
56            const prev = sourceCode.getTokenBefore(operator);
57            const next = sourceCode.getTokenAfter(operator);
58
59            if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) {
60                return operator;
61            }
62
63            return null;
64        }
65
66        /**
67         * Reports an AST node as a rule violation
68         * @param {ASTNode} mainNode The node to report
69         * @param {Object} culpritToken The token which has a problem
70         * @returns {void}
71         * @private
72         */
73        function report(mainNode, culpritToken) {
74            context.report({
75                node: mainNode,
76                loc: culpritToken.loc,
77                messageId: "missingSpace",
78                data: {
79                    operator: culpritToken.value
80                },
81                fix(fixer) {
82                    const previousToken = sourceCode.getTokenBefore(culpritToken);
83                    const afterToken = sourceCode.getTokenAfter(culpritToken);
84                    let fixString = "";
85
86                    if (culpritToken.range[0] - previousToken.range[1] === 0) {
87                        fixString = " ";
88                    }
89
90                    fixString += culpritToken.value;
91
92                    if (afterToken.range[0] - culpritToken.range[1] === 0) {
93                        fixString += " ";
94                    }
95
96                    return fixer.replaceText(culpritToken, fixString);
97                }
98            });
99        }
100
101        /**
102         * Check if the node is binary then report
103         * @param {ASTNode} node node to evaluate
104         * @returns {void}
105         * @private
106         */
107        function checkBinary(node) {
108            const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left;
109            const rightNode = node.right;
110
111            // search for = in AssignmentPattern nodes
112            const operator = node.operator || "=";
113
114            const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
115
116            if (nonSpacedNode) {
117                if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
118                    report(node, nonSpacedNode);
119                }
120            }
121        }
122
123        /**
124         * Check if the node is conditional
125         * @param {ASTNode} node node to evaluate
126         * @returns {void}
127         * @private
128         */
129        function checkConditional(node) {
130            const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
131            const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
132
133            if (nonSpacedConsequentNode) {
134                report(node, nonSpacedConsequentNode);
135            } else if (nonSpacedAlternateNode) {
136                report(node, nonSpacedAlternateNode);
137            }
138        }
139
140        /**
141         * Check if the node is a variable
142         * @param {ASTNode} node node to evaluate
143         * @returns {void}
144         * @private
145         */
146        function checkVar(node) {
147            const leftNode = (node.id.typeAnnotation) ? node.id.typeAnnotation : node.id;
148            const rightNode = node.init;
149
150            if (rightNode) {
151                const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
152
153                if (nonSpacedNode) {
154                    report(node, nonSpacedNode);
155                }
156            }
157        }
158
159        return {
160            AssignmentExpression: checkBinary,
161            AssignmentPattern: checkBinary,
162            BinaryExpression: checkBinary,
163            LogicalExpression: checkBinary,
164            ConditionalExpression: checkConditional,
165            VariableDeclarator: checkVar
166        };
167
168    }
169};
170