mm_eslint 1.2.6 → 1.2.8
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_EN.md +18 -18
- package/config.js +523 -0
- package/detector.js +998 -0
- package/index.js +268 -2836
- package/package.json +4 -2
- package/README.md +0 -410
package/index.js
CHANGED
|
@@ -1,2891 +1,323 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 命名规范检测器类
|
|
5
|
-
*/
|
|
6
|
-
class Detector {
|
|
7
|
-
static config = {
|
|
8
|
-
// 推荐词功能开关
|
|
9
|
-
recommen: true,
|
|
10
|
-
// 推荐词,当命名时较长时推荐使用的词
|
|
11
|
-
recommen_words: {
|
|
12
|
-
// 函数推荐词
|
|
13
|
-
function: {
|
|
14
|
-
// === 动词类 ===
|
|
15
|
-
// 增加
|
|
16
|
-
add: {
|
|
17
|
-
words: ["push", "insert"]
|
|
18
|
-
},
|
|
19
|
-
// 删除
|
|
20
|
-
del: {
|
|
21
|
-
words: ["remove", "delete"]
|
|
22
|
-
},
|
|
23
|
-
// 查询
|
|
24
|
-
get: {
|
|
25
|
-
words: ["query"]
|
|
26
|
-
},
|
|
27
|
-
// 修改
|
|
28
|
-
set: {
|
|
29
|
-
words: ["setting"]
|
|
30
|
-
},
|
|
31
|
-
// 转换
|
|
32
|
-
to: {
|
|
33
|
-
words: ["convert", "transform"]
|
|
34
|
-
},
|
|
35
|
-
// 检查
|
|
36
|
-
check: {
|
|
37
|
-
words: ["verify", "validate", "confirm"]
|
|
38
|
-
},
|
|
39
|
-
// 创建
|
|
40
|
-
create: {
|
|
41
|
-
words: ["construct"]
|
|
42
|
-
},
|
|
43
|
-
// 生成
|
|
44
|
-
gen: {
|
|
45
|
-
words: ["generate"]
|
|
46
|
-
},
|
|
47
|
-
// 初始化
|
|
48
|
-
init: {
|
|
49
|
-
words: ["initialize"]
|
|
50
|
-
},
|
|
51
|
-
// 处理
|
|
52
|
-
run: {
|
|
53
|
-
words: ["process", "handle"]
|
|
54
|
-
},
|
|
55
|
-
// 执行
|
|
56
|
-
exec: {
|
|
57
|
-
words: ["execute"]
|
|
58
|
-
},
|
|
59
|
-
// 计算
|
|
60
|
-
calc: {
|
|
61
|
-
words: ["calculate", "compute"]
|
|
62
|
-
},
|
|
63
|
-
// 加载
|
|
64
|
-
load: {
|
|
65
|
-
words: ["fetch", "retrieve"]
|
|
66
|
-
},
|
|
67
|
-
// 保存
|
|
68
|
-
save: {
|
|
69
|
-
words: ["store", "persist"]
|
|
70
|
-
},
|
|
71
|
-
// 比较
|
|
72
|
-
cmp: {
|
|
73
|
-
words: ["compare", "diff"]
|
|
74
|
-
},
|
|
75
|
-
// 排序
|
|
76
|
-
sort: {
|
|
77
|
-
words: ["order", "arrange"]
|
|
78
|
-
},
|
|
79
|
-
// 查找
|
|
80
|
-
find: {
|
|
81
|
-
words: ["search", "locate"]
|
|
82
|
-
},
|
|
83
|
-
// 删除
|
|
84
|
-
del: {
|
|
85
|
-
words: ["remove", "delete"]
|
|
86
|
-
},
|
|
87
|
-
// 解析
|
|
88
|
-
parse: {
|
|
89
|
-
words: ["analyze"]
|
|
90
|
-
},
|
|
91
|
-
// 复制
|
|
92
|
-
copy: {
|
|
93
|
-
words: ["duplicate", "clone"]
|
|
94
|
-
},
|
|
95
|
-
// 移动
|
|
96
|
-
move: {
|
|
97
|
-
words: ["transfer", "relocate"]
|
|
98
|
-
},
|
|
99
|
-
// 压缩
|
|
100
|
-
zip: {
|
|
101
|
-
words: ["compress"]
|
|
102
|
-
},
|
|
103
|
-
// 解压
|
|
104
|
-
unzip: {
|
|
105
|
-
words: ["decompress"]
|
|
106
|
-
},
|
|
107
|
-
// 反转
|
|
108
|
-
flip: {
|
|
109
|
-
words: ["reverse"]
|
|
110
|
-
},
|
|
111
|
-
// === 名词类 ===
|
|
112
|
-
// 函数
|
|
113
|
-
Func: {
|
|
114
|
-
words: ["Function"]
|
|
115
|
-
},
|
|
116
|
-
// 值
|
|
117
|
-
Val: {
|
|
118
|
-
words: ["Value"]
|
|
119
|
-
},
|
|
120
|
-
// 数组
|
|
121
|
-
Arr: {
|
|
122
|
-
words: ["Array"]
|
|
123
|
-
},
|
|
124
|
-
// 对象
|
|
125
|
-
Obj: {
|
|
126
|
-
words: ["Object"]
|
|
127
|
-
},
|
|
128
|
-
// 数值
|
|
129
|
-
Num: {
|
|
130
|
-
words: ["Number"]
|
|
131
|
-
},
|
|
132
|
-
// 字符串
|
|
133
|
-
Str: {
|
|
134
|
-
words: ["String"]
|
|
135
|
-
},
|
|
136
|
-
// 字典
|
|
137
|
-
Dict: {
|
|
138
|
-
words: ["Dictionary"]
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
variable: {
|
|
142
|
-
// 连接器
|
|
143
|
-
conn: {
|
|
144
|
-
words: ["connector"]
|
|
145
|
-
},
|
|
146
|
-
// 函数
|
|
147
|
-
func: {
|
|
148
|
-
words: ["function"]
|
|
149
|
-
},
|
|
150
|
-
// 值
|
|
151
|
-
val: {
|
|
152
|
-
words: ["value"]
|
|
153
|
-
},
|
|
154
|
-
// 枚举
|
|
155
|
-
enum: {
|
|
156
|
-
words: ["enumeration"]
|
|
157
|
-
},
|
|
158
|
-
// 数组
|
|
159
|
-
arr: {
|
|
160
|
-
words: ["array"]
|
|
161
|
-
},
|
|
162
|
-
// 对象
|
|
163
|
-
obj: {
|
|
164
|
-
words: ["object"]
|
|
165
|
-
},
|
|
166
|
-
// 数值
|
|
167
|
-
num: {
|
|
168
|
-
words: ["number"]
|
|
169
|
-
},
|
|
170
|
-
// 字符串
|
|
171
|
-
str: {
|
|
172
|
-
words: ["string"]
|
|
173
|
-
},
|
|
174
|
-
bool: {
|
|
175
|
-
words: ["boolean"]
|
|
176
|
-
},
|
|
177
|
-
// 字典
|
|
178
|
-
dict: {
|
|
179
|
-
words: ["dictionary"]
|
|
180
|
-
},
|
|
181
|
-
// 模块
|
|
182
|
-
mod: {
|
|
183
|
-
words: ["module"]
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
// 私有方法前缀列表:以这些前缀开头的方法名应该被检测为私有方法
|
|
188
|
-
private_method_prefixes: [
|
|
189
|
-
'setup', 'init', 'internal', 'private', 'helper', 'util'
|
|
190
|
-
],
|
|
191
|
-
|
|
192
|
-
// 忽略词列表:这些词汇在命名检测时会被忽略,不进行长度和风格检测
|
|
193
|
-
ignore_words: {
|
|
194
|
-
// 类名忽略词:这些词汇在类名中允许使用,即使超过8个字符
|
|
195
|
-
class: [
|
|
196
|
-
"Controller", "Middleware", "Component", "Repository", "Validator",
|
|
197
|
-
"Transformer", "Serializer", "Interceptor", "Decorator", "Provider"
|
|
198
|
-
],
|
|
199
|
-
// 方法名忽略词:这些词汇在方法名中允许使用,即使超过8个字符
|
|
200
|
-
method: [
|
|
201
|
-
"middleware", "controller", "component", "repository", "validator",
|
|
202
|
-
"transformer", "serializer", "interceptor", "decorator", "provider"
|
|
203
|
-
],
|
|
204
|
-
// 函数名忽略词:这些词汇在函数名中允许使用,即使超过8个字符
|
|
205
|
-
function: [
|
|
206
|
-
"middleware", "controller", "component", "repository", "validator",
|
|
207
|
-
"transformer", "serializer", "interceptor", "decorator", "provider"
|
|
208
|
-
],
|
|
209
|
-
variable: [
|
|
210
|
-
"middleware", "controller", "component", "repository", "validator",
|
|
211
|
-
"transformer", "serializer", "interceptor", "decorator", "provider"
|
|
212
|
-
]
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
// 废话词
|
|
216
|
-
forbidden_words: {
|
|
217
|
-
// 类废话词:禁止在类名中使用的冗余拼接词汇(统一小写)
|
|
218
|
-
// 这些词汇在类名中属于过度设计或冗余描述,即使在某些场景下合理,但作为拼接命名仍然是冗余的
|
|
219
|
-
class: [
|
|
220
|
-
"manager", "handler", "processor", "controller", "service", "provider",
|
|
221
|
-
"factory", "builder", "adapter", "decorator", "proxy", "facade",
|
|
222
|
-
"mediator", "observer", "strategy", "command", "visitor",
|
|
223
|
-
"iterator", "template", "flyweight", "memento", "interpreter",
|
|
224
|
-
"model", "view", "route", "router", "component", "widget", "panel",
|
|
225
|
-
"dialog", "window", "form", "field", "property", "entity"
|
|
226
|
-
// 例如:UserModel → User (直接使用User),UserController → User (直接使用User)
|
|
227
|
-
],
|
|
228
|
-
|
|
229
|
-
// 函数废话词:禁止在函数名中使用的冗余拼接词汇(统一小写)
|
|
230
|
-
// 基于动词+名词组合分析:如果动词已经表达了动作,名词只是数据描述,则名词是废话词
|
|
231
|
-
// 有意义的词通常是拼接get、set、to后能有意义、直接表达的名词,如getUser、toJSON等
|
|
232
|
-
function: [
|
|
233
|
-
// 无意义的数据描述词汇(动词已经表达了动作,这些名词是冗余的)
|
|
234
|
-
"data", "result", "output", "input", "param", "params", "parameter", "parameters",
|
|
235
|
-
"value", "values", "item", "items",
|
|
236
|
-
// 通用处理词汇(动词已经表达了处理,这些名词是冗余的)
|
|
237
|
-
"process", "processor", "provider",
|
|
238
|
-
"builder", "adapter", "decorator", "proxy", "facade", "mediator",
|
|
239
|
-
"observer", "strategy", "command", "visitor", "iterator", "template",
|
|
240
|
-
"flyweight", "memento", "interpreter",
|
|
241
|
-
// 临时缓存词汇(动词已经表达了操作,这些名词是冗余的)
|
|
242
|
-
"temp", "tmp", "temporary", "cached", "buffered",
|
|
243
|
-
// 计数索引词汇(动词已经表达了计数索引,这些名词是冗余的)
|
|
244
|
-
"idx", "counter",
|
|
245
|
-
// 例如:getData → get(data是冗余),toResult → to(result是冗余)
|
|
246
|
-
// processData → process(data是冗余),handleRequest → handle(request是冗余)
|
|
247
|
-
],
|
|
248
|
-
|
|
249
|
-
// 变量废话词:禁止在变量名中使用的冗余拼接词汇(统一小写)
|
|
250
|
-
// 这些词汇在变量名中属于"不用说也知道"的过度描述
|
|
251
|
-
variable: [
|
|
252
|
-
// 数据类型词:变量本身就有类型,不需要重复说明
|
|
253
|
-
"object", "array", "map", "set", "collection", "container", "instance",
|
|
254
|
-
// 通用数据词:变量本身就是数据/值,不需要重复说明
|
|
255
|
-
"value", "data", "item", "items", "element", "elements", "entry", "entries",
|
|
256
|
-
// 临时状态词:临时变量不需要说明是临时的
|
|
257
|
-
"temp", "tmp", "temporary", "cached", "buffer", "buffered",
|
|
258
|
-
// 输入输出词:变量本身就有用途,不需要重复说明
|
|
259
|
-
"input", "output", "result", "source", "target", "destination",
|
|
260
|
-
// 计数索引词:变量本身就有计数/索引功能,不需要重复说明
|
|
261
|
-
"index", "idx", "counter", "length", "total", "sum",
|
|
262
|
-
// 引用指针词:变量本身就是引用/指针,不需要重复说明
|
|
263
|
-
"pointer", "reference", "ref", "handle", "handler",
|
|
264
|
-
// 通用描述词:变量本身就是实体,不需要重复说明
|
|
265
|
-
"entity", "record", "row", "column", "cell", "property", "attr", "attribute"
|
|
266
|
-
// 例如:user_list → users(list是冗余),temp_value → temp(value是冗余)
|
|
267
|
-
]
|
|
268
|
-
},
|
|
269
|
-
regex: {
|
|
270
|
-
PascalCase: /^[A-Z][a-zA-Z]*$/, // 大驼峰:大写字母开头,只包含字母。主要用于类名
|
|
271
|
-
camelCase: /^[a-z][a-zA-Z]*$/, // 小驼峰:小写字母开头,只包含字母。主要用于方法名、函数名
|
|
272
|
-
snake_case: /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写蛇形:小写字母开头,可包含下划线。主要用于变量名、入参名
|
|
273
|
-
UPPER_SNAKE_CASE: /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/, // 大写蛇形:大写字母开头,可包含下划线。主要用于常量名
|
|
274
|
-
"kebab-case": /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 小写横杠:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
|
|
275
|
-
"UPPER-KEBAB-CASE": /^[A-Z][A-Z0-9-]*(-[A-Z0-9]+)*$/, // 大写横杠:大写字母开头,可包含短横线,大写短横线。主要用于资源名、路由路径
|
|
276
|
-
lowercase: /^[a-z][a-z0-9]*$/, // 小写单词:小写字母开头,只包含小写字母和数字。主要用于对象、属性名
|
|
277
|
-
UPPERCASE: /^[A-Z][A-Z0-9]*$/, // 大写单词:大写字母开头,只包含大写字母和数字。主要用于枚举值、常量名
|
|
278
|
-
"camelCase-kebab": /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 支持横杠小驼峰:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
|
|
279
|
-
"PascalCase-kebab": /^[A-Z][a-zA-Z]*$/, // 支持横杠大驼峰:大写字母开头,只包含字母。主要用于资源名、路由路径
|
|
280
|
-
_camelCase: /^_[a-z][a-zA-Z]*$/, // 下划线小驼峰:下划线加小写字母开头,只包含字母。主要用于私有方法名
|
|
281
|
-
_snake_case: /^_[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 下划线小写蛇形:下划线加小写字母开头,可包含下划线。主要用于私有变量名
|
|
282
|
-
_lowercase: /^_[a-z][a-z0-9]*$/, // 下划线小写单词:下划线加小写字母开头,只包含小写字母和数字。主要用于对象、属性名
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
// 类名大驼峰规则
|
|
286
|
-
"class-name": {
|
|
287
|
-
name: "类名",
|
|
288
|
-
message:
|
|
289
|
-
"必须使用大驼峰命名法(PascalCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
|
|
290
|
-
regex: null, // 使用styles属性进行验证
|
|
291
|
-
min: 1,
|
|
292
|
-
max: 20,
|
|
293
|
-
styles: ["PascalCase"],
|
|
294
|
-
single_word: true,
|
|
295
|
-
single_word_len: 8,
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
// 函数名小驼峰规则
|
|
299
|
-
"function-name": {
|
|
300
|
-
name: "函数名",
|
|
301
|
-
message:
|
|
302
|
-
"必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
|
|
303
|
-
regex: null, // 使用styles属性进行验证
|
|
304
|
-
min: 1,
|
|
305
|
-
max: 20,
|
|
306
|
-
styles: ["camelCase"],
|
|
307
|
-
single_word: true,
|
|
308
|
-
single_word_len: 8,
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
// 方法名小驼峰规则
|
|
312
|
-
"method-name": {
|
|
313
|
-
name: "方法名",
|
|
314
|
-
message:
|
|
315
|
-
"必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
|
|
316
|
-
regex: null, // 使用styles属性进行验证
|
|
317
|
-
min: 1,
|
|
318
|
-
max: 20,
|
|
319
|
-
styles: ["camelCase"],
|
|
320
|
-
single_word: true,
|
|
321
|
-
single_word_len: 8,
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
// 入参名小写蛇形规则
|
|
325
|
-
"param-name": {
|
|
326
|
-
name: "入参名",
|
|
327
|
-
message:
|
|
328
|
-
"必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
|
|
329
|
-
regex: null, // 使用styles属性进行验证
|
|
330
|
-
min: 1,
|
|
331
|
-
max: 20,
|
|
332
|
-
styles: ["lowercase", "snake_case"],
|
|
333
|
-
single_word: true,
|
|
334
|
-
single_word_len: 8,
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
// 变量名小写蛇形规则
|
|
338
|
-
"variable-name": {
|
|
339
|
-
name: "变量名",
|
|
340
|
-
message:
|
|
341
|
-
"必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
|
|
342
|
-
regex: null, // 使用styles属性进行验证
|
|
343
|
-
min: 1,
|
|
344
|
-
max: 20,
|
|
345
|
-
styles: ["lowercase", "snake_case"],
|
|
346
|
-
single_word: true,
|
|
347
|
-
single_word_len: 8,
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
// 常量名大写蛇形规则
|
|
351
|
-
"constant-name": {
|
|
352
|
-
name: "常量名",
|
|
353
|
-
message:
|
|
354
|
-
"必须使用大写蛇形命名法(UPPER_SNAKE_CASE),长度{min}-{max}字符",
|
|
355
|
-
regex: null, // 使用styles属性进行验证
|
|
356
|
-
min: 1,
|
|
357
|
-
max: 20,
|
|
358
|
-
styles: ["UPPERCASE", "UPPER_SNAKE_CASE"],
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
// 属性名多风格规则(根据值类型应用不同规则)
|
|
362
|
-
"property-name": {
|
|
363
|
-
name: "属性名",
|
|
364
|
-
message: "根据属性值类型应用不同命名规则",
|
|
365
|
-
min: 1,
|
|
366
|
-
max: 20,
|
|
367
|
-
single_word: true,
|
|
368
|
-
single_word_len: 8,
|
|
369
|
-
styles: [
|
|
370
|
-
"lowercase",
|
|
371
|
-
"_lowercase",
|
|
372
|
-
"snake_case",
|
|
373
|
-
"_snake_case",
|
|
374
|
-
"UPPERCASE",
|
|
375
|
-
"UPPER_SNAKE_CASE"
|
|
376
|
-
],
|
|
377
|
-
strong: true, // 强验证模式:根据属性值类型智能判断;false:弱验证模式:只需符合变量名或常量名规则
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
// 私有方法规则
|
|
381
|
-
"private-method-naming": {
|
|
382
|
-
name: "私有方法",
|
|
383
|
-
message:
|
|
384
|
-
"必须以单下划线开头,后跟小驼峰命名或小写单词,长度{min}-{max}字符",
|
|
385
|
-
regex: null, // 使用styles属性进行验证
|
|
386
|
-
min: 2,
|
|
387
|
-
max: 20,
|
|
388
|
-
styles: ["_lowercase", "_camelCase"],
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
// 私有变量规则
|
|
392
|
-
"private-variable-naming": {
|
|
393
|
-
name: "私有变量",
|
|
394
|
-
message:
|
|
395
|
-
"必须以单下划线开头,后跟小写蛇形命名或小写单词,长度{min}-{max}字符",
|
|
396
|
-
regex: null, // 使用styles属性进行验证
|
|
397
|
-
min: 2,
|
|
398
|
-
max: 20,
|
|
399
|
-
styles: ["_lowercase", "_snake_case"],
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
constructor(config) {
|
|
404
|
-
// 合并默认配置和传入配置
|
|
405
|
-
const merged_cfg = { ...Detector.config };
|
|
406
|
-
if (config && typeof config === "object") {
|
|
407
|
-
// 先合并顶层配置
|
|
408
|
-
Object.keys(config).forEach((key) => {
|
|
409
|
-
if (merged_cfg[key] && typeof merged_cfg[key] === "object" && !Array.isArray(merged_cfg[key])) {
|
|
410
|
-
// 如果是对象配置(如规则配置),则深度合并
|
|
411
|
-
merged_cfg[key] = this._deepMerge(merged_cfg[key], config[key]);
|
|
412
|
-
|
|
413
|
-
// 处理字符串形式的正则表达式
|
|
414
|
-
if (typeof merged_cfg[key].regex === "string") {
|
|
415
|
-
try {
|
|
416
|
-
// 从字符串中提取正则表达式模式和标志
|
|
417
|
-
const regex_match =
|
|
418
|
-
merged_cfg[key].regex.match(/^\/(.*)\/([gimuy]*)$/);
|
|
419
|
-
if (regex_match) {
|
|
420
|
-
merged_cfg[key].regex = new RegExp(
|
|
421
|
-
regex_match[1],
|
|
422
|
-
regex_match[2],
|
|
423
|
-
);
|
|
424
|
-
} else {
|
|
425
|
-
// 如果不是标准的正则表达式字符串格式,直接创建
|
|
426
|
-
merged_cfg[key].regex = new RegExp(merged_cfg[key].regex);
|
|
427
|
-
}
|
|
428
|
-
} catch (error) {
|
|
429
|
-
console.warn(`无效的正则表达式: ${merged_cfg[key].regex}`, error);
|
|
430
|
-
merged_cfg[key].regex = null;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
} else {
|
|
434
|
-
// 如果是顶层字段(如recommen),直接覆盖
|
|
435
|
-
merged_cfg[key] = config[key];
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
this.config = merged_cfg;
|
|
440
|
-
this._initRule();
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* 深度合并两个对象
|
|
445
|
-
* @param {Object} target 目标对象
|
|
446
|
-
* @param {Object} source 源对象
|
|
447
|
-
* @returns {Object} 合并后的对象
|
|
448
|
-
* @private
|
|
449
|
-
*/
|
|
450
|
-
_deepMerge(target, source) {
|
|
451
|
-
const result = { ...target };
|
|
452
|
-
|
|
453
|
-
Object.keys(source).forEach((key) => {
|
|
454
|
-
const source_val = source[key];
|
|
455
|
-
const target_val = target[key];
|
|
456
|
-
|
|
457
|
-
if (Array.isArray(source_val) && Array.isArray(target_val)) {
|
|
458
|
-
// 如果是数组,则合并数组(去重)
|
|
459
|
-
result[key] = [...new Set([...target_val, ...source_val])];
|
|
460
|
-
} else if (typeof source_val === 'object' && source_val !== null &&
|
|
461
|
-
typeof target_val === 'object' && target_val !== null &&
|
|
462
|
-
!Array.isArray(source_val) && !Array.isArray(target_val)) {
|
|
463
|
-
// 如果是对象,则递归深度合并
|
|
464
|
-
result[key] = this._deepMerge(target_val, source_val);
|
|
465
|
-
} else {
|
|
466
|
-
// 其他情况直接覆盖
|
|
467
|
-
result[key] = source_val;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
return result;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* 创建新的规则模型
|
|
477
|
-
* @returns {Object} 规则模型
|
|
478
|
-
* @private
|
|
479
|
-
*/
|
|
480
|
-
Detector.prototype._newModel = function () {
|
|
481
|
-
return {
|
|
482
|
-
name: "",
|
|
483
|
-
message: "",
|
|
484
|
-
regex: null,
|
|
485
|
-
min: 1,
|
|
486
|
-
max: 20,
|
|
487
|
-
};
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* 初始化规则检查方法
|
|
492
|
-
* @private
|
|
493
|
-
*/
|
|
494
|
-
Detector.prototype._initRule = function () {
|
|
495
|
-
// 绑定规则检查方法
|
|
496
|
-
this._rules = {
|
|
497
|
-
"class-name": this._checkName.bind(this, "class-name"),
|
|
498
|
-
"function-name": this._checkName.bind(this, "function-name"),
|
|
499
|
-
"method-name": this._checkName.bind(this, "method-name"),
|
|
500
|
-
"param-name": this._checkName.bind(this, "param-name"),
|
|
501
|
-
"variable-name": this._checkName.bind(this, "variable-name"),
|
|
502
|
-
"constant-name": this._checkName.bind(this, "constant-name"),
|
|
503
|
-
"property-name": this._checkPropName.bind(this),
|
|
504
|
-
"private-method-naming": this._checkName.bind(
|
|
505
|
-
this,
|
|
506
|
-
"private-method-naming",
|
|
507
|
-
),
|
|
508
|
-
"private-variable-naming": this._checkName.bind(
|
|
509
|
-
this,
|
|
510
|
-
"private-variable-naming",
|
|
511
|
-
),
|
|
512
|
-
};
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* 通用命名检查方法
|
|
517
|
-
* @param {string} rule_type - 规则类型
|
|
518
|
-
* @param {string} name - 名称
|
|
519
|
-
* @returns {Object} 检查结果
|
|
520
|
-
* @private
|
|
521
|
-
*/
|
|
522
|
-
Detector.prototype._checkName = function (rule_type, name) {
|
|
523
|
-
if (!name) {
|
|
524
|
-
throw new TypeError("名称不能为空");
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const config = this.config[rule_type];
|
|
528
|
-
if (!config) {
|
|
529
|
-
throw new Error(`不支持的规则类型: ${rule_type}`);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const errors = [];
|
|
533
|
-
const warnings = [];
|
|
534
|
-
const recommendations = [];
|
|
535
|
-
|
|
536
|
-
// 首先检查是否为忽略词,如果是则跳过其他检测
|
|
537
|
-
if (this._isIgnoreWord(name, rule_type)) {
|
|
538
|
-
return {
|
|
539
|
-
valid: true,
|
|
540
|
-
errors: [],
|
|
541
|
-
warnings: [],
|
|
542
|
-
recommendations: []
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
this._checkNameLength(name, config, errors);
|
|
547
|
-
this._checkNameFormat(name, rule_type, config, errors);
|
|
548
|
-
this._checkBadWords(name, config, errors);
|
|
549
|
-
this._checkSingleWord(name, rule_type, config, warnings);
|
|
550
|
-
this._checkWordLength(name, rule_type, config, errors);
|
|
551
|
-
this._checkRecommen(name, rule_type, config, recommendations);
|
|
552
|
-
|
|
553
|
-
return {
|
|
554
|
-
valid: errors.length === 0,
|
|
555
|
-
errors: errors,
|
|
556
|
-
warnings: warnings,
|
|
557
|
-
recommendations: recommendations,
|
|
558
|
-
};
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* 检查名称长度限制
|
|
563
|
-
* @private
|
|
564
|
-
*/
|
|
565
|
-
Detector.prototype._checkNameLength = function (name, config, errors) {
|
|
566
|
-
if (name.length < config.min) {
|
|
567
|
-
errors.push(`${config.name}长度不能少于${config.min}个字符`);
|
|
568
|
-
}
|
|
569
|
-
if (name.length > config.max) {
|
|
570
|
-
errors.push(`${config.name}长度不能超过${config.max}个字符`);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* 检查名称格式
|
|
576
|
-
* @private
|
|
577
|
-
*/
|
|
578
|
-
Detector.prototype._checkNameFormat = function (
|
|
579
|
-
name,
|
|
580
|
-
rule_type,
|
|
581
|
-
config,
|
|
582
|
-
errors,
|
|
583
|
-
) {
|
|
584
|
-
let name_valid = false;
|
|
585
|
-
|
|
586
|
-
if (config.regex && config.regex.toString() !== "/(?:)/") {
|
|
587
|
-
name_valid = config.regex.test(name);
|
|
588
|
-
if (!name_valid) {
|
|
589
|
-
const error_message = config.message
|
|
590
|
-
.replace("{name}", `"${name}"`)
|
|
591
|
-
.replace("{type}", config.name)
|
|
592
|
-
.replace("{min}", config.min)
|
|
593
|
-
.replace("{max}", config.max)
|
|
594
|
-
.replace("{word_len}", config.single_word_len || 8)
|
|
595
|
-
.replace("{regex}", config.regex.toString());
|
|
596
|
-
errors.push(error_message);
|
|
597
|
-
}
|
|
598
|
-
} else if (config.styles && config.styles.length > 0) {
|
|
599
|
-
name_valid = this._checkStyle(name, config.styles);
|
|
600
|
-
if (!name_valid) {
|
|
601
|
-
const allowed_styles = config.styles.join("、");
|
|
602
|
-
errors.push(`${config.name}必须符合以下命名风格之一:${allowed_styles}`);
|
|
603
|
-
}
|
|
604
|
-
} else {
|
|
605
|
-
name_valid = true;
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* 检查禁止词汇
|
|
611
|
-
* @private
|
|
612
|
-
*/
|
|
613
|
-
Detector.prototype._checkBadWords = function (name, config, errors) {
|
|
614
|
-
// 根据配置类型确定代码实体类型
|
|
615
|
-
let type = 'variable'; // 默认变量类型
|
|
616
|
-
|
|
617
|
-
if (config.name.includes('类名')) {
|
|
618
|
-
type = 'class';
|
|
619
|
-
} else if (config.name.includes('函数名') || config.name.includes('方法名')) {
|
|
620
|
-
type = 'function';
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const bad_word = this._checkBadWord(name, type);
|
|
624
|
-
if (bad_word) {
|
|
625
|
-
errors.push(`${config.name}包含禁止的废话词"${bad_word}"`);
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* 检查是否优先使用单个单词
|
|
631
|
-
* @private
|
|
632
|
-
*/
|
|
633
|
-
Detector.prototype._checkSingleWord = function (
|
|
634
|
-
name,
|
|
635
|
-
rule_type,
|
|
636
|
-
config,
|
|
637
|
-
warnings,
|
|
638
|
-
) {
|
|
639
|
-
if (!config.single_word) return;
|
|
640
|
-
|
|
641
|
-
const has_uppercase = /[A-Z]/.test(name.slice(1));
|
|
642
|
-
const has_separators = name.includes("_") || name.includes("-");
|
|
643
|
-
const is_func_or_method =
|
|
644
|
-
rule_type === "function-name" || rule_type === "method-name";
|
|
645
|
-
let is_multi_word = has_uppercase || has_separators;
|
|
646
|
-
|
|
647
|
-
if (is_func_or_method && is_multi_word) {
|
|
648
|
-
const prefix_match = name.match(
|
|
649
|
-
/^(add|del|set|get|is|has|can|not)([A-Z][a-z]*)$/,
|
|
650
|
-
);
|
|
651
|
-
if (prefix_match && prefix_match[2]) {
|
|
652
|
-
is_multi_word = false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (is_multi_word) {
|
|
657
|
-
warnings.push(`${config.name}应优先使用单个单词`);
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* 检查单词长度限制
|
|
663
|
-
* @private
|
|
664
|
-
*/
|
|
665
|
-
Detector.prototype._checkWordLength = function (
|
|
666
|
-
name,
|
|
667
|
-
rule_type,
|
|
668
|
-
config,
|
|
669
|
-
errors,
|
|
670
|
-
) {
|
|
671
|
-
if (!config.single_word_len || config.single_word_len <= 0) return;
|
|
672
|
-
|
|
673
|
-
const words = this._splitWords(name, rule_type);
|
|
674
|
-
const long_words = words.filter(
|
|
675
|
-
(word) => word.length > config.single_word_len,
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
if (long_words.length > 0) {
|
|
679
|
-
errors.push(
|
|
680
|
-
`${config.name}中的单词"${long_words.join("、")}"超过${config.single_word_len}个字符限制`,
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* 根据styles列表检查名称是否符合命名风格
|
|
687
|
-
* @param {string} name - 名称
|
|
688
|
-
* @param {Array} styles - 命名风格列表
|
|
689
|
-
* @returns {boolean} 是否符合任一风格
|
|
690
|
-
* @private
|
|
691
|
-
*/
|
|
692
|
-
Detector.prototype._checkStyle = function (name, styles) {
|
|
693
|
-
if (!name || !styles || styles.length === 0) {
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// 检查名称是否符合任一指定的命名风格
|
|
698
|
-
for (const style of styles) {
|
|
699
|
-
const regex = this.config.regex[style];
|
|
700
|
-
if (regex && regex.test(name)) {
|
|
701
|
-
return true;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
return false;
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* 拆分名称中的单词
|
|
710
|
-
* @param {string} name - 名称
|
|
711
|
-
* @param {string} rule_type - 规则类型
|
|
712
|
-
* @returns {Array} 拆分后的单词数组
|
|
713
|
-
* @private
|
|
714
|
-
*/
|
|
715
|
-
Detector.prototype._splitWords = function (name, rule_type) {
|
|
716
|
-
if (!name) {
|
|
717
|
-
return [];
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const words = [];
|
|
721
|
-
|
|
722
|
-
// 根据命名风格拆分单词
|
|
723
|
-
if (name.includes("_")) {
|
|
724
|
-
// 蛇形命名:user_name → ["user", "name"]
|
|
725
|
-
words.push(...name.split("_").filter((word) => word.length > 0));
|
|
726
|
-
} else if (name.includes("-")) {
|
|
727
|
-
// 横杠命名:user-name → ["user", "name"]
|
|
728
|
-
words.push(...name.split("-").filter((word) => word.length > 0));
|
|
729
|
-
} else {
|
|
730
|
-
// 检查是否是纯小写单词(不包含大写字母)
|
|
731
|
-
if (!/[A-Z]/.test(name)) {
|
|
732
|
-
// 纯小写单词:configuration → ["configuration"]
|
|
733
|
-
words.push(name);
|
|
734
|
-
} else {
|
|
735
|
-
// 驼峰命名:userName → ["user", "Name"]
|
|
736
|
-
// 使用正则表达式拆分驼峰命名
|
|
737
|
-
const camel_case_words = name.split(/(?=[A-Z])/);
|
|
738
|
-
|
|
739
|
-
// 对于函数名和方法名,处理常见前缀
|
|
740
|
-
if (rule_type === "function-name" || rule_type === "method-name") {
|
|
741
|
-
const prefix_match = name.match(
|
|
742
|
-
/^(add|del|set|get|is|has|can|not)([A-Z].*)$/,
|
|
743
|
-
);
|
|
744
|
-
if (prefix_match) {
|
|
745
|
-
// 如果是前缀+单词的组合,将前缀作为单独单词
|
|
746
|
-
words.push(prefix_match[1]);
|
|
747
|
-
// 拆分剩余部分
|
|
748
|
-
const remaining_words = prefix_match[2].split(/(?=[A-Z])/);
|
|
749
|
-
words.push(...remaining_words);
|
|
750
|
-
} else {
|
|
751
|
-
words.push(...camel_case_words);
|
|
752
|
-
}
|
|
753
|
-
} else {
|
|
754
|
-
words.push(...camel_case_words);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// 过滤空字符串并转换为小写进行比较
|
|
760
|
-
return words
|
|
761
|
-
.filter((word) => word.length > 0)
|
|
762
|
-
.map((word) => word.toLowerCase());
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* 属性名检查方法(根据属性值类型应用不同规则)
|
|
767
|
-
* @param {string} name - 属性名
|
|
768
|
-
* @param {Object} val_node - 属性值AST节点
|
|
769
|
-
* @param {Object} parent_node - 父节点AST节点(可选,用于上下文感知检测)
|
|
770
|
-
* @returns {Object} 检查结果
|
|
771
|
-
* @private
|
|
772
|
-
*/
|
|
773
|
-
Detector.prototype._checkPropName = function (name, val_node, parent_node) {
|
|
774
|
-
if (!name) {
|
|
775
|
-
throw new TypeError("属性名不能为空");
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
const config = this.config["property-name"];
|
|
779
|
-
if (!config) {
|
|
780
|
-
throw new Error("属性名规则配置不存在");
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const errors = [];
|
|
784
|
-
const warnings = [];
|
|
785
|
-
|
|
786
|
-
this._checkPropLength(name, config, errors);
|
|
787
|
-
this._checkBadWords(name, config, errors);
|
|
788
|
-
|
|
789
|
-
const use_strong_val = config.strong !== false;
|
|
790
|
-
const val_type = this._getPropType(val_node, name, parent_node);
|
|
791
|
-
|
|
792
|
-
if (use_strong_val) {
|
|
793
|
-
this._checkPropStrong(name, val_type, config, errors, val_node);
|
|
794
|
-
} else {
|
|
795
|
-
this._checkPropWeak(name, val_type, config, errors);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
this._checkPropSingleWord(name, config, warnings);
|
|
799
|
-
|
|
800
|
-
return {
|
|
801
|
-
valid: errors.length === 0,
|
|
802
|
-
errors: errors,
|
|
803
|
-
warnings: warnings,
|
|
804
|
-
value_type: val_type,
|
|
805
|
-
};
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* 检查属性名长度
|
|
810
|
-
* @private
|
|
811
|
-
*/
|
|
812
|
-
Detector.prototype._checkPropLength = function (name, config, errors) {
|
|
813
|
-
if (name.length < config.min) {
|
|
814
|
-
errors.push(`${config.name}长度不能少于${config.min}个字符`);
|
|
815
|
-
}
|
|
816
|
-
if (name.length > config.max) {
|
|
817
|
-
errors.push(`${config.name}长度不能超过${config.max}个字符`);
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
/**
|
|
822
|
-
* 强验证模式检查
|
|
823
|
-
* @param {string} name - 属性名
|
|
824
|
-
* @param {string} val_type - 值类型
|
|
825
|
-
* @param {Object} config - 配置对象
|
|
826
|
-
* @param {Array} errors - 错误数组
|
|
827
|
-
* @param {Object} val_node - 属性值AST节点(可选)
|
|
828
|
-
* @private
|
|
829
|
-
*/
|
|
830
|
-
Detector.prototype._checkPropStrong = function (
|
|
831
|
-
name,
|
|
832
|
-
val_type,
|
|
833
|
-
config,
|
|
834
|
-
errors,
|
|
835
|
-
val_node,
|
|
836
|
-
) {
|
|
837
|
-
switch (val_type) {
|
|
838
|
-
case "function":
|
|
839
|
-
this._checkFuncProp(name, config, errors);
|
|
840
|
-
break;
|
|
841
|
-
case "method":
|
|
842
|
-
this._checkMethodProp(name, config, errors);
|
|
843
|
-
break;
|
|
844
|
-
case "class":
|
|
845
|
-
this._checkClassProp(name, config, errors, val_node);
|
|
846
|
-
break;
|
|
847
|
-
case "constant":
|
|
848
|
-
this._checkConstProp(name, config, errors);
|
|
849
|
-
break;
|
|
850
|
-
case "value":
|
|
851
|
-
default:
|
|
852
|
-
this._checkValueProp(name, config, errors);
|
|
853
|
-
break;
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* 弱验证模式检查
|
|
859
|
-
* @private
|
|
860
|
-
*/
|
|
861
|
-
Detector.prototype._checkPropWeak = function (name, val_type, config, errors) {
|
|
862
|
-
switch (val_type) {
|
|
863
|
-
case "function":
|
|
864
|
-
this._checkFuncProp(name, config, errors);
|
|
865
|
-
break;
|
|
866
|
-
case "method":
|
|
867
|
-
this._checkMethodProp(name, config, errors);
|
|
868
|
-
break;
|
|
869
|
-
case "class":
|
|
870
|
-
this._checkClassProp(name, config, errors);
|
|
871
|
-
break;
|
|
872
|
-
case "constant":
|
|
873
|
-
case "value":
|
|
874
|
-
default:
|
|
875
|
-
this._checkWeakValue(name, config, errors);
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* 检查函数属性
|
|
882
|
-
* @private
|
|
883
|
-
*/
|
|
884
|
-
Detector.prototype._checkFuncProp = function (name, config, errors) {
|
|
885
|
-
const func_res = this._checkName("function-name", name);
|
|
886
|
-
if (!func_res.valid) {
|
|
887
|
-
errors.push(`${config.name}(函数)${func_res.errors.join(", ")}`);
|
|
888
|
-
}
|
|
889
|
-
};
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* 检查类属性
|
|
893
|
-
* @param {string} name - 属性名
|
|
894
|
-
* @param {Object} config - 配置对象
|
|
895
|
-
* @param {Array} errors - 错误数组
|
|
896
|
-
* @param {Object} val_node - 属性值AST节点(可选)
|
|
897
|
-
* @private
|
|
898
|
-
*/
|
|
899
|
-
Detector.prototype._checkClassProp = function (name, config, errors, val_node) {
|
|
900
|
-
// 根据值节点类型和属性名模式判断是类定义还是类实例
|
|
901
|
-
// 1. 如果值节点是require调用,则是类定义
|
|
902
|
-
// 2. 如果属性名符合PascalCase命名规范,优先认为是类定义
|
|
903
|
-
const is_class_definition = (val_node && val_node.type === "CallExpression" &&
|
|
904
|
-
val_node.callee && val_node.callee.name === "require") ||
|
|
905
|
-
(name && /^[A-Z][a-zA-Z0-9]*$/.test(name));
|
|
906
|
-
const is_class_instance = !is_class_definition;
|
|
907
|
-
|
|
908
|
-
if (is_class_definition) {
|
|
909
|
-
// 类定义要求使用PascalCase
|
|
910
|
-
const cls_res = this._checkName("class-name", name);
|
|
911
|
-
if (!cls_res.valid) {
|
|
912
|
-
errors.push(`${config.name}(类)${cls_res.errors.join(", ")}`);
|
|
913
|
-
}
|
|
914
|
-
} else {
|
|
915
|
-
// 类实例允许使用小驼峰命名
|
|
916
|
-
const func_res = this._checkName("function-name", name);
|
|
917
|
-
if (!func_res.valid) {
|
|
918
|
-
errors.push(`${config.name}(类实例)${func_res.errors.join(", ")}`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
};
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
* 检查常量属性
|
|
925
|
-
* @private
|
|
926
|
-
*/
|
|
927
|
-
Detector.prototype._checkConstProp = function (name, config, errors) {
|
|
928
|
-
const is_valid_const = this._checkStyle(name, [
|
|
929
|
-
"UPPERCASE",
|
|
930
|
-
"UPPER_SNAKE_CASE",
|
|
931
|
-
]);
|
|
932
|
-
if (!is_valid_const) {
|
|
933
|
-
this._suggestConstName(name, config, errors);
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
/**
|
|
938
|
-
* 检查方法属性
|
|
939
|
-
* @private
|
|
940
|
-
*/
|
|
941
|
-
Detector.prototype._checkMethodProp = function (name, config, errors) {
|
|
942
|
-
const method_res = this._checkName("method-name", name);
|
|
943
|
-
if (!method_res.valid) {
|
|
944
|
-
errors.push(`${config.name}(方法)${method_res.errors.join(", ")}`);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* 建议常量命名
|
|
950
|
-
* @private
|
|
951
|
-
*/
|
|
952
|
-
Detector.prototype._suggestConstName = function (name, config, errors) {
|
|
953
|
-
if (/^[a-z][a-zA-Z0-9]*$/.test(name)) {
|
|
954
|
-
const sug_name = name.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
955
|
-
errors.push(
|
|
956
|
-
`${config.name}(常量)必须使用大写命名风格,建议改为:${sug_name}`,
|
|
957
|
-
);
|
|
958
|
-
} else if (/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
959
|
-
const sug_name = name.toUpperCase();
|
|
960
|
-
errors.push(
|
|
961
|
-
`${config.name}(常量)必须使用大写命名风格,建议改为:${sug_name}`,
|
|
962
|
-
);
|
|
963
|
-
} else {
|
|
964
|
-
errors.push(
|
|
965
|
-
`${config.name}(常量)必须使用大写命名风格(如API或API_BASE_URL)`,
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* 检查值属性
|
|
972
|
-
* @private
|
|
973
|
-
*/
|
|
974
|
-
Detector.prototype._checkValueProp = function (name, config, errors) {
|
|
975
|
-
// 检查常量规则
|
|
976
|
-
const const_res = this._checkName("constant-name", name);
|
|
977
|
-
if (const_res.valid) {
|
|
978
|
-
return; // 满足常量规则,通过
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// 检查变量规则
|
|
982
|
-
const var_res = this._checkName("variable-name", name);
|
|
983
|
-
if (var_res.valid) {
|
|
984
|
-
return; // 满足变量规则,通过
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// 都不满足,报告错误
|
|
988
|
-
errors.push(`${config.name}(值)必须符合变量或常量命名规则`);
|
|
989
|
-
};
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* 建议值属性命名
|
|
993
|
-
* @private
|
|
994
|
-
*/
|
|
995
|
-
Detector.prototype._suggestValueName = function (name, config, errors) {
|
|
996
|
-
if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
997
|
-
const sug_name = name
|
|
998
|
-
.replace(/([A-Z])/g, "_$1")
|
|
999
|
-
.toLowerCase()
|
|
1000
|
-
.replace(/^_/, "");
|
|
1001
|
-
errors.push(
|
|
1002
|
-
`${config.name}(值)必须使用小写命名风格,建议改为:${sug_name}`,
|
|
1003
|
-
);
|
|
1004
|
-
} else if (/^[a-z][a-zA-Z0-9]*$/.test(name)) {
|
|
1005
|
-
const sug_name = name.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
1006
|
-
errors.push(
|
|
1007
|
-
`${config.name}(值)必须使用小写命名风格,建议改为:${sug_name}`,
|
|
1008
|
-
);
|
|
1009
|
-
} else {
|
|
1010
|
-
const allowed_styles = config.styles.join("、");
|
|
1011
|
-
errors.push(
|
|
1012
|
-
`${config.name}(值)必须符合以下命名风格之一:${allowed_styles}`,
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* 弱验证模式值检查
|
|
1019
|
-
* @private
|
|
1020
|
-
*/
|
|
1021
|
-
Detector.prototype._checkWeakValue = function (name, config, errors) {
|
|
1022
|
-
const is_valid_var = this._checkStyle(name, ["lowercase", "snake_case"]);
|
|
1023
|
-
const is_valid_const = this._checkStyle(name, [
|
|
1024
|
-
"UPPERCASE",
|
|
1025
|
-
"UPPER_SNAKE_CASE",
|
|
1026
|
-
]);
|
|
1027
|
-
|
|
1028
|
-
if (!is_valid_var && !is_valid_const) {
|
|
1029
|
-
errors.push(
|
|
1030
|
-
`${config.name}(值)必须符合变量名(小写蛇形)或常量名(大写蛇形)规则`,
|
|
1031
|
-
);
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
/**
|
|
1036
|
-
* 检查属性名是否优先使用单个单词
|
|
1037
|
-
* @private
|
|
1038
|
-
*/
|
|
1039
|
-
Detector.prototype._checkPropSingleWord = function (name, config, warnings) {
|
|
1040
|
-
if (!config.single_word) return;
|
|
1041
|
-
|
|
1042
|
-
const has_uppercase = /[A-Z]/.test(name.slice(1));
|
|
1043
|
-
const has_separators = name.includes("_") || name.includes("-");
|
|
1044
|
-
|
|
1045
|
-
if (has_uppercase || has_separators) {
|
|
1046
|
-
warnings.push(`${config.name}应优先使用单个单词`);
|
|
1047
|
-
}
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* 获取属性值类型
|
|
1052
|
-
* @param {Object} val_node - 属性值AST节点
|
|
1053
|
-
* @param {string} prop_name - 属性名
|
|
1054
|
-
* @param {Object} parent_node - 父节点AST节点(可选,用于上下文感知检测)
|
|
1055
|
-
* @returns {string} 属性值类型('function', 'method', 'class', 'value')
|
|
1056
|
-
* @private
|
|
1057
|
-
*/
|
|
1058
|
-
Detector.prototype._getPropType = function (val_node, prop_name, parent_node) {
|
|
1059
|
-
if (!val_node) {
|
|
1060
|
-
return "value"; // 默认值类型
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// 检查是否为对象方法(在对象字面量中的函数)
|
|
1064
|
-
if (parent_node && parent_node.type === "Property" &&
|
|
1065
|
-
(val_node.type === "FunctionExpression" || val_node.type === "ArrowFunctionExpression")) {
|
|
1066
|
-
return "method"; // 对象方法类型
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// 优先检查属性名是否符合类命名规范(PascalCase)
|
|
1070
|
-
// 当属性名符合PascalCase时,优先考虑类类型
|
|
1071
|
-
if (prop_name && /^[A-Z][a-zA-Z0-9]*$/.test(prop_name)) {
|
|
1072
|
-
const common_config_names = ['config', 'options', 'params', 'settings', 'props'];
|
|
1073
|
-
const is_common_config = common_config_names.includes(prop_name.toLowerCase());
|
|
1074
|
-
|
|
1075
|
-
// 如果属性名符合PascalCase且不是常见配置参数名,优先认为是类类型
|
|
1076
|
-
if (!is_common_config) {
|
|
1077
|
-
return "class";
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// 检查是否为函数
|
|
1082
|
-
if (
|
|
1083
|
-
val_node.type === "FunctionExpression" ||
|
|
1084
|
-
val_node.type === "ArrowFunctionExpression" ||
|
|
1085
|
-
(val_node.type === "Identifier" &&
|
|
1086
|
-
val_node.name &&
|
|
1087
|
-
(val_node.name.endsWith("Function") ||
|
|
1088
|
-
val_node.name.endsWith("Func") ||
|
|
1089
|
-
val_node.name.startsWith("on") ||
|
|
1090
|
-
val_node.name.startsWith("handle") ||
|
|
1091
|
-
// 新增:常见函数命名模式识别
|
|
1092
|
-
val_node.name.endsWith("Manager") ||
|
|
1093
|
-
val_node.name.endsWith("Admin") ||
|
|
1094
|
-
val_node.name.endsWith("Handler") ||
|
|
1095
|
-
val_node.name.endsWith("Service") ||
|
|
1096
|
-
val_node.name.endsWith("Util") ||
|
|
1097
|
-
val_node.name.endsWith("Helper") ||
|
|
1098
|
-
val_node.name.endsWith("Factory") ||
|
|
1099
|
-
val_node.name.endsWith("Provider") ||
|
|
1100
|
-
// 检查是否为小驼峰命名(函数常见命名风格)
|
|
1101
|
-
(/^[a-z][a-zA-Z0-9]*$/.test(val_node.name) &&
|
|
1102
|
-
val_node.name !== val_node.name.toLowerCase() &&
|
|
1103
|
-
!val_node.name.includes("_") &&
|
|
1104
|
-
!val_node.name.includes("-"))))
|
|
1105
|
-
) {
|
|
1106
|
-
return "function";
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// 检查是否为类
|
|
1110
|
-
if (val_node.type === "ClassExpression" || val_node.type === "NewExpression") {
|
|
1111
|
-
return "class";
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// 对于标识符类型的值,采用保守策略
|
|
1115
|
-
// 只有当明显是类名时才认为是类类型
|
|
1116
|
-
if (val_node.type === "Identifier" && val_node.name) {
|
|
1117
|
-
// 检查标识符名称是否符合类名模式(PascalCase)
|
|
1118
|
-
const is_pascal_case = /^[A-Z][a-zA-Z0-9]*$/.test(val_node.name);
|
|
1119
|
-
|
|
1120
|
-
// 如果标识符名称符合PascalCase,且不是常见的配置参数名,则认为是类类型
|
|
1121
|
-
const common_config_names = ['config', 'options', 'params', 'settings', 'props'];
|
|
1122
|
-
const is_common_config = common_config_names.includes(val_node.name.toLowerCase());
|
|
1123
|
-
|
|
1124
|
-
if (is_pascal_case && !is_common_config) {
|
|
1125
|
-
return "class";
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// 检查是否为常量/配置值(应该使用UPPERCASE风格)
|
|
1130
|
-
if (this._isConst(val_node, prop_name)) {
|
|
1131
|
-
return "constant";
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
// 默认为值类型
|
|
1135
|
-
return "value";
|
|
1136
|
-
};
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* 检查是否为常量值(应该使用UPPERCASE风格命名)
|
|
1140
|
-
* @param {Object} val_node - 属性值AST节点
|
|
1141
|
-
* @param {string} prop_name - 属性名(可选,用于更智能的判断)
|
|
1142
|
-
* @returns {boolean} 是否为常量值
|
|
1143
|
-
* @private
|
|
1144
|
-
*/
|
|
1145
|
-
Detector.prototype._isConst = function (val_node, prop_name) {
|
|
1146
|
-
if (!val_node) {
|
|
1147
|
-
return false;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
const is_prim_const = this._isPrimConst(val_node);
|
|
1151
|
-
const is_cfg_obj = this._isCfgObj(val_node);
|
|
1152
|
-
const is_const_id = this._isConstId(val_node);
|
|
1153
|
-
|
|
1154
|
-
if (prop_name) {
|
|
1155
|
-
return this._isConstWithName(
|
|
1156
|
-
val_node,
|
|
1157
|
-
prop_name,
|
|
1158
|
-
is_prim_const,
|
|
1159
|
-
is_cfg_obj,
|
|
1160
|
-
is_const_id,
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
return this._isConstWithoutName(is_prim_const, is_cfg_obj, is_const_id);
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
* 判断标识符是否为类名(基于上下文分析)
|
|
1169
|
-
* @param {string} name - 标识符名称
|
|
1170
|
-
* @param {Object} context_node - 上下文AST节点
|
|
1171
|
-
* @returns {boolean} 是否为类名
|
|
1172
|
-
* @private
|
|
1173
|
-
*/
|
|
1174
|
-
Detector.prototype._isClassName = function (name, context_node) {
|
|
1175
|
-
if (!name || typeof name !== 'string') {
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// 首先检查是否为大驼峰命名(类名的基本要求)
|
|
1180
|
-
if (!/^[A-Z][a-zA-Z]*$/.test(name)) {
|
|
1181
|
-
return false;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// 如果有上下文节点,进行更精确的分析
|
|
1185
|
-
if (context_node) {
|
|
1186
|
-
// 检查是否在类声明中
|
|
1187
|
-
if (context_node.type === 'ClassDeclaration' ||
|
|
1188
|
-
context_node.type === 'ClassExpression') {
|
|
1189
|
-
return true;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// 检查是否在 extends 或 implements 中
|
|
1193
|
-
if (context_node.type === 'Identifier' &&
|
|
1194
|
-
context_node.parent &&
|
|
1195
|
-
(context_node.parent.type === 'ClassDeclaration' ||
|
|
1196
|
-
context_node.parent.type === 'ClassExpression')) {
|
|
1197
|
-
return true;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// 检查是否在 new 表达式中
|
|
1201
|
-
if (context_node.type === 'NewExpression' &&
|
|
1202
|
-
context_node.callee &&
|
|
1203
|
-
context_node.callee.name === name) {
|
|
1204
|
-
return true;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
// 对于没有上下文的情况,采用保守策略
|
|
1209
|
-
// 只对明显是类名的标识符返回 true
|
|
1210
|
-
const class_suffixes = [
|
|
1211
|
-
'Manager', 'Controller', 'Service', 'Factory', 'Builder',
|
|
1212
|
-
'Provider', 'Middleware', 'Component', 'Repository', 'Handler',
|
|
1213
|
-
'Adapter', 'Decorator', 'Validator', 'Generator', 'Processor'
|
|
1214
|
-
];
|
|
1215
|
-
|
|
1216
|
-
return class_suffixes.some(suffix => name.endsWith(suffix));
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
/**
|
|
1220
|
-
* 检查是否为基本类型常量
|
|
1221
|
-
* @private
|
|
1222
|
-
*/
|
|
1223
|
-
Detector.prototype._isPrimConst = function (val_node) {
|
|
1224
|
-
return (
|
|
1225
|
-
val_node.type === "Literal" &&
|
|
1226
|
-
(typeof val_node.value === "string" ||
|
|
1227
|
-
typeof val_node.value === "number" ||
|
|
1228
|
-
typeof val_node.value === "boolean")
|
|
1229
|
-
);
|
|
1230
|
-
};
|
|
1231
|
-
|
|
1232
|
-
/**
|
|
1233
|
-
* 检查是否为配置对象
|
|
1234
|
-
* @private
|
|
1235
|
-
*/
|
|
1236
|
-
Detector.prototype._isCfgObj = function (val_node) {
|
|
1237
|
-
// 配置对象应该是包含大写标识符的对象,而不是普通对象字面量
|
|
1238
|
-
return (
|
|
1239
|
-
val_node.type === "ObjectExpression" &&
|
|
1240
|
-
val_node.properties &&
|
|
1241
|
-
val_node.properties.length > 0 &&
|
|
1242
|
-
val_node.properties.every(
|
|
1243
|
-
(prop) =>
|
|
1244
|
-
prop.value &&
|
|
1245
|
-
prop.value.type === "Identifier" &&
|
|
1246
|
-
/^[A-Z_][A-Z0-9_]*$/.test(prop.value.name)
|
|
1247
|
-
)
|
|
1248
|
-
);
|
|
1249
|
-
};
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* 检查是否为常量标识符
|
|
1253
|
-
* @private
|
|
1254
|
-
*/
|
|
1255
|
-
Detector.prototype._isConstId = function (val_node) {
|
|
1256
|
-
return (
|
|
1257
|
-
val_node.type === "Identifier" && /^[A-Z_][A-Z0-9_]*$/.test(val_node.name)
|
|
1258
|
-
);
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1261
|
-
/**
|
|
1262
|
-
* 有属性名时的常量判断
|
|
1263
|
-
* @private
|
|
1264
|
-
*/
|
|
1265
|
-
Detector.prototype._isConstWithName = function (
|
|
1266
|
-
val_node,
|
|
1267
|
-
prop_name,
|
|
1268
|
-
is_prim_const,
|
|
1269
|
-
is_cfg_obj,
|
|
1270
|
-
is_const_id,
|
|
1271
|
-
) {
|
|
1272
|
-
const is_upper_name = /^[A-Z_][A-Z0-9_]*$/.test(prop_name);
|
|
1273
|
-
const is_inst_data_name =
|
|
1274
|
-
/^[a-z][a-z0-9_]*$/.test(prop_name) ||
|
|
1275
|
-
/^[a-z][a-zA-Z0-9]*$/.test(prop_name);
|
|
1276
|
-
const is_val_const = this._isValConst(val_node, prop_name);
|
|
1277
|
-
|
|
1278
|
-
if (is_inst_data_name && is_prim_const) {
|
|
1279
|
-
return false; // 实例数据的属性名,即使值是基本类型,也不应视为常量
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
if (is_upper_name && (is_prim_const || is_cfg_obj)) {
|
|
1283
|
-
return true;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
return is_cfg_obj || is_const_id;
|
|
1287
|
-
};
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* 无属性名时的常量判断
|
|
1291
|
-
* @private
|
|
1292
|
-
*/
|
|
1293
|
-
Detector.prototype._isConstWithoutName = function (
|
|
1294
|
-
is_prim_const,
|
|
1295
|
-
is_cfg_obj,
|
|
1296
|
-
is_const_id,
|
|
1297
|
-
) {
|
|
1298
|
-
if (is_cfg_obj || is_const_id) {
|
|
1299
|
-
return true;
|
|
1300
|
-
}
|
|
1301
|
-
return is_prim_const;
|
|
1302
|
-
};
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* 通过值内容分析判断是否为常量
|
|
1306
|
-
* @param {Object} val_node - 属性值AST节点
|
|
1307
|
-
* @param {string} prop_name - 属性名
|
|
1308
|
-
* @returns {boolean} 值内容是否为常量特征
|
|
1309
|
-
* @private
|
|
1310
|
-
*/
|
|
1311
|
-
Detector.prototype._isValConst = function (val_node, prop_name) {
|
|
1312
|
-
if (!val_node || val_node.type !== "Literal") {
|
|
1313
|
-
return false;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
const value = val_node.value;
|
|
1317
|
-
|
|
1318
|
-
switch (typeof value) {
|
|
1319
|
-
case "string":
|
|
1320
|
-
return this._isStringConst(value, prop_name);
|
|
1321
|
-
case "number":
|
|
1322
|
-
return this._isNumberConst(value, prop_name);
|
|
1323
|
-
case "boolean":
|
|
1324
|
-
return this._isBooleanConst(value, prop_name);
|
|
1325
|
-
default:
|
|
1326
|
-
return this._isConstSem(prop_name, value);
|
|
1327
|
-
}
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
/**
|
|
1331
|
-
* 检查字符串值是否为常量
|
|
1332
|
-
* @private
|
|
1333
|
-
*/
|
|
1334
|
-
Detector.prototype._isStringConst = function (value, prop_name) {
|
|
1335
|
-
const is_url_const = this._isUrlConst(value, prop_name);
|
|
1336
|
-
if (is_url_const) return true;
|
|
1337
|
-
|
|
1338
|
-
const is_config_const = this._isConfigConst(value, prop_name);
|
|
1339
|
-
if (is_config_const) return true;
|
|
1340
|
-
|
|
1341
|
-
return false;
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
/**
|
|
1345
|
-
* 检查URL相关的字符串常量
|
|
1346
|
-
* @private
|
|
1347
|
-
*/
|
|
1348
|
-
Detector.prototype._isUrlConst = function (value, prop_name) {
|
|
1349
|
-
const is_url_related_name =
|
|
1350
|
-
/(url|uri|endpoint|api|base|host|domain|path)$/i.test(prop_name);
|
|
1351
|
-
const is_url_value =
|
|
1352
|
-
/^(https?:\/\/|\/\/|www\.|ftp:\/\/)/.test(value) ||
|
|
1353
|
-
/\.[a-z]{2,6}(\/|$)/.test(value);
|
|
1354
|
-
|
|
1355
|
-
return is_url_related_name && is_url_value;
|
|
1356
|
-
};
|
|
1357
|
-
|
|
1358
|
-
/**
|
|
1359
|
-
* 检查配置相关的字符串常量
|
|
1360
|
-
* @private
|
|
1361
|
-
*/
|
|
1362
|
-
Detector.prototype._isConfigConst = function (value, prop_name) {
|
|
1363
|
-
const is_config_related_name =
|
|
1364
|
-
/(config|setting|option|default|mode|env|version)$/i.test(prop_name);
|
|
1365
|
-
const is_config_value =
|
|
1366
|
-
/^(v?\d+\.\d+|true|false|null|undefined|production|development|test)$/i.test(
|
|
1367
|
-
value,
|
|
1368
|
-
);
|
|
1369
|
-
|
|
1370
|
-
return is_config_related_name && is_config_value;
|
|
1371
|
-
};
|
|
1372
|
-
|
|
1373
|
-
/**
|
|
1374
|
-
* 检查数字值是否为常量
|
|
1375
|
-
* @private
|
|
1376
|
-
*/
|
|
1377
|
-
Detector.prototype._isNumberConst = function (value, prop_name) {
|
|
1378
|
-
const is_config_num = this._isConfigNumber(value, prop_name);
|
|
1379
|
-
if (is_config_num) return true;
|
|
1380
|
-
|
|
1381
|
-
const is_common_const = this._isCommonNumber(value);
|
|
1382
|
-
if (is_common_const) return true;
|
|
1383
|
-
|
|
1384
|
-
return false;
|
|
1385
|
-
};
|
|
1386
|
-
|
|
1387
|
-
/**
|
|
1388
|
-
* 检查配置相关的数字常量
|
|
1389
|
-
* @private
|
|
1390
|
-
*/
|
|
1391
|
-
Detector.prototype._isConfigNumber = function (value, prop_name) {
|
|
1392
|
-
const is_config_number_name =
|
|
1393
|
-
/(timeout|delay|interval|retry|count|limit|max|min|size|length|port)$/i.test(
|
|
1394
|
-
prop_name,
|
|
1395
|
-
);
|
|
1396
|
-
return is_config_number_name;
|
|
1397
|
-
};
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* 检查常见数字常量
|
|
1401
|
-
* @private
|
|
1402
|
-
*/
|
|
1403
|
-
Detector.prototype._isCommonNumber = function (value) {
|
|
1404
|
-
const common_constants = [
|
|
1405
|
-
0, 1, 10, 100, 1000, 60, 3600, 86400, 1024, 2048, 4096, 8192,
|
|
1406
|
-
];
|
|
1407
|
-
return common_constants.includes(value);
|
|
1408
|
-
};
|
|
1409
|
-
|
|
1410
|
-
/**
|
|
1411
|
-
* 检查布尔值是否为常量
|
|
1412
|
-
* @private
|
|
1413
|
-
*/
|
|
1414
|
-
Detector.prototype._isBooleanConst = function (value, prop_name) {
|
|
1415
|
-
const is_config_bool = this._isConfigBoolean(prop_name);
|
|
1416
|
-
const is_inst_state = this._isInstanceState(prop_name);
|
|
1417
|
-
|
|
1418
|
-
if (is_config_bool && !is_inst_state) {
|
|
1419
|
-
return true;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
if (is_inst_state) {
|
|
1423
|
-
return false;
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
return false;
|
|
1427
|
-
};
|
|
1428
|
-
|
|
1429
|
-
/**
|
|
1430
|
-
* 检查配置相关的布尔值
|
|
1431
|
-
* @private
|
|
1432
|
-
*/
|
|
1433
|
-
Detector.prototype._isConfigBoolean = function (prop_name) {
|
|
1434
|
-
return /^(is_)?(enabled|disabled|debug|verbose|strict|lazy|secure|ssl|tls|compressed|cached)$/i.test(
|
|
1435
|
-
prop_name,
|
|
1436
|
-
);
|
|
1437
|
-
};
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* 检查实例数据状态的布尔值
|
|
1441
|
-
* @private
|
|
1442
|
-
*/
|
|
1443
|
-
Detector.prototype._isInstanceState = function (prop_name) {
|
|
1444
|
-
return /^(is_)?(active|inactive|visible|hidden|required|optional|valid|invalid|available|busy|online|offline|open|closed|locked|unlocked)$/i.test(
|
|
1445
|
-
prop_name,
|
|
1446
|
-
);
|
|
1447
|
-
};
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* 通过属性名语义分析判断是否为常量
|
|
1451
|
-
* @param {string} prop_name - 属性名
|
|
1452
|
-
* @param {any} value - 属性值
|
|
1453
|
-
* @returns {boolean} 根据语义分析是否为常量
|
|
1454
|
-
* @private
|
|
1455
|
-
*/
|
|
1456
|
-
Detector.prototype._isConstSem = function (prop_name, value) {
|
|
1457
|
-
// 精确匹配常量相关词汇(使用单词边界匹配)
|
|
1458
|
-
const const_keys = [
|
|
1459
|
-
"api",
|
|
1460
|
-
"base",
|
|
1461
|
-
"url",
|
|
1462
|
-
"uri",
|
|
1463
|
-
"endpoint",
|
|
1464
|
-
"host",
|
|
1465
|
-
"domain",
|
|
1466
|
-
"timeout",
|
|
1467
|
-
"delay",
|
|
1468
|
-
"interval",
|
|
1469
|
-
"retry",
|
|
1470
|
-
"count",
|
|
1471
|
-
"limit",
|
|
1472
|
-
"max",
|
|
1473
|
-
"min",
|
|
1474
|
-
"size",
|
|
1475
|
-
"length",
|
|
1476
|
-
"port",
|
|
1477
|
-
"version",
|
|
1478
|
-
"config",
|
|
1479
|
-
"setting",
|
|
1480
|
-
"option",
|
|
1481
|
-
"default",
|
|
1482
|
-
"mode",
|
|
1483
|
-
"env",
|
|
1484
|
-
"debug",
|
|
1485
|
-
"verbose",
|
|
1486
|
-
"strict",
|
|
1487
|
-
"lazy",
|
|
1488
|
-
"secure",
|
|
1489
|
-
"ssl",
|
|
1490
|
-
"tls",
|
|
1491
|
-
"compressed",
|
|
1492
|
-
"cached",
|
|
1493
|
-
];
|
|
1494
|
-
|
|
1495
|
-
// 使用单词边界匹配,避免误匹配(如"active"匹配"is_active")
|
|
1496
|
-
const has_const_key = const_keys.some((keyword) => {
|
|
1497
|
-
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
1498
|
-
return regex.test(prop_name);
|
|
1499
|
-
});
|
|
1500
|
-
|
|
1501
|
-
if (has_const_key) {
|
|
1502
|
-
// 如果属性名包含常量相关词汇,且值是基本类型,更可能是常量
|
|
1503
|
-
return (
|
|
1504
|
-
typeof value === "string" ||
|
|
1505
|
-
typeof value === "number" ||
|
|
1506
|
-
typeof value === "boolean"
|
|
1507
|
-
);
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
// 实例数据相关词汇(更精确的匹配)
|
|
1511
|
-
const inst_keys = [
|
|
1512
|
-
"user",
|
|
1513
|
-
"person",
|
|
1514
|
-
"customer",
|
|
1515
|
-
"member",
|
|
1516
|
-
"client",
|
|
1517
|
-
"product",
|
|
1518
|
-
"item",
|
|
1519
|
-
"goods",
|
|
1520
|
-
"article",
|
|
1521
|
-
"order",
|
|
1522
|
-
"cart",
|
|
1523
|
-
"payment",
|
|
1524
|
-
"invoice",
|
|
1525
|
-
"name",
|
|
1526
|
-
"age",
|
|
1527
|
-
"email",
|
|
1528
|
-
"phone",
|
|
1529
|
-
"address",
|
|
1530
|
-
"title",
|
|
1531
|
-
"description",
|
|
1532
|
-
"content",
|
|
1533
|
-
"message",
|
|
1534
|
-
"active",
|
|
1535
|
-
"inactive",
|
|
1536
|
-
"visible",
|
|
1537
|
-
"hidden",
|
|
1538
|
-
"required",
|
|
1539
|
-
"optional",
|
|
1540
|
-
"valid",
|
|
1541
|
-
"invalid",
|
|
1542
|
-
"available",
|
|
1543
|
-
"busy",
|
|
1544
|
-
"online",
|
|
1545
|
-
"offline",
|
|
1546
|
-
"open",
|
|
1547
|
-
"closed",
|
|
1548
|
-
"locked",
|
|
1549
|
-
"unlocked",
|
|
1550
|
-
];
|
|
1551
|
-
|
|
1552
|
-
// 使用单词边界匹配
|
|
1553
|
-
const has_inst_key = inst_keys.some((keyword) => {
|
|
1554
|
-
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
1555
|
-
return regex.test(prop_name);
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
if (has_inst_key) {
|
|
1559
|
-
// 如果属性名包含实例数据相关词汇,更可能是实例数据
|
|
1560
|
-
return false;
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
// 属性名模式分析
|
|
1564
|
-
const is_likely_const_pat = this._isConstPat(prop_name, value);
|
|
1565
|
-
|
|
1566
|
-
return is_likely_const_pat;
|
|
1567
|
-
};
|
|
1568
|
-
|
|
1569
|
-
/**
|
|
1570
|
-
* 通过属性名模式分析判断是否为常量
|
|
1571
|
-
* @param {string} prop_name - 属性名
|
|
1572
|
-
* @param {any} value - 属性值
|
|
1573
|
-
* @returns {boolean} 根据模式分析是否为常量
|
|
1574
|
-
* @private
|
|
1575
|
-
*/
|
|
1576
|
-
Detector.prototype._isConstPat = function (prop_name, value) {
|
|
1577
|
-
// UPPERCASE模式:全大写或大写蛇形
|
|
1578
|
-
const is_uppercase_pattern = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/.test(prop_name);
|
|
1579
|
-
|
|
1580
|
-
// 配置前缀模式:包含config、setting、default等前缀
|
|
1581
|
-
const has_config_prefix = /^(config|setting|option|default|env|mode)_/i.test(
|
|
1582
|
-
prop_name,
|
|
1583
|
-
);
|
|
1584
|
-
|
|
1585
|
-
// 常量后缀模式:包含url、timeout、count等后缀
|
|
1586
|
-
const has_constant_suffix =
|
|
1587
|
-
/_(url|uri|endpoint|api|base|host|domain|timeout|delay|interval|retry|count|limit|max|min|size|length|port|version)$/i.test(
|
|
1588
|
-
prop_name,
|
|
1589
|
-
);
|
|
1590
|
-
|
|
1591
|
-
// 实例数据模式:包含user、product、order等前缀或后缀
|
|
1592
|
-
const has_instance_pattern =
|
|
1593
|
-
/^(user|person|customer|member|client|product|item|goods|article|order|cart|payment|invoice)_/i.test(
|
|
1594
|
-
prop_name,
|
|
1595
|
-
) ||
|
|
1596
|
-
/_(user|person|customer|member|client|product|item|goods|article|order|cart|payment|invoice)$/i.test(
|
|
1597
|
-
prop_name,
|
|
1598
|
-
);
|
|
1599
|
-
|
|
1600
|
-
// 状态模式:以is_开头或包含状态相关词汇
|
|
1601
|
-
const is_status_pattern =
|
|
1602
|
-
/^is_/i.test(prop_name) ||
|
|
1603
|
-
/_(active|inactive|visible|hidden|required|optional|valid|invalid|available|busy|online|offline|open|closed|locked|unlocked)$/i.test(
|
|
1604
|
-
prop_name,
|
|
1605
|
-
);
|
|
1606
|
-
|
|
1607
|
-
// 判断逻辑
|
|
1608
|
-
if (is_uppercase_pattern || has_config_prefix || has_constant_suffix) {
|
|
1609
|
-
return true; // 常量模式
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
if (has_instance_pattern || is_status_pattern) {
|
|
1613
|
-
return false; // 实例数据模式
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
return false;
|
|
1617
|
-
};
|
|
1618
|
-
|
|
1619
|
-
/**
|
|
1620
|
-
* 检查是否包含禁止的废话词(拼接命名检测)
|
|
1621
|
-
* @param {string} name - 名称
|
|
1622
|
-
* @param {string} type - 代码实体类型(class/function/variable)
|
|
1623
|
-
* @returns {string|null} 检测到的废话词,未检测到返回null
|
|
1624
|
-
* @private
|
|
1625
|
-
*/
|
|
1626
|
-
Detector.prototype._checkBadWord = function (name, type) {
|
|
1627
|
-
if (!name || !type) {
|
|
1628
|
-
return null;
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
const forbidden_words = this.config.forbidden_words[type] || [];
|
|
1632
|
-
const nameLower = name.toLowerCase();
|
|
1633
|
-
|
|
1634
|
-
// 检测拼接命名:名称由多个单词组成,且包含禁止词
|
|
1635
|
-
// 例如:UserManager(包含manager)、DataHandler(包含handler)
|
|
1636
|
-
// 但允许单个单词:Manager、Handler等
|
|
1637
|
-
|
|
1638
|
-
// 如果名称是单个单词,不进行废话词检测
|
|
1639
|
-
if (!this._isCompoundName(name)) {
|
|
1640
|
-
return null;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
// 检查名称是否包含废话词作为独立的单词(而不是子字符串)
|
|
1644
|
-
for (const word of forbidden_words) {
|
|
1645
|
-
const wordLower = word.toLowerCase();
|
|
1646
|
-
|
|
1647
|
-
// 检查是否作为独立的单词存在
|
|
1648
|
-
// 1. 作为整个名称(单个单词)
|
|
1649
|
-
if (nameLower === wordLower) {
|
|
1650
|
-
continue; // 单个单词允许使用
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// 2. 作为复合名称的一部分(以下划线分隔)
|
|
1654
|
-
if (nameLower.includes('_')) {
|
|
1655
|
-
const words = nameLower.split('_');
|
|
1656
|
-
if (words.includes(wordLower)) {
|
|
1657
|
-
return word;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
// 3. 作为驼峰命名的一部分(包括小驼峰和大驼峰)
|
|
1662
|
-
if (/^[a-z]+([A-Z][a-z]+)+$/.test(name) || /^[A-Z][a-z]+([A-Z][a-z]+)+$/.test(name)) {
|
|
1663
|
-
const camelWords = name.replace(/([A-Z])/g, '_$1').toLowerCase().split('_');
|
|
1664
|
-
if (camelWords.includes(wordLower)) {
|
|
1665
|
-
// 返回原词,保持大小写格式
|
|
1666
|
-
return word;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
return null;
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
/**
|
|
1675
|
-
* 检查是否为拼接命名(由多个单词组成)
|
|
1676
|
-
* @param {string} name - 名称
|
|
1677
|
-
* @returns {boolean} 是否为拼接命名
|
|
1678
|
-
* @private
|
|
1679
|
-
*/
|
|
1680
|
-
Detector.prototype._isCompoundName = function (name) {
|
|
1681
|
-
if (!name) {
|
|
1682
|
-
return false;
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
// 检测大驼峰命名(PascalCase):每个单词首字母大写
|
|
1686
|
-
if (/^[A-Z][a-z]+([A-Z][a-z]+)+$/.test(name)) {
|
|
1687
|
-
return true;
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
// 检测小驼峰命名(camelCase):第一个单词小写,后续单词首字母大写
|
|
1691
|
-
if (/^[a-z]+([A-Z][a-z]+)+$/.test(name)) {
|
|
1692
|
-
return true;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// 检测蛇形命名(snake_case):单词间以下划线分隔
|
|
1696
|
-
if (/^[a-z]+(_[a-z]+)+$/.test(name)) {
|
|
1697
|
-
return true;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
// 检测横杠命名(kebab-case):单词间以短横线分隔
|
|
1701
|
-
if (/^[a-z]+(-[a-z]+)+$/.test(name)) {
|
|
1702
|
-
return true;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
return false;
|
|
1706
|
-
};
|
|
1707
|
-
|
|
1708
|
-
/**
|
|
1709
|
-
* 检查是否为忽略词
|
|
1710
|
-
* @param {string} name - 名称
|
|
1711
|
-
* @param {string} rule_type - 规则类型
|
|
1712
|
-
* @returns {boolean} 是否为忽略词
|
|
1713
|
-
* @private
|
|
1714
|
-
*/
|
|
1715
|
-
Detector.prototype._isIgnoreWord = function (name, rule_type) {
|
|
1716
|
-
// 获取忽略词配置
|
|
1717
|
-
const ignore_words_config = this.config.ignore_words;
|
|
1718
|
-
if (!ignore_words_config) {
|
|
1719
|
-
return false;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// 根据规则类型获取对应的忽略词列表
|
|
1723
|
-
let ignore_list = [];
|
|
1724
|
-
if (rule_type === "class-name") {
|
|
1725
|
-
ignore_list = ignore_words_config.class || [];
|
|
1726
|
-
} else if (rule_type === "method-name") {
|
|
1727
|
-
ignore_list = ignore_words_config.method || [];
|
|
1728
|
-
} else if (rule_type === "function-name") {
|
|
1729
|
-
ignore_list = ignore_words_config.function || [];
|
|
1730
|
-
} else if (rule_type === "variable-name") {
|
|
1731
|
-
ignore_list = ignore_words_config.variable || [];
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
// 检查名称是否完全匹配忽略词列表中的任何一个词
|
|
1735
|
-
// 支持精确匹配和包含匹配(如果名称包含忽略词,则整个名称都被忽略)
|
|
1736
|
-
// 支持大小写不敏感匹配
|
|
1737
|
-
const name_lower = name.toLowerCase();
|
|
1738
|
-
for (const ignore_word of ignore_list) {
|
|
1739
|
-
const ignore_word_lower = ignore_word.toLowerCase();
|
|
1740
|
-
if (name_lower === ignore_word_lower || name_lower.includes(ignore_word_lower)) {
|
|
1741
|
-
return true;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
return false;
|
|
1746
|
-
};
|
|
1747
|
-
|
|
1748
|
-
/**
|
|
1749
|
-
* 检查推荐词并生成推荐建议
|
|
1750
|
-
* @param {string} name - 名称
|
|
1751
|
-
* @param {string} rule_type - 规则类型
|
|
1752
|
-
* @param {Object} config - 配置
|
|
1753
|
-
* @param {Array} recommendations - 推荐建议数组
|
|
1754
|
-
* @private
|
|
1755
|
-
*/
|
|
1756
|
-
Detector.prototype._checkRecommen = function (name, rule_type, config, recommendations) {
|
|
1757
|
-
// 只对函数和方法名进行推荐词检测
|
|
1758
|
-
if (rule_type !== "function-name" && rule_type !== "method-name") {
|
|
1759
|
-
return;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
const recommen_words = this.config.recommen_words.function;
|
|
1763
|
-
|
|
1764
|
-
// 拆分命名中的单词
|
|
1765
|
-
const words = this._splitWords(name, rule_type);
|
|
1766
|
-
|
|
1767
|
-
// 检查每个推荐词类别
|
|
1768
|
-
Object.keys(recommen_words).forEach((category) => {
|
|
1769
|
-
const category_config = recommen_words[category];
|
|
1770
|
-
const category_words = category_config.words || [];
|
|
1771
|
-
|
|
1772
|
-
// 检查拆分后的单词是否包含推荐词类别对应的推荐词
|
|
1773
|
-
let has_target_word = false;
|
|
1774
|
-
words.forEach((word) => {
|
|
1775
|
-
if (category_words.includes(word)) {
|
|
1776
|
-
// 如果单词是目标词(如remove、delete),则生成建议
|
|
1777
|
-
has_target_word = true;
|
|
1778
|
-
}
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
// 如果包含目标词,则生成建议
|
|
1782
|
-
if (has_target_word) {
|
|
1783
|
-
// 检查是否需要生成推荐
|
|
1784
|
-
let should_recommend = false;
|
|
1785
|
-
|
|
1786
|
-
if (this.config.recommen) {
|
|
1787
|
-
// recommen为true:所有符合条件的命名都应该推荐
|
|
1788
|
-
should_recommend = true;
|
|
1789
|
-
} else {
|
|
1790
|
-
// recommen为false:只有当命名中出现单个词汇长度超过指定字符或命名总长度超过限制时才推荐
|
|
1791
|
-
const has_long_word = words.some(word => word.length > (config.single_word_len || 8));
|
|
1792
|
-
const has_long_name = name.length > config.max;
|
|
1793
|
-
|
|
1794
|
-
should_recommend = has_long_word || has_long_name;
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
if (should_recommend) {
|
|
1798
|
-
// 生成推荐建议:将目标词替换为推荐类别词
|
|
1799
|
-
const suggestions = [];
|
|
1800
|
-
|
|
1801
|
-
// 为每个检测到的目标词生成一个推荐
|
|
1802
|
-
words.forEach((word) => {
|
|
1803
|
-
if (category_words.includes(word)) {
|
|
1804
|
-
// 只生成使用类别词替换的推荐,不包含原始词
|
|
1805
|
-
const regex = new RegExp(word, 'gi');
|
|
1806
|
-
const suggestion = name.replace(regex, (match) => {
|
|
1807
|
-
// 保持原始大小写格式
|
|
1808
|
-
if (match === match.toUpperCase()) {
|
|
1809
|
-
return category.toUpperCase();
|
|
1810
|
-
} else if (match[0] === match[0].toUpperCase()) {
|
|
1811
|
-
return category[0].toUpperCase() + category.slice(1);
|
|
1812
|
-
} else {
|
|
1813
|
-
return category;
|
|
1814
|
-
}
|
|
1815
|
-
});
|
|
1816
|
-
|
|
1817
|
-
// 只有当推荐结果与原始名称不同时才添加
|
|
1818
|
-
if (suggestion !== name) {
|
|
1819
|
-
suggestions.push(suggestion);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
});
|
|
1823
|
-
|
|
1824
|
-
if (suggestions.length > 0) {
|
|
1825
|
-
recommendations.push({
|
|
1826
|
-
original: name,
|
|
1827
|
-
category: category,
|
|
1828
|
-
suggestions: suggestions,
|
|
1829
|
-
message: `检测到"${category_words.join('、')}",推荐使用: ${suggestions.join(', ')}`
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1835
|
-
};
|
|
1836
|
-
|
|
1837
|
-
/**
|
|
1838
|
-
* 检测名称是否符合规范
|
|
1839
|
-
* @param {string} name - 名称
|
|
1840
|
-
* @param {string} type - 类型(class/function/variable/constant/private)
|
|
1841
|
-
* @returns {Object} 检测结果
|
|
1842
|
-
*/
|
|
1843
|
-
Detector.prototype.checkName = function (name, type) {
|
|
1844
|
-
if (typeof name !== "string" || !name.trim()) {
|
|
1845
|
-
return {
|
|
1846
|
-
valid: false,
|
|
1847
|
-
errors: ["名称必须是有效的字符串"],
|
|
1848
|
-
};
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
if (typeof type !== "string" || !type.trim()) {
|
|
1852
|
-
return {
|
|
1853
|
-
valid: false,
|
|
1854
|
-
errors: ["类型必须是有效的字符串"],
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
const rule_method = this._rules[type];
|
|
1859
|
-
if (!rule_method) {
|
|
1860
|
-
return {
|
|
1861
|
-
valid: false,
|
|
1862
|
-
errors: [`不支持的类型: ${type}`],
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
try {
|
|
1867
|
-
return rule_method(name);
|
|
1868
|
-
} catch (error) {
|
|
1869
|
-
return {
|
|
1870
|
-
valid: false,
|
|
1871
|
-
errors: [`检测名称时出错: ${error.message}`],
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
};
|
|
1875
|
-
|
|
1876
|
-
/**
|
|
1877
|
-
* 批量检测名称
|
|
1878
|
-
* @param {Array} names - 名称数组
|
|
1879
|
-
* @param {string} type - 类型
|
|
1880
|
-
* @returns {Array} 检测结果数组
|
|
1881
|
-
*/
|
|
1882
|
-
Detector.prototype.checkNames = function (names, type) {
|
|
1883
|
-
if (!Array.isArray(names)) {
|
|
1884
|
-
throw new TypeError("名称必须是数组");
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
return names.map((name) => this.check_name(name, type));
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
|
-
/**
|
|
1891
|
-
* ESLint规则实现
|
|
1892
|
-
*/
|
|
1893
|
-
|
|
1894
1
|
/**
|
|
1895
|
-
*
|
|
2
|
+
* ESLint命名规范检测插件
|
|
3
|
+
* 版本: 1.1.0 - 支持跳过null值和未赋值属性检测
|
|
1896
4
|
*/
|
|
1897
|
-
const class_name_rule = {
|
|
1898
|
-
meta: {
|
|
1899
|
-
type: "suggestion",
|
|
1900
|
-
docs: {
|
|
1901
|
-
description: "类名必须使用大驼峰命名法(PascalCase)且优先使用单个单词",
|
|
1902
|
-
category: "Stylistic Issues",
|
|
1903
|
-
recommen: true,
|
|
1904
|
-
},
|
|
1905
|
-
schema: [
|
|
1906
|
-
{
|
|
1907
|
-
type: "object",
|
|
1908
|
-
properties: {
|
|
1909
|
-
regex: { type: "string" },
|
|
1910
|
-
min: { type: "number" },
|
|
1911
|
-
max: { type: "number" },
|
|
1912
|
-
message: { type: "string" },
|
|
1913
|
-
single_word: { type: "boolean" },
|
|
1914
|
-
single_word_len: { type: "number" },
|
|
1915
|
-
styles: { type: "array", items: { type: "string" } },
|
|
1916
|
-
ignore_words: {
|
|
1917
|
-
type: "object",
|
|
1918
|
-
properties: {
|
|
1919
|
-
class: { type: "array", items: { type: "string" } },
|
|
1920
|
-
method: { type: "array", items: { type: "string" } },
|
|
1921
|
-
function: { type: "array", items: { type: "string" } }
|
|
1922
|
-
},
|
|
1923
|
-
additionalProperties: false
|
|
1924
|
-
}
|
|
1925
|
-
},
|
|
1926
|
-
additionalProperties: false,
|
|
1927
|
-
},
|
|
1928
|
-
],
|
|
1929
|
-
},
|
|
1930
|
-
create(context) {
|
|
1931
|
-
const options = context.options[0] || {};
|
|
1932
|
-
return {
|
|
1933
|
-
ClassDeclaration(node) {
|
|
1934
|
-
const class_name = node.id.name;
|
|
1935
|
-
const detector = new Detector({ "class-name": options });
|
|
1936
|
-
const result = detector.checkName(class_name, "class-name");
|
|
1937
5
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
context.report({
|
|
1941
|
-
node: node.id,
|
|
1942
|
-
message: `类名"${class_name}"不符合规范: ${error}`,
|
|
1943
|
-
});
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
},
|
|
1947
|
-
};
|
|
1948
|
-
},
|
|
1949
|
-
};
|
|
6
|
+
const { Config } = require('./config');
|
|
7
|
+
const { Detector } = require('./detector');
|
|
1950
8
|
|
|
1951
9
|
/**
|
|
1952
|
-
*
|
|
10
|
+
* 创建ESLint规则配置
|
|
11
|
+
* @returns {object} ESLint规则配置对象
|
|
1953
12
|
*/
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
docs: {
|
|
1958
|
-
description: "函数名必须使用小驼峰命名法(camelCase)且优先使用单个单词",
|
|
1959
|
-
category: "Stylistic Issues",
|
|
1960
|
-
recommen: true,
|
|
1961
|
-
},
|
|
1962
|
-
schema: [
|
|
1963
|
-
{
|
|
1964
|
-
type: "object",
|
|
1965
|
-
properties: {
|
|
1966
|
-
regex: { type: "string" },
|
|
1967
|
-
min: { type: "number" },
|
|
1968
|
-
max: { type: "number" },
|
|
1969
|
-
message: { type: "string" },
|
|
1970
|
-
single_word: { type: "boolean" },
|
|
1971
|
-
single_word_len: { type: "number" },
|
|
1972
|
-
styles: { type: "array", items: { type: "string" } },
|
|
1973
|
-
ignore_words: {
|
|
1974
|
-
type: "object",
|
|
1975
|
-
properties: {
|
|
1976
|
-
class: { type: "array", items: { type: "string" } },
|
|
1977
|
-
method: { type: "array", items: { type: "string" } },
|
|
1978
|
-
function: { type: "array", items: { type: "string" } }
|
|
1979
|
-
},
|
|
1980
|
-
additionalProperties: false
|
|
1981
|
-
}
|
|
1982
|
-
},
|
|
1983
|
-
additionalProperties: false,
|
|
1984
|
-
},
|
|
1985
|
-
],
|
|
1986
|
-
},
|
|
1987
|
-
create(context) {
|
|
1988
|
-
const options = context.options[0] || {};
|
|
1989
|
-
return {
|
|
1990
|
-
FunctionDeclaration(node) {
|
|
1991
|
-
const function_name = node.id ? node.id.name : "anonymous";
|
|
1992
|
-
if (function_name !== "anonymous") {
|
|
1993
|
-
const detector = new Detector({ "function-name": options });
|
|
1994
|
-
const result = detector.checkName(function_name, "function-name");
|
|
13
|
+
function createNamingRules() {
|
|
14
|
+
var config = new Config({});
|
|
15
|
+
var detector = new Detector(config);
|
|
1995
16
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
17
|
+
return {
|
|
18
|
+
'class-name': {
|
|
19
|
+
meta: {
|
|
20
|
+
type: 'suggestion',
|
|
21
|
+
docs: {
|
|
22
|
+
description: '检测类名是否符合命名规范',
|
|
23
|
+
category: 'Stylistic Issues',
|
|
24
|
+
recommended: true,
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
2005
27
|
},
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
if (!result.valid) {
|
|
2014
|
-
result.errors.forEach((error) => {
|
|
28
|
+
create: function (context) {
|
|
29
|
+
return {
|
|
30
|
+
ClassDeclaration: function (node) {
|
|
31
|
+
var class_name = node.id.name;
|
|
32
|
+
var err = detector._checkClassName(class_name, node);
|
|
33
|
+
if (err) {
|
|
2015
34
|
context.report({
|
|
2016
|
-
node: node
|
|
2017
|
-
message:
|
|
35
|
+
node: err.node,
|
|
36
|
+
message: err.message,
|
|
2018
37
|
});
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
2022
41
|
},
|
|
2023
|
-
};
|
|
2024
|
-
},
|
|
2025
|
-
};
|
|
2026
|
-
|
|
2027
|
-
/**
|
|
2028
|
-
* 方法名小驼峰规则
|
|
2029
|
-
*/
|
|
2030
|
-
const method_name_rule = {
|
|
2031
|
-
meta: {
|
|
2032
|
-
type: "suggestion",
|
|
2033
|
-
docs: {
|
|
2034
|
-
description: "方法名必须使用小驼峰命名法(camelCase)且优先使用单个单词",
|
|
2035
|
-
category: "Stylistic Issues",
|
|
2036
|
-
recommen: true,
|
|
2037
42
|
},
|
|
2038
|
-
schema: [
|
|
2039
|
-
{
|
|
2040
|
-
type: "object",
|
|
2041
|
-
properties: {
|
|
2042
|
-
regex: { type: "string" },
|
|
2043
|
-
min: { type: "number" },
|
|
2044
|
-
max: { type: "number" },
|
|
2045
|
-
message: { type: "string" },
|
|
2046
|
-
single_word: { type: "boolean" },
|
|
2047
|
-
single_word_len: { type: "number" },
|
|
2048
|
-
styles: { type: "array", items: { type: "string" } },
|
|
2049
|
-
ignore_words: {
|
|
2050
|
-
type: "object",
|
|
2051
|
-
properties: {
|
|
2052
|
-
class: { type: "array", items: { type: "string" } },
|
|
2053
|
-
method: { type: "array", items: { type: "string" } },
|
|
2054
|
-
function: { type: "array", items: { type: "string" } }
|
|
2055
|
-
},
|
|
2056
|
-
additionalProperties: false
|
|
2057
|
-
}
|
|
2058
|
-
},
|
|
2059
|
-
additionalProperties: false,
|
|
2060
|
-
},
|
|
2061
|
-
],
|
|
2062
|
-
},
|
|
2063
|
-
create(context) {
|
|
2064
|
-
const options = context.options[0] || {};
|
|
2065
|
-
|
|
2066
|
-
function checkMethodName(node, method_name) {
|
|
2067
|
-
// 跳过私有方法的检测(私有方法由private-naming规则处理)
|
|
2068
|
-
if (method_name.startsWith("_")) {
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// 跳过JavaScript内置特殊方法名
|
|
2073
|
-
const builtin_methods = [
|
|
2074
|
-
"constructor",
|
|
2075
|
-
"toString",
|
|
2076
|
-
"valueOf",
|
|
2077
|
-
"toJSON",
|
|
2078
|
-
];
|
|
2079
|
-
if (builtin_methods.includes(method_name)) {
|
|
2080
|
-
return;
|
|
2081
|
-
}
|
|
2082
43
|
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
message: `方法名"${method_name}"不符合规范: ${error}`,
|
|
2091
|
-
});
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
return {
|
|
2097
|
-
// 处理类中的方法
|
|
2098
|
-
MethodDefinition(node) {
|
|
2099
|
-
checkMethodName(node, node.key.name);
|
|
2100
|
-
},
|
|
2101
|
-
|
|
2102
|
-
// 处理对象字面量中的方法
|
|
2103
|
-
Property(node) {
|
|
2104
|
-
// 检查是否是方法(函数表达式或箭头函数或方法简写)
|
|
2105
|
-
if (node.key.type === "Identifier" && !node.key.name.startsWith("_")) {
|
|
2106
|
-
const is_method = node.value &&
|
|
2107
|
-
(node.value.type === "FunctionExpression" ||
|
|
2108
|
-
node.value.type === "ArrowFunctionExpression" ||
|
|
2109
|
-
(node.method === true)); // 方法简写语法
|
|
2110
|
-
|
|
2111
|
-
if (is_method) {
|
|
2112
|
-
checkMethodName(node, node.key.name);
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
},
|
|
2116
|
-
};
|
|
2117
|
-
},
|
|
2118
|
-
};
|
|
2119
|
-
|
|
2120
|
-
/**
|
|
2121
|
-
* 变量名小写蛇形规则
|
|
2122
|
-
*/
|
|
2123
|
-
const variable_name_rule = {
|
|
2124
|
-
meta: {
|
|
2125
|
-
type: "suggestion",
|
|
2126
|
-
docs: {
|
|
2127
|
-
description: "变量名必须使用小写蛇形命名法(snake_case)",
|
|
2128
|
-
category: "Stylistic Issues",
|
|
2129
|
-
recommen: true,
|
|
2130
|
-
},
|
|
2131
|
-
schema: [
|
|
2132
|
-
{
|
|
2133
|
-
type: "object",
|
|
2134
|
-
properties: {
|
|
2135
|
-
regex: { type: "string" },
|
|
2136
|
-
min: { type: "number" },
|
|
2137
|
-
max: { type: "number" },
|
|
2138
|
-
message: { type: "string" },
|
|
2139
|
-
single_word: { type: "boolean" },
|
|
2140
|
-
single_word_len: { type: "number" },
|
|
2141
|
-
styles: { type: "array", items: { type: "string" } },
|
|
2142
|
-
ignore_words: {
|
|
2143
|
-
type: "object",
|
|
2144
|
-
properties: {
|
|
2145
|
-
variable: { type: "array", items: { type: "string" } }
|
|
2146
|
-
},
|
|
2147
|
-
additionalProperties: false
|
|
2148
|
-
}
|
|
44
|
+
'function-name': {
|
|
45
|
+
meta: {
|
|
46
|
+
type: 'suggestion',
|
|
47
|
+
docs: {
|
|
48
|
+
description: '检测函数名是否符合命名规范',
|
|
49
|
+
category: 'Stylistic Issues',
|
|
50
|
+
recommended: true,
|
|
2149
51
|
},
|
|
2150
|
-
|
|
52
|
+
schema: [],
|
|
2151
53
|
},
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
// 1. 在模块顶层作用域(不在函数或块内)
|
|
2160
|
-
// 2. 名称符合常量命名模式(全大写或大写蛇形)
|
|
2161
|
-
// 3. 不是模块导入(避免将require导入识别为常量)
|
|
2162
|
-
|
|
2163
|
-
// 检查是否在模块顶层
|
|
2164
|
-
let scope_node = node;
|
|
2165
|
-
while (scope_node.parent) {
|
|
2166
|
-
scope_node = scope_node.parent;
|
|
2167
|
-
if (
|
|
2168
|
-
scope_node.type === "FunctionDeclaration" ||
|
|
2169
|
-
scope_node.type === "FunctionExpression" ||
|
|
2170
|
-
scope_node.type === "ArrowFunctionExpression" ||
|
|
2171
|
-
scope_node.type === "BlockStatement"
|
|
2172
|
-
) {
|
|
2173
|
-
return false; // 在函数或块内,不是真正的常量
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
// 检查是否是模块导入(避免将require导入识别为常量)
|
|
2178
|
-
if (node.init &&
|
|
2179
|
-
(node.init.type === "CallExpression" &&
|
|
2180
|
-
node.init.callee &&
|
|
2181
|
-
node.init.callee.name === "require")) {
|
|
2182
|
-
return false; // 这是模块导入,不是真正的常量
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
// 检查名称是否符合常量命名模式
|
|
2186
|
-
const name = node.id.name;
|
|
2187
|
-
|
|
2188
|
-
// 常量命名模式:全大写或大写蛇形
|
|
2189
|
-
const is_uppercase = /^[A-Z][A-Z0-9]*$/.test(name); // 全大写:PORT, DEBUG
|
|
2190
|
-
const is_upper_snake = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/.test(name); // 大写蛇形:API_BASE_URL
|
|
2191
|
-
|
|
2192
|
-
// 只有符合常量命名模式的才被认为是真正的常量
|
|
2193
|
-
return is_uppercase || is_upper_snake;
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
return {
|
|
2197
|
-
VariableDeclarator(node) {
|
|
2198
|
-
if (node.id.type === "Identifier") {
|
|
2199
|
-
// 对于const声明,只对不是真正常量的应用变量命名规则
|
|
2200
|
-
if (node.parent.kind === "const" && isRealConstant(node)) {
|
|
2201
|
-
return; // 这是真正的常量,由常量规则处理
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
// 检查是否是函数声明(由函数名规则处理,这里跳过)
|
|
2205
|
-
if (node.init &&
|
|
2206
|
-
(node.init.type === "FunctionExpression" ||
|
|
2207
|
-
node.init.type === "ArrowFunctionExpression")) {
|
|
2208
|
-
// 这是函数声明,由函数名规则处理,这里跳过避免重复检测
|
|
2209
|
-
return;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
// 检查是否是类实例
|
|
2213
|
-
if (node.init &&
|
|
2214
|
-
node.init.type === "NewExpression" &&
|
|
2215
|
-
node.init.callee &&
|
|
2216
|
-
node.init.callee.type === "Identifier" &&
|
|
2217
|
-
/^[A-Z][a-zA-Z]*$/.test(node.init.callee.name)) {
|
|
2218
|
-
// 这是类实例,应该使用函数名规则(camelCase)
|
|
2219
|
-
const class_instance_name = node.id.name;
|
|
2220
|
-
const detector = new Detector({ "function-name": options });
|
|
2221
|
-
const result = detector.checkName(class_instance_name, "function-name");
|
|
2222
|
-
|
|
2223
|
-
if (!result.valid) {
|
|
2224
|
-
result.errors.forEach((error) => {
|
|
54
|
+
create: function (context) {
|
|
55
|
+
return {
|
|
56
|
+
FunctionDeclaration: function (node) {
|
|
57
|
+
if (node.id) {
|
|
58
|
+
var function_name = node.id.name;
|
|
59
|
+
var err = detector._checkFunctionName(function_name, node);
|
|
60
|
+
if (err) {
|
|
2225
61
|
context.report({
|
|
2226
|
-
node: node
|
|
2227
|
-
message:
|
|
2228
|
-
});
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
// 检查是否是模块导入
|
|
2235
|
-
if (node.init &&
|
|
2236
|
-
node.init.type === "CallExpression" &&
|
|
2237
|
-
node.init.callee &&
|
|
2238
|
-
node.init.callee.name === "require") {
|
|
2239
|
-
// 这是模块导入,应用模块导入命名规则
|
|
2240
|
-
const module_name = node.id.name;
|
|
2241
|
-
|
|
2242
|
-
// 智能判断模块导入的命名风格
|
|
2243
|
-
if (/^[A-Z][a-zA-Z]*$/.test(module_name)) {
|
|
2244
|
-
// 类/构造函数导入:Koa, Express - 应该使用PascalCase
|
|
2245
|
-
const detector = new Detector({ "class-name": options });
|
|
2246
|
-
const result = detector.checkName(module_name, "class-name");
|
|
2247
|
-
|
|
2248
|
-
if (!result.valid) {
|
|
2249
|
-
result.errors.forEach((error) => {
|
|
2250
|
-
context.report({
|
|
2251
|
-
node: node.id,
|
|
2252
|
-
message: `模块导入名"${module_name}"不符合规范: ${error}`,
|
|
2253
|
-
});
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2256
|
-
} else if (/^[a-z][a-zA-Z]*$/.test(module_name)) {
|
|
2257
|
-
// 默认导出导入:express, config - 应该使用camelCase
|
|
2258
|
-
const detector = new Detector({ "function-name": options });
|
|
2259
|
-
const result = detector.checkName(module_name, "function-name");
|
|
2260
|
-
|
|
2261
|
-
if (!result.valid) {
|
|
2262
|
-
result.errors.forEach((error) => {
|
|
2263
|
-
context.report({
|
|
2264
|
-
node: node.id,
|
|
2265
|
-
message: `模块导入名"${module_name}"不符合规范: ${error}`,
|
|
2266
|
-
});
|
|
62
|
+
node: err.node,
|
|
63
|
+
message: err.message,
|
|
2267
64
|
});
|
|
2268
65
|
}
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
FunctionExpression: function (node) {
|
|
69
|
+
if (node.id) {
|
|
70
|
+
var function_name = node.id.name;
|
|
71
|
+
var err = detector._checkFunctionName(function_name, node);
|
|
72
|
+
if (err) {
|
|
73
|
+
context.report({
|
|
74
|
+
node: err.node,
|
|
75
|
+
message: err.message,
|
|
2280
76
|
});
|
|
2281
77
|
}
|
|
2282
78
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
// 真正的变量声明
|
|
2287
|
-
const variable_name = node.id.name;
|
|
2288
|
-
const detector = new Detector({ "variable-name": options });
|
|
2289
|
-
const result = detector.checkName(variable_name, "variable-name");
|
|
2290
|
-
|
|
2291
|
-
if (!result.valid) {
|
|
2292
|
-
result.errors.forEach((error) => {
|
|
2293
|
-
context.report({
|
|
2294
|
-
node: node.id,
|
|
2295
|
-
message: `变量名"${variable_name}"不符合规范: ${error}`,
|
|
2296
|
-
});
|
|
2297
|
-
});
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
2300
81
|
},
|
|
2301
|
-
};
|
|
2302
|
-
},
|
|
2303
|
-
};
|
|
2304
|
-
|
|
2305
|
-
/**
|
|
2306
|
-
* 常量名大写蛇形规则
|
|
2307
|
-
*/
|
|
2308
|
-
const constant_name_rule = {
|
|
2309
|
-
meta: {
|
|
2310
|
-
type: "suggestion",
|
|
2311
|
-
docs: {
|
|
2312
|
-
description: "常量名必须使用大写蛇形命名法(UPPER_SNAKE_CASE)",
|
|
2313
|
-
category: "Stylistic Issues",
|
|
2314
|
-
recommen: true,
|
|
2315
82
|
},
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
single_word: { type: "boolean" },
|
|
2325
|
-
single_word_len: { type: "number" },
|
|
2326
|
-
styles: { type: "array", items: { type: "string" } },
|
|
83
|
+
|
|
84
|
+
'method-name': {
|
|
85
|
+
meta: {
|
|
86
|
+
type: 'suggestion',
|
|
87
|
+
docs: {
|
|
88
|
+
description: '检测方法名是否符合命名规范',
|
|
89
|
+
category: 'Stylistic Issues',
|
|
90
|
+
recommended: true,
|
|
2327
91
|
},
|
|
2328
|
-
|
|
92
|
+
schema: [],
|
|
2329
93
|
},
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
// 1. 在模块顶层作用域(不在函数或块内)
|
|
2338
|
-
// 2. 名称符合常量命名模式(全大写或大写蛇形)
|
|
2339
|
-
// 3. 不是模块导入(避免将require导入识别为常量)
|
|
2340
|
-
|
|
2341
|
-
// 检查是否在模块顶层
|
|
2342
|
-
let scope_node = node;
|
|
2343
|
-
while (scope_node.parent) {
|
|
2344
|
-
scope_node = scope_node.parent;
|
|
2345
|
-
if (
|
|
2346
|
-
scope_node.type === "FunctionDeclaration" ||
|
|
2347
|
-
scope_node.type === "FunctionExpression" ||
|
|
2348
|
-
scope_node.type === "ArrowFunctionExpression" ||
|
|
2349
|
-
scope_node.type === "BlockStatement"
|
|
2350
|
-
) {
|
|
2351
|
-
return false; // 在函数或块内,不是真正的常量
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
// 检查是否是模块导入(避免将require导入识别为常量)
|
|
2356
|
-
if (node.init &&
|
|
2357
|
-
(node.init.type === "CallExpression" &&
|
|
2358
|
-
node.init.callee &&
|
|
2359
|
-
node.init.callee.name === "require")) {
|
|
2360
|
-
return false; // 这是模块导入,不是真正的常量
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
// 检查名称是否符合常量命名模式
|
|
2364
|
-
const name = node.id.name;
|
|
2365
|
-
|
|
2366
|
-
// 常量命名模式:全大写或大写蛇形
|
|
2367
|
-
const is_uppercase = /^[A-Z][A-Z0-9]*$/.test(name); // 全大写:PORT, DEBUG
|
|
2368
|
-
const is_upper_snake = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/.test(name); // 大写蛇形:API_BASE_URL
|
|
2369
|
-
|
|
2370
|
-
// 只有符合常量命名模式的才被认为是真正的常量
|
|
2371
|
-
return is_uppercase || is_upper_snake;
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
return {
|
|
2375
|
-
VariableDeclarator(node) {
|
|
2376
|
-
if (node.id.type === "Identifier" && node.parent.kind === "const") {
|
|
2377
|
-
// 只对真正的常量应用常量命名规则
|
|
2378
|
-
if (isRealConstant(node)) {
|
|
2379
|
-
const constant_name = node.id.name;
|
|
2380
|
-
const detector = new Detector({ "constant-name": options });
|
|
2381
|
-
const result = detector.checkName(constant_name, "constant-name");
|
|
2382
|
-
|
|
2383
|
-
if (!result.valid) {
|
|
2384
|
-
result.errors.forEach((error) => {
|
|
94
|
+
create: function (context) {
|
|
95
|
+
return {
|
|
96
|
+
MethodDefinition: function (node) {
|
|
97
|
+
if (node.key && node.key.name) {
|
|
98
|
+
var method_name = node.key.name;
|
|
99
|
+
var err = detector._checkMethodName(method_name, node);
|
|
100
|
+
if (err) {
|
|
2385
101
|
context.report({
|
|
2386
|
-
node: node
|
|
2387
|
-
message:
|
|
102
|
+
node: err.node,
|
|
103
|
+
message: err.message,
|
|
2388
104
|
});
|
|
2389
|
-
}
|
|
105
|
+
}
|
|
2390
106
|
}
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
},
|
|
2394
|
-
};
|
|
2395
|
-
},
|
|
2396
|
-
};
|
|
2397
|
-
|
|
2398
|
-
/**
|
|
2399
|
-
* 入参名小写蛇形规则
|
|
2400
|
-
*/
|
|
2401
|
-
const param_name_rule = {
|
|
2402
|
-
meta: {
|
|
2403
|
-
type: "suggestion",
|
|
2404
|
-
docs: {
|
|
2405
|
-
description: "入参名必须使用小写蛇形命名法(snake_case)",
|
|
2406
|
-
category: "Stylistic Issues",
|
|
2407
|
-
recommen: true,
|
|
2408
|
-
},
|
|
2409
|
-
schema: [
|
|
2410
|
-
{
|
|
2411
|
-
type: "object",
|
|
2412
|
-
properties: {
|
|
2413
|
-
regex: { type: "string" },
|
|
2414
|
-
min: { type: "number" },
|
|
2415
|
-
max: { type: "number" },
|
|
2416
|
-
message: { type: "string" },
|
|
2417
|
-
single_word: { type: "boolean" },
|
|
2418
|
-
single_word_len: { type: "number" },
|
|
2419
|
-
styles: { type: "array", items: { type: "string" } },
|
|
2420
|
-
},
|
|
2421
|
-
additionalProperties: false,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
2422
109
|
},
|
|
2423
|
-
],
|
|
2424
|
-
},
|
|
2425
|
-
create(context) {
|
|
2426
|
-
const options = context.options[0] || {};
|
|
2427
|
-
|
|
2428
|
-
function checkParam(node) {
|
|
2429
|
-
// 处理不同节点类型的参数
|
|
2430
|
-
let params = [];
|
|
2431
|
-
|
|
2432
|
-
if (node.type === "MethodDefinition") {
|
|
2433
|
-
// MethodDefinition节点(包括构造函数)的参数在node.value.params中
|
|
2434
|
-
params = node.value ? node.value.params || [] : [];
|
|
2435
|
-
} else {
|
|
2436
|
-
// 其他函数节点的参数在node.params中
|
|
2437
|
-
params = node.params || [];
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
params.forEach((param) => {
|
|
2441
|
-
if (param.type === "Identifier") {
|
|
2442
|
-
const param_name = param.name;
|
|
2443
|
-
const detector = new Detector({ "param-name": options });
|
|
2444
|
-
const result = detector.checkName(param_name, "param-name");
|
|
2445
|
-
|
|
2446
|
-
if (!result.valid) {
|
|
2447
|
-
result.errors.forEach((error) => {
|
|
2448
|
-
context.report({
|
|
2449
|
-
node: param,
|
|
2450
|
-
message: `入参名"${param_name}"不符合规范: ${error}`,
|
|
2451
|
-
});
|
|
2452
|
-
});
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
|
-
});
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
return {
|
|
2459
|
-
FunctionDeclaration: checkParam,
|
|
2460
|
-
FunctionExpression: checkParam,
|
|
2461
|
-
ArrowFunctionExpression: checkParam,
|
|
2462
|
-
MethodDefinition: checkParam,
|
|
2463
|
-
};
|
|
2464
|
-
},
|
|
2465
|
-
};
|
|
2466
|
-
|
|
2467
|
-
/**
|
|
2468
|
-
* 属性名小写蛇形规则
|
|
2469
|
-
*/
|
|
2470
|
-
const prop_name_rule = {
|
|
2471
|
-
meta: {
|
|
2472
|
-
type: "suggestion",
|
|
2473
|
-
docs: {
|
|
2474
|
-
description: "属性名必须使用小写蛇形命名法(snake_case)",
|
|
2475
|
-
category: "Stylistic Issues",
|
|
2476
|
-
recommen: true,
|
|
2477
110
|
},
|
|
2478
|
-
schema: [
|
|
2479
|
-
{
|
|
2480
|
-
type: "object",
|
|
2481
|
-
properties: {
|
|
2482
|
-
regex: { type: "string" },
|
|
2483
|
-
min: { type: "number" },
|
|
2484
|
-
max: { type: "number" },
|
|
2485
|
-
message: { type: "string" },
|
|
2486
|
-
single_word: { type: "boolean" },
|
|
2487
|
-
single_word_len: { type: "number" },
|
|
2488
|
-
styles: { type: "array", items: { type: "string" } },
|
|
2489
|
-
},
|
|
2490
|
-
additionalProperties: false,
|
|
2491
|
-
},
|
|
2492
|
-
],
|
|
2493
|
-
},
|
|
2494
|
-
create(context) {
|
|
2495
|
-
const options = context.options[0] || {};
|
|
2496
|
-
return {
|
|
2497
|
-
Property(node) {
|
|
2498
|
-
// 检测所有非私有属性(不以_开头)
|
|
2499
|
-
if (node.key.type === "Identifier" && !node.key.name.startsWith("_")) {
|
|
2500
|
-
// 检查是否是方法(函数表达式或箭头函数或方法简写)
|
|
2501
|
-
const is_method = node.value &&
|
|
2502
|
-
(node.value.type === "FunctionExpression" ||
|
|
2503
|
-
node.value.type === "ArrowFunctionExpression" ||
|
|
2504
|
-
(node.method === true)); // 方法简写语法
|
|
2505
|
-
|
|
2506
|
-
// 如果是方法,跳过属性检测(方法由method-name规则处理)
|
|
2507
|
-
if (is_method) {
|
|
2508
|
-
return;
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
// 如果是真正的属性,使用属性规则检测
|
|
2512
|
-
const prop_name = node.key.name;
|
|
2513
|
-
const detector = new Detector({ "property-name": options });
|
|
2514
|
-
const result = detector._checkPropName(prop_name, node.value, node);
|
|
2515
|
-
|
|
2516
|
-
if (!result.valid) {
|
|
2517
|
-
result.errors.forEach((error) => {
|
|
2518
|
-
context.report({
|
|
2519
|
-
node: node.key,
|
|
2520
|
-
message: `属性名"${prop_name}"不符合规范: ${error}`,
|
|
2521
|
-
ruleId: "naming-convention/property-name",
|
|
2522
|
-
});
|
|
2523
|
-
});
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
},
|
|
2527
|
-
};
|
|
2528
|
-
},
|
|
2529
|
-
};
|
|
2530
111
|
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
description: "私有方法必须以单下划线开头,后跟小驼峰命名或小写单词",
|
|
2539
|
-
category: "Stylistic Issues",
|
|
2540
|
-
recommen: true,
|
|
2541
|
-
},
|
|
2542
|
-
schema: [
|
|
2543
|
-
{
|
|
2544
|
-
type: "object",
|
|
2545
|
-
properties: {
|
|
2546
|
-
regex: { type: "string" },
|
|
2547
|
-
min: { type: "number" },
|
|
2548
|
-
max: { type: "number" },
|
|
2549
|
-
message: { type: "string" },
|
|
2550
|
-
single_word: { type: "boolean" },
|
|
2551
|
-
single_word_len: { type: "number" },
|
|
2552
|
-
styles: { type: "array", items: { type: "string" } },
|
|
112
|
+
'variable-name': {
|
|
113
|
+
meta: {
|
|
114
|
+
type: 'suggestion',
|
|
115
|
+
docs: {
|
|
116
|
+
description: '检测变量名是否符合命名规范',
|
|
117
|
+
category: 'Stylistic Issues',
|
|
118
|
+
recommended: true,
|
|
2553
119
|
},
|
|
2554
|
-
|
|
2555
|
-
},
|
|
2556
|
-
],
|
|
2557
|
-
},
|
|
2558
|
-
create(context) {
|
|
2559
|
-
const options = context.options[0] || {};
|
|
2560
|
-
|
|
2561
|
-
function checkPrivateMethodName(node, method_name) {
|
|
2562
|
-
const detector = new Detector({ "private-method-naming": options });
|
|
2563
|
-
const result = detector.checkName(
|
|
2564
|
-
method_name,
|
|
2565
|
-
"private-method-naming",
|
|
2566
|
-
);
|
|
2567
|
-
|
|
2568
|
-
if (!result.valid) {
|
|
2569
|
-
result.errors.forEach((error) => {
|
|
2570
|
-
context.report({
|
|
2571
|
-
node: node.key || node,
|
|
2572
|
-
message: `私有方法名"${method_name}"不符合规范: ${error}`,
|
|
2573
|
-
});
|
|
2574
|
-
});
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
// 检测应该是私有方法但没有_前缀的情况
|
|
2579
|
-
function checkMissingPrivatePrefix(node, method_name) {
|
|
2580
|
-
// 使用配置中的私有方法前缀列表
|
|
2581
|
-
const detector = new Detector({ "private-method-naming": options });
|
|
2582
|
-
const private_prefixes = detector.config.private_method_prefixes || [];
|
|
2583
|
-
|
|
2584
|
-
// 判断是否应该是私有方法(基于前缀判断)
|
|
2585
|
-
const should_be_private = private_prefixes.some(prefix => {
|
|
2586
|
-
if (!method_name.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
2587
|
-
return false;
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
// 额外检查:只有多单词的方法名才需要是私有方法
|
|
2591
|
-
// 例如:init() 可以非私有,但initService() 必须私有
|
|
2592
|
-
const remaining_part = method_name.substring(prefix.length);
|
|
2593
|
-
const is_multi_word = remaining_part.length > 0 &&
|
|
2594
|
-
remaining_part[0].toUpperCase() === remaining_part[0];
|
|
2595
|
-
|
|
2596
|
-
return is_multi_word;
|
|
2597
|
-
});
|
|
2598
|
-
|
|
2599
|
-
if (should_be_private && !method_name.startsWith("_")) {
|
|
2600
|
-
context.report({
|
|
2601
|
-
node: node.key || node,
|
|
2602
|
-
message: `方法名"${method_name}"应该是私有方法,但缺少_前缀`,
|
|
2603
|
-
});
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
return {
|
|
2608
|
-
// 处理类中的私有方法
|
|
2609
|
-
MethodDefinition(node) {
|
|
2610
|
-
const method_name = node.key.name;
|
|
2611
|
-
if (method_name.startsWith("_")) {
|
|
2612
|
-
checkPrivateMethodName(node, method_name);
|
|
2613
|
-
} else {
|
|
2614
|
-
// 检测应该是私有方法但没有_前缀的情况
|
|
2615
|
-
checkMissingPrivatePrefix(node, method_name);
|
|
2616
|
-
}
|
|
120
|
+
schema: [],
|
|
2617
121
|
},
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
checkMissingPrivatePrefix(node, node.key.name);
|
|
122
|
+
create: function (context) {
|
|
123
|
+
return {
|
|
124
|
+
VariableDeclarator: function (node) {
|
|
125
|
+
// 排除const声明,只检测let和var声明的变量
|
|
126
|
+
if (node.parent && node.parent.kind !== 'const') {
|
|
127
|
+
if (node.id && node.id.name) {
|
|
128
|
+
var variable_name = node.id.name;
|
|
129
|
+
var err = detector._checkVariableName(variable_name, node);
|
|
130
|
+
if (err) {
|
|
131
|
+
context.report({
|
|
132
|
+
node: err.node,
|
|
133
|
+
message: err.message,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
2634
137
|
}
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
2637
140
|
},
|
|
2638
|
-
};
|
|
2639
|
-
},
|
|
2640
|
-
};
|
|
2641
|
-
|
|
2642
|
-
/**
|
|
2643
|
-
* 私有变量命名规则
|
|
2644
|
-
*/
|
|
2645
|
-
const private_variable_rule = {
|
|
2646
|
-
meta: {
|
|
2647
|
-
type: "suggestion",
|
|
2648
|
-
docs: {
|
|
2649
|
-
description: "私有变量必须以单下划线开头,后跟小写蛇形命名或小写单词",
|
|
2650
|
-
category: "Stylistic Issues",
|
|
2651
|
-
recommen: true,
|
|
2652
141
|
},
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
single_word: { type: "boolean" },
|
|
2662
|
-
single_word_len: { type: "number" },
|
|
2663
|
-
styles: { type: "array", items: { type: "string" } },
|
|
142
|
+
|
|
143
|
+
'param-name': {
|
|
144
|
+
meta: {
|
|
145
|
+
type: 'suggestion',
|
|
146
|
+
docs: {
|
|
147
|
+
description: '检测参数名是否符合命名规范',
|
|
148
|
+
category: 'Stylistic Issues',
|
|
149
|
+
recommended: true,
|
|
2664
150
|
},
|
|
2665
|
-
|
|
151
|
+
schema: [],
|
|
2666
152
|
},
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
"private-method-naming",
|
|
2685
|
-
);
|
|
2686
|
-
|
|
2687
|
-
if (!result.valid) {
|
|
2688
|
-
result.errors.forEach((error) => {
|
|
2689
|
-
context.report({
|
|
2690
|
-
node: node.key,
|
|
2691
|
-
message: `私有方法名"${node.key.name}"不符合规范: ${error}`,
|
|
2692
|
-
});
|
|
2693
|
-
});
|
|
153
|
+
create: function (context) {
|
|
154
|
+
return {
|
|
155
|
+
FunctionDeclaration: function (node) {
|
|
156
|
+
if (node.params) {
|
|
157
|
+
for (var i = 0; i < node.params.length; i++) {
|
|
158
|
+
var param = node.params[i];
|
|
159
|
+
if (param.name) {
|
|
160
|
+
var param_name = param.name;
|
|
161
|
+
var err = detector._checkParamName(param_name, param);
|
|
162
|
+
if (err) {
|
|
163
|
+
context.report({
|
|
164
|
+
node: err.node,
|
|
165
|
+
message: err.message,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
2694
170
|
}
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
171
|
+
},
|
|
172
|
+
FunctionExpression: function (node) {
|
|
173
|
+
if (node.params) {
|
|
174
|
+
for (var i = 0; i < node.params.length; i++) {
|
|
175
|
+
var param = node.params[i];
|
|
176
|
+
if (param.name) {
|
|
177
|
+
var param_name = param.name;
|
|
178
|
+
var err = detector._checkParamName(param_name, param);
|
|
179
|
+
if (err) {
|
|
180
|
+
context.report({
|
|
181
|
+
node: err.node,
|
|
182
|
+
message: err.message,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
2710
187
|
}
|
|
2711
|
-
}
|
|
2712
|
-
|
|
188
|
+
},
|
|
189
|
+
ArrowFunctionExpression: function (node) {
|
|
190
|
+
if (node.params) {
|
|
191
|
+
for (var i = 0; i < node.params.length; i++) {
|
|
192
|
+
var param = node.params[i];
|
|
193
|
+
if (param.name) {
|
|
194
|
+
var param_name = param.name;
|
|
195
|
+
var err = detector._checkParamName(param_name, param);
|
|
196
|
+
if (err) {
|
|
197
|
+
context.report({
|
|
198
|
+
node: err.node,
|
|
199
|
+
message: err.message,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
2713
207
|
},
|
|
2714
|
-
};
|
|
2715
|
-
},
|
|
2716
|
-
};
|
|
2717
|
-
|
|
2718
|
-
/**
|
|
2719
|
-
* 实例属性赋值命名规则(检测 this.property = value 模式)
|
|
2720
|
-
*/
|
|
2721
|
-
const instance_property_rule = {
|
|
2722
|
-
meta: {
|
|
2723
|
-
type: "suggestion",
|
|
2724
|
-
docs: {
|
|
2725
|
-
description: "实例属性赋值必须符合命名规范",
|
|
2726
|
-
category: "Stylistic Issues",
|
|
2727
|
-
recommen: true,
|
|
2728
208
|
},
|
|
2729
|
-
schema: [
|
|
2730
|
-
{
|
|
2731
|
-
type: "object",
|
|
2732
|
-
properties: {
|
|
2733
|
-
regex: { type: "string" },
|
|
2734
|
-
min: { type: "number" },
|
|
2735
|
-
max: { type: "number" },
|
|
2736
|
-
message: { type: "string" },
|
|
2737
|
-
single_word: { type: "boolean" },
|
|
2738
|
-
single_word_len: { type: "number" },
|
|
2739
|
-
styles: { type: "array", items: { type: "string" } },
|
|
2740
|
-
},
|
|
2741
|
-
additionalProperties: false,
|
|
2742
|
-
},
|
|
2743
|
-
],
|
|
2744
|
-
},
|
|
2745
|
-
create(context) {
|
|
2746
|
-
const options = context.options[0] || {};
|
|
2747
|
-
return {
|
|
2748
|
-
AssignmentExpression(node) {
|
|
2749
|
-
// 检测 this.property = value 模式
|
|
2750
|
-
if (
|
|
2751
|
-
node.left.type === "MemberExpression" &&
|
|
2752
|
-
node.left.object.type === "ThisExpression" &&
|
|
2753
|
-
node.left.property.type === "Identifier" &&
|
|
2754
|
-
!node.left.property.name.startsWith("_")
|
|
2755
|
-
) {
|
|
2756
|
-
const prop_name = node.left.property.name;
|
|
2757
|
-
// 实例属性应该符合属性命名规范(小写蛇形)
|
|
2758
|
-
const detector = new Detector({ "property-name": options });
|
|
2759
|
-
const result = detector._checkPropName(prop_name, node.right, node);
|
|
2760
209
|
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
210
|
+
'constant-name': {
|
|
211
|
+
meta: {
|
|
212
|
+
type: 'suggestion',
|
|
213
|
+
docs: {
|
|
214
|
+
description: '检测常量名是否符合命名规范',
|
|
215
|
+
category: 'Stylistic Issues',
|
|
216
|
+
recommended: true,
|
|
217
|
+
},
|
|
218
|
+
schema: [],
|
|
2770
219
|
},
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
message: `实例属性名"${prop_name}"不符合规范: ${error}`,
|
|
2789
|
-
});
|
|
2790
|
-
});
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
220
|
+
create: function (context) {
|
|
221
|
+
return {
|
|
222
|
+
VariableDeclarator: function (node) {
|
|
223
|
+
if (node.parent && node.parent.kind === 'const') {
|
|
224
|
+
if (node.id && node.id.name) {
|
|
225
|
+
var constant_name = node.id.name;
|
|
226
|
+
var err = detector._checkConstantName(constant_name, node, node.init);
|
|
227
|
+
if (err) {
|
|
228
|
+
context.report({
|
|
229
|
+
node: err.node,
|
|
230
|
+
message: err.message,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
2793
237
|
},
|
|
2794
|
-
};
|
|
2795
|
-
},
|
|
2796
|
-
};
|
|
2797
|
-
|
|
2798
|
-
/**
|
|
2799
|
-
* 原型方法命名规则(检测 ClassName.prototype.methodName = function() {} 模式)
|
|
2800
|
-
*/
|
|
2801
|
-
const prototype_method_rule = {
|
|
2802
|
-
meta: {
|
|
2803
|
-
type: "suggestion",
|
|
2804
|
-
docs: {
|
|
2805
|
-
description: "原型方法名必须使用小驼峰命名法(camelCase)且优先使用单个单词",
|
|
2806
|
-
category: "Stylistic Issues",
|
|
2807
|
-
recommen: true,
|
|
2808
238
|
},
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
single_word: { type: "boolean" },
|
|
2818
|
-
single_word_len: { type: "number" },
|
|
2819
|
-
styles: { type: "array", items: { type: "string" } },
|
|
239
|
+
|
|
240
|
+
'property-name': {
|
|
241
|
+
meta: {
|
|
242
|
+
type: 'suggestion',
|
|
243
|
+
docs: {
|
|
244
|
+
description: '检测属性名是否符合命名规范',
|
|
245
|
+
category: 'Stylistic Issues',
|
|
246
|
+
recommended: true,
|
|
2820
247
|
},
|
|
2821
|
-
|
|
248
|
+
schema: [],
|
|
2822
249
|
},
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
];
|
|
2848
|
-
if (builtin_methods.includes(method_name)) {
|
|
2849
|
-
return;
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
// 根据方法名是否以下划线开头,选择不同的检测规则
|
|
2853
|
-
const rule_name = method_name.startsWith("_") ? "private-method-naming" : "method-name";
|
|
2854
|
-
const detector = new Detector({ [rule_name]: options });
|
|
2855
|
-
const result = detector.checkName(method_name, rule_name);
|
|
2856
|
-
|
|
2857
|
-
if (!result.valid) {
|
|
2858
|
-
result.errors.forEach((error) => {
|
|
250
|
+
create: function (context) {
|
|
251
|
+
return {
|
|
252
|
+
Property: function (node) {
|
|
253
|
+
if (node.key && node.key.name) {
|
|
254
|
+
var prop_name = node.key.name;
|
|
255
|
+
|
|
256
|
+
// 直接跳过null值和未赋值属性的检测
|
|
257
|
+
// 注意:null值的类型是Literal,但value为null
|
|
258
|
+
var is_null_value = node.value && node.value.type === 'Literal' && node.value.value === null;
|
|
259
|
+
var is_undefined_value = !node.value;
|
|
260
|
+
|
|
261
|
+
if (is_null_value || is_undefined_value) {
|
|
262
|
+
// 对于null值和未赋值属性,直接跳过检测,不报错
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
var err = detector._checkPropertyName(
|
|
267
|
+
prop_name,
|
|
268
|
+
node,
|
|
269
|
+
node.value,
|
|
270
|
+
node.parent,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (err) {
|
|
2859
274
|
context.report({
|
|
2860
|
-
node: node
|
|
2861
|
-
message:
|
|
275
|
+
node: err.node,
|
|
276
|
+
message: err.message,
|
|
2862
277
|
});
|
|
2863
|
-
}
|
|
278
|
+
}
|
|
2864
279
|
}
|
|
2865
|
-
}
|
|
280
|
+
},
|
|
281
|
+
AssignmentExpression: function (node) {
|
|
282
|
+
if (
|
|
283
|
+
node.left &&
|
|
284
|
+
node.left.type === 'MemberExpression' &&
|
|
285
|
+
node.left.property &&
|
|
286
|
+
node.left.property.name
|
|
287
|
+
) {
|
|
288
|
+
var prop_name = node.left.property.name;
|
|
289
|
+
|
|
290
|
+
// 直接跳过null值和未赋值属性的检测
|
|
291
|
+
// 注意:null值的类型是Literal,但value为null
|
|
292
|
+
var is_null_value = node.right && node.right.type === 'Literal' && node.right.value === null;
|
|
293
|
+
var is_undefined_value = !node.right;
|
|
294
|
+
|
|
295
|
+
if (is_null_value || is_undefined_value) {
|
|
296
|
+
// 对于null值和未赋值属性,直接跳过检测,不报错
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
var err = detector._checkPropertyName(
|
|
301
|
+
prop_name,
|
|
302
|
+
node,
|
|
303
|
+
node.right,
|
|
304
|
+
node.parent,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (err) {
|
|
308
|
+
context.report({
|
|
309
|
+
node: err.node,
|
|
310
|
+
message: err.message,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
};
|
|
2866
316
|
},
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
/**
|
|
2872
|
-
* 命名规范ESLint插件
|
|
2873
|
-
*/
|
|
2874
|
-
const naming_rules = {
|
|
2875
|
-
"class-name": class_name_rule,
|
|
2876
|
-
"function-name": function_name_rule,
|
|
2877
|
-
"method-name": method_name_rule,
|
|
2878
|
-
"variable-name": variable_name_rule,
|
|
2879
|
-
"constant-name": constant_name_rule,
|
|
2880
|
-
"private-method-naming": private_method_rule,
|
|
2881
|
-
"private-variable-naming": private_variable_rule,
|
|
2882
|
-
"param-name": param_name_rule,
|
|
2883
|
-
"property-name": prop_name_rule,
|
|
2884
|
-
"instance-property": instance_property_rule,
|
|
2885
|
-
"prototype-method": prototype_method_rule,
|
|
2886
|
-
};
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
2887
320
|
|
|
2888
321
|
module.exports = {
|
|
2889
|
-
|
|
2890
|
-
rules: naming_rules,
|
|
322
|
+
rules: createNamingRules(),
|
|
2891
323
|
};
|