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

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
@@ -25,7 +25,7 @@ module.exports = {
25
25
  '@foundbyte/arrow-function-padding': 'error',
26
26
  '@foundbyte/no-initial-as-assertion': ['error', {
27
27
  excludeVariableAssertions: true,
28
- excludeTypePatterns: ['^const$', '^[A-Z]$'],
28
+ excludeTypePatterns: ['^const$', '^[A-Z]$', '^unknown$'],
29
29
  excludeMethodReturns: ['parse', 'querySelector']
30
30
  }],
31
31
  '@foundbyte/hooks-arrow-function-only': 'error',
@@ -26,11 +26,16 @@ module.exports = {
26
26
  },
27
27
 
28
28
  create(context) {
29
- const options = context.options[0] || {}
30
- const maxAnyCount = options.maxAnyCount || 0
29
+ const options = context.options[0] || {}
30
+ const maxAnyCount = options.maxAnyCount || 0
31
31
 
32
- // 存储 any 类型使用的位置
33
- const anyUsages = []
32
+ // 导入共享工具函数
33
+ const { isBuiltInFunction, isSimpleCollectionInstantiation } = require('../utils/is-built-in-function')
34
+
35
+ // 存储 any 类型使用的位置
36
+ const anyUsages = []
37
+ // 存储 any 类型的变量名
38
+ const anyVariables = new Set()
34
39
 
35
40
  /**
36
41
  * 检查文件路径是否应该被排除
@@ -62,6 +67,13 @@ module.exports = {
62
67
  })
63
68
  }
64
69
 
70
+ // 检查类型参数实例化(如 ref<any[]> 中的 <any[]>)
71
+ if (node.type === 'TSTypeParameterInstantiation' && node.params) {
72
+ node.params.forEach(param => {
73
+ checkTypeNodeForAny(param, parentNode || node)
74
+ })
75
+ }
76
+
65
77
  // 检查联合类型和交叉类型
66
78
  if ((node.type === 'TSUnionType' || node.type === 'TSIntersectionType') && node.types) {
67
79
  node.types.forEach(type => {
@@ -85,6 +97,32 @@ module.exports = {
85
97
  checkTypeNodeForAny(node.returnType.typeAnnotation, node)
86
98
  }
87
99
  }
100
+
101
+ // 检查元组类型
102
+ if (node.type === 'TSTupleType' && node.elementTypes) {
103
+ node.elementTypes.forEach(elementType => {
104
+ checkTypeNodeForAny(elementType, node)
105
+ })
106
+ }
107
+
108
+ // 检查条件类型
109
+ if (node.type === 'TSConditionalType') {
110
+ checkTypeNodeForAny(node.checkType, node)
111
+ checkTypeNodeForAny(node.extendsType, node)
112
+ checkTypeNodeForAny(node.trueType, node)
113
+ checkTypeNodeForAny(node.falseType, node)
114
+ }
115
+
116
+ // 检查索引访问类型
117
+ if (node.type === 'TSIndexedAccessType') {
118
+ checkTypeNodeForAny(node.objectType, node)
119
+ checkTypeNodeForAny(node.indexType, node)
120
+ }
121
+
122
+ // 检查映射类型
123
+ if (node.type === 'TSMappedType' && node.typeAnnotation) {
124
+ checkTypeNodeForAny(node.typeAnnotation, node)
125
+ }
88
126
  }
89
127
 
90
128
  /**
@@ -94,58 +132,18 @@ module.exports = {
94
132
  if (shouldExcludeFile()) return
95
133
  if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
96
134
  checkTypeNodeForAny(node.typeAnnotation.typeAnnotation, node)
97
- }
98
- }
99
-
100
- /**
101
- * 检查是否是内置函数
102
- */
103
- function isBuiltInFunction(node) {
104
- if (node.type !== 'CallExpression') return false
105
-
106
- // 检查全局对象的方法调用,如 JSON.parse, Promise.all 等
107
- if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier') {
108
- const objectName = node.callee.object.name
109
-
110
- // 内置全局对象列表 - 这些对象的所有方法都认为是内置的
111
- const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
112
-
113
- if (builtInObjects.includes(objectName)) {
114
- // 对于这些内置对象,假设它们的所有方法都是内置的
115
- // 这样可以避免维护庞大的方法列表
116
- return true
117
- }
118
- }
119
-
120
- // 检查可选链调用,如 spotArr?.find()
121
- if (node.callee.type === 'ChainExpression' && node.callee.expression.type === 'CallExpression') {
122
- const chainCall = node.callee.expression
123
- if (chainCall.callee.type === 'MemberExpression' && chainCall.callee.object.type === 'Identifier') {
124
- const objectName = chainCall.callee.object.name
125
-
126
- // 内置全局对象列表
127
- const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
128
-
129
- if (builtInObjects.includes(objectName)) {
130
- // 对于这些内置对象,假设它们的所有方法都是内置的
131
- return true
135
+ // 如果节点是标识符且有名称,检查类型注解中是否有 any
136
+ if (node.type === 'Identifier' && node.name) {
137
+ const typeAnnotation = node.typeAnnotation.typeAnnotation
138
+ if (typeAnnotation && typeAnnotation.type === 'TSAnyKeyword') {
139
+ // 记录 any 类型的变量名
140
+ anyVariables.add(node.name)
132
141
  }
133
142
  }
134
143
  }
135
-
136
- // 检查全局函数调用,如 parseInt, parseFloat, isNaN 等
137
- if (node.callee.type === 'Identifier') {
138
- const functionName = node.callee.name
139
- const globalFunctions = [
140
- 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'eval', 'encodeURI',
141
- 'encodeURIComponent', 'decodeURI', 'decodeURIComponent', 'escape', 'unescape'
142
- ]
143
- return globalFunctions.includes(functionName)
144
- }
145
-
146
- return false
147
144
  }
148
145
 
146
+
149
147
  /**
150
148
  * 检查表达式是否可以推导类型
151
149
  */
@@ -172,6 +170,21 @@ module.exports = {
172
170
  return true
173
171
  }
174
172
 
173
+ // 二元表达式可以推导类型(如 1 + 1)
174
+ if (node.type === 'BinaryExpression') {
175
+ return true
176
+ }
177
+
178
+ // 一元表达式可以推导类型(如 -1, !true)
179
+ if (node.type === 'UnaryExpression') {
180
+ return true
181
+ }
182
+
183
+ // 标识符通常可以推导类型(变量有类型注解)
184
+ if (node.type === 'Identifier') {
185
+ return true
186
+ }
187
+
175
188
  // 函数调用:检查参数是否可以推导类型
176
189
  if (node.type === 'CallExpression') {
177
190
  // 检查是否是第三方库函数调用
@@ -231,6 +244,15 @@ module.exports = {
231
244
  return true
232
245
  }
233
246
 
247
+ // 条件表达式(三元表达式)可以推导类型
248
+ if (node.type === 'ConditionalExpression') {
249
+ // 检查条件和两个分支是否可以推导类型
250
+ const testCanInfer = canInferTypeFromExpression(node.test)
251
+ const consequentCanInfer = canInferTypeFromExpression(node.consequent)
252
+ const alternateCanInfer = canInferTypeFromExpression(node.alternate)
253
+ return testCanInfer && consequentCanInfer && alternateCanInfer
254
+ }
255
+
234
256
  // 其他情况默认无法推导类型
235
257
  return false
236
258
  }
@@ -274,55 +296,6 @@ module.exports = {
274
296
  return false
275
297
  }
276
298
 
277
- /**
278
- * 检查是否是内置函数
279
- */
280
- function isBuiltInFunction(node) {
281
- if (node.type !== 'CallExpression') return false
282
-
283
- // 检查全局对象的方法调用,如 JSON.parse, Promise.all 等
284
- if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier') {
285
- const objectName = node.callee.object.name
286
- const propertyName = node.callee.property.type === 'Identifier' ? node.callee.property.name : ''
287
-
288
- // 内置全局对象列表
289
- const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
290
-
291
- if (builtInObjects.includes(objectName)) {
292
- // 对于这些内置对象,假设它们的所有方法都是内置的
293
- // 这样可以避免维护庞大的方法列表
294
- return true
295
- }
296
- }
297
-
298
- // 检查可选链调用,如 spotArr?.find()
299
- if (node.callee.type === 'ChainExpression' && node.callee.expression.type === 'CallExpression') {
300
- const chainCall = node.callee.expression
301
- if (chainCall.callee.type === 'MemberExpression' && chainCall.callee.object.type === 'Identifier') {
302
- const objectName = chainCall.callee.object.name
303
-
304
- // 内置全局对象列表
305
- const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
306
-
307
- if (builtInObjects.includes(objectName)) {
308
- // 对于这些内置对象,假设它们的所有方法都是内置的
309
- return true
310
- }
311
- }
312
- }
313
-
314
- // 检查全局函数调用,如 parseInt, parseFloat, isNaN 等
315
- if (node.callee.type === 'Identifier') {
316
- const functionName = node.callee.name
317
- const globalFunctions = [
318
- 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'eval', 'encodeURI',
319
- 'encodeURIComponent', 'decodeURI', 'decodeURIComponent', 'escape', 'unescape'
320
- ]
321
- return globalFunctions.includes(functionName)
322
- }
323
-
324
- return false
325
- }
326
299
 
327
300
  /**
328
301
  * 检查模块路径是否应该被排除
@@ -335,13 +308,7 @@ module.exports = {
335
308
  return true // 所有第三方模块都排除
336
309
  }
337
310
 
338
- return excludePatterns.some(pattern => {
339
- if (pattern.includes('*')) {
340
- const regex = new RegExp(pattern.replace(/\*/g, '.*'))
341
- return regex.test(modulePath)
342
- }
343
- return modulePath.includes(pattern)
344
- })
311
+ return false
345
312
  }
