Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: no-unknown-properties rule #5

Merged
merged 3 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ export default [

<!-- Rule Table Start -->

| **Rule Name** | **Description** | **Recommended** |
| :------------------------------------------------------------- | :-------------------------------- | :-------------: |
| [`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 |
| **Rule Name** | **Description** | **Recommended** |
| :--------------------------------------------------------------- | :-------------------------------- | :-------------: |
| [`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-properties`](./docs/rules/no-unknown-properties.md) | Disallow unknown properties. | yes |

<!-- Rule Table End -->

Expand Down
40 changes: 40 additions & 0 deletions docs/rules/no-unknown-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# no-unknown-properties

Disallow unknown properties.

## Background

CSS rules may contain any number of properties consisting of a name and a value. As long as the property name is valid CSS, it will parse correctly, even if the property won't be recognized by a web browser. For example:

```css
a {
ccolor: black;
}
```

Here, `ccolor` is a syntactically valid identifier even though it will be ignored by browsers. Such errors are often caused by typos.

## Rule Details

This rule warns when it finds a CSS property that isn't part of the CSS specification and aren't custom properties (beginning with `--` as in `--my-color`). The property data is provided via the [CSSTree](https://github.com/csstree/csstree) project.

Examples of incorrect code:

```css
a {
ccolor: black;
}

body {
bg: red;
}
```

## When Not to Use It

If you aren't concerned with unknown properties, you can safely disable this rule.

## Prior Art

- [`known-properties`](https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties)
- [`property-no-unknown`](https://stylelint.io/user-guide/rules/property-no-unknown)
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CSSLanguage } from "./languages/css-language.js";
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";

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

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

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

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

const knownProperties = new Set(Object.keys(data.properties));

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

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

docs: {
description: "Disallow unknown properties.",
recommended: true,
},

messages: {
unknownProperty: "Unknown property '{{property}}' found.",
},
},

create(context) {
return {
Declaration(node) {
if (
!node.property.startsWith("--") &&
!knownProperties.has(node.property)
) {
const loc = node.loc;

context.report({
loc: {
start: loc.start,
end: {
line: loc.start.line,
column: loc.start.column + node.property.length,
},
},
messageId: "unknownProperty",
data: {
property: node.property,
},
});
}
},
};
},
};
81 changes: 81 additions & 0 deletions tests/rules/no-unknown-properties.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @fileoverview Tests for no-unknown-properties rule.
* @author Nicholas C. Zakas
*/

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

import rule from "../../src/rules/no-unknown-properties.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-properties", rule, {
valid: [
"a { color: red; }",
"a { color: red; background-color: blue; }",
"a { color: red; transition: none; }",
"body { --custom-property: red; }",
],
invalid: [
{
code: "a { foo: bar }",
errors: [
{
messageId: "unknownProperty",
data: { property: "foo" },
line: 1,
column: 5,
endLine: 1,
endColumn: 8,
},
],
},
{
code: "a { color: red; -moz-transition: bar }",
errors: [
{
messageId: "unknownProperty",
data: { property: "-moz-transition" },
line: 1,
column: 17,
endLine: 1,
endColumn: 32,
},
],
},
{
code: "a { my-color: red; -webkit-transition: bar }",
errors: [
{
messageId: "unknownProperty",
data: { property: "my-color" },
line: 1,
column: 5,
endLine: 1,
endColumn: 13,
},
{
messageId: "unknownProperty",
data: { property: "-webkit-transition" },
line: 1,
column: 20,
endLine: 1,
endColumn: 38,
},
],
},
],
});