Skip to content
This repository was archived by the owner on Sep 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-cougars-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/stylelint-polaris': patch
---

Add constraints to `stylelint-polaris/coverage` disable comments
76 changes: 75 additions & 1 deletion stylelint-polaris/plugins/coverage/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const stylelint = require('stylelint');

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

const ruleName = 'stylelint-polaris/coverage';

Expand Down Expand Up @@ -55,10 +55,84 @@ module.exports = stylelint.createPlugin(
},
);
}

const disabledCoverageWarnings =
result.stylelint.disabledWarnings?.filter((disabledWarning) =>
disabledWarning.rule.startsWith(ruleName),
);

if (!disabledCoverageWarnings?.length) return;

const disabledCoverageLines = Array.from(
new Set(disabledCoverageWarnings.map(({line}) => line)),
);

// Ensure all stylelint-polaris/coverage disable comments
// have a description prefixed with "polaris:"
for (const disabledRange of result.stylelint.disabledRanges.all) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought only targeting the disabledRanges.all would be an issue if a specific rule name was used; however, because of how we are creating the rules dynamically, Stylelint won't let you target it since it isn't present as a static rule in the config 🤯 brilliant

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, leveraging stylelint-polaris/coverage worked out well! Because we categorize rules with custom names and check them manually, I was able to easily filter out disabled coverage rules from disabledRanges.all:
image

Notice the above result.stylelint.disabledWarnings includes the category that was disabled e.g. /motion. This can be leveraged in future iterations to require authors define each disabled category e.g.

/* stylelint-disable -- p-colors, p-motion: context */

Note: I started to explore this behavior, but felt the logic was becoming too complex for the initial implementation

if (
!isDisabledCoverageRule(disabledCoverageLines, disabledRange) ||
disabledRange.description?.startsWith('polaris:')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this. I do want to make sure we are aligned with the polaris: prefix for disabling Polaris rules. It could be p-ignore p-skip p-context lots of different things. The main thing is that it is searchable and the value we want for the long term.

Not against polaris but might be worthwhile discussing before shipping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great points. We could change it if needed in a future major and have overlap of multiply supported prefixes. So maybe we start with polaris: for simplicity then can introduce a convention for others later on.

Just some alternatives prefixes:

  • yo:
  • give-me-the-context:
  • but-why:
  • promise-i-need-this:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, great callout! My main focus in this iteration was ensuring we can capture disabled coverage rules and validate some form of context was provided for tracking. I landed on polaris: to avoid redundancy as the alternatives (stylelint-polaris:, p-ignore, p-skip, etc.) are already communicated by the stylelint-disable comment. That said, I'm happy to make any changes to the required prefix 👍

) {
continue;
}

const stylelintDisableText = disabledRange.comment.text
.split('--')[0]
.trim();

stylelint.utils.report({
message: `Expected /* ${stylelintDisableText} -- polaris: Reason for disabling */`,
ruleName,
result,
node: disabledRange.comment,
// Note: `stylelint-disable` comments (without next-line) appear to
// be special cased in that they do not trigger warnings when reported.
// Setting `line` to an invalid line number forces the warning to be
// reported and the above comment `node` is used to display the
// location information:
// https://github.com/stylelint/stylelint/blob/57cbcd4eb0ee809006a1e3d2ccfe73af48744ad5/lib/utils/report.js#L49-L52
line: -1,
});
}
};
},
);

/**
* @param {number[]} disabledCoverageLines
* @param {import('stylelint').DisabledRange} disabledRange
*/
function isDisabledCoverageRule(disabledCoverageLines, disabledRange) {
if (isUnclosedDisabledRange(disabledRange)) {
return disabledCoverageLines.some(
(disabledCoverageLine) => disabledCoverageLine >= disabledRange.start,
);
}

return disabledCoverageLines.some(
(coverageDisabledLine) =>
coverageDisabledLine >= disabledRange.start &&
coverageDisabledLine <= disabledRange.end,
);
}

/**
* Checks if the `disabledRange` is an unclosed `stylelint-disable` comment
* e.g. The `stylelint-disable` comment is NOT followed by `stylelint-enable`
* @param {import('stylelint').DisabledRange} disabledRange
*/
function isUnclosedDisabledRange(disabledRange) {
if (
!disabledRange.comment.text.startsWith('stylelint-disable-next-line') &&
!isNumber(disabledRange.end)
) {
return true;
}

return false;
}

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

Expand Down
57 changes: 57 additions & 0 deletions stylelint-polaris/plugins/coverage/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ testRule({
code: '@media (min-width: 320px) {}',
description: 'Uses allowed at-rule',
},
{
code: `
/* stylelint-disable -- polaris: context */
@keyframes foo {}
/* stylelint-enable */
`,
description:
'Uses disallowed at-rule with disable/enable comment and context',
},
{
code: `
/* stylelint-disable -- polaris: context */
@keyframes foo {}
`,
description:
'Uses disallowed at-rule with disable comment and context and without enable comment',
},
{
code: `
/* stylelint-disable-next-line -- polaris: context */
@keyframes foo {}
`,
description:
'Uses disallowed at-rule with disable next line comment and context',
},
],

reject: [
Expand All @@ -25,5 +50,37 @@ testRule({
message:
'Unexpected at-rule "keyframes" (stylelint-polaris/coverage/motion)',
},
{
code: `
/* stylelint-disable */
@keyframes foo {}
/* stylelint-enable */
`,
description:
'Uses disallowed at-rule with disable/enable comment and without context',
message:
'Expected /* stylelint-disable -- polaris: Reason for disabling */',
},
{
code: `
/* stylelint-disable */

@keyframes fooz {}
`,
description:
'Uses disallowed at-rule with disable comment and without context and enable comment',
message:
'Expected /* stylelint-disable -- polaris: Reason for disabling */',
},
{
code: `
/* stylelint-disable-next-line */
@keyframes foo {}
`,
description:
'Uses disallowed at-rule with disable next line comment and without context',
message:
'Expected /* stylelint-disable-next-line -- polaris: Reason for disabling */',
},
],
});