346
313
 
347
314
  /**
@@ -427,6 +394,10 @@ module.exports = {
427
394
  })
428
395
  }
429
396
  }
397
+ } else {
398
+ // 如果可以推导类型,还需要检查初始值中是否包含显式 any
399
+ // 例如:ref<any[]>() 中的 any[] 包含 any
400
+ checkExpressionForAny(declaration.init)
430
401
  }
431
402
  }
432
403
  }
@@ -478,26 +449,6 @@ module.exports = {
478
449
  return false
479
450
  }
480
451
 
481
- /**
482
- * 检查是否是简单的集合实例化(如 new Map(), new Set())
483
- * 这些可以通过上下文推导类型,不应该算作隐式 any
484
- */
485
- function isSimpleCollectionInstantiation(node) {
486
- if (!node) return false
487
-
488
- // 检查 new Map() 或 new Set()
489
- if (node.type === 'NewExpression') {
490
- const calleeName = node.callee.type === 'Identifier' ? node.callee.name : ''
491
- if (calleeName === 'Map' || calleeName === 'Set') {
492
- // 检查是否有参数(无参数的 new Map() 或 new Set() 是简单的实例化)
493
- if (!node.arguments || node.arguments.length === 0) {
494
- return true
495
- }
496
- }
497
- }
498
-
499
- return false
500
- }
501
452
 
