@foundbyte/eslint-plugin 1.1.1 → 1.1.4-alpha

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
@@ -24,7 +24,9 @@ module.exports = {
24
24
  // 启用所有推荐的规则
25
25
  '@foundbyte/arrow-function-padding': 'error',
26
26
  '@foundbyte/no-initial-as-assertion': ['error', {
27
- 'excludeMethodReturns': ['map', 'parse', 'filter', 'reduce','querySelector']
27
+ excludeVariableAssertions: true,
28
+ excludeTypePatterns: ['^const$', '^[A-Z]$'],
29
+ excludeMethodReturns: ['parse', 'querySelector']
28
30
  }],
29
31
  '@foundbyte/hooks-arrow-function-only': 'error',
30
32
  '@foundbyte/type-comment-requirement': ['error', {
@@ -45,12 +47,105 @@ module.exports = {
45
47
  }
46
48
  ]
47
49
  }],
50
+ '@foundbyte/no-excessive-any': ['error', { maxAnyCount: 5 }],
48
51
  },
49
52
  }
50
53
  ```
51
54
 
52
55
  ## 规则列表与配置示例
53
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
+
54
149
  ### no-initial-as-assertion
55
150
 
56
151
  禁止在初始变量定义时使用 `as` 类型断言,强制使用显式类型注解。
@@ -106,6 +201,11 @@ const parsed = JSON.parse(jsonString) as { name: string } // 不会报错
106
201
  - 支持全局函数:`JSON.parse` 等
107
202
  - 支持链式调用:`arr.map().filter() as Type`
108
203
 
204
+ - `excludeTypePatterns` (array, 默认 []): 排除匹配正则表达式模式的类型断言
205
+ - 支持正则表达式字符串数组
206
+ - 用于排除特定的类型断言模式,如 `as const`
207
+ - 示例:`['^const$', '^Readonly<.*>$']` 可以排除 `as const` 和 `as Readonly<...>`
208
+
109
209
  **注意:**
110
210
  - 该规则检查变量声明时的所有类型断言,包括嵌套在函数调用参数中的类型断言
111
211
  - 推荐使用显式类型注解,因为类型断言会绕过 TypeScript 的类型检查
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,236 @@
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
+ * 递归检查类型节点中的 any 类型
37
+ */
38
+ function checkTypeNodeForAny(node, parentNode = null) {
39
+ if (!node) return
40
+
41
+ // 检查显式 any 类型
42
+ if (node.type === 'TSAnyKeyword') {
43
+ anyUsages.push({
44
+ node: parentNode || node,
45
+ type: 'explicit',
46
+ })
47
+ return
48
+ }
49
+
50
+ // 检查泛型类型参数中的 any
51
+ if (node.type === 'TSTypeReference' && node.typeParameters) {
52
+ node.typeParameters.params.forEach(param => {
53
+ checkTypeNodeForAny(param, node)
54
+ })
55
+ }
56
+
57
+ // 检查联合类型和交叉类型
58
+ if ((node.type === 'TSUnionType' || node.type === 'TSIntersectionType') && node.types) {
59
+ node.types.forEach(type => {
60
+ checkTypeNodeForAny(type, node)
61
+ })
62
+ }
63
+
64
+ // 检查数组类型
65
+ if (node.type === 'TSArrayType' && node.elementType) {
66
+ checkTypeNodeForAny(node.elementType, node)
67
+ }
68
+
69
+ // 检查函数类型
70
+ if (node.type === 'TSFunctionType' && node.parameters) {
71
+ node.parameters.forEach(param => {
72
+ if (param.typeAnnotation) {
73
+ checkTypeNodeForAny(param.typeAnnotation.typeAnnotation, param)
74
+ }
75
+ })
76
+ if (node.returnType) {
77
+ checkTypeNodeForAny(node.returnType.typeAnnotation, node)
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 检查 TSAnyKeyword 节点(显式 any 类型)
84
+ */
85
+ function checkTSAnyKeyword(node) {
86
+ anyUsages.push({
87
+ node,
88
+ type: 'explicit',
89
+ })
90
+ }
91
+
92
+ /**
93
+ * 检查类型注解中的 any 类型
94
+ */
95
+ function checkTypeAnnotation(node) {
96
+ if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
97
+ checkTypeNodeForAny(node.typeAnnotation.typeAnnotation, node)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 检查表达式是否可以推导类型
103
+ */
104
+ function canInferTypeFromExpression(node) {
105
+ if (!node) return false
106
+
107
+ // 字面量可以直接推导类型
108
+ if (node.type === 'Literal') {
109
+ return true
110
+ }
111
+
112
+ // 对象字面量可以推导类型
113
+ if (node.type === 'ObjectExpression') {
114
+ return true
115
+ }
116
+
117
+ // 数组字面量可以推导类型
118
+ if (node.type === 'ArrayExpression') {
119
+ return true
120
+ }
121
+
122
+ // 函数表达式和箭头函数可以推导类型(函数本身有类型)
123
+ if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
124
+ return true
125
+ }
126
+
127
+ // 函数调用:检查参数是否可以推导类型
128
+ if (node.type === 'CallExpression') {
129
+ // 如果函数调用有参数,并且所有参数都可以推导类型,则认为可以推导返回类型
130
+ if (node.arguments && node.arguments.length > 0) {
131
+ return node.arguments.every(arg => canInferTypeFromExpression(arg))
132
+ }
133
+ // 没有参数的函数调用无法推导类型(如 ref())
134
+ return false
135
+ }
136
+
137
+ // 属性访问通常可以推导类型(如 ref.value)
138
+ if (node.type === 'MemberExpression') {
139
+ return true
140
+ }
141
+
142
+ // await 表达式可以推导类型(等待 Promise 的结果)
143
+ if (node.type === 'AwaitExpression') {
144
+ return true
145
+ }
146
+
147
+ // 其他情况默认无法推导类型
148
+ return false
149
+ }
150
+
151
+ /**
152
+ * 检查变量声明中的隐式 any 类型
153
+ */
154
+ function checkVariableDeclaration(node) {
155
+ // 遍历所有声明
156
+ node.declarations.forEach(declaration => {
157
+ if (declaration.id && declaration.id.type === 'Identifier') {
158
+ // 检查变量声明是否没有类型注解
159
+ if (declaration.id.typeAnnotation === undefined) {
160
+ // 检查是否是函数参数
161
+ const isFunctionParam = node.parent &&
162
+ (node.parent.type === 'FunctionDeclaration' ||
163
+ node.parent.type === 'FunctionExpression' ||
164
+ node.parent.type === 'ArrowFunctionExpression')
165
+
166
+ if (!isFunctionParam) {
167
+ // 检查是否有初始值可以推导类型
168
+ const hasInitializer = declaration.init !== undefined && declaration.init !== null
169
+
170
+ // 如果变量没有初始值,则认为是隐式 any
171
+ if (!hasInitializer) {
172
+ anyUsages.push({
173
+ node: declaration,
174
+ type: 'implicit',
175
+ })
176
+ } else if (declaration.init) {
177
+ // 检查初始值是否可以推导类型
178
+ const canInferType = canInferTypeFromExpression(declaration.init)
179
+ if (!canInferType) {
180
+ // 额外检查:确保不是函数声明
181
+ const isFunctionDeclaration = declaration.init.type === 'FunctionExpression' ||
182
+ declaration.init.type === 'ArrowFunctionExpression'
183
+
184
+ if (!isFunctionDeclaration) {
185
+ anyUsages.push({
186
+ node: declaration,
187
+ type: 'implicit',
188
+ })
189
+ }
190
+ }
191
+ }
192
+ }
193
+ } else if (declaration.id.typeAnnotation) {
194
+ // 检查类型注解中的 any
195
+ checkTypeAnnotation(declaration.id)
196
+ }
197
+ }
198
+ })
199
+ }
200
+
201
+ /**
202
+ * 检查类型别名和接口中的 any 类型
203
+ */
204
+ function checkTypeDeclaration(node) {
205
+ if (node.typeAnnotation) {
206
+ checkTypeNodeForAny(node.typeAnnotation, node)
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 在文件结束时检查 any 类型使用次数
212
+ */
213
+ function checkAnyUsageCount() {
214
+ if (anyUsages.length > maxAnyCount) {
215
+ // 报告第一个超出限制的 any 使用(第 maxAnyCount + 1 个)
216
+ const firstExceededUsage = anyUsages[maxAnyCount]
217
+ context.report({
218
+ node: firstExceededUsage.node,
219
+ messageId: 'exceedMaxAnyCount',
220
+ data: {
221
+ maxAnyCount,
222
+ currentCount: anyUsages.length,
223
+ },
224
+ })
225
+ }
226
+ }
227
+
228
+ return {
229
+ TSAnyKeyword: checkTSAnyKeyword,
230
+ VariableDeclaration: checkVariableDeclaration,
231
+ TSTypeAliasDeclaration: checkTypeDeclaration,
232
+ TSInterfaceDeclaration: checkTypeDeclaration,
233
+ 'Program:exit': checkAnyUsageCount,
234
+ }
235
+ },
236
+ }
@@ -12,6 +12,12 @@ export interface NoInitialAsAssertionOptions {
12
12
  * @default []
13
13
  */
14
14
  excludeMethodReturns?: string[]
15
+
16
+ /**
17
+ * 排除匹配正则表达式模式的类型断言
18
+ * @default []
19
+ */
20
+ excludeTypePatterns?: string[]
15
21
  }
16
22
 
17
23
  /**
@@ -23,6 +23,14 @@ module.exports = {
23
23
  },
24
24
  default: [],
25
25
  description: '排除特定方法返回类型的 as 断言'
26
+ },
27
+ excludeTypePatterns: {
28
+ type: 'array',
29
+ items: {
30
+ type: 'string'
31
+ },
32
+ default: [],
33
+ description: '排除匹配正则表达式模式的类型断言'
26
34
  }
27
35
  },
28
36
  additionalProperties: false
@@ -37,6 +45,7 @@ module.exports = {
37
45
  const options = context.options[0] || {};
38
46
  const excludeVariableAssertions = options.excludeVariableAssertions || false;
39
47
  const excludeMethodReturns = options.excludeMethodReturns || [];
48
+ const excludeTypePatterns = options.excludeTypePatterns || [];
40
49
 
41
50
  /**
42
51
  * 检查是否是变量断言的情况
@@ -45,9 +54,10 @@ module.exports = {
45
54
  */
46
55
  function isVariableAssertion(node) {
47
56
  // 检查是否是变量断言:const a = variable as Type
57
+ // 当 excludeVariableAssertions 为 true 时,返回 true 表示要排除
48
58
  return node.expression &&
49
59
  node.expression.type === 'Identifier' &&
50
- !excludeVariableAssertions;
60
+ excludeVariableAssertions;
51
61
  }
52
62
 
53
63
  /**
@@ -78,6 +88,45 @@ module.exports = {
78
88
  return false;
79
89
  }
80
90
 
91
+ /**
92
+ * 检查是否是 as const 断言
93
+ * @param {Object} node TSAsExpression 节点
94
+ * @returns {boolean}
95
+ */
96
+ function isConstAssertion(node) {
97
+ // 检查是否是 as const 断言
98
+ return node.typeAnnotation &&
99
+ node.typeAnnotation.type === 'TSTypeReference' &&
100
+ node.typeAnnotation.typeName &&
101
+ node.typeAnnotation.typeName.name === 'const';
102
+ }
103
+
104
+ /**
105
+ * 检查是否匹配排除的类型模式
106
+ * @param {Object} node TSAsExpression 节点
107
+ * @returns {boolean}
108
+ */
109
+ function matchesExcludedTypePattern(node) {
110
+ if (excludeTypePatterns.length === 0) {
111
+ return false;
112
+ }
113
+
114
+ // 获取类型注解的源代码
115
+ const sourceCode = context.getSourceCode();
116
+ const typeAnnotationText = sourceCode.getText(node.typeAnnotation);
117
+
118
+ // 检查是否匹配任何排除模式
119
+ return excludeTypePatterns.some(pattern => {
120
+ try {
121
+ const regex = new RegExp(pattern);
122
+ return regex.test(typeAnnotationText);
123
+ } catch (error) {
124
+ // 如果正则表达式无效,忽略该模式
125
+ return false;
126
+ }
127
+ });
128
+ }
129
+
81
130
  /**
82
131
  * 检查变量声明是否使用了 as 类型断言
83
132
  * @param {Object} node AST节点
@@ -104,6 +153,11 @@ module.exports = {
104
153
  return;
105
154
  }
106
155
 
156
+ // 排除匹配正则表达式模式的类型断言
157
+ if (matchesExcludedTypePattern(declaration.init)) {
158
+ return;
159
+ }
160
+
107
161
  // 对于直接的类型断言,应该限制
108
162
  // 例如:const a = 1 as number, const a = {} as { s: string }
109
163
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundbyte/eslint-plugin",
3
- "version": "1.1.1",
3
+ "version": "1.1.4-alpha",
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'