| "use strict"; |
| |
| const addEmptyLineAfter = require("../../utils/addEmptyLineAfter"); |
| const blockString = require("../../utils/blockString"); |
| const hasBlock = require("../../utils/hasBlock"); |
| const hasEmptyBlock = require("../../utils/hasEmptyBlock"); |
| const hasEmptyLine = require("../../utils/hasEmptyLine"); |
| const isSingleLineString = require("../../utils/isSingleLineString"); |
| const optionsMatches = require("../../utils/optionsMatches"); |
| const removeEmptyLineAfter = require("../../utils/removeEmptyLinesAfter"); |
| const report = require("../../utils/report"); |
| const ruleMessages = require("../../utils/ruleMessages"); |
| const validateOptions = require("../../utils/validateOptions"); |
| |
| const ruleName = "block-closing-brace-empty-line-before"; |
| |
| const messages = ruleMessages(ruleName, { |
| expected: "Expected empty line before closing brace", |
| rejected: "Unexpected empty line before closing brace" |
| }); |
| |
| const rule = function(expectation, options, context) { |
| return (root, result) => { |
| const validOptions = validateOptions( |
| result, |
| ruleName, |
| { |
| actual: expectation, |
| possible: ["always-multi-line", "never"] |
| }, |
| { |
| actual: options, |
| possible: { |
| except: ["after-closing-brace"] |
| }, |
| optional: true |
| } |
| ); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| // Check both kinds of statements: rules and at-rules |
| root.walkRules(check); |
| root.walkAtRules(check); |
| |
| function check(statement) { |
| // Return early if blockless or has empty block |
| if (!hasBlock(statement) || hasEmptyBlock(statement)) { |
| return; |
| } |
| |
| // Get whitespace after ""}", ignoring extra semicolon |
| const before = (statement.raws.after || "").replace(/;+/, ""); |
| |
| // Calculate index |
| const statementString = statement.toString(); |
| let index = statementString.length - 1; |
| |
| if (statementString[index - 1] === "\r") { |
| index -= 1; |
| } |
| |
| // Set expectation |
| const expectEmptyLineBefore = (() => { |
| const childNodeTypes = statement.nodes.map(item => item.type); |
| |
| // Reverse the primary options if `after-closing-brace` is set |
| if ( |
| optionsMatches(options, "except", "after-closing-brace") && |
| (statement.type === "atrule" && childNodeTypes.indexOf("decl") === -1) |
| ) { |
| return expectation === "never" ? true : false; |
| } |
| |
| return expectation === "always-multi-line" && |
| !isSingleLineString(blockString(statement)) |
| ? true |
| : false; |
| })(); |
| |
| // Check for at least one empty line |
| const hasEmptyLineBefore = hasEmptyLine(before); |
| |
| // Return if the expectation is met |
| if (expectEmptyLineBefore === hasEmptyLineBefore) { |
| return; |
| } |
| |
| if (context.fix) { |
| if (expectEmptyLineBefore) { |
| addEmptyLineAfter(statement, context.newline); |
| } else { |
| removeEmptyLineAfter(statement, context.newline); |
| } |
| |
| return; |
| } |
| |
| const message = expectEmptyLineBefore |
| ? messages.expected |
| : messages.rejected; |
| |
| report({ |
| message, |
| result, |
| ruleName, |
| node: statement, |
| index |
| }); |
| } |
| }; |
| }; |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |