no-multiple-empty-lines.js 4.17 KB
/**
 * @fileoverview Disallows multiple blank lines.
 * implementation adapted from the no-trailing-spaces rule.
 * @author Greg Cochard
 * @copyright 2014 Greg Cochard. All rights reserved.
 */
"use strict";

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

module.exports = function(context) {

    // Use options.max or 2 as default
    var max = 2,
        maxEOF;

    // store lines that appear empty but really aren't
    var notEmpty = [];

    if (context.options.length) {
        max = context.options[0].max;
        maxEOF = context.options[0].maxEOF;
    }

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

    return {

        "TemplateLiteral": function(node) {
            var start = node.loc.start.line;
            var end = node.loc.end.line;
            while (start <= end) {
                notEmpty.push(start);
                start++;
            }
        },

        "Program:exit": function checkBlankLines(node) {
            var lines = context.getSourceLines(),
                currentLocation = -1,
                lastLocation,
                blankCounter = 0,
                location,
                trimmedLines = lines.map(function(str) {
                    return str.trim();
                }),
                firstOfEndingBlankLines;

            // add the notEmpty lines in there with a placeholder
            notEmpty.forEach(function(x, i) {
                trimmedLines[i] = x;
            });

            if (typeof maxEOF === "undefined") {
                // swallow the final newline, as some editors add it
                // automatically and we don't want it to cause an issue
                if (trimmedLines[trimmedLines.length - 1] === "") {
                    trimmedLines = trimmedLines.slice(0, -1);
                }
                firstOfEndingBlankLines = trimmedLines.length;
            } else {
                // save the number of the first of the last blank lines
                firstOfEndingBlankLines = trimmedLines.length;
                while (trimmedLines[firstOfEndingBlankLines - 1] === ""
                        && firstOfEndingBlankLines > 0) {
                    firstOfEndingBlankLines--;
                }
            }

            // Aggregate and count blank lines
            lastLocation = currentLocation;
            currentLocation = trimmedLines.indexOf("", currentLocation + 1);
            while (currentLocation !== -1) {
                lastLocation = currentLocation;
                currentLocation = trimmedLines.indexOf("", currentLocation + 1);
                if (lastLocation === currentLocation - 1) {
                    blankCounter++;
                } else {
                    location = {
                        line: lastLocation + 1,
                        column: 1
                    };
                    if (lastLocation < firstOfEndingBlankLines) {
                        // within the file, not at the end
                        if (blankCounter >= max) {
                            context.report(node, location,
                                    "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed.");
                        }
                    } else {
                        // inside the last blank lines
                        if (blankCounter >= maxEOF) {
                            context.report(node, location,
                                    "Too many blank lines at the end of file. Max of " + maxEOF + " allowed.");
                        }
                    }

                    // Finally, reset the blank counter
                    blankCounter = 0;
                }
            }
        }
    };

};

module.exports.schema = [
    {
        "type": "object",
        "properties": {
            "max": {
                "type": "integer"
            },
            "maxEOF": {
                "type": "integer"
            }
        },
        "required": ["max"],
        "additionalProperties": false
    }
];