Skip to content

Commit 93d65f3

Browse files
nzakasfasttimemdjermanovic
authored
feat: CSS variable tracking (#136)
* feat: CSS variable tracking * Add TypeScript types to clarify new features * Add @Property, switch customProperties to private * Update designs/2025-css-vars-tracking/README.md Co-authored-by: Francesco Trotta <[email protected]> * Update designs/2025-css-vars-tracking/README.md Co-authored-by: Francesco Trotta <[email protected]> * Answer open questions * Update getClosestVariableValue() definition * Update getVariableValues() definition * Mention Raw | Value for dealing with custom property values * Update designs/2025-css-vars-tracking/README.md Co-authored-by: Milos Djermanovic <[email protected]> * Update designs/2025-css-vars-tracking/README.md Co-authored-by: Milos Djermanovic <[email protected]> * Update designs/2025-css-vars-tracking/README.md Co-authored-by: Milos Djermanovic <[email protected]> --------- Co-authored-by: Francesco Trotta <[email protected]> Co-authored-by: Milos Djermanovic <[email protected]>
1 parent c3c4106 commit 93d65f3

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
- Repo: eslint/css
2+
- Start Date: 2025-06-24
3+
- RFC PR: https://github.com/eslint/rfcs/pull/136
4+
- Authors: Nicholas C. Zakas
5+
6+
# CSS Custom Property Tracking in SourceCode
7+
8+
## Summary
9+
10+
This RFC proposes to add capabilities to `CSSSourceCode` to track CSS custom properties (also known as CSS variables). This includes identifying where custom properties are defined and where they are used, as well as providing a way to retrieve the value of a custom property at a specific location in the code.
11+
12+
## Motivation
13+
14+
CSS custom properties are a foundational part of modern CSS development. They allow for more modular and maintainable stylesheets. For a CSS linter to provide accurate and helpful rules, it needs to be able to understand how custom properties are being used. Currently, any rule that needs to validate property values must implement its own logic for tracking custom properties, leading to duplicated effort and potential inconsistencies.
15+
16+
By building this functionality directly into `CSSSourceCode`, we can provide a consistent and reliable way for all rules to access information about custom properties. Uses of this information include:
17+
18+
* `no-invalid-properties` rule
19+
* `font-family-fallbacks` rule
20+
* Detecting references to undefined custom properties.
21+
* Future: Detecting unused custom properties.
22+
23+
This change is based on the discussion in [eslint/css#160](https://github.com/eslint/css/issues/160).
24+
25+
**Note:** The logic described in this RFC is already implemented in the [`no-invalid-properties`](https://github.com/eslint/css/blob/main/docs/rules/no-invalid-properties.md) rule. This proposal wants to standardize the logic and make it available to all rules.
26+
27+
## Detailed Design
28+
29+
The proposed changes are implemented in the [`CSSSourceCode`](https://github.com/eslint/css/blob/main/src/languages/css-source-code.js) class.
30+
31+
### `customProperties` Map
32+
33+
```ts
34+
interface CustomPropertyUses {
35+
declarations: Array<DeclarationPlain>;
36+
definitions: Array<AtrulePlain>;
37+
references: Array<FunctionNode>;
38+
}
39+
40+
interface CSSSourceCode {
41+
#customProperties: Map<string, CustomPropertyUses>
42+
}
43+
```
44+
45+
A new private property, `#customProperties`, will be added to `CSSSourceCode`. This will be a `Map` where the keys are the custom property names (e.g., `--my-color`) and the values are `CustomPropertyUses` objects. The `CustomPropertyUses` class will have three properties:
46+
47+
* `declarations`: An array of `DeclarationPlain` nodes where the custom property value is declared.
48+
* `definitions`: Array of `AtrulePlain` nodes where the custom property is defined using an [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/@property) rule.
49+
* `references`: An array of `FunctionNode` nodes (specifically `var()` functions) where the custom property is used.
50+
51+
### `getDeclarationVariables()` Method
52+
53+
```ts
54+
interface CSSSourceCode {
55+
getDeclarationVariables(declaration: DeclarationPlain): Array<FunctionNode>;
56+
}
57+
```
58+
59+
A new public method, `getDeclarationVariables(declaration)`, will be added to `CSSSourceCode`. This method will take a `Declaration` node as an argument and return an array of `Function` nodes representing the `var()` functions used in that declaration's value.
60+
61+
### `getClosestVariableValue()` Method
62+
63+
```ts
64+
interface CSSSourceCode {
65+
getClosestVariableValue(func: FunctionNode): Value | Raw | undefined;
66+
}
67+
```
68+
69+
A new public method, `getClosestVariableValue(node)`, will be added to `CSSSourceCode`. This method will take a `var()` `Function` node as an argument and return the computed value of the custom property. It will do this by:
70+
71+
1. If the current rule block has one or more custom properties declaration for the variable, then return the value of the last custom property declaration in the block. This mimics the way CSS calculates custom property values.
72+
2. If `var()` has a fallback value, return the fallback value.
73+
3. If one of the previous rules had a custom property declaration, then return the last value of the custom property.
74+
4. If there's a `@property` block for the custom property that has an `initial-value`, return the `initial-value`.
75+
5. Otherwise, return `undefined`.
76+
77+
### `getVariableValues()` Method
78+
79+
```ts
80+
interface CSSSourceCode {
81+
getVariableValues(func: FunctionNode): Array<Raw | Value>;
82+
}
83+
```
84+
85+
A new public method, `getVariableValues(func)`, will be added to `CSSSourceCode`. This method will take a `var()` `FunctionNode` as an argument and return an array of nodes representing the declared values of the custom property. The returned array is made up of the following:
86+
87+
1. If there is a `@property` for the custom property that has an `initial-value`, then the `initial-value` is the first element in the array (a `Value` node).
88+
2. The `Raw` values defined in custom property declarations throughout the file, both before and after the `FunctionNode` come next in source order.
89+
3. The fallback value, if specified in the `FunctionNode`, is returned as the last element of the array.
90+
91+
### Initialization
92+
93+
The `traverse()` method in `CSSSourceCode` will be updated to populate the `#customProperties` map and the internal data structure (`WeakMap<DeclarationPlain, Array<FunctionNode>>`) used by `getDeclarationVariables()`. During traversal, it will identify `Declaration` nodes that define custom properties and `Function` nodes that are `var()` calls.
94+
95+
## Documentation
96+
97+
Because we don't provide documentation for `CSSSourceCode`, we will rely primarily on TypeScript types to inform rule developers as to these new class members.
98+
99+
## Drawbacks
100+
101+
The primary drawback of this approach is that it only tracks custom properties within a single file. It cannot resolve custom properties that are defined in other files or via mechanisms like inline styles on HTML elements. For the initial implementation, this is considered an acceptable tradeoff.
102+
103+
## Backwards Compatibility Analysis
104+
105+
This is a new feature and does not change any existing APIs. It is fully backwards compatible.
106+
107+
## Alternatives
108+
109+
The primary alternative is to have each rule implement its own custom property tracking logic. This is the current state of affairs and is what this RFC aims to improve upon. A centralized approach is more efficient and less error-prone.
110+
111+
## Open Questions
112+
113+
n/a
114+
115+
## Help Needed
116+
117+
No help is needed to implement this RFC.
118+
119+
## Frequently Asked Questions
120+
121+
**Why does `getVariableValue()` return `Raw | Value` instead of a string?**
122+
123+
The `getVariableValue()` method returns `Raw | Value` instead of a string to preserve the original source information and maintain consistency with the AST structure. A `Raw` or `Value` node contains not only the text value but also the location, which is valuable for rules that need to report issues or apply fixes at specific locations in the source code. Additionally, returning the actual AST node allows for future extensibility. If we later need to return more complex computed values or support different node types, the API won't need to change.
124+
125+
## Related Discussions
126+
127+
- [Change Request: Track variables on SourceCode #160](https://github.com/eslint/css/issues/160)

0 commit comments

Comments
 (0)