@foundbyte/eslint-plugin 1.1.4 → 1.1.5-alpha.1

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
@@ -23,45 +23,67 @@ module.exports = {
23
23
  rules: {
24
24
  // 启用所有推荐的规则
25
25
  '@foundbyte/arrow-function-padding': 'error',
26
- '@foundbyte/no-initial-as-assertion': ['error', {
27
- excludeVariableAssertions: true,
28
- excludeTypePatterns: ['^const$', '^[A-Z]$', '^unknown$'],
29
- excludeMethodReturns: ['parse', 'querySelector']
30
- }],
26
+ '@foundbyte/no-initial-as-assertion': [
27
+ 'error',
28
+ {
29
+ excludeVariableAssertions: true,
30
+ excludeTypePatterns: ['^const$', '^[A-Z]$', '^unknown$'],
31
+ excludeMethodReturns: ['parse', 'querySelector'],
32
+ },
33
+ ],
31
34
  '@foundbyte/hooks-arrow-function-only': 'error',
32
- '@foundbyte/type-comment-requirement': ['error', {
33
- minCommentsPerGroup: 1,
34
- groupSize: 8,
35
- includeTypes: true,
36
- includeInterfaces: true,
37
- }],
35
+ '@foundbyte/type-comment-requirement': [
36
+ 'error',
37
+ {
38
+ minCommentsPerGroup: 1,
39
+ groupSize: 8,
40
+ includeTypes: true,
41
+ includeInterfaces: true,
42
+ },
43
+ ],
38
44
  '@foundbyte/hooks-export-limit': ['error', { maxExports: 20 }],
39
45
  '@foundbyte/vue-script-lines-limit': ['error', { maxLines: 300 }],
40
- '@foundbyte/tag-attribute-restriction': ['error', {
41
- restrictions: [
42
- {
43
- tag: 'image',
44
- attributes: [
45
- { name: 'mode', disallowedValues: [''] },
46
- ]
47
- }
48
- ]
49
- }],
50
- '@foundbyte/no-restricted-components': ['error', [
46
+ '@foundbyte/tag-attribute-restriction': [
47
+ 'error',
51
48
  {
52
- pattern: '/^a-(textarea|table)$/',
53
- message: '禁止使用的Arco Design组件,请使用Keyblade组件替代'
54
- }
55
- ]],
49
+ restrictions: [
50
+ {
51
+ tag: 'image',
52
+ attributes: [{ name: 'mode', disallowedValues: [''] }],
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ '@foundbyte/no-restricted-components': [
58
+ 'error',
59
+ [
60
+ {
61
+ pattern: '/^a-(textarea|table)$/',
62
+ message: '禁止使用的Arco Design组件,请使用Keyblade组件替代',
63
+ },
64
+ ],
65
+ ],
56
66
  '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }],
57
- '@foundbyte/no-disabled-comments': ['error', {
58
- patterns: [
59
- '/^\\s*eslint-disable-next-line\\s*$/',
60
- '/^\\s*eslint-disable\\s*$/',
61
- '/no-explicit-any/',
62
- '/vue-script-lines-limit/',
63
- '/hooks-export-limit/'
64
- ]}]
67
+ '@foundbyte/no-disabled-comments': [
68
+ 'error',
69
+ {
70
+ patterns: [
71
+ '/^\\s*eslint-disable-next-line\\s*$/',
72
+ '/^\\s*eslint-disable\\s*$/',
73
+ '/no-explicit-any/',
74
+ '/vue-script-lines-limit/',
75
+ '/hooks-export-limit/',
76
+ ],
77
+ },
78
+ ],
79
+ '@foundbyte/require-json-parse-type': 'error',
80
+ '@foundbyte/no-restricted-literal-types': [
81
+ 'error',
82
+ {
83
+ forbidden: ['null', 'undefined', ''],
84
+ allowInUnions: true,
85
+ },
86
+ ],
65
87
  },
66
88
  }
67
89
  ```
@@ -71,11 +93,13 @@ module.exports = {
71
93
  ### no-excessive-any
72
94
 
73
95
  限制 `any` 类型的使用次数,包括:
96
+
74
97
  - 显式 `any` 类型
75
98
  - 隐式 `any` 类型(没有类型注解且无法推导类型的变量)
76
99
  - 泛型类型中的 `any`(如 `Ref<any>`, `Promise<any>`, `any[]` 等)
77
100
 
78
101
  **配置示例:**
102
+
79
103
  ```javascript
80
104
  // 基本用法 - 限制 any 类型最多使用 5 次
81
105
  '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }]
@@ -88,6 +112,7 @@ module.exports = {
88
112
  ```
89
113
 
90
114
  **检测范围:**
115
+
91
116
  - 显式 `any` 类型:`const a: any = 1`
92
117
  - 隐式 `any` 类型:
93
118
  - 没有初始值的变量声明:`const a`
@@ -99,6 +124,7 @@ module.exports = {
99
124
 
100
125
  **智能类型推导:**
101
126
  以下情况不计入 any 使用:
127
+
102
128
  - 有初始值的变量:`const a = 1`, `const b = ref(60)`
103
129
  - 对象字面量:`const obj = { name: 'test' }`
104
130
  - 数组字面量:`const arr = [1, 2, 3]`
@@ -108,6 +134,7 @@ module.exports = {
108
134
  - 异步函数调用:`const res = await ssoMobileAuth(...)`
109
135
 
110
136
  **示例:**
137
+
111
138
  ```typescript
112
139
  // ✅ 正确 - 当 maxAnyCount 为 3 时
113
140
  const a: any = 1
@@ -155,6 +182,7 @@ interface MyInterface {
155
182
  - 设置为正整数表示允许的最大次数
156
183
 
157
184
  **注意:**
185
+
158
186
  - 该规则统计文件中所有 `any` 类型的使用,包括显式和隐式
159
187
  - 规则会智能识别可以类型推导的情况,避免过度检测
160
188
  - 建议在 TypeScript 项目中设置合理的限制,鼓励使用具体类型
@@ -165,6 +193,7 @@ interface MyInterface {
165
193
  禁止在初始变量定义时使用 `as` 类型断言,强制使用显式类型注解。
166
194
 
167
195
  **配置示例:**
196
+
168
197
  ```javascript
169
198
  // 基本用法
170
199
  '@foundbyte/no-initial-as-assertion': 'error'
@@ -177,6 +206,7 @@ interface MyInterface {
177
206
  ```
178
207
 
179
208
  **示例:**
209
+
180
210
  ```typescript
181
211
  // ✅ 正确 - 使用显式类型注解
182
212
  const a: Ref<string> = ref()
@@ -191,13 +221,16 @@ const c = reactive({}) as Reactive<object>
191
221
  const d = ref({} as { a: string }) // 嵌套的类型断言也会被检测到
192
222
 
193
223
  // ✅ 当 excludeVariableAssertions 为 true 时 - 变量断言被排除
194
- interface ID { a?: string, b?: string }
224
+ interface ID {
225
+ a?: string
226
+ b?: string
227
+ }
195
228
  const d: ID = {}
196
229
  const e = d as { a?: string } // 不会报错
197
230
 
198
231
  // ✅ 当 excludeMethodReturns 包含 'map' 时 - 数组方法返回类型断言被排除
199
232
  const arr = [1, 2, 3]
200
- const mapped = arr.map(x => x * 2) as number[] // 不会报错
233
+ const mapped = arr.map((x) => x * 2) as number[] // 不会报错
201
234
 
202
235
  // ✅ 当 excludeMethodReturns 包含 'parse' 时 - JSON.parse 返回类型断言被排除
203
236
  const jsonString = '{"name": "test"}'
@@ -221,6 +254,7 @@ const parsed = JSON.parse(jsonString) as { name: string } // 不会报错
221
254
  - 示例:`['^const$', '^Readonly<.*>$']` 可以排除 `as const` 和 `as Readonly<...>`
222
255
 
223
256
  **注意:**
257
+
224
258
  - 该规则检查变量声明时的所有类型断言,包括嵌套在函数调用参数中的类型断言
225
259
  - 推荐使用显式类型注解,因为类型断言会绕过 TypeScript 的类型检查
226
260
  - 对于函数调用返回值的类型断言,可以通过配置选项排除特定情况
@@ -230,11 +264,13 @@ const parsed = JSON.parse(jsonString) as { name: string } // 不会报错
230
264
  要求函数体之间必须空行。
231
265
 
232
266
  **配置示例:**
267
+
233
268
  ```javascript
234
269
  '@foundbyte/arrow-function-padding': 'error' // 总是要求空行
235
270
  ```
236
271
 
237
272
  **示例:**
273
+
238
274
  ```javascript
239
275
  // ✅ 正确
240
276
  const one = () => {}
@@ -251,12 +287,13 @@ const two = () => {}
251
287
  要求 React/Vue Hooks 必须使用箭头函数。
252
288
 
253
289
  **示例:**
290
+
254
291
  ```javascript
255
292
  // ✅ 正确
256
293
  const useMyHook = () => {
257
294
  const [state, setState] = useState()
258
295
  return state
259
- };
296
+ }
260
297
 
261
298
  // ❌ 错误
262
299
  function useMyHook() {
@@ -270,17 +307,19 @@ function useMyHook() {
270
307
  限制 Hooks 文件内部成员的导出数量。
271
308
 
272
309
  **配置示例:**
310
+
273
311
  ```javascript
274
312
  '@foundbyte/hooks-export-limit': ['error', { maxExports: 2 }] // 最大允许的导出数量
275
313
  ```
276
314
 
277
315
  **示例:**
316
+
278
317
  ```javascript
279
318
  // ✅ 正确 - 只有一个导出
280
319
  export const useMyHook = () => {
281
320
  const a = ''
282
321
  return {
283
- a
322
+ a,
284
323
  }
285
324
  }
286
325
 
@@ -290,7 +329,7 @@ export const useMyHook = () => {
290
329
  const b = ''
291
330
  return {
292
331
  a,
293
- b
332
+ b,
294
333
  }
295
334
  }
296
335
  ```
@@ -300,8 +339,9 @@ export const useMyHook = () => {
300
339
  要求复杂类型定义必须有类型注释。
301
340
 
302
341
  **配置示例:**
342
+
303
343
  ```javascript
304
- '@foundbyte/type-comment-requirement': ['error', {
344
+ '@foundbyte/type-comment-requirement': ['error', {
305
345
  /** 至少1个注释 */
306
346
  minCommentsPerGroup: 1,
307
347
  /** 每3个类型 */
@@ -310,6 +350,7 @@ export const useMyHook = () => {
310
350
  ```
311
351
 
312
352
  **示例:**
353
+
313
354
  ```ts
314
355
  // ✅ 正确 - 有类型注释
315
356
  interface user {
@@ -317,11 +358,15 @@ interface user {
317
358
  name: 'John'
318
359
  /** 年龄 */
319
360
  age: 30
320
- ddress: { city: 'NY', zip: '10001' }
361
+ ddress: { city: 'NY'; zip: '10001' }
321
362
  }
322
363
 
323
364
  // ❌ 错误 - 复杂对象没有类型注释
324
- interface user { name: 'John', age: 30, address: { city: 'NY', zip: '10001' } }
365
+ interface user {
366
+ name: 'John'
367
+ age: 30
368
+ address: { city: 'NY'; zip: '10001' }
369
+ }
325
370
  ```
326
371
 
327
372
  ### vue-script-lines-limit
@@ -329,11 +374,13 @@ interface user { name: 'John', age: 30, address: { city: 'NY', zip: '10001' } }
329
374
  限制 Vue 单文件组件中 `<script>` 标签的行数。
330
375
 
331
376
  **配置示例:**
377
+
332
378
  ```javascript
333
379
  '@foundbyte/vue-script-lines-limit': ['error', { maxLines: 200 }] // 最大允许的行数
334
380
  ```
335
381
 
336
382
  **示例:**
383
+
337
384
  ```vue
338
385
  <!-- ✅ 正确 - script 标签行数在限制内 -->
339
386
  <script>
@@ -344,8 +391,8 @@ export default {
344
391
  methods: {
345
392
  increment() {
346
393
  this.count++
347
- }
348
- }
394
+ },
395
+ },
349
396
  }
350
397
  </script>
351
398
 
@@ -360,6 +407,7 @@ export default {
360
407
  禁止使用特定的 ESLint 禁用注释,如 `eslint-disable`、`eslint-disable-next-line` 等。支持 JavaScript/TypeScript 注释和 Vue 模板中的 HTML 注释。
361
408
 
362
409
  **配置示例:**
410
+
363
411
  ```javascript
364
412
  '@foundbyte/no-disabled-comments': ['error', {
365
413
  patterns: [
@@ -373,6 +421,7 @@ export default {
373
421
  ```
374
422
 
375
423
  **支持的注释类型:**
424
+
376
425
  1. **JavaScript/TypeScript 注释**:
377
426
  - 行注释:`// eslint-disable-next-line`
378
427
  - 块注释:`/* eslint-disable */`
@@ -382,11 +431,13 @@ export default {
382
431
  - `<!-- eslint-disable-next-line -->`
383
432
 
384
433
  **检测的注释内容:**
434
+
385
435
  - 注释内容经过 `trim()` 处理,去除前后空白字符
386
436
  - 支持正则表达式模糊匹配
387
437
  - 支持多个模式同时匹配
388
438
 
389
439
  **示例:**
440
+
390
441
  ```javascript
391
442
  // ✅ 正确 - 不匹配任何禁用模式
392
443
  // 普通注释
@@ -434,11 +485,13 @@ const x: any = 1;
434
485
  - 当为 true 时,匹配区分大小写
435
486
 
436
487
  **错误消息:**
488
+
437
489
  - `禁止使用注释: "eslint-disable-next-line",如必须排除,请在 .eslintignore 中排除,并写清楚排除原因`
438
490
  - `禁止使用注释: "eslint-disable",如必须排除,请在 .eslintignore 中排除,并写清楚排除原因`
439
491
  - `禁止使用注释: "eslint-disable-next-line @typescript-eslint/no-explicit-any",如必须排除,请在 .eslintignore 中排除,并写清楚排除原因`
440
492
 
441
493
  **注意:**
494
+
442
495
  - 该规则会检查所有 JavaScript/TypeScript 注释和 Vue 模板中的 HTML 注释
443
496
  - 对于 Vue 文件,规则会同时检查 `<script>` 中的 JavaScript 注释和 `<template>` 中的 HTML 注释
444
497
  - 建议只禁用特定的、有问题的规则,而不是使用无参数的 `eslint-disable` 禁用所有规则
@@ -449,6 +502,7 @@ const x: any = 1;
449
502
  通过正则表达式配置禁止使用某些组件,支持自定义错误消息。
450
503
 
451
504
  **配置示例:**
505
+
452
506
  ```javascript
453
507
  '@foundbyte/no-restricted-components': ['error', [
454
508
  {
@@ -463,6 +517,7 @@ const x: any = 1;
463
517
  ```
464
518
 
465
519
  **支持的语法:**
520
+
466
521
  - 正则表达式字符串:以 `/` 开头和结尾,如 `/^a-(textarea|table)$/`
467
522
  - 普通字符串:自动转义并创建正则表达式,如 `'a-textarea'`
468
523
  - 支持自闭合标签:`<a-textarea />`
@@ -471,14 +526,18 @@ const x: any = 1;
471
526
  - 只对 .vue 文件生效,不会影响其他文件类型
472
527
 
473
528
  **示例:**
529
+
474
530
  ```vue
475
531
  <!-- ✅ 正确 - 不匹配任何禁止模式的组件 -->
476
532
  <div>正常内容</div>
533
+
477
534
  <span>正常内容</span>
535
+
478
536
  <custom-component />
479
537
 
480
538
  <!-- ❌ 错误 - 匹配 a-textarea 模式 -->
481
539
  <a-textarea />
540
+
482
541
  <a-textarea></a-textarea>
483
542
 
484
543
  <!-- ❌ 错误 - 匹配 a-input 模式 -->
@@ -489,9 +548,11 @@ const x: any = 1;
489
548
  ```
490
549
 
491
550
  **错误消息:**
551
+
492
552
  - 显示配置中指定的自定义消息,如:`禁止使用 a-textarea 和 a-input 组件,请使用自定义组件替代`
493
553
 
494
554
  **注意:**
555
+
495
556
  - 该规则只检查 .vue 文件中的模板部分,不会检查 JavaScript/TypeScript 代码中的字符串
496
557
  - 支持同时配置多个禁止模式,每个模式独立检查
497
558
  - 正则表达式匹配组件名,不区分大小写
@@ -502,20 +563,21 @@ const x: any = 1;
502
563
  限制特定标签使用某些属性或属性值。
503
564
 
504
565
  **配置示例:**
566
+
505
567
  ```javascript
506
568
  '@foundbyte/tag-attribute-restriction': ['error', {
507
569
  restrictions: [
508
570
  {
509
571
  tag: 'image', // 标签名称
510
572
  attributes: [ // 属性限制列表
511
- {
573
+ {
512
574
  name: 'mode', // 属性名称
513
575
  disallowedValues: [ // 禁止的值列表(可选)
514
- '',
576
+ '',
515
577
  'scaleToFill'
516
- ]
578
+ ]
517
579
  },
518
- {
580
+ {
519
581
  name: 'lazy-load' // 完全禁止该属性
520
582
  }
521
583
  ]
@@ -525,6 +587,7 @@ const x: any = 1;
525
587
  ```
526
588
 
527
589
  **支持的语法:**
590
+
528
591
  - 静态属性: `<image mode="" />`
529
592
  - 动态属性: `<image :mode="'scaleToFill'" />`
530
593
  - 无值属性: `<image lazy-load />`
@@ -532,6 +595,7 @@ const x: any = 1;
532
595
  - JSX 属性
533
596
 
534
597
  **示例:**
598
+
535
599
  ```vue
536
600
  <!-- ✅ 正确 -->
537
601
  <image src="test.jpg" mode="aspectFit" />
@@ -547,5 +611,121 @@ const x: any = 1;
547
611
  ```
548
612
 
549
613
  **错误消息:**
614
+
550
615
  - `标签 <image> 不允许使用属性 mode` - 当属性被完全禁止时
551
616
  - `标签 <image> 的属性 mode 不允许使用值 ""` - 当特定属性值被禁止时
617
+
618
+ ### require-json-parse-type
619
+
620
+ 要求 `JSON.parse` 的返回值在赋值给变量时必须带有显式类型注解。
621
+
622
+ **配置示例:**
623
+
624
+ ```javascript
625
+ // 基本用法 - 检查 const、let、var
626
+ '@foundbyte/require-json-parse-type': 'error'
627
+
628
+ // 只检查 const
629
+ '@foundbyte/require-json-parse-type': ['error', { declarationTypes: ['const'] }]
630
+ ```
631
+
632
+ **示例:**
633
+
634
+ ```typescript
635
+ // ✅ 正确 - 变量带有显式类型注解
636
+ const a: { s: number } = JSON.parse('{s:1}')
637
+
638
+ // ✅ 正确 - 解构赋值带有显式类型注解
639
+ const { s }: { s: number } = JSON.parse('{s:1}')
640
+
641
+ // ✅ 正确 - 三元表达式中的 JSON.parse 带有显式类型注解
642
+ const qualification: string[] = info.qualification ? JSON.parse(info.qualification) : []
643
+
644
+ // ❌ 错误 - 缺少类型注解
645
+ const a = JSON.parse('{s:1}')
646
+
647
+ // ❌ 错误 - 三元表达式中的 JSON.parse 缺少类型注解
648
+ const qualification = info.qualification ? JSON.parse(info.qualification) : []
649
+
650
+ // ❌ 错误 - 使用 as 断言不算作变量声明类型注解
651
+ const a = JSON.parse('{s:1}') as { s: number }
652
+ ```
653
+
654
+ **配置选项:**
655
+
656
+ - `declarationTypes` (array, 默认 `['const', 'let', 'var']`): 要检查的变量声明类型
657
+ - 可选项:`const`、`let`、`var`
658
+ - 例如设置为 `['const']` 则只检查 `const` 声明
659
+
660
+ **注意:**
661
+
662
+ - 该规则检查变量声明的初始化表达式中是否包含 `JSON.parse(...)`,包括三元表达式、逻辑表达式、对象/数组字面量等场景
663
+ - 对于 `const a = JSON.parse('...') as Type` 也会报错,因为它要求类型注解必须声明在变量本身
664
+ - 函数体内部的 `JSON.parse` 不影响外层变量,不会被误判
665
+ - 如希望允许 `as` 类型断言,请搭配 `@foundbyte/no-initial-as-assertion` 规则按需配置
666
+
667
+ ### no-restricted-literal-types
668
+
669
+ 禁止在类型注解中使用指定的无意义字面量类型,例如 `null`、`undefined`、空字符串字面量 `''` 等。
670
+
671
+ **配置示例:**
672
+
673
+ ```javascript
674
+ // 基本用法 - 禁止 null、undefined 和空字符串字面量类型
675
+ '@foundbyte/no-restricted-literal-types': ['error', {
676
+ forbidden: ['null', 'undefined', ''],
677
+ }]
678
+
679
+ // 允许联合类型中的 null / undefined
680
+ '@foundbyte/no-restricted-literal-types': ['error', {
681
+ forbidden: ['null', 'undefined'],
682
+ allowInUnions: true,
683
+ }]
684
+ ```
685
+
686
+ **示例:**
687
+
688
+ ```typescript
689
+ // ❌ 错误 - 无意义的 null 类型
690
+ interface OrderItem {
691
+ seatName: null
692
+ refundStatus: null
693
+ }
694
+
695
+ // ❌ 错误 - 无意义的 undefined 类型
696
+ interface Config {
697
+ value: undefined
698
+ }
699
+
700
+ // ❌ 错误 - 无意义的空字符串字面量类型
701
+ interface Query {
702
+ keyword: ''
703
+ }
704
+
705
+ // ✅ 正确 - 使用具体类型
706
+ interface OrderItem {
707
+ seatName: string
708
+ refundStatus: number
709
+ }
710
+
711
+ // ✅ 正确 - 联合类型中的 null / undefined 是有意义的(当 allowInUnions 为 true 时)
712
+ interface User {
713
+ phone: string | null
714
+ email?: string
715
+ }
716
+ ```
717
+
718
+ **配置选项:**
719
+
720
+ - `forbidden` (array, 必填): 要禁止的字面量类型列表
721
+ - 支持关键字类型:`'null'`、`'undefined'`、`'void'`、`'never'`、`'any'`、`'unknown'`、`'boolean'`、`'number'`、`'string'`、`'symbol'`、`'bigint'`、`'object'`
722
+ - 支持字面量值:`''`、`0`、`true`、`false` 等
723
+
724
+ - `allowInUnions` (boolean, 默认 `false`): 是否允许在联合类型中使用
725
+ - 为 `true` 时,`string | null` 不会被报错,但独立的 `null` 类型仍然会报错
726
+ - 为 `false` 时,任何出现 `null` / `undefined` 等的地方都会报错
727
+
728
+ **注意:**
729
+
730
+ - 该规则针对类型注解中的字面量,不影响运行时值,例如 `const a = null` 不会触发
731
+ - 建议配合 TypeScript 严格模式使用,逐步替换项目中的无意义类型声明
package/index.d.ts CHANGED
@@ -1,8 +1,13 @@
1
- import { ESLint } from 'eslint'
1
+ import { ESLint, Rule } from 'eslint'
2
2
  import { Rules } from './lib/rules'
3
- import { Rule } from 'eslint'
4
3
 
5
- export { ArrowFunctionPaddingOptions, HooksExportLimitOptions, TypeCommentRequirementOptions, VueScriptLinesLimitOptions, HooksArrowFunctionOnlyOptions } from './types'
4
+ export {
5
+ ArrowFunctionPaddingOptions,
6
+ HooksArrowFunctionOnlyOptions,
7
+ HooksExportLimitOptions,
8
+ TypeCommentRequirementOptions,
9
+ VueScriptLinesLimitOptions,
10
+ } from './types'
6
11
 
7
12
  declare const plugin: ESLint.Plugin & {
8
13
  rules: Rules
@@ -11,4 +16,4 @@ declare const plugin: ESLint.Plugin & {
11
16
  }
12
17
  }
13
18
 
14
- export { type Rules, plugin as default }
19
+ export { plugin as default, type Rules }
package/index.js CHANGED
@@ -8,30 +8,27 @@ const noInitialAsAssertionRule = require('./lib/rules/no-initial-as-assertion')
8
8
  const noExcessiveAnyRule = require('./lib/rules/no-excessive-any')
9
9
  const noDisabledCommentsRule = require('./lib/rules/no-disabled-comments')
10
10
  const noRestrictedComponentsRule = require('./lib/rules/no-restricted-components')
11
+ const requireJsonParseTypeRule = require('./lib/rules/require-json-parse-type')
12
+ const noRestrictedLiteralTypesRule = require('./lib/rules/no-restricted-literal-types')
11
13
 
12
14
  // 限制属性,同时相当于限制某些组件禁止使用
13
15
  const restrictions = [
14
16
  {
15
17
  // 禁止<image />直接使用空的mode: <image mode="" />
16
18
  tag: 'image',
17
- attributes: [
18
- { name: 'mode', disallowedValues: [''] },
19
- ]
19
+ attributes: [{ name: 'mode', disallowedValues: [''] }],
20
20
  },
21
21
  {
22
22
  tag: 'page-meta',
23
23
  attributes: [
24
24
  // harmony 不兼容
25
- { name: 'page-style' }
26
- ]
25
+ { name: 'page-style' },
26
+ ],
27
27
  },
28
28
  {
29
29
  // 禁止<kb-pro-table /> 使用v-bind,请使用main.ts全局配置
30
30
  tag: 'kb-pro-table',
31
- attributes: [
32
- { name: 'v-bind' },
33
- { name: 'bordered' },
34
- ],
31
+ attributes: [{ name: 'v-bind' }, { name: 'bordered' }],
35
32
  },
36
33
  ]
37
34
 
@@ -47,6 +44,8 @@ module.exports = {
47
44
  'no-excessive-any': noExcessiveAnyRule,
48
45
  'no-disabled-comments': noDisabledCommentsRule,
49
46
  'no-restricted-components': noRestrictedComponentsRule,
47
+ 'require-json-parse-type': requireJsonParseTypeRule,
48
+ 'no-restricted-literal-types': noRestrictedLiteralTypesRule,
50
49
  },
51
50
  configs: {
52
51
  customize: (options) => {
@@ -57,38 +56,50 @@ module.exports = {
57
56
  '@foundbyte/vue-script-lines-limit': ['error', { maxLines: 300 }],
58
57
  '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }],
59
58
  '@foundbyte/hooks-export-limit': ['error', { maxExports: 20 }],
60
- '@foundbyte/no-initial-as-assertion': ['error', {
61
- excludeVariableAssertions: true,
62
- excludeTypePatterns: ['^const$', '^[A-Z]$', '^unknown$', '^EAppEnv$'],
63
- excludeMethodReturns: ['parse', 'querySelector'],
64
- }],
65
- '@foundbyte/no-restricted-components': ['error', [
59
+ '@foundbyte/no-initial-as-assertion': [
60
+ 'error',
66
61
  {
67
- pattern: '/^a-(textarea|table)$/',
68
- message: '禁止使用的Arco Design组件,请使用Keyblade组件替代'
69
- }
70
- ]],
62
+ excludeVariableAssertions: true,
63
+ excludeTypePatterns: ['^const$', '^[A-Z]$', '^unknown$', '^EAppEnv$'],
64
+ excludeMethodReturns: ['parse', 'querySelector'],
65
+ },
66
+ ],
67
+ '@foundbyte/no-restricted-components': [
68
+ 'error',
69
+ [
70
+ {
71
+ pattern: '/^a-(textarea|table)$/',
72
+ message: '禁止使用的Arco Design组件,请使用Keyblade组件替代',
73
+ },
74
+ ],
75
+ ],
71
76
  '@foundbyte/tag-attribute-restriction': ['error', { restrictions }],
72
77
  // 类型加注释
73
- '@foundbyte/type-comment-requirement': ['error', {
74
- minCommentsPerGroup: 1,
75
- groupSize: 8,
76
- includeTypes: true,
77
- includeInterfaces: true,
78
- }],
78
+ '@foundbyte/type-comment-requirement': [
79
+ 'error',
80
+ {
81
+ minCommentsPerGroup: 1,
82
+ groupSize: 8,
83
+ includeTypes: true,
84
+ includeInterfaces: true,
85
+ },
86
+ ],
79
87
  // 禁止使用某些注释
80
- '@foundbyte/no-disabled-comments': ['error', {
81
- patterns: [
82
- '/^\\s*eslint-disable-next-line\\s*$/',
83
- '/^\\s*eslint-disable\\s*$/',
84
- '/no-explicit-any/',
85
- '/vue-script-lines-limit/',
86
- '/hooks-export-limit/',
87
- ],
88
- }],
88
+ '@foundbyte/no-disabled-comments': [
89
+ 'error',
90
+ {
91
+ patterns: [
92
+ '/^\\s*eslint-disable-next-line\\s*$/',
93
+ '/^\\s*eslint-disable\\s*$/',
94
+ '/no-explicit-any/',
95
+ '/vue-script-lines-limit/',
96
+ '/hooks-export-limit/',
97
+ ],
98
+ },
99
+ ],
89
100
  ...(options || {}),
90
- }
101
+ },
91
102
  }
92
- }
93
- }
103
+ },
104
+ },
94
105
  }
@@ -1,15 +1,15 @@
1
+ import { Rule } from 'eslint'
2
+
1
3
  export { ArrowFunctionPaddingOptions } from './arrow-function-padding'
2
- export { HooksExportLimitOptions } from './hooks-export-limit'
3
- export { TypeCommentRequirementOptions } from './type-comment-requirement'
4
- export { VueScriptLinesLimitOptions } from './vue-script-lines-limit'
5
4
  export { HooksArrowFunctionOnlyOptions } from './hooks-arrow-function-only'
6
- export { TagAttributeRestrictionOptions } from './tag-attribute-restriction'
7
- export { NoInitialAsAssertionOptions } from './no-initial-as-assertion'
8
- export { NoExcessiveAnyOptions } from './no-excessive-any'
5
+ export { HooksExportLimitOptions } from './hooks-export-limit'
9
6
  export { NoDisabledCommentsOptions } from './no-disabled-comments'
7
+ export { NoExcessiveAnyOptions } from './no-excessive-any'
8
+ export { NoInitialAsAssertionOptions } from './no-initial-as-assertion'
10
9
  export { NoRestrictedComponentsOptions } from './no-restricted-components'
11
-
12
- import { Rule } from 'eslint'
10
+ export { TagAttributeRestrictionOptions } from './tag-attribute-restriction'
11
+ export { TypeCommentRequirementOptions } from './type-comment-requirement'
12
+ export { VueScriptLinesLimitOptions } from './vue-script-lines-limit'
13
13
 
14
14
  /**
15
15
  * @foundbyte/eslint-plugin 规则配置
@@ -2,21 +2,21 @@ import { Rule } from 'eslint'
2
2
 
3
3
  export interface NoInitialAsAssertionOptions {
4
4
  /**
5
- * 是否排除对变量的 as 断言
6
- * @default false
7
- */
5
+ * 是否排除对变量的 as 断言
6
+ * @default false
7
+ */
8
8
  excludeVariableAssertions?: boolean
9
9
 
10
10
  /**
11
- * 排除特定方法返回类型的 as 断言
12
- * @default []
13
- */
11
+ * 排除特定方法返回类型的 as 断言
12
+ * @default []
13
+ */
14
14
  excludeMethodReturns?: string[]
15
15
 
16
16
  /**
17
- * 排除匹配正则表达式模式的类型断言
18
- * @default []
19
- */
17
+ * 排除匹配正则表达式模式的类型断言
18
+ * @default []
19
+ */
20
20
  excludeTypePatterns?: string[]
21
21
  }
22
22
 
@@ -0,0 +1,22 @@
1
+ import { Rule } from 'eslint'
2
+
3
+ export interface NoRestrictedLiteralTypesOptions {
4
+ /**
5
+ * 要禁止的字面量类型列表
6
+ * @example ['null', 'undefined', '']
7
+ */
8
+ forbidden: Array<string | number | boolean>
9
+
10
+ /**
11
+ * 是否允许在联合类型中使用,例如 string | null
12
+ * @default false
13
+ */
14
+ allowInUnions?: boolean
15
+ }
16
+
17
+ /**
18
+ * 禁止在类型注解中使用指定的字面量类型
19
+ */
20
+ declare const rule: Rule.RuleModule
21
+
22
+ export default rule
@@ -0,0 +1,158 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ docs: {
5
+ description: '禁止在类型注解中使用指定的字面量类型',
6
+ category: 'Best Practices',
7
+ recommended: false,
8
+ },
9
+ fixable: null,
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ forbidden: {
15
+ type: 'array',
16
+ items: {
17
+ anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }],
18
+ },
19
+ description:
20
+ '要禁止的字面量类型列表,支持 null、undefined、void、never、any、unknown 等关键字,以及空字符串、0、true 等具体字面量',
21
+ },
22
+ allowInUnions: {
23
+ type: 'boolean',
24
+ default: false,
25
+ description: '是否允许在联合类型中使用,例如 string | null',
26
+ },
27
+ },
28
+ required: ['forbidden'],
29
+ additionalProperties: false,
30
+ },
31
+ ],
32
+ messages: {
33
+ noRestrictedLiteralType: '禁止在类型中使用 "{{type}}" 字面量类型',
34
+ },
35
+ },
36
+
37
+ create(context) {
38
+ const options = context.options[0] || {}
39
+ const forbidden = options.forbidden || []
40
+ const allowInUnions = options.allowInUnions || false
41
+
42
+ if (forbidden.length === 0) {
43
+ return {}
44
+ }
45
+
46
+ const forbiddenStrings = new Set()
47
+ const forbiddenNumbers = new Set()
48
+ const forbiddenBooleans = new Set()
49
+
50
+ forbidden.forEach((item) => {
51
+ if (typeof item === 'string') {
52
+ forbiddenStrings.add(item)
53
+ } else if (typeof item === 'number') {
54
+ forbiddenNumbers.add(item)
55
+ } else if (typeof item === 'boolean') {
56
+ forbiddenBooleans.add(item)
57
+ }
58
+ })
59
+
60
+ // 关键字类型映射
61
+ const keywordTypeMap = {
62
+ null: 'TSNullKeyword',
63
+ undefined: 'TSUndefinedKeyword',
64
+ void: 'TSVoidKeyword',
65
+ never: 'TSNeverKeyword',
66
+ any: 'TSAnyKeyword',
67
+ unknown: 'TSUnknownKeyword',
68
+ boolean: 'TSBooleanKeyword',
69
+ number: 'TSNumberKeyword',
70
+ string: 'TSStringKeyword',
71
+ symbol: 'TSSymbolKeyword',
72
+ bigint: 'TSBigIntKeyword',
73
+ object: 'TSObjectKeyword',
74
+ }
75
+
76
+ /**
77
+ * 检查类型是否位于联合类型中
78
+ * @param {Object} node AST 节点
79
+ * @returns {boolean}
80
+ */
81
+ function isInUnion(node) {
82
+ return node.parent && node.parent.type === 'TSUnionType'
83
+ }
84
+
85
+ /**
86
+ * 获取匹配到的 forbidden 值
87
+ * @param {Object} node AST 节点
88
+ * @returns {string|number|boolean|null}
89
+ */
90
+ function getMatchedForbidden(node) {
91
+ if (allowInUnions && isInUnion(node)) {
92
+ return null
93
+ }
94
+
95
+ // 关键字类型检查
96
+ for (const [key, typeName] of Object.entries(keywordTypeMap)) {
97
+ if (forbiddenStrings.has(key) && node.type === typeName) {
98
+ return key
99
+ }
100
+ }
101
+
102
+ // 字面量类型检查
103
+ if (node.type === 'TSLiteralType' && node.literal) {
104
+ const literal = node.literal
105
+
106
+ if (literal.type === 'Literal') {
107
+ if (typeof literal.value === 'string' && forbiddenStrings.has(literal.value)) {
108
+ return literal.value
109
+ }
110
+ if (typeof literal.value === 'number' && forbiddenNumbers.has(literal.value)) {
111
+ return literal.value
112
+ }
113
+ if (typeof literal.value === 'boolean' && forbiddenBooleans.has(literal.value)) {
114
+ return literal.value
115
+ }
116
+ }
117
+ }
118
+
119
+ return null
120
+ }
121
+
122
+ const targetTypes = [
123
+ 'TSNullKeyword',
124
+ 'TSUndefinedKeyword',
125
+ 'TSVoidKeyword',
126
+ 'TSNeverKeyword',
127
+ 'TSAnyKeyword',
128
+ 'TSUnknownKeyword',
129
+ 'TSBooleanKeyword',
130
+ 'TSNumberKeyword',
131
+ 'TSStringKeyword',
132
+ 'TSSymbolKeyword',
133
+ 'TSBigIntKeyword',
134
+ 'TSObjectKeyword',
135
+ 'TSLiteralType',
136
+ ]
137
+
138
+ const visitors = {}
139
+ targetTypes.forEach((type) => {
140
+ visitors[type] = function (node) {
141
+ const matched = getMatchedForbidden(node)
142
+ if (matched === null) {
143
+ return
144
+ }
145
+
146
+ context.report({
147
+ node,
148
+ messageId: 'noRestrictedLiteralType',
149
+ data: {
150
+ type: String(matched),
151
+ },
152
+ })
153
+ }
154
+ })
155
+
156
+ return visitors
157
+ },
158
+ }
@@ -0,0 +1,16 @@
1
+ import { Rule } from 'eslint'
2
+
3
+ export interface RequireJsonParseTypeOptions {
4
+ /**
5
+ * 要检查的变量声明类型
6
+ * @default ['const', 'let', 'var']
7
+ */
8
+ declarationTypes?: Array<'const' | 'let' | 'var'>
9
+ }
10
+
11
+ /**
12
+ * 要求 JSON.parse 的返回值必须声明显式类型
13
+ */
14
+ declare const rule: Rule.RuleModule
15
+
16
+ export default rule
@@ -0,0 +1,156 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description: '要求 JSON.parse 的返回值必须声明显式类型',
6
+ category: 'Best Practices',
7
+ recommended: false,
8
+ },
9
+ fixable: null,
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ declarationTypes: {
15
+ type: 'array',
16
+ items: {
17
+ type: 'string',
18
+ enum: ['const', 'let', 'var'],
19
+ },
20
+ default: ['const', 'let', 'var'],
21
+ description: '要检查的变量声明类型',
22
+ },
23
+ },
24
+ additionalProperties: false,
25
+ },
26
+ ],
27
+ messages: {
28
+ requireJsonParseType:
29
+ "JSON.parse 的返回值必须声明显式类型,例如:const a: { s: number } = JSON.parse('...')",
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ const options = context.options[0] || {}
35
+ const declarationTypes = options.declarationTypes || ['const', 'let', 'var']
36
+
37
+ /**
38
+ * 判断调用表达式是否是 JSON.parse(...)
39
+ * @param {Object} node AST 节点
40
+ * @returns {boolean}
41
+ */
42
+ function isJsonParseCall(node) {
43
+ if (!node || node.type !== 'CallExpression') {
44
+ return false
45
+ }
46
+
47
+ const callee = node.callee
48
+ if (!callee || callee.type !== 'MemberExpression') {
49
+ return false
50
+ }
51
+
52
+ const object = callee.object
53
+ const property = callee.property
54
+
55
+ return (
56
+ object &&
57
+ object.type === 'Identifier' &&
58
+ object.name === 'JSON' &&
59
+ property &&
60
+ property.type === 'Identifier' &&
61
+ property.name === 'parse'
62
+ )
63
+ }
64
+
65
+ /**
66
+ * 从变量声明的初始化表达式中取出最底层的调用表达式
67
+ * 用于处理 const a = JSON.parse('...') as Type 这种情况
68
+ * @param {Object} node AST 节点
69
+ * @returns {Object|null}
70
+ */
71
+ function unwrapExpression(node) {
72
+ if (!node) {
73
+ return null
74
+ }
75
+
76
+ if (node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression') {
77
+ return unwrapExpression(node.expression)
78
+ }
79
+
80
+ return node
81
+ }
82
+
83
+ /**
84
+ * 递归检查表达式中是否包含 JSON.parse(...) 调用
85
+ * 用于处理三元表达式、逻辑表达式、对象/数组字面量等场景
86
+ * @param {Object} node AST 节点
87
+ * @returns {boolean}
88
+ */
89
+ function containsJsonParse(node) {
90
+ if (!node || typeof node !== 'object') {
91
+ return false
92
+ }
93
+
94
+ const unwrapped = unwrapExpression(node)
95
+ if (isJsonParseCall(unwrapped)) {
96
+ return true
97
+ }
98
+
99
+ // 不进入函数体内部,函数体是独立作用域,不影响外层变量类型
100
+ if (unwrapped.type === 'ArrowFunctionExpression' || unwrapped.type === 'FunctionExpression') {
101
+ return false
102
+ }
103
+
104
+ return Object.keys(unwrapped).some((key) => {
105
+ if (key === 'parent' || key === 'loc' || key === 'range') {
106
+ return false
107
+ }
108
+
109
+ const value = unwrapped[key]
110
+ if (Array.isArray(value)) {
111
+ return value.some((child) => containsJsonParse(child))
112
+ }
113
+ if (value && typeof value === 'object' && value.type) {
114
+ return containsJsonParse(value)
115
+ }
116
+ return false
117
+ })
118
+ }
119
+
120
+ /**
121
+ * 检查变量声明是否使用了 JSON.parse 并且缺少显式类型注解
122
+ * @param {Object} node VariableDeclaration 节点
123
+ */
124
+ function checkVariableDeclaration(node) {
125
+ if (!declarationTypes.includes(node.kind)) {
126
+ return
127
+ }
128
+
129
+ node.declarations.forEach((declaration) => {
130
+ if (!declaration.init) {
131
+ return
132
+ }
133
+
134
+ if (!containsJsonParse(declaration.init)) {
135
+ return
136
+ }
137
+
138
+ // 检查变量声明是否有显式类型注解
139
+ // 支持普通变量:const a: Type = ...
140
+ // 支持解构:const { a }: Type = ...
141
+ if (declaration.id && declaration.id.typeAnnotation) {
142
+ return
143
+ }
144
+
145
+ context.report({
146
+ node: declaration,
147
+ messageId: 'requireJsonParseType',
148
+ })
149
+ })
150
+ }
151
+
152
+ return {
153
+ VariableDeclaration: checkVariableDeclaration,
154
+ }
155
+ },
156
+ }
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
- "name": "@foundbyte/eslint-plugin",
3
- "version": "1.1.4",
4
- "description": "ESLint plugin for enforcing code style rules including arrow function padding, Vue script lines limit, and TypeScript type comment requirements",
5
- "main": "index.js",
6
- "types": "index.d.ts",
7
- "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1"
9
- },
10
- "keywords": [
11
- "eslint",
12
- "eslintplugin",
13
- "arrow-function",
14
- "padding",
15
- "vue",
16
- "script",
17
- "lines-limit",
18
- "typescript",
19
- "type-comment",
20
- "comment-requirement"
21
- ],
22
- "author": "",
23
- "license": "ISC",
24
- "publishConfig": {
25
- "access": "public"
26
- },
27
- "peerDependencies": {
28
- "eslint": ">=8.0.0"
29
- }
30
- }
2
+ "name": "@foundbyte/eslint-plugin",
3
+ "version": "1.1.5-alpha.1",
4
+ "description": "ESLint plugin for enforcing code style rules including arrow function padding, Vue script lines limit, and TypeScript type comment requirements",
5
+ "author": "",
6
+ "license": "ISC",
7
+ "keywords": [
8
+ "eslint",
9
+ "eslintplugin",
10
+ "arrow-function",
11
+ "padding",
12
+ "vue",
13
+ "script",
14
+ "lines-limit",
15
+ "typescript",
16
+ "type-comment",
17
+ "comment-requirement"
18
+ ],
19
+ "main": "index.js",
20
+ "types": "index.d.ts",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "peerDependencies": {
25
+ "eslint": ">=8.0.0"
26
+ },
27
+ "scripts": {
28
+ "test": "echo \"Error: no test specified\" && exit 1"
29
+ }
30
+ }
package/types.d.ts CHANGED
@@ -1,2 +1,10 @@
1
1
  // 导出所有规则配置类型
2
- export type { ArrowFunctionPaddingOptions, HooksExportLimitOptions, TypeCommentRequirementOptions, VueScriptLinesLimitOptions, HooksArrowFunctionOnlyOptions, NoExcessiveAnyOptions, NoDisabledCommentsOptions } from './lib/rules'
2
+ export type {
3
+ ArrowFunctionPaddingOptions,
4
+ HooksArrowFunctionOnlyOptions,
5
+ HooksExportLimitOptions,
6
+ NoDisabledCommentsOptions,
7
+ NoExcessiveAnyOptions,
8
+ TypeCommentRequirementOptions,
9
+ VueScriptLinesLimitOptions,
10
+ } from './lib/rules'