Skip to content

Commit

Permalink
feat: Add support for media conditions in require-baseline rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ryo-manba committed Feb 16, 2025
1 parent b787339 commit ff2af32
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 1 deletion.
8 changes: 8 additions & 0 deletions docs/rules/require-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This rule warns when it finds any of the following:

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

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

/* invalid - device-posture is not widely available */
@media (device-posture: folded) {
a {
color: red;
}
}

/* invalid - property value doesn't match @supports indicator */
@supports (accent-color: auto) {
a {
Expand Down
40 changes: 40 additions & 0 deletions src/data/baseline-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,46 @@ export const atRules = new Map([
["starting-style", 0],
["supports", 10],
]);
export const mediaConditions = new Map([
["color-gamut", 5],
["device-posture", 0],
["device-aspect-ratio", 0],
["device-height", 0],
["device-width", 0],
["display-mode", 0],
["dynamic-range", 10],
["forced-colors", 5],
["any-hover", 10],
["any-pointer", 10],
["hover", 10],
["pointer", 10],
["inverted-colors", 0],
["aspect-ratio", 10],
["calc", 10],
["color", 10],
["color-index", 10],
["grid", 10],
["height", 10],
["monochrome", 10],
["nested-queries", 10],
["orientation", 10],
["width", 10],
["overflow-block", 5],
["overflow-inline", 5],
["prefers-color-scheme", 10],
["prefers-contrast", 10],
["prefers-reduced-data", 0],
["prefers-reduced-motion", 10],
["prefers-reduced-transparency", 0],
["resolution", 5],
["-webkit-device-pixel-ratio", 10],
["-webkit-max-device-pixel-ratio", 10],
["-webkit-min-device-pixel-ratio", 10],
["scripting", 5],
["-webkit-transform-3d", 10],
["update", 5],
["video-dynamic-range", 0],
]);
export const types = new Map([
["abs", 0],
["sign", 0],
Expand Down
42 changes: 42 additions & 0 deletions src/rules/require-baseline.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
properties,
propertyValues,
atRules,
mediaConditions,
types,
} from "../data/baseline-data.js";
import { namedColors } from "../data/colors.js";
Expand Down Expand Up @@ -344,6 +345,8 @@ export default {
"At-rule '@{{atRule}}' is not a {{availability}} available baseline feature.",
notBaselineType:
"Type '{{type}}' is not a {{availability}} available baseline feature.",
notBaselineMediaCondition:
"Media condition '{{condition}}' is not a {{availability}} available baseline feature.",
},
},

Expand Down Expand Up @@ -549,6 +552,45 @@ export default {
supportsRules.pop();
},

"Atrule[name=media] > AtrulePrelude > MediaQueryList > MediaQuery > Condition"(
node,
) {
for (const child of node.children) {
if (child.type !== "Feature") {
continue;
}

const conditionLevel = mediaConditions.get(child.name);

if (conditionLevel < baselineLevel) {
const loc = child.loc;

context.report({
loc: {
start: {
line: loc.start.line,
// add 1 to account for the @ symbol
column: loc.start.column + 1,
},
end: {
line: loc.start.line,
column:
// add 1 to account for the @ symbol
loc.start.column +
child.name.length +
1,
},
},
messageId: "notBaselineMediaCondition",
data: {
condition: child.name,
availability,
},
});
}
}
},

Atrule(node) {
// ignore unknown at-rules - no-invalid-at-rules already catches this
if (!atRules.has(node.name)) {
Expand Down
63 changes: 62 additions & 1 deletion tests/rules/require-baseline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ruleTester.run("require-baseline", rule, {
"a { color: red; -moz-transition: bar }",
"@font-face { font-weight: 100 400 }",
"@media (min-width: 800px) { a { color: red; } }",
"@media (prefers-color-scheme: dark) { a { color: red; } }",
"@supports (accent-color: auto) { a { accent-color: auto; } }",
"@supports (accent-color: red) { a { accent-color: red; } }",
"@supports (accent-color: auto) { a { accent-color: red; } }",
Expand Down Expand Up @@ -179,7 +180,7 @@ ruleTester.run("require-baseline", rule, {
@supports (backdrop-filter: auto) {
a { accent-color: red; }
}
a { backdrop-filter: auto; }
}`,
errors: [
Expand Down Expand Up @@ -261,5 +262,65 @@ ruleTester.run("require-baseline", rule, {
},
],
},
{
code: "@media (color-gamut: srgb) { a { color: red; } }",
errors: [
{
messageId: "notBaselineMediaCondition",
data: {
condition: "color-gamut",
availability: "widely",
},
line: 1,
column: 9,
endLine: 1,
endColumn: 20,
},
],
},
{
code: "@media (device-posture: folded) { a { color: red; } }",
options: [{ available: "newly" }],
errors: [
{
messageId: "notBaselineMediaCondition",
data: {
condition: "device-posture",
availability: "newly",
},
line: 1,
column: 9,
endLine: 1,
endColumn: 23,
},
],
},
{
code: "@media (height: 600px) and (color-gamut: srgb) and (device-posture: folded) { a { color: red; } }",
errors: [
{
messageId: "notBaselineMediaCondition",
data: {
condition: "color-gamut",
availability: "widely",
},
line: 1,
column: 29,
endLine: 1,
endColumn: 40,
},
{
messageId: "notBaselineMediaCondition",
data: {
condition: "device-posture",
availability: "widely",
},
line: 1,
column: 53,
endLine: 1,
endColumn: 67,
},
],
},
],
});
11 changes: 11 additions & 0 deletions tools/generate-baseline.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ function extractCSSFeatures(features) {
const cssPropertyValuePattern =
/^css\.properties\.(?<property>[a-zA-Z$\d-]+)\.(?<value>[a-zA-Z$\d-]+)$/u;
const cssAtRulePattern = /^css\.at-rules\.(?<atRule>[a-zA-Z$\d-]+)$/u;
const cssMediaConditionPattern =
/^css\.at-rules\.media\.(?<condition>[a-zA-Z$\d-]+)$/u;
const cssTypePattern = /^css\.types\.(?<type>[a-zA-Z$\d-]+)$/u;
const cssSelectorPattern = /^css\.selectors\.(?<selector>[a-zA-Z$\d-]+)$/u;

const properties = {};
const propertyValues = {};
const atRules = {};
const mediaConditions = {};
const types = {};
const selectors = {};

Expand Down Expand Up @@ -99,6 +102,12 @@ function extractCSSFeatures(features) {
continue;
}

// Media conditions (@media features)
if ((match = cssMediaConditionPattern.exec(key)) !== null) {
mediaConditions[match.groups.condition] = baselineIds.get(baseline);
continue;
}

// types
if ((match = cssTypePattern.exec(key)) !== null) {
types[match.groups.type] = baselineIds.get(baseline);
Expand All @@ -116,6 +125,7 @@ function extractCSSFeatures(features) {
properties,
propertyValues,
atRules,
mediaConditions,
types,
selectors,
};
Expand Down Expand Up @@ -151,6 +161,7 @@ export const BASELINE_FALSE = ${BASELINE_FALSE};
export const properties = new Map(${JSON.stringify(Object.entries(cssFeatures.properties), null, "\t")});
export const atRules = new Map(${JSON.stringify(Object.entries(cssFeatures.atRules), null, "\t")});
export const mediaConditions = new Map(${JSON.stringify(Object.entries(cssFeatures.mediaConditions), null, "\t")});
export const types = new Map(${JSON.stringify(Object.entries(cssFeatures.types), null, "\t")});
export const selectors = new Map(${JSON.stringify(Object.entries(cssFeatures.selectors), null, "\t")});
export const propertyValues = new Map([${Object.entries(
Expand Down

0 comments on commit ff2af32

Please sign in to comment.