Skip to content

Commit 932cf62

Browse files
jgozTanujkanti4441
andauthored
feat: add allowUnknownVariables option to no-invalid-properties (#178)
Co-authored-by: Tanuj Kanti <[email protected]>
1 parent d6b2e3a commit 932cf62

File tree

4 files changed

+151
-22
lines changed

4 files changed

+151
-22
lines changed

docs/rules/no-invalid-properties.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,31 @@ body {
4444
}
4545
```
4646

47-
### Limitations
47+
## Options
4848

49-
When a variable is used in a property value, such as `var(--my-color)`, the rule can only properly be validated if the parser has already encountered the `--my-color` custom property. For example, this will validate correctly:
49+
This rule accepts an option which is an object with the following property:
50+
51+
- `allowUnknownVariables` (default: `false`) - Ignore variables that cannot be traced to custom properties in the current file.
52+
53+
When a variable is used in a property value, such as `var(--my-color)`, the rule can only properly be validated if the parser has already encountered the `--my-color` custom property. With `{ allowUnknownVariables: false }`, unknown variables will result in a linting error. With `{ allowUnknownVariables: true }`, the property value will be ignored and only the property name will be validated.
54+
55+
Examples of **incorrect** code with `{ allowUnknownVariables: false }` (the default):
5056

5157
```css
58+
/* eslint css/no-invalid-properties: ["error", { allowUnknownVariables: false }] */
59+
60+
a {
61+
color: var(--my-color);
62+
}
63+
```
64+
65+
This code uses `var(--my-color)` before `--my-color` is defined, or whether it is defined in another CSS file. Therefore, `color: var(--my-color)` cannot be properly validated.
66+
67+
Examples of **correct** code with `{ allowUnknownVariables: false }`:
68+
69+
```css
70+
/* eslint css/no-invalid-properties: ["error", { allowUnknownVariables: false }] */
71+
5272
:root {
5373
--my-color: red;
5474
}
@@ -60,7 +80,29 @@ a {
6080

6181
This code defines `--my-color` before it is used and therefore the rule can validate the `color` property. If `--my-color` was not defined before `var(--my-color)` was used, it results in a lint error because the validation cannot be completed.
6282

63-
If the custom property is defined in another file, it's recommended to create a dummy rule just to ensure proper validation.
83+
Examples of **incorrect** code with `{ allowUnknownVariables: true }`:
84+
85+
```css
86+
/* eslint css/no-invalid-properties: ["error", { allowUnknownVariables: true }] */
87+
88+
a {
89+
ccolorr: var(--my-color);
90+
}
91+
```
92+
93+
This code uses an unknown property `ccolorr`, which results in a validation error. The unknown reference to `var(--my-color)` is ignored.
94+
95+
Examples of **correct** code with `{ allowUnknownVariables: true }`:
96+
97+
```css
98+
/* eslint css/no-invalid-properties: ["error", { allowUnknownVariables: true }] */
99+
100+
a {
101+
color: var(--my-color);
102+
}
103+
```
104+
105+
Even though `var(--my-color)` cannot be traced to a custom property definition, this code passes validation.
64106

65107
## When Not to Use It
66108

src/rules/no-invalid-properties.js

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// Imports
88
//-----------------------------------------------------------------------------
99

10-
import { isSyntaxMatchError } from "../util.js";
10+
import { isSyntaxMatchError, isSyntaxReferenceError } from "../util.js";
1111

1212
//-----------------------------------------------------------------------------
1313
// Type Definitions
@@ -17,7 +17,8 @@ import { isSyntaxMatchError } from "../util.js";
1717
* @import { CSSRuleDefinition } from "../types.js"
1818
* @import { ValuePlain, FunctionNodePlain, CssLocationRange } from "@eslint/css-tree";
1919
* @typedef {"invalidPropertyValue" | "unknownProperty" | "unknownVar"} NoInvalidPropertiesMessageIds
20-
* @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoInvalidPropertiesMessageIds }>} NoInvalidPropertiesRuleDefinition
20+
* @typedef {[{allowUnknownVariables?: boolean}]} NoInvalidPropertiesOptions
21+
* @typedef {CSSRuleDefinition<{ RuleOptions: NoInvalidPropertiesOptions, MessageIds: NoInvalidPropertiesMessageIds }>} NoInvalidPropertiesRuleDefinition
2122
*/
2223

2324
//-----------------------------------------------------------------------------
@@ -72,6 +73,24 @@ export default {
7273
url: "https://github.com/eslint/css/blob/main/docs/rules/no-invalid-properties.md",
7374
},
7475

