From 43dec96553557be58039a658e0bb100cd6ae2ab8 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 13 Nov 2024 10:38:50 -0500 Subject: [PATCH] feat: no-unknown-properties rule (#5) * feat: no-unknown-properties rule * Fix rule description * Update docs/rules/no-unknown-properties.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- README.md | 9 +-- docs/rules/no-unknown-properties.md | 40 +++++++++++ src/index.js | 3 + src/rules/no-unknown-properties.js | 62 +++++++++++++++++ tests/rules/no-unknown-properties.test.js | 81 +++++++++++++++++++++++ 5 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 docs/rules/no-unknown-properties.md create mode 100644 src/rules/no-unknown-properties.js create mode 100644 tests/rules/no-unknown-properties.test.js diff --git a/README.md b/README.md index 4d6eb9e..53f6900 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,11 @@ export default [ -| **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 | diff --git a/docs/rules/no-unknown-properties.md b/docs/rules/no-unknown-properties.md new file mode 100644 index 0000000..dd275ee --- /dev/null +++ b/docs/rules/no-unknown-properties.md @@ -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) diff --git a/src/index.js b/src/index.js index 5c150e5..841c4ff 100644 --- a/src/index.js +++ b/src/index.js @@ -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 @@ -27,6 +28,7 @@ const plugin = { rules: { "no-empty-blocks": noEmptyBlocks, "no-duplicate-imports": noDuplicateImports, + "no-unknown-properties": noUnknownProperties, }, configs: {}, }; @@ -37,6 +39,7 @@ Object.assign(plugin.configs, { rules: { "css/no-empty-blocks": "error", "css/no-duplicate-imports": "error", + "css/no-unknown-properties": "error", }, }, }); diff --git a/src/rules/no-unknown-properties.js b/src/rules/no-unknown-properties.js new file mode 100644 index 0000000..9b1b52b --- /dev/null +++ b/src/rules/no-unknown-properties.js @@ -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, + }, + }); + } + }, + }; + }, +}; diff --git a/tests/rules/no-unknown-properties.test.js b/tests/rules/no-unknown-properties.test.js new file mode 100644 index 0000000..8980e2f --- /dev/null +++ b/tests/rules/no-unknown-properties.test.js @@ -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, + }, + ], + }, + ], +});