Skip to content

Commit 9a4d027

Browse files
nzakasmdjermanovic
andauthored
feat: no-unknown-at-rules rule (#7)
* feat: no-unknown-properties rule * Fix rule description * feat: no-unknown-at-rules rule * Fix test error * Update docs/rules/no-unknown-at-rules.md Co-authored-by: Milos Djermanovic <[email protected]> * Update examples in docs and add test * Update docs/rules/no-unknown-at-rules.md Co-authored-by: Milos Djermanovic <[email protected]> --------- Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 43dec96 commit 9a4d027

File tree

5 files changed

+211
-0
lines changed

5 files changed

+211
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default [
6060
| :--------------------------------------------------------------- | :-------------------------------- | :-------------: |
6161
| [`no-duplicate-imports`](./docs/rules/no-duplicate-imports.md) | Disallow duplicate @import rules. | yes |
6262
| [`no-empty-blocks`](./docs/rules/no-empty-blocks.md) | Disallow empty blocks. | yes |
63+
| [`no-unknown-at-rules`](./docs/rules/no-unknown-at-rules.md) | Disallow unknown at-rules. | yes |
6364
| [`no-unknown-properties`](./docs/rules/no-unknown-properties.md) | Disallow unknown properties. | yes |
6465

6566
<!-- Rule Table End -->

docs/rules/no-unknown-at-rules.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# no-unknown-at-rules
2+
3+
Disallow unknown at-rules.
4+
5+
## Background
6+
7+
CSS contains a number of at-rules, each beginning with a `@`, that perform various operations. Some common at-rules include:
8+
9+
- `@import`
10+
- `@media`
11+
- `@font-face`
12+
- `@keyframes`
13+
- `@supports`
14+
- `@namespace`
15+
- `@page`
16+
- `@charset`
17+
18+
It's important to use a known at-rule because unknown at-rules cause the browser to ignore the entire block, including any rules contained within. For example:
19+
20+
```css
21+
/* typo */
22+
@charse "UTF-8";
23+
```
24+
25+
Here, the `@charset` at-rule is incorrectly spelled as `@charse`, which means that it will be ignored.
26+
27+
## Rule Details
28+
29+
This rule warns when it finds a CSS at-rule that isn't part of the CSS specification. The at-rule data is provided via the [CSSTree](https://github.com/csstree/csstree) project.
30+
31+
Examples of incorrect code:
32+
33+
```css
34+
@charse "UTF-8";
35+
36+
@importx url(foo.css);
37+
38+
@foobar {
39+
.my-style {
40+
color: red;
41+
}
42+
}
43+
```
44+
45+
## When Not to Use It
46+
47+
If you are purposely using at-rules that aren't part of the CSS specification, then you can safely disable this rule.
48+
49+
## Prior Art
50+
51+
- [`at-rule-no-unknown`](https://stylelint.io/user-guide/rules/at-rule-no-unknown)

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CSSSourceCode } from "./languages/css-source-code.js";
1212
import noEmptyBlocks from "./rules/no-empty-blocks.js";
1313
import noDuplicateImports from "./rules/no-duplicate-imports.js";
1414
import noUnknownProperties from "./rules/no-unknown-properties.js";
15+
import noUnknownAtRules from "./rules/no-unknown-at-rules.js";
1516

1617
//-----------------------------------------------------------------------------
1718
// Plugin
@@ -28,6 +29,7 @@ const plugin = {
2829
rules: {
2930
"no-empty-blocks": noEmptyBlocks,
3031
"no-duplicate-imports": noDuplicateImports,
32+
"no-unknown-at-rules": noUnknownAtRules,
3133
"no-unknown-properties": noUnknownProperties,
3234
},
3335
configs: {},
@@ -39,6 +41,7 @@ Object.assign(plugin.configs, {
3941
rules: {
4042
"css/no-empty-blocks": "error",
4143
"css/no-duplicate-imports": "error",
44+
"css/no-unknown-at-rules": "error",
4245
"css/no-unknown-properties": "error",
4346
},
4447
},

src/rules/no-unknown-at-rules.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @fileoverview Rule to prevent the use of unknown at-rules in CSS.
3+
* @author Nicholas C. Zakas
4+
*/
5+
6+
//-----------------------------------------------------------------------------
7+
// Imports
8+
//-----------------------------------------------------------------------------
9+
10+
import data from "css-tree/definition-syntax-data";
11+
12+
//-----------------------------------------------------------------------------
13+
// Helpers
14+
//-----------------------------------------------------------------------------
15+
16+
const knownAtRules = new Set(Object.keys(data.atrules));
17+
18+
//-----------------------------------------------------------------------------
19+
// Rule Definition
20+
//-----------------------------------------------------------------------------
21+
22+
export default {
23+
meta: {
24+
type: "problem",
25+
26+
docs: {
27+
description: "Disallow unknown at-rules.",
28+
recommended: true,
29+
},
30+
31+
messages: {
32+
unknownAtRule: "Unknown at-rule '{{name}}' found.",
33+
},
34+
},
35+
36+
create(context) {
37+
return {
38+
Atrule(node) {
39+
if (!knownAtRules.has(node.name)) {
40+
const loc = node.loc;
41+
42+
context.report({
43+
loc: {
44+
start: loc.start,
45+
end: {
46+
line: loc.start.line,
47+
48+
// add 1 to account for the @ symbol
49+
column: loc.start.column + node.name.length + 1,
50+
},
51+
},
52+
messageId: "unknownAtRule",
53+
data: {
54+
name: node.name,
55+
},
56+
});
57+
}
58+
},
59+
};
60+
},
61+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @fileoverview Tests for no-unknown-at-rules rule.
3+
* @author Nicholas C. Zakas
4+
*/
5+
6+
//------------------------------------------------------------------------------
7+
// Imports
8+
//------------------------------------------------------------------------------
9+
10+
import rule from "../../src/rules/no-unknown-at-rules.js";
11+
import css from "../../src/index.js";
12+
import { RuleTester } from "eslint";
13+
14+
//------------------------------------------------------------------------------
15+
// Tests
16+
//------------------------------------------------------------------------------
17+
18+
const ruleTester = new RuleTester({
19+
plugins: {
20+
css,
21+
},
22+
language: "css/css",
23+
});
24+
25+
ruleTester.run("no-unknown-at-rules", rule, {
26+
valid: [
27+
"@import url('styles.css');",
28+
"@font-face { font-family: 'MyFont'; src: url('myfont.woff2') format('woff2'); }",
29+
"@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }",
30+
"@supports (display: grid) { .grid-container { display: grid; } }",
31+
"@namespace url(http://www.w3.org/1999/xhtml);",
32+
"@media screen and (max-width: 600px) { body { font-size: 12px; } }",
33+
],
34+
invalid: [
35+
{
36+
code: "@foobar { }",
37+
errors: [
38+
{
39+
messageId: "unknownAtRule",
40+
data: { name: "foobar" },
41+
line: 1,
42+
column: 1,
43+
endLine: 1,
44+
endColumn: 8,
45+
},
46+
],
47+
},
48+
{
49+
code: '@charse "test";',
50+
errors: [
51+
{
52+
messageId: "unknownAtRule",
53+
data: { name: "charse" },
54+
line: 1,
55+
column: 1,
56+
endLine: 1,
57+
endColumn: 8,
58+
},
59+
],
60+
},
61+
{
62+
code: "@foobaz { }",
63+
errors: [
64+
{
65+
message: "Unknown at-rule 'foobaz' found.",
66+
line: 1,
67+
column: 1,
68+
endLine: 1,
69+
endColumn: 8,
70+
},
71+
],
72+
},
73+
{
74+
code: "@unknownrule { } @anotherunknown { }",
75+
errors: [
76+
{
77+
messageId: "unknownAtRule",
78+
data: { name: "unknownrule" },
79+
line: 1,
80+
column: 1,
81+
endLine: 1,
82+
endColumn: 13,
83+
},
84+
{
85+
messageId: "unknownAtRule",
86+
data: { name: "anotherunknown" },
87+
line: 1,
88+
column: 18,
89+
endLine: 1,
90+
endColumn: 33,
91+
},
92+
],
93+
},
94+
],
95+
});

0 commit comments

Comments
 (0)