Skip to content

Commit 9b80bdd

Browse files
nzakasmdjermanovic
andauthored
feat: no-invalid-properties rule (#11)
* 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]>
1 parent 4c0761b commit 9b80bdd

12 files changed

+1353
-188
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ export default [
6060
| :--------------------------------------------------------------- | :-------------------------------- | :-------------: |
6161
| [`no-duplicate-imports`](./docs/rules/no-duplicate-imports.md) | Disallow duplicate @import rules. | yes |
6262
| [`no-empty-blocks`](./docs/rules/no-empty-blocks.md) | Disallow empty blocks. | yes |
63+
| [`no-invalid-properties`](./docs/rules/no-invalid-properties.md) | Disallow invalid properties. | yes |
6364
| [`no-unknown-at-rules`](./docs/rules/no-unknown-at-rules.md) | Disallow unknown at-rules. | yes |
64-
| [`no-unknown-properties`](./docs/rules/no-unknown-properties.md) | Disallow unknown properties. | yes |
6565

6666
<!-- Rule Table End -->
6767

docs/rules/no-invalid-properties.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# no-invalid-properties
2+
3+
Disallow invalid properties.
4+
5+
## Background
6+
7+
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:
8+
9+
```css
10+
a {
11+
display: black;
12+
}
13+
```
14+
15+
The property `display` doesn't accept a color for its value, so while this code will parse without error, it's still invalid CSS.
16+
17+
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:
18+
19+
```css
20+
a {
21+
ccolor: black;
22+
}
23+
```
24+
25+
Here, `ccolor` is a syntactically valid identifier even though it will be ignored by browsers. Such errors are often caused by typos.
26+
27+
## Rule Details
28+
29+
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.
30+
31+
Examples of incorrect code:
32+
33+
```css
34+
a {
35+
display: black;
36+
ccolor: black;
37+
}
38+
39+
body {
40+
overflow: 100%;
41+
bg: red;
42+
}
43+
```
44+
45+
## When Not to Use It
46+
47+
If you aren't concerned with invalid properties, then you can safely disable this rule.
48+
49+
## Prior Art
50+
51+
- [`declaration-property-value-no-unknown`](https://stylelint.io/user-guide/rules/declaration-property-value-no-unknown/)
52+
53+
- [`property-no-unknown`](https://stylelint.io/user-guide/rules/property-no-unknown)

docs/rules/no-unknown-properties.md

-40
This file was deleted.

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
"devDependencies": {
7575
"@eslint/core": "^0.6.0",
7676
"@eslint/json": "^0.5.0",
77-
"@types/css-tree": "^2.3.8",
7877
"@types/eslint": "^8.56.10",
7978
"c8": "^9.1.0",
8079
"dedent": "^1.5.3",

src/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { CSSLanguage } from "./languages/css-language.js";
1111
import { CSSSourceCode } from "./languages/css-source-code.js";
1212
import noEmptyBlocks from "./rules/no-empty-blocks.js";
1313
import noDuplicateImports from "./rules/no-duplicate-imports.js";
14-
import noUnknownProperties from "./rules/no-unknown-properties.js";
1514
import noUnknownAtRules from "./rules/no-unknown-at-rules.js";
15+
import noInvalidProperties from "./rules/no-invalid-properties.js";
1616

1717
//-----------------------------------------------------------------------------
1818
// Plugin
@@ -30,7 +30,7 @@ const plugin = {
3030
"no-empty-blocks": noEmptyBlocks,
3131
"no-duplicate-imports": noDuplicateImports,
3232
"no-unknown-at-rules": noUnknownAtRules,
33-
"no-unknown-properties": noUnknownProperties,
33+
"no-invalid-properties": noInvalidProperties,
3434
},
3535
configs: {},
3636
};
@@ -42,7 +42,7 @@ Object.assign(plugin.configs, {
4242
"css/no-empty-blocks": "error",
4343
"css/no-duplicate-imports": "error",
4444
"css/no-unknown-at-rules": "error",
45-
"css/no-unknown-properties": "error",
45+
"css/no-invalid-properties": "error",
4646
},
4747
},
4848
});

src/rules/no-invalid-properties.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @fileoverview Rule to prevent invalid properties in CSS.
3+
* @author Nicholas C. Zakas
4+
*/
5+
6+
//-----------------------------------------------------------------------------
7+
// Imports
8+
//-----------------------------------------------------------------------------
9+
10+
import { lexer } from "css-tree";
11+
12+
//-----------------------------------------------------------------------------
13+
// Type Definitions
14+
//-----------------------------------------------------------------------------
15+
16+
/** @typedef {import("css-tree").SyntaxMatchError} SyntaxMatchError */
17+
18+
//-----------------------------------------------------------------------------
19+
// Helpers
20+
//-----------------------------------------------------------------------------
21+
22+
/**
23+
* Determines if an error is a syntax match error.
24+
* @param {Object} error The error object from the CSS parser.
25+
* @returns {error is SyntaxMatchError} True if the error is a syntax match error, false if not.
26+
*/
27+
function isSyntaxMatchError(error) {
28+
return typeof error.css === "string";
29+
}
30+
31+
//-----------------------------------------------------------------------------
32+
// Rule Definition
33+
//-----------------------------------------------------------------------------
34+
35+
export default {
36+
meta: {
37+
type: "problem",
38+
39+
docs: {
40+
description: "Disallow invalid properties.",
41+
recommended: true,
42+
},
43+
44+
messages: {
45+
invalidPropertyValue:
46+
"Invalid value '{{value}}' for property '{{property}}'. Expected {{expected}}.",
47+
unknownProperty: "Unknown property '{{property}}' found.",
48+
},
49+
},
50+
51+
create(context) {
52+
return {
53+
"Rule > Block > Declaration"(node) {
54+
// don't validate custom properties
55+
if (node.property.startsWith("--")) {
56+
return;
57+
}
58+
59+
const { error } = lexer.matchDeclaration(node);
60+
61+
if (error) {
62+
// validation failure
63+
if (isSyntaxMatchError(error)) {
64+
context.report({
65+
loc: error.loc,
66+
messageId: "invalidPropertyValue",
67+
data: {
68+
property: node.property,
69+
value: error.css,
70+
expected: error.syntax,
71+
},
72+
});
73+
return;
74+
}
75+
76+
// unknown property
77+
context.report({
78+
loc: {
79+
start: node.loc.start,
80+
end: {
81+
line: node.loc.start.line,
82+
column:
83+
node.loc.start.column +
84+
node.property.length,
85+
},
86+
},
87+
messageId: "unknownProperty",
88+
data: {
89+
property: node.property,
90+
},
91+
});
92+
}
93+
},
94+
};
95+
},
96+
};

src/rules/no-unknown-properties.js

-62
This file was deleted.

0 commit comments

Comments
 (0)