diff --git a/README.md b/README.md index 3cae642..b3ef125 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ 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 | - +| [`no-empty-blocks`](./docs/rules/no-empty-blocks.md) | Disallow empty blocks. | yes | +| [`no-unknown-properties`](./docs/rules/no-unknown-properties.md) | Disallow empty blocks. | yes | **Note:** This plugin does not provide formatting rules. We recommend using a source code formatter such as [Prettier](https://prettier.io) for that purpose. diff --git a/docs/rules/no-unknown-properties.md b/docs/rules/no-unknown-properties.md new file mode 100644 index 0000000..954b47e --- /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 `--` and 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..b0fa1cf --- /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 empty blocks.", + 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, + }, + ], + }, + ], +});