From 9c140f5ffd2a827e657a63f22ea3ad02da8330fb Mon Sep 17 00:00:00 2001 From: Kyle Hensel Date: Fri, 19 Jun 2026 22:11:46 +1000 Subject: [PATCH] feat: add suggestions to no-empty-blocks --- src/rules/no-empty-blocks.js | 26 ++++- tests/rules/no-empty-blocks.test.js | 143 ++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/rules/no-empty-blocks.js b/src/rules/no-empty-blocks.js index 8a9287fd..66aa333d 100644 --- a/src/rules/no-empty-blocks.js +++ b/src/rules/no-empty-blocks.js @@ -9,7 +9,7 @@ /** * @import { CSSRuleDefinition } from "../types.js" - * @typedef {"emptyBlock"} NoEmptyBlocksMessageIds + * @typedef {"emptyBlock" | "removeRule" | "convertToStatement"} NoEmptyBlocksMessageIds * @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoEmptyBlocksMessageIds }>} NoEmptyBlocksRuleDefinition */ @@ -21,6 +21,8 @@ export default /** @satisfies {NoEmptyBlocksRuleDefinition} */ ({ meta: { type: "problem", + hasSuggestions: true, + docs: { description: "Disallow empty blocks", recommended: true, @@ -29,6 +31,8 @@ export default /** @satisfies {NoEmptyBlocksRuleDefinition} */ ({ messages: { emptyBlock: "Unexpected empty block found.", + removeRule: "Remove the empty rule.", + convertToStatement: "Convert to layer statement.", }, }, @@ -36,9 +40,29 @@ export default /** @satisfies {NoEmptyBlocksRuleDefinition} */ ({ return { Block(node) { if (node.children.length === 0) { + const parent = context.sourceCode.getParent(node); + const isNamedAtLayer = + parent.type === "Atrule" && + parent.name === "layer" && + parent.prelude; + context.report({ loc: node.loc, messageId: "emptyBlock", + suggest: isNamedAtLayer + ? [ + { + messageId: "convertToStatement", + fix: fixer => + fixer.replaceText(node, ";"), + }, + ] + : [ + { + messageId: "removeRule", + fix: fixer => fixer.remove(parent), + }, + ], }); } }, diff --git a/tests/rules/no-empty-blocks.test.js b/tests/rules/no-empty-blocks.test.js index 436b8e05..baba0ac1 100644 --- a/tests/rules/no-empty-blocks.test.js +++ b/tests/rules/no-empty-blocks.test.js @@ -34,6 +34,7 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 1, endColumn: 6, + suggestions: [{ messageId: "removeRule", output: "" }], }, ], }, @@ -46,6 +47,7 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 1, endColumn: 20, + suggestions: [{ messageId: "removeRule", output: "" }], }, ], }, @@ -58,6 +60,7 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 2, endColumn: 2, + suggestions: [{ messageId: "removeRule", output: "" }], }, ], }, @@ -70,6 +73,7 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 2, endColumn: 3, + suggestions: [{ messageId: "removeRule", output: "" }], }, ], }, @@ -82,6 +86,12 @@ ruleTester.run("no-empty-blocks", rule, { column: 14, endLine: 1, endColumn: 17, + suggestions: [ + { + messageId: "removeRule", + output: "", + }, + ], }, ], }, @@ -94,6 +104,12 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 1, endColumn: 6, + suggestions: [ + { + messageId: "removeRule", + output: "\n@media print { \nb { } \n}", // only a{} was removed + }, + ], }, { messageId: "emptyBlock", @@ -101,6 +117,133 @@ ruleTester.run("no-empty-blocks", rule, { column: 3, endLine: 3, endColumn: 6, + suggestions: [ + { + messageId: "removeRule", + output: "a { }\n@media print { \n \n}", // only b{} was removed + }, + ], + }, + ], + }, + { + code: "@layer defaults { \n }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 17, + endLine: 2, + endColumn: 3, + suggestions: [ + { + messageId: "convertToStatement", + output: "@layer defaults ;", + }, + ], + }, + ], + }, + { + code: "@layer a, b { \n }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 13, + endLine: 2, + endColumn: 3, + suggestions: [ + { + messageId: "convertToStatement", + output: "@layer a, b ;", + }, + ], + }, + ], + }, + { + code: "@layer { \n }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 8, + endLine: 2, + endColumn: 3, + suggestions: [ + { + messageId: "removeRule", + output: "", + }, + ], + }, + ], + }, + { + code: "@supports not selector(h2 > p) { }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 32, + endLine: 1, + endColumn: 35, + suggestions: [ + { + messageId: "removeRule", + output: "", + }, + ], + }, + ], + }, + { + code: "@keyframes a { 0% {} 100% {} }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 19, + endLine: 1, + endColumn: 21, + suggestions: [ + { + messageId: "removeRule", + output: "@keyframes a { 100% {} }", + }, + ], + }, + { + messageId: "emptyBlock", + line: 1, + column: 27, + endLine: 1, + endColumn: 29, + suggestions: [ + { + messageId: "removeRule", + output: "@keyframes a { 0% {} }", + }, + ], + }, + ], + }, + { + code: "@keyframes a { }", + errors: [ + { + messageId: "emptyBlock", + line: 1, + column: 14, + endLine: 1, + endColumn: 17, + suggestions: [ + { + messageId: "removeRule", + output: "", + }, + ], }, ], },