diff --git a/docs/user-guide/rules.md b/docs/user-guide/rules.md index 0602443cf1..61457602a9 100644 --- a/docs/user-guide/rules.md +++ b/docs/user-guide/rules.md @@ -87,7 +87,7 @@ Here are all the rules within stylelint, grouped by the [*thing*](http://apps.wo ### Custom property -- [`custom-property-empty-line-before`](../../lib/rules/custom-property-empty-line-before/README.md): Require or disallow an empty line before custom properties. +- [`custom-property-empty-line-before`](../../lib/rules/custom-property-empty-line-before/README.md): Require or disallow an empty line before custom properties (Autofixable). - [`custom-property-no-outside-root`](../../lib/rules/custom-property-no-outside-root/README.md): Disallow custom properties outside of `:root` rules **(deprecated)**. - [`custom-property-pattern`](../../lib/rules/custom-property-pattern/README.md): Specify a pattern for custom properties. @@ -114,7 +114,7 @@ Here are all the rules within stylelint, grouped by the [*thing*](http://apps.wo - [`declaration-colon-newline-after`](../../lib/rules/declaration-colon-newline-after/README.md): Require a newline or disallow whitespace after the colon of declarations. - [`declaration-colon-space-after`](../../lib/rules/declaration-colon-space-after/README.md): Require a single space or disallow whitespace after the colon of declarations. - [`declaration-colon-space-before`](../../lib/rules/declaration-colon-space-before/README.md): Require a single space or disallow whitespace before the colon of declarations. -- [`declaration-empty-line-before`](../../lib/rules/declaration-empty-line-before/README.md): Require or disallow an empty line before declarations. +- [`declaration-empty-line-before`](../../lib/rules/declaration-empty-line-before/README.md): Require or disallow an empty line before declarations (Autofixable). - [`declaration-no-important`](../../lib/rules/declaration-no-important/README.md): Disallow `!important` within declarations. - [`declaration-property-unit-blacklist`](../../lib/rules/declaration-property-unit-blacklist/README.md): Specify a blacklist of disallowed property and unit pairs within declarations. - [`declaration-property-unit-whitelist`](../../lib/rules/declaration-property-unit-whitelist/README.md): Specify a whitelist of allowed property and unit pairs within declarations. @@ -199,7 +199,7 @@ Here are all the rules within stylelint, grouped by the [*thing*](http://apps.wo ### Rule -- [`rule-empty-line-before`](../../lib/rules/rule-empty-line-before/README.md): Require or disallow an empty line before rules. +- [`rule-empty-line-before`](../../lib/rules/rule-empty-line-before/README.md): Require or disallow an empty line before rules (Autofixable). - [`rule-nested-empty-line-before`](../../lib/rules/rule-nested-empty-line-before/README.md): Require or disallow an empty line before nested rules **(deprecated)**. - [`rule-non-nested-empty-line-before`](../../lib/rules/rule-non-nested-empty-line-before/README.md): Require or disallow an empty line before non-nested rules **(deprecated)**. @@ -231,7 +231,7 @@ Here are all the rules within stylelint, grouped by the [*thing*](http://apps.wo ### At-rule - [`at-rule-blacklist`](../../lib/rules/at-rule-blacklist/README.md): Specify a blacklist of disallowed at-rules. -- [`at-rule-empty-line-before`](../../lib/rules/at-rule-empty-line-before/README.md): Require or disallow an empty line before at-rules. +- [`at-rule-empty-line-before`](../../lib/rules/at-rule-empty-line-before/README.md): Require or disallow an empty line before at-rules (Autofixable). - [`at-rule-name-case`](../../lib/rules/at-rule-name-case/README.md): Specify lowercase or uppercase for at-rules names (Autofixable). - [`at-rule-name-newline-after`](../../lib/rules/at-rule-name-newline-after/README.md): Require a newline after at-rule names. - [`at-rule-name-space-after`](../../lib/rules/at-rule-name-space-after/README.md): Require a single space after at-rule names. diff --git a/lib/rules/at-rule-empty-line-before/README.md b/lib/rules/at-rule-empty-line-before/README.md index cede7a0e51..3ae81ee8a0 100644 --- a/lib/rules/at-rule-empty-line-before/README.md +++ b/lib/rules/at-rule-empty-line-before/README.md @@ -12,6 +12,8 @@ a {} If the at-rule is the very first node in a stylesheet then it is ignored. +The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix all of the problems reported by this rule. + ## Options `string`: `"always"|"never"` diff --git a/lib/rules/at-rule-empty-line-before/__tests__/index.js b/lib/rules/at-rule-empty-line-before/__tests__/index.js index c932400a63..73ea3898a7 100644 --- a/lib/rules/at-rule-empty-line-before/__tests__/index.js +++ b/lib/rules/at-rule-empty-line-before/__tests__/index.js @@ -42,33 +42,43 @@ const sharedAlwaysTests = { reject: [ { code: "a {} @media {}", + fixed: "a {}\n\n @media {}", message: messages.expected, }, { code: "a {} @mEdIa {}", + fixed: "a {}\n\n @mEdIa {}", message: messages.expected, }, { code: "a {} @MEDIA {}", + fixed: "a {}\n\n @MEDIA {}", message: messages.expected, }, { code: "@keyframes foo {} @media {}", + fixed: "@keyframes foo {}\n\n @media {}", message: messages.expected, }, { code: "@-webkit-keyframes foo {} @media {}", + fixed: "@-webkit-keyframes foo {}\n\n @media {}", message: messages.expected, }, { code: "@-webkit-keyframes foo {} @-webkit-keyframes bar {}", + fixed: "@-webkit-keyframes foo {}\n\n @-webkit-keyframes bar {}", message: messages.expected, }, { code: "a {}\n@media {}", + fixed: "a {}\n\n@media {}", message: messages.expected, }, { code: "a {}\r\n@media {}", + fixed: "a {}\r\n\r\n@media {}", message: messages.expected, }, { code: "a {}\n\n/* comment */\n@media {}", + fixed: "a {}\n\n/* comment */\n\n@media {}", message: messages.expected, }, { code: "a {}\r\n\r\n/* comment */\r\n@media {}", + fixed: "a {}\r\n\r\n/* comment */\r\n\r\n@media {}", message: messages.expected, } ], } @@ -92,9 +102,19 @@ const sharedNeverTests = { reject: [ { code: "a {}\n\n@media {}", + fixed: "a {}\n@media {}", + message: messages.rejected, + }, { + code: "a {}\r\n\r\n@media {}", + fixed: "a {}\r\n@media {}", message: messages.rejected, }, { code: "@keyframes foo {}\n/* comment */\n\n@media {}", + fixed: "@keyframes foo {}\n/* comment */\n@media {}", + message: messages.rejected, + }, { + code: "@keyframes foo {}\r\n/* comment */\r\n\r\n@media {}", + fixed: "@keyframes foo {}\r\n/* comment */\r\n@media {}", message: messages.rejected, } ], } @@ -102,6 +122,7 @@ const sharedNeverTests = { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: ["always"], + fix: true, accept: [{ code: "a {\n\n @mixin foo;\n}", @@ -111,6 +132,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: ["blockless-group"] } ], + fix: true, accept: [ { code: "a {\n\n @mixin foo;\n}", @@ -127,9 +149,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "@keyframes foo {}\n@import 'x.css'", + fixed: "@keyframes foo {}\n\n@import 'x.css'", message: messages.expected, }, { code: "@import 'x.css';\n\n@import 'y.css'", + fixed: "@import 'x.css';\n@import 'y.css'", message: messages.rejected, } ], })) @@ -137,6 +161,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: ["blockless-after-blockless"] } ], + fix: true, accept: [ { code: "a {\n\n @mixin foo;\n}", @@ -153,9 +178,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "@keyframes foo {}\n@import 'x.css'", + fixed: "@keyframes foo {}\n\n@import 'x.css'", message: messages.expected, }, { code: "@import 'x.css';\n\n@import 'y.css'", + fixed: "@import 'x.css';\n@import 'y.css'", message: messages.rejected, } ], })) @@ -163,6 +190,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, { ruleName, config: [ "always", { ignore: ["blockless-group"] } ], + fix: true, accept: [{ code: "@media {}; @import 'x.css';", @@ -170,9 +198,11 @@ testRule(rule, { reject: [ { code: "@import 'x.css'; @media {};", + fixed: "@import 'x.css';\n\n @media {};", message: messages.expected, }, { code: "@import 'test'; @include mixin(1) { @content; };", + fixed: "@import 'test';\n\n @include mixin(1) { @content; };", message: messages.expected, } ], }) @@ -180,6 +210,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["blockless-after-blockless"] } ], + fix: true, accept: [{ code: "@media {}; @import 'x.css';", @@ -187,9 +218,11 @@ testRule(rule, { reject: [ { code: "@import 'x.css'; @media {};", + fixed: "@import 'x.css';\n\n @media {};", message: messages.expected, }, { code: "@import 'test'; @include mixin(1) { @content; };", + fixed: "@import 'test';\n\n @include mixin(1) { @content; };", message: messages.expected, } ], }) @@ -197,6 +230,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["after-comment"] } ], + fix: true, accept: [ { code: "/* foo */\n@media {}", @@ -209,6 +243,7 @@ testRule(rule, { reject: [{ code: "a {} @media {}", + fixed: "a {}\n\n @media {}", message: messages.expected, }], }) @@ -216,6 +251,7 @@ testRule(rule, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: ["all-nested"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -225,9 +261,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "a {\n\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n @mixin foo;\n color: pink;\n}", message: messages.rejected, }, { code: "a {\n\n color: pink;\n\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n @mixin foo;\n}", message: messages.rejected, } ], })) @@ -235,6 +273,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: ["inside-block"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -244,9 +283,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "a {\n\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n @mixin foo;\n color: pink;\n}", message: messages.rejected, }, { code: "a {\n\n color: pink;\n\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n @mixin foo;\n}", message: messages.rejected, } ], })) @@ -254,6 +295,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: ["first-nested"] } ], + fix: true, accept: [{ code: "a {\n @mixin foo;\n color: pink;\n}", @@ -261,9 +303,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "a {\n color: pink;\n @mixin foo;\n}", + fixed: "a {\n color: pink;\n\n @mixin foo;\n}", message: messages.expected, }, { code: "a {\n\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n @mixin foo;\n color: pink;\n}", message: messages.rejected, } ], })) @@ -271,6 +315,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { ignore: ["all-nested"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -286,6 +331,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { ignore: ["inside-block"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -301,6 +347,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: [ "blockless-group", "all-nested" ] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -321,15 +368,19 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "@keyframes foo {}\n@import 'x.css'", + fixed: "@keyframes foo {}\n\n@import 'x.css'", message: messages.expected, }, { code: "@import 'x.css';\n\n@import 'y.css'", + fixed: "@import 'x.css';\n@import 'y.css'", message: messages.rejected, }, { code: "a {\n\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n @mixin foo;\n color: pink;\n}", message: messages.rejected, }, { code: "a {\n\n color: pink;\n\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n @mixin foo;\n}", message: messages.rejected, } ], })) @@ -337,6 +388,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { ruleName, config: [ "always", { except: [ "blockless-after-blockless", "inside-block" ] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -357,15 +409,19 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: "@keyframes foo {}\n@import 'x.css'", + fixed: "@keyframes foo {}\n\n@import 'x.css'", message: messages.expected, }, { code: "@import 'x.css';\n\n@import 'y.css'", + fixed: "@import 'x.css';\n@import 'y.css'", message: messages.rejected, }, { code: "a {\n\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n @mixin foo;\n color: pink;\n}", message: messages.rejected, }, { code: "a {\n\n color: pink;\n\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n @mixin foo;\n}", message: messages.rejected, } ], })) @@ -373,6 +429,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: ["never"], + fix: true, accept: [{ code: "a {\n @mixin foo;\n}", @@ -382,6 +439,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { except: ["blockless-group"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n}", @@ -398,9 +456,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { reject: [ { code: "@keyframes foo {}\n\n@import 'x.css'", + fixed: "@keyframes foo {}\n@import 'x.css'", message: messages.rejected, }, { code: "@import 'x.css';\n@import 'y.css'", + fixed: "@import 'x.css';\n\n@import 'y.css'", message: messages.expected, } ], })) @@ -408,6 +468,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { except: ["blockless-after-blockless"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n}", @@ -424,9 +485,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { reject: [ { code: "@keyframes foo {}\n\n@import 'x.css'", + fixed: "@keyframes foo {}\n@import 'x.css'", message: messages.rejected, }, { code: "@import 'x.css';\n@import 'y.css'", + fixed: "@import 'x.css';\n\n@import 'y.css'", message: messages.expected, } ], })) @@ -434,6 +497,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { except: ["all-nested"] } ], + fix: true, accept: [ { code: "a {\n\n @mixin foo;\n color: pink;\n}", @@ -443,9 +507,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { reject: [ { code: "a {\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n\n @mixin foo;\n color: pink;\n}", message: messages.expected, }, { code: "a {\n\n color: pink;\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n\n @mixin foo;\n}", message: messages.expected, } ], })) @@ -453,6 +519,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { except: ["inside-block"] } ], + fix: true, accept: [ { code: "a {\n\n @mixin foo;\n color: pink;\n}", @@ -462,9 +529,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { reject: [ { code: "a {\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n\n @mixin foo;\n color: pink;\n}", message: messages.expected, }, { code: "a {\n\n color: pink;\n @mixin foo;\n}", + fixed: "a {\n\n color: pink;\n\n @mixin foo;\n}", message: messages.expected, } ], })) @@ -472,6 +541,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { except: ["first-nested"] } ], + fix: true, accept: [{ code: "a {\n\n @mixin foo;\n color: pink;\n}", @@ -479,9 +549,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { reject: [ { code: "a {\n color: pink;\n\n @mixin foo;\n}", + fixed: "a {\n color: pink;\n @mixin foo;\n}", message: messages.rejected, }, { code: "a {\n @mixin foo;\n color: pink;\n}", + fixed: "a {\n\n @mixin foo;\n color: pink;\n}", message: messages.expected, } ], })) @@ -489,6 +561,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, { ruleName, config: [ "never", { ignore: ["blockless-group"] } ], + fix: true, accept: [ { code: ` @@ -510,6 +583,10 @@ testRule(rule, { @media {}; `, + fixed: ` + @import 'x.css'; + @media {}; + `, message: messages.rejected, line: 4, column: 7, @@ -519,6 +596,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { ignore: ["blockless-after-blockless"] } ], + fix: true, accept: [ { code: ` @@ -540,6 +618,10 @@ testRule(rule, { @media {}; `, + fixed: ` + @import 'x.css'; + @media {}; + `, message: messages.rejected, line: 4, column: 7, @@ -549,6 +631,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { ignore: ["after-comment"] } ], + fix: true, accept: [ { code: "/* foo */\n@media {}", @@ -561,9 +644,11 @@ testRule(rule, { reject: [ { code: "b {}\n\n@media {}", + fixed: "b {}\n@media {}", message: messages.rejected, }, { code: "b {}\r\n\r\n@media {}", + fixed: "b {}\r\n@media {}", description: "CRLF", message: messages.rejected, } ], @@ -572,6 +657,7 @@ testRule(rule, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { ignore: ["all-nested"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -587,6 +673,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, mergeTestDescriptions(sharedNeverTests, { ruleName, config: [ "never", { ignore: ["inside-block"] } ], + fix: true, accept: [ { code: "a {\n @mixin foo;\n color: pink;\n}", @@ -602,6 +689,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { testRule(rule, { ruleName, config: [ "always", { ignoreAtRules: ["else"] } ], + fix: true, accept: [ { code: ` @@ -632,6 +720,13 @@ testRule(rule, { } @else-mixin { } `, + fixed: ` + @if(true) { + } + + @else-mixin { + } + `, message: messages.expected, line: 3, column: 9, @@ -641,6 +736,12 @@ testRule(rule, { @if (false) { } `, + fixed: ` + @if (true) {} + + @if (false) { + } + `, message: messages.expected, line: 3, column: 7, @@ -650,6 +751,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignoreAtRules: "/el/" } ], + fix: true, accept: [ { code: "@keyframes {}; @an-element-mixin();", @@ -668,6 +770,13 @@ testRule(rule, { } @if true {} `, + fixed: ` + @else { + color: pink; + } + + @if true {} + `, message: messages.expected, line: 5, column: 7, @@ -677,6 +786,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { ignoreAtRules: ["else"] } ], + fix: true, accept: [ { code: ` @@ -709,6 +819,11 @@ testRule(rule, { @else-mixin {} `, + fixed: ` + @if(true) { + } + @else-mixin {} + `, message: messages.rejected, line: 5, column: 7, @@ -720,6 +835,12 @@ testRule(rule, { @if (false) { } `, + fixed: ` + @if (true) + {} + @if (false) { + } + `, message: messages.rejected, line: 5, column: 7, @@ -729,6 +850,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { ignoreAtRules: "/el/" } ], + fix: true, accept: [ { code: ` @@ -753,6 +875,12 @@ testRule(rule, { @if true {} `, + fixed: ` + @else { + color: pink; + } + @if true {} + `, message: messages.rejected, line: 6, column: 7, @@ -764,6 +892,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { config: [ "always", { ignore: ["blockless-after-same-name-blockless"], } ], + fix: true, accept: [ { code: stripIndent` @@ -786,6 +915,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: stripIndent` @charset "UTF-8"; + @import url(x.css); + @import url(y.css);`, + fixed: stripIndent` + @charset "UTF-8"; + @import url(x.css); @import url(y.css);`, message: messages.expected, @@ -797,6 +931,15 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { @extends .foo; @extends .bar; + @include loop; + @include doo; + }`, + fixed: stripIndent` + a { + + @extends .foo; + @extends .bar; + @include loop; @include doo; }`, @@ -811,6 +954,7 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { config: [ "always", { except: ["blockless-after-same-name-blockless"], } ], + fix: true, accept: [ { code: stripIndent` @@ -833,6 +977,11 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { reject: [ { code: stripIndent` @charset "UTF-8"; + @import url(x.css); + @import url(y.css);`, + fixed: stripIndent` + @charset "UTF-8"; + @import url(x.css); @import url(y.css);`, message: messages.expected, @@ -844,6 +993,15 @@ testRule(rule, mergeTestDescriptions(sharedAlwaysTests, { @extends .foo; @extends .bar; + @include loop; + @include doo; + }`, + fixed: stripIndent` + a { + + @extends .foo; + @extends .bar; + @include loop; @include doo; }`, @@ -858,6 +1016,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { config: [ "never", { except: ["blockless-after-same-name-blockless"], } ], + fix: true, accept: [ { code: stripIndent` @@ -881,6 +1040,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { code: stripIndent` @charset "UTF-8"; @import url(x.css); + @import url(y.css);`, + fixed: stripIndent` + @charset "UTF-8"; + @import url(x.css); + @import url(y.css);`, message: messages.expected, line: 3, @@ -890,6 +1054,13 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { a { @extends .bar; @include loop; + @include doo; + }`, + fixed: stripIndent` + a { + @extends .bar; + @include loop; + @include doo; }`, message: messages.expected, @@ -903,6 +1074,7 @@ testRule(rule, { config: [ "always", { except: ["after-same-name"], } ], + fix: true, accept: [ { code: stripIndent` @@ -925,6 +1097,11 @@ testRule(rule, { reject: [ { code: stripIndent` @charset "UTF-8"; + @include x; + @include y {}`, + fixed: stripIndent` + @charset "UTF-8"; + @include x; @include y {}`, message: messages.expected, @@ -936,6 +1113,15 @@ testRule(rule, { @extends .foo; @extends .bar; + @include y {} + @include x; + }`, + fixed: stripIndent` + a { + + @extends .foo; + @extends .bar; + @include y {} @include x; }`, @@ -950,6 +1136,7 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { config: [ "never", { except: ["after-same-name"], } ], + fix: true, accept: [ { code: stripIndent` @@ -973,6 +1160,11 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { code: stripIndent` @charset "UTF-8"; @include x; + @include y {}`, + fixed: stripIndent` + @charset "UTF-8"; + @include x; + @include y {}`, message: messages.expected, line: 3, @@ -982,6 +1174,13 @@ testRule(rule, mergeTestDescriptions(sharedNeverTests, { a { @extends .bar; @include x; + @include y {} + }`, + fixed: stripIndent` + a { + @extends .bar; + @include x; + @include y {} }`, message: messages.expected, diff --git a/lib/rules/at-rule-empty-line-before/index.js b/lib/rules/at-rule-empty-line-before/index.js index 52bbf122eb..504b663cb5 100644 --- a/lib/rules/at-rule-empty-line-before/index.js +++ b/lib/rules/at-rule-empty-line-before/index.js @@ -1,9 +1,11 @@ "use strict" const _ = require("lodash") +const addEmptyLineBefore = require("../../utils/addEmptyLineBefore") const hasBlock = require("../../utils/hasBlock") const hasEmptyLine = require("../../utils/hasEmptyLine") const optionsMatches = require("../../utils/optionsMatches") +const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") @@ -15,7 +17,7 @@ const messages = ruleMessages(ruleName, { rejected: "Unexpected empty line before at-rule", }) -const rule = function (expectation, options) { +const rule = function (expectation, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, @@ -158,6 +160,17 @@ const rule = function (expectation, options) { return } + // Fix + if (context.fix) { + if (expectEmptyLineBefore) { + addEmptyLineBefore(atRule, context.newline) + } else { + removeEmptyLinesBefore(atRule, context.newline) + } + + return + } + const message = expectEmptyLineBefore ? messages.expected : messages.rejected diff --git a/lib/rules/comment-empty-line-before/README.md b/lib/rules/comment-empty-line-before/README.md index 81d8a95b96..91563dfae1 100644 --- a/lib/rules/comment-empty-line-before/README.md +++ b/lib/rules/comment-empty-line-before/README.md @@ -16,7 +16,7 @@ If you're using a custom syntax which support single-line comments with `//`, th **Caveat:** Comments within *selector and value lists* are currently ignored. -The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix some of the problems reported by this rule. +The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix all of the problems reported by this rule. ## Options diff --git a/lib/rules/comment-empty-line-before/index.js b/lib/rules/comment-empty-line-before/index.js index 464d2b3b65..7f9950a558 100644 --- a/lib/rules/comment-empty-line-before/index.js +++ b/lib/rules/comment-empty-line-before/index.js @@ -125,7 +125,8 @@ const rule = function (expectation, options, context) { } else { removeEmptyLinesBefore(comment, context.newline) } - return result + + return } const message = expectEmptyLineBefore diff --git a/lib/rules/custom-property-empty-line-before/README.md b/lib/rules/custom-property-empty-line-before/README.md index 827b3975ba..a833cbdbd2 100644 --- a/lib/rules/custom-property-empty-line-before/README.md +++ b/lib/rules/custom-property-empty-line-before/README.md @@ -12,6 +12,8 @@ a { * This line */ ``` +The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix all of the problems reported by this rule. + ## Options `string`: `"always"|"never"` diff --git a/lib/rules/custom-property-empty-line-before/__tests__/index.js b/lib/rules/custom-property-empty-line-before/__tests__/index.js index 735f29d551..e6ba59bf63 100644 --- a/lib/rules/custom-property-empty-line-before/__tests__/index.js +++ b/lib/rules/custom-property-empty-line-before/__tests__/index.js @@ -9,6 +9,7 @@ const rule = rules[ruleName] testRule(rule, { ruleName, config: ["always"], + fix: true, accept: [ { code: "a {\n\n --custom-prop: value;\n}", @@ -30,36 +31,43 @@ testRule(rule, { reject: [ { code: "a {\n--custom-prop: value;\n}", + fixed: "a {\n\n--custom-prop: value;\n}", message: messages.expected, line: 2, column: 1, }, { code: "a {\r\n --custom-prop: value;\r\n}", + fixed: "a {\r\n\r\n --custom-prop: value;\r\n}", message: messages.expected, line: 2, column: 2, }, { code: "a{\n\n --custom-prop: value; \n --custom-prop2: value;}", + fixed: "a{\n\n --custom-prop: value; \n\n --custom-prop2: value;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\r\n\r\n --custom-prop: value;\r\n --custom-prop2: value;}", + fixed: "a{\r\n\r\n --custom-prop: value;\r\n\r\n --custom-prop2: value;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\n top: 10px;\n --custom-prop: value;}", + fixed: "a{\n top: 10px;\n\n --custom-prop: value;}", message: messages.expected, line: 3, column: 2, }, { - code: "a{\n @extends .class;\r\n --custom-prop: value;}", + code: "a{\r\n @extends .class;\r\n --custom-prop: value;}", + fixed: "a{\r\n @extends .class;\r\n\r\n --custom-prop: value;}", message: messages.expected, line: 3, column: 2, }, { code: "a{\n $var: value;\n --custom-prop: value;}", + fixed: "a{\n $var: value;\n\n --custom-prop: value;}", message: messages.expected, line: 3, column: 2, @@ -69,6 +77,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n/* comment */ --custom-prop: value;\n}", @@ -80,6 +89,7 @@ testRule(rule, { reject: [{ code: "a {\n --custom-prop: value;\n}", + fixed: "a {\n\n --custom-prop: value;\n}", message: messages.expected, line: 2, column: 2, @@ -89,6 +99,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["inside-single-line-block"] } ], + fix: true, accept: [ { code: "a { --custom-prop: value; }", @@ -98,6 +109,7 @@ testRule(rule, { reject: [{ code: "a {\n --custom-prop: value;\n}", + fixed: "a {\n\n --custom-prop: value;\n}", message: messages.expected, line: 2, column: 2, @@ -107,6 +119,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["first-nested"] } ], + fix: true, accept: [ { code: "a { --custom-prop: value;\n}", @@ -118,11 +131,13 @@ testRule(rule, { reject: [ { code: "a {\n\n --custom-prop: value;\n}", + fixed: "a {\n --custom-prop: value;\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a {\r\n\r\n --custom-prop: value;\r\n}", + fixed: "a {\r\n --custom-prop: value;\r\n}", message: messages.rejected, line: 3, column: 2, @@ -132,6 +147,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n\n --custom-prop: value;\n}", @@ -143,11 +159,13 @@ testRule(rule, { reject: [ { code: "a {\n\n --custom-prop: value;\n /* I am a comment */ \n\n --custom-prop2: value;}", + fixed: "a {\n\n --custom-prop: value;\n /* I am a comment */ \n --custom-prop2: value;}", message: messages.rejected, line: 6, column: 2, }, { - code: "a {\n /* I am a comment */ \r\n\r\n --custom-prop2: value;}", + code: "a {\r\n /* I am a comment */ \r\n\r\n --custom-prop2: value;}", + fixed: "a {\r\n /* I am a comment */ \r\n --custom-prop2: value;}", message: messages.rejected, line: 4, column: 2, @@ -157,6 +175,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["after-custom-property"] } ], + fix: true, accept: [ { code: "a {\n\n --custom-prop: value;\n}", @@ -168,11 +187,13 @@ testRule(rule, { reject: [ { code: "a {\n\n --custom-prop:value;\n\n --custom-prop2: value;}", + fixed: "a {\n\n --custom-prop:value;\n --custom-prop2: value;}", message: messages.rejected, line: 5, column: 2, }, { - code: "a {\n\n --custom-prop: value;\r\n\r\n --custom-prop2: value;}", + code: "a {\r\n\r\n --custom-prop: value;\r\n\r\n --custom-prop2: value;}", + fixed: "a {\r\n\r\n --custom-prop: value;\r\n --custom-prop2: value;}", message: messages.rejected, line: 5, column: 2, @@ -183,6 +204,7 @@ testRule(rule, { ruleName, config: [ "always", { except: [ "first-nested", "after-comment", "after-custom-property" ] } ], + fix: true, accept: [{ code: "a {\n --custom-prop: value; \n --custom-prop2: value; \n /* comment */ \n --custom-prop3: value;\n\n @extends 'x';\n\n --custom-prop4: value; \n & b {\n prop: value;\n } \n\n --custom-prop5: value; \n }" }], @@ -191,6 +213,7 @@ testRule(rule, { testRule(rule, { ruleName, config: ["never"], + fix: true, accept: [ { code: "a {\n --custom-prop: value;\n}", @@ -208,26 +231,31 @@ testRule(rule, { reject: [ { code: "a {\n\n --custom-prop: value;\n}", + fixed: "a {\n --custom-prop: value;\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a {\r\n\r\n --custom-prop: value;\r\n}", + fixed: "a {\r\n --custom-prop: value;\r\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a{\n top: 10px;\n\n --custom-prop: value;}", + fixed: "a{\n top: 10px;\n --custom-prop: value;}", message: messages.rejected, line: 4, column: 2, }, { - code: "a{\n @extends .class;\n\r\n --custom-prop: value;}", + code: "a{\r\n @extends .class;\r\n\r\n --custom-prop: value;}", + fixed: "a{\r\n @extends .class;\r\n --custom-prop: value;}", message: messages.rejected, line: 4, column: 2, }, { code: "a{\n $var: value;\n\n --custom-prop: value;}", + fixed: "a{\n $var: value;\n --custom-prop: value;}", message: messages.rejected, line: 4, column: 2, @@ -237,6 +265,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["first-nested"] } ], + fix: true, accept: [ { code: "a {\n\n --custom-prop: value;\n}", @@ -248,11 +277,13 @@ testRule(rule, { reject: [ { code: "a {\n --custom-prop: value;\n}", + fixed: "a {\n\n --custom-prop: value;\n}", message: messages.expected, line: 2, column: 2, }, { code: "a {\r\n --custom-prop: value;\r\n}", + fixed: "a {\r\n\r\n --custom-prop: value;\r\n}", message: messages.expected, line: 2, column: 2, @@ -262,6 +293,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n --custom-prop: value;\n}", @@ -273,11 +305,13 @@ testRule(rule, { reject: [ { code: "a {\n --custom-prop: value;\n /* I am a comment */ \n --custom-prop2: value;}", + fixed: "a {\n --custom-prop: value;\n /* I am a comment */ \n\n --custom-prop2: value;}", message: messages.expected, line: 4, column: 2, }, { - code: "a {\n /* I am a comment */ \r\n --custom-prop2: value;}", + code: "a {\r\n /* I am a comment */ \r\n --custom-prop2: value;}", + fixed: "a {\r\n /* I am a comment */ \r\n\r\n --custom-prop2: value;}", message: messages.expected, line: 3, column: 2, @@ -287,6 +321,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["after-custom-property"] } ], + fix: true, accept: [ { code: "a {\n --custom-prop: value;\n}", @@ -298,11 +333,13 @@ testRule(rule, { reject: [ { code: "a {\n --custom-prop:value;\n--custom-prop2: value;}", + fixed: "a {\n --custom-prop:value;\n\n--custom-prop2: value;}", message: messages.expected, line: 3, column: 1, }, { code: "a {\n --custom-prop: value;\n --custom-prop2: value;}", + fixed: "a {\n --custom-prop: value;\n\n --custom-prop2: value;}", message: messages.expected, line: 3, column: 2, @@ -313,6 +350,7 @@ testRule(rule, { ruleName, config: [ "never", { except: [ "first-nested", "after-comment", "after-custom-property" ] } ], + fix: true, accept: [{ code: "a {\n\n --custom-prop: value; \n\n --custom-prop2: value; \n /* comment */ \n\n --custom-prop3: value;\n\n @extends 'x';\n --custom-prop4: value; \n & b {\n prop: value;\n } \n --custom-prop5: value; \n }" }], diff --git a/lib/rules/custom-property-empty-line-before/index.js b/lib/rules/custom-property-empty-line-before/index.js index 1be5229a34..d5df161dc4 100644 --- a/lib/rules/custom-property-empty-line-before/index.js +++ b/lib/rules/custom-property-empty-line-before/index.js @@ -1,11 +1,13 @@ "use strict" +const addEmptyLineBefore = require("../../utils/addEmptyLineBefore") const blockString = require("../../utils/blockString") const hasEmptyLine = require("../../utils/hasEmptyLine") const isCustomProperty = require("../../utils/isCustomProperty") const isSingleLineString = require("../../utils/isSingleLineString") const isStandardSyntaxDeclaration = require("../../utils/isStandardSyntaxDeclaration") const optionsMatches = require("../../utils/optionsMatches") +const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") @@ -17,7 +19,7 @@ const messages = ruleMessages(ruleName, { rejected: "Unexpected empty line before custom property", }) -const rule = function (expectation, options) { +const rule = function (expectation, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, @@ -108,6 +110,17 @@ const rule = function (expectation, options) { return } + // Fix + if (context.fix) { + if (expectEmptyLineBefore) { + addEmptyLineBefore(decl, context.newline) + } else { + removeEmptyLinesBefore(decl, context.newline) + } + + return + } + const message = expectEmptyLineBefore ? messages.expected : messages.rejected report({ message, diff --git a/lib/rules/declaration-empty-line-before/README.md b/lib/rules/declaration-empty-line-before/README.md index 7aecb5570d..94794ed488 100644 --- a/lib/rules/declaration-empty-line-before/README.md +++ b/lib/rules/declaration-empty-line-before/README.md @@ -14,6 +14,8 @@ a { This rule only applies to standard property declarations. Use the [`custom-property-empty-line-before`](../custom-property-empty-line-before/README.md) rule for custom property declarations. +The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix all of the problems reported by this rule. + ## Options `string`: `"always"|"never"` diff --git a/lib/rules/declaration-empty-line-before/__tests__/index.js b/lib/rules/declaration-empty-line-before/__tests__/index.js index d022206606..6d188784fb 100644 --- a/lib/rules/declaration-empty-line-before/__tests__/index.js +++ b/lib/rules/declaration-empty-line-before/__tests__/index.js @@ -9,6 +9,7 @@ const rule = rules[ruleName] testRule(rule, { ruleName, config: ["always"], + fix: true, accept: [ { code: "a {\n\n top: 15px;\n}", @@ -32,46 +33,49 @@ testRule(rule, { reject: [ { code: "a { top: 15px; }", + fixed: "a {\n\n top: 15px; }", message: messages.expected, line: 1, column: 5, }, { code: "a {\ntop: 15px;\n}", - message: messages.expected, - line: 2, - column: 1, - }, { - code: "a {\ntop: 15px;\n}", + fixed: "a {\n\ntop: 15px;\n}", message: messages.expected, line: 2, column: 1, }, { code: "a {\r\n top: 15px;\r\n}", + fixed: "a {\r\n\r\n top: 15px;\r\n}", message: messages.expected, line: 2, column: 2, }, { code: "a{\n\n top: 15px; \n bottom: 5px;}", + fixed: "a{\n\n top: 15px; \n\n bottom: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\r\n\r\n top: 15px;\r\n bottom: 5px;}", + fixed: "a{\r\n\r\n top: 15px;\r\n\r\n bottom: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\n --custom-prop: value;\n top: 15px;}", + fixed: "a{\n --custom-prop: value;\n\n top: 15px;}", message: messages.expected, line: 3, column: 2, }, { - code: "a{\n @extends .class;\r\n top: 15px;}", + code: "a{\r\n @extends .class;\r\n top: 15px;}", + fixed: "a{\r\n @extends .class;\r\n\r\n top: 15px;}", message: messages.expected, line: 3, column: 2, }, { code: "a{\n $var: 15px;\n top: 15px;}", + fixed: "a{\n $var: 15px;\n\n top: 15px;}", message: messages.expected, line: 3, column: 2, @@ -82,6 +86,7 @@ testRule(rule, { ruleName, config: ["always"], syntax: "scss", + fix: true, accept: [ { code: "a {\n\n #{var}: 15px;\n}", @@ -93,21 +98,25 @@ testRule(rule, { reject: [ { code: "a { #{var}: 15px; }", + fixed: "a {\n\n #{var}: 15px; }", message: messages.expected, line: 1, column: 5, }, { code: "a{\n\n top: 15px; \n #{var}: 5px;}", + fixed: "a{\n\n top: 15px; \n\n #{var}: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\r\n\r\n #{var}: 15px;\r\n prop#{var2}: 5px;}", + fixed: "a{\r\n\r\n #{var}: 15px;\r\n\r\n prop#{var2}: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\n $var: 15px;\n #{var}: 15px;}", + fixed: "a{\n $var: 15px;\n\n #{var}: 15px;}", message: messages.expected, line: 3, column: 2, @@ -118,6 +127,7 @@ testRule(rule, { ruleName, config: ["always"], syntax: "less", + fix: true, accept: [ { code: "a {\n\n @{var}: 15px;\n}", @@ -129,21 +139,25 @@ testRule(rule, { reject: [ { code: "a { @{var}: 15px; }", + fixed: "a {\n\n @{var}: 15px; }", message: messages.expected, line: 1, column: 5, }, { code: "a{\n\n top: 15px; \n @{var}: 5px;}", + fixed: "a{\n\n top: 15px; \n\n @{var}: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\r\n\r\n @{var}: 15px;\r\n prop@{var2}: 5px;}", + fixed: "a{\r\n\r\n @{var}: 15px;\r\n\r\n prop@{var2}: 5px;}", message: messages.expected, line: 4, column: 2, }, { code: "a{\n $var: 15px;\n @{var}: 15px;}", + fixed: "a{\n $var: 15px;\n\n @{var}: 15px;}", message: messages.expected, line: 3, column: 2, @@ -153,6 +167,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["inside-single-line-block"] } ], + fix: true, accept: [ { code: "a { top: 15px; }", @@ -162,6 +177,7 @@ testRule(rule, { reject: [{ code: "a {\n top: 15px;\n}", + fixed: "a {\n\n top: 15px;\n}", message: messages.expected, line: 2, column: 2, @@ -171,6 +187,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n/* comment */ top: 15px;\n}", @@ -182,6 +199,7 @@ testRule(rule, { reject: [{ code: "a {\n top: 15px;\n}", + fixed: "a {\n\n top: 15px;\n}", message: messages.expected, line: 2, column: 2, @@ -191,6 +209,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { ignore: ["after-declaration"] } ], + fix: true, accept: [ { code: "a {\n\n top: 15px; bottom: 5px;\n}", @@ -208,31 +227,37 @@ testRule(rule, { reject: [ { code: "a{\n @extends .class;\n top: 15px;\n}", + fixed: "a{\n @extends .class;\n\n top: 15px;\n}", message: messages.expected, line: 3, column: 2, }, { code: "a{\r\n @extends .class;\r\n top: 15px;\r\n}", + fixed: "a{\r\n @extends .class;\r\n\r\n top: 15px;\r\n}", message: messages.expected, line: 3, column: 2, }, { code: "a{\n @include mixin;\n top: 15px;\n}", + fixed: "a{\n @include mixin;\n\n top: 15px;\n}", message: messages.expected, line: 3, column: 2, }, { code: "a{\r\n @include mixin;\r\n top: 15px;\r\n}", + fixed: "a{\r\n @include mixin;\r\n\r\n top: 15px;\r\n}", message: messages.expected, line: 3, column: 2, }, { code: "a {\n top: 15px;\n}", + fixed: "a {\n\n top: 15px;\n}", message: messages.expected, line: 2, column: 2, }, { code: "a {\r\n top: 15px;\r\n}", + fixed: "a {\r\n\r\n top: 15px;\r\n}", message: messages.expected, line: 2, column: 2, @@ -242,6 +267,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["first-nested"] } ], + fix: true, accept: [ { code: "a { top: 15px;\n}", @@ -253,11 +279,13 @@ testRule(rule, { reject: [ { code: "a {\n\n top: 15px;\n}", + fixed: "a {\n top: 15px;\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a {\r\n\r\n top: 15px;\r\n}", + fixed: "a {\r\n top: 15px;\r\n}", message: messages.rejected, line: 3, column: 2, @@ -267,6 +295,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n\n top: 15px;\n}", @@ -278,11 +307,13 @@ testRule(rule, { reject: [ { code: "a {\n\n top: 15px;\n /* I am a comment */ \n\n bottom: 5px;}", + fixed: "a {\n\n top: 15px;\n /* I am a comment */ \n bottom: 5px;}", message: messages.rejected, line: 6, column: 2, }, { - code: "a {\n /* I am a comment */ \r\n\r\n bottom: 5px;}", + code: "a {\r\n /* I am a comment */ \r\n\r\n bottom: 5px;}", + fixed: "a {\r\n /* I am a comment */ \r\n bottom: 5px;}", message: messages.rejected, line: 4, column: 2, @@ -292,6 +323,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["after-declaration"] } ], + fix: true, accept: [ { code: "a {\n\n top: 15px;\n}", @@ -303,11 +335,13 @@ testRule(rule, { reject: [ { code: "a {\n\n top:15px;\n\n bottom: 5px;}", + fixed: "a {\n\n top:15px;\n bottom: 5px;}", message: messages.rejected, line: 5, column: 2, }, { - code: "a {\n\n top: 15px;\r\n\r\n bottom: 5px;}", + code: "a {\r\n\r\n top: 15px;\r\n\r\n bottom: 5px;}", + fixed: "a {\r\n\r\n top: 15px;\r\n bottom: 5px;}", message: messages.rejected, line: 5, column: 2, @@ -318,6 +352,7 @@ testRule(rule, { ruleName, config: [ "always", { except: ["after-declaration"] } ], syntax: "scss", + fix: true, accept: [ { code: "a {\n\n #{$var}: 15px;\n}", @@ -327,11 +362,13 @@ testRule(rule, { reject: [ { code: "a {\n\n top:15px;\n\n #{$var}: 5px; }", + fixed: "a {\n\n top:15px;\n #{$var}: 5px; }", message: messages.rejected, line: 5, column: 2, }, { - code: "a {\n\n prop#{$var}erty: 15px;\r\n\r\n #{$var2}: 5px; }", + code: "a {\r\n\r\n prop#{$var}erty: 15px;\r\n\r\n #{$var2}: 5px; }", + fixed: "a {\r\n\r\n prop#{$var}erty: 15px;\r\n #{$var2}: 5px; }", message: messages.rejected, line: 5, column: 2, @@ -342,6 +379,7 @@ testRule(rule, { ruleName, config: [ "always", { except: ["after-declaration"] } ], syntax: "less", + fix: true, accept: [ { code: "a {\n\n @{var}: 15px;\n}", @@ -351,11 +389,13 @@ testRule(rule, { reject: [ { code: "a {\n\n top:15px;\n\n @{var}: 5px; }", + fixed: "a {\n\n top:15px;\n @{var}: 5px; }", message: messages.rejected, line: 5, column: 2, }, { - code: "a {\n\n prop@{var}erty: 15px;\r\n\r\n @{var2}: 5px; }", + code: "a {\r\n\r\n prop@{var}erty: 15px;\r\n\r\n @{var2}: 5px; }", + fixed: "a {\r\n\r\n prop@{var}erty: 15px;\r\n @{var2}: 5px; }", message: messages.rejected, line: 5, column: 2, @@ -366,6 +406,7 @@ testRule(rule, { ruleName, config: [ "always", { except: [ "first-nested", "after-comment", "after-declaration" ] } ], + fix: true, accept: [{ code: "a {\n top: 15px; \n bottom: 5px; \n /* comment */ \n prop: 15px;\n\n @extends 'x';\n\n prop: 15px; \n & b {\n prop: 15px;\n } \n\n prop: 15px; \n }" }], }) @@ -373,6 +414,7 @@ testRule(rule, { testRule(rule, { ruleName, config: ["never"], + fix: true, accept: [ { code: "a { top: 15px;\n}", @@ -396,31 +438,37 @@ testRule(rule, { reject: [ { code: "a {\n\n top: 15px;\n}", + fixed: "a {\n top: 15px;\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a {\r\n\r\n top: 15px;\r\n}", + fixed: "a {\r\n top: 15px;\r\n}", message: messages.rejected, line: 3, column: 2, }, { code: "a{\n bottom: 5px;\n\n top: 15px;}", + fixed: "a{\n bottom: 5px;\n top: 15px;}", message: messages.rejected, line: 4, column: 2, }, { code: "a{\n --custom-prop: value;\n\n top: 15px;}", + fixed: "a{\n --custom-prop: value;\n top: 15px;}", message: messages.rejected, line: 4, column: 2, }, { - code: "a{\n @extends .class;\n\r\n top: 15px;}", + code: "a{\r\n @extends .class;\r\n\r\n top: 15px;}", + fixed: "a{\r\n @extends .class;\r\n top: 15px;}", message: messages.rejected, line: 4, column: 2, }, { code: "a{\n $var: 15px;\n\n top: 15px;}", + fixed: "a{\n $var: 15px;\n top: 15px;}", message: messages.rejected, line: 4, column: 2, @@ -430,6 +478,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["first-nested"] } ], + fix: true, accept: [ { code: "a {\n\n top: 15px;\n}", @@ -441,16 +490,19 @@ testRule(rule, { reject: [ { code: "a {\n\n top: 15px;\n\nbottom:5px; }", + fixed: "a {\n\n top: 15px;\nbottom:5px; }", message: messages.rejected, line: 5, column: 1, }, { code: "a {\n top: 15px;\n}", + fixed: "a {\n\n top: 15px;\n}", message: messages.expected, line: 2, column: 2, }, { code: "a {\r\n top: 15px;\r\n}", + fixed: "a {\r\n\r\n top: 15px;\r\n}", message: messages.expected, line: 2, column: 2, @@ -460,6 +512,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["after-comment"] } ], + fix: true, accept: [ { code: "a {\n top: 15px;\n}", @@ -471,16 +524,19 @@ testRule(rule, { reject: [ { code: "a {\n/* I am a comment */ \n\n bottom: 5px;\n\ntop: 15px;}", + fixed: "a {\n/* I am a comment */ \n\n bottom: 5px;\ntop: 15px;}", message: messages.rejected, line: 6, column: 1, }, { code: "a {\n top: 15px;\n /* I am a comment */ \n bottom: 5px;}", + fixed: "a {\n top: 15px;\n /* I am a comment */ \n\n bottom: 5px;}", message: messages.expected, line: 4, column: 2, }, { - code: "a {\n /* I am a comment */ \r\n bottom: 5px;}", + code: "a {\r\n /* I am a comment */ \r\n bottom: 5px;}", + fixed: "a {\r\n /* I am a comment */ \r\n\r\n bottom: 5px;}", message: messages.expected, line: 3, column: 2, @@ -490,6 +546,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "never", { except: ["after-declaration"] } ], + fix: true, accept: [ { code: "a {\n top: 15px;\n}", @@ -501,16 +558,19 @@ testRule(rule, { reject: [ { code: "a {\n\n top:15px;\n\nbottom: 5px;}", + fixed: "a {\n top:15px;\n\nbottom: 5px;}", message: messages.rejected, line: 3, column: 2, }, { code: "a {\n top:15px;\nbottom: 5px;}", + fixed: "a {\n top:15px;\n\nbottom: 5px;}", message: messages.expected, line: 3, column: 1, }, { code: "a {\n top: 15px;\n bottom: 5px;}", + fixed: "a {\n top: 15px;\n\n bottom: 5px;}", message: messages.expected, line: 3, column: 2, @@ -521,6 +581,7 @@ testRule(rule, { ruleName, config: [ "never", { except: [ "first-nested", "after-comment", "after-declaration" ] } ], + fix: true, accept: [{ code: "a {\n\n top: 15px; \n\n bottom: 5px; \n /* comment */ \n\n prop: 15px;\n\n @extends 'x';\n prop: 15px; \n & b {\n\n prop: 15px;\n } \n prop: 15px; \n }" }], diff --git a/lib/rules/declaration-empty-line-before/index.js b/lib/rules/declaration-empty-line-before/index.js index 0d3d30e3fd..d1ceee07ff 100644 --- a/lib/rules/declaration-empty-line-before/index.js +++ b/lib/rules/declaration-empty-line-before/index.js @@ -1,11 +1,13 @@ "use strict" +const addEmptyLineBefore = require("../../utils/addEmptyLineBefore") const blockString = require("../../utils/blockString") const hasEmptyLine = require("../../utils/hasEmptyLine") const isCustomProperty = require("../../utils/isCustomProperty") const isSingleLineString = require("../../utils/isSingleLineString") const isStandardSyntaxDeclaration = require("../../utils/isStandardSyntaxDeclaration") const optionsMatches = require("../../utils/optionsMatches") +const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") @@ -17,7 +19,7 @@ const messages = ruleMessages(ruleName, { rejected: "Unexpected empty line before declaration", }) -const rule = function (expectation, options) { +const rule = function (expectation, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, @@ -122,6 +124,17 @@ const rule = function (expectation, options) { return } + // Fix + if (context.fix) { + if (expectEmptyLineBefore) { + addEmptyLineBefore(decl, context.newline) + } else { + removeEmptyLinesBefore(decl, context.newline) + } + + return + } + const message = expectEmptyLineBefore ? messages.expected : messages.rejected diff --git a/lib/rules/rule-empty-line-before/README.md b/lib/rules/rule-empty-line-before/README.md index 617a002508..bdd02891fb 100644 --- a/lib/rules/rule-empty-line-before/README.md +++ b/lib/rules/rule-empty-line-before/README.md @@ -12,6 +12,8 @@ b {} /* ↑ */ If the rule is the very first node in a stylesheet then it is ignored. +The `--fix` option on the [command line](../../../docs/user-guide/cli.md#autofixing-errors) can automatically fix all of the problems reported by this rule. + ## Options `string`: `"always"|"never"|"always-multi-line"|"never-multi-line"` diff --git a/lib/rules/rule-empty-line-before/__tests__/index.js b/lib/rules/rule-empty-line-before/__tests__/index.js index d151c32732..fa7ced2452 100644 --- a/lib/rules/rule-empty-line-before/__tests__/index.js +++ b/lib/rules/rule-empty-line-before/__tests__/index.js @@ -9,6 +9,7 @@ const rule = rules[ruleName] testRule(rule, { ruleName, config: ["always"], + fix: true, accept: [ { code: "a {}", @@ -35,19 +36,24 @@ testRule(rule, { reject: [ { code: "b {} a {}", + fixed: "b {}\n\n a {}", message: messages.expected, }, { code: "b {}\na {}", + fixed: "b {}\n\na {}", message: messages.expected, }, { code: "b {}\n\n/* comment here*/\na {}", + fixed: "b {}\n\n/* comment here*/\n\na {}", message: messages.expected, }, { code: "b {}\r\n\r\n/* comment here*/\r\na {}", + fixed: "b {}\r\n\r\n/* comment here*/\r\n\r\na {}", description: "CRLF", message: messages.expected, }, { code: "@media { b {}\n\n/* comment here*/\na {} }", + fixed: "@media {\n\n b {}\n\n/* comment here*/\n\na {} }", description: "nested", message: messages.expected, } ], @@ -57,6 +63,7 @@ testRule(rule, { ruleName, config: [ "always", { ignore: ["after-comment"] } ], skipBasicChecks: true, + fix: true, accept: [ { code: "/* foo */\na {}", @@ -72,9 +79,11 @@ testRule(rule, { reject: [ { code: "b {} a {}", + fixed: "b {}\n\n a {}", message: messages.expected, }, { code: "@media { b {} a {} }", + fixed: "@media {\n\n b {}\n\n a {} }", message: messages.expected, } ], }) @@ -83,6 +92,7 @@ testRule(rule, { ruleName, config: [ "always", { ignore: ["inside-block"] } ], skipBasicChecks: true, + fix: true, accept: [ { code: "b {}\n\na {}", @@ -92,6 +102,7 @@ testRule(rule, { reject: [{ code: "b {} a {}", + fixed: "b {}\n\n a {}", message: messages.expected, }], }) @@ -99,6 +110,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["after-rule"] } ], + fix: true, accept: [ { code: "a {} \nb {}", @@ -115,15 +127,19 @@ testRule(rule, { reject: [ { code: "a {}\n\nb {}", + fixed: "a {}\nb {}", message: messages.rejected, }, { code: "$var: pink;\nb {}", + fixed: "$var: pink;\n\nb {}", message: messages.expected, }, { code: "@media {}\na{}", + fixed: "@media {}\n\na{}", message: messages.expected, }, { code: "@media {\n\na{}\n\nb{}}", + fixed: "@media {\n\na{}\nb{}}", message: messages.rejected, } ], }) @@ -132,6 +148,7 @@ testRule(rule, { ruleName, config: [ "always", { except: ["after-single-line-comment"] } ], skipBasicChecks: true, + fix: true, accept: [ { code: "/**\n * comment\n*/\n\na {}", @@ -145,12 +162,15 @@ testRule(rule, { reject: [ { code: "/**\n * comment\n*/\na {}", + fixed: "/**\n * comment\n*/\n\na {}", message: messages.expected, }, { code: "/* comment */\n\na {}", + fixed: "/* comment */\na {}", message: messages.rejected, }, { code: "@media { /* comment */\n\na {} }", + fixed: "@media { /* comment */\na {} }", message: messages.rejected, } ], }) @@ -158,6 +178,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["first-nested"] } ], + fix: true, accept: [ { code: "@media {\n a {}\n\n}", @@ -182,26 +203,33 @@ testRule(rule, { reject: [ { code: "b {} a {}", + fixed: "b {}\n\n a {}", message: messages.expected, }, { code: "@media {\n\n a {}\n}", + fixed: "@media {\n a {}\n}", message: messages.rejected, }, { code: "@media {\n\n a {}\n\n b{}\n}", + fixed: "@media {\n a {}\n\n b{}\n}", message: messages.rejected, }, { code: "@media {\r\n\r\n a {}\r\n\r\n b{}\r\n}", + fixed: "@media {\r\n a {}\r\n\r\n b{}\r\n}", description: "CRLF", message: messages.rejected, }, { code: "@media {\n b {} a {} }", + fixed: "@media {\n b {}\n\n a {} }", message: messages.expected, }, { code: "@media {\r\n b {} a {} }", + fixed: "@media {\r\n b {}\r\n\r\n a {} }", description: "CRLF", message: messages.expected, }, { code: "@media {\n b {}\n a {}\n\n}", + fixed: "@media {\n b {}\n\n a {}\n\n}", message: messages.expected, } ], }) @@ -209,6 +237,7 @@ testRule(rule, { testRule(rule, { ruleName, config: [ "always", { except: ["inside-block-and-after-rule"] } ], + fix: true, accept: [ { code: "a {\n color: pink; \n\n b {color: red; } \n c {color: blue; }\n}", @@ -229,25 +258,32 @@ testRule(rule, { reject: [ { code: "a {} b {}", + fixed: "a {}\n\n b {}", message: messages.expected, }, { code: "a {}\nb {}", + fixed: "a {}\n\nb {}", message: messages.expected, }, { code: "a {\n color: pink; b {color: red; }\n c {color: blue; }\n}", + fixed: "a {\n color: pink;\n\n b {color: red; }\n c {color: blue; }\n}", message: messages.expected, }, { code: "a {\n color: pink;\n b {color: red; }\n c {color: blue; }\n}", + fixed: "a {\n color: pink;\n\n b {color: red; }\n c {color: blue; }\n}", message: messages.expected, }, { code: "a {\n color: pink;\n\n b {color: red; }\n\n c {color: blue; }\n}", + fixed: "a {\n color: pink;\n\n b {color: red; }\n c {color: blue; }\n}", message: messages.rejected, }, { code: "a {\n @media {\n\n b {}\n }\n c {}\n d {}\n}", + fixed: "a {\n @media {\n\n b {}\n }\n\n c {}\n d {}\n}", description: "media rule", message: messages.expected, }, { code: "a {\r\n color: pink;\r\n b {\r\ncolor: red; \r\n}\r\n c {\r\ncolor: blue; \r\n}\r\n}", + fixed: "a {\r\n color: pink;\r\n\r\n b {\r\ncolor: red; \r\n}\r\n c {\r\ncolor: blue; \r\n}\r\n}", description: "CRLF", message: messages.expected, } ], @@ -256,6 +292,7 @@ testRule(rule, { testRule(rule, { ruleName, config: ["never"], + fix: true, accept: [ { code: "\n\na {}", @@ -276,19 +313,24 @@ testRule(rule, { reject: [ { code: "b {}\n\na {}", + fixed: "b {}\na {}", message: messages.rejected, }, { code: "b {}\t\n\n\ta {}", + fixed: "b {}\t\n\ta {}", message: messages.rejected, }, { code: "b {}\t\r\n\r\n\ta {}", + fixed: "b {}\t\r\n\ta {}", description: "CRLF", message: messages.rejected, }, { code: "b {}\n\n/* comment here*/\n\na {}", + fixed: "b {}\n\n/* comment here*/\na {}", message: messages.rejected, }, { code: "@media {\n\na {} }", + fixed: "@media {\na {} }", message: messages.rejected, } ], }) @@ -297,6 +339,7 @@ testRule(rule, { ruleName, config: [ "never", { except: ["after-single-line-comment"] } ], skipBasicChecks: true, + fix: true, accept: [ { code: "/**\n * comment\n*/\na {}", @@ -306,9 +349,11 @@ testRule(rule, { reject: [ { code: "/**\n * comment\n*/\n\na {}", + fixed: "/**\n * comment\n*/\na {}", message: messages.rejected, }, { code: "/* comment */\na {}", + fixed: "/* comment */\n\na {}", message: messages.expected, } ], }) @@ -317,6 +362,7 @@ testRule(rule, { ruleName, config: [ "never", { ignore: ["after-comment"] } ], skipBasicChecks: true, + fix: true, accept: [ { code: "/* foo */\na {}", @@ -329,6 +375,7 @@ testRule(rule, { reject: [{ code: "b {}\n\na {}", + fixed: "b {}\na {}", message: messages.rejected, }], }) @@ -336,6 +383,7 @@ testRule(rule, { testRule(rule, { ruleName, config: ["always-multi-line"], + fix: true, accept: [ { code: "a {}", @@ -359,19 +407,24 @@ testRule(rule, { reject: [ { code: "b {} a\n{}", + fixed: "b {}\n\n a\n{}", message: messages.expected, }, { code: "b\n{}\na\n{}", + fixed: "b\n{}\n\na\n{}", message: messages.expected, }, { code: "b\r\n{}\r\na\r\n{}", + fixed: "b\r\n{}\r\n\r\na\r\n{}", description: "CRLF", message: messages.expected, }, { code: "b {}\n\n/* comment here*/\na\n{}", + fixed: "b {}\n\n/* comment here*/\n\na\n{}", message: messages.expected, }, { code: "@media { a\n{} }", + fixed: "@media {\n\n a\n{} }", message: messages.expected, } ], }) @@ -379,6 +432,7 @@ testRule(rule, { testRule(rule, { ruleName, config: ["never-multi-line"], + fix: true, accept: [ { code: "\n\na\n{}", @@ -397,26 +451,33 @@ testRule(rule, { reject: [ { code: "b {}\n\na\n{}", + fixed: "b {}\na\n{}", message: messages.rejected, }, { code: "b {}\t\n\n\ta\n{}", + fixed: "b {}\t\n\ta\n{}", message: messages.rejected, }, { code: "b {}\t\r\n\r\n\ta\r\n{}", + fixed: "b {}\t\r\n\ta\r\n{}", description: "CRLF", message: messages.rejected, }, { code: "b {}\n\n/* comment here*/\n\na\n{}", + fixed: "b {}\n\n/* comment here*/\na\n{}", message: messages.rejected, }, { code: "b {}\r\n\r\n/* comment here*/\r\n\r\na\r\n{}", + fixed: "b {}\r\n\r\n/* comment here*/\r\na\r\n{}", description: "CRLF", message: messages.rejected, }, { code: "@media\n{\n\na\n{} }", + fixed: "@media\n{\na\n{} }", message: messages.rejected, }, { code: "@media\r\n{\r\n\r\na\r\n{} }", + fixed: "@media\r\n{\r\na\r\n{} }", message: messages.rejected, } ], }) @@ -425,6 +486,7 @@ testRule(rule, { ruleName, syntax: "less", config: ["always"], + fix: true, accept: [ { code: "a {}\n.mixin-call() {}", diff --git a/lib/rules/rule-empty-line-before/index.js b/lib/rules/rule-empty-line-before/index.js index a51a6f4f66..9944be3007 100644 --- a/lib/rules/rule-empty-line-before/index.js +++ b/lib/rules/rule-empty-line-before/index.js @@ -1,9 +1,11 @@ "use strict" +const addEmptyLineBefore = require("../../utils/addEmptyLineBefore") const hasEmptyLine = require("../../utils/hasEmptyLine") const isSingleLineString = require("../../utils/isSingleLineString") const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule") const optionsMatches = require("../../utils/optionsMatches") +const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore") const report = require("../../utils/report") const ruleMessages = require("../../utils/ruleMessages") const validateOptions = require("../../utils/validateOptions") @@ -15,7 +17,7 @@ const messages = ruleMessages(ruleName, { rejected: "Unexpected empty line before rule", }) -const rule = function (expectation, options) { +const rule = function (expectation, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, @@ -127,6 +129,17 @@ const rule = function (expectation, options) { return } + // Fix + if (context.fix) { + if (expectEmptyLineBefore) { + addEmptyLineBefore(rule, context.newline) + } else { + removeEmptyLinesBefore(rule, context.newline) + } + + return + } + const message = expectEmptyLineBefore ? messages.expected : messages.rejected report({