Skip to content

Commit

Permalink
Merge pull request #371 from paulgv/add-no-duplicate-mixin-rule
Browse files Browse the repository at this point in the history
Add no-duplicate-mixins rule
  • Loading branch information
kristerkari committed Oct 16, 2019
2 parents 7ad5cd2 + 0873014 commit a568697
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ Please also see the [example configs](./docs/examples/) for special cases.

- [`no-dollar-variables`](./src/rules/no-dollar-variables/README.md): Disallow dollar variables within a stylesheet.
- [`no-duplicate-dollar-variables`](./src/rules/no-duplicate-dollar-variables/README.md): Disallow duplicate dollar variables within a stylesheet.
- [`no-duplicate-mixins`](./src/rules/no-duplicate-mixins/README.md): Disallow duplicate mixins within a stylesheet.

## Help out

Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import mapKeysQuotes from "./map-keys-quotes";
import mediaFeatureValueDollarVariable from "./media-feature-value-dollar-variable";
import noDollarVariables from "./no-dollar-variables";
import noDuplicateDollarVariables from "./no-duplicate-dollar-variables";
import noDuplicateMixins from "./no-duplicate-mixins";
import operatorNoNewlineAfter from "./operator-no-newline-after";
import operatorNoNewlineBefore from "./operator-no-newline-before";
import operatorNoUnspaced from "./operator-no-unspaced";
Expand Down Expand Up @@ -94,6 +95,7 @@ export default {
"media-feature-value-dollar-variable": mediaFeatureValueDollarVariable,
"no-dollar-variables": noDollarVariables,
"no-duplicate-dollar-variables": noDuplicateDollarVariables,
"no-duplicate-mixins": noDuplicateMixins,
"operator-no-newline-after": operatorNoNewlineAfter,
"operator-no-newline-before": operatorNoNewlineBefore,
"operator-no-unspaced": operatorNoUnspaced,
Expand Down
83 changes: 83 additions & 0 deletions src/rules/no-duplicate-mixins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# no-duplicate-mixins

Disallow duplicate mixins within a stylesheet.

```scss
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-default {
font-size: 18px;
}
/** ↑
* These are duplicates */
```

## Options

### `true`

The following patterns are considered violations:

```scss
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-default {
font-size: 18px;
}
```

```scss
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-sm {
font-size: 14px;
}
@mixin font-size-default {
font-size: 18px;
}
```

```scss
@mixin font-size {
font-size: 16px;
}
@mixin font-size($var) {
font-size: $var;
}
```

```scss
@mixin font-size($property, $value) {
#{$property}: $value;
}
@mixin font-size($var) {
font-size: $var;
}
```

```scss
@mixin font-size {
color: blue;
}

.b {
@mixin font-size {
color: red;
}
@include font-size;
}
```

The following patterns are _not_ considered violations:

```scss
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-lg {
font-size: 18px;
}
```
110 changes: 110 additions & 0 deletions src/rules/no-duplicate-mixins/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import rule, { ruleName, messages } from "..";

testRule(rule, {
ruleName,
config: [true],
syntax: "scss",

accept: [
{
code: `
@mixin font-size-default {
font-size: 16px;
}
`,
description: "A single mixin."
},
{
code: `
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-lg {
font-size: 18px;
}
`,
description: "Two mixins with different names."
}
],

reject: [
{
code: `
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-default {
font-size: 18px;
}
`,
column: 7,
line: 5,
message: messages.rejected("font-size-default"),
description: "Two mixins with the same names."
},
{
code: `
@mixin font-size-default {
font-size: 16px;
}
@mixin font-size-sm {
font-size: 14px;
}
@mixin font-size-default {
font-size: 18px;
}
`,
column: 7,
line: 8,
message: messages.rejected("font-size-default"),
description: "Three mixins including two with the same names."
},
{
code: `
@mixin font-size {
font-size: 16px;
}
@mixin font-size($var) {
font-size: $var;
}
`,
column: 7,
line: 5,
message: messages.rejected("font-size"),
description:
"Two mixins with the same names including one accepting arguments."
},
{
code: `
@mixin font-size($property, $value) {
#{$property}: $value;
}
@mixin font-size($var) {
font-size: $var;
}
`,
column: 7,
line: 5,
message: messages.rejected("font-size"),
description: "Two mixins with the same names accepting arguments."
},
{
code: `
@mixin font-size {
color: blue;
}
.b {
@mixin font-size {
color: red;
}
@include font-size;
}
`,
column: 9,
line: 7,
message: messages.rejected("font-size"),
description: "Two mixins with the same names accepting arguments."
}
]
});
43 changes: 43 additions & 0 deletions src/rules/no-duplicate-mixins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { utils } from "stylelint";
import { atRuleBaseName, namespace } from "../../utils";

export const ruleName = namespace("no-duplicate-mixins");

export const messages = utils.ruleMessages(ruleName, {
rejected: mixin => `Unexpected duplicate mixin ${mixin}`,
});

export default function(value) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, {
actual: value,
});

if (!validOptions) {
return;
}

const mixins = {};

root.walkAtRules(decl => {
const isMixin = decl.name === "mixin";

if (!isMixin) {
return;
}

const mixinName = atRuleBaseName(decl);

if (mixins[mixinName]) {
utils.report({
message: messages.rejected(mixinName),
node: decl,
result,
ruleName,
});
}

mixins[mixinName] = true;
});
};
}
9 changes: 9 additions & 0 deletions src/utils/atRuleBaseName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Get an at rule's base name
*
* @param {AtRule} atRule
* @return {string} The name
*/
export default function(atRule) {
return atRule.params.replace(/\([^)]*\)/, "").trim();
}
9 changes: 3 additions & 6 deletions src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { addEmptyLineBefore } from "./addEmptyLineBefore";
export { default as atRuleBaseName } from "./atRuleBaseName";
export { default as atRuleParamIndex } from "./atRuleParamIndex";
export { default as beforeBlockString } from "./beforeBlockString";
export { default as blockString } from "./blockString";
Expand All @@ -8,17 +9,13 @@ export { default as eachRoot } from "./eachRoot";
export { default as findCommentsInRaws } from "./findCommentsInRaws";
export { default as hasBlock } from "./hasBlock";
export { default as hasEmptyLine } from "./hasEmptyLine";
export {
default as hasInterpolatingAmpersand
} from "./hasInterpolatingAmpersand";
export { default as hasInterpolatingAmpersand } from "./hasInterpolatingAmpersand";
export { default as isInlineComment } from "./isInlineComment";
export { default as isNativeCssFunction } from "./isNativeCssFunction";
export { default as isSingleLineString } from "./isSingleLineString";
export { default as isStandardRule } from "./isStandardRule";
export { default as isStandardSelector } from "./isStandardSelector";
export {
default as isStandardSyntaxProperty
} from "./isStandardSyntaxProperty";
export { default as isStandardSyntaxProperty } from "./isStandardSyntaxProperty";
export { default as isWhitespace } from "./isWhitespace";
export { default as namespace } from "./namespace";
export { default as optionsHaveException } from "./optionsHaveException";
Expand Down

0 comments on commit a568697

Please sign in to comment.