Skip to content

Commit 623ad8e

Browse files
authored
feat: add allow-list options for at-rules, properties, and selectors (#228)
* feat: add allow-list options for at-rules, properties, and selectors fixes #210 * docs: improve use-baseline rule options docs * test: add invalid test cases for allow options in use-baseline rule * docs: improve structure of use-baseline rule documentation
1 parent 330c326 commit 623ad8e

File tree

3 files changed

+175
-2
lines changed

3 files changed

+175
-2
lines changed

docs/rules/use-baseline.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,66 @@ Examples of **correct** code:
9999

100100
### Options
101101

102-
This rule accepts an option object with the following properties:
102+
This rule accepts an options object with the following properties:
103103

104104
- `available` (default: `"widely"`)
105105
- change to `"newly"` to allow features that are at the Baseline newly available stage: features that have been supported on all core browsers for less than 30 months
106106
- set to a numeric baseline year, such as `2023`, to allow features that became Baseline newly available that year, or earlier
107+
- `allowAtRules` (default: `[]`) - Specify an array of at-rules that are allowed to be used.
108+
- `allowProperties` (default: `[]`) - Specify an array of properties that are allowed to be used.
109+
- `allowSelectors` (default: `[]`) - Specify an array of selectors that are allowed to be used.
110+
111+
#### `allowAtRules`
112+
113+
Examples of **correct** code with `{ allowAtRules: ["container"] }`:
114+
115+
```css
116+
/* eslint css/use-baseline: ["error", { allowAtRules: ["container"] }] */
117+
118+
@container (width > 400px) {
119+
h2 {
120+
font-size: 1.5em;
121+
}
122+
}
123+
```
124+
125+
#### `allowProperties`
126+
127+
Examples of **correct** code with `{ allowProperties: ["user-select"] }`:
128+
129+
```css
130+
/* eslint css/use-baseline: ["error", { allowProperties: ["user-select"] }] */
131+
132+
.unselectable {
133+
user-select: none;
134+
}
135+
```
136+
137+
#### `allowSelectors`
138+
139+
When you want to allow the [& nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector), you can use `"nesting"`.
140+
141+
Examples of **correct** code with `{ allowSelectors: ["nesting"] }`:
142+
143+
```css
144+
/* eslint css/use-baseline: ["error", { allowSelectors: ["nesting"] }] */
145+
146+
.parent {
147+
&:hover {
148+
background-color: blue;
149+
}
150+
}
151+
```
152+
153+
Examples of **correct** code with `{ allowSelectors: ["has"] }`:
154+
155+
```css
156+
/* eslint css/use-baseline: ["error", { allowSelectors: ["has"] }] */
157+
158+
h1:has(+ h2) {
159+
margin: 0 0 0.25rem 0;
160+
}
161+
```
107162

108163
## When Not to Use It
109164

src/rules/use-baseline.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import { namedColors } from "../data/colors.js";
2828
* @import { Identifier, FunctionNodePlain } from "@eslint/css-tree"
2929
* @typedef {"notBaselineProperty" | "notBaselinePropertyValue" | "notBaselineAtRule" | "notBaselineType" | "notBaselineMediaCondition" | "notBaselineSelector"} UseBaselineMessageIds
3030
* @typedef {[{
31-
* available?: "widely" | "newly" | number
31+
* available?: "widely" | "newly" | number,
32+
* allowAtRules?: string[],
33+
* allowProperties?: string[],
34+
* allowSelectors?: string[]
3235
* }]} UseBaselineOptions
3336
* @typedef {CSSRuleDefinition<{ RuleOptions: UseBaselineOptions, MessageIds: UseBaselineMessageIds }>} UseBaselineRuleDefinition
3437
*/
@@ -430,6 +433,27 @@ export default {
430433
},
431434
],
432435
},
436+
allowAtRules: {
437+
type: "array",
438+
items: {
439+
type: "string",
440+
},
441+
uniqueItems: true,
442+
},
443+
allowProperties: {
444+
type: "array",
445+
items: {
446+
type: "string",
447+
},
448+
uniqueItems: true,
449+
},
450+
allowSelectors: {
451+
type: "array",
452+
items: {
453+
type: "string",
454+
},
455+
uniqueItems: true,
456+
},
433457
},
434458
additionalProperties: false,
435459
},
@@ -438,6 +462,9 @@ export default {
438462
defaultOptions: [
439463
{
440464
available: "widely",
465+
allowAtRules: [],
466+
allowProperties: [],
467+
allowSelectors: [],
441468
},
442469
],
443470

@@ -462,6 +489,9 @@ export default {
462489
context.options[0].available,
463490
);
464491
const supportsRules = new SupportsRules();
492+
const allowAtRules = new Set(context.options[0].allowAtRules);
493+
const allowProperties = new Set(context.options[0].allowProperties);
494+
const allowSelectors = new Set(context.options[0].allowSelectors);
465495

466496
/**
467497
* Checks a property value identifier to see if it's a baseline feature.
@@ -587,6 +617,10 @@ export default {
587617
return;
588618
}
589619

620+
if (allowProperties.has(property)) {
621+
return;
622+
}
623+
590624
/*
591625
* Step 1: Check that the property is in the baseline.
592626
*
@@ -724,6 +758,10 @@ export default {
724758
return;
725759
}
726760

761+
if (allowAtRules.has(atRuleName)) {
762+
return;
763+
}
764+
727765
const featureStatus = atRules.get(atRuleName);
728766

729767
if (!baselineAvailability.isSupported(featureStatus)) {
@@ -757,6 +795,10 @@ export default {
757795
return;
758796
}
759797

798+
if (allowSelectors.has(selector)) {
799+
return;
800+
}
801+
760802
// if the selector has been tested in a @supports rule, don't check it
761803
if (supportsRules.hasSelector(selector)) {
762804
return;
@@ -800,6 +842,11 @@ export default {
800842
NestingSelector(node) {
801843
// NestingSelector implies CSS nesting
802844
const selector = "nesting";
845+
846+
if (allowSelectors.has(selector)) {
847+
return;
848+
}
849+
803850
const featureStatus = selectors.get(selector);
804851
if (baselineAvailability.isSupported(featureStatus)) {
805852
return;

tests/rules/use-baseline.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ ruleTester.run("use-baseline", rule, {
103103
"@container (min-width: 800px) { a { color: red; } }",
104104
"@media (color-gamut: srgb) { a { color: red; } }",
105105
"@MEDIA (color-gamut: srgb) { a { color: red; } }",
106+
{
107+
code: "h1:has(+ h2) { margin: 0 0 0.25rem 0; }",
108+
options: [{ allowSelectors: ["has"] }],
109+
},
110+
{
111+
code: `label {
112+
& input {
113+
border: blue 2px dashed;
114+
}
115+
}`,
116+
options: [{ available: 2022, allowSelectors: ["nesting"] }],
117+
},
118+
{
119+
code: "@container (min-width: 800px) { a { color: red; } }",
120+
options: [{ available: 2022, allowAtRules: ["container"] }],
121+
},
122+
{
123+
code: "a { accent-color: bar; backdrop-filter: auto }",
124+
options: [{ allowProperties: ["accent-color", "backdrop-filter"] }],
125+
},
106126
],
107127
invalid: [
108128
{
@@ -533,5 +553,56 @@ ruleTester.run("use-baseline", rule, {
533553
},
534554
],
535555
},
556+
{
557+
code: "@view-transition { from-view: a; to-view: b; }\n@container (min-width: 800px) { a { color: red; } }",
558+
options: [{ allowAtRules: ["container"] }],
559+
errors: [
560+
{
561+
messageId: "notBaselineAtRule",
562+
data: {
563+
atRule: "view-transition",
564+
availability: "widely",
565+
},
566+
line: 1,
567+
column: 1,
568+
endLine: 1,
569+
endColumn: 17,
570+
},
571+
],
572+
},
573+
{
574+
code: "a { accent-color: red; backdrop-filter: blur(10px); }",
575+
options: [{ allowProperties: ["accent-color"] }],
576+
errors: [
577+
{
578+
messageId: "notBaselineProperty",
579+
data: {
580+
property: "backdrop-filter",
581+
availability: "widely",
582+
},
583+
line: 1,
584+
column: 24,
585+
endLine: 1,
586+
endColumn: 39,
587+
},
588+
],
589+
},
590+
{
591+
code: "h1:has(+ h2) { margin: 0; }\nh1:fullscreen { color: red; }",
592+
options: [{ allowSelectors: ["has"] }],
593+
errors: [
594+
{
595+
messageId: "notBaselineSelector",
596+
data: {
597+
selector: "fullscreen",
598+
availability: "widely",
599+
},
600+
line: 2,
601+
column: 3,
602+
endLine: 2,
603+
endColumn: 14,
604+
},
605+
],
606+
},
536607
],
537608
});

0 commit comments

Comments
 (0)