502
453
  /**
503
454
  * 检查函数声明中的 any 类型
@@ -539,6 +490,155 @@ module.exports = {
539
490
  }
540
491
  })
541
492
  }
493
+
494
+ // 检查箭头函数体中的 any 类型变量使用
495
+ if (node.body) {
496
+ if (node.body.type === 'BlockStatement') {
497
+ // 遍历函数体中的语句
498
+ node.body.body.forEach(statement => {
499
+ // 检查返回语句
500
+ if (statement.type === 'ReturnStatement' && statement.argument) {
501
+ checkExpressionForAnyVariableUse(statement.argument)
502
+ }
503
+ // 检查表达式语句
504
+ else if (statement.type === 'ExpressionStatement') {
505
+ checkExpressionForAnyVariableUse(statement.expression)
506
+ }
507
+ })
508
+ } else {
509
+ // 简写箭头函数体(直接返回表达式)
510
+ checkExpressionForAnyVariableUse(node.body)
511
+ }
512
+ }
513
+ }
514
+
515
+ /**
516
+ * 检查表达式中的 any 类型
517
+ */
518
+ function checkExpressionForAny(node) {
519
+ if (!node) return
520
+
521
+ // 检查函数调用中的类型参数
522
+ if (node.type === 'CallExpression') {
523
+ const sourceCode = context.getSourceCode()
524
+ const callText = sourceCode.getText(node)
525
+
526
+ // 检查类型参数(泛型)
527
+ if (node.typeParameters) {
528
+ // 直接检查类型参数中的 any
529
+ if (node.typeParameters.params) {
530
+ node.typeParameters.params.forEach(param => {
531
+ checkTypeNodeForAny(param, node)
532
+ })
533
+ }
534
+ } else {
535
+ // 如果没有 typeParameters 节点,检查源代码文本中是否包含泛型类型参数
536
+ // 这可能是因为 Vue SFC 中的 TypeScript 泛型没有被正确解析
537
+ if (callText.includes('<') && callText.includes('>')) {
538
+ // 提取泛型类型参数部分
539
+ const match = callText.match(/<([^>]+)>/)
540
+ if (match) {
541
+ const typeParamText = match[1]
542
+ // 检查类型参数文本中是否包含 "any"
543
+ if (typeParamText.includes('any')) {
544
+ // 报告 any 使用
545
+ anyUsages.push({
546
+ node: node,
547
+ type: 'explicit',
548
+ })
549
+ }
550
+ }
551
+ }
552
+ }
553
+ }
554
+ }
555
+
556
+ /**
557
+ * 检查表达式中是否使用了 any 类型的变量
558
+ */
559
+ function checkExpressionForAnyVariableUse(node) {
560
+ if (!node) return
561
+
562
+ // 检查标识符的使用
563
+ if (node.type === 'Identifier') {
564
+ if (anyVariables.has(node.name)) {
565
+ // 使用了 any 类型的变量,计数一次
566
+ anyUsages.push({
567
+ node: node,
568
+ type: 'explicit',
569
+ })
570
+ }
571
+ return // 标识符不需要进一步递归检查
572
+ }
573
+
574
+ // 检查成员表达式(如 param.id)
575
+ if (node.type === 'MemberExpression') {
576
+ // 检查对象是否是标识符
577
+ if (node.object.type === 'Identifier') {
578
+ if (anyVariables.has(node.object.name)) {
579
+ // 使用了 any 类型的变量,计数一次
580
+ anyUsages.push({
581
+ node: node,
582
+ type: 'explicit',
583
+ })
584
+ }
585
+ } else {
586
+ // 如果对象不是标识符,递归检查对象
587
+ checkExpressionForAnyVariableUse(node.object)
588
+ }
589
+ // 检查属性(如果不是标识符)
590
+ if (node.property && node.property.type !== 'Identifier') {
591
+ checkExpressionForAnyVariableUse(node.property)
592
+ }
593
+ return // 成员表达式检查完成
594
+ }
595
+
596
+ // 递归检查子表达式
597
+ if (node.type === 'CallExpression') {
598
+ if (node.callee) {
599
+ checkExpressionForAnyVariableUse(node.callee)
600
+ }
601
+ if (node.arguments) {
602
+ node.arguments.forEach(arg => {
603
+ checkExpressionForAnyVariableUse(arg)
604
+ })
605
+ }
606
+ return
607
+ }
608
+
609
+ if (node.type === 'ReturnStatement' && node.argument) {
610
+ checkExpressionForAnyVariableUse(node.argument)
611
+ return
612
+ }
613
+
614
+ if (node.type === 'ObjectExpression' && node.properties) {
615
+ node.properties.forEach(prop => {
616
+ if (prop.value) {
617
+ checkExpressionForAnyVariableUse(prop.value)
618
+ }
619
+ })
620
+ return
621
+ }
622
+
623
+ if (node.type === 'ArrayExpression' && node.elements) {
624
+ node.elements.forEach(element => {
625
+ if (element) {
626
+ checkExpressionForAnyVariableUse(element)
627
+ }
628
+ })
629
+ return
630
+ }
631
+
632
+ if (node.type === 'BinaryExpression') {
633
+ checkExpressionForAnyVariableUse(node.left)
634
+ checkExpressionForAnyVariableUse(node.right)
635
+ return
636
+ }
637
+
638
+ if (node.type === 'UnaryExpression' && node.argument) {
639
+ checkExpressionForAnyVariableUse(node.argument)
640
+ return
641
+ }
542
642
  }
