Skip to content

Commit

Permalink
feat: no-invalid-properties rule (#11)
Browse files Browse the repository at this point in the history
* feat: no-invalid-property-values rule

* Fix README format

* Fix typings errors

* Fix tests

* feat: no-invalid-properties rule

* Update docs

* Ignore descriptor declarations

* Update tests/rules/no-invalid-properties.test.js

Co-authored-by: Milos Djermanovic <[email protected]>

* Update tests/rules/no-invalid-properties.test.js

Co-authored-by: Milos Djermanovic <[email protected]>

* Update docs/rules/no-invalid-properties.md

Co-authored-by: Milos Djermanovic <[email protected]>

---------

Co-authored-by: Milos Djermanovic <[email protected]>
  • Loading branch information
nzakas and mdjermanovic authored Nov 20, 2024
1 parent 4c0761b commit 9b80bdd
Show file tree
Hide file tree
Showing 12 changed files with 1,353 additions and 188 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ 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-invalid-properties`](./docs/rules/no-invalid-properties.md) | Disallow invalid properties. | 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
53 changes: 53 additions & 0 deletions docs/rules/no-invalid-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# no-invalid-properties

Disallow invalid properties.

## Background

CSS has a defined set of known properties that are expected to have specific values. While CSS may parse correctly, that doesn't mean that the properties are correctly matched with their values. For example, the following is syntactically valid CSS code:

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

The property `display` doesn't accept a color for its value, so while this code will parse without error, it's still invalid CSS.

Similarly, as long as a property name is syntactically valid, it will parse without error even if it's not a known property. 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 doesn't exist or a value that doesn't match with the property name in the CSS specification (custom properties such as `--my-color` are ignored). The property data is provided via the [CSSTree](https://github.com/csstree/csstree) project.

Examples of incorrect code:

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

body {
overflow: 100%;
bg: red;
}
```

## When Not to Use It

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

## Prior Art

- [`declaration-property-value-no-unknown`](https://stylelint.io/user-guide/rules/declaration-property-value-no-unknown/)

- [`property-no-unknown`](https://stylelint.io/user-guide/rules/property-no-unknown)
40 changes: 0 additions & 40 deletions docs/rules/no-unknown-properties.md

This file was deleted.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"devDependencies": {
"@eslint/core": "^0.6.0",
"@eslint/json": "^0.5.0",
"@types/css-tree": "^2.3.8",
"@types/eslint": "^8.56.10",
"c8": "^9.1.0",
"dedent": "^1.5.3",
Expand Down
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ 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";
import noUnknownAtRules from "./rules/no-unknown-at-rules.js";
import noInvalidProperties from "./rules/no-invalid-properties.js";

//-----------------------------------------------------------------------------
// Plugin
Expand All @@ -30,7 +30,7 @@ const plugin = {
"no-empty-blocks": noEmptyBlocks,
"no-duplicate-imports": noDuplicateImports,
"no-unknown-at-rules": noUnknownAtRules,
"no-unknown-properties": noUnknownProperties,
"no-invalid-properties": noInvalidProperties,
},
configs: {},
};
Expand All @@ -42,7 +42,7 @@ Object.assign(plugin.configs, {
"css/no-empty-blocks": "error",
"css/no-duplicate-imports": "error",
"css/no-unknown-at-rules": "error",
"css/no-unknown-properties": "error",
"css/no-invalid-properties": "error",
},
},
});
Expand Down
96 changes: 96 additions & 0 deletions src/rules/no-invalid-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @fileoverview Rule to prevent invalid properties in CSS.
* @author Nicholas C. Zakas
*/

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

import { lexer } from "css-tree";

//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------

/** @typedef {import("css-tree").SyntaxMatchError} SyntaxMatchError */

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

/**
* Determines if an error is a syntax match error.
* @param {Object} error The error object from the CSS parser.
* @returns {error is SyntaxMatchError} True if the error is a syntax match error, false if not.
*/
function isSyntaxMatchError(error) {
return typeof error.css === "string";
}

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

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

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

messages: {
invalidPropertyValue:
"Invalid value '{{value}}' for property '{{property}}'. Expected {{expected}}.",
unknownProperty: "Unknown property '{{property}}' found.",
},
},

create(context) {
return {
"Rule > Block > Declaration"(node) {
// don't validate custom properties
if (node.property.startsWith("--")) {
return;
}

const { error } = lexer.matchDeclaration(node);

if (error) {
// validation failure
if (isSyntaxMatchError(error)) {
context.report({
loc: error.loc,
messageId: "invalidPropertyValue",
data: {
property: node.property,
value: error.css,
expected: error.syntax,
},
});
return;
}

// unknown property
context.report({
loc: {
start: node.loc.start,
end: {
line: node.loc.start.line,
column:
node.loc.start.column +
node.property.length,
},
},
messageId: "unknownProperty",
data: {
property: node.property,
},
});
}
},
};
},
};
62 changes: 0 additions & 62 deletions src/rules/no-unknown-properties.js

This file was deleted.

Loading

0 comments on commit 9b80bdd

Please sign in to comment.