| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 | /** * @fileoverview A rule to ensure blank lines within blocks. * @author Mathias Schreck <https://github.com/lo1tuma> */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const astUtils = require("./utils/ast-utils");//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "layout",        docs: {            description: "Require or disallow padding within blocks",            recommended: false,            url: "https://eslint.org/docs/rules/padded-blocks"        },        fixable: "whitespace",        schema: [            {                oneOf: [                    {                        enum: ["always", "never"]                    },                    {                        type: "object",                        properties: {                            blocks: {                                enum: ["always", "never"]                            },                            switches: {                                enum: ["always", "never"]                            },                            classes: {                                enum: ["always", "never"]                            }                        },                        additionalProperties: false,                        minProperties: 1                    }                ]            },            {                type: "object",                properties: {                    allowSingleLineBlocks: {                        type: "boolean"                    }                },                additionalProperties: false            }        ],        messages: {            alwaysPadBlock: "Block must be padded by blank lines.",            neverPadBlock: "Block must not be padded by blank lines."        }    },    create(context) {        const options = {};        const typeOptions = context.options[0] || "always";        const exceptOptions = context.options[1] || {};        if (typeof typeOptions === "string") {            const shouldHavePadding = typeOptions === "always";            options.blocks = shouldHavePadding;            options.switches = shouldHavePadding;            options.classes = shouldHavePadding;        } else {            if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) {                options.blocks = typeOptions.blocks === "always";            }            if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) {                options.switches = typeOptions.switches === "always";            }            if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) {                options.classes = typeOptions.classes === "always";            }        }        if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) {            options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true;        }        const sourceCode = context.getSourceCode();        /**         * Gets the open brace token from a given node.         * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace.         * @returns {Token} The token of the open brace.         */        function getOpenBrace(node) {            if (node.type === "SwitchStatement") {                return sourceCode.getTokenBefore(node.cases[0]);            }            if (node.type === "StaticBlock") {                return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token            }            // `BlockStatement` or `ClassBody`            return sourceCode.getFirstToken(node);        }        /**         * Checks if the given parameter is a comment node         * @param {ASTNode|Token} node An AST node or token         * @returns {boolean} True if node is a comment         */        function isComment(node) {            return node.type === "Line" || node.type === "Block";        }        /**         * Checks if there is padding between two tokens         * @param {Token} first The first token         * @param {Token} second The second token         * @returns {boolean} True if there is at least a line between the tokens         */        function isPaddingBetweenTokens(first, second) {            return second.loc.start.line - first.loc.end.line >= 2;        }        /**         * Checks if the given token has a blank line after it.         * @param {Token} token The token to check.         * @returns {boolean} Whether or not the token is followed by a blank line.         */        function getFirstBlockToken(token) {            let prev,                first = token;            do {                prev = first;                first = sourceCode.getTokenAfter(first, { includeComments: true });            } while (isComment(first) && first.loc.start.line === prev.loc.end.line);            return first;        }        /**         * Checks if the given token is preceded by a blank line.         * @param {Token} token The token to check         * @returns {boolean} Whether or not the token is preceded by a blank line         */        function getLastBlockToken(token) {            let last = token,                next;            do {                next = last;                last = sourceCode.getTokenBefore(last, { includeComments: true });            } while (isComment(last) && last.loc.end.line === next.loc.start.line);            return last;        }        /**         * Checks if a node should be padded, according to the rule config.         * @param {ASTNode} node The AST node to check.         * @throws {Error} (Unreachable)         * @returns {boolean} True if the node should be padded, false otherwise.         */        function requirePaddingFor(node) {            switch (node.type) {                case "BlockStatement":                case "StaticBlock":                    return options.blocks;                case "SwitchStatement":                    return options.switches;                case "ClassBody":                    return options.classes;                /* c8 ignore next */                default:                    throw new Error("unreachable");            }        }        /**         * Checks the given BlockStatement node to be padded if the block is not empty.         * @param {ASTNode} node The AST node of a BlockStatement.         * @returns {void} undefined.         */        function checkPadding(node) {            const openBrace = getOpenBrace(node),                firstBlockToken = getFirstBlockToken(openBrace),                tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),                closeBrace = sourceCode.getLastToken(node),                lastBlockToken = getLastBlockToken(closeBrace),                tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),                blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),                blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);            if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) {                return;            }            if (requirePaddingFor(node)) {                if (!blockHasTopPadding) {                    context.report({                        node,                        loc: {                            start: tokenBeforeFirst.loc.start,                            end: firstBlockToken.loc.start                        },                        fix(fixer) {                            return fixer.insertTextAfter(tokenBeforeFirst, "\n");                        },                        messageId: "alwaysPadBlock"                    });                }                if (!blockHasBottomPadding) {                    context.report({                        node,                        loc: {                            end: tokenAfterLast.loc.start,                            start: lastBlockToken.loc.end                        },                        fix(fixer) {                            return fixer.insertTextBefore(tokenAfterLast, "\n");                        },                        messageId: "alwaysPadBlock"                    });                }            } else {                if (blockHasTopPadding) {                    context.report({                        node,                        loc: {                            start: tokenBeforeFirst.loc.start,                            end: firstBlockToken.loc.start                        },                        fix(fixer) {                            return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");                        },                        messageId: "neverPadBlock"                    });                }                if (blockHasBottomPadding) {                    context.report({                        node,                        loc: {                            end: tokenAfterLast.loc.start,                            start: lastBlockToken.loc.end                        },                        messageId: "neverPadBlock",                        fix(fixer) {                            return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");                        }                    });                }            }        }        const rule = {};        if (Object.prototype.hasOwnProperty.call(options, "switches")) {            rule.SwitchStatement = function(node) {                if (node.cases.length === 0) {                    return;                }                checkPadding(node);            };        }        if (Object.prototype.hasOwnProperty.call(options, "blocks")) {            rule.BlockStatement = function(node) {                if (node.body.length === 0) {                    return;                }                checkPadding(node);            };            rule.StaticBlock = rule.BlockStatement;        }        if (Object.prototype.hasOwnProperty.call(options, "classes")) {            rule.ClassBody = function(node) {                if (node.body.length === 0) {                    return;                }                checkPadding(node);            };        }        return rule;    }};
 |