Skip to content

Commit

Permalink
feat!: no-unknown-at-rule -> no-invalid-at-rule
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Nov 19, 2024
1 parent 9a4d027 commit ad53575
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 117 deletions.
80 changes: 80 additions & 0 deletions docs/rules/no-invalid-at-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# no-invalid-at-rules

Disallow invalid 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`

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 */
@charse "UTF-8";
```

Here, the `@charset` at-rule is incorrectly spelled as `@charse`, which means that it will be ignored.

Each at-rule also has a defined prelude (which may be empty) and potentially one or more descriptors. For example:

```css
@property --main-bg-color {
syntax: "<color>";
inherits: false;
initial-value: #000000;
}
```

Here, `--main-bg-color` is the prelude for `@property` while `syntax`, `inherits`, and `initial-value` are descriptors. The `@property` at-rule requires a specific format for its prelude and only specific descriptors to be present. If any of these are incorrect, the browser ignores the at-rule.

## Rule Details

This rule warns when it finds a CSS at-rule that is unknown or invalid according to the CSS specification. As such, the rule warns for the following problems:

- An unknown at-rule
- An invalid prelude for a known at-rule
- An unknown descriptor for a known at-rule
- An invalid descriptor value for a known at-rule

The at-rule data is provided via the [CSSTree](https://github.com/csstree/csstree) project.

Examples of incorrect code:

```css
@charse "UTF-8";

@importx url(foo.css);

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

@property main-bg-color {
syntax: "<color>";
inherits: false;
initial-value: #000000;
}

@property --main-bg-color {
syntax: 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)
51 changes: 0 additions & 51 deletions docs/rules/no-unknown-at-rules.md

This file was deleted.

2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +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";
import noUnknownAtRules from "./rules/no-invalid-at-rules.js";

//-----------------------------------------------------------------------------
// Plugin
Expand Down
126 changes: 126 additions & 0 deletions src/rules/no-invalid-at-rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* @fileoverview Rule to prevent the use of unknown at-rules in CSS.
* @author Nicholas C. Zakas
*/

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

import { lexer } from "css-tree";

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

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

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

messages: {
unknownAtRule: "Unknown at-rule '@{{name}}' found.",
invalidPrelude:
"Invalid prelude '{{prelude}}' found for at-rule '@{{name}}'. Expected '{{expected}}'.",
unknownDescriptor:
"Unknown descriptor '{{descriptor}}' found for at-rule '@{{name}}'.",
invalidDescriptor:
"Invalid value '{{value}}' for descriptor '{{descriptor}}' found for at-rule '@{{name}}'. Expected {{expected}}.",
},
},

create(context) {
const { sourceCode } = context;

return {
Atrule(node) {
// checks both name and prelude
const { error } = lexer.matchAtrulePrelude(
node.name,
node.prelude,
);

if (error) {
if (error.reference) {
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,
},
});
} else {
context.report({
loc: error.loc,
messageId: "invalidPrelude",
data: {
name: node.name,
prelude: error.css,
expected: error.syntax,
},
});
}
}
},

"AtRule > Block > Declaration"(node) {
// get at rule node
const atRule = sourceCode.getParent(sourceCode.getParent(node));

const { error } = lexer.matchAtruleDescriptor(
atRule.name,
node.property,
node.value,
);

if (error) {
if (error.reference) {
const loc = node.loc;

context.report({
loc: {
start: loc.start,
end: {
line: loc.start.line,
column:
loc.start.column + node.property.length,
},
},
messageId: "unknownDescriptor",
data: {
name: atRule.name,
descriptor: error.reference,
},
});
} else {
context.report({
loc: error.loc,
messageId: "invalidDescriptor",
data: {
name: atRule.name,
descriptor: node.property,
value: error.css,
expected: error.syntax,
},
});
}
}
},
};
},
};
61 changes: 0 additions & 61 deletions src/rules/no-unknown-at-rules.js

This file was deleted.

Loading

0 comments on commit ad53575

Please sign in to comment.