summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js
blob: 2b8e89d3c8712373d041e1a1e221bdfe1f761db7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * @fileoverview Disallow mixed spaces and tabs for indentation
 * @author Jary Niebur
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        docs: {
            description: "disallow mixed spaces and tabs for indentation",
            category: "Stylistic Issues",
            recommended: true
        },

        schema: [
            {
                enum: ["smart-tabs", true, false]
            }
        ]
    },

    create(context) {
        const sourceCode = context.getSourceCode();

        let smartTabs;
        const ignoredLocs = [];

        switch (context.options[0]) {
            case true: // Support old syntax, maybe add deprecation warning here
            case "smart-tabs":
                smartTabs = true;
                break;
            default:
                smartTabs = false;
        }

        /**
         * Determines if a given line and column are before a location.
         * @param {Location} loc The location object from an AST node.
         * @param {int} line The line to check.
         * @param {int} column The column to check.
         * @returns {boolean} True if the line and column are before the location, false if not.
         * @private
         */
        function beforeLoc(loc, line, column) {
            if (line < loc.start.line) {
                return true;
            }
            return line === loc.start.line && column < loc.start.column;
        }

        /**
         * Determines if a given line and column are after a location.
         * @param {Location} loc The location object from an AST node.
         * @param {int} line The line to check.
         * @param {int} column The column to check.
         * @returns {boolean} True if the line and column are after the location, false if not.
         * @private
         */
        function afterLoc(loc, line, column) {
            if (line > loc.end.line) {
                return true;
            }
            return line === loc.end.line && column > loc.end.column;
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {

            TemplateElement(node) {
                ignoredLocs.push(node.loc);
            },

            "Program:exit"(node) {

                /*
                 * At least one space followed by a tab
                 * or the reverse before non-tab/-space
                 * characters begin.
                 */
                let regex = /^(?=[\t ]*(\t | \t))/;
                const lines = sourceCode.lines,
                    comments = sourceCode.getAllComments();

                comments.forEach(comment => {
                    ignoredLocs.push(comment.loc);
                });

                ignoredLocs.sort((first, second) => {
                    if (beforeLoc(first, second.start.line, second.start.column)) {
                        return 1;
                    }

                    if (beforeLoc(second, first.start.line, second.start.column)) {
                        return -1;
                    }

                    return 0;
                });

                if (smartTabs) {

                    /*
                     * At least one space followed by a tab
                     * before non-tab/-space characters begin.
                     */
                    regex = /^(?=[\t ]* \t)/;
                }

                lines.forEach((line, i) => {
                    const match = regex.exec(line);

                    if (match) {
                        const lineNumber = i + 1,
                            column = match.index + 1;

                        for (let j = 0; j < ignoredLocs.length; j++) {
                            if (beforeLoc(ignoredLocs[j], lineNumber, column)) {
                                continue;
                            }
                            if (afterLoc(ignoredLocs[j], lineNumber, column)) {
                                continue;
                            }

                            return;
                        }

                        context.report({ node, loc: { line: lineNumber, column }, message: "Mixed spaces and tabs." });
                    }
                });
            }

        };

    }
};