@wsxjs/eslint-plugin-wsx 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,11 +58,20 @@ export default [
58
58
  },
59
59
  rules: {
60
60
  ...typescript.configs.recommended.rules,
61
+ "@typescript-eslint/no-explicit-any": "warn",
62
+ "@typescript-eslint/no-unused-vars": [
63
+ "error",
64
+ {
65
+ argsIgnorePattern: "^_",
66
+ varsIgnorePattern: "^_",
67
+ },
68
+ ],
61
69
  // WSX plugin rules
62
70
  "wsx/render-method-required": "error",
63
71
  "wsx/no-react-imports": "error",
64
72
  "wsx/web-component-naming": "warn",
65
73
  "wsx/state-requires-initial-value": "error",
74
+ "no-undef": "off", // TypeScript handles this
66
75
  },
67
76
  },
68
77
  ];
@@ -76,6 +85,8 @@ Make sure you have these peer dependencies installed:
76
85
  npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser globals
77
86
  ```
78
87
 
88
+ **Important**: The `experimentalDecorators: true` option in `parserOptions` is **required** for the `wsx/state-requires-initial-value` rule to work correctly. Without it, ESLint cannot parse `@state` decorators and the rule will not detect violations.
89
+
79
90
  ## Rules
80
91
 
81
92
  ### `wsx/render-method-required`
@@ -139,12 +150,17 @@ Enforces proper Web Component tag naming conventions (kebab-case with at least o
139
150
 
140
151
  **Error level**: `error`
141
152
 
142
- Requires `@state` decorator properties to have initial values. This is mandatory because we need the initial value to determine if it's a primitive or object/array.
153
+ Requires `@state` decorator properties to have initial values. This is mandatory because:
154
+ 1. The Babel plugin needs the initial value to determine if it's a primitive (uses `useState`) or object/array (uses `reactive`)
155
+ 2. Without an initial value, the decorator cannot be properly transformed at compile time
156
+ 3. The runtime fallback also requires an initial value to set up reactive state correctly
143
157
 
144
158
  **Invalid**:
145
159
  ```typescript
146
160
  class MyComponent extends WebComponent {
147
161
  @state private maskStrokeColor?: string; // ❌ Missing initial value
162
+ @state private count; // ❌ Missing initial value
163
+ @state private user; // ❌ Missing initial value
148
164
  }
149
165
  ```
150
166
 
@@ -153,12 +169,26 @@ class MyComponent extends WebComponent {
153
169
  class MyComponent extends WebComponent {
154
170
  @state private maskStrokeColor = ""; // ✅ String
155
171
  @state private count = 0; // ✅ Number
172
+ @state private enabled = false; // ✅ Boolean
156
173
  @state private user = { name: "John" }; // ✅ Object
157
174
  @state private items = []; // ✅ Array
158
- @state private optional?: string = undefined; // ✅ Optional with explicit undefined
175
+ @state private optional: string | undefined = undefined; // ✅ Optional with explicit undefined
176
+ @state private size?: number = 32; // ✅ Optional with default value
159
177
  }
160
178
  ```
161
179
 
180
+ **Error Message Example**:
181
+ ```
182
+ @state decorator on property 'size' requires an initial value.
183
+
184
+ Examples:
185
+ @state private size = ''; // for string
186
+ @state private size = 0; // for number
187
+ @state private size = {}; // for object
188
+ @state private size = []; // for array
189
+ @state private size = undefined; // for optional
190
+ ```
191
+
162
192
  ## Configuration Options
163
193
 
164
194
  ### Disable Specific Rules
package/dist/index.d.mts CHANGED
@@ -21,6 +21,7 @@ interface WSXConfig {
21
21
  };
22
22
  jsxPragma?: string;
23
23
  jsxFragmentName?: string;
24
+ experimentalDecorators?: boolean;
24
25
  };
25
26
  plugins?: string[];
26
27
  rules?: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ interface WSXConfig {
21
21
  };
22
22
  jsxPragma?: string;
23
23
  jsxFragmentName?: string;
24
+ experimentalDecorators?: boolean;
24
25
  };
25
26
  plugins?: string[];
26
27
  rules?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -184,110 +184,74 @@ var webComponentNaming = {
184
184
  }
185
185
  };
186
186
 
