-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
The constructor for the Node class receives a map of default values, and then assigns the values to itself the following way:
Line 52 in 1cc6ac3
| this[name] = defaults[name] |
This pattern is error prone because in JS, property initialization happens after calling the super() constructor. That means that even if a value is set by calling super(defaults) from a child node, the value could then be silently reset to undefined or some other initial value if the property happens to be declared in the subclass.
Example snippet in typescript:
class MyNode extends Node {
private _foo!: string;
get foo() { return _foo }
set foo(f: string) { this._foo = f };
constructor(defaults) {
super(defaults);
}
}
const myNode = new MyNode({foo: 'foo'});
console.log(myNode.foo); // OUTPUT: undefinedWe expected the output to be "foo", but instead it was undefined because the MyNode properties are initialized after calling super(). This is JS behavior according to spec, and it can only be seen when compiling TS using the ES2022 target (or above) and enabling the tsconfig option useDefineForClassFields. The reason TS didn't fail is because TS moved the property initialization prior to calling the constructor, but it fails now that it emits more accurate JS.
The fix for this example using the current PostCSS API in this case is to replace private _foo!: string with declare _foo: string. However, this has two issues. First, it's not ideal to use declare in this case. Second, the error is silent and is only noticeable with thorough testing, which is therefore error prone because it's somewhat easy to miss.
So, I believe the PostCSS API should do something different to keep/set the default values in a different way that doesn't invite the user to write this pattern. Maybe the node super class could just keep an internal copy of the default values inside a JSON property called _defaults, and the getters/setters would just access that?