Skip to content

Commit

Permalink
feat: no-unknown-at-rules rule
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Nov 12, 2024
1 parent 577e832 commit ab0c996
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default [
| :--------------------------------------------------------------- | :-------------------------------- | :-------------: |
| [`no-duplicate-imports`](./docs/rules/no-duplicate-imports.md) | Disallow duplicate @import rules. | yes |
| [`no-empty-blocks`](./docs/rules/no-empty-blocks.md) | Disallow empty blocks. | yes |
| [`no-unknown-at-rules`](./docs/rules/no-unknown-at-rules.md) | Disallow unknown at-rules. | yes |
| [`no-unknown-properties`](./docs/rules/no-unknown-properties.md) | Disallow unknown properties. | yes |

<!-- Rule Table End -->
Expand Down
61 changes: 61 additions & 0 deletions docs/rules/no-unknown-at-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# no-unknown-at-rules

Disallow unknown at-rules.

## Background

CSS contains a number of at-rules, each beginning with a `@`, that perform various operations. Some common at-rules include:

- `@import`
- `@media`
- `@font-face`
- `@keyframes`
- `@supports`
- `@namespace`
- `@page`
- `@charset`
- `@document`
- `@viewport`

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:

```css
/* typo */
@support (display: grid) {
.grid-container {
display: grid;
}
}
```

Here, the `@supports` at-rule is incorrectly spelled as `@support`, which means that the rule for `.grid-container` will be ignored even if the browser supports `display: grid`.

## Rule Details

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.

Examples of incorrect code:

```css
@support (display: grid) {
.grid-container {
display: grid;
}
}

@importx url(foo.css);

@foobar {
.my-style {
color: red;
}
}
```

## When Not to Use It

If you are purposely using at-rules that aren't part of the CSS specification, then you can safely disable this rule.

## Prior Art

- [`at-rule-no-unknown`](https://stylelint.io/user-guide/rules/at-rule-no-unknown)
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CSSSourceCode } from "./languages/css-source-code.js";
import noEmptyBlocks from "./rules/no-empty-blocks.js";
import noDuplicateImports from "./rules/no-duplicate-imports.js";
import noUnknownProperties from "./rules/no-unknown-properties.js";
import noUnknownAtRules from "./rules/no-unknown-at-rules.js";

//-----------------------------------------------------------------------------
// Plugin
Expand All @@ -28,6 +29,7 @@ const plugin = {
rules: {
"no-empty-blocks": noEmptyBlocks,
"no-duplicate-imports": noDuplicateImports,
"no-unknown-at-rules": noUnknownAtRules,
"no-unknown-properties": noUnknownProperties,
},
configs: {},
Expand All @@ -39,6 +41,7 @@ Object.assign(plugin.configs, {
rules: {
"css/no-empty-blocks": "error",
"css/no-duplicate-imports": "error",
"css/no-unknown-at-rules": "error",
"css/no-unknown-properties": "error",
},
},
Expand Down
61 changes: 61 additions & 0 deletions src/rules/no-unknown-at-rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @fileoverview Rule to prevent the use of unknown at-rules in CSS.
* @author Nicholas C. Zakas
*/

//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------

import data from "css-tree/definition-syntax-data";

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const knownAtRules = new Set(Object.keys(data.atrules));

//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------

export default {
meta: {
type: "problem",

docs: {
description: "Disallow unknown at-rules.",
recommended: true,
},

messages: {
unknownAtRule: "Unknown at-rule '{{name}}' found.",
},
},

create(context) {
return {
Atrule(node) {
if (!knownAtRules.has(node.name)) {
const loc = node.loc;

context.report({
loc: {
start: loc.start,
end: {
line: loc.start.line,

// add 1 to account for the @ symbol
column: loc.start.column + node.name.length + 1,
},
},
messageId: "unknownAtRule",
data: {
name: node.name,
},
});
}
},
};
},
};
82 changes: 82 additions & 0 deletions tests/rules/no-unknown-at-rules.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* @fileoverview Tests for no-unknown-at-rules rule.
* @author Nicholas C. Zakas
*/

//------------------------------------------------------------------------------
// Imports
//------------------------------------------------------------------------------

import rule from "../../src/rules/no-unknown-at-rules.js";
import css from "../../src/index.js";
import { RuleTester } from "eslint";

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
plugins: {
css,
},
language: "css/css",
});

ruleTester.run("no-unknown-at-rules", rule, {
valid: [
"@import url('styles.css');",
"@font-face { font-family: 'MyFont'; src: url('myfont.woff2') format('woff2'); }",
"@keyframes slidein { from { transform: translateX(0%); } to { transform: translateX(100%); } }",
"@supports (display: grid) { .grid-container { display: grid; } }",
"@namespace url(http://www.w3.org/1999/xhtml);",
"@media screen and (max-width: 600px) { body { font-size: 12px; } }",
],
invalid: [
{
code: "@foobar { }",
errors: [
{
messageId: "unknownAtRule",
data: { name: "foobar" },
line: 1,
column: 1,
endLine: 1,
endColumn: 8,
},
],
},
{
code: "@foobaz { }",
errors: [
{
message: "Unknown @ rule 'foobaz' found.",
line: 1,
column: 1,
endLine: 1,
endColumn: 8,
},
],
},
{
code: "@unknownrule { } @anotherunknown { }",
errors: [
{
messageId: "unknownAtRule",
data: { name: "unknownrule" },
line: 1,
column: 1,
endLine: 1,
endColumn: 13,
},
{
messageId: "unknownAtRule",
data: { name: "anotherunknown" },
line: 1,
column: 18,
endLine: 1,
endColumn: 33,
},
],
},
],
});

0 comments on commit ab0c996

Please sign in to comment.