187
- // src/rules/no-state-on-html-attributes.ts
188
- var noStateOnHtmlAttributes = {
187
+ // src/rules/state-requires-initial-value.ts
188
+ var stateRequiresInitialValue = {
189
189
  meta: {
190
190
  type: "problem",
191
191
  docs: {
192
- description: "disallow @state decorator on HTML attributes",
192
+ description: "require @state decorator properties to have initial values",
193
193
  category: "Possible Errors",
194
194
  recommended: true
195
195
  },
196
196
  messages: {
197
- stateOnHtmlAttribute: "@state decorator cannot be used on properties that are HTML attributes. Property '{{propertyName}}' is defined in observedAttributes. HTML attributes should be handled via onAttributeChanged, not @state decorator."
197
+ missingInitialValue: "@state decorator on property '{{propertyName}}' requires an initial value.\n\nExamples:\n @state private {{propertyName}} = ''; // for string\n @state private {{propertyName}} = 0; // for number\n @state private {{propertyName}} = {}; // for object\n @state private {{propertyName}} = []; // for array\n @state private {{propertyName}} = undefined; // for optional"
198
198
  },
199
199
  schema: []
200
200
  },
201
201
  create(context) {
202
- const classObservedAttributes = /* @__PURE__ */ new Map();
203
202
  const stateImports = /* @__PURE__ */ new Set();
204
203
  return {
205
- // Track imports
206
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
+ // Track imports to identify @state decorator
207
205
  ImportDeclaration(node) {
208
- if (node.source.type === "StringLiteral" && (node.source.value === "@wsxjs/wsx-core" || node.source.value.endsWith("/wsx-core"))) {
209
- node.specifiers.forEach((spec) => {
210
- if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "state") {
211
- const localName = spec.local.type === "Identifier" ? spec.local.name : null;
206
+ if (node.source.type === "Literal" && typeof node.source.value === "string" && node.source.value === "@wsxjs/wsx-core") {
207
+ node.specifiers.forEach((specifier) => {
208
+ if (specifier.type === "ImportSpecifier") {
209
+ if (specifier.imported.type === "Identifier" && specifier.imported.name === "state") {
210
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
211
+ if (localName) {
212
+ stateImports.add(localName);
213
+ }
214
+ }
215
+ } else if (specifier.type === "ImportDefaultSpecifier") {
216
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
212
217
  if (localName) {
213
218
  stateImports.add(localName);
214
219
  }
220
+ } else if (specifier.type === "ImportNamespaceSpecifier") {
221
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
222
+ if (localName) {
223
+ stateImports.add("state");
224
+ }
215
225
  }
216
226
  });
217
227
  }
218
228
  },
229
+ // Check class properties for @state decorator
230
+ // Support both ClassProperty (older) and PropertyDefinition (newer TypeScript ESLint)
219
231
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
- ClassDeclaration(node) {
221
- const observedAttributes = [];
222
- for (const member of node.body.body) {
223
- if ((member.type === "ClassProperty" || member.type === "ClassPrivateProperty") && member.static && member.key.type === "Identifier" && member.key.name === "observedAttributes") {
224
- if (member.value?.type === "ArrayExpression") {
225
- observedAttributes.push(
226
- ...member.value.elements.filter((el) => el && el.type === "StringLiteral").map((el) => el.value)
227
- );
232
+ "ClassProperty, ClassPrivateProperty, PropertyDefinition"(node) {
233
+ if (!node.decorators || node.decorators.length === 0) {
234
+ return;
235
+ }
236
+ const hasStateDecorator = node.decorators.some((decorator) => {
237
+ if (decorator.expression.type === "Identifier") {
238
+ return decorator.expression.name === "state" || stateImports.has(decorator.expression.name);
239
+ } else if (decorator.expression.type === "CallExpression") {
240
+ if (decorator.expression.callee.type === "Identifier") {
241
+ return decorator.expression.callee.name === "state" || stateImports.has(decorator.expression.callee.name);
228
242
  }
229
- } else if (member.type === "ClassMethod" && member.static && member.kind === "get" && member.key.type === "Identifier" && member.key.name === "observedAttributes") {
230
- const returnStmt = member.body.body.find(
231
- (stmt) => stmt.type === "ReturnStatement"
232
- );
233
- if (returnStmt?.argument?.type === "ArrayExpression") {
234
- observedAttributes.push(
235
- ...returnStmt.argument.elements.filter((el) => el && el.type === "StringLiteral").map((el) => el.value)
236
- );
243
+ } else if (decorator.expression.type === "MemberExpression") {
244
+ if (decorator.expression.property.type === "Identifier" && decorator.expression.property.name === "state") {
245
+ return true;
237
246
  }
238
247
  }
239
- }
240
- if (observedAttributes.length > 0) {
241
- classObservedAttributes.set(node, observedAttributes);
242
- }
243
- },
244
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
- "ClassDeclaration:exit"(node) {
246
- classObservedAttributes.delete(node);
247
- },
248
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
- Decorator(node) {
250
- let isStateDecorator = false;
251
- if (node.expression.type === "Identifier") {
252
- const name = node.expression.name;
253
- isStateDecorator = name === "state" || stateImports.has(name);
254
- } else if (node.expression.type === "CallExpression") {
255
- const callee = node.expression.callee;
256
- if (callee.type === "Identifier") {
257
- const name = callee.name;
258
- isStateDecorator = name === "state" || stateImports.has(name);
259
- }
260
- }
261
- if (!isStateDecorator) {
262
- return;
263
- }
264
- let classNode = node.parent;
265
- while (classNode && classNode.type !== "ClassDeclaration" && classNode.type !== "ClassExpression") {
266
- classNode = classNode.parent;
267
- }
268
- if (!classNode) {
269
- return;
270
- }
271
- const propertyNode = node.parent;
272
- if (!propertyNode || propertyNode.type !== "ClassProperty" && propertyNode.type !== "ClassPrivateProperty") {
273
- return;
274
- }
275
- const propertyName = propertyNode.key.type === "Identifier" ? propertyNode.key.name : null;
276
- if (!propertyName) {
277
- return;
278
- }
279
- const observedAttributes = classObservedAttributes.get(classNode);
280
- if (!observedAttributes || observedAttributes.length === 0) {
281
- return;
282
- }
283
- const propertyKebabCase = propertyName.replace(/([A-Z])/g, "-$1").toLowerCase();
284
- const propertyLower = propertyName.toLowerCase();
285
- if (observedAttributes.some(
286
- (attr) => attr.toLowerCase() === propertyLower || attr.toLowerCase() === propertyKebabCase
287
- )) {
248
+ return false;
249
+ });
250
+ if (hasStateDecorator && !node.value) {
251
+ const propertyName = node.key.type === "Identifier" ? node.key.name : node.key.type === "PrivateIdentifier" ? node.key.name : "unknown";
288
252
  context.report({
289
253
  node,
290
- messageId: "stateOnHtmlAttribute",
254
+ messageId: "missingInitialValue",
291
255
  data: { propertyName }
292
256
  });
293
257
  }
@@ -296,69 +260,6 @@ var noStateOnHtmlAttributes = {
296
260
  }
297
261
  };
298
262
 
299
- // src/rules/no-state-on-methods.ts
300
- var noStateOnMethods = {
301
- meta: {
302
- type: "problem",
303
- docs: {
304
- description: "disallow @state decorator on methods",
305
- category: "Possible Errors",
306
- recommended: true
307
- },
308
- messages: {
309
- stateOnMethod: "@state decorator cannot be used on methods. '{{methodName}}' is a method, not a property. @state can only be used on properties with primitive, object, or array values."
310
- },
311
- schema: []
312
- },
313
- create(context) {
314
- const stateImports = /* @__PURE__ */ new Set();
315
- return {
316
- // Track imports
317
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
- ImportDeclaration(node) {
319
- if (node.source.type === "StringLiteral" && (node.source.value === "@wsxjs/wsx-core" || node.source.value.endsWith("/wsx-core"))) {
320
- node.specifiers.forEach((spec) => {
321
- if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "state") {
322
- const localName = spec.local.type === "Identifier" ? spec.local.name : null;
323
- if (localName) {
324
- stateImports.add(localName);
325
- }
326
- }
327
- });
328
- }
329
- },
330
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
- Decorator(node) {
332
- let isStateDecorator = false;
333
- if (node.expression.type === "Identifier") {
334
- const name = node.expression.name;
335
- isStateDecorator = name === "state" || stateImports.has(name);
336
- } else if (node.expression.type === "CallExpression") {
337
- const callee = node.expression.callee;
338
- if (callee.type === "Identifier") {
339
- const name = callee.name;
340
- isStateDecorator = name === "state" || stateImports.has(name);
341
- }
342
- }
343
- if (!isStateDecorator) {
344
- return;
345
- }
346
- const parent = node.parent;
347
- if (parent && (parent.type === "ClassMethod" || parent.type === "ClassPrivateMethod")) {
348
- const methodName = parent.key.type === "Identifier" ? parent.key.name : parent.key.type === "StringLiteral" ? parent.key.value : null;
349
- if (methodName) {
350
- context.report({
351
- node,
352
- messageId: "stateOnMethod",
353
- data: { methodName }
354
- });
355
- }
356
- }
357
- }
358
- };
359
- }
360
- };
361
-
362
263
  // src/configs/recommended.ts
363
264
  var recommendedConfig = {
364
265
  parser: "@typescript-eslint/parser",
@@ -369,7 +270,9 @@ var recommendedConfig = {
369
270
  jsx: true
370
271
  },
371
272
  jsxPragma: "h",
372
- jsxFragmentName: "Fragment"
273
+ jsxFragmentName: "Fragment",
274
+ experimentalDecorators: true
275
+ // Required to parse @state decorators
373
276
  },
374
277
  plugins: ["wsx"],
375
278
  rules: {
@@ -377,8 +280,7 @@ var recommendedConfig = {
377
280
  "wsx/render-method-required": "error",
378
281
  "wsx/no-react-imports": "error",
379
282
  "wsx/web-component-naming": "warn",
380
- "wsx/no-state-on-html-attributes": "error",
381
- "wsx/no-state-on-methods": "error",
283
+ "wsx/state-requires-initial-value": "error",
382
284
  // TypeScript 规则(推荐)
383
285
  "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
384
286
  "@typescript-eslint/no-explicit-any": "warn",
@@ -544,8 +446,7 @@ var plugin = {
544
446
  "render-method-required": renderMethodRequired,
545
447
  "no-react-imports": noReactImports,
546
448
  "web-component-naming": webComponentNaming,
547
- "no-state-on-html-attributes": noStateOnHtmlAttributes,
548
- "no-state-on-methods": noStateOnMethods
449
+ "state-requires-initial-value": stateRequiresInitialValue
549
450
  },
550
451
  // 配置预设
551
452
  configs: {
package/dist/index.mjs CHANGED
@@ -155,110 +155,74 @@ var webComponentNaming = {
155
155
  }
156
156
  };
157
157
 
158
- // src/rules/no-state-on-html-attributes.ts
159
- var noStateOnHtmlAttributes = {
158
+ // src/rules/state-requires-initial-value.ts
159
+ var stateRequiresInitialValue = {
160
160
  meta: {
161
161
  type: "problem",
162
162
  docs: {
163
- description: "disallow @state decorator on HTML attributes",
163
+ description: "require @state decorator properties to have initial values",
164
164
  category: "Possible Errors",
165
165
  recommended: true
166
166
  },
167
167
  messages: {
168
- stateOnHtmlAttribute: "@state decorator cannot be used on properties that are HTML attributes. Property '{{propertyName}}' is defined in observedAttributes. HTML attributes should be handled via onAttributeChanged, not @state decorator."
168
+ missingInitialValue: "@state decorator on property '{{propertyName}}' requires an initial value.\n\nExamples:\n @state private {{propertyName}} = ''; // for string\n @state private {{propertyName}} = 0; // for number\n @state private {{propertyName}} = {}; // for object\n @state private {{propertyName}} = []; // for array\n @state private {{propertyName}} = undefined; // for optional"
169
169
  },
170
170
  schema: []
171
171
  },
172
172
  create(context) {
173
- const classObservedAttributes = /* @__PURE__ */ new Map();
174
173
  const stateImports = /* @__PURE__ */ new Set();
175
174
  return {
176
- // Track imports
177
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
+ // Track imports to identify @state decorator
178
176
  ImportDeclaration(node) {
179
- if (node.source.type === "StringLiteral" && (node.source.value === "@wsxjs/wsx-core" || node.source.value.endsWith("/wsx-core"))) {
180
- node.specifiers.forEach((spec) => {
181
- if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "state") {
182
- const localName = spec.local.type === "Identifier" ? spec.local.name : null;
177
+ if (node.source.type === "Literal" && typeof node.source.value === "string" && node.source.value === "@wsxjs/wsx-core") {
178
+ node.specifiers.forEach((specifier) => {
179
+ if (specifier.type === "ImportSpecifier") {
180
+ if (specifier.imported.type === "Identifier" && specifier.imported.name === "state") {
181
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
182
+ if (localName) {
183
+ stateImports.add(localName);
184
+ }
185
+ }
186
+ } else if (specifier.type === "ImportDefaultSpecifier") {
187
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
183
188
  if (localName) {
184
189
  stateImports.add(localName);
185
190
  }
191
+ } else if (specifier.type === "ImportNamespaceSpecifier") {
192
+ const localName = specifier.local.type === "Identifier" ? specifier.local.name : null;
193
+ if (localName) {
194
+ stateImports.add("state");
195
+ }
186
196
  }
187
197
  });
188
198
  }
189
199
  },
200
+ // Check class properties for @state decorator
201
+ // Support both ClassProperty (older) and PropertyDefinition (newer TypeScript ESLint)
190
202
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- ClassDeclaration(node) {
192
- const observedAttributes = [];
193
- for (const member of node.body.body) {
194
- if ((member.type === "ClassProperty" || member.type === "ClassPrivateProperty") && member.static && member.key.type === "Identifier" && member.key.name === "observedAttributes") {
195
- if (member.value?.type === "ArrayExpression") {
196
- observedAttributes.push(
197
- ...member.value.elements.filter((el) => el && el.type === "StringLiteral").map((el) => el.value)
198
- );
203
+ "ClassProperty, ClassPrivateProperty, PropertyDefinition"(node) {
204
+ if (!node.decorators || node.decorators.length === 0) {
205
+ return;
206
+ }
207
+ const hasStateDecorator = node.decorators.some((decorator) => {
208
+ if (decorator.expression.type === "Identifier") {
209
+ return decorator.expression.name === "state" || stateImports.has(decorator.expression.name);
210
+ } else if (decorator.expression.type === "CallExpression") {
211
+ if (decorator.expression.callee.type === "Identifier") {
212
+ return decorator.expression.callee.name === "state" || stateImports.has(decorator.expression.callee.name);
199
213
  }
200
- } else if (member.type === "ClassMethod" && member.static && member.kind === "get" && member.key.type === "Identifier" && member.key.name === "observedAttributes") {
201
- const returnStmt = member.body.body.find(
202
- (stmt) => stmt.type === "ReturnStatement"
203
- );
204
- if (returnStmt?.argument?.type === "ArrayExpression") {
205
- observedAttributes.push(
206
- ...returnStmt.argument.elements.filter((el) => el && el.type === "StringLiteral").map((el) => el.value)
207
- );
214
+ } else if (decorator.expression.type === "MemberExpression") {
215
+ if (decorator.expression.property.type === "Identifier" && decorator.expression.property.name === "state") {
216
+ return true;
208
217
  }
209
218
  }
210
- }
211
- if (observedAttributes.length > 0) {
212
- classObservedAttributes.set(node, observedAttributes);
213
- }
214
- },
215
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
216
- "ClassDeclaration:exit"(node) {
217
- classObservedAttributes.delete(node);
218
- },
219
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
- Decorator(node) {
221
- let isStateDecorator = false;
222
- if (node.expression.type === "Identifier") {
223
- const name = node.expression.name;
224
- isStateDecorator = name === "state" || stateImports.has(name);
225
- } else if (node.expression.type === "CallExpression") {
226
- const callee = node.expression.callee;
227
- if (callee.type === "Identifier") {
228
- const name = callee.name;
229
- isStateDecorator = name === "state" || stateImports.has(name);
230
- }
231
- }
232
- if (!isStateDecorator) {
233
- return;
234
- }
235
- let classNode = node.parent;
236
- while (classNode && classNode.type !== "ClassDeclaration" && classNode.type !== "ClassExpression") {
237
- classNode = classNode.parent;
238
- }
239
- if (!classNode) {
240
- return;
241
- }
242
- const propertyNode = node.parent;
243
- if (!propertyNode || propertyNode.type !== "ClassProperty" && propertyNode.type !== "ClassPrivateProperty") {
244
- return;
245
- }
246
- const propertyName = propertyNode.key.type === "Identifier" ? propertyNode.key.name : null;
247
- if (!propertyName) {
248
- return;
249
- }
250
- const observedAttributes = classObservedAttributes.get(classNode);
251
- if (!observedAttributes || observedAttributes.length === 0) {
252
- return;
253
- }
254
- const propertyKebabCase = propertyName.replace(/([A-Z])/g, "-$1").toLowerCase();
255
- const propertyLower = propertyName.toLowerCase();
256
- if (observedAttributes.some(
257
- (attr) => attr.toLowerCase() === propertyLower || attr.toLowerCase() === propertyKebabCase
258
- )) {
219
+ return false;
220
+ });
221
+ if (hasStateDecorator && !node.value) {
222
+ const propertyName = node.key.type === "Identifier" ? node.key.name : node.key.type === "PrivateIdentifier" ? node.key.name : "unknown";
259
223
  context.report({
260
224
  node,
261
- messageId: "stateOnHtmlAttribute",
225
+ messageId: "missingInitialValue",
262
226
  data: { propertyName }
263
227
  });
264
228
  }
@@ -267,69 +231,6 @@ var noStateOnHtmlAttributes = {
267
231
  }
268
232
  };
269
233
 
270
- // src/rules/no-state-on-methods.ts
271
- var noStateOnMethods = {
272
- meta: {
273
- type: "problem",
274
- docs: {
275
- description: "disallow @state decorator on methods",
276
- category: "Possible Errors",
277
- recommended: true
278
- },
279
- messages: {
280
- stateOnMethod: "@state decorator cannot be used on methods. '{{methodName}}' is a method, not a property. @state can only be used on properties with primitive, object, or array values."
281
- },
282
- schema: []
283
- },
284
- create(context) {
285
- const stateImports = /* @__PURE__ */ new Set();
286
- return {
287
- // Track imports
288
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
- ImportDeclaration(node) {
290
- if (node.source.type === "StringLiteral" && (node.source.value === "@wsxjs/wsx-core" || node.source.value.endsWith("/wsx-core"))) {
291
- node.specifiers.forEach((spec) => {
292
- if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "state") {
293
- const localName = spec.local.type === "Identifier" ? spec.local.name : null;
294
- if (localName) {
295
- stateImports.add(localName);
296
- }
297
- }
298
- });
299
- }
300
- },
301
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
302
- Decorator(node) {
303
- let isStateDecorator = false;
304
- if (node.expression.type === "Identifier") {
305
- const name = node.expression.name;
306
- isStateDecorator = name === "state" || stateImports.has(name);
307
- } else if (node.expression.type === "CallExpression") {
308
- const callee = node.expression.callee;
309
- if (callee.type === "Identifier") {
310
- const name = callee.name;
311
- isStateDecorator = name === "state" || stateImports.has(name);
312
- }
313
- }
314
- if (!isStateDecorator) {
315
- return;
316
- }
317
- const parent = node.parent;
318
- if (parent && (parent.type === "ClassMethod" || parent.type === "ClassPrivateMethod")) {
319
- const methodName = parent.key.type === "Identifier" ? parent.key.name : parent.key.type === "StringLiteral" ? parent.key.value : null;
320
- if (methodName) {
321
- context.report({
322
- node,
323
- messageId: "stateOnMethod",
324
- data: { methodName }
325
- });
326
- }
327
- }
328
- }
329
- };
330
- }
331
- };
332
-
333
234
  // src/configs/recommended.ts
334
235
  var recommendedConfig = {
335
236
  parser: "@typescript-eslint/parser",
@@ -340,7 +241,9 @@ var recommendedConfig = {
340
241
  jsx: true
341
242
  },
342
243
  jsxPragma: "h",
343
- jsxFragmentName: "Fragment"
244
+ jsxFragmentName: "Fragment",
245
+ experimentalDecorators: true
246
+ // Required to parse @state decorators
344
247
  },
345
248
  plugins: ["wsx"],
346
249
  rules: {
@@ -348,8 +251,7 @@ var recommendedConfig = {
348
251
  "wsx/render-method-required": "error",
349
252
  "wsx/no-react-imports": "error",
350
253
  "wsx/web-component-naming": "warn",
351
- "wsx/no-state-on-html-attributes": "error",
352
- "wsx/no-state-on-methods": "error",
254
+ "wsx/state-requires-initial-value": "error",
353
255
  // TypeScript 规则(推荐)
354
256
  "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
355
257
  "@typescript-eslint/no-explicit-any": "warn",
@@ -515,8 +417,7 @@ var plugin = {
515
417
  "render-method-required": renderMethodRequired,
516
418
  "no-react-imports": noReactImports,
517
419
  "web-component-naming": webComponentNaming,
518
- "no-state-on-html-attributes": noStateOnHtmlAttributes,
519
- "no-state-on-methods": noStateOnMethods
420
+ "state-requires-initial-value": stateRequiresInitialValue
520
421
  },
521
422
  // 配置预设
522
423
  configs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/eslint-plugin-wsx",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "ESLint plugin for WSX Framework",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -25,7 +25,7 @@
25
25
  "web-components"
26
26
  ],
27
27
  "dependencies": {
28
- "@wsxjs/wsx-core": "0.0.12"
28
+ "@wsxjs/wsx-core": "0.0.13"
29
29
  },
30
30
  "devDependencies": {
31
31
  "tsup": "^8.0.0",