• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Disallow mixed spaces and tabs for indentation
3 * @author Jary Niebur
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = {
12    meta: {
13        type: "layout",
14
15        docs: {
16            description: "disallow mixed spaces and tabs for indentation",
17            category: "Stylistic Issues",
18            recommended: true,
19            url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs"
20        },
21
22        schema: [
23            {
24                enum: ["smart-tabs", true, false]
25            }
26        ],
27
28        messages: {
29            mixedSpacesAndTabs: "Mixed spaces and tabs."
30        }
31    },
32
33    create(context) {
34        const sourceCode = context.getSourceCode();
35
36        let smartTabs;
37
38        switch (context.options[0]) {
39            case true: // Support old syntax, maybe add deprecation warning here
40            case "smart-tabs":
41                smartTabs = true;
42                break;
43            default:
44                smartTabs = false;
45        }
46
47        //--------------------------------------------------------------------------
48        // Public
49        //--------------------------------------------------------------------------
50
51        return {
52
53            "Program:exit"(node) {
54                const lines = sourceCode.lines,
55                    comments = sourceCode.getAllComments(),
56                    ignoredCommentLines = new Set();
57
58                // Add all lines except the first ones.
59                comments.forEach(comment => {
60                    for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) {
61                        ignoredCommentLines.add(i);
62                    }
63                });
64
65                /*
66                 * At least one space followed by a tab
67                 * or the reverse before non-tab/-space
68                 * characters begin.
69                 */
70                let regex = /^(?=( +|\t+))\1(?:\t| )/u;
71
72                if (smartTabs) {
73
74                    /*
75                     * At least one space followed by a tab
76                     * before non-tab/-space characters begin.
77                     */
78                    regex = /^(?=(\t*))\1(?=( +))\2\t/u;
79                }
80
81                lines.forEach((line, i) => {
82                    const match = regex.exec(line);
83
84                    if (match) {
85                        const lineNumber = i + 1;
86                        const loc = {
87                            start: {
88                                line: lineNumber,
89                                column: match[0].length - 2
90                            },
91                            end: {
92                                line: lineNumber,
93                                column: match[0].length
94                            }
95                        };
96
97                        if (!ignoredCommentLines.has(lineNumber)) {
98                            const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
99
100                            if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
101                                context.report({
102                                    node,
103                                    loc,
104                                    messageId: "mixedSpacesAndTabs"
105                                });
106                            }
107                        }
108                    }
109                });
110            }
111        };
112    }
113};
114