Skip to content

Commit d7dc443

Browse files
Add stylelint-polaris/coverage rule (#7551)
1 parent 7a6fb7c commit d7dc443

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

.changeset/weak-islands-wait.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/stylelint-polaris': minor
3+
---
4+
5+
Add `stylelint-polaris/coverage` rule
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const stylelint = require('stylelint');
2+
3+
const {isObject} = require('../../utils');
4+
5+
const ruleName = 'stylelint-polaris/coverage';
6+
7+
/**
8+
* @typedef {{
9+
* [category: string]: import('stylelint').ConfigRules
10+
* }} PrimaryOptions
11+
*/
12+
13+
module.exports = stylelint.createPlugin(
14+
ruleName,
15+
/** @param {PrimaryOptions} primaryOptions */
16+
(primaryOptions) => {
17+
const isPrimaryOptionsValid = validatePrimaryOptions(primaryOptions);
18+
19+
const rules = !isPrimaryOptionsValid
20+
? []
21+
: Object.entries(primaryOptions).flatMap(
22+
([categoryName, categoryConfigRules]) =>
23+
Object.entries(categoryConfigRules).map(
24+
([categoryRuleName, categoryRuleSettings]) => ({
25+
categoryRuleName,
26+
categoryRuleSettings,
27+
coverageRuleName: `${ruleName}/${categoryName}`,
28+
}),
29+
),
30+
);
31+
32+
return (root, result) => {
33+
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
34+
actual: isPrimaryOptionsValid,
35+
});
36+
37+
if (!validOptions) return;
38+
39+
for (const rule of rules) {
40+
const {categoryRuleName, categoryRuleSettings, coverageRuleName} = rule;
41+
42+
stylelint.utils.checkAgainstRule(
43+
{
44+
ruleName: categoryRuleName,
45+
ruleSettings: categoryRuleSettings,
46+
root,
47+
},
48+
(warning) => {
49+
stylelint.utils.report({
50+
result,
51+
node: warning.node,
52+
ruleName: coverageRuleName,
53+
message: warning.text.replace(categoryRuleName, coverageRuleName),
54+
});
55+
},
56+
);
57+
}
58+
};
59+
},
60+
);
61+
62+
function validatePrimaryOptions(primaryOptions) {
63+
if (!isObject(primaryOptions)) return false;
64+
65+
for (const categoryConfigRules of Object.values(primaryOptions)) {
66+
if (!isObject(categoryConfigRules)) return false;
67+
}
68+
69+
return true;
70+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const {ruleName} = require('.');
2+
3+
const config = {
4+
motion: {
5+
'at-rule-disallowed-list': [['keyframes'], {severity: 'warning'}],
6+
},
7+
};
8+
9+
testRule({
10+
ruleName,
11+
plugins: [__dirname],
12+
config,
13+
customSyntax: 'postcss-scss',
14+
accept: [
15+
{
16+
code: '@media (min-width: 320px) {}',
17+
description: 'Uses allowed at-rule',
18+
},
19+
],
20+
21+
reject: [
22+
{
23+
code: '@keyframes foo {}',
24+
description: 'Uses disallowed at-rule',
25+
message:
26+
'Unexpected at-rule "keyframes" (stylelint-polaris/coverage/motion)',
27+
},
28+
],
29+
});

stylelint-polaris/utils/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ function isNumber(value) {
181181
return typeof value === 'number' || value instanceof Number;
182182
}
183183

184+
/**
185+
* Checks if the value is an object and not an array or null.
186+
* https://github.com/jonschlinkert/isobject/blob/15d5d58ea9fbc632dffd52917ac6791cd92251ab/index.js#L9
187+
* @param {unknown} value
188+
*/
189+
function isObject(value) {
190+
return (
191+
value != null && typeof value === 'object' && Array.isArray(value) === false
192+
);
193+
}
194+
184195
/**
185196
* Checks if the value is a RegExp object.
186197
* @param {unknown} value
@@ -212,6 +223,7 @@ module.exports.hasScssInterpolation = hasScssInterpolation;
212223
module.exports.isBoolean = isBoolean;
213224
module.exports.isCustomProperty = isCustomProperty;
214225
module.exports.isNumber = isNumber;
226+
module.exports.isObject = isObject;
215227
module.exports.isRegExp = isRegExp;
216228
module.exports.isScssInterpolation = isScssInterpolation;
217229
module.exports.isString = isString;

0 commit comments

Comments
 (0)