Skip to content

Commit e8dc57d

Browse files
authored
feat: add allow* options to use-baseline rule (#310)
* feat: add `allow*` options to `use-baseline` rule * validate `allow*` options against baseline data
1 parent 23caee3 commit e8dc57d

File tree

3 files changed

+215
-3
lines changed

3 files changed

+215
-3
lines changed

docs/rules/use-baseline.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ This rule accepts an options object with the following properties:
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
107107
- `allowAtRules` (default: `[]`) - Specify an array of at-rules that are allowed to be used.
108+
- `allowFunctions` (default: `[]`) - Specify an array of functions that are allowed to be used.
109+
- `allowMediaConditions` (default: `[]`) - Specify an array of media conditions inside `@media` that are allowed to be used.
108110
- `allowProperties` (default: `[]`) - Specify an array of properties that are allowed to be used.
111+
- `allowPropertyValues` (default: `{}`) - Specify an object mapping properties to an array of allowed identifier values for that property.
109112
- `allowSelectors` (default: `[]`) - Specify an array of selectors that are allowed to be used.
110113

111114
#### `allowAtRules`
@@ -122,6 +125,32 @@ Examples of **correct** code with `{ allowAtRules: ["container"] }`:
122125
}
123126
```
124127

128+
#### `allowFunctions`
129+
130+
Examples of **correct** code with `{ allowFunctions: ["color-mix"] }`:
131+
132+
```css
133+
/* eslint css/use-baseline: ["error", { allowFunctions: ["color-mix"] }] */
134+
135+
.selector {
136+
color: color-mix(in srgb, plum, #123456);
137+
}
138+
```
139+
140+
#### `allowMediaConditions`
141+
142+
Examples of **correct** code with `{ allowMediaConditions: ["scripting"] }`:
143+
144+
```css
145+
/* eslint css/use-baseline: ["error", { allowMediaConditions: ["scripting"] }] */
146+
147+
@media (scripting: none) {
148+
.selector {
149+
color: red;
150+
}
151+
}
152+
```
153+
125154
#### `allowProperties`
126155

127156
Examples of **correct** code with `{ allowProperties: ["user-select"] }`:
@@ -134,6 +163,18 @@ Examples of **correct** code with `{ allowProperties: ["user-select"] }`:
134163
}
135164
```
136165

166+
#### `allowPropertyValues`
167+
168+
Examples of **correct** code with `{ allowPropertyValues: { "clip-path": ["fill-box"] } }`:
169+
170+
```css
171+
/* eslint css/use-baseline: ["error", { allowPropertyValues: { "clip-path": ["fill-box"] } }] */
172+
173+
.selector {
174+
clip-path: fill-box;
175+
}
176+
```
177+
137178
#### `allowSelectors`
138179

139180
When you want to allow the [& nesting selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector), you can use `"nesting"`.

src/rules/use-baseline.js

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import { namedColors } from "../data/colors.js";
3030
* @typedef {[{
3131
* available?: "widely" | "newly" | number,
3232
* allowAtRules?: string[],
33+
* allowFunctions?: string[],
34+
* allowMediaConditions?: string[],
3335
* allowProperties?: string[],
36+
* allowPropertyValues?: { [property: string]: string[] },
3437
* allowSelectors?: string[]
3538
* }]} UseBaselineOptions
3639
* @typedef {CSSRuleDefinition<{ RuleOptions: UseBaselineOptions, MessageIds: UseBaselineMessageIds }>} UseBaselineRuleDefinition
@@ -436,21 +439,53 @@ export default {
436439
allowAtRules: {
437440
type: "array",
438441
items: {
439-
type: "string",
442+
enum: Array.from(atRules.keys()),
443+
},
444+
uniqueItems: true,
445+
},
446+
allowFunctions: {
447+
type: "array",
448+
items: {
449+
enum: Array.from(functions.keys()),
450+
},
451+
uniqueItems: true,
452+
},
453+
allowMediaConditions: {
454+
type: "array",
455+
items: {
456+
enum: Array.from(mediaConditions.keys()),
440457
},
441458
uniqueItems: true,
442459
},
443460
allowProperties: {
444461
type: "array",
445462
items: {
446-
type: "string",
463+
enum: Array.from(properties.keys()),
447464
},
448465
uniqueItems: true,
449466
},
467+
allowPropertyValues: {
468+
type: "object",
469+
properties: Object.fromEntries(
470+
Array.from(propertyValues.entries()).map(
471+
([prop, valuesMap]) => [
472+
prop,
473+
{
474+
type: "array",
475+
items: {
476+
enum: Array.from(valuesMap.keys()),
477+
},
478+
uniqueItems: true,
479+
},
480+
],
481+
),
482+
),
483+
additionalProperties: false,
484+
},
450485
allowSelectors: {
451486
type: "array",
452487
items: {
453-
type: "string",
488+
enum: Array.from(selectors.keys()),
454489
},
455490
uniqueItems: true,
456491
},
@@ -463,7 +498,10 @@ export default {
463498
{
464499
available: "widely",
465500
allowAtRules: [],
501+
allowFunctions: [],
502+
allowMediaConditions: [],
466503
allowProperties: [],
504+
allowPropertyValues: {},
467505
allowSelectors: [],
468506
},
469507
],
@@ -492,6 +530,16 @@ export default {
492530
const allowAtRules = new Set(context.options[0].allowAtRules);
493531
const allowProperties = new Set(context.options[0].allowProperties);
494532
const allowSelectors = new Set(context.options[0].allowSelectors);
533+
const allowFunctions = new Set(context.options[0].allowFunctions);
534+
const allowMediaConditions = new Set(
535+
context.options[0].allowMediaConditions,
536+
);
537+
const allowPropertyValuesMap = new Map();
538+
for (const [prop, values] of Object.entries(
539+
context.options[0].allowPropertyValues,
540+
)) {
541+
allowPropertyValuesMap.set(prop, new Set(values));
542+
}
495543

496544
/**
497545
* Checks a property value identifier to see if it's a baseline feature.
@@ -504,6 +552,12 @@ export default {
504552
if (namedColors.has(child.name)) {
505553
return;
506554
}
555+
556+
const allowedValues = allowPropertyValuesMap.get(property);
557+
if (allowedValues?.has(child.name)) {
558+
return;
559+
}
560+
507561
const possiblePropertyValues = propertyValues.get(property);
508562

509563
// if we don't know of any possible property values, just skip it
@@ -537,6 +591,10 @@ export default {
537591
* @returns {void}
538592
**/
539593
function checkPropertyValueFunction(child) {
594+
if (allowFunctions.has(child.name)) {
595+
return;
596+
}
597+
540598
const featureStatus = functions.get(child.name);
541599

542600
// if we don't know of any possible property values, just skip it
@@ -718,6 +776,10 @@ export default {
718776
continue;
719777
}
720778

779+
if (allowMediaConditions.has(child.name)) {
780+
continue;
781+
}
782+
721783
const featureStatus = mediaConditions.get(child.name);
722784

723785
if (!baselineAvailability.isSupported(featureStatus)) {

tests/rules/use-baseline.test.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,33 @@ ruleTester.run("use-baseline", rule, {
123123
code: "a { accent-color: bar; backdrop-filter: auto }",
124124
options: [{ allowProperties: ["accent-color", "backdrop-filter"] }],
125125
},
126+
{
127+
code: "a { color: color-mix(in srgb, plum, #123456); }",
128+
options: [{ allowFunctions: ["color-mix"] }],
129+
},
130+
{
131+
code: "@media (scripting: none) { a { color: red; } }",
132+
options: [{ allowMediaConditions: ["scripting"] }],
133+
},
134+
{
135+
code: "a { clip-path: fill-box; }",
136+
options: [{ allowPropertyValues: { "clip-path": ["fill-box"] } }],
137+
},
138+
{
139+
code: ".a { clip-path: fill-box; }\n.b { clip-path: stroke-box; }\n.c { max-height: stretch; }",
140+
options: [
141+
{
142+
allowPropertyValues: {
143+
"clip-path": ["fill-box", "stroke-box"],
144+
"max-height": ["stretch"],
145+
},
146+
},
147+
],
148+
},
149+
{
150+
code: "@supports (clip-path: fill-box) { .a { clip-path: fill-box; }\n.b { clip-path: stroke-box; } }",
151+
options: [{ allowPropertyValues: { "clip-path": ["stroke-box"] } }],
152+
},
126153
],
127154
invalid: [
128155
{
@@ -604,5 +631,87 @@ ruleTester.run("use-baseline", rule, {
604631
},
605632
],
606633
},
634+
{
635+
code: "a { width: abs(20% - 100px); }",
636+
options: [{ allowFunctions: ["color-mix"] }],
637+
errors: [
638+
{
639+
messageId: "notBaselineFunction",
640+
data: {
641+
function: "abs",
642+
availability: "widely",
643+
},
644+
line: 1,
645+
column: 12,
646+
endLine: 1,
647+
endColumn: 28,
648+
},
649+
],
650+
},
651+
{
652+
code: "@media (inverted-colors: inverted) { a { color: red; } }",
653+
options: [{ allowMediaConditions: ["scripting"] }],
654+
errors: [
655+
{
656+
messageId: "notBaselineMediaCondition",
657+
data: {
658+
condition: "inverted-colors",
659+
availability: "widely",
660+
},
661+
line: 1,
662+
column: 9,
663+
endLine: 1,
664+
endColumn: 24,
665+
},
666+
],
667+
},
668+
{
669+
code: "@supports (clip-path: fill-box) { a { clip-path: stroke-box; } }",
670+
options: [{ allowPropertyValues: { "clip-path": ["fill-box"] } }],
671+
errors: [
672+
{
673+
messageId: "notBaselinePropertyValue",
674+
data: {
675+
property: "clip-path",
676+
value: "stroke-box",
677+
availability: "widely",
678+
},
679+
line: 1,
680+
column: 50,
681+
endLine: 1,
682+
endColumn: 60,
683+
},
684+
],
685+
},
686+
{
687+
code: ".a { clip-path: fill-box; }\n.b { clip-path: stroke-box; }\n.c { max-height: stretch; }",
688+
options: [{ allowPropertyValues: { "clip-path": ["fill-box"] } }],
689+
errors: [
690+
{
691+
messageId: "notBaselinePropertyValue",
692+
data: {
693+
property: "clip-path",
694+
value: "stroke-box",
695+
availability: "widely",
696+
},
697+
line: 2,
698+
column: 17,
699+
endLine: 2,
700+
endColumn: 27,
701+
},
702+
{
703+
messageId: "notBaselinePropertyValue",
704+
data: {
705+
property: "max-height",
706+
value: "stretch",
707+
availability: "widely",
708+
},
709+
line: 3,
710+
column: 18,
711+
endLine: 3,
712+
endColumn: 25,
713+
},
714+
],
715+
},
607716
],
608717
});

0 commit comments

Comments
 (0)