Skip to content

Commit 1cf52a9

Browse files
crisbetojelbourn
authored andcommitted
build: add stylelint rule to ban concrete CSS rules inside theme files (#10003)
Adds a stylelint rule that will ensure that all of the CSS rules inside theme files are placed inside a mixin. This avoids style duplication whenever the file is imported. Relates to #9999.
1 parent 6ec0af1 commit 1cf52a9

File tree

4 files changed

+56
-17
lines changed

4 files changed

+56
-17
lines changed

src/lib/button/_button-base.scss

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ $mat-mini-fab-size: 40px !default;
3232
$mat-mini-fab-padding: 8px !default;
3333

3434
// Applies base styles to all button types.
35-
%mat-button-base {
35+
@mixin mat-button-base {
3636
box-sizing: border-box;
3737
position: relative;
3838

@@ -69,9 +69,8 @@ $mat-mini-fab-padding: 8px !default;
6969
}
7070

7171
// Applies styles to buttons with backgrounds: raised, fab, and mini-fab
72-
%mat-raised-button {
73-
@extend %mat-button-base;
74-
72+
@mixin mat-raised-button {
73+
@include mat-button-base;
7574
@include mat-overridable-elevation(2);
7675

7776
// Force hardware acceleration.
@@ -92,8 +91,7 @@ $mat-mini-fab-padding: 8px !default;
9291

9392
// Applies styles to fab and mini-fab button types only
9493
@mixin mat-fab($size, $padding) {
95-
@extend %mat-raised-button;
96-
94+
@include mat-raised-button;
9795
@include mat-overridable-elevation(6);
9896

9997
// Reset the min-width from the button base.

src/lib/button/button.scss

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
@import '../core/style/layout-common';
55
@import '../../cdk/a11y/a11y';
66

7-
.mat-button, .mat-icon-button {
8-
@extend %mat-button-base;
7+
.mat-button, .mat-icon-button, .mat-stroked-button, .mat-flat-button {
8+
@include mat-button-base;
9+
}
910

11+
.mat-button, .mat-icon-button {
1012
.mat-button-focus-overlay {
1113
transition: none;
1214
opacity: 0;
@@ -21,12 +23,10 @@
2123
}
2224

2325
.mat-raised-button {
24-
@extend %mat-raised-button;
26+
@include mat-raised-button;
2527
}
2628

2729
.mat-stroked-button {
28-
@extend %mat-button-base;
29-
3030
@include mat-overridable-elevation(0);
3131

3232
border: 1px solid currentColor;
@@ -35,8 +35,6 @@
3535
}
3636

3737
.mat-flat-button {
38-
@extend %mat-button-base;
39-
4038
@include mat-overridable-elevation(0);
4139
}
4240

stylelint-config.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"./tools/stylelint/no-prefixes/no-prefixes.js",
44
"./tools/stylelint/selector-nested-pattern-scoped/index.js",
55
"./tools/stylelint/selector-no-deep/index.js",
6-
"./tools/stylelint/no-nested-mixin/index.js"
6+
"./tools/stylelint/no-nested-mixin/index.js",
7+
"./tools/stylelint/no-concrete-rules/index.js"
78
],
89
"rules": {
910
"material/no-prefixes": [["last 2 versions", "not ie <= 10", "not ie_mob <= 10"]],
@@ -13,6 +14,9 @@
1314
"message": "The & operator is not allowed at the end of theme selectors.",
1415
"filePattern": "-theme\\.scss$"
1516
}],
17+
"material/no-concrete-rules": [true, {
18+
"filePattern": "^_.*\\.scss$"
19+
}],
1620

1721
"color-hex-case": "lower",
1822
"color-no-invalid-hex": true,
@@ -43,7 +47,7 @@
4347

4448
"property-case": "lower",
4549

46-
"declaration-block-no-duplicate-properties": [ true, {
50+
"declaration-block-no-duplicate-properties": [true, {
4751
"ignore": ["consecutive-duplicates-with-different-values"]
4852
}],
4953
"declaration-block-trailing-semicolon": "always",
@@ -53,8 +57,8 @@
5357
"declaration-block-semicolon-newline-before": "never-multi-line",
5458
"declaration-block-semicolon-newline-after": "always-multi-line",
5559
"declaration-property-value-blacklist": [
56-
{ "/.*/": ["initial"] },
57-
{ "message": "The `initial` value is not supported in IE."}
60+
{"/.*/": ["initial"]},
61+
{"message": "The `initial` value is not supported in IE."}
5862
],
5963

6064
"block-closing-brace-newline-after": "always",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const stylelint = require('stylelint');
2+
const path = require('path');
3+
const ruleName = 'material/no-concrete-rules';
4+
const messages = stylelint.utils.ruleMessages(ruleName, {
5+
expected: pattern => `CSS rules must be placed inside a mixin for files matching '${pattern}'.`
6+
});
7+
8+
/**
9+
* Stylelint plugin that will log a warning for all top-level CSS rules.
10+
* Can be used in theme files to ensure that everything is inside a mixin.
11+
*/
12+
const plugin = stylelint.createPlugin(ruleName, (isEnabled, options) => {
13+
return (root, result) => {
14+
if (!isEnabled) return;
15+
16+
const filePattern = new RegExp(options.filePattern);
17+
const fileName = path.basename(root.source.input.file);
18+
19+
if (!filePattern.test(fileName)) return;
20+
21+
// Go through all the nodes and report a warning for every CSS rule or mixin inclusion.
22+
// We use a regular `forEach`, instead of the PostCSS walker utils, because we only care
23+
// about the top-level nodes.
24+
root.nodes.forEach(node => {
25+
if (node.type === 'rule' || (node.type === 'atrule' && node.name === 'include')) {
26+
stylelint.utils.report({
27+
result,
28+
ruleName,
29+
node,
30+
message: messages.expected(filePattern)
31+
});
32+
}
33+
});
34+
};
35+
});
36+
37+
plugin.ruleName = ruleName;
38+
plugin.messages = messages;
39+
module.exports = plugin;

0 commit comments

Comments
 (0)