76+
schema: [
77+
{
78+
type: "object",
79+
properties: {
80+
allowUnknownVariables: {
81+
type: "boolean",
82+
},
83+
},
84+
additionalProperties: false,
85+
},
86+
],
87+
88+
defaultOptions: [
89+
{
90+
allowUnknownVariables: false,
91+
},
92+
],
93+
7594
messages: {
7695
invalidPropertyValue:
7796
"Invalid value '{{value}}' for property '{{property}}'. Expected {{expected}}.",
@@ -95,6 +114,8 @@ export default {
95114
*/
96115
const replacements = [];
97116

117+
const [{ allowUnknownVariables }] = context.options;
118+
98119
return {
99120
"Rule > Block > Declaration"() {
100121
replacements.push(new Map());
@@ -153,7 +174,7 @@ export default {
153174
offsets.forEach(offset => {
154175
varsFoundLocs.set(offset, func.loc);
155176
});
156-
} else {
177+
} else if (!allowUnknownVariables) {
157178
context.report({
158179
loc: func.children[0].loc,
159180
messageId: "unknownVar",
@@ -205,22 +226,27 @@ export default {
205226
return;
206227
}
207228

208-
// unknown property
209-
context.report({
210-
loc: {
211-
start: node.loc.start,
212-
end: {
213-
line: node.loc.start.line,
214-
column:
215-
node.loc.start.column +
216-
node.property.length,
229+
if (
230+
!allowUnknownVariables ||
231+
isSyntaxReferenceError(error)
232+
) {
233+
// unknown property
234+
context.report({
235+
loc: {
236+
start: node.loc.start,
237+
end: {
238+
line: node.loc.start.line,
239+
column:
240+
node.loc.start.column +
241+
node.property.length,
242+
},
243+
},
244+
messageId: "unknownProperty",
245+
data: {
246+
property: node.property,
217247
},
218-
},
219-
messageId: "unknownProperty",
220-
data: {
221-
property: node.property,
222-
},
223-
});
248+
});
249+
}
224250
}
225251
},
226252
};

src/util.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//-----------------------------------------------------------------------------
99

1010
/**
11-
* @import { SyntaxMatchError } from "@eslint/css-tree"
11+
* @import { SyntaxMatchError, SyntaxReferenceError } from "@eslint/css-tree"
1212
*/
1313

1414
//-----------------------------------------------------------------------------
@@ -24,6 +24,15 @@ export function isSyntaxMatchError(error) {
2424
return typeof error.syntax === "string";
2525
}
2626

27+
/**
28+
* Determines if an error is a syntax reference error.
29+
* @param {Object} error The error object to check.
30+
* @returns {error is SyntaxReferenceError} True if the error is a syntax reference error, false if not.
31+
*/
32+
export function isSyntaxReferenceError(error) {
33+
return typeof error.reference === "string";
34+
}
35+
2736
/**
2837
* Finds the line and column offsets for a given offset in a string.
2938
* @param {string} text The text to search.

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ ruleTester.run("no-invalid-properties", rule, {
4747
},
4848
},
4949
},
50+
{
51+
code: "a { color: var(--my-color); }",
52+
options: [{ allowUnknownVariables: true }],
53+
},
54+
{
55+
code: "a { --my-color: red; color: var(--my-color); background-color: var(--unknown-var); }",
56+
options: [{ allowUnknownVariables: true }],
57+
},
5058

5159
/*
5260
* CSSTree doesn't currently support custom functions properly, so leaving
@@ -404,5 +412,49 @@ ruleTester.run("no-invalid-properties", rule, {
404412
},
405413
],
406414
},
415+
{
416+
code: "a { colorr: var(--my-color); }",
417+
options: [{ allowUnknownVariables: true }],
418+
errors: [
419+
{
420+
messageId: "unknownProperty",
421+
data: {
422+
property: "colorr",
423+
},
424+
line: 1,
425+
column: 5,
426+
endLine: 1,
427+
endColumn: 11,
428+
},
429+
],
430+
},
431+
{
432+
code: "a { --my-color: 10px; color: var(--my-color); background_color: var(--unknown-var); }",
433+
options: [{ allowUnknownVariables: true }],
434+
errors: [
435+
{
436+
messageId: "invalidPropertyValue",
437+
data: {
438+
property: "color",
439+
value: "10px",
440+
expected: "<color>",
441+
},
442+
line: 1,
443+
column: 30,
444+
endLine: 1,
445+
endColumn: 45,
446+
},
447+
{
448+
messageId: "unknownProperty",
449+
data: {
450+
property: "background_color",
451+
},
452+
line: 1,
453+
column: 47,
454+
endLine: 1,
455+
endColumn: 63,
456+
},
457+
],
458+
},
407459
],
408460
});

0 commit comments

Comments
 (0)