diff --git a/src/rules/no-duplicate-imports.js b/src/rules/no-duplicate-imports.js index f98d7461..05801564 100644 --- a/src/rules/no-duplicate-imports.js +++ b/src/rules/no-duplicate-imports.js @@ -42,7 +42,7 @@ export default { const imports = new Set(); return { - "Atrule[name=import]"(node) { + "Atrule[name=/^import$/i]"(node) { const url = node.prelude.children[0].value; if (imports.has(url)) { diff --git a/src/rules/no-duplicate-keyframe-selectors.js b/src/rules/no-duplicate-keyframe-selectors.js index da97d743..e5899363 100644 --- a/src/rules/no-duplicate-keyframe-selectors.js +++ b/src/rules/no-duplicate-keyframe-selectors.js @@ -39,12 +39,12 @@ export default { const seen = new Map(); return { - "Atrule[name=keyframes]"() { + "Atrule[name=/^keyframes$/i]"() { insideKeyframes = true; seen.clear(); }, - "Atrule[name=keyframes]:exit"() { + "Atrule[name=/^keyframes$/i]:exit"() { insideKeyframes = false; }, diff --git a/src/rules/use-baseline.js b/src/rules/use-baseline.js index fbb7d00c..5484d2e6 100644 --- a/src/rules/use-baseline.js +++ b/src/rules/use-baseline.js @@ -527,11 +527,11 @@ export default { } return { - "Atrule[name=supports]"() { + "Atrule[name=/^supports$/i]"() { supportsRules.push(new SupportsRule()); }, - "Atrule[name=supports] > AtrulePrelude > Condition"(node) { + "Atrule[name=/^supports$/i] > AtrulePrelude > Condition"(node) { const supportsRule = supportsRules.last(); for (let i = 0; i < node.children.length; i++) { @@ -667,11 +667,11 @@ export default { } }, - "Atrule[name=supports]:exit"() { + "Atrule[name=/^supports$/i]:exit"() { supportsRules.pop(); }, - "Atrule[name=media] > AtrulePrelude > MediaQueryList > MediaQuery > Condition"( + "Atrule[name=/^media$/i] > AtrulePrelude > MediaQueryList > MediaQuery > Condition"( node, ) { for (const child of node.children) { @@ -719,11 +719,12 @@ export default { Atrule(node) { // ignore unknown at-rules - no-invalid-at-rules already catches this - if (!atRules.has(node.name)) { + const atRuleName = node.name.toLowerCase(); + if (!atRules.has(atRuleName)) { return; } - const featureStatus = atRules.get(node.name); + const featureStatus = atRules.get(atRuleName); if (!baselineAvailability.isSupported(featureStatus)) { const loc = node.loc; diff --git a/src/rules/use-layers.js b/src/rules/use-layers.js index 636f2500..49f320de 100644 --- a/src/rules/use-layers.js +++ b/src/rules/use-layers.js @@ -75,7 +75,7 @@ export default { : null; return { - "Atrule[name=import]"(node) { + "Atrule[name=/^import$/i]"(node) { // layer, if present, must always be the second child of the prelude const secondChild = node.prelude.children[1]; const layerNode = @@ -140,7 +140,7 @@ export default { }); }, - "Atrule[name=layer]"(node) { + "Atrule[name=/^layer$/i]"(node) { layerDepth++; if (!options.allowUnnamedLayers && !node.prelude) { @@ -151,7 +151,7 @@ export default { } }, - "Atrule[name=layer]:exit"() { + "Atrule[name=/^layer$/i]:exit"() { layerDepth--; }, diff --git a/tests/rules/no-duplicate-imports.test.js b/tests/rules/no-duplicate-imports.test.js index 869c1ee3..f670bb89 100644 --- a/tests/rules/no-duplicate-imports.test.js +++ b/tests/rules/no-duplicate-imports.test.js @@ -27,6 +27,9 @@ ruleTester.run("no-duplicate-imports", rule, { "@import url('x.css');", "@import url('x.css'); @import url('y.css');", "@import 'x.css'; @import url('y.css'); @import 'z.css';", + "@IMPORT url('x.css');", + "@imPort url('x.css'); @IMport url('y.css');", + "@IMPORT 'x.css'; @import url('y.css'); @IMport 'z.css';", ], invalid: [ { @@ -157,5 +160,83 @@ ruleTester.run("no-duplicate-imports", rule, { }, ], }, + { + code: "@IMPORT url('x.css');\n@IMPORT url('x.css');", + output: "@IMPORT url('x.css');\n", + errors: [ + { + messageId: "duplicateImport", + data: { url: "x.css" }, + line: 2, + column: 1, + endLine: 2, + endColumn: 22, + }, + ], + }, + { + code: "@IMport url('x.css');@IMPORT url('x.css');", + output: "@IMport url('x.css');", + errors: [ + { + messageId: "duplicateImport", + data: { url: "x.css" }, + line: 1, + column: 22, + endLine: 1, + endColumn: 43, + }, + ], + }, + { + code: "@IMPORT url('x.css');@IMPORT url('x.css');@IMPORT url('y.css')", + output: "@IMPORT url('x.css');@IMPORT url('y.css')", + errors: [ + { + messageId: "duplicateImport", + data: { url: "x.css" }, + line: 1, + column: 22, + endLine: 1, + endColumn: 43, + }, + ], + }, + { + code: "@IMPORT url('x.css');\n@IMPORT 'x.css';\n@IMPORT 'x.css';", + output: "@IMPORT url('x.css');\n@IMPORT 'x.css';", + errors: [ + { + messageId: "duplicateImport", + data: { url: "x.css" }, + line: 2, + column: 1, + endLine: 2, + endColumn: 17, + }, + { + messageId: "duplicateImport", + data: { url: "x.css" }, + line: 3, + column: 1, + endLine: 3, + endColumn: 17, + }, + ], + }, + { + code: "@IMPORT url('a.css');\n@import url('b.css');\n@IMPORT url('c.css');\n@import url('a.css');\n@IMPORT url('d.css');", + output: "@IMPORT url('a.css');\n@import url('b.css');\n@IMPORT url('c.css');\n@IMPORT url('d.css');", + errors: [ + { + messageId: "duplicateImport", + data: { url: "a.css" }, + line: 4, + column: 1, + endLine: 4, + endColumn: 22, + }, + ], + }, ], }); diff --git a/tests/rules/no-duplicate-keyframe-selectors.test.js b/tests/rules/no-duplicate-keyframe-selectors.test.js index 504aafae..7589db62 100644 --- a/tests/rules/no-duplicate-keyframe-selectors.test.js +++ b/tests/rules/no-duplicate-keyframe-selectors.test.js @@ -51,6 +51,19 @@ ruleTester.run("no-duplicate-keyframe-selectors", rule, { dedent`@keyframes test { 0% { opacity: 0; } 0.0% { opacity: 1; } + }`, + dedent`@KEYFRAMES test { + from { opacity: 0; } + to { opacity: 1; } + }`, + dedent`@KeYFrames test { + 0% { opacity: 0; } + 100% { opacity: 1; } + }`, + dedent`@Keyframes test { + from { opacity: 0; } + 50% { opacity: 0.5; } + to { opacity: 1; } }`, ], invalid: [ @@ -245,5 +258,67 @@ ruleTester.run("no-duplicate-keyframe-selectors", rule, { }, ], }, + { + code: dedent`@KEYFRAMES test { + 0% { opacity: 0; } + 0% { opacity: 1; } + }`, + errors: [ + { + messageId: "duplicateKeyframeSelector", + line: 3, + column: 5, + endLine: 3, + endColumn: 7, + }, + ], + }, + { + code: dedent`@Keyframes test { + 0% { + opacity: 0; + } + + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 50% { + opacity: 0.75; + } + + 50% { + opacity: 0.5; + } + + }`, + errors: [ + { + messageId: "duplicateKeyframeSelector", + line: 6, + column: 5, + endLine: 6, + endColumn: 7, + }, + { + messageId: "duplicateKeyframeSelector", + line: 14, + column: 5, + endLine: 14, + endColumn: 8, + }, + { + messageId: "duplicateKeyframeSelector", + line: 18, + column: 5, + endLine: 18, + endColumn: 8, + }, + ], + }, ], }); diff --git a/tests/rules/use-baseline.test.js b/tests/rules/use-baseline.test.js index 19fb8b4d..c5328ab8 100644 --- a/tests/rules/use-baseline.test.js +++ b/tests/rules/use-baseline.test.js @@ -36,6 +36,9 @@ ruleTester.run("use-baseline", rule, { "@media (min-width: 800px) { a { color: red; } }", "@media (foo) { a { color: red; } }", "@media (prefers-color-scheme: dark) { a { color: red; } }", + "@MEDIA (min-width: 800px) { a { color: red; } }", + "@Media (foo) { 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; } }", @@ -48,6 +51,15 @@ ruleTester.run("use-baseline", rule, { a { accent-color: auto; background-filter: auto } } }`, + "@SUPPORTS (clip-path: fill-box) { a { clip-path: fill-box; } }", + `@Supports (accent-color: auto) and (backdrop-filter: auto) { + a { accent-color: auto; background-filter: auto } + }`, + `@SUPPORTS (accent-color: auto) { + @SuPpOrTs (backdrop-filter: auto) { + a { accent-color: auto; background-filter: auto } + } + }`, `@supports (accent-color: auto) { @supports (accent-color: auto) { a { accent-color: auto; } @@ -190,6 +202,23 @@ ruleTester.run("use-baseline", rule, { }, ], }, + { + code: "@VIEW-TRANSITION { from-view: a; to-view: b; }", + options: [{ available: "newly" }], + errors: [ + { + messageId: "notBaselineAtRule", + data: { + atRule: "VIEW-TRANSITION", + availability: "newly", + }, + line: 1, + column: 1, + endLine: 1, + endColumn: 17, + }, + ], + }, { code: dedent`@supports (accent-color: auto) { @supports (backdrop-filter: auto) { @@ -212,6 +241,28 @@ ruleTester.run("use-baseline", rule, { }, ], }, + { + code: dedent`@SUPPORTS (accent-color: auto) { + @SuPpOrTs (backdrop-filter: auto) { + a { accent-color: red; } + } + + a { backdrop-filter: auto; } + }`, + errors: [ + { + messageId: "notBaselineProperty", + data: { + property: "backdrop-filter", + availability: "widely", + }, + line: 6, + column: 6, + endLine: 6, + endColumn: 21, + }, + ], + }, { code: "@supports (clip-path: fill-box) { a { clip-path: stroke-box; } }", errors: [ @@ -229,6 +280,23 @@ ruleTester.run("use-baseline", rule, { }, ], }, + { + code: "@SUPPORTS (clip-path: fill-box) { a { clip-path: stroke-box; } }", + errors: [ + { + messageId: "notBaselinePropertyValue", + data: { + property: "clip-path", + value: "stroke-box", + availability: "widely", + }, + line: 1, + column: 50, + endLine: 1, + endColumn: 60, + }, + ], + }, { code: "@supports (accent-color: auto) { a { accent-color: abs(20% - 10px); } }", errors: [ @@ -309,6 +377,22 @@ ruleTester.run("use-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" }], diff --git a/tests/rules/use-layers.test.js b/tests/rules/use-layers.test.js index 363500c3..265d6b39 100644 --- a/tests/rules/use-layers.test.js +++ b/tests/rules/use-layers.test.js @@ -35,6 +35,16 @@ ruleTester.run("use-layers", rule, { } } `, + "@LAYER bar { a { color: red; } }", + "@Layer foo { a { color: red; } }", + "@IMPORT 'foo.css' layer(foo);", + dedent` + @media (min-width: 600px) { + @LAYER foo { + a { color: bar } + } + } + `, dedent` @media (min-width: 600px) { @layer foo { @@ -49,6 +59,10 @@ ruleTester.run("use-layers", rule, { code: "@layer { a { color: red; } }", options: [{ allowUnnamedLayers: true }], }, + { + code: "@LAYER { a { color: red; } }", + options: [{ allowUnnamedLayers: true }], + }, { code: "@import 'foo.css' layer;", options: [{ allowUnnamedLayers: true }], @@ -57,10 +71,18 @@ ruleTester.run("use-layers", rule, { code: "@import 'foo.css';", options: [{ requireImportLayers: false }], }, + { + code: "@IMPORT 'foo.css';", + options: [{ requireImportLayers: false }], + }, { code: "@layer foo.bar { a { color: red; } }", options: [{ layerNamePattern: "^(foo|bar)$" }], }, + { + code: "@LAYER foo.bar { a { color: red; } }", + options: [{ layerNamePattern: "^(foo|bar)$" }], + }, { code: "@layer foo.bar.baz { a { color: red; } }", options: [{ layerNamePattern: "^(foo|bar|baz)$" }], @@ -131,6 +153,28 @@ ruleTester.run("use-layers", rule, { }, ], }, + { + code: dedent` + @LAYER { a { color: red; } } + a { color: bar } + `, + errors: [ + { + messageId: "missingLayerName", + line: 1, + column: 1, + endLine: 1, + endColumn: 29, + }, + { + messageId: "missingLayer", + line: 2, + column: 1, + endLine: 2, + endColumn: 17, + }, + ], + }, { code: dedent` @media (min-width: 600px) { @@ -182,6 +226,18 @@ ruleTester.run("use-layers", rule, { }, ], }, + { + code: "@IMPORT 'foo.css';", + errors: [ + { + messageId: "missingImportLayer", + line: 1, + column: 1, + endLine: 1, + endColumn: 19, + }, + ], + }, { code: "@import 'foo.css' layer;", errors: [ @@ -250,6 +306,34 @@ ruleTester.run("use-layers", rule, { }, ], }, + { + code: "@LAYER foo, bar, baz;", + options: [{ layerNamePattern: "bar" }], + errors: [ + { + messageId: "layerNameMismatch", + data: { + name: "foo", + pattern: "bar", + }, + line: 1, + column: 8, + endLine: 1, + endColumn: 11, + }, + { + messageId: "layerNameMismatch", + data: { + name: "baz", + pattern: "bar", + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 21, + }, + ], + }, { code: "@import 'foo.css' layer(baz);", options: [{ layerNamePattern: "bar" }],