Skip to content

Commit 26b902c

Browse files
authored
feat: add @namespace validation to no-invalid-at-rule-placement rule (#183)
* feat: add @namespace validation to no-invalid-at-rule-placement rule * feat: add @namespace validation to no-invalid-at-rule-placement rule * clarify docs
1 parent 5678024 commit 26b902c

File tree

3 files changed

+254
-4
lines changed

3 files changed

+254
-4
lines changed

docs/rules/no-invalid-at-rule-placement.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ At-rules are CSS statements that instruct CSS how to behave. Some at-rules have
88

99
- The `@charset` rule must be placed at the very beginning of a stylesheet, before any other rules, comments, or whitespace.
1010
- The `@import` rule must be placed at the beginning of a stylesheet, before any other at-rules (except `@charset` and `@layer` statements) and style rules.
11+
- The `@namespace` rule must be placed after `@charset` and `@import` and before any other at-rules and style rules.
1112

12-
If these rules are placed incorrectly, browsers will ignore them, resulting in potential encoding issues or missing imported styles.
13+
If these rules are placed incorrectly, browsers will ignore them, resulting in potential encoding issues, missing imported styles, or incorrect namespace application.
1314

1415
## Rule Details
1516

1617
This rule warns when it finds:
1718

1819
1. A `@charset` rule that is not the first rule in the stylesheet
1920
2. An `@import` rule that appears after any other at-rules or style rules (except `@charset` and `@layer` statements)
21+
3. A `@namespace` rule that appears before `@charset` or `@import` or after any other at-rules or style rules
2022

2123
Examples of **incorrect** code:
2224

@@ -47,6 +49,25 @@ a {
4749
@import "bar.css";
4850
```
4951

52+
```css
53+
/* eslint css/no-invalid-at-rule-placement: "error" */
54+
55+
/* @namespace after style rules */
56+
a {
57+
color: red;
58+
}
59+
@namespace svg url("http://www.w3.org/2000/svg");
60+
```
61+
62+
```css
63+
/* eslint css/no-invalid-at-rule-placement: "error" */
64+
65+
/* @namespace after @media */
66+
@media (min-width: 600px) {
67+
}
68+
@namespace svg url("http://www.w3.org/2000/svg");
69+
```
70+
5071
Examples of **correct** code:
5172

5273
```css
@@ -85,9 +106,40 @@ a {
85106
}
86107
```
87108

109+
```css
110+
/* eslint css/no-invalid-at-rule-placement: "error" */
111+
112+
/* @namespace before style rules */
113+
@namespace svg url("http://www.w3.org/2000/svg");
114+
a {
115+
color: red;
116+
}
117+
```
118+
119+
```css
120+
/* eslint css/no-invalid-at-rule-placement: "error" */
121+
122+
/* @namespace before @media */
123+
@namespace svg url("http://www.w3.org/2000/svg");
124+
@media (min-width: 600px) {
125+
}
126+
```
127+
128+
```css
129+
/* eslint css/no-invalid-at-rule-placement: "error" */
130+
131+
/* @charset, @import, and @namespace in correct order */
132+
@charset "utf-8";
133+
@import "foo.css";
134+
@namespace svg url("http://www.w3.org/2000/svg");
135+
a {
136+
color: red;
137+
}
138+
```
139+
88140
## When Not to Use It
89141

90-
You can disable this rule if your stylesheets don't use `@charset` or `@import` rules, or if you're not concerned about the impact of incorrect placement on encoding and style loading.
142+
You can disable this rule if your stylesheets don't use `@charset`, `@import`, or `@namespace` rules, or if you're not concerned about the impact of incorrect placement on encoding, namespace usage, or style loading.
91143

92144
## Prior Art
93145

@@ -97,3 +149,4 @@ You can disable this rule if your stylesheets don't use `@charset` or `@import`
97149

98150
- [@charset - CSS | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@charset)
99151
- [@import - CSS | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@import)
152+
- [@namespace - CSS | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@namespace)

src/rules/no-invalid-at-rule-placement.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
/**
1111
* @import { CSSRuleDefinition } from "../types.js"
12-
* @typedef {"invalidCharsetPlacement" | "invalidImportPlacement"} NoInvalidAtRulePlacementMessageIds
12+
* @typedef {"invalidCharsetPlacement" | "invalidImportPlacement" | "invalidNamespacePlacement"} NoInvalidAtRulePlacementMessageIds
1313
* @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoInvalidAtRulePlacementMessageIds }>} NoInvalidAtRulePlacementRuleDefinition
1414
*/
1515

@@ -33,12 +33,16 @@ export default {
3333
"@charset must be placed at the very beginning of the stylesheet, before any rules, comments, or whitespace.",
3434
invalidImportPlacement:
3535
"@import must be placed before all other rules, except @charset and @layer statements.",
36+
invalidNamespacePlacement:
37+
"@namespace must be placed before all other rules, except @charset and @import.",
3638
},
3739
},
3840

3941
create(context) {
4042
let hasSeenNonImportRule = false;
4143
let hasSeenLayerBlock = false;
44+
let hasSeenLayer = false;
45+
let hasSeenNamespace = false;
4246

4347
return {
4448
Atrule(node) {
@@ -61,11 +65,27 @@ export default {
6165
if (node.block) {
6266
hasSeenLayerBlock = true;
6367
}
68+
hasSeenLayer = true;
69+
return;
70+
}
71+
72+
if (name === "namespace") {
73+
if (hasSeenNonImportRule || hasSeenLayer) {
74+
context.report({
75+
node,
76+
messageId: "invalidNamespacePlacement",
77+
});
78+
}
79+
hasSeenNamespace = true;
6480
return;
6581
}
6682

6783
if (name === "import") {
68-
if (hasSeenNonImportRule || hasSeenLayerBlock) {
84+
if (
85+
hasSeenNonImportRule ||
86+
hasSeenNamespace ||
87+
hasSeenLayerBlock
88+
) {
6989
context.report({
7090
node,
7191
messageId: "invalidImportPlacement",

tests/rules/no-invalid-at-rule-placement.test.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ ruleTester.run("no-invalid-at-rule-placement", rule, {
3232
dedent`
3333
@charset "utf-8";
3434
/* comment */
35+
a { color: red; }`,
36+
"@namespace url(http://www.w3.org/1999/xhtml);",
37+
'@namespace svg "http://www.w3.org/2000/svg"',
38+
dedent`
39+
@charset "utf-8";
40+
@namespace url(http://www.w3.org/1999/xhtml);`,
41+
dedent`
42+
@charset "utf-8";
43+
@import "foo.css";
44+
@namespace url(http://www.w3.org/1999/xhtml);`,
45+
dedent`
46+
@charset "utf-8";
47+
@import "foo.css";
48+
@NAMESPACE svg url(http://www.w3.org/2000/svg);
3549
a { color: red; }`,
3650
"@import 'foo.css';",
3751
"@import url('foo.css');",
@@ -267,5 +281,168 @@ ruleTester.run("no-invalid-at-rule-placement", rule, {
267281
},
268282
],
269283
},
284+
{
285+
code: dedent`
286+
a { color: red; }
287+
@namespace url(http://www.w3.org/1999/xhtml);`,
288+
errors: [
289+
{
290+
messageId: "invalidNamespacePlacement",
291+
line: 2,
292+
column: 1,
293+
endLine: 2,
294+
endColumn: 46,
295+
},
296+
],
297+
},
298+
{
299+
code: dedent`
300+
a { color: red; }
301+
@namespace url(http://www.w3.org/1999/xhtml);
302+
@namespace svg url(http://www.w3.org/2000/svg);`,
303+
errors: [
304+
{
305+
messageId: "invalidNamespacePlacement",
306+
line: 2,
307+
column: 1,
308+
endLine: 2,
309+
endColumn: 46,
310+
},
311+
{
312+
messageId: "invalidNamespacePlacement",
313+
line: 3,
314+
column: 1,
315+
endLine: 3,
316+
endColumn: 48,
317+
},
318+
],
319+
},
320+
{
321+
code: dedent`
322+
@charset "utf-8";
323+
@namespace url(http://www.w3.org/1999/xhtml);
324+
a { color: red; }
325+
@namespace svg url(http://www.w3.org/2000/svg);`,
326+
errors: [
327+
{
328+
messageId: "invalidNamespacePlacement",
329+
line: 4,
330+
column: 1,
331+
endLine: 4,
332+
endColumn: 48,
333+
},
334+
],
335+
},
336+
{
337+
code: dedent`
338+
@custom-rule {}
339+
@namespace url(http://www.w3.org/1999/xhtml);
340+
`,
341+
languageOptions: {
342+
customSyntax: {
343+
atrules: {
344+
"custom-rule": {},
345+
},
346+
},
347+
},
348+
errors: [
349+
{
350+
messageId: "invalidNamespacePlacement",
351+
line: 2,
352+
column: 1,
353+
endLine: 2,
354+
endColumn: 46,
355+
},
356+
],
357+
},
358+
{
359+
code: dedent`
360+
@media print { }
361+
@namespace svg url(http://www.w3.org/2000/svg);`,
362+
errors: [
363+
{
364+
messageId: "invalidNamespacePlacement",
365+
line: 2,
366+
column: 1,
367+
endLine: 2,
368+
endColumn: 48,
369+
},
370+
],
371+
},
372+
{
373+
code: dedent`
374+
@layer base;
375+
@namespace url(http://www.w3.org/1999/xhtml);`,
376+
errors: [
377+
{
378+
messageId: "invalidNamespacePlacement",
379+
line: 2,
380+
column: 1,
381+
endLine: 2,
382+
endColumn: 46,
383+
},
384+
],
385+
},
386+
{
387+
code: dedent`
388+
@charset "utf-8";
389+
@namespace url(http://www.w3.org/1999/xhtml);
390+
@import "foo.css";`,
391+
errors: [
392+
{
393+
messageId: "invalidImportPlacement",
394+
line: 3,
395+
column: 1,
396+
endLine: 3,
397+
endColumn: 19,
398+
},
399+
],
400+
},
401+
{
402+
code: dedent`
403+
@namespace url(http://www.w3.org/1999/xhtml);
404+
@charset "utf-8";
405+
a { color: red; }
406+
@import "foo.css";`,
407+
errors: [
408+
{
409+
messageId: "invalidCharsetPlacement",
410+
line: 2,
411+
column: 1,
412+
endLine: 2,
413+
endColumn: 18,
414+
},
415+
{
416+
messageId: "invalidImportPlacement",
417+
line: 4,
418+
column: 1,
419+
endLine: 4,
420+
endColumn: 19,
421+
},
422+
],
423+
},
424+
{
425+
code: dedent`
426+
@namespace url(http://www.w3.org/1999/xhtml);
427+
@charset "utf-8";
428+
@namespace svg url(http://www.w3.org/2000/svg);
429+
@import "foo.css";`,
430+
errors: [
431+
{
432+
messageId: "invalidCharsetPlacement",
433+
line: 2,
434+
column: 1,
435+
endLine: 2,
436+
endColumn: 18,
437+
},
438+
{
439+
messageId: "invalidImportPlacement",
440+
line: 4,
441+
column: 1,
442+
endLine: 4,
443+
endColumn: 19,
444+
},
445+
],
446+
},
270447
],
271448
});

0 commit comments

Comments
 (0)