Skip to content

Commit

Permalink
Add stylelint-polaris/coverage rule (#7551)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronccasanova authored Oct 31, 2022
1 parent 7a6fb7c commit d7dc443
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-islands-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/stylelint-polaris': minor
---

Add `stylelint-polaris/coverage` rule
70 changes: 70 additions & 0 deletions stylelint-polaris/plugins/coverage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const stylelint = require('stylelint');

const {isObject} = require('../../utils');

const ruleName = 'stylelint-polaris/coverage';

/**
* @typedef {{
* [category: string]: import('stylelint').ConfigRules
* }} PrimaryOptions
*/

module.exports = stylelint.createPlugin(
ruleName,
/** @param {PrimaryOptions} primaryOptions */
(primaryOptions) => {
const isPrimaryOptionsValid = validatePrimaryOptions(primaryOptions);

const rules = !isPrimaryOptionsValid
? []
: Object.entries(primaryOptions).flatMap(
([categoryName, categoryConfigRules]) =>
Object.entries(categoryConfigRules).map(
([categoryRuleName, categoryRuleSettings]) => ({
categoryRuleName,
categoryRuleSettings,
coverageRuleName: `${ruleName}/${categoryName}`,
}),
),
);

return (root, result) => {
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
actual: isPrimaryOptionsValid,
});

if (!validOptions) return;

for (const rule of rules) {
const {categoryRuleName, categoryRuleSettings, coverageRuleName} = rule;

stylelint.utils.checkAgainstRule(
{
ruleName: categoryRuleName,
ruleSettings: categoryRuleSettings,
root,
},
(warning) => {
stylelint.utils.report({
result,
node: warning.node,
ruleName: coverageRuleName,
message: warning.text.replace(categoryRuleName, coverageRuleName),
});
},
);
}
};
},
);

function validatePrimaryOptions(primaryOptions) {
if (!isObject(primaryOptions)) return false;

for (const categoryConfigRules of Object.values(primaryOptions)) {
if (!isObject(categoryConfigRules)) return false;
}

return true;
}
29 changes: 29 additions & 0 deletions stylelint-polaris/plugins/coverage/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const {ruleName} = require('.');

const config = {
motion: {
'at-rule-disallowed-list': [['keyframes'], {severity: 'warning'}],
},
};

testRule({
ruleName,
plugins: [__dirname],
config,
customSyntax: 'postcss-scss',
accept: [
{
code: '@media (min-width: 320px) {}',
description: 'Uses allowed at-rule',
},
],

reject: [
{
code: '@keyframes foo {}',
description: 'Uses disallowed at-rule',
message:
'Unexpected at-rule "keyframes" (stylelint-polaris/coverage/motion)',
},
],
});
12 changes: 12 additions & 0 deletions stylelint-polaris/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@ function isNumber(value) {
return typeof value === 'number' || value instanceof Number;
}

/**
* Checks if the value is an object and not an array or null.
* https://github.com/jonschlinkert/isobject/blob/15d5d58ea9fbc632dffd52917ac6791cd92251ab/index.js#L9
* @param {unknown} value
*/
function isObject(value) {
return (
value != null && typeof value === 'object' && Array.isArray(value) === false
);
}

/**
* Checks if the value is a RegExp object.
* @param {unknown} value
Expand Down Expand Up @@ -212,6 +223,7 @@ module.exports.hasScssInterpolation = hasScssInterpolation;
module.exports.isBoolean = isBoolean;
module.exports.isCustomProperty = isCustomProperty;
module.exports.isNumber = isNumber;
module.exports.isObject = isObject;
module.exports.isRegExp = isRegExp;
module.exports.isScssInterpolation = isScssInterpolation;
module.exports.isString = isString;
Expand Down

0 comments on commit d7dc443

Please sign in to comment.