/** * @fileoverview Disallow mixed spaces and tabs for indentation * @author Jary Niebur * @copyright 2014 Nicholas C. Zakas. All rights reserved. * @copyright 2014 Jary Niebur. All rights reserved. * See LICENSE in root directory for full license. */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = function(context) { var smartTabs, 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": function(node) { ignoredLocs.push(node.loc); }, "Program:exit": function(node) { /* * At least one space followed by a tab * or the reverse before non-tab/-space * characters begin. */ var regex = /^(?=[\t ]*(\t | \t))/, match, lines = context.getSourceLines(), comments = context.getAllComments(); comments.forEach(function(comment) { ignoredLocs.push(comment.loc); }); ignoredLocs.sort(function(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(function(line, i) { match = regex.exec(line); if (match) { var lineNumber = i + 1, column = match.index + 1; for (var j = 0; j < ignoredLocs.length; j++) { if (beforeLoc(ignoredLocs[j], lineNumber, column)) { continue; } if (afterLoc(ignoredLocs[j], lineNumber, column)) { continue; } return; } context.report(node, { line: lineNumber, column: column }, "Mixed spaces and tabs."); } }); } }; }; module.exports.schema = [ { "enum": ["smart-tabs", true, false] } ];