diff --git a/src/rules/at-rule-conditional-no-parentheses/README.md b/src/rules/at-rule-conditional-no-parentheses/README.md new file mode 100644 index 00000000..cc981f39 --- /dev/null +++ b/src/rules/at-rule-conditional-no-parentheses/README.md @@ -0,0 +1,43 @@ +# at-rule-conditional-no-parentheses + +Disallow parentheses in conditional @ rules (if, elsif, while) + +```css + @if (true) {} +/** ↑ ↑ + * Get rid of parentheses like this. */ +``` + + + +## Options + +### `true` + +The following patterns are considered warnings: + +```scss +@if(true) +``` + +```scss +@else if(true) +``` + +```scss +@while(true) +``` + +The following patterns are *not* considered warnings: + +```scss +@if true +``` + +```scss +@else if true +``` + +```scss +@while true +``` diff --git a/src/rules/at-rule-conditional-no-parentheses/__tests__/index.js b/src/rules/at-rule-conditional-no-parentheses/__tests__/index.js new file mode 100644 index 00000000..2a5d7724 --- /dev/null +++ b/src/rules/at-rule-conditional-no-parentheses/__tests__/index.js @@ -0,0 +1,57 @@ +import rule, { ruleName, messages } from ".."; + +testRule(rule, { + ruleName, + config: [true], + syntax: "scss", + fix: true, + + accept: [ + { + code: "@if true {}", + description: "accepts @if with no parentheses" + }, + { + code: "@if (1 + 1) == 2 {}", + description: "accepts parentheses statement where () used for math" + }, + { + code: "@while true {}", + description: "accepts @while with no parentheses" + }, + { + code: `@if true {} + @else if true {}`, + description: "accepts @else if with no parentheses" + }, + { + code: `@if true {} + @else if true {} + @else {}`, + description: "accepts @else unconditionally" + } + ], + + reject: [ + { + code: "@if(true) {}", + fixed: "@if true {}", + message: messages.rejected, + description: "does not accept @if with parentheses" + }, + { + code: "@while(true) {}", + fixed: "@while true {}", + message: messages.rejected, + description: "does not accept @while with parentheses" + }, + { + code: `@if true {} + @else if(true) {}`, + fixed: `@if true {} + @else if true {}`, + message: messages.rejected, + description: "does not accept @else if with parentheses" + } + ] +}); diff --git a/src/rules/at-rule-conditional-no-parentheses/index.js b/src/rules/at-rule-conditional-no-parentheses/index.js new file mode 100644 index 00000000..e4aa3831 --- /dev/null +++ b/src/rules/at-rule-conditional-no-parentheses/index.js @@ -0,0 +1,70 @@ +import { utils } from "stylelint"; +import { namespace } from "../../utils"; +import _ from "lodash"; + +export const ruleName = namespace("at-rule-conditional-no-parentheses"); + +export const messages = utils.ruleMessages(ruleName, { + rejected: "Unexpected () used to surround statements for @-rules" +}); + +// postcss picks up else-if as else. +const conditional_rules = ["if", "while", "else"]; + +function report(atrule, result) { + utils.report({ + message: messages.rejected, + node: atrule, + result, + ruleName + }); +} + +function fix(atrule) { + const regex = /(if)? ?\((.*)\)/; + + // 2 regex groups: 'if ' and cond. + const groups = atrule.params.match(regex).slice(1); + + atrule.params = _.uniq(groups).join(" "); +} + +export default function(primary, _unused, context) { + return (root, result) => { + const validOptions = utils.validateOptions(result, ruleName, { + actual: primary + }); + + if (!validOptions) { + return; + } + + root.walkAtRules(atrule => { + // Check if this is a conditional rule. + if (!_.includes(conditional_rules, atrule.name)) { + return; + } + + // Else uses a different regex + // params are of format "`if (cond)` or `if cond` + // instead of `(cond)` or `cond`" + if (atrule.name === "else") { + if (atrule.params.match(/ ?if ?\(.*\) ?$/)) { + if (context.fix) { + fix(atrule); + } else { + report(atrule, result); + } + } + } else { + if (atrule.params.match(/ ?\(.*\) ?$/)) { + if (context.fix) { + fix(atrule); + } else { + report(atrule, result); + } + } + } + }); + }; +} diff --git a/src/rules/index.js b/src/rules/index.js index 71817c9f..58e75274 100644 --- a/src/rules/index.js +++ b/src/rules/index.js @@ -17,6 +17,7 @@ import atMixinNamedArguments from "./at-mixin-named-arguments"; import atMixinParenthesesSpaceBefore from "./at-mixin-parentheses-space-before"; import atMixinPattern from "./at-mixin-pattern"; import atEachKeyValue from "./at-each-key-value-single-line"; +import atRuleConditionalNoParen from "./at-rule-conditional-no-parentheses"; import atRuleNoUnknown from "./at-rule-no-unknown"; import commentNoLoud from "./comment-no-loud"; import declarationNestedProperties from "./declaration-nested-properties"; @@ -67,6 +68,7 @@ export default { "at-mixin-parentheses-space-before": atMixinParenthesesSpaceBefore, "at-mixin-pattern": atMixinPattern, "at-each-key-value-single-line": atEachKeyValue, + "at-rule-conditional-no-parentheses": atRuleConditionalNoParen, "at-rule-no-unknown": atRuleNoUnknown, "comment-no-loud": commentNoLoud, "declaration-nested-properties": declarationNestedProperties,