543
643
 
544
644
  /**
@@ -557,10 +657,10 @@ module.exports = {
557
657
  function checkAnyUsageCount() {
558
658
  if (shouldExcludeFile()) return
559
659
  if (anyUsages.length > maxAnyCount) {
560
- // 报告第一个超出限制的 any 使用(第 maxAnyCount + 1 个)
561
- const firstExceededUsage = anyUsages[maxAnyCount]
660
+ // 报告最后一个超出限制的 any 使用
661
+ const lastExceededUsage = anyUsages[anyUsages.length - 1]
562
662
  context.report({
563
- node: firstExceededUsage.node,
663
+ node: lastExceededUsage.node,
564
664
  messageId: 'exceedMaxAnyCount',
565
665
  data: {
566
666
  maxAnyCount,
@@ -0,0 +1,78 @@
1
+ /**
2
+ * 检查是否是内置函数
3
+ * @param {import('@typescript-eslint/types').TSESTree.CallExpression} node - 调用表达式节点
4
+ * @returns {boolean} 是否是内置函数
5
+ */
6
+ function isBuiltInFunction(node) {
7
+ if (node.type !== 'CallExpression') return false
8
+
9
+ // 检查全局对象的方法调用,如 JSON.parse, Promise.all 等
10
+ if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier') {
11
+ const objectName = node.callee.object.name
12
+
13
+ // 内置全局对象列表 - 这些对象的所有方法都认为是内置的
14
+ const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
15
+
16
+ if (builtInObjects.includes(objectName)) {
17
+ // 对于这些内置对象,假设它们的所有方法都是内置的
18
+ // 这样可以避免维护庞大的方法列表
19
+ return true
20
+ }
21
+ }
22
+
23
+ // 检查可选链调用,如 spotArr?.find()
24
+ if (node.callee.type === 'ChainExpression' && node.callee.expression.type === 'CallExpression') {
25
+ const chainCall = node.callee.expression
26
+ if (chainCall.callee.type === 'MemberExpression' && chainCall.callee.object.type === 'Identifier') {
27
+ const objectName = chainCall.callee.object.name
28
+
29
+ // 内置全局对象列表
30
+ const builtInObjects = ['JSON', 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Math', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet']
31
+
32
+ if (builtInObjects.includes(objectName)) {
33
+ // 对于这些内置对象,假设它们的所有方法都是内置的
34
+ return true
35
+ }
36
+ }
37
+ }
38
+
39
+ // 检查全局函数调用,如 parseInt, parseFloat, isNaN 等
40
+ if (node.callee.type === 'Identifier') {
41
+ const functionName = node.callee.name
42
+ const globalFunctions = [
43
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'eval', 'encodeURI',
44
+ 'encodeURIComponent', 'decodeURI', 'decodeURIComponent', 'escape', 'unescape'
45
+ ]
46
+ return globalFunctions.includes(functionName)
47
+ }
48
+
49
+ return false
50
+ }
51
+
52
+ /**
53
+ * 检查是否是简单的集合实例化(如 new Map(), new Set())
54
+ * 这些可以通过上下文推导类型,不应该算作隐式 any
55
+ * @param {import('@typescript-eslint/types').TSESTree.NewExpression} node - new 表达式节点
56
+ * @returns {boolean} 是否是简单的集合实例化
57
+ */
58
+ function isSimpleCollectionInstantiation(node) {
59
+ if (!node) return false
60
+
61
+ // 检查 new Map() 或 new Set()
62
+ if (node.type === 'NewExpression') {
63
+ const calleeName = node.callee.type === 'Identifier' ? node.callee.name : ''
64
+ if (calleeName === 'Map' || calleeName === 'Set') {
65
+ // 检查是否有参数(无参数的 new Map() 或 new Set() 是简单的实例化)
66
+ if (!node.arguments || node.arguments.length === 0) {
67
+ return true
68
+ }
69
+ }
70
+ }
71
+
72
+ return false
73
+ }
74
+
75
+ module.exports = {
76
+ isBuiltInFunction,
77
+ isSimpleCollectionInstantiation
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundbyte/eslint-plugin",
3
- "version": "1.1.4-alpha.1",
3
+ "version": "1.1.4-alpha.3",
4
4
  "description": "ESLint plugin for enforcing code style rules including arrow function padding, Vue script lines limit, and TypeScript type comment requirements",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",