@wsxjs/eslint-plugin-wsx 0.0.10 → 0.0.11

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/dist/index.js CHANGED
@@ -184,6 +184,181 @@ var webComponentNaming = {
184
184
  }
185
185
  };
186
186
 
187
+ // src/rules/no-state-on-html-attributes.ts
188
+ var noStateOnHtmlAttributes = {
189
+ meta: {
190
+ type: "problem",
191
+ docs: {
192
+ description: "disallow @state decorator on HTML attributes",
193
+ category: "Possible Errors",
194
+ recommended: true
195
+ },
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."
198
+ },
199
+ schema: []
200
+ },
201
+ create(context) {
202
+ const classObservedAttributes = /* @__PURE__ */ new Map();
203
+ const stateImports = /* @__PURE__ */ new Set();
204
+ return {
205
+ // Track imports
206
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
207
+ 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;
212
+ if (localName) {
213
+ stateImports.add(localName);
214
+ }
215
+ }
216
+ });
217
+ }
218
+ },
219
+ // 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
+ );
228
+ }
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
+ );
237
+ }
238
+ }
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
+ )) {
288
+ context.report({
289
+ node,
290
+ messageId: "stateOnHtmlAttribute",
291
+ data: { propertyName }
292
+ });
293
+ }
294
+ }
295
+ };
296
+ }
297
+ };
298
+
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
+
187
362
  // src/configs/recommended.ts
188
363
  var recommendedConfig = {
189
364
  parser: "@typescript-eslint/parser",
@@ -202,6 +377,8 @@ var recommendedConfig = {
202
377
  "wsx/render-method-required": "error",
203
378
  "wsx/no-react-imports": "error",
204
379
  "wsx/web-component-naming": "warn",
380
+ "wsx/no-state-on-html-attributes": "error",
381
+ "wsx/no-state-on-methods": "error",
205
382
  // TypeScript 规则(推荐)
206
383
  "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
207
384
  "@typescript-eslint/no-explicit-any": "warn",
@@ -366,7 +543,9 @@ var plugin = {
366
543
  rules: {
367
544
  "render-method-required": renderMethodRequired,
368
545
  "no-react-imports": noReactImports,
369
- "web-component-naming": webComponentNaming
546
+ "web-component-naming": webComponentNaming,
547
+ "no-state-on-html-attributes": noStateOnHtmlAttributes,
548
+ "no-state-on-methods": noStateOnMethods
370
549
  },
371
550
  // 配置预设
372
551
  configs: {
package/dist/index.mjs CHANGED
@@ -155,6 +155,181 @@ var webComponentNaming = {
155
155
  }
156
156
  };
157
157
 
158
+ // src/rules/no-state-on-html-attributes.ts
159
+ var noStateOnHtmlAttributes = {
160
+ meta: {
161
+ type: "problem",
162
+ docs: {
163
+ description: "disallow @state decorator on HTML attributes",
164
+ category: "Possible Errors",
165
+ recommended: true
166
+ },
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."
169
+ },
170
+ schema: []
171
+ },
172
+ create(context) {
173
+ const classObservedAttributes = /* @__PURE__ */ new Map();
174
+ const stateImports = /* @__PURE__ */ new Set();
175
+ return {
176
+ // Track imports
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ 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;
183
+ if (localName) {
184
+ stateImports.add(localName);
185
+ }
186
+ }
187
+ });
188
+ }
189
+ },
190
+ // 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
+ );
199
+ }
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
+ );
208
+ }
209
+ }
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
+ )) {
259
+ context.report({
260
+ node,
261
+ messageId: "stateOnHtmlAttribute",
262
+ data: { propertyName }
263
+ });
264
+ }
265
+ }
266
+ };
267
+ }
268
+ };
269
+
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
+
158
333
  // src/configs/recommended.ts
159
334
  var recommendedConfig = {
160
335
  parser: "@typescript-eslint/parser",
@@ -173,6 +348,8 @@ var recommendedConfig = {
173
348
  "wsx/render-method-required": "error",
174
349
  "wsx/no-react-imports": "error",
175
350
  "wsx/web-component-naming": "warn",
351
+ "wsx/no-state-on-html-attributes": "error",
352
+ "wsx/no-state-on-methods": "error",
176
353
  // TypeScript 规则(推荐)
177
354
  "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
178
355
  "@typescript-eslint/no-explicit-any": "warn",
@@ -337,7 +514,9 @@ var plugin = {
337
514
  rules: {
338
515
  "render-method-required": renderMethodRequired,
339
516
  "no-react-imports": noReactImports,
340
- "web-component-naming": webComponentNaming
517
+ "web-component-naming": webComponentNaming,
518
+ "no-state-on-html-attributes": noStateOnHtmlAttributes,
519
+ "no-state-on-methods": noStateOnMethods
341
520
  },
342
521
  // 配置预设
343
522
  configs: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/eslint-plugin-wsx",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
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.10"
28
+ "@wsxjs/wsx-core": "0.0.11"
29
29
  },
30
30
  "devDependencies": {
31
31
  "tsup": "^8.0.0",
@@ -43,6 +43,9 @@
43
43
  "peerDependencies": {
44
44
  "eslint": ">=8.0.0 || ^9.0.0"
45
45
  },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
46
49
  "scripts": {
47
50
  "build": "tsup src/index.ts --format cjs,esm --dts --cjs-interop",
48
51
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",