@foundbyte/eslint-plugin 1.1.2 → 1.1.4-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
@@ -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$'],
28
+ excludeTypePatterns: ['^const$', '^[A-Z]$'],
29
29
  excludeMethodReturns: ['parse', 'querySelector']
30
30
  }],
31
31
  '@foundbyte/hooks-arrow-function-only': 'error',
@@ -47,12 +47,105 @@ module.exports = {
47
47
  }
48
48
  ]
49
49
  }],
50
+ '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }],
50
51
  },
51
52
  }
52
53
  ```
53
54
 
54
55
  ## 规则列表与配置示例
55
56
 
57
+ ### no-excessive-any
58
+
59
+ 限制 `any` 类型的使用次数,包括:
60
+ - 显式 `any` 类型
61
+ - 隐式 `any` 类型(没有类型注解且无法推导类型的变量)
62
+ - 泛型类型中的 `any`(如 `Ref<any>`, `Promise<any>`, `any[]` 等)
63
+
64
+ **配置示例:**
65
+ ```javascript
66
+ // 基本用法 - 限制 any 类型最多使用 5 次
67
+ '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }]
68
+
69
+ // 严格模式 - 禁止使用 any 类型
70
+ '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 0 }]
71
+
72
+ // 宽松模式 - 允许最多 10 次 any 类型使用
73
+ '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 10 }]
74
+ ```
75
+
76
+ **检测范围:**
77
+ - 显式 `any` 类型:`const a: any = 1`
78
+ - 隐式 `any` 类型:
79
+ - 没有初始值的变量声明:`const a`
80
+ - 没有参数的函数调用:`const a = ref()`
81
+ - 泛型类型中的 `any`:
82
+ - `Ref<any>`, `Promise<any>`, `Array<any>`
83
+ - `any[]`, `string | any`, `T extends any`
84
+ - 类型别名和接口中的 `any`
85
+
86
+ **智能类型推导:**
87
+ 以下情况不计入 any 使用:
88
+ - 有初始值的变量:`const a = 1`, `const b = ref(60)`
89
+ - 对象字面量:`const obj = { name: 'test' }`
90
+ - 数组字面量:`const arr = [1, 2, 3]`
91
+ - 函数表达式和箭头函数:`const fn = () => {}`
92
+ - 有参数的函数调用:`const a = useRegex(phoneNum, 'phone-number')`
93
+ - 属性访问:`const value = ref.value`
94
+ - 异步函数调用:`const res = await ssoMobileAuth(...)`
95
+
96
+ **示例:**
97
+ ```typescript
98
+ // ✅ 正确 - 当 maxAnyCount 为 3 时
99
+ const a: any = 1
100
+ const b: any = 'hello'
101
+ const c = ref() // 隐式 any
102
+
103
+ // ❌ 错误 - 当 maxAnyCount 为 3 时,第 4 次 any 使用会报错
104
+ const a: any = 1
105
+ const b: any = 'hello'
106
+ const c = ref() // 隐式 any
107
+ const d: any = true // 错误:any 类型使用次数不能超过 3 次,当前使用了 4 次
108
+
109
+ // ✅ 正确 - 使用具体类型
110
+ const a: number = 1
111
+ const b: string = 'hello'
112
+ const c: number = 123
113
+ const d: boolean = true
114
+
115
+ // ✅ 正确 - 可以推导类型的情况不计入 any 使用
116
+ const time = ref(60) // 有数字字面量参数,可以推导类型
117
+ const messages = { noMore: 'text' } // 对象字面量,可以推导类型
118
+ const fn = () => {} // 函数表达式,可以推导类型
119
+ const res = await ssoMobileAuth({ nonce: nonce.value }) // 异步函数调用,可以推导类型
120
+
121
+ // ❌ 错误 - 无法推导类型的隐式 any
122
+ const a // 没有初始值
123
+ const b = ref() // 没有参数的函数调用
124
+ const c: Ref<any> = ref() // 显式 any 类型
125
+
126
+ // ❌ 错误 - 泛型类型中的 any
127
+ type MyType<T = any> = T
128
+ interface MyInterface {
129
+ value: any
130
+ ref: Ref<any>
131
+ array: any[]
132
+ union: string | any
133
+ generic: Promise<any>
134
+ }
135
+ ```
136
+
137
+ **配置选项:**
138
+
139
+ - `maxAnyCount` (number, 默认 0): 允许的 `any` 类型最大使用次数
140
+ - 设置为 0 表示完全禁止使用 `any` 类型
141
+ - 设置为正整数表示允许的最大次数
142
+
143
+ **注意:**
144
+ - 该规则统计文件中所有 `any` 类型的使用,包括显式和隐式
145
+ - 规则会智能识别可以类型推导的情况,避免过度检测
146
+ - 建议在 TypeScript 项目中设置合理的限制,鼓励使用具体类型
147
+ - 对于遗留代码迁移,可以设置较高的限制值,然后逐步减少
148
+
56
149
  ### no-initial-as-assertion
57
150
 
58
151
  禁止在初始变量定义时使用 `as` 类型断言,强制使用显式类型注解。
package/index.js CHANGED
@@ -5,6 +5,7 @@ const hooksExportLimitRule = require('./lib/rules/hooks-export-limit')
5
5
  const hooksArrowFunctionOnlyRule = require('./lib/rules/hooks-arrow-function-only')
6
6
  const tagAttributeRestrictionRule = require('./lib/rules/tag-attribute-restriction')
7
7
  const noInitialAsAssertionRule = require('./lib/rules/no-initial-as-assertion')
8
+ const noExcessiveAnyRule = require('./lib/rules/no-excessive-any')
8
9
 
9
10
  module.exports = {
10
11
  rules: {
@@ -15,5 +16,6 @@ module.exports = {
15
16
  'hooks-arrow-function-only': hooksArrowFunctionOnlyRule,
16
17
  'tag-attribute-restriction': tagAttributeRestrictionRule,
17
18
  'no-initial-as-assertion': noInitialAsAssertionRule,
19
+ 'no-excessive-any': noExcessiveAnyRule,
18
20
  },
19
21
  }
@@ -5,6 +5,7 @@ export { VueScriptLinesLimitOptions } from './vue-script-lines-limit'
5
5
  export { HooksArrowFunctionOnlyOptions } from './hooks-arrow-function-only'
6
6
  export { TagAttributeRestrictionOptions } from './tag-attribute-restriction'
7
7
  export { NoInitialAsAssertionOptions } from './no-initial-as-assertion'
8
+ export { NoExcessiveAnyOptions } from './no-excessive-any'
8
9
 
9
10
  import { Rule } from 'eslint'
10
11
 
@@ -47,6 +48,11 @@ export interface Rules {
47
48
  */
48
49
  'no-initial-as-assertion': Rule.RuleModule
49
50
 
51
+ /**
52
+ * 限制 any 类型的使用次数
53
+ */
54
+ 'no-excessive-any': Rule.RuleModule
55
+
50
56
  /**
51
57
  * 索引签名,用于兼容 ESLint 插件类型
52
58
  */
@@ -0,0 +1,9 @@
1
+ import { Rule } from 'eslint'
2
+
3
+ export interface NoExcessiveAnyOptions {
4
+ maxAnyCount?: number
5
+ }
6
+
7
+ declare const rule: Rule.RuleModule
8
+
9
+ export default rule
@@ -0,0 +1,582 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description: '限制 any 类型的使用次数',
6
+ category: 'Best Practices',
7
+ recommended: false,
8
+ },
9
+ fixable: null,
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ maxAnyCount: {
15
+ type: 'number',
16
+ minimum: 0,
17
+ description: '允许的 any 类型最大使用次数',
18
+ },
19
+ },
20
+ additionalProperties: false,
21
+ },
22
+ ],
23
+ messages: {
24
+ exceedMaxAnyCount: 'any 类型使用次数不能超过 {{maxAnyCount}} 次,当前使用了 {{currentCount}} 次',
25
+ },
26
+ },
27
+
28
+ create(context) {
29
+ const options = context.options[0] || {}
30
+ const maxAnyCount = options.maxAnyCount || 0
31
+
32
+ // 存储 any 类型使用的位置
33
+ const anyUsages = []
34
+
35
+ /**
36
+ * 检查文件路径是否应该被排除
37
+ */
38
+ function shouldExcludeFile() {
39
+ // 不再排除任何文件,所有文件都检查
40
+ return false
41
+ }
42
+
43
+ /**
44
+ * 递归检查类型节点中的 any 类型
45
+ */
46
+ function checkTypeNodeForAny(node, parentNode = null) {
47
+ if (!node) return
48
+
49
+ // 检查显式 any 类型
50
+ if (node.type === 'TSAnyKeyword') {
51
+ anyUsages.push({
52
+ node: parentNode || node,
53
+ type: 'explicit',
54
+ })
55
+ return
56
+ }
57
+
58
+ // 检查泛型类型参数中的 any
59
+ if (node.type === 'TSTypeReference' && node.typeParameters) {
60
+ node.typeParameters.params.forEach(param => {
61
+ checkTypeNodeForAny(param, node)
62
+ })
63
+ }
64
+
65
+ // 检查联合类型和交叉类型
66
+ if ((node.type === 'TSUnionType' || node.type === 'TSIntersectionType') && node.types) {
67
+ node.types.forEach(type => {
68
+ checkTypeNodeForAny(type, node)
69
+ })
70
+ }
71
+
72
+ // 检查数组类型
73
+ if (node.type === 'TSArrayType' && node.elementType) {
74
+ checkTypeNodeForAny(node.elementType, node)
75
+ }
76
+
77
+ // 检查函数类型
78
+ if (node.type === 'TSFunctionType' && node.parameters) {
79
+ node.parameters.forEach(param => {
80
+ if (param.typeAnnotation) {
81
+ checkTypeNodeForAny(param.typeAnnotation.typeAnnotation, param)
82
+ }
83
+ })
84
+ if (node.returnType) {
85
+ checkTypeNodeForAny(node.returnType.typeAnnotation, node)
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 检查类型注解中的 any 类型
92
+ */
93
+ function checkTypeAnnotation(node) {
94
+ if (shouldExcludeFile()) return
95
+ if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
96
+ 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
132
+ }
133
+ }
134
+ }
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
+ }
148
+
149
+ /**
150
+ * 检查表达式是否可以推导类型
151
+ */
152
+ function canInferTypeFromExpression(node) {
153
+ if (!node) return false
154
+
155
+ // 字面量可以直接推导类型
156
+ if (node.type === 'Literal') {
157
+ return true
158
+ }
159
+
160
+ // 对象字面量可以推导类型
161
+ if (node.type === 'ObjectExpression') {
162
+ return true
163
+ }
164
+
165
+ // 数组字面量可以推导类型
166
+ if (node.type === 'ArrayExpression') {
167
+ return true
168
+ }
169
+
170
+ // 函数表达式和箭头函数可以推导类型(函数本身有类型)
171
+ if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
172
+ return true
173
+ }
174
+
175
+ // 函数调用:检查参数是否可以推导类型
176
+ if (node.type === 'CallExpression') {
177
+ // 检查是否是第三方库函数调用
178
+ if (isThirdPartyFunctionCall(node)) {
179
+ return true // 第三方库函数调用,假设可以推导类型
180
+ }
181
+
182
+ // 检查是否是 Vue 响应式函数调用且有明确的类型参数
183
+ if (isTypedVueReactiveCall(node)) {
184
+ return true // 有明确的类型参数,可以推导类型
185
+ }
186
+
187
+ // 检查是否是 Vue 响应式函数(无论是否有类型参数)
188
+ if (isVueReactiveFunction(node)) {
189
+ // 对于 Vue 响应式函数调用,需要检查是否有参数可以推导类型
190
+ if (node.arguments && node.arguments.length > 0) {
191
+ // 如果有参数,并且所有参数都可以推导类型,则认为可以推导返回类型
192
+ return node.arguments.every(arg => canInferTypeFromExpression(arg))
193
+ }
194
+
195
+ // 检查是否有类型参数(通过源代码文本检查)
196
+ // 由于 Vue SFC 文件中的 TypeScript 泛型可能没有被正确解析为 AST 节点,
197
+ // 我们通过检查源代码文本来判断是否有类型参数
198
+ const sourceCode = context.getSourceCode()
199
+ const callText = sourceCode.getText(node)
200
+
201
+ // 检查调用文本中是否包含泛型类型参数(如 <string>)
202
+ const hasTypeParameters = callText.includes('<') && callText.includes('>')
203
+
204
+ // 没有参数的 Vue 响应式函数调用,如果有类型参数,可以推导类型
205
+ // 如 ref<string>() 可以推导为 Ref<string | undefined>
206
+ if (hasTypeParameters) {
207
+ return true
208
+ }
209
+
210
+ // 没有类型参数也没有参数的 Vue 响应式函数调用(如 ref())无法推导类型
211
+ return false
212
+ }
213
+
214
+ // 对于其他函数调用,假设可以推导类型
215
+ // 因为本地函数有明确的返回类型,TypeScript 可以推导
216
+ return true
217
+ }
218
+
219
+ // 属性访问通常可以推导类型(如 ref.value)
220
+ if (node.type === 'MemberExpression') {
221
+ return true
222
+ }
223
+
224
+ // 可选链表达式可以推导类型(如 spotArr?.find())
225
+ if (node.type === 'ChainExpression') {
226
+ return true
227
+ }
228
+
229
+ // await 表达式可以推导类型(等待 Promise 的结果)
230
+ if (node.type === 'AwaitExpression') {
231
+ return true
232
+ }
233
+
234
+ // 其他情况默认无法推导类型
235
+ return false
236
+ }
237
+
238
+ /**
239
+ * 检查是否是第三方库函数调用
240
+ */
241
+ function isThirdPartyFunctionCall(node) {
242
+ if (node.type !== 'CallExpression') return false
243
+
244
+ // 检查是否是内置函数
245
+ if (isBuiltInFunction(node)) {
246
+ return true
247
+ }
248
+
249
+ // 获取当前文件的导入信息
250
+ const imports = getImportsForCurrentFile()
251
+
252
+ // 检查标识符调用,如 isEqualWith(a, b)
253
+ if (node.callee.type === 'Identifier') {
254
+ const functionName = node.callee.name
255
+ const importInfo = imports.find(imp => imp.localName === functionName)
256
+
257
+ if (importInfo) {
258
+ // 检查导入的模块是否在排除模式中
259
+ return isModuleExcluded(importInfo.modulePath)
260
+ }
261
+ }
262
+
263
+ // 检查成员表达式调用,如 lodash.isEqualWith(a, b)
264
+ if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier') {
265
+ const objectName = node.callee.object.name
266
+ const importInfo = imports.find(imp => imp.localName === objectName)
267
+
268
+ if (importInfo) {
269
+ // 检查导入的模块是否在排除模式中
270
+ return isModuleExcluded(importInfo.modulePath)
271
+ }
272
+ }
273
+
274
+ return false
275
+ }
276
+
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
+
327
+ /**
328
+ * 检查模块路径是否应该被排除
329
+ */
330
+ function isModuleExcluded(modulePath) {
331
+ // 检查是否是第三方模块(不以 ./ 或 ../ 开头)
332
+ const isThirdPartyModule = !modulePath.startsWith('./') && !modulePath.startsWith('../')
333
+
334
+ if (isThirdPartyModule) {
335
+ return true // 所有第三方模块都排除
336
+ }
337
+
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
+ })
345
+ }
346
+
347
+ /**
348
+ * 获取当前文件的导入信息
349
+ */
350
+ function getImportsForCurrentFile() {
351
+ const imports = []
352
+ const programNode = context.getSourceCode().ast
353
+
354
+ // 遍历所有导入声明
355
+ programNode.body.forEach(node => {
356
+ if (node.type === 'ImportDeclaration') {
357
+ const modulePath = node.source.value
358
+
359
+ node.specifiers.forEach(specifier => {
360
+ if (specifier.type === 'ImportSpecifier') {
361
+ imports.push({
362
+ localName: specifier.local.name,
363
+ importedName: specifier.imported.name,
364
+ modulePath: modulePath
365
+ })
366
+ } else if (specifier.type === 'ImportDefaultSpecifier') {
367
+ imports.push({
368
+ localName: specifier.local.name,
369
+ importedName: 'default',
370
+ modulePath: modulePath
371
+ })
372
+ } else if (specifier.type === 'ImportNamespaceSpecifier') {
373
+ imports.push({
374
+ localName: specifier.local.name,
375
+ importedName: '*',
376
+ modulePath: modulePath
377
+ })
378
+ }
379
+ })
380
+ }
381
+ })
382
+
383
+ return imports
384
+ }
385
+
386
+ /**
387
+ * 检查变量声明中的隐式 any 类型
388
+ */
389
+ function checkVariableDeclaration(node) {
390
+ if (shouldExcludeFile()) return
391
+ // 遍历所有声明
392
+ node.declarations.forEach(declaration => {
393
+ if (declaration.id && declaration.id.type === 'Identifier') {
394
+ // 检查变量声明是否没有类型注解
395
+ if (declaration.id.typeAnnotation === undefined) {
396
+ // 检查是否是函数参数
397
+ const isFunctionParam = node.parent &&
398
+ (node.parent.type === 'FunctionDeclaration' ||
399
+ node.parent.type === 'FunctionExpression' ||
400
+ node.parent.type === 'ArrowFunctionExpression')
401
+
402
+ if (!isFunctionParam) {
403
+ // 检查是否有初始值可以推导类型
404
+ const hasInitializer = declaration.init !== undefined && declaration.init !== null
405
+
406
+ // 如果变量没有初始值,则认为是隐式 any
407
+ if (!hasInitializer) {
408
+ anyUsages.push({
409
+ node: declaration,
410
+ type: 'implicit',
411
+ })
412
+ } else if (declaration.init) {
413
+ // 检查初始值是否可以推导类型
414
+ const canInferType = canInferTypeFromExpression(declaration.init)
415
+ if (!canInferType) {
416
+ // 额外检查:确保不是函数声明
417
+ const isFunctionDeclaration = declaration.init.type === 'FunctionExpression' ||
418
+ declaration.init.type === 'ArrowFunctionExpression'
419
+
420
+ if (!isFunctionDeclaration) {
421
+ // 检查是否是简单的 Map/Set 实例化,这些可以通过上下文推导类型
422
+ const isSimpleCollection = isSimpleCollectionInstantiation(declaration.init)
423
+ if (!isSimpleCollection) {
424
+ anyUsages.push({
425
+ node: declaration,
426
+ type: 'implicit',
427
+ })
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ } else if (declaration.id.typeAnnotation) {
434
+ // 检查类型注解中的 any
435
+ checkTypeAnnotation(declaration.id)
436
+ }
437
+ }
438
+ })
439
+ }
440
+
441
+ /**
442
+ * 检查是否是 Vue 响应式函数(无论是否有类型参数)
443
+ */
444
+ function isVueReactiveFunction(node) {
445
+ if (!node || node.type !== 'CallExpression') return false
446
+
447
+ // 检查是否是 ref() 或 reactive() 调用
448
+ if (node.callee.type === 'Identifier') {
449
+ const functionName = node.callee.name
450
+ const vueReactiveFunctions = ['ref', 'reactive', 'computed', 'watch', 'watchEffect']
451
+
452
+ return vueReactiveFunctions.includes(functionName)
453
+ }
454
+
455
+ return false
456
+ }
457
+
458
+ /**
459
+ * 检查是否是 Vue 响应式函数调用且有明确的类型参数
460
+ * 这个函数专门处理所有有类型参数的 Vue 响应式函数调用
461
+ */
462
+ function isTypedVueReactiveCall(node) {
463
+ if (!node || node.type !== 'CallExpression') return false
464
+
465
+ // 检查是否是 Vue 响应式函数调用
466
+ if (node.callee.type === 'Identifier') {
467
+ const functionName = node.callee.name
468
+ const vueReactiveFunctions = ['ref', 'reactive', 'computed', 'watch', 'watchEffect']
469
+
470
+ if (vueReactiveFunctions.includes(functionName)) {
471
+ // 检查是否有类型参数(TypeScript 泛型)
472
+ if (node.typeParameters && node.typeParameters.params && node.typeParameters.params.length > 0) {
473
+ return true // 有明确的类型参数
474
+ }
475
+ }
476
+ }
477
+
478
+ return false
479
+ }
480
+
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
+
502
+ /**
503
+ * 检查函数声明中的 any 类型
504
+ */
505
+ function checkFunctionDeclaration(node) {
506
+ if (shouldExcludeFile()) return
507
+
508
+ // 检查函数返回类型中的 any
509
+ if (node.returnType && node.returnType.typeAnnotation) {
510
+ checkTypeNodeForAny(node.returnType.typeAnnotation, node)
511
+ }
512
+
513
+ // 检查函数参数类型中的 any
514
+ if (node.params) {
515
+ node.params.forEach(param => {
516
+ if (param.typeAnnotation) {
517
+ checkTypeAnnotation(param)
518
+ }
519
+ })
520
+ }
521
+ }
522
+
523
+ /**
524
+ * 检查箭头函数中的 any 类型
525
+ */
526
+ function checkArrowFunctionExpression(node) {
527
+ if (shouldExcludeFile()) return
528
+
529
+ // 检查箭头函数返回类型中的 any
530
+ if (node.returnType && node.returnType.typeAnnotation) {
531
+ checkTypeNodeForAny(node.returnType.typeAnnotation, node)
532
+ }
533
+
534
+ // 检查箭头函数参数类型中的 any
535
+ if (node.params) {
536
+ node.params.forEach(param => {
537
+ if (param.typeAnnotation) {
538
+ checkTypeAnnotation(param)
539
+ }
540
+ })
541
+ }
542
+ }
543
+
544
+ /**
545
+ * 检查类型别名和接口中的 any 类型
546
+ */
547
+ function checkTypeDeclaration(node) {
548
+ if (shouldExcludeFile()) return
549
+ if (node.typeAnnotation) {
550
+ checkTypeNodeForAny(node.typeAnnotation, node)
551
+ }
552
+ }
553
+
554
+ /**
555
+ * 在文件结束时检查 any 类型使用次数
556
+ */
557
+ function checkAnyUsageCount() {
558
+ if (shouldExcludeFile()) return
559
+ if (anyUsages.length > maxAnyCount) {
560
+ // 报告第一个超出限制的 any 使用(第 maxAnyCount + 1 个)
561
+ const firstExceededUsage = anyUsages[maxAnyCount]
562
+ context.report({
563
+ node: firstExceededUsage.node,
564
+ messageId: 'exceedMaxAnyCount',
565
+ data: {
566
+ maxAnyCount,
567
+ currentCount: anyUsages.length,
568
+ },
569
+ })
570
+ }
571
+ }
572
+
573
+ return {
574
+ VariableDeclaration: checkVariableDeclaration,
575
+ FunctionDeclaration: checkFunctionDeclaration,
576
+ ArrowFunctionExpression: checkArrowFunctionExpression,
577
+ TSTypeAliasDeclaration: checkTypeDeclaration,
578
+ TSInterfaceDeclaration: checkTypeDeclaration,
579
+ 'Program:exit': checkAnyUsageCount,
580
+ }
581
+ },
582
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundbyte/eslint-plugin",
3
- "version": "1.1.2",
3
+ "version": "1.1.4-alpha.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
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // 导出所有规则配置类型
2
- export type { ArrowFunctionPaddingOptions, HooksExportLimitOptions, TypeCommentRequirementOptions, VueScriptLinesLimitOptions, HooksArrowFunctionOnlyOptions } from './lib/rules'
2
+ export type { ArrowFunctionPaddingOptions, HooksExportLimitOptions, TypeCommentRequirementOptions, VueScriptLinesLimitOptions, HooksArrowFunctionOnlyOptions, NoExcessiveAnyOptions } from './lib/rules'