@foundbyte/eslint-plugin 1.1.4-alpha.2 → 1.1.4-alpha.4

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,24 @@ 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
+
256
+ // 模板字符串可以推导类型(字符串类型)
257
+ if (node.type === 'TemplateLiteral') {
258
+ // 检查模板字符串中的所有表达式是否可以推导类型
259
+ if (node.expressions && node.expressions.length > 0) {
260
+ return node.expressions.every(expr => canInferTypeFromExpression(expr))
261
+ }
262
+ return true
263
+ }
264
+
234
265
  // 其他情况默认无法推导类型
235
266
  return false
236
267
  }
@@ -274,55 +305,6 @@ module.exports = {
274
305
  return false
275
306
  }
276
307
 
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
308
 
327
309
  /**
328
310
  * 检查模块路径是否应该被排除
@@ -421,6 +403,10 @@ module.exports = {
421
403
  })
422
404
  }
423
405
  }
406
+ } else {
407
+ // 如果可以推导类型,还需要检查初始值中是否包含显式 any
408
+ // 例如:ref<any[]>() 中的 any[] 包含 any
409
+ checkExpressionForAny(declaration.init)
424
410
  }
425
411
  }
426
412
  }
@@ -472,26 +458,6 @@ module.exports = {
472
458
  return false
473
459
  }
474
460
 
475
- /**
476
- * 检查是否是简单的集合实例化(如 new Map(), new Set())
477
- * 这些可以通过上下文推导类型,不应该算作隐式 any
478
- */
479
- function isSimpleCollectionInstantiation(node) {
480
- if (!node) return false
481
-
482
- // 检查 new Map() 或 new Set()
483
- if (node.type === 'NewExpression') {
484
- const calleeName = node.callee.type === 'Identifier' ? node.callee.name : ''
485
- if (calleeName === 'Map' || calleeName === 'Set') {
486
- // 检查是否有参数(无参数的 new Map() 或 new Set() 是简单的实例化)
487
- if (!node.arguments || node.arguments.length === 0) {
488
- return true
489
- }
490
- }
491
- }
492
-
493
- return false
494
- }
495
461
 
