@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 +32 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +46 -145
- package/dist/index.mjs +46 -145
- package/package.json +2 -2
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
|
|
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
|
|
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
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -184,110 +184,74 @@ var webComponentNaming = {
|
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
-
// src/rules/
|
|
188
|
-
var
|
|
187
|
+
// src/rules/state-requires-initial-value.ts
|
|
188
|
+
var stateRequiresInitialValue = {
|
|
189
189
|
meta: {
|
|
190
190
|
type: "problem",
|
|
191
191
|
docs: {
|
|
192
|
-
description: "
|
|
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
|
-
|
|
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 === "
|
|
209
|
-
node.specifiers.forEach((
|
|
210
|
-
if (
|
|
211
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 (
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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: "
|
|
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/
|
|
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
|
-
"
|
|
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/
|
|
159
|
-
var
|
|
158
|
+
// src/rules/state-requires-initial-value.ts
|
|
159
|
+
var stateRequiresInitialValue = {
|
|
160
160
|
meta: {
|
|
161
161
|
type: "problem",
|
|
162
162
|
docs: {
|
|
163
|
-
description: "
|
|
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
|
-
|
|
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 === "
|
|
180
|
-
node.specifiers.forEach((
|
|
181
|
-
if (
|
|
182
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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 (
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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: "
|
|
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/
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
28
|
+
"@wsxjs/wsx-core": "0.0.13"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"tsup": "^8.0.0",
|