eslint-config-agent 1.3.6 → 1.3.7
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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. See [Conven
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [1.3.7](https://github.com/tupe12334/eslint-config/compare/v1.3.6...v1.3.7) (2025-09-24)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add error handling rules from eslint-plugin-error ([082701f](https://github.com/tupe12334/eslint-config/commit/082701f8abe8730ed12afe206caf8f10825b4d1c))
|
|
12
|
+
* add jsx-classname-required rule to enforce className attribute on HTML elements in JSX ([3603865](https://github.com/tupe12334/eslint-config/commit/360386582f05e63d6ba1625548725b9216c4f4d6))
|
|
13
|
+
* integrate error handling rules from eslint-plugin-error and update configuration ([7c8b846](https://github.com/tupe12334/eslint-config/commit/7c8b84694550b92ac2a265591fac736d5ddb1180))
|
|
14
|
+
|
|
7
15
|
## [1.3.6](https://github.com/tupe12334/eslint-config/compare/v1.3.5...v1.3.6) (2025-09-21)
|
|
8
16
|
|
|
9
17
|
### Bug Fixes
|
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import storybookPlugin from "eslint-plugin-storybook";
|
|
|
12
12
|
import globals from "globals";
|
|
13
13
|
import allRules from "./rules/index.js";
|
|
14
14
|
import { noDefaultClassExportRule } from "./rules/no-default-class-export/index.js";
|
|
15
|
+
import errorPlugin from "eslint-plugin-error";
|
|
15
16
|
|
|
16
17
|
// Conditionally import preact plugin if available
|
|
17
18
|
let preactPlugin = null;
|
|
@@ -40,6 +41,8 @@ const sharedRules = {
|
|
|
40
41
|
"implicit-arrow-linebreak": "off",
|
|
41
42
|
"arrow-body-style": "off",
|
|
42
43
|
"no-continue": "off",
|
|
44
|
+
// Additional built-in error handling rules
|
|
45
|
+
"prefer-promise-reject-errors": "error",
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
// Shared no-restricted-syntax rules for both JS and TS
|
|
@@ -101,7 +104,6 @@ const sharedRestrictedSyntax = [
|
|
|
101
104
|
...allRules.noDefaultClassExportRules,
|
|
102
105
|
];
|
|
103
106
|
|
|
104
|
-
|
|
105
107
|
// TypeScript-specific no-restricted-syntax rules
|
|
106
108
|
const tsOnlyRestrictedSyntax = [
|
|
107
109
|
{
|
|
@@ -189,9 +191,11 @@ const config = [
|
|
|
189
191
|
"class-export": classExportPlugin,
|
|
190
192
|
"single-export": singleExportPlugin,
|
|
191
193
|
"required-exports": requiredExportsPlugin,
|
|
194
|
+
error: errorPlugin,
|
|
192
195
|
custom: {
|
|
193
196
|
rules: {
|
|
194
197
|
"no-default-class-export": noDefaultClassExportRule,
|
|
198
|
+
"jsx-classname-required": allRules.jsxClassNameRequiredRule,
|
|
195
199
|
},
|
|
196
200
|
},
|
|
197
201
|
},
|
|
@@ -199,19 +203,29 @@ const config = [
|
|
|
199
203
|
// Use recommended-latest if available (v5+), otherwise create flat config equivalent of legacy recommended
|
|
200
204
|
...(reactHooks.configs["recommended-latest"]
|
|
201
205
|
? [reactHooks.configs["recommended-latest"]]
|
|
202
|
-
: [
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
: [
|
|
207
|
+
{
|
|
208
|
+
name: "react-hooks/recommended-flat",
|
|
209
|
+
plugins: {
|
|
210
|
+
"react-hooks": reactHooks,
|
|
211
|
+
},
|
|
212
|
+
rules: {
|
|
213
|
+
"react-hooks/rules-of-hooks": "error",
|
|
214
|
+
"react-hooks/exhaustive-deps": "warn",
|
|
215
|
+
},
|
|
210
216
|
},
|
|
211
|
-
|
|
212
|
-
),
|
|
217
|
+
]),
|
|
213
218
|
js.configs.recommended,
|
|
214
219
|
...tseslint.configs.recommended,
|
|
220
|
+
// Error handling plugin strict config
|
|
221
|
+
{
|
|
222
|
+
plugins: {
|
|
223
|
+
error: errorPlugin,
|
|
224
|
+
},
|
|
225
|
+
rules: {
|
|
226
|
+
...errorPlugin.configs.strict.rules,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
215
229
|
|
|
216
230
|
// TypeScript and TSX files
|
|
217
231
|
{
|
|
@@ -248,15 +262,18 @@ const config = [
|
|
|
248
262
|
"no-undef": "off", // TypeScript handles this
|
|
249
263
|
"custom/no-default-class-export": "error",
|
|
250
264
|
"single-export/single-export": "error",
|
|
251
|
-
"required-exports/required-exports": [
|
|
252
|
-
"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
"required-exports/required-exports": [
|
|
266
|
+
"error",
|
|
267
|
+
{
|
|
268
|
+
variable: false, // Don't require exporting variables/constants
|
|
269
|
+
function: false, // Don't require exporting functions
|
|
270
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
271
|
+
interface: false, // Don't require exporting interfaces
|
|
272
|
+
type: false, // Don't require exporting types
|
|
273
|
+
enum: true, // Require exporting enums (matches old behavior)
|
|
274
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
275
|
+
},
|
|
276
|
+
],
|
|
260
277
|
"no-restricted-syntax": [
|
|
261
278
|
"error",
|
|
262
279
|
...sharedRestrictedSyntax,
|
|
@@ -273,15 +290,18 @@ const config = [
|
|
|
273
290
|
// Include all shared rules (like max-lines-per-function)
|
|
274
291
|
...sharedRules,
|
|
275
292
|
"single-export/single-export": "off", // TSX files have their own specific export rules
|
|
276
|
-
"required-exports/required-exports": [
|
|
277
|
-
"
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
"required-exports/required-exports": [
|
|
294
|
+
"error",
|
|
295
|
+
{
|
|
296
|
+
variable: false, // Don't require exporting variables/constants
|
|
297
|
+
function: false, // Don't require exporting functions
|
|
298
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
299
|
+
interface: false, // Don't require exporting interfaces
|
|
300
|
+
type: false, // Don't require exporting types
|
|
301
|
+
enum: true, // Require exporting enums (matches old behavior)
|
|
302
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
303
|
+
},
|
|
304
|
+
],
|
|
285
305
|
"no-restricted-syntax": [
|
|
286
306
|
"error",
|
|
287
307
|
// Switch case rules as errors
|
|
@@ -343,12 +363,6 @@ const config = [
|
|
|
343
363
|
message:
|
|
344
364
|
"Function expressions containing switch statements must have explicit return type annotations.",
|
|
345
365
|
},
|
|
346
|
-
// className requirement for HTML elements
|
|
347
|
-
{
|
|
348
|
-
selector:
|
|
349
|
-
'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))',
|
|
350
|
-
message: "HTML elements must have a className attribute.",
|
|
351
|
-
},
|
|
352
366
|
],
|
|
353
367
|
},
|
|
354
368
|
},
|
|
@@ -474,19 +488,19 @@ const config = [
|
|
|
474
488
|
rules: {
|
|
475
489
|
...sharedRules,
|
|
476
490
|
"single-export/single-export": "error",
|
|
477
|
-
"required-exports/required-exports": [
|
|
478
|
-
"variable": false, // Don't require exporting variables/constants
|
|
479
|
-
"function": false, // Don't require exporting functions
|
|
480
|
-
"class": true, // Require exporting classes (matches old behavior)
|
|
481
|
-
"interface": false, // Don't require exporting interfaces (N/A for JS)
|
|
482
|
-
"type": false, // Don't require exporting types (N/A for JS)
|
|
483
|
-
"enum": false, // Don't require exporting enums (N/A for JS)
|
|
484
|
-
"ignorePrivate": true // Ignore declarations starting with _
|
|
485
|
-
}],
|
|
486
|
-
"no-restricted-syntax": [
|
|
491
|
+
"required-exports/required-exports": [
|
|
487
492
|
"error",
|
|
488
|
-
|
|
493
|
+
{
|
|
494
|
+
variable: false, // Don't require exporting variables/constants
|
|
495
|
+
function: false, // Don't require exporting functions
|
|
496
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
497
|
+
interface: false, // Don't require exporting interfaces (N/A for JS)
|
|
498
|
+
type: false, // Don't require exporting types (N/A for JS)
|
|
499
|
+
enum: false, // Don't require exporting enums (N/A for JS)
|
|
500
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
501
|
+
},
|
|
489
502
|
],
|
|
503
|
+
"no-restricted-syntax": ["error", ...sharedRestrictedSyntax],
|
|
490
504
|
},
|
|
491
505
|
},
|
|
492
506
|
|
|
@@ -538,15 +552,18 @@ const config = [
|
|
|
538
552
|
...sharedRules,
|
|
539
553
|
"no-undef": "off", // TypeScript handles this
|
|
540
554
|
"single-export/single-export": "off", // JSX files have their own specific export rules
|
|
541
|
-
"required-exports/required-exports": [
|
|
542
|
-
"
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
555
|
+
"required-exports/required-exports": [
|
|
556
|
+
"error",
|
|
557
|
+
{
|
|
558
|
+
variable: false, // Don't require exporting variables/constants
|
|
559
|
+
function: false, // Don't require exporting functions
|
|
560
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
561
|
+
interface: false, // Don't require exporting interfaces (N/A for JSX)
|
|
562
|
+
type: false, // Don't require exporting types (N/A for JSX)
|
|
563
|
+
enum: false, // Don't require exporting enums (N/A for JSX)
|
|
564
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
565
|
+
},
|
|
566
|
+
],
|
|
550
567
|
"no-restricted-syntax": [
|
|
551
568
|
"error",
|
|
552
569
|
// Switch case rules as errors
|
|
@@ -608,12 +625,6 @@ const config = [
|
|
|
608
625
|
message:
|
|
609
626
|
"Function expressions containing switch statements must have explicit return type annotations.",
|
|
610
627
|
},
|
|
611
|
-
// className requirement for HTML elements
|
|
612
|
-
{
|
|
613
|
-
selector:
|
|
614
|
-
'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))',
|
|
615
|
-
message: "HTML elements must have a className attribute.",
|
|
616
|
-
},
|
|
617
628
|
// Required export rules as errors (class rule only for JSX)
|
|
618
629
|
{
|
|
619
630
|
selector:
|
|
@@ -658,15 +669,18 @@ const config = [
|
|
|
658
669
|
rules: {
|
|
659
670
|
"no-undef": "off", // TypeScript handles this
|
|
660
671
|
"single-export/single-export": "off", // JSX files have their own specific export rules
|
|
661
|
-
"required-exports/required-exports": [
|
|
662
|
-
"
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
672
|
+
"required-exports/required-exports": [
|
|
673
|
+
"warn",
|
|
674
|
+
{
|
|
675
|
+
variable: false, // Don't require exporting variables/constants
|
|
676
|
+
function: false, // Don't require exporting functions
|
|
677
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
678
|
+
interface: false, // Don't require exporting interfaces (N/A for JSX)
|
|
679
|
+
type: false, // Don't require exporting types (N/A for JSX)
|
|
680
|
+
enum: false, // Don't require exporting enums (N/A for JSX)
|
|
681
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
682
|
+
},
|
|
683
|
+
],
|
|
670
684
|
"no-restricted-syntax": [
|
|
671
685
|
"warn",
|
|
672
686
|
// Include shared rules but remove the multiple exports restriction and switch case rules for JSX
|
|
@@ -829,18 +843,22 @@ const config = [
|
|
|
829
843
|
"**/test/**",
|
|
830
844
|
"!**/test/export/**",
|
|
831
845
|
"!**/test/required-exports/**",
|
|
846
|
+
"!**/test/switch-case/**",
|
|
832
847
|
"**/rules/**/index.js",
|
|
833
848
|
],
|
|
834
849
|
rules: {
|
|
835
|
-
"required-exports/required-exports": [
|
|
836
|
-
"
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
850
|
+
"required-exports/required-exports": [
|
|
851
|
+
"error",
|
|
852
|
+
{
|
|
853
|
+
variable: false, // Don't require exporting variables/constants
|
|
854
|
+
function: false, // Don't require exporting functions
|
|
855
|
+
class: true, // Require exporting classes (matches old behavior)
|
|
856
|
+
interface: false, // Don't require exporting interfaces
|
|
857
|
+
type: false, // Don't require exporting types
|
|
858
|
+
enum: true, // Require exporting enums (matches old behavior)
|
|
859
|
+
ignorePrivate: true, // Ignore declarations starting with _
|
|
860
|
+
},
|
|
861
|
+
],
|
|
844
862
|
"no-restricted-syntax": [
|
|
845
863
|
"error",
|
|
846
864
|
// Switch case rules as errors
|
|
@@ -933,14 +951,7 @@ const config = [
|
|
|
933
951
|
files: ["**/*.{tsx,jsx}"],
|
|
934
952
|
ignores: ["**/*.stories.{js,jsx,ts,tsx}"],
|
|
935
953
|
rules: {
|
|
936
|
-
"
|
|
937
|
-
"error",
|
|
938
|
-
{
|
|
939
|
-
selector:
|
|
940
|
-
'JSXOpeningElement:not([name.name=/^[A-Z]/]):not([name.name="Fragment"]):not(:has(JSXAttribute[name.name="className"]))',
|
|
941
|
-
message: "HTML elements must have a className attribute.",
|
|
942
|
-
},
|
|
943
|
-
],
|
|
954
|
+
"custom/jsx-classname-required": "error",
|
|
944
955
|
},
|
|
945
956
|
},
|
|
946
957
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"description": "ESLint configuration package with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -106,5 +106,8 @@
|
|
|
106
106
|
"globals": "^16.3.0",
|
|
107
107
|
"release-it": "^19.0.4",
|
|
108
108
|
"typescript-eslint": "^8.40.0"
|
|
109
|
+
},
|
|
110
|
+
"dependencies": {
|
|
111
|
+
"eslint-plugin-error": "^1.1.1"
|
|
109
112
|
}
|
|
110
113
|
}
|
package/rules/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { noTypeAssertionsConfig } from "./no-type-assertions/index.js";
|
|
|
16
16
|
import { noExportSpecifiersConfig } from "./no-empty-exports/index.js";
|
|
17
17
|
import { noClassPropertyDefaultsConfig } from "./no-class-property-defaults/index.js";
|
|
18
18
|
import { noDefaultClassExportRules } from "./no-default-class-export/index.js";
|
|
19
|
+
import { jsxClassNameRequiredRule } from "./jsx-classname-required/index.js";
|
|
19
20
|
|
|
20
21
|
// Plugin rule configurations
|
|
21
22
|
import { pluginRules } from "./plugin/index.js";
|
|
@@ -36,6 +37,7 @@ const allRules = {
|
|
|
36
37
|
noExportSpecifiersConfig,
|
|
37
38
|
noClassPropertyDefaultsConfig,
|
|
38
39
|
noDefaultClassExportRules,
|
|
40
|
+
jsxClassNameRequiredRule,
|
|
39
41
|
|
|
40
42
|
// Plugin rule configurations
|
|
41
43
|
pluginRules,
|
|
@@ -59,6 +61,7 @@ export {
|
|
|
59
61
|
noExportSpecifiersConfig,
|
|
60
62
|
noClassPropertyDefaultsConfig,
|
|
61
63
|
noDefaultClassExportRules,
|
|
64
|
+
jsxClassNameRequiredRule,
|
|
62
65
|
|
|
63
66
|
// Plugin rule configurations
|
|
64
67
|
pluginRules,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule to require className attribute on HTML elements in JSX
|
|
3
|
+
*
|
|
4
|
+
* This rule enforces that all HTML elements in JSX must have a className attribute.
|
|
5
|
+
* It excludes React components (starting with capital letters) and fragments.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - ❌ <div>Content</div>
|
|
9
|
+
* - ✅ <div className="container">Content</div>
|
|
10
|
+
* - ✅ <MyComponent>Content</MyComponent> (ignored - React component)
|
|
11
|
+
* - ✅ <Fragment>Content</Fragment> (ignored - fragment)
|
|
12
|
+
* - ✅ <React.Fragment>Content</React.Fragment> (ignored - React fragment)
|
|
13
|
+
* - ✅ <React.StrictMode>Content</React.StrictMode> (ignored - React component)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const jsxClassNameRequiredRule = {
|
|
17
|
+
meta: {
|
|
18
|
+
type: "layout",
|
|
19
|
+
docs: {
|
|
20
|
+
description: "Require className attribute on HTML elements in JSX",
|
|
21
|
+
category: "Stylistic Issues",
|
|
22
|
+
recommended: false,
|
|
23
|
+
},
|
|
24
|
+
fixable: null,
|
|
25
|
+
schema: [],
|
|
26
|
+
messages: {
|
|
27
|
+
missingClassName: "HTML elements must have a className attribute.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create(context) {
|
|
32
|
+
return {
|
|
33
|
+
JSXOpeningElement(node) {
|
|
34
|
+
// Skip if this is a React component (starts with capital letter)
|
|
35
|
+
if (node.name.type === 'JSXIdentifier' && /^[A-Z]/.test(node.name.name)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Skip if this is Fragment
|
|
40
|
+
if (node.name.type === 'JSXIdentifier' && node.name.name === 'Fragment') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Skip if this is React.Something (like React.Fragment, React.StrictMode, etc.)
|
|
45
|
+
if (node.name.type === 'JSXMemberExpression' &&
|
|
46
|
+
node.name.object.name === 'React' &&
|
|
47
|
+
/^[A-Z]/.test(node.name.property.name)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if element has className attribute
|
|
52
|
+
const hasClassName = node.attributes.some(attr =>
|
|
53
|
+
attr.type === 'JSXAttribute' &&
|
|
54
|
+
attr.name.name === 'className'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!hasClassName) {
|
|
58
|
+
context.report({
|
|
59
|
+
node,
|
|
60
|
+
messageId: 'missingClassName',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export { jsxClassNameRequiredRule };
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { RuleTester } from "eslint";
|
|
2
|
+
import { jsxClassNameRequiredRule } from "./index.js";
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
languageOptions: {
|
|
6
|
+
ecmaVersion: 2022,
|
|
7
|
+
sourceType: "module",
|
|
8
|
+
parser: (await import("typescript-eslint")).parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaFeatures: {
|
|
11
|
+
jsx: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
ruleTester.run('jsx-classname-required', jsxClassNameRequiredRule, {
|
|
18
|
+
valid: [
|
|
19
|
+
// HTML elements with className
|
|
20
|
+
{
|
|
21
|
+
code: '<div className="container">Content</div>',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: '<span className="text">Text</span>',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: '<p className="paragraph">Paragraph</p>',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
code: '<button className="btn">Button</button>',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
code: '<input className="input" type="text" />',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: '<img className="image" src="test.jpg" alt="test" />',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// React components (should be ignored)
|
|
40
|
+
{
|
|
41
|
+
code: '<MyComponent>Content</MyComponent>',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
code: '<CustomButton onClick={() => {}}>Click</CustomButton>',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: '<AnotherComponent prop="value" />',
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Fragments (should be ignored)
|
|
51
|
+
{
|
|
52
|
+
code: '<Fragment>Content</Fragment>',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
code: '<React.Fragment>Content</React.Fragment>',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
code: '<>Content</>',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// React.* components (should be ignored)
|
|
62
|
+
{
|
|
63
|
+
code: '<React.StrictMode><div className="content">Content</div></React.StrictMode>',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: '<React.Suspense fallback={<div className="loading">Loading</div>}><div className="content">Content</div></React.Suspense>',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
code: '<React.Profiler id="test" onRender={() => {}}><div className="content">Content</div></React.Profiler>',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Mixed cases with valid HTML elements
|
|
73
|
+
{
|
|
74
|
+
code: `
|
|
75
|
+
<div className="container">
|
|
76
|
+
<MyComponent>
|
|
77
|
+
<p className="text">Text</p>
|
|
78
|
+
</MyComponent>
|
|
79
|
+
<React.Fragment>
|
|
80
|
+
<span className="fragment-content">Fragment content</span>
|
|
81
|
+
</React.Fragment>
|
|
82
|
+
</div>
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Nested valid cases
|
|
87
|
+
{
|
|
88
|
+
code: `
|
|
89
|
+
<div className="outer">
|
|
90
|
+
<div className="inner">
|
|
91
|
+
<span className="nested">Nested content</span>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
invalid: [
|
|
99
|
+
// HTML elements without className
|
|
100
|
+
{
|
|
101
|
+
code: '<div>Content</div>',
|
|
102
|
+
errors: [
|
|
103
|
+
{
|
|
104
|
+
messageId: 'missingClassName',
|
|
105
|
+
type: 'JSXOpeningElement',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
code: '<span>Text</span>',
|
|
111
|
+
errors: [
|
|
112
|
+
{
|
|
113
|
+
messageId: 'missingClassName',
|
|
114
|
+
type: 'JSXOpeningElement',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
code: '<p>Paragraph</p>',
|
|
120
|
+
errors: [
|
|
121
|
+
{
|
|
122
|
+
messageId: 'missingClassName',
|
|
123
|
+
type: 'JSXOpeningElement',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
code: '<button>Button</button>',
|
|
129
|
+
errors: [
|
|
130
|
+
{
|
|
131
|
+
messageId: 'missingClassName',
|
|
132
|
+
type: 'JSXOpeningElement',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
code: '<input type="text" />',
|
|
138
|
+
errors: [
|
|
139
|
+
{
|
|
140
|
+
messageId: 'missingClassName',
|
|
141
|
+
type: 'JSXOpeningElement',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
code: '<img src="test.jpg" alt="test" />',
|
|
147
|
+
errors: [
|
|
148
|
+
{
|
|
149
|
+
messageId: 'missingClassName',
|
|
150
|
+
type: 'JSXOpeningElement',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// HTML elements inside React components (should still error)
|
|
156
|
+
{
|
|
157
|
+
code: '<MyComponent><div>Content</div></MyComponent>',
|
|
158
|
+
errors: [
|
|
159
|
+
{
|
|
160
|
+
messageId: 'missingClassName',
|
|
161
|
+
type: 'JSXOpeningElement',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
code: '<React.StrictMode><div>Content</div></React.StrictMode>',
|
|
167
|
+
errors: [
|
|
168
|
+
{
|
|
169
|
+
messageId: 'missingClassName',
|
|
170
|
+
type: 'JSXOpeningElement',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
code: '<React.Fragment><span>Content</span></React.Fragment>',
|
|
176
|
+
errors: [
|
|
177
|
+
{
|
|
178
|
+
messageId: 'missingClassName',
|
|
179
|
+
type: 'JSXOpeningElement',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
code: '<Fragment><p>Content</p></Fragment>',
|
|
185
|
+
errors: [
|
|
186
|
+
{
|
|
187
|
+
messageId: 'missingClassName',
|
|
188
|
+
type: 'JSXOpeningElement',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
code: '<><div>Content</div></>',
|
|
194
|
+
errors: [
|
|
195
|
+
{
|
|
196
|
+
messageId: 'missingClassName',
|
|
197
|
+
type: 'JSXOpeningElement',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Multiple errors in one file
|
|
203
|
+
{
|
|
204
|
+
code: `
|
|
205
|
+
<div className="container">
|
|
206
|
+
<span>Missing className</span>
|
|
207
|
+
<p>Also missing className</p>
|
|
208
|
+
</div>
|
|
209
|
+
`,
|
|
210
|
+
errors: [
|
|
211
|
+
{
|
|
212
|
+
messageId: 'missingClassName',
|
|
213
|
+
type: 'JSXOpeningElement',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
messageId: 'missingClassName',
|
|
217
|
+
type: 'JSXOpeningElement',
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// Mixed valid/invalid cases
|
|
223
|
+
{
|
|
224
|
+
code: `
|
|
225
|
+
<div className="container">
|
|
226
|
+
<span className="valid">Valid</span>
|
|
227
|
+
<p>Invalid - no className</p>
|
|
228
|
+
<MyComponent>
|
|
229
|
+
<div>Invalid - no className</div>
|
|
230
|
+
</MyComponent>
|
|
231
|
+
</div>
|
|
232
|
+
`,
|
|
233
|
+
errors: [
|
|
234
|
+
{
|
|
235
|
+
messageId: 'missingClassName',
|
|
236
|
+
type: 'JSXOpeningElement',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
messageId: 'missingClassName',
|
|
240
|
+
type: 'JSXOpeningElement',
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// Edge cases
|
|
246
|
+
{
|
|
247
|
+
code: '<h1>Heading</h1>',
|
|
248
|
+
errors: [
|
|
249
|
+
{
|
|
250
|
+
messageId: 'missingClassName',
|
|
251
|
+
type: 'JSXOpeningElement',
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
code: '<section>Section</section>',
|
|
257
|
+
errors: [
|
|
258
|
+
{
|
|
259
|
+
messageId: 'missingClassName',
|
|
260
|
+
type: 'JSXOpeningElement',
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
code: '<article>Article</article>',
|
|
266
|
+
errors: [
|
|
267
|
+
{
|
|
268
|
+
messageId: 'missingClassName',
|
|
269
|
+
type: 'JSXOpeningElement',
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
code: '<nav>Navigation</nav>',
|
|
275
|
+
errors: [
|
|
276
|
+
{
|
|
277
|
+
messageId: 'missingClassName',
|
|
278
|
+
type: 'JSXOpeningElement',
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
});
|