Skip to content

Commit ad9ddd7

Browse files
authored
feat: Add support for media conditions in require-baseline rule (#49)
* feat: Add support for media conditions in require-baseline rule * Add check for invalid at-rules * test: ignore unknown media conditions
1 parent 21b5aad commit ad9ddd7

File tree

5 files changed

+185
-1
lines changed

5 files changed

+185
-1
lines changed

docs/rules/require-baseline.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This rule warns when it finds any of the following:
2020

2121
- A CSS property that isn't widely available or otherwise isn't enclosed in a `@supports` block.
2222
- An at-rule that isn't widely available.
23+
- A media condition inside `@media` that isn't widely available.
2324
- A CSS property value that isn't widely available or otherwise isn't enclosed in a `@supports` block (currently limited to identifiers only).
2425
- A CSS property function that isn't widely available.
2526

@@ -38,6 +39,13 @@ a {
3839
width: abs(20% - 100px);
3940
}
4041

42+
/* invalid - device-posture is not widely available */
43+
@media (device-posture: folded) {
44+
a {
45+
color: red;
46+
}
47+
}
48+
4149
/* invalid - property value doesn't match @supports indicator */
4250
@supports (accent-color: auto) {
4351
a {

src/data/baseline-data.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,46 @@ export const atRules = new Map([
517517
["starting-style", 0],
518518
["supports", 10],
519519
]);
520+
export const mediaConditions = new Map([
521+
["color-gamut", 5],
522+
["device-posture", 0],
523+
["device-aspect-ratio", 0],
524+
["device-height", 0],
525+
["device-width", 0],
526+
["display-mode", 0],
527+
["dynamic-range", 10],
528+
["forced-colors", 5],
529+
["any-hover", 10],
530+
["any-pointer", 10],
531+
["hover", 10],
532+
["pointer", 10],
533+
["inverted-colors", 0],
534+
["aspect-ratio", 10],
535+
["calc", 10],
536+
["color", 10],
537+
["color-index", 10],
538+
["grid", 10],
539+
["height", 10],
540+
["monochrome", 10],
541+
["nested-queries", 10],
542+
["orientation", 10],
543+
["width", 10],
544+
["overflow-block", 5],
545+
["overflow-inline", 5],
546+
["prefers-color-scheme", 10],
547+
["prefers-contrast", 10],
548+
["prefers-reduced-data", 0],
549+
["prefers-reduced-motion", 10],
550+
["prefers-reduced-transparency", 0],
551+
["resolution", 5],
552+
["-webkit-device-pixel-ratio", 10],
553+
["-webkit-max-device-pixel-ratio", 10],
554+
["-webkit-min-device-pixel-ratio", 10],
555+
["scripting", 5],
556+
["-webkit-transform-3d", 10],
557+
["update", 5],
558+
["video-dynamic-range", 0],
559+
]);
520560
export const types = new Map([
521561
["abs", 0],
522562
["sign", 0],

src/rules/require-baseline.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
properties,
1414
propertyValues,
1515
atRules,
16+
mediaConditions,
1617
types,
1718
} from "../data/baseline-data.js";
1819
import { namedColors } from "../data/colors.js";
@@ -344,6 +345,8 @@ export default {
344345
"At-rule '@{{atRule}}' is not a {{availability}} available baseline feature.",
345346
notBaselineType:
346347
"Type '{{type}}' is not a {{availability}} available baseline feature.",
348+
notBaselineMediaCondition:
349+
"Media condition '{{condition}}' is not a {{availability}} available baseline feature.",
347350
},
348351
},
349352

@@ -549,6 +552,50 @@ export default {
549552
supportsRules.pop();
550553
},
551554

555+
"Atrule[name=media] > AtrulePrelude > MediaQueryList > MediaQuery > Condition"(
556+
node,
557+
) {
558+
for (const child of node.children) {
559+
// ignore unknown media conditions - no-invalid-at-rules already catches this
560+
if (!mediaConditions.has(child.name)) {
561+
continue;
562+
}
563+
564+
if (child.type !== "Feature") {
565+
continue;
566+
}
567+
568+
const conditionLevel = mediaConditions.get(child.name);
569+
570+
if (conditionLevel < baselineLevel) {
571+
const loc = child.loc;
572+
573+
context.report({
574+
loc: {
575+
start: {
576+
line: loc.start.line,
577+
// add 1 to account for the @ symbol
578+
column: loc.start.column + 1,
579+
},
580+
end: {
581+
line: loc.start.line,
582+
column:
583+
// add 1 to account for the @ symbol
584+
loc.start.column +
585+
child.name.length +
586+
1,
587+
},
588+
},
589+
messageId: "notBaselineMediaCondition",
590+
data: {
591+
condition: child.name,
592+
availability,
593+
},
594+
});
595+
}
596+
}
597+
},
598+
552599
Atrule(node) {
553600
// ignore unknown at-rules - no-invalid-at-rules already catches this
554601
if (!atRules.has(node.name)) {

tests/rules/require-baseline.test.js

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ ruleTester.run("require-baseline", rule, {
3434
"a { color: red; -moz-transition: bar }",
3535
"@font-face { font-weight: 100 400 }",
3636
"@media (min-width: 800px) { a { color: red; } }",
37+
"@media (foo) { a { color: red; } }",
38+
"@media (prefers-color-scheme: dark) { a { color: red; } }",
3739
"@supports (accent-color: auto) { a { accent-color: auto; } }",
3840
"@supports (accent-color: red) { a { accent-color: red; } }",
3941
"@supports (accent-color: auto) { a { accent-color: red; } }",
@@ -180,7 +182,7 @@ ruleTester.run("require-baseline", rule, {
180182
@supports (backdrop-filter: auto) {
181183
a { accent-color: red; }
182184
}
183-
185+
184186
a { backdrop-filter: auto; }
185187
}`,
186188
errors: [
@@ -262,5 +264,81 @@ ruleTester.run("require-baseline", rule, {
262264
},
263265
],
264266
},
267+
{
268+
code: "@media (color-gamut: srgb) { a { color: red; } }",
269+
errors: [
270+
{
271+
messageId: "notBaselineMediaCondition",
272+
data: {
273+
condition: "color-gamut",
274+
availability: "widely",
275+
},
276+
line: 1,
277+
column: 9,
278+
endLine: 1,
279+
endColumn: 20,
280+
},
281+
],
282+
},
283+
{
284+
code: "@media (device-posture: folded) { a { color: red; } }",
285+
options: [{ available: "newly" }],
286+
errors: [
287+
{
288+
messageId: "notBaselineMediaCondition",
289+
data: {
290+
condition: "device-posture",
291+
availability: "newly",
292+
},
293+
line: 1,
294+
column: 9,
295+
endLine: 1,
296+
endColumn: 23,
297+
},
298+
],
299+
},
300+
{
301+
code: "@media (height: 600px) and (color-gamut: srgb) and (device-posture: folded) { a { color: red; } }",
302+
errors: [
303+
{
304+
messageId: "notBaselineMediaCondition",
305+
data: {
306+
condition: "color-gamut",
307+
availability: "widely",
308+
},
309+
line: 1,
310+
column: 29,
311+
endLine: 1,
312+
endColumn: 40,
313+
},
314+
{
315+
messageId: "notBaselineMediaCondition",
316+
data: {
317+
condition: "device-posture",
318+
availability: "widely",
319+
},
320+
line: 1,
321+
column: 53,
322+
endLine: 1,
323+
endColumn: 67,
324+
},
325+
],
326+
},
327+
{
328+
code: "@media (foo) and (color-gamut: srgb) { a { color: red; } }",
329+
errors: [
330+
{
331+
messageId: "notBaselineMediaCondition",
332+
data: {
333+
condition: "color-gamut",
334+
availability: "widely",
335+
},
336+
line: 1,
337+
column: 19,
338+
endLine: 1,
339+
endColumn: 30,
340+
},
341+
],
342+
},
265343
],
266344
});

tools/generate-baseline.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,15 @@ function extractCSSFeatures(features) {
7272
const cssPropertyValuePattern =
7373
/^css\.properties\.(?<property>[a-zA-Z$\d-]+)\.(?<value>[a-zA-Z$\d-]+)$/u;
7474
const cssAtRulePattern = /^css\.at-rules\.(?<atRule>[a-zA-Z$\d-]+)$/u;
75+
const cssMediaConditionPattern =
76+
/^css\.at-rules\.media\.(?<condition>[a-zA-Z$\d-]+)$/u;
7577
const cssTypePattern = /^css\.types\.(?<type>[a-zA-Z$\d-]+)$/u;
7678
const cssSelectorPattern = /^css\.selectors\.(?<selector>[a-zA-Z$\d-]+)$/u;
7779

7880
const properties = {};
7981
const propertyValues = {};
8082
const atRules = {};
83+
const mediaConditions = {};
8184
const types = {};
8285
const selectors = {};
8386

@@ -114,6 +117,12 @@ function extractCSSFeatures(features) {
114117
continue;
115118
}
116119

120+
// Media conditions (@media features)
121+
if ((match = cssMediaConditionPattern.exec(key)) !== null) {
122+
mediaConditions[match.groups.condition] = baselineIds.get(baseline);
123+
continue;
124+
}
125+
117126
// types
118127
if ((match = cssTypePattern.exec(key)) !== null) {
119128
types[match.groups.type] = baselineIds.get(baseline);
@@ -131,6 +140,7 @@ function extractCSSFeatures(features) {
131140
properties,
132141
propertyValues,
133142
atRules,
143+
mediaConditions,
134144
types,
135145
selectors,
136146
};
@@ -166,6 +176,7 @@ export const BASELINE_FALSE = ${BASELINE_FALSE};
166176
167177
export const properties = new Map(${JSON.stringify(Object.entries(cssFeatures.properties), null, "\t")});
168178
export const atRules = new Map(${JSON.stringify(Object.entries(cssFeatures.atRules), null, "\t")});
179+
export const mediaConditions = new Map(${JSON.stringify(Object.entries(cssFeatures.mediaConditions), null, "\t")});
169180
export const types = new Map(${JSON.stringify(Object.entries(cssFeatures.types), null, "\t")});
170181
export const selectors = new Map(${JSON.stringify(Object.entries(cssFeatures.selectors), null, "\t")});
171182
export const propertyValues = new Map([${Object.entries(

0 commit comments

Comments
 (0)