496
462
  /**
497
463
  * 检查函数声明中的 any 类型
@@ -533,6 +499,155 @@ module.exports = {
533
499
  }
534
500
  })
535
501
  }
502
+
503
+ // 检查箭头函数体中的 any 类型变量使用
504
+ if (node.body) {
505
+ if (node.body.type === 'BlockStatement') {
506
+ // 遍历函数体中的语句
507
+ node.body.body.forEach(statement => {
508
+ // 检查返回语句
509
+ if (statement.type === 'ReturnStatement' && statement.argument) {
510
+ checkExpressionForAnyVariableUse(statement.argument)
511
+ }
512
+ // 检查表达式语句
513
+ else if (statement.type === 'ExpressionStatement') {
514
+ checkExpressionForAnyVariableUse(statement.expression)
515
+ }
516
+ })
517
+ } else {
518
+ // 简写箭头函数体(直接返回表达式)
519
+ checkExpressionForAnyVariableUse(node.body)
520
+ }
521
+ }
522
+ }
523
+
524
+ /**
525
+ * 检查表达式中的 any 类型
526
+ */
527
+ function checkExpressionForAny(node) {
528
+ if (!node) return
529
+
530
+ // 检查函数调用中的类型参数
531
+ if (node.type === 'CallExpression') {
532
+ const sourceCode = context.getSourceCode()
533
+ const callText = sourceCode.getText(node)
534
+
535
+ // 检查类型参数(泛型)
536
+ if (node.typeParameters) {
537
+ // 直接检查类型参数中的 any
538
+ if (node.typeParameters.params) {
539
+ node.typeParameters.params.forEach(param => {
540
+ checkTypeNodeForAny(param, node)
541
+ })
542
+ }
543
+ } else {
544
+ // 如果没有 typeParameters 节点,检查源代码文本中是否包含泛型类型参数
545
+ // 这可能是因为 Vue SFC 中的 TypeScript 泛型没有被正确解析
546
+ if (callText.includes('<') && callText.includes('>')) {
547
+ // 提取泛型类型参数部分
548
+ const match = callText.match(/<([^>]+)>/)
549
+ if (match) {
550
+ const typeParamText = match[1]
551
+ // 检查类型参数文本中是否包含 "any"
552
+ if (typeParamText.includes('any')) {
553
+ // 报告 any 使用
554
+ anyUsages.push({
555
+ node: node,
556
+ type: 'explicit',
557
+ })
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
563
+ }
564
+
565
+ /**
566
+ * 检查表达式中是否使用了 any 类型的变量
567
+ */
568
+ function checkExpressionForAnyVariableUse(node) {
569
+ if (!node) return
570
+
571
+ // 检查标识符的使用
572
+ if (node.type === 'Identifier') {
573
+ if (anyVariables.has(node.name)) {
574
+ // 使用了 any 类型的变量,计数一次
575
+ anyUsages.push({
576
+ node: node,
577
+ type: 'explicit',
578
+ })
579
+ }
580
+ return // 标识符不需要进一步递归检查
581
+ }
582
+
583
+ // 检查成员表达式(如 param.id)
584
+ if (node.type === 'MemberExpression') {
585
+ // 检查对象是否是标识符
586
+ if (node.object.type === 'Identifier') {
587
+ if (anyVariables.has(node.object.name)) {
588
+ // 使用了 any 类型的变量,计数一次
589
+ anyUsages.push({
590
+ node: node,
591
+ type: 'explicit',
592
+ })
593
+ }
594
+ } else {
595
+ // 如果对象不是标识符,递归检查对象
596
+ checkExpressionForAnyVariableUse(node.object)
597
+ }
598
+ // 检查属性(如果不是标识符)
599
+ if (node.property && node.property.type !== 'Identifier') {
600
+ checkExpressionForAnyVariableUse(node.property)
601
+ }
602
+ return // 成员表达式检查完成
603
+ }
604
+
605
+ // 递归检查子表达式
606
+ if (node.type === 'CallExpression') {
607
+ if (node.callee) {
608
+ checkExpressionForAnyVariableUse(node.callee)
609
+ }
610
+ if (node.arguments) {
611
+ node.arguments.forEach(arg => {
612
+ checkExpressionForAnyVariableUse(arg)
613
+ })
614
+ }
615
+ return
616
+ }
617
+
618
+ if (node.type === 'ReturnStatement' && node.argument) {
619
+ checkExpressionForAnyVariableUse(node.argument)
620
+ return
621
+ }
622
+
623
+ if (node.type === 'ObjectExpression' && node.properties) {
624
+ node.properties.forEach(prop => {
625
+ if (prop.value) {
626
+ checkExpressionForAnyVariableUse(prop.value)
627
+ }
628
+ })
629
+ return
630
+ }
631
+
632
+ if (node.type === 'ArrayExpression' && node.elements) {
633
+ node.elements.forEach(element => {
634
+ if (element) {
635
+ checkExpressionForAnyVariableUse(element)
636
+ }
637
+ })
638
+ return
639
+ }
640
+
641
+ if (node.type === 'BinaryExpression') {
642
+ checkExpressionForAnyVariableUse(node.left)
643
+ checkExpressionForAnyVariableUse(node.right)
644
+ return
645
+ }
646
+
647
+ if (node.type === 'UnaryExpression' && node.argument) {
648
+ checkExpressionForAnyVariableUse(node.argument)
649
+ return
650
+ }
536
651
  }
537
652
 
538
653
  /**
@@ -551,10 +666,10 @@ module.exports = {
551
666
  function checkAnyUsageCount() {
552
667
  if (shouldExcludeFile()) return
553
668
  if (anyUsages.length > maxAnyCount) {
554
- // 报告第一个超出限制的 any 使用(第 maxAnyCount + 1 个)
555
- const firstExceededUsage = anyUsages[maxAnyCount]
669
+ // 报告最后一个超出限制的 any 使用
670
+ const lastExceededUsage = anyUsages[anyUsages.length - 1]
556
671
  context.report({
557
- node: firstExceededUsage.node,
672
+ node: lastExceededUsage.node,
558
673
  messageId: 'exceedMaxAnyCount',
559
674
  data: {
560
675
  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.2",
3
+ "version": "1.1.4-alpha.4",
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",