You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A reactive property listed in `static properties = { … }` MUST be
typed via `declare propName: Type` and have its default set inside
`constructor()`. A plain class-field initializer
(`propName = value` or `propName: Type = value`) compiles under
modern class-field semantics to `Object.defineProperty(this, …)`
*after* super(), which uses [[Define]] semantics and overwrites the
reactive accessor the framework installed in `_initializeProperties`.
The reactive contract is then silently broken — `this.propName = x`
no longer goes through the setter, so no `requestUpdate`, no reflect,
no `hasChanged`.
The rule extracts each `class … extends WebComponent { … }` body
(brace-counted, comment- and string-aware), reads the `static
properties` keys, and flags any class-field initializer at the top
level of the body (depth 0) whose name matches one of those keys.
`this.x = …` inside methods is ignored (depth > 0). Non-reactive
helper fields whose names are NOT in `static properties` (sockets,
counters, refs) are ignored.
Tests cover: bug detected on typed initializer, bug detected on
untyped initializer, declare + constructor passes, non-reactive
fields ignored, `this.x = …` inside methods ignored. 5 new tests.
Copy file name to clipboardExpand all lines: packages/server/src/check.js
+223Lines changed: 223 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -72,6 +72,11 @@ export const RULES = [
72
72
description:
73
73
'Static tag = \'...\' in component files must contain a hyphen (HTML custom element spec).',
74
74
},
75
+
{
76
+
name: 'reactive-props-use-declare',
77
+
description:
78
+
'Reactive properties listed in `static properties = { … }` must be typed with `declare propName: Type` (no value), and have their default set in `constructor()`. Plain class-field initializers (`prop = value` or `prop: Type = value`) compile to Object.defineProperty *after* super() under modern class-field semantics, clobbering the framework\'s reactive accessor and silently breaking re-renders.',
79
+
},
75
80
];
76
81
77
82
/** Set of all known rule names for fast lookup. */
@@ -203,6 +208,205 @@ function countExportedFunctions(content) {
203
208
returnseen.size;
204
209
}
205
210
211
+
/**
212
+
* Extract the body of every `class … extends WebComponent { … }` block.
213
+
* Brace-counts to handle nested template literals, methods, and arrow
214
+
* functions. String state is tracked so braces inside strings/templates
message: `Reactive prop \`${bad}\` uses a class-field initializer; this clobbers the framework's reactive accessor under modern class-field semantics.`,
545
+
fix: `Replace with \`declare ${bad}: <Type>;\` and set the default inside \`constructor()\` after \`super()\`.`,
0 commit comments