mm_eslint 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +77 -67
  2. package/README_EN.md +77 -67
  3. package/index.js +1899 -1085
  4. package/package.json +1 -1
package/index.js CHANGED
@@ -1,1085 +1,1899 @@
1
- 'use strict';
2
-
3
- /**
4
- * 命名规范检测器类
5
- */
6
- class Detector {
7
- static config = {
8
- forbidden_words: [
9
- 'manager', 'handler', 'processor', 'controller',
10
- 'Management', 'Handler', 'Processor', 'Controller',
11
- 'LoggerManager', 'AppState', 'UserHandler'
12
- ],
13
- regex: {
14
- 'PascalCase': /^[A-Z][a-zA-Z]*$/, // 大驼峰:大写字母开头,只包含字母。主要用于类名
15
- 'camelCase': /^[a-z][a-zA-Z]*$/, // 小驼峰:小写字母开头,只包含字母。主要用于方法名、函数名
16
- 'snake_case': /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写蛇形:小写字母开头,可包含下划线。主要用于变量名、入参名
17
- 'UPPER_SNAKE_CASE': /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/, // 大写蛇形:大写字母开头,可包含下划线。主要用于常量名
18
- 'kebab-case': /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 小写横杠:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
19
- 'UPPER-KEBAB-CASE': /^[A-Z][A-Z0-9-]*(-[A-Z0-9]+)*$/, // 大写横杠:大写字母开头,可包含短横线,大写短横线。主要用于资源名、路由路径
20
- 'lowercase': /^[a-z][a-z0-9]*$/, // 小写单词:小写字母开头,只包含小写字母和数字。主要用于对象、属性名
21
- 'UPPERCASE': /^[A-Z][A-Z0-9]*$/, // 大写单词:大写字母开头,只包含大写字母和数字。主要用于枚举值、常量名
22
- 'camelCase-kebab': /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 支持横杠小驼峰:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
23
- 'PascalCase-kebab': /^[A-Z][a-zA-Z]*$/, // 支持横杠大驼峰:大写字母开头,只包含字母。主要用于资源名、路由路径
24
- '_camelCase': /^_[a-z][a-zA-Z]*$/, // 下划线小驼峰:下划线加小写字母开头,只包含字母。主要用于私有方法名
25
- '_snake_case': /^_[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 下划线小写蛇形:下划线加小写字母开头,可包含下划线。主要用于私有变量名
26
- '_lowercase': /^_[a-z][a-z0-9]*$/, // 下划线小写单词:下划线加小写字母开头,只包含小写字母和数字。主要用于对象、属性名
27
- },
28
-
29
- // 类名大驼峰规则
30
- 'class-name': {
31
- name: '类名',
32
- message: '必须使用大驼峰命名法(PascalCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
33
- regex: null, // 使用styles属性进行验证
34
- min: 1,
35
- max: 20,
36
- styles: ['PascalCase'],
37
- single_word: true,
38
- single_word_len: 8
39
- },
40
-
41
- // 函数名小驼峰规则
42
- 'function-name': {
43
- name: '函数名',
44
- message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
45
- regex: null, // 使用styles属性进行验证
46
- min: 1,
47
- max: 20,
48
- styles: ['camelCase'],
49
- single_word: true,
50
- single_word_len: 8
51
- },
52
-
53
- // 方法名小驼峰规则
54
- 'method-name': {
55
- name: '方法名',
56
- message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
57
- regex: null, // 使用styles属性进行验证
58
- min: 1,
59
- max: 20,
60
- styles: ['camelCase'],
61
- single_word: true,
62
- single_word_len: 8
63
- },
64
-
65
- // 入参名小写蛇形规则
66
- 'param-name': {
67
- name: '入参名',
68
- message: '必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符',
69
- regex: null, // 使用styles属性进行验证
70
- min: 1,
71
- max: 20,
72
- styles: ['lowercase', 'snake_case']
73
- },
74
-
75
- // 变量名小写蛇形规则
76
- 'variable-name': {
77
- name: '变量名',
78
- message: '必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符',
79
- regex: null, // 使用styles属性进行验证
80
- min: 1,
81
- max: 20,
82
- styles: ['lowercase', 'snake_case']
83
- },
84
-
85
- // 常量名大写蛇形规则
86
- 'constant-name': {
87
- name: '常量名',
88
- message: '必须使用大写蛇形命名法(UPPER_SNAKE_CASE),长度{min}-{max}字符',
89
- regex: null, // 使用styles属性进行验证
90
- min: 1,
91
- max: 20,
92
- styles: ['UPPERCASE', 'UPPER_SNAKE_CASE']
93
- },
94
-
95
- // 属性名多风格规则(根据值类型应用不同规则)
96
- 'property-name': {
97
- name: '属性名',
98
- message: '根据属性值类型应用不同命名规则',
99
- min: 1,
100
- max: 20,
101
- single_word: true,
102
- single_word_len: 8,
103
- styles: ['lowercase', '_lowercase', 'UPPERCASE', 'snake_case', '_snake_case']
104
- },
105
-
106
- // 私有方法/属性规则
107
- 'private-naming': {
108
- name: '私有成员',
109
- message: '必须以单下划线开头,后跟小写蛇形命名,长度{min}-{max}字符',
110
- regex: null, // 使用styles属性进行验证
111
- min: 2,
112
- max: 20,
113
- styles: ['_lowercase', '_snake_case', '_camelCase']
114
- }
115
- }
116
-
117
- constructor(config) {
118
- // 合并默认配置和传入配置
119
- const merged_config = { ...Detector.config };
120
- if (config && typeof config === 'object') {
121
- Object.keys(config).forEach(key => {
122
- if (merged_config[key]) {
123
- merged_config[key] = { ...merged_config[key], ...config[key] };
124
-
125
- // 处理字符串形式的正则表达式
126
- if (typeof merged_config[key].regex === 'string') {
127
- try {
128
- // 从字符串中提取正则表达式模式和标志
129
- const regex_match = merged_config[key].regex.match(/^\/(.*)\/([gimuy]*)$/);
130
- if (regex_match) {
131
- merged_config[key].regex = new RegExp(regex_match[1], regex_match[2]);
132
- } else {
133
- // 如果不是标准的正则表达式字符串格式,直接创建
134
- merged_config[key].regex = new RegExp(merged_config[key].regex);
135
- }
136
- } catch (error) {
137
- console.warn(`无效的正则表达式: ${merged_config[key].regex}`, error);
138
- merged_config[key].regex = null;
139
- }
140
- }
141
- }
142
- });
143
- }
144
- this.config = merged_config;
145
- this._init_rules();
146
- }
147
-
148
-
149
- }
150
-
151
- /**
152
- * 创建新的规则模型
153
- * @returns {Object} 规则模型
154
- * @private
155
- */
156
- Detector.prototype._new_model = function () {
157
- return {
158
- name: '',
159
- message: '',
160
- regex: null,
161
- min: 1,
162
- max: 20
163
- };
164
- }
165
-
166
- /**
167
- * 初始化规则检查方法
168
- * @private
169
- */
170
- Detector.prototype._init_rules = function () {
171
- // 绑定规则检查方法
172
- this._rules = {
173
- 'class-name': this._checkName.bind(this, 'class-name'),
174
- 'function-name': this._checkName.bind(this, 'function-name'),
175
- 'method-name': this._checkName.bind(this, 'method-name'),
176
- 'param-name': this._checkName.bind(this, 'param-name'),
177
- 'variable-name': this._checkName.bind(this, 'variable-name'),
178
- 'constant-name': this._checkName.bind(this, 'constant-name'),
179
- 'property-name': this._checkPropertyName.bind(this),
180
- 'private-naming': this._checkName.bind(this, 'private-naming')
181
- };
182
- }
183
-
184
- /**
185
- * 通用命名检查方法
186
- * @param {string} rule_type - 规则类型
187
- * @param {string} name - 名称
188
- * @returns {Object} 检查结果
189
- * @private
190
- */
191
- Detector.prototype._checkName = function (rule_type, name) {
192
- if (!name) {
193
- throw new TypeError('名称不能为空');
194
- }
195
-
196
- const config = this.config[rule_type];
197
- if (!config) {
198
- throw new Error(`不支持的规则类型: ${rule_type}`);
199
- }
200
-
201
- const errors = [];
202
- const warnings = [];
203
-
204
- // 检查长度
205
- if (name.length < config.min) {
206
- errors.push(`${config.name}长度不能少于${config.min}个字符`);
207
- }
208
- if (name.length > config.max) {
209
- errors.push(`${config.name}长度不能超过${config.max}个字符`);
210
- }
211
-
212
- // 检查命名规则(优先使用regex,如果regex为空则使用styles)
213
- let name_valid = false;
214
-
215
- if (config.regex && config.regex.toString() !== '/(?:)/') {
216
- // 使用regex进行验证
217
- name_valid = config.regex.test(name);
218
- if (!name_valid) {
219
- const error_message = config.message
220
- .replace('{name}', `"${name}"`)
221
- .replace('{type}', config.name)
222
- .replace('{min}', config.min)
223
- .replace('{max}', config.max)
224
- .replace('{word_len}', config.single_word_len || 8)
225
- .replace('{regex}', config.regex.toString());
226
- errors.push(error_message);
227
- }
228
- } else if (config.styles && config.styles.length > 0) {
229
- // 使用styles进行验证
230
- name_valid = this._checkNameByStyles(name, config.styles);
231
- if (!name_valid) {
232
- const allowed_styles = config.styles.join('、');
233
- errors.push(`${config.name}必须符合以下命名风格之一:${allowed_styles}`);
234
- }
235
- } else {
236
- // 既没有regex也没有styles,跳过命名格式检查
237
- name_valid = true;
238
- }
239
-
240
- // 检查禁止词汇
241
- if (this._hasForbiddenWords(name)) {
242
- errors.push(`${config.name}包含禁止的废话词`);
243
- }
244
-
245
- // 检查是否优先使用单个单词(警告级别)
246
- if (config.single_word) {
247
- // 检测驼峰命名中的大写字母(表示多单词组合)
248
- // 排除第一个字母后,检查是否包含大写字母(表示多单词)
249
- const has_uppercase = /[A-Z]/.test(name.slice(1));
250
- // 检测下划线或连字符
251
- const has_separators = name.includes('_') || name.includes('-');
252
-
253
- // 对于函数名和方法名,需要更精确地检测多单词
254
- // getName(get前缀+单个单词)不算多单词,getUserName(get前缀+多单词)才算多单词
255
- const is_function_or_method = rule_type === 'function-name' || rule_type === 'method-name';
256
- let is_multi_word = has_uppercase || has_separators;
257
-
258
- if (is_function_or_method && is_multi_word) {
259
- // 检查是否是add/del/set/get/is/has等前缀后跟单个单词
260
- const prefix_match = name.match(/^(add|del|set|get|is|has|can|not)([A-Z][a-z]*)$/);
261
- if (prefix_match && prefix_match[2]) {
262
- // 如果前缀后只有一个单词,不算多单词
263
- is_multi_word = false;
264
- }
265
- }
266
-
267
- if (is_multi_word) {
268
- warnings.push(`${config.name}应优先使用单个单词`);
269
- }
270
- }
271
-
272
- // 检查单个单词长度限制
273
- if (config.single_word_len && config.single_word_len > 0) {
274
- const words = this._splitWords(name, rule_type);
275
- const long_words = words.filter(word => word.length > config.single_word_len);
276
-
277
- if (long_words.length > 0) {
278
- errors.push(`${config.name}中的单词"${long_words.join('、')}"超过${config.single_word_len}个字符限制`);
279
- }
280
- }
281
-
282
- return {
283
- valid: errors.length === 0,
284
- errors: errors,
285
- warnings: warnings
286
- };
287
- }
288
-
289
- /**
290
- * 根据styles列表检查名称是否符合命名风格
291
- * @param {string} name - 名称
292
- * @param {Array} styles - 命名风格列表
293
- * @returns {boolean} 是否符合任一风格
294
- * @private
295
- */
296
- Detector.prototype._checkNameByStyles = function (name, styles) {
297
- if (!name || !styles || styles.length === 0) {
298
- return false;
299
- }
300
-
301
- // 检查名称是否符合任一指定的命名风格
302
- for (const style of styles) {
303
- const regex = this.config.regex[style];
304
- if (regex && regex.test(name)) {
305
- return true;
306
- }
307
- }
308
-
309
- return false;
310
- }
311
-
312
- /**
313
- * 拆分名称中的单词
314
- * @param {string} name - 名称
315
- * @param {string} rule_type - 规则类型
316
- * @returns {Array} 拆分后的单词数组
317
- * @private
318
- */
319
- Detector.prototype._splitWords = function (name, rule_type) {
320
- if (!name) {
321
- return [];
322
- }
323
-
324
- const words = [];
325
-
326
- // 根据命名风格拆分单词
327
- if (name.includes('_')) {
328
- // 蛇形命名:user_name → ["user", "name"]
329
- words.push(...name.split('_').filter(word => word.length > 0));
330
- } else if (name.includes('-')) {
331
- // 横杠命名:user-name → ["user", "name"]
332
- words.push(...name.split('-').filter(word => word.length > 0));
333
- } else {
334
- // 驼峰命名:userName → ["user", "Name"]
335
- // 使用正则表达式拆分驼峰命名
336
- const camel_case_words = name.split(/(?=[A-Z])/);
337
-
338
- // 对于函数名和方法名,处理常见前缀
339
- if (rule_type === 'function-name' || rule_type === 'method-name') {
340
- const prefix_match = name.match(/^(add|del|set|get|is|has|can|not)([A-Z].*)$/);
341
- if (prefix_match) {
342
- // 如果是前缀+单词的组合,将前缀作为单独单词
343
- words.push(prefix_match[1]);
344
- // 拆分剩余部分
345
- const remaining_words = prefix_match[2].split(/(?=[A-Z])/);
346
- words.push(...remaining_words);
347
- } else {
348
- words.push(...camel_case_words);
349
- }
350
- } else {
351
- words.push(...camel_case_words);
352
- }
353
- }
354
-
355
- // 过滤空字符串并转换为小写进行比较
356
- return words.filter(word => word.length > 0).map(word => word.toLowerCase());
357
- }
358
-
359
- /**
360
- * 属性名检查方法(根据属性值类型应用不同规则)
361
- * @param {string} name - 属性名
362
- * @param {Object} value_node - 属性值AST节点
363
- * @returns {Object} 检查结果
364
- * @private
365
- */
366
- Detector.prototype._checkPropertyName = function (name, value_node) {
367
- if (!name) {
368
- throw new TypeError('属性名不能为空');
369
- }
370
-
371
- const config = this.config['property-name'];
372
- if (!config) {
373
- throw new Error('属性名规则配置不存在');
374
- }
375
-
376
- const errors = [];
377
- const warnings = [];
378
-
379
- // 检查长度
380
- if (name.length < config.min) {
381
- errors.push(`${config.name}长度不能少于${config.min}个字符`);
382
- }
383
- if (name.length > config.max) {
384
- errors.push(`${config.name}长度不能超过${config.max}个字符`);
385
- }
386
-
387
- // 检查禁止词汇
388
- if (this._hasForbiddenWords(name)) {
389
- errors.push(`${config.name}包含禁止的废话词`);
390
- }
391
-
392
- // 根据属性值类型应用不同的命名规则
393
- const value_type = this._getPropertyValueType(value_node);
394
-
395
- switch (value_type) {
396
- case 'function':
397
- // 函数属性:使用函数名检测规则
398
- const function_result = this._checkName('function-name', name);
399
- if (!function_result.valid) {
400
- errors.push(`${config.name}(函数属性)${function_result.errors.join(', ')}`);
401
- }
402
- break;
403
-
404
- case 'class':
405
- // 类属性:使用类名检测规则
406
- const class_result = this._checkName('class-name', name);
407
- if (!class_result.valid) {
408
- errors.push(`${config.name}(类属性)${class_result.errors.join(', ')}`);
409
- }
410
- break;
411
-
412
- case 'value':
413
- default:
414
- // 值属性:使用styles配置检查命名风格
415
- const is_uppercase_snake = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)+$/.test(name); // 包含下划线的大写蛇形
416
- const is_uppercase_multi = /^[A-Z][A-Z0-9]*[A-Z][A-Z0-9]*$/.test(name); // 多单词大写组合(没有下划线)
417
-
418
- // 检查是否为大写蛇形(禁止)
419
- if (is_uppercase_snake || is_uppercase_multi) {
420
- errors.push(`${config.name}(值属性)禁止使用大写蛇形命名(如PRODUCT_ID或PRODUCTID)`);
421
- }
422
-
423
- // 使用styles配置检查命名风格
424
- if (config.styles && config.styles.length > 0) {
425
- const is_valid_style = this._checkNameByStyles(name, config.styles);
426
- if (!is_valid_style && !is_uppercase_snake && !is_uppercase_multi) {
427
- const allowed_styles = config.styles.join('、');
428
- errors.push(`${config.name}(值属性)必须符合以下命名风格之一:${allowed_styles}`);
429
- }
430
- }
431
- break;
432
- }
433
-
434
- // 检查是否优先使用单个单词(警告级别)
435
- if (config.single_word) {
436
- // 检测驼峰命名中的大写字母(表示多单词组合)
437
- const has_uppercase = /[A-Z]/.test(name.slice(1));
438
- // 检测下划线或连字符
439
- const has_separators = name.includes('_') || name.includes('-');
440
-
441
- if (has_uppercase || has_separators) {
442
- warnings.push(`${config.name}应优先使用单个单词`);
443
- }
444
- }
445
-
446
- return {
447
- valid: errors.length === 0,
448
- errors: errors,
449
- warnings: warnings,
450
- value_type: value_type
451
- };
452
- }
453
-
454
- /**
455
- * 获取属性值类型
456
- * @param {Object} value_node - 属性值AST节点
457
- * @returns {string} 属性值类型('function', 'class', 'value')
458
- * @private
459
- */
460
- Detector.prototype._getPropertyValueType = function (value_node) {
461
- if (!value_node) {
462
- return 'value'; // 默认值类型
463
- }
464
-
465
- // 检查是否为函数
466
- if (value_node.type === 'FunctionExpression' ||
467
- value_node.type === 'ArrowFunctionExpression' ||
468
- (value_node.type === 'Identifier' && value_node.name &&
469
- (value_node.name.endsWith('Function') || value_node.name.endsWith('Func') ||
470
- value_node.name.startsWith('on') || value_node.name.startsWith('handle')))) {
471
- return 'function';
472
- }
473
-
474
- // 检查是否为类
475
- if (value_node.type === 'ClassExpression' ||
476
- (value_node.type === 'Identifier' && value_node.name &&
477
- /^[A-Z][a-zA-Z]*$/.test(value_node.name))) {
478
- return 'class';
479
- }
480
-
481
- // 默认为值类型
482
- return 'value';
483
- }
484
-
485
- /**
486
- * 检查是否包含禁止的废话词
487
- * @param {string} name - 名称
488
- * @returns {boolean} 是否包含禁止词
489
- * @private
490
- */
491
- Detector.prototype._hasForbiddenWords = function (name) {
492
- if (!name) {
493
- return false;
494
- }
495
-
496
- const forbidden_words = this.config.forbidden_words || [];
497
-
498
- return forbidden_words.some(word =>
499
- name.toLowerCase().includes(word.toLowerCase())
500
- );
501
- }
502
-
503
- /**
504
- * 检测名称是否符合规范
505
- * @param {string} name - 名称
506
- * @param {string} type - 类型(class/function/variable/constant/private)
507
- * @returns {Object} 检测结果
508
- */
509
- Detector.prototype.checkName = function (name, type) {
510
- if (typeof name !== 'string' || !name.trim()) {
511
- return {
512
- valid: false,
513
- errors: ['名称必须是有效的字符串']
514
- };
515
- }
516
-
517
- if (typeof type !== 'string' || !type.trim()) {
518
- return {
519
- valid: false,
520
- errors: ['类型必须是有效的字符串']
521
- };
522
- }
523
-
524
- const rule_method = this._rules[type];
525
- if (!rule_method) {
526
- return {
527
- valid: false,
528
- errors: [`不支持的类型: ${type}`]
529
- };
530
- }
531
-
532
- try {
533
- return rule_method(name);
534
- } catch (error) {
535
- return {
536
- valid: false,
537
- errors: [`检测名称时出错: ${error.message}`]
538
- };
539
- }
540
- }
541
-
542
- /**
543
- * 批量检测名称
544
- * @param {Array} names - 名称数组
545
- * @param {string} type - 类型
546
- * @returns {Array} 检测结果数组
547
- */
548
- Detector.prototype.checkNames = function (names, type) {
549
- if (!Array.isArray(names)) {
550
- throw new TypeError('名称必须是数组');
551
- }
552
-
553
- return names.map(name => this.check_name(name, type));
554
- }
555
-
556
- /**
557
- * ESLint规则实现
558
- */
559
-
560
- /**
561
- * 类名大驼峰规则
562
- */
563
- const class_name_rule = {
564
- meta: {
565
- type: 'suggestion',
566
- docs: {
567
- description: '类名必须使用大驼峰命名法(PascalCase)且优先使用单个单词',
568
- category: 'Stylistic Issues',
569
- recommended: true
570
- },
571
- schema: [
572
- {
573
- type: 'object',
574
- properties: {
575
- regex: { type: 'string' },
576
- min: { type: 'number' },
577
- max: { type: 'number' },
578
- message: { type: 'string' },
579
- single_word: { type: 'boolean' },
580
- single_word_len: { type: 'number' },
581
- styles: { type: 'array', items: { type: 'string' } }
582
- },
583
- additionalProperties: false
584
- }
585
- ]
586
- },
587
- create(context) {
588
- const options = context.options[0] || {};
589
- return {
590
- ClassDeclaration(node) {
591
- const class_name = node.id.name;
592
- const detector = new Detector({ 'class-name': options });
593
- const result = detector.checkName(class_name, 'class-name');
594
-
595
- if (!result.valid) {
596
- result.errors.forEach(error => {
597
- context.report({
598
- node: node.id,
599
- message: `类名"${class_name}"不符合规范: ${error}`
600
- });
601
- });
602
- }
603
- }
604
- };
605
- }
606
- };
607
-
608
- /**
609
- * 函数名小驼峰规则
610
- */
611
- const function_name_rule = {
612
- meta: {
613
- type: 'suggestion',
614
- docs: {
615
- description: '函数名必须使用小驼峰命名法(camelCase)且优先使用单个单词',
616
- category: 'Stylistic Issues',
617
- recommended: true
618
- },
619
- schema: [
620
- {
621
- type: 'object',
622
- properties: {
623
- regex: { type: 'string' },
624
- min: { type: 'number' },
625
- max: { type: 'number' },
626
- message: { type: 'string' },
627
- single_word: { type: 'boolean' },
628
- single_word_len: { type: 'number' },
629
- styles: { type: 'array', items: { type: 'string' } }
630
- },
631
- additionalProperties: false
632
- }
633
- ]
634
- },
635
- create(context) {
636
- const options = context.options[0] || {};
637
- return {
638
- FunctionDeclaration(node) {
639
- const function_name = node.id ? node.id.name : 'anonymous';
640
- if (function_name !== 'anonymous') {
641
- const detector = new Detector({ 'function-name': options });
642
- const result = detector.checkName(function_name, 'function-name');
643
-
644
- if (!result.valid) {
645
- result.errors.forEach(error => {
646
- context.report({
647
- node: node.id,
648
- message: `函数名"${function_name}"不符合规范: ${error}`
649
- });
650
- });
651
- }
652
- }
653
- },
654
-
655
- FunctionExpression(node) {
656
- if (node.id) {
657
- const function_name = node.id.name;
658
- const detector = new Detector({ 'function-name': options });
659
- const result = detector.checkName(function_name, 'function-name');
660
-
661
- if (!result.valid) {
662
- result.errors.forEach(error => {
663
- context.report({
664
- node: node.id,
665
- message: `函数表达式名"${function_name}"不符合规范: ${error}`
666
- });
667
- });
668
- }
669
- }
670
- }
671
- };
672
- }
673
- };
674
-
675
- /**
676
- * 方法名小驼峰规则
677
- */
678
- const method_name_rule = {
679
- meta: {
680
- type: 'suggestion',
681
- docs: {
682
- description: '方法名必须使用小驼峰命名法(camelCase)且优先使用单个单词',
683
- category: 'Stylistic Issues',
684
- recommended: true
685
- },
686
- schema: [
687
- {
688
- type: 'object',
689
- properties: {
690
- regex: { type: 'string' },
691
- min: { type: 'number' },
692
- max: { type: 'number' },
693
- message: { type: 'string' },
694
- single_word: { type: 'boolean' },
695
- single_word_len: { type: 'number' },
696
- styles: { type: 'array', items: { type: 'string' } }
697
- },
698
- additionalProperties: false
699
- }
700
- ]
701
- },
702
- create(context) {
703
- const options = context.options[0] || {};
704
- return {
705
- MethodDefinition(node) {
706
- const method_name = node.key.name;
707
-
708
- // 跳过私有方法的检测(私有方法由private-naming规则处理)
709
- if (method_name.startsWith('_')) {
710
- return;
711
- }
712
-
713
- const detector = new Detector({ 'method-name': options });
714
- const result = detector.checkName(method_name, 'method-name');
715
-
716
- if (!result.valid) {
717
- result.errors.forEach(error => {
718
- context.report({
719
- node: node.key,
720
- message: `方法名"${method_name}"不符合规范: ${error}`
721
- });
722
- });
723
- }
724
- }
725
- };
726
- }
727
- };
728
-
729
- /**
730
- * 变量名小写蛇形规则
731
- */
732
- const variable_name_rule = {
733
- meta: {
734
- type: 'suggestion',
735
- docs: {
736
- description: '变量名必须使用小写蛇形命名法(snake_case)',
737
- category: 'Stylistic Issues',
738
- recommended: true
739
- },
740
- schema: [
741
- {
742
- type: 'object',
743
- properties: {
744
- regex: { type: 'string' },
745
- min: { type: 'number' },
746
- max: { type: 'number' },
747
- message: { type: 'string' },
748
- single_word: { type: 'boolean' },
749
- single_word_len: { type: 'number' },
750
- styles: { type: 'array', items: { type: 'string' } }
751
- },
752
- additionalProperties: false
753
- }
754
- ]
755
- },
756
- create(context) {
757
- const options = context.options[0] || {};
758
-
759
- function isRealConstant(node) {
760
- // 真正的常量特征:
761
- // 1. 在模块顶层作用域(不在函数或块内)
762
- // 2. 名称包含大写字母(表示意图作为常量)
763
-
764
- // 检查是否在模块顶层
765
- let scope_node = node;
766
- while (scope_node.parent) {
767
- scope_node = scope_node.parent;
768
- if (scope_node.type === 'FunctionDeclaration' ||
769
- scope_node.type === 'FunctionExpression' ||
770
- scope_node.type === 'ArrowFunctionExpression' ||
771
- scope_node.type === 'BlockStatement') {
772
- return false; // 在函数或块内,不是真正的常量
773
- }
774
- }
775
-
776
- // 检查名称是否包含大写字母(表示意图作为常量)
777
- const name = node.id.name;
778
- return /[A-Z]/.test(name);
779
- }
780
-
781
- return {
782
- VariableDeclarator(node) {
783
- if (node.id.type === 'Identifier') {
784
- // 对于const声明,只对不是真正常量的应用变量命名规则
785
- if (node.parent.kind === 'const' && isRealConstant(node)) {
786
- return; // 这是真正的常量,由常量规则处理
787
- }
788
-
789
- const variable_name = node.id.name;
790
- const detector = new Detector({ 'variable-name': options });
791
- const result = detector.checkName(variable_name, 'variable-name');
792
-
793
- if (!result.valid) {
794
- result.errors.forEach(error => {
795
- context.report({
796
- node: node.id,
797
- message: `变量名"${variable_name}"不符合规范: ${error}`
798
- });
799
- });
800
- }
801
- }
802
- }
803
- };
804
- }
805
- };
806
-
807
- /**
808
- * 常量名大写蛇形规则
809
- */
810
- const constant_name_rule = {
811
- meta: {
812
- type: 'suggestion',
813
- docs: {
814
- description: '常量名必须使用大写蛇形命名法(UPPER_SNAKE_CASE)',
815
- category: 'Stylistic Issues',
816
- recommended: true
817
- },
818
- schema: [
819
- {
820
- type: 'object',
821
- properties: {
822
- regex: { type: 'string' },
823
- min: { type: 'number' },
824
- max: { type: 'number' },
825
- message: { type: 'string' },
826
- single_word: { type: 'boolean' },
827
- single_word_len: { type: 'number' },
828
- styles: { type: 'array', items: { type: 'string' } }
829
- },
830
- additionalProperties: false
831
- }
832
- ]
833
- },
834
- create(context) {
835
- const options = context.options[0] || {};
836
-
837
- function isRealConstant(node) {
838
- // 真正的常量特征:
839
- // 1. 在模块顶层作用域(不在函数或块内)
840
- // 2. 名称包含大写字母(表示意图作为常量)
841
- // 3. 或者名称是常见的常量模式(如全大写)
842
-
843
- // 检查是否在模块顶层
844
- let scope_node = node;
845
- while (scope_node.parent) {
846
- scope_node = scope_node.parent;
847
- if (scope_node.type === 'FunctionDeclaration' ||
848
- scope_node.type === 'FunctionExpression' ||
849
- scope_node.type === 'ArrowFunctionExpression' ||
850
- scope_node.type === 'BlockStatement') {
851
- return false; // 在函数或块内,不是真正的常量
852
- }
853
- }
854
-
855
- // 检查名称是否包含大写字母(表示意图作为常量)
856
- const name = node.id.name;
857
- return /[A-Z]/.test(name);
858
- }
859
-
860
- return {
861
- VariableDeclarator(node) {
862
- if (node.id.type === 'Identifier' && node.parent.kind === 'const') {
863
- // 只对真正的常量应用常量命名规则
864
- if (isRealConstant(node)) {
865
- const constant_name = node.id.name;
866
- const detector = new Detector({ 'constant-name': options });
867
- const result = detector.checkName(constant_name, 'constant-name');
868
-
869
- if (!result.valid) {
870
- result.errors.forEach(error => {
871
- context.report({
872
- node: node.id,
873
- message: `常量名"${constant_name}"不符合规范: ${error}`
874
- });
875
- });
876
- }
877
- }
878
- }
879
- }
880
- };
881
- }
882
- };
883
-
884
- /**
885
- * 入参名小写蛇形规则
886
- */
887
- const param_name_rule = {
888
- meta: {
889
- type: 'suggestion',
890
- docs: {
891
- description: '入参名必须使用小写蛇形命名法(snake_case)',
892
- category: 'Stylistic Issues',
893
- recommended: true
894
- },
895
- schema: [
896
- {
897
- type: 'object',
898
- properties: {
899
- regex: { type: 'string' },
900
- min: { type: 'number' },
901
- max: { type: 'number' },
902
- message: { type: 'string' },
903
- single_word: { type: 'boolean' },
904
- single_word_len: { type: 'number' },
905
- styles: { type: 'array', items: { type: 'string' } }
906
- },
907
- additionalProperties: false
908
- }
909
- ]
910
- },
911
- create(context) {
912
- const options = context.options[0] || {};
913
-
914
- function checkParam(node) {
915
- node.params.forEach(param => {
916
- if (param.type === 'Identifier') {
917
- const param_name = param.name;
918
- const detector = new Detector({ 'param-name': options });
919
- const result = detector.checkName(param_name, 'param-name');
920
-
921
- if (!result.valid) {
922
- result.errors.forEach(error => {
923
- context.report({
924
- node: param,
925
- message: `入参名"${param_name}"不符合规范: ${error}`
926
- });
927
- });
928
- }
929
- }
930
- });
931
- }
932
-
933
- return {
934
- FunctionDeclaration: checkParam,
935
- FunctionExpression: checkParam,
936
- ArrowFunctionExpression: checkParam
937
- };
938
- }
939
- };
940
-
941
- /**
942
- * 属性名小写蛇形规则
943
- */
944
- const property_name_rule = {
945
- meta: {
946
- type: 'suggestion',
947
- docs: {
948
- description: '属性名必须使用小写蛇形命名法(snake_case)',
949
- category: 'Stylistic Issues',
950
- recommended: true
951
- },
952
- schema: [
953
- {
954
- type: 'object',
955
- properties: {
956
- regex: { type: 'string' },
957
- min: { type: 'number' },
958
- max: { type: 'number' },
959
- message: { type: 'string' },
960
- single_word: { type: 'boolean' },
961
- single_word_len: { type: 'number' },
962
- styles: { type: 'array', items: { type: 'string' } }
963
- },
964
- additionalProperties: false
965
- }
966
- ]
967
- },
968
- create(context) {
969
- const options = context.options[0] || {};
970
- return {
971
- Property(node) {
972
- // 检测类属性、对象字面量属性、模块导出属性
973
- const is_class_property = node.parent &&
974
- (node.parent.type === 'ClassBody' ||
975
- (node.parent.type === 'ObjectExpression' &&
976
- node.parent.parent &&
977
- node.parent.parent.type === 'ClassDeclaration'));
978
-
979
- const is_object_property = node.parent && node.parent.type === 'ObjectExpression';
980
-
981
- // 检测所有非私有属性(不以_开头)
982
- if (node.key.type === 'Identifier' && !node.key.name.startsWith('_')) {
983
- const property_name = node.key.name;
984
- const detector = new Detector({ 'property-name': options });
985
- const result = detector._checkPropertyName(property_name, node.value);
986
-
987
- if (!result.valid) {
988
- result.errors.forEach(error => {
989
- context.report({
990
- node: node.key,
991
- message: `属性名"${property_name}"不符合规范: ${error}`
992
- });
993
- });
994
- }
995
- }
996
- }
997
- };
998
- }
999
- };
1000
-
1001
- /**
1002
- * 私有成员命名规则
1003
- */
1004
- const private_naming_rule = {
1005
- meta: {
1006
- type: 'suggestion',
1007
- docs: {
1008
- description: '私有成员必须以单下划线开头,后跟小写蛇形命名',
1009
- category: 'Stylistic Issues',
1010
- recommended: true
1011
- },
1012
- schema: [
1013
- {
1014
- type: 'object',
1015
- properties: {
1016
- regex: { type: 'string' },
1017
- min: { type: 'number' },
1018
- max: { type: 'number' },
1019
- message: { type: 'string' },
1020
- single_word: { type: 'boolean' },
1021
- single_word_len: { type: 'number' },
1022
- styles: { type: 'array', items: { type: 'string' } }
1023
- },
1024
- additionalProperties: false
1025
- }
1026
- ]
1027
- },
1028
- create(context) {
1029
- const options = context.options[0] || {};
1030
- return {
1031
- MethodDefinition(node) {
1032
- const method_name = node.key.name;
1033
- if (method_name.startsWith('_')) {
1034
- const detector = new Detector({ 'private-naming': options });
1035
- const result = detector.checkName(method_name, 'private-naming');
1036
-
1037
- if (!result.valid) {
1038
- result.errors.forEach(error => {
1039
- context.report({
1040
- node: node.key,
1041
- message: `私有方法名"${method_name}"不符合规范: ${error}`
1042
- });
1043
- });
1044
- }
1045
- }
1046
- },
1047
-
1048
- Property(node) {
1049
- if (node.key.type === 'Identifier' && node.key.name.startsWith('_')) {
1050
- const private_name = node.key.name;
1051
- const detector = new Detector({ 'private-naming': options });
1052
- const result = detector.checkName(private_name, 'private-naming');
1053
-
1054
- if (!result.valid) {
1055
- result.errors.forEach(error => {
1056
- context.report({
1057
- node: node.key,
1058
- message: `私有属性名"${private_name}"不符合规范: ${error}`
1059
- });
1060
- });
1061
- }
1062
- }
1063
- }
1064
- };
1065
- }
1066
- };
1067
-
1068
- /**
1069
- * 命名规范ESLint插件
1070
- */
1071
- const naming_rules = {
1072
- 'class-name': class_name_rule,
1073
- 'function-name': function_name_rule,
1074
- 'method-name': method_name_rule,
1075
- 'variable-name': variable_name_rule,
1076
- 'constant-name': constant_name_rule,
1077
- 'private-naming': private_naming_rule,
1078
- 'param-name': param_name_rule,
1079
- 'property-name': property_name_rule
1080
- };
1081
-
1082
- module.exports = {
1083
- Detector,
1084
- rules: naming_rules
1085
- };
1
+ "use strict";
2
+
3
+ /**
4
+ * 命名规范检测器类
5
+ */
6
+ class Detector {
7
+ static config = {
8
+ forbidden_words: [
9
+ "manager",
10
+ "handler",
11
+ "processor",
12
+ "controller",
13
+ "Management",
14
+ "Handler",
15
+ "Processor",
16
+ "Controller",
17
+ "LoggerManager",
18
+ "AppState",
19
+ "UserHandler",
20
+ ],
21
+ regex: {
22
+ PascalCase: /^[A-Z][a-zA-Z]*$/, // 大驼峰:大写字母开头,只包含字母。主要用于类名
23
+ camelCase: /^[a-z][a-zA-Z]*$/, // 小驼峰:小写字母开头,只包含字母。主要用于方法名、函数名
24
+ snake_case: /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写蛇形:小写字母开头,可包含下划线。主要用于变量名、入参名
25
+ UPPER_SNAKE_CASE: /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/, // 大写蛇形:大写字母开头,可包含下划线。主要用于常量名
26
+ "kebab-case": /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 小写横杠:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
27
+ "UPPER-KEBAB-CASE": /^[A-Z][A-Z0-9-]*(-[A-Z0-9]+)*$/, // 大写横杠:大写字母开头,可包含短横线,大写短横线。主要用于资源名、路由路径
28
+ lowercase: /^[a-z][a-z0-9]*$/, // 小写单词:小写字母开头,只包含小写字母和数字。主要用于对象、属性名
29
+ UPPERCASE: /^[A-Z][A-Z0-9]*$/, // 大写单词:大写字母开头,只包含大写字母和数字。主要用于枚举值、常量名
30
+ "camelCase-kebab": /^[a-z][a-z0-9-]*(-[a-z0-9]+)*$/, // 支持横杠小驼峰:小写字母开头,可包含短横线,小写短横线。主要用于资源名、路由路径
31
+ "PascalCase-kebab": /^[A-Z][a-zA-Z]*$/, // 支持横杠大驼峰:大写字母开头,只包含字母。主要用于资源名、路由路径
32
+ _camelCase: /^_[a-z][a-zA-Z]*$/, // 下划线小驼峰:下划线加小写字母开头,只包含字母。主要用于私有方法名
33
+ _snake_case: /^_[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 下划线小写蛇形:下划线加小写字母开头,可包含下划线。主要用于私有变量名
34
+ _lowercase: /^_[a-z][a-z0-9]*$/, // 下划线小写单词:下划线加小写字母开头,只包含小写字母和数字。主要用于对象、属性名
35
+ },
36
+
37
+ // 类名大驼峰规则
38
+ "class-name": {
39
+ name: "类名",
40
+ message:
41
+ "必须使用大驼峰命名法(PascalCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
42
+ regex: null, // 使用styles属性进行验证
43
+ min: 1,
44
+ max: 20,
45
+ styles: ["PascalCase"],
46
+ single_word: true,
47
+ single_word_len: 8,
48
+ },
49
+
50
+ // 函数名小驼峰规则
51
+ "function-name": {
52
+ name: "函数名",
53
+ message:
54
+ "必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
55
+ regex: null, // 使用styles属性进行验证
56
+ min: 1,
57
+ max: 20,
58
+ styles: ["camelCase"],
59
+ single_word: true,
60
+ single_word_len: 8,
61
+ },
62
+
63
+ // 方法名小驼峰规则
64
+ "method-name": {
65
+ name: "方法名",
66
+ message:
67
+ "必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
68
+ regex: null, // 使用styles属性进行验证
69
+ min: 1,
70
+ max: 20,
71
+ styles: ["camelCase"],
72
+ single_word: true,
73
+ single_word_len: 8,
74
+ },
75
+
76
+ // 入参名小写蛇形规则
77
+ "param-name": {
78
+ name: "入参名",
79
+ message:
80
+ "必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
81
+ regex: null, // 使用styles属性进行验证
82
+ min: 1,
83
+ max: 20,
84
+ styles: ["lowercase", "snake_case"],
85
+ single_word: true,
86
+ single_word_len: 8,
87
+ },
88
+
89
+ // 变量名小写蛇形规则
90
+ "variable-name": {
91
+ name: "变量名",
92
+ message:
93
+ "必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符",
94
+ regex: null, // 使用styles属性进行验证
95
+ min: 1,
96
+ max: 20,
97
+ styles: ["lowercase", "snake_case"],
98
+ single_word: true,
99
+ single_word_len: 8,
100
+ },
101
+
102
+ // 常量名大写蛇形规则
103
+ "constant-name": {
104
+ name: "常量名",
105
+ message:
106
+ "必须使用大写蛇形命名法(UPPER_SNAKE_CASE),长度{min}-{max}字符",
107
+ regex: null, // 使用styles属性进行验证
108
+ min: 1,
109
+ max: 20,
110
+ styles: ["UPPERCASE", "UPPER_SNAKE_CASE"],
111
+ },
112
+
113
+ // 属性名多风格规则(根据值类型应用不同规则)
114
+ "property-name": {
115
+ name: "属性名",
116
+ message: "根据属性值类型应用不同命名规则",
117
+ min: 1,
118
+ max: 20,
119
+ single_word: true,
120
+ single_word_len: 8,
121
+ styles: [
122
+ "lowercase",
123
+ "_lowercase",
124
+ "snake_case",
125
+ "_snake_case",
126
+ "UPPERCASE",
127
+ "UPPER_SNAKE_CASE",
128
+ "PascalCase",
129
+ ],
130
+ strong: true, // 强验证模式:根据属性值类型智能判断;false:弱验证模式:只需符合变量名或常量名规则
131
+ },
132
+
133
+ // 私有方法规则
134
+ "private-method-naming": {
135
+ name: "私有方法",
136
+ message:
137
+ "必须以单下划线开头,后跟小驼峰命名或小写单词,长度{min}-{max}字符",
138
+ regex: null, // 使用styles属性进行验证
139
+ min: 2,
140
+ max: 20,
141
+ styles: ["_lowercase", "_camelCase"],
142
+ },
143
+
144
+ // 私有变量规则
145
+ "private-variable-naming": {
146
+ name: "私有变量",
147
+ message:
148
+ "必须以单下划线开头,后跟小写蛇形命名或小写单词,长度{min}-{max}字符",
149
+ regex: null, // 使用styles属性进行验证
150
+ min: 2,
151
+ max: 20,
152
+ styles: ["_lowercase", "_snake_case"],
153
+ },
154
+ };
155
+
156
+ constructor(config) {
157
+ // 合并默认配置和传入配置
158
+ const merged_cfg = { ...Detector.config };
159
+ if (config && typeof config === "object") {
160
+ Object.keys(config).forEach((key) => {
161
+ if (merged_cfg[key]) {
162
+ merged_cfg[key] = { ...merged_cfg[key], ...config[key] };
163
+
164
+ // 处理字符串形式的正则表达式
165
+ if (typeof merged_cfg[key].regex === "string") {
166
+ try {
167
+ // 从字符串中提取正则表达式模式和标志
168
+ const regex_match =
169
+ merged_cfg[key].regex.match(/^\/(.*)\/([gimuy]*)$/);
170
+ if (regex_match) {
171
+ merged_cfg[key].regex = new RegExp(
172
+ regex_match[1],
173
+ regex_match[2],
174
+ );
175
+ } else {
176
+ // 如果不是标准的正则表达式字符串格式,直接创建
177
+ merged_cfg[key].regex = new RegExp(merged_cfg[key].regex);
178
+ }
179
+ } catch (error) {
180
+ console.warn(`无效的正则表达式: ${merged_cfg[key].regex}`, error);
181
+ merged_cfg[key].regex = null;
182
+ }
183
+ }
184
+ }
185
+ });
186
+ }
187
+ this.config = merged_cfg;
188
+ this._initRule();
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 创建新的规则模型
194
+ * @returns {Object} 规则模型
195
+ * @private
196
+ */
197
+ Detector.prototype._newModel = function () {
198
+ return {
199
+ name: "",
200
+ message: "",
201
+ regex: null,
202
+ min: 1,
203
+ max: 20,
204
+ };
205
+ };
206
+
207
+ /**
208
+ * 初始化规则检查方法
209
+ * @private
210
+ */
211
+ Detector.prototype._initRule = function () {
212
+ // 绑定规则检查方法
213
+ this._rules = {
214
+ "class-name": this._checkName.bind(this, "class-name"),
215
+ "function-name": this._checkName.bind(this, "function-name"),
216
+ "method-name": this._checkName.bind(this, "method-name"),
217
+ "param-name": this._checkName.bind(this, "param-name"),
218
+ "variable-name": this._checkName.bind(this, "variable-name"),
219
+ "constant-name": this._checkName.bind(this, "constant-name"),
220
+ "property-name": this._checkPropName.bind(this),
221
+ "private-method-naming": this._checkName.bind(
222
+ this,
223
+ "private-method-naming",
224
+ ),
225
+ "private-variable-naming": this._checkName.bind(
226
+ this,
227
+ "private-variable-naming",
228
+ ),
229
+ };
230
+ };
231
+
232
+ /**
233
+ * 通用命名检查方法
234
+ * @param {string} rule_type - 规则类型
235
+ * @param {string} name - 名称
236
+ * @returns {Object} 检查结果
237
+ * @private
238
+ */
239
+ Detector.prototype._checkName = function (rule_type, name) {
240
+ if (!name) {
241
+ throw new TypeError("名称不能为空");
242
+ }
243
+
244
+ const config = this.config[rule_type];
245
+ if (!config) {
246
+ throw new Error(`不支持的规则类型: ${rule_type}`);
247
+ }
248
+
249
+ const errors = [];
250
+ const warnings = [];
251
+
252
+ this._checkNameLength(name, config, errors);
253
+ this._checkNameFormat(name, rule_type, config, errors);
254
+ this._checkBadWords(name, config, errors);
255
+ this._checkSingleWord(name, rule_type, config, warnings);
256
+ this._checkWordLength(name, rule_type, config, errors);
257
+
258
+ return {
259
+ valid: errors.length === 0,
260
+ errors: errors,
261
+ warnings: warnings,
262
+ };
263
+ };
264
+
265
+ /**
266
+ * 检查名称长度限制
267
+ * @private
268
+ */
269
+ Detector.prototype._checkNameLength = function (name, config, errors) {
270
+ if (name.length < config.min) {
271
+ errors.push(`${config.name}长度不能少于${config.min}个字符`);
272
+ }
273
+ if (name.length > config.max) {
274
+ errors.push(`${config.name}长度不能超过${config.max}个字符`);
275
+ }
276
+ };
277
+
278
+ /**
279
+ * 检查名称格式
280
+ * @private
281
+ */
282
+ Detector.prototype._checkNameFormat = function (
283
+ name,
284
+ rule_type,
285
+ config,
286
+ errors,
287
+ ) {
288
+ let name_valid = false;
289
+
290
+ if (config.regex && config.regex.toString() !== "/(?:)/") {
291
+ name_valid = config.regex.test(name);
292
+ if (!name_valid) {
293
+ const error_message = config.message
294
+ .replace("{name}", `"${name}"`)
295
+ .replace("{type}", config.name)
296
+ .replace("{min}", config.min)
297
+ .replace("{max}", config.max)
298
+ .replace("{word_len}", config.single_word_len || 8)
299
+ .replace("{regex}", config.regex.toString());
300
+ errors.push(error_message);
301
+ }
302
+ } else if (config.styles && config.styles.length > 0) {
303
+ name_valid = this._checkStyle(name, config.styles);
304
+ if (!name_valid) {
305
+ const allowed_styles = config.styles.join("、");
306
+ errors.push(`${config.name}必须符合以下命名风格之一:${allowed_styles}`);
307
+ }
308
+ } else {
309
+ name_valid = true;
310
+ }
311
+ };
312
+
313
+ /**
314
+ * 检查禁止词汇
315
+ * @private
316
+ */
317
+ Detector.prototype._checkBadWords = function (name, config, errors) {
318
+ if (this._hasBadWord(name)) {
319
+ errors.push(`${config.name}包含禁止的废话词`);
320
+ }
321
+ };
322
+
323
+ /**
324
+ * 检查是否优先使用单个单词
325
+ * @private
326
+ */
327
+ Detector.prototype._checkSingleWord = function (
328
+ name,
329
+ rule_type,
330
+ config,
331
+ warnings,
332
+ ) {
333
+ if (!config.single_word) return;
334
+
335
+ const has_uppercase = /[A-Z]/.test(name.slice(1));
336
+ const has_separators = name.includes("_") || name.includes("-");
337
+ const is_func_or_method =
338
+ rule_type === "function-name" || rule_type === "method-name";
339
+ let is_multi_word = has_uppercase || has_separators;
340
+
341
+ if (is_func_or_method && is_multi_word) {
342
+ const prefix_match = name.match(
343
+ /^(add|del|set|get|is|has|can|not)([A-Z][a-z]*)$/,
344
+ );
345
+ if (prefix_match && prefix_match[2]) {
346
+ is_multi_word = false;
347
+ }
348
+ }
349
+
350
+ if (is_multi_word) {
351
+ warnings.push(`${config.name}应优先使用单个单词`);
352
+ }
353
+ };
354
+
355
+ /**
356
+ * 检查单词长度限制
357
+ * @private
358
+ */
359
+ Detector.prototype._checkWordLength = function (
360
+ name,
361
+ rule_type,
362
+ config,
363
+ errors,
364
+ ) {
365
+ if (!config.single_word_len || config.single_word_len <= 0) return;
366
+
367
+ const words = this._splitWords(name, rule_type);
368
+ const long_words = words.filter(
369
+ (word) => word.length > config.single_word_len,
370
+ );
371
+
372
+ if (long_words.length > 0) {
373
+ errors.push(
374
+ `${config.name}中的单词"${long_words.join("、")}"超过${config.single_word_len}个字符限制`,
375
+ );
376
+ }
377
+ };
378
+
379
+ /**
380
+ * 根据styles列表检查名称是否符合命名风格
381
+ * @param {string} name - 名称
382
+ * @param {Array} styles - 命名风格列表
383
+ * @returns {boolean} 是否符合任一风格
384
+ * @private
385
+ */
386
+ Detector.prototype._checkStyle = function (name, styles) {
387
+ if (!name || !styles || styles.length === 0) {
388
+ return false;
389
+ }
390
+
391
+ // 检查名称是否符合任一指定的命名风格
392
+ for (const style of styles) {
393
+ const regex = this.config.regex[style];
394
+ if (regex && regex.test(name)) {
395
+ return true;
396
+ }
397
+ }
398
+
399
+ return false;
400
+ };
401
+
402
+ /**
403
+ * 拆分名称中的单词
404
+ * @param {string} name - 名称
405
+ * @param {string} rule_type - 规则类型
406
+ * @returns {Array} 拆分后的单词数组
407
+ * @private
408
+ */
409
+ Detector.prototype._splitWords = function (name, rule_type) {
410
+ if (!name) {
411
+ return [];
412
+ }
413
+
414
+ const words = [];
415
+
416
+ // 根据命名风格拆分单词
417
+ if (name.includes("_")) {
418
+ // 蛇形命名:user_name → ["user", "name"]
419
+ words.push(...name.split("_").filter((word) => word.length > 0));
420
+ } else if (name.includes("-")) {
421
+ // 横杠命名:user-name → ["user", "name"]
422
+ words.push(...name.split("-").filter((word) => word.length > 0));
423
+ } else {
424
+ // 检查是否是纯小写单词(不包含大写字母)
425
+ if (!/[A-Z]/.test(name)) {
426
+ // 纯小写单词:configuration ["configuration"]
427
+ words.push(name);
428
+ } else {
429
+ // 驼峰命名:userName → ["user", "Name"]
430
+ // 使用正则表达式拆分驼峰命名
431
+ const camel_case_words = name.split(/(?=[A-Z])/);
432
+
433
+ // 对于函数名和方法名,处理常见前缀
434
+ if (rule_type === "function-name" || rule_type === "method-name") {
435
+ const prefix_match = name.match(
436
+ /^(add|del|set|get|is|has|can|not)([A-Z].*)$/,
437
+ );
438
+ if (prefix_match) {
439
+ // 如果是前缀+单词的组合,将前缀作为单独单词
440
+ words.push(prefix_match[1]);
441
+ // 拆分剩余部分
442
+ const remaining_words = prefix_match[2].split(/(?=[A-Z])/);
443
+ words.push(...remaining_words);
444
+ } else {
445
+ words.push(...camel_case_words);
446
+ }
447
+ } else {
448
+ words.push(...camel_case_words);
449
+ }
450
+ }
451
+ }
452
+
453
+ // 过滤空字符串并转换为小写进行比较
454
+ return words
455
+ .filter((word) => word.length > 0)
456
+ .map((word) => word.toLowerCase());
457
+ };
458
+
459
+ /**
460
+ * 属性名检查方法(根据属性值类型应用不同规则)
461
+ * @param {string} name - 属性名
462
+ * @param {Object} val_node - 属性值AST节点
463
+ * @returns {Object} 检查结果
464
+ * @private
465
+ */
466
+ Detector.prototype._checkPropName = function (name, val_node) {
467
+ if (!name) {
468
+ throw new TypeError("属性名不能为空");
469
+ }
470
+
471
+ const config = this.config["property-name"];
472
+ if (!config) {
473
+ throw new Error("属性名规则配置不存在");
474
+ }
475
+
476
+ const errors = [];
477
+ const warnings = [];
478
+
479
+ this._checkPropLength(name, config, errors);
480
+ this._checkBadWords(name, config, errors);
481
+
482
+ const use_strong_val = config.strong !== false;
483
+ const val_type = this._getPropType(val_node, name);
484
+
485
+ if (use_strong_val) {
486
+ this._checkPropStrong(name, val_type, config, errors);
487
+ } else {
488
+ this._checkPropWeak(name, val_type, config, errors);
489
+ }
490
+
491
+ this._checkPropSingleWord(name, config, warnings);
492
+
493
+ return {
494
+ valid: errors.length === 0,
495
+ errors: errors,
496
+ warnings: warnings,
497
+ value_type: val_type,
498
+ };
499
+ };
500
+
501
+ /**
502
+ * 检查属性名长度
503
+ * @private
504
+ */
505
+ Detector.prototype._checkPropLength = function (name, config, errors) {
506
+ if (name.length < config.min) {
507
+ errors.push(`${config.name}长度不能少于${config.min}个字符`);
508
+ }
509
+ if (name.length > config.max) {
510
+ errors.push(`${config.name}长度不能超过${config.max}个字符`);
511
+ }
512
+ };
513
+
514
+ /**
515
+ * 强验证模式检查
516
+ * @private
517
+ */
518
+ Detector.prototype._checkPropStrong = function (
519
+ name,
520
+ val_type,
521
+ config,
522
+ errors,
523
+ ) {
524
+ switch (val_type) {
525
+ case "function":
526
+ this._checkFuncProp(name, config, errors);
527
+ break;
528
+ case "class":
529
+ this._checkClassProp(name, config, errors);
530
+ break;
531
+ case "constant":
532
+ this._checkConstProp(name, config, errors);
533
+ break;
534
+ case "value":
535
+ default:
536
+ this._checkValueProp(name, config, errors);
537
+ break;
538
+ }
539
+ };
540
+
541
+ /**
542
+ * 弱验证模式检查
543
+ * @private
544
+ */
545
+ Detector.prototype._checkPropWeak = function (name, val_type, config, errors) {
546
+ switch (val_type) {
547
+ case "function":
548
+ this._checkFuncProp(name, config, errors);
549
+ break;
550
+ case "class":
551
+ this._checkClassProp(name, config, errors);
552
+ break;
553
+ case "constant":
554
+ case "value":
555
+ default:
556
+ this._checkWeakValue(name, config, errors);
557
+ break;
558
+ }
559
+ };
560
+
561
+ /**
562
+ * 检查函数属性
563
+ * @private
564
+ */
565
+ Detector.prototype._checkFuncProp = function (name, config, errors) {
566
+ const func_res = this._checkName("function-name", name);
567
+ if (!func_res.valid) {
568
+ errors.push(`${config.name}(函数)${func_res.errors.join(", ")}`);
569
+ }
570
+ };
571
+
572
+ /**
573
+ * 检查类属性
574
+ * @private
575
+ */
576
+ Detector.prototype._checkClassProp = function (name, config, errors) {
577
+ const cls_res = this._checkName("class-name", name);
578
+ if (!cls_res.valid) {
579
+ errors.push(`${config.name}(类)${cls_res.errors.join(", ")}`);
580
+ }
581
+ };
582
+
583
+ /**
584
+ * 检查常量属性
585
+ * @private
586
+ */
587
+ Detector.prototype._checkConstProp = function (name, config, errors) {
588
+ const is_valid_const = this._checkStyle(name, [
589
+ "UPPERCASE",
590
+ "UPPER_SNAKE_CASE",
591
+ ]);
592
+ if (!is_valid_const) {
593
+ this._suggestConstName(name, config, errors);
594
+ }
595
+ };
596
+
597
+ /**
598
+ * 建议常量命名
599
+ * @private
600
+ */
601
+ Detector.prototype._suggestConstName = function (name, config, errors) {
602
+ if (/^[a-z][a-zA-Z0-9]*$/.test(name)) {
603
+ const sug_name = name.replace(/([A-Z])/g, "_$1").toUpperCase();
604
+ errors.push(
605
+ `${config.name}(常量)必须使用大写命名风格,建议改为:${sug_name}`,
606
+ );
607
+ } else if (/^[a-z][a-z0-9_]*$/.test(name)) {
608
+ const sug_name = name.toUpperCase();
609
+ errors.push(
610
+ `${config.name}(常量)必须使用大写命名风格,建议改为:${sug_name}`,
611
+ );
612
+ } else {
613
+ errors.push(
614
+ `${config.name}(常量)必须使用大写命名风格(如API或API_BASE_URL)`,
615
+ );
616
+ }
617
+ };
618
+
619
+ /**
620
+ * 检查值属性
621
+ * @private
622
+ */
623
+ Detector.prototype._checkValueProp = function (name, config, errors) {
624
+ const is_upper_snake = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)+$/.test(name);
625
+ const is_upper_multi = /^[A-Z][A-Z0-9]*[A-Z][A-Z0-9]*$/.test(name);
626
+
627
+ if (is_upper_snake || is_upper_multi) {
628
+ errors.push(
629
+ `${config.name}(值)禁止使用大写蛇形命名(如PRODUCT_ID或PRODUCTID)`,
630
+ );
631
+ }
632
+
633
+ if (config.styles && config.styles.length > 0) {
634
+ const is_valid_style = this._checkStyle(name, config.styles);
635
+ if (!is_valid_style && !is_upper_snake && !is_upper_multi) {
636
+ this._suggestValueName(name, config, errors);
637
+ }
638
+ }
639
+ };
640
+
641
+ /**
642
+ * 建议值属性命名
643
+ * @private
644
+ */
645
+ Detector.prototype._suggestValueName = function (name, config, errors) {
646
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
647
+ const sug_name = name
648
+ .replace(/([A-Z])/g, "_$1")
649
+ .toLowerCase()
650
+ .replace(/^_/, "");
651
+ errors.push(
652
+ `${config.name}(值)必须使用小写命名风格,建议改为:${sug_name}`,
653
+ );
654
+ } else if (/^[a-z][a-zA-Z0-9]*$/.test(name)) {
655
+ const sug_name = name.replace(/([A-Z])/g, "_$1").toLowerCase();
656
+ errors.push(
657
+ `${config.name}(值)必须使用小写命名风格,建议改为:${sug_name}`,
658
+ );
659
+ } else {
660
+ const allowed_styles = config.styles.join("、");
661
+ errors.push(
662
+ `${config.name}(值)必须符合以下命名风格之一:${allowed_styles}`,
663
+ );
664
+ }
665
+ };
666
+
667
+ /**
668
+ * 弱验证模式值检查
669
+ * @private
670
+ */
671
+ Detector.prototype._checkWeakValue = function (name, config, errors) {
672
+ const is_valid_var = this._checkStyle(name, ["lowercase", "snake_case"]);
673
+ const is_valid_const = this._checkStyle(name, [
674
+ "UPPERCASE",
675
+ "UPPER_SNAKE_CASE",
676
+ ]);
677
+
678
+ if (!is_valid_var && !is_valid_const) {
679
+ errors.push(
680
+ `${config.name}(值)必须符合变量名(小写蛇形)或常量名(大写蛇形)规则`,
681
+ );
682
+ }
683
+ };
684
+
685
+ /**
686
+ * 检查属性名是否优先使用单个单词
687
+ * @private
688
+ */
689
+ Detector.prototype._checkPropSingleWord = function (name, config, warnings) {
690
+ if (!config.single_word) return;
691
+
692
+ const has_uppercase = /[A-Z]/.test(name.slice(1));
693
+ const has_separators = name.includes("_") || name.includes("-");
694
+
695
+ if (has_uppercase || has_separators) {
696
+ warnings.push(`${config.name}应优先使用单个单词`);
697
+ }
698
+ };
699
+
700
+ /**
701
+ * 获取属性值类型
702
+ * @param {Object} val_node - 属性值AST节点
703
+ * @returns {string} 属性值类型('function', 'class', 'value')
704
+ * @private
705
+ */
706
+ Detector.prototype._getPropType = function (val_node, prop_name) {
707
+ if (!val_node) {
708
+ return "value"; // 默认值类型
709
+ }
710
+
711
+ // 检查是否为函数
712
+ if (
713
+ val_node.type === "FunctionExpression" ||
714
+ val_node.type === "ArrowFunctionExpression" ||
715
+ (val_node.type === "Identifier" &&
716
+ val_node.name &&
717
+ (val_node.name.endsWith("Function") ||
718
+ val_node.name.endsWith("Func") ||
719
+ val_node.name.startsWith("on") ||
720
+ val_node.name.startsWith("handle")))
721
+ ) {
722
+ return "function";
723
+ }
724
+
725
+ // 检查是否为类
726
+ if (
727
+ val_node.type === "ClassExpression" ||
728
+ (val_node.type === "Identifier" &&
729
+ val_node.name &&
730
+ /^[A-Z][a-zA-Z]*$/.test(val_node.name))
731
+ ) {
732
+ return "class";
733
+ }
734
+
735
+ // 检查是否为常量/配置值(应该使用UPPERCASE风格)
736
+ if (this._isConst(val_node, prop_name)) {
737
+ return "constant";
738
+ }
739
+
740
+ // 默认为值类型
741
+ return "value";
742
+ };
743
+
744
+ /**
745
+ * 检查是否为常量值(应该使用UPPERCASE风格命名)
746
+ * @param {Object} val_node - 属性值AST节点
747
+ * @param {string} prop_name - 属性名(可选,用于更智能的判断)
748
+ * @returns {boolean} 是否为常量值
749
+ * @private
750
+ */
751
+ Detector.prototype._isConst = function (val_node, prop_name) {
752
+ if (!val_node) {
753
+ return false;
754
+ }
755
+
756
+ const is_prim_const = this._isPrimConst(val_node);
757
+ const is_cfg_obj = this._isCfgObj(val_node);
758
+ const is_const_id = this._isConstId(val_node);
759
+
760
+ if (prop_name) {
761
+ return this._isConstWithName(
762
+ val_node,
763
+ prop_name,
764
+ is_prim_const,
765
+ is_cfg_obj,
766
+ is_const_id,
767
+ );
768
+ }
769
+
770
+ return this._isConstWithoutName(is_prim_const, is_cfg_obj, is_const_id);
771
+ };
772
+
773
+ /**
774
+ * 检查是否为基本类型常量
775
+ * @private
776
+ */
777
+ Detector.prototype._isPrimConst = function (val_node) {
778
+ return (
779
+ val_node.type === "Literal" &&
780
+ (typeof val_node.value === "string" ||
781
+ typeof val_node.value === "number" ||
782
+ typeof val_node.value === "boolean")
783
+ );
784
+ };
785
+
786
+ /**
787
+ * 检查是否为配置对象
788
+ * @private
789
+ */
790
+ Detector.prototype._isCfgObj = function (val_node) {
791
+ return (
792
+ val_node.type === "ObjectExpression" &&
793
+ val_node.properties &&
794
+ val_node.properties.length > 0 &&
795
+ val_node.properties.every(
796
+ (prop) =>
797
+ prop.value &&
798
+ (prop.value.type === "Literal" ||
799
+ (prop.value.type === "Identifier" &&
800
+ /^[A-Z_][A-Z0-9_]*$/.test(prop.value.name))),
801
+ )
802
+ );
803
+ };
804
+
805
+ /**
806
+ * 检查是否为常量标识符
807
+ * @private
808
+ */
809
+ Detector.prototype._isConstId = function (val_node) {
810
+ return (
811
+ val_node.type === "Identifier" && /^[A-Z_][A-Z0-9_]*$/.test(val_node.name)
812
+ );
813
+ };
814
+
815
+ /**
816
+ * 有属性名时的常量判断
817
+ * @private
818
+ */
819
+ Detector.prototype._isConstWithName = function (
820
+ val_node,
821
+ prop_name,
822
+ is_prim_const,
823
+ is_cfg_obj,
824
+ is_const_id,
825
+ ) {
826
+ const is_upper_name = /^[A-Z_][A-Z0-9_]*$/.test(prop_name);
827
+ const is_inst_data_name =
828
+ /^[a-z][a-z0-9_]*$/.test(prop_name) ||
829
+ /^[a-z][a-zA-Z0-9]*$/.test(prop_name);
830
+ const is_val_const = this._isValConst(val_node, prop_name);
831
+
832
+ if (is_inst_data_name && is_prim_const) {
833
+ return is_val_const ? true : false;
834
+ }
835
+
836
+ if (is_upper_name && (is_prim_const || is_cfg_obj)) {
837
+ return true;
838
+ }
839
+
840
+ return is_cfg_obj || is_const_id;
841
+ };
842
+
843
+ /**
844
+ * 无属性名时的常量判断
845
+ * @private
846
+ */
847
+ Detector.prototype._isConstWithoutName = function (
848
+ is_prim_const,
849
+ is_cfg_obj,
850
+ is_const_id,
851
+ ) {
852
+ if (is_cfg_obj || is_const_id) {
853
+ return true;
854
+ }
855
+ return is_prim_const;
856
+ };
857
+
858
+ /**
859
+ * 通过值内容分析判断是否为常量
860
+ * @param {Object} val_node - 属性值AST节点
861
+ * @param {string} prop_name - 属性名
862
+ * @returns {boolean} 值内容是否为常量特征
863
+ * @private
864
+ */
865
+ Detector.prototype._isValConst = function (val_node, prop_name) {
866
+ if (!val_node || val_node.type !== "Literal") {
867
+ return false;
868
+ }
869
+
870
+ const value = val_node.value;
871
+
872
+ switch (typeof value) {
873
+ case "string":
874
+ return this._isStringConst(value, prop_name);
875
+ case "number":
876
+ return this._isNumberConst(value, prop_name);
877
+ case "boolean":
878
+ return this._isBooleanConst(value, prop_name);
879
+ default:
880
+ return this._isConstSem(prop_name, value);
881
+ }
882
+ };
883
+
884
+ /**
885
+ * 检查字符串值是否为常量
886
+ * @private
887
+ */
888
+ Detector.prototype._isStringConst = function (value, prop_name) {
889
+ const is_url_const = this._isUrlConst(value, prop_name);
890
+ if (is_url_const) return true;
891
+
892
+ const is_config_const = this._isConfigConst(value, prop_name);
893
+ if (is_config_const) return true;
894
+
895
+ return false;
896
+ };
897
+
898
+ /**
899
+ * 检查URL相关的字符串常量
900
+ * @private
901
+ */
902
+ Detector.prototype._isUrlConst = function (value, prop_name) {
903
+ const is_url_related_name =
904
+ /(url|uri|endpoint|api|base|host|domain|path)$/i.test(prop_name);
905
+ const is_url_value =
906
+ /^(https?:\/\/|\/\/|www\.|ftp:\/\/)/.test(value) ||
907
+ /\.[a-z]{2,6}(\/|$)/.test(value);
908
+
909
+ return is_url_related_name && is_url_value;
910
+ };
911
+
912
+ /**
913
+ * 检查配置相关的字符串常量
914
+ * @private
915
+ */
916
+ Detector.prototype._isConfigConst = function (value, prop_name) {
917
+ const is_config_related_name =
918
+ /(config|setting|option|default|mode|env|version)$/i.test(prop_name);
919
+ const is_config_value =
920
+ /^(v?\d+\.\d+|true|false|null|undefined|production|development|test)$/i.test(
921
+ value,
922
+ );
923
+
924
+ return is_config_related_name && is_config_value;
925
+ };
926
+
927
+ /**
928
+ * 检查数字值是否为常量
929
+ * @private
930
+ */
931
+ Detector.prototype._isNumberConst = function (value, prop_name) {
932
+ const is_config_num = this._isConfigNumber(value, prop_name);
933
+ if (is_config_num) return true;
934
+
935
+ const is_common_const = this._isCommonNumber(value);
936
+ if (is_common_const) return true;
937
+
938
+ return false;
939
+ };
940
+
941
+ /**
942
+ * 检查配置相关的数字常量
943
+ * @private
944
+ */
945
+ Detector.prototype._isConfigNumber = function (value, prop_name) {
946
+ const is_config_number_name =
947
+ /(timeout|delay|interval|retry|count|limit|max|min|size|length|port)$/i.test(
948
+ prop_name,
949
+ );
950
+ return is_config_number_name;
951
+ };
952
+
953
+ /**
954
+ * 检查常见数字常量
955
+ * @private
956
+ */
957
+ Detector.prototype._isCommonNumber = function (value) {
958
+ const common_constants = [
959
+ 0, 1, 10, 100, 1000, 60, 3600, 86400, 1024, 2048, 4096, 8192,
960
+ ];
961
+ return common_constants.includes(value);
962
+ };
963
+
964
+ /**
965
+ * 检查布尔值是否为常量
966
+ * @private
967
+ */
968
+ Detector.prototype._isBooleanConst = function (value, prop_name) {
969
+ const is_config_bool = this._isConfigBoolean(prop_name);
970
+ const is_inst_state = this._isInstanceState(prop_name);
971
+
972
+ if (is_config_bool && !is_inst_state) {
973
+ return true;
974
+ }
975
+
976
+ if (is_inst_state) {
977
+ return false;
978
+ }
979
+
980
+ return false;
981
+ };
982
+
983
+ /**
984
+ * 检查配置相关的布尔值
985
+ * @private
986
+ */
987
+ Detector.prototype._isConfigBoolean = function (prop_name) {
988
+ return /^(is_)?(enabled|disabled|debug|verbose|strict|lazy|secure|ssl|tls|compressed|cached)$/i.test(
989
+ prop_name,
990
+ );
991
+ };
992
+
993
+ /**
994
+ * 检查实例数据状态的布尔值
995
+ * @private
996
+ */
997
+ Detector.prototype._isInstanceState = function (prop_name) {
998
+ return /^(is_)?(active|inactive|visible|hidden|required|optional|valid|invalid|available|busy|online|offline|open|closed|locked|unlocked)$/i.test(
999
+ prop_name,
1000
+ );
1001
+ };
1002
+
1003
+ /**
1004
+ * 通过属性名语义分析判断是否为常量
1005
+ * @param {string} prop_name - 属性名
1006
+ * @param {any} value - 属性值
1007
+ * @returns {boolean} 根据语义分析是否为常量
1008
+ * @private
1009
+ */
1010
+ Detector.prototype._isConstSem = function (prop_name, value) {
1011
+ // 精确匹配常量相关词汇(使用单词边界匹配)
1012
+ const const_keys = [
1013
+ "api",
1014
+ "base",
1015
+ "url",
1016
+ "uri",
1017
+ "endpoint",
1018
+ "host",
1019
+ "domain",
1020
+ "timeout",
1021
+ "delay",
1022
+ "interval",
1023
+ "retry",
1024
+ "count",
1025
+ "limit",
1026
+ "max",
1027
+ "min",
1028
+ "size",
1029
+ "length",
1030
+ "port",
1031
+ "version",
1032
+ "config",
1033
+ "setting",
1034
+ "option",
1035
+ "default",
1036
+ "mode",
1037
+ "env",
1038
+ "debug",
1039
+ "verbose",
1040
+ "strict",
1041
+ "lazy",
1042
+ "secure",
1043
+ "ssl",
1044
+ "tls",
1045
+ "compressed",
1046
+ "cached",
1047
+ ];
1048
+
1049
+ // 使用单词边界匹配,避免误匹配(如"active"匹配"is_active")
1050
+ const has_const_key = const_keys.some((keyword) => {
1051
+ const regex = new RegExp(`\\b${keyword}\\b`, "i");
1052
+ return regex.test(prop_name);
1053
+ });
1054
+
1055
+ if (has_const_key) {
1056
+ // 如果属性名包含常量相关词汇,且值是基本类型,更可能是常量
1057
+ return (
1058
+ typeof value === "string" ||
1059
+ typeof value === "number" ||
1060
+ typeof value === "boolean"
1061
+ );
1062
+ }
1063
+
1064
+ // 实例数据相关词汇(更精确的匹配)
1065
+ const inst_keys = [
1066
+ "user",
1067
+ "person",
1068
+ "customer",
1069
+ "member",
1070
+ "client",
1071
+ "product",
1072
+ "item",
1073
+ "goods",
1074
+ "article",
1075
+ "order",
1076
+ "cart",
1077
+ "payment",
1078
+ "invoice",
1079
+ "name",
1080
+ "age",
1081
+ "email",
1082
+ "phone",
1083
+ "address",
1084
+ "title",
1085
+ "description",
1086
+ "content",
1087
+ "message",
1088
+ "active",
1089
+ "inactive",
1090
+ "visible",
1091
+ "hidden",
1092
+ "required",
1093
+ "optional",
1094
+ "valid",
1095
+ "invalid",
1096
+ "available",
1097
+ "busy",
1098
+ "online",
1099
+ "offline",
1100
+ "open",
1101
+ "closed",
1102
+ "locked",
1103
+ "unlocked",
1104
+ ];
1105
+
1106
+ // 使用单词边界匹配
1107
+ const has_inst_key = inst_keys.some((keyword) => {
1108
+ const regex = new RegExp(`\\b${keyword}\\b`, "i");
1109
+ return regex.test(prop_name);
1110
+ });
1111
+
1112
+ if (has_inst_key) {
1113
+ // 如果属性名包含实例数据相关词汇,更可能是实例数据
1114
+ return false;
1115
+ }
1116
+
1117
+ // 属性名模式分析
1118
+ const is_likely_const_pat = this._isConstPat(prop_name, value);
1119
+
1120
+ return is_likely_const_pat;
1121
+ };
1122
+
1123
+ /**
1124
+ * 通过属性名模式分析判断是否为常量
1125
+ * @param {string} prop_name - 属性名
1126
+ * @param {any} value - 属性值
1127
+ * @returns {boolean} 根据模式分析是否为常量
1128
+ * @private
1129
+ */
1130
+ Detector.prototype._isConstPat = function (prop_name, value) {
1131
+ // UPPERCASE模式:全大写或大写蛇形
1132
+ const is_uppercase_pattern = /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/.test(prop_name);
1133
+
1134
+ // 配置前缀模式:包含config、setting、default等前缀
1135
+ const has_config_prefix = /^(config|setting|option|default|env|mode)_/i.test(
1136
+ prop_name,
1137
+ );
1138
+
1139
+ // 常量后缀模式:包含url、timeout、count等后缀
1140
+ const has_constant_suffix =
1141
+ /_(url|uri|endpoint|api|base|host|domain|timeout|delay|interval|retry|count|limit|max|min|size|length|port|version)$/i.test(
1142
+ prop_name,
1143
+ );
1144
+
1145
+ // 实例数据模式:包含user、product、order等前缀或后缀
1146
+ const has_instance_pattern =
1147
+ /^(user|person|customer|member|client|product|item|goods|article|order|cart|payment|invoice)_/i.test(
1148
+ prop_name,
1149
+ ) ||
1150
+ /_(user|person|customer|member|client|product|item|goods|article|order|cart|payment|invoice)$/i.test(
1151
+ prop_name,
1152
+ );
1153
+
1154
+ // 状态模式:以is_开头或包含状态相关词汇
1155
+ const is_status_pattern =
1156
+ /^is_/i.test(prop_name) ||
1157
+ /_(active|inactive|visible|hidden|required|optional|valid|invalid|available|busy|online|offline|open|closed|locked|unlocked)$/i.test(
1158
+ prop_name,
1159
+ );
1160
+
1161
+ // 判断逻辑
1162
+ if (is_uppercase_pattern || has_config_prefix || has_constant_suffix) {
1163
+ return true; // 常量模式
1164
+ }
1165
+
1166
+ if (has_instance_pattern || is_status_pattern) {
1167
+ return false; // 实例数据模式
1168
+ }
1169
+
1170
+ return false;
1171
+ };
1172
+
1173
+ /**
1174
+ * 检查是否包含禁止的废话词
1175
+ * @param {string} name - 名称
1176
+ * @returns {boolean} 是否包含禁止词
1177
+ * @private
1178
+ */
1179
+ Detector.prototype._hasBadWord = function (name) {
1180
+ if (!name) {
1181
+ return false;
1182
+ }
1183
+
1184
+ const forbidden_words = this.config.forbidden_words || [];
1185
+
1186
+ return forbidden_words.some((word) =>
1187
+ name.toLowerCase().includes(word.toLowerCase()),
1188
+ );
1189
+ };
1190
+
1191
+ /**
1192
+ * 检测名称是否符合规范
1193
+ * @param {string} name - 名称
1194
+ * @param {string} type - 类型(class/function/variable/constant/private)
1195
+ * @returns {Object} 检测结果
1196
+ */
1197
+ Detector.prototype.checkName = function (name, type) {
1198
+ if (typeof name !== "string" || !name.trim()) {
1199
+ return {
1200
+ valid: false,
1201
+ errors: ["名称必须是有效的字符串"],
1202
+ };
1203
+ }
1204
+
1205
+ if (typeof type !== "string" || !type.trim()) {
1206
+ return {
1207
+ valid: false,
1208
+ errors: ["类型必须是有效的字符串"],
1209
+ };
1210
+ }
1211
+
1212
+ const rule_method = this._rules[type];
1213
+ if (!rule_method) {
1214
+ return {
1215
+ valid: false,
1216
+ errors: [`不支持的类型: ${type}`],
1217
+ };
1218
+ }
1219
+
1220
+ try {
1221
+ return rule_method(name);
1222
+ } catch (error) {
1223
+ return {
1224
+ valid: false,
1225
+ errors: [`检测名称时出错: ${error.message}`],
1226
+ };
1227
+ }
1228
+ };
1229
+
1230
+ /**
1231
+ * 批量检测名称
1232
+ * @param {Array} names - 名称数组
1233
+ * @param {string} type - 类型
1234
+ * @returns {Array} 检测结果数组
1235
+ */
1236
+ Detector.prototype.checkNames = function (names, type) {
1237
+ if (!Array.isArray(names)) {
1238
+ throw new TypeError("名称必须是数组");
1239
+ }
1240
+
1241
+ return names.map((name) => this.check_name(name, type));
1242
+ };
1243
+
1244
+ /**
1245
+ * ESLint规则实现
1246
+ */
1247
+
1248
+ /**
1249
+ * 类名大驼峰规则
1250
+ */
1251
+ const class_name_rule = {
1252
+ meta: {
1253
+ type: "suggestion",
1254
+ docs: {
1255
+ description: "类名必须使用大驼峰命名法(PascalCase)且优先使用单个单词",
1256
+ category: "Stylistic Issues",
1257
+ recommended: true,
1258
+ },
1259
+ schema: [
1260
+ {
1261
+ type: "object",
1262
+ properties: {
1263
+ regex: { type: "string" },
1264
+ min: { type: "number" },
1265
+ max: { type: "number" },
1266
+ message: { type: "string" },
1267
+ single_word: { type: "boolean" },
1268
+ single_word_len: { type: "number" },
1269
+ styles: { type: "array", items: { type: "string" } },
1270
+ },
1271
+ additionalProperties: false,
1272
+ },
1273
+ ],
1274
+ },
1275
+ create(context) {
1276
+ const options = context.options[0] || {};
1277
+ return {
1278
+ ClassDeclaration(node) {
1279
+ const class_name = node.id.name;
1280
+ const detector = new Detector({ "class-name": options });
1281
+ const result = detector.checkName(class_name, "class-name");
1282
+
1283
+ if (!result.valid) {
1284
+ result.errors.forEach((error) => {
1285
+ context.report({
1286
+ node: node.id,
1287
+ message: `类名"${class_name}"不符合规范: ${error}`,
1288
+ });
1289
+ });
1290
+ }
1291
+ },
1292
+ };
1293
+ },
1294
+ };
1295
+
1296
+ /**
1297
+ * 函数名小驼峰规则
1298
+ */
1299
+ const function_name_rule = {
1300
+ meta: {
1301
+ type: "suggestion",
1302
+ docs: {
1303
+ description: "函数名必须使用小驼峰命名法(camelCase)且优先使用单个单词",
1304
+ category: "Stylistic Issues",
1305
+ recommended: true,
1306
+ },
1307
+ schema: [
1308
+ {
1309
+ type: "object",
1310
+ properties: {
1311
+ regex: { type: "string" },
1312
+ min: { type: "number" },
1313
+ max: { type: "number" },
1314
+ message: { type: "string" },
1315
+ single_word: { type: "boolean" },
1316
+ single_word_len: { type: "number" },
1317
+ styles: { type: "array", items: { type: "string" } },
1318
+ },
1319
+ additionalProperties: false,
1320
+ },
1321
+ ],
1322
+ },
1323
+ create(context) {
1324
+ const options = context.options[0] || {};
1325
+ return {
1326
+ FunctionDeclaration(node) {
1327
+ const function_name = node.id ? node.id.name : "anonymous";
1328
+ if (function_name !== "anonymous") {
1329
+ const detector = new Detector({ "function-name": options });
1330
+ const result = detector.checkName(function_name, "function-name");
1331
+
1332
+ if (!result.valid) {
1333
+ result.errors.forEach((error) => {
1334
+ context.report({
1335
+ node: node.id,
1336
+ message: `函数名"${function_name}"不符合规范: ${error}`,
1337
+ });
1338
+ });
1339
+ }
1340
+ }
1341
+ },
1342
+
1343
+ FunctionExpression(node) {
1344
+ if (node.id) {
1345
+ const function_name = node.id.name;
1346
+ const detector = new Detector({ "function-name": options });
1347
+ const result = detector.checkName(function_name, "function-name");
1348
+
1349
+ if (!result.valid) {
1350
+ result.errors.forEach((error) => {
1351
+ context.report({
1352
+ node: node.id,
1353
+ message: `函数表达式名"${function_name}"不符合规范: ${error}`,
1354
+ });
1355
+ });
1356
+ }
1357
+ }
1358
+ },
1359
+ };
1360
+ },
1361
+ };
1362
+
1363
+ /**
1364
+ * 方法名小驼峰规则
1365
+ */
1366
+ const method_name_rule = {
1367
+ meta: {
1368
+ type: "suggestion",
1369
+ docs: {
1370
+ description: "方法名必须使用小驼峰命名法(camelCase)且优先使用单个单词",
1371
+ category: "Stylistic Issues",
1372
+ recommended: true,
1373
+ },
1374
+ schema: [
1375
+ {
1376
+ type: "object",
1377
+ properties: {
1378
+ regex: { type: "string" },
1379
+ min: { type: "number" },
1380
+ max: { type: "number" },
1381
+ message: { type: "string" },
1382
+ single_word: { type: "boolean" },
1383
+ single_word_len: { type: "number" },
1384
+ styles: { type: "array", items: { type: "string" } },
1385
+ },
1386
+ additionalProperties: false,
1387
+ },
1388
+ ],
1389
+ },
1390
+ create(context) {
1391
+ const options = context.options[0] || {};
1392
+ return {
1393
+ MethodDefinition(node) {
1394
+ const method_name = node.key.name;
1395
+
1396
+ // 跳过私有方法的检测(私有方法由private-naming规则处理)
1397
+ if (method_name.startsWith("_")) {
1398
+ return;
1399
+ }
1400
+
1401
+ // 跳过JavaScript内置特殊方法名
1402
+ const builtin_methods = [
1403
+ "constructor",
1404
+ "toString",
1405
+ "valueOf",
1406
+ "toJSON",
1407
+ ];
1408
+ if (builtin_methods.includes(method_name)) {
1409
+ return;
1410
+ }
1411
+
1412
+ const detector = new Detector({ "method-name": options });
1413
+ const result = detector.checkName(method_name, "method-name");
1414
+
1415
+ if (!result.valid) {
1416
+ result.errors.forEach((error) => {
1417
+ context.report({
1418
+ node: node.key,
1419
+ message: `方法名"${method_name}"不符合规范: ${error}`,
1420
+ });
1421
+ });
1422
+ }
1423
+ },
1424
+ };
1425
+ },
1426
+ };
1427
+
1428
+ /**
1429
+ * 变量名小写蛇形规则
1430
+ */
1431
+ const variable_name_rule = {
1432
+ meta: {
1433
+ type: "suggestion",
1434
+ docs: {
1435
+ description: "变量名必须使用小写蛇形命名法(snake_case)",
1436
+ category: "Stylistic Issues",
1437
+ recommended: true,
1438
+ },
1439
+ schema: [
1440
+ {
1441
+ type: "object",
1442
+ properties: {
1443
+ regex: { type: "string" },
1444
+ min: { type: "number" },
1445
+ max: { type: "number" },
1446
+ message: { type: "string" },
1447
+ single_word: { type: "boolean" },
1448
+ single_word_len: { type: "number" },
1449
+ styles: { type: "array", items: { type: "string" } },
1450
+ },
1451
+ additionalProperties: false,
1452
+ },
1453
+ ],
1454
+ },
1455
+ create(context) {
1456
+ const options = context.options[0] || {};
1457
+
1458
+ function isRealConstant(node) {
1459
+ // 真正的常量特征:
1460
+ // 1. 在模块顶层作用域(不在函数或块内)
1461
+ // 2. 名称包含大写字母(表示意图作为常量)
1462
+
1463
+ // 检查是否在模块顶层
1464
+ let scope_node = node;
1465
+ while (scope_node.parent) {
1466
+ scope_node = scope_node.parent;
1467
+ if (
1468
+ scope_node.type === "FunctionDeclaration" ||
1469
+ scope_node.type === "FunctionExpression" ||
1470
+ scope_node.type === "ArrowFunctionExpression" ||
1471
+ scope_node.type === "BlockStatement"
1472
+ ) {
1473
+ return false; // 在函数或块内,不是真正的常量
1474
+ }
1475
+ }
1476
+
1477
+ // 检查名称是否包含大写字母(表示意图作为常量)
1478
+ const name = node.id.name;
1479
+ return /[A-Z]/.test(name);
1480
+ }
1481
+
1482
+ return {
1483
+ VariableDeclarator(node) {
1484
+ if (node.id.type === "Identifier") {
1485
+ // 对于const声明,只对不是真正常量的应用变量命名规则
1486
+ if (node.parent.kind === "const" && isRealConstant(node)) {
1487
+ return; // 这是真正的常量,由常量规则处理
1488
+ }
1489
+
1490
+ const variable_name = node.id.name;
1491
+ const detector = new Detector({ "variable-name": options });
1492
+ const result = detector.checkName(variable_name, "variable-name");
1493
+
1494
+ if (!result.valid) {
1495
+ result.errors.forEach((error) => {
1496
+ context.report({
1497
+ node: node.id,
1498
+ message: `变量名"${variable_name}"不符合规范: ${error}`,
1499
+ });
1500
+ });
1501
+ }
1502
+ }
1503
+ },
1504
+ };
1505
+ },
1506
+ };
1507
+
1508
+ /**
1509
+ * 常量名大写蛇形规则
1510
+ */
1511
+ const constant_name_rule = {
1512
+ meta: {
1513
+ type: "suggestion",
1514
+ docs: {
1515
+ description: "常量名必须使用大写蛇形命名法(UPPER_SNAKE_CASE)",
1516
+ category: "Stylistic Issues",
1517
+ recommended: true,
1518
+ },
1519
+ schema: [
1520
+ {
1521
+ type: "object",
1522
+ properties: {
1523
+ regex: { type: "string" },
1524
+ min: { type: "number" },
1525
+ max: { type: "number" },
1526
+ message: { type: "string" },
1527
+ single_word: { type: "boolean" },
1528
+ single_word_len: { type: "number" },
1529
+ styles: { type: "array", items: { type: "string" } },
1530
+ },
1531
+ additionalProperties: false,
1532
+ },
1533
+ ],
1534
+ },
1535
+ create(context) {
1536
+ const options = context.options[0] || {};
1537
+
1538
+ function isRealConstant(node) {
1539
+ // 真正的常量特征:
1540
+ // 1. 在模块顶层作用域(不在函数或块内)
1541
+ // 2. 名称包含大写字母(表示意图作为常量)
1542
+ // 3. 或者名称是常见的常量模式(如全大写)
1543
+
1544
+ // 检查是否在模块顶层
1545
+ let scope_node = node;
1546
+ while (scope_node.parent) {
1547
+ scope_node = scope_node.parent;
1548
+ if (
1549
+ scope_node.type === "FunctionDeclaration" ||
1550
+ scope_node.type === "FunctionExpression" ||
1551
+ scope_node.type === "ArrowFunctionExpression" ||
1552
+ scope_node.type === "BlockStatement"
1553
+ ) {
1554
+ return false; // 在函数或块内,不是真正的常量
1555
+ }
1556
+ }
1557
+
1558
+ // 检查名称是否包含大写字母(表示意图作为常量)
1559
+ const name = node.id.name;
1560
+ return /[A-Z]/.test(name);
1561
+ }
1562
+
1563
+ return {
1564
+ VariableDeclarator(node) {
1565
+ if (node.id.type === "Identifier" && node.parent.kind === "const") {
1566
+ // 只对真正的常量应用常量命名规则
1567
+ if (isRealConstant(node)) {
1568
+ const constant_name = node.id.name;
1569
+ const detector = new Detector({ "constant-name": options });
1570
+ const result = detector.checkName(constant_name, "constant-name");
1571
+
1572
+ if (!result.valid) {
1573
+ result.errors.forEach((error) => {
1574
+ context.report({
1575
+ node: node.id,
1576
+ message: `常量名"${constant_name}"不符合规范: ${error}`,
1577
+ });
1578
+ });
1579
+ }
1580
+ }
1581
+ }
1582
+ },
1583
+ };
1584
+ },
1585
+ };
1586
+
1587
+ /**
1588
+ * 入参名小写蛇形规则
1589
+ */
1590
+ const param_name_rule = {
1591
+ meta: {
1592
+ type: "suggestion",
1593
+ docs: {
1594
+ description: "入参名必须使用小写蛇形命名法(snake_case)",
1595
+ category: "Stylistic Issues",
1596
+ recommended: true,
1597
+ },
1598
+ schema: [
1599
+ {
1600
+ type: "object",
1601
+ properties: {
1602
+ regex: { type: "string" },
1603
+ min: { type: "number" },
1604
+ max: { type: "number" },
1605
+ message: { type: "string" },
1606
+ single_word: { type: "boolean" },
1607
+ single_word_len: { type: "number" },
1608
+ styles: { type: "array", items: { type: "string" } },
1609
+ },
1610
+ additionalProperties: false,
1611
+ },
1612
+ ],
1613
+ },
1614
+ create(context) {
1615
+ const options = context.options[0] || {};
1616
+
1617
+ function checkParam(node) {
1618
+ // 处理不同节点类型的参数
1619
+ let params = [];
1620
+
1621
+ if (node.type === "MethodDefinition") {
1622
+ // MethodDefinition节点(包括构造函数)的参数在node.value.params中
1623
+ params = node.value ? node.value.params || [] : [];
1624
+ } else {
1625
+ // 其他函数节点的参数在node.params中
1626
+ params = node.params || [];
1627
+ }
1628
+
1629
+ params.forEach((param) => {
1630
+ if (param.type === "Identifier") {
1631
+ const param_name = param.name;
1632
+ const detector = new Detector({ "param-name": options });
1633
+ const result = detector.checkName(param_name, "param-name");
1634
+
1635
+ if (!result.valid) {
1636
+ result.errors.forEach((error) => {
1637
+ context.report({
1638
+ node: param,
1639
+ message: `入参名"${param_name}"不符合规范: ${error}`,
1640
+ });
1641
+ });
1642
+ }
1643
+ }
1644
+ });
1645
+ }
1646
+
1647
+ return {
1648
+ FunctionDeclaration: checkParam,
1649
+ FunctionExpression: checkParam,
1650
+ ArrowFunctionExpression: checkParam,
1651
+ MethodDefinition: checkParam,
1652
+ };
1653
+ },
1654
+ };
1655
+
1656
+ /**
1657
+ * 属性名小写蛇形规则
1658
+ */
1659
+ const prop_name_rule = {
1660
+ meta: {
1661
+ type: "suggestion",
1662
+ docs: {
1663
+ description: "属性名必须使用小写蛇形命名法(snake_case)",
1664
+ category: "Stylistic Issues",
1665
+ recommended: true,
1666
+ },
1667
+ schema: [
1668
+ {
1669
+ type: "object",
1670
+ properties: {
1671
+ regex: { type: "string" },
1672
+ min: { type: "number" },
1673
+ max: { type: "number" },
1674
+ message: { type: "string" },
1675
+ single_word: { type: "boolean" },
1676
+ single_word_len: { type: "number" },
1677
+ styles: { type: "array", items: { type: "string" } },
1678
+ },
1679
+ additionalProperties: false,
1680
+ },
1681
+ ],
1682
+ },
1683
+ create(context) {
1684
+ const options = context.options[0] || {};
1685
+ return {
1686
+ Property(node) {
1687
+ // 检测类属性、对象字面量属性、模块导出属性
1688
+ const is_class_property =
1689
+ node.parent &&
1690
+ (node.parent.type === "ClassBody" ||
1691
+ (node.parent.type === "ObjectExpression" &&
1692
+ node.parent.parent &&
1693
+ node.parent.parent.type === "ClassDeclaration"));
1694
+
1695
+ const is_object_property =
1696
+ node.parent && node.parent.type === "ObjectExpression";
1697
+
1698
+ // 检测所有非私有属性(不以_开头)
1699
+ if (node.key.type === "Identifier" && !node.key.name.startsWith("_")) {
1700
+ const prop_name = node.key.name;
1701
+ const detector = new Detector({ "property-name": options });
1702
+ const result = detector._checkPropName(prop_name, node.value);
1703
+
1704
+ if (!result.valid) {
1705
+ result.errors.forEach((error) => {
1706
+ context.report({
1707
+ node: node.key,
1708
+ message: `属性名"${prop_name}"不符合规范: ${error}`,
1709
+ });
1710
+ });
1711
+ }
1712
+ }
1713
+ },
1714
+ };
1715
+ },
1716
+ };
1717
+
1718
+ /**
1719
+ * 私有方法命名规则
1720
+ */
1721
+ const private_method_rule = {
1722
+ meta: {
1723
+ type: "suggestion",
1724
+ docs: {
1725
+ description: "私有方法必须以单下划线开头,后跟小驼峰命名或小写单词",
1726
+ category: "Stylistic Issues",
1727
+ recommended: true,
1728
+ },
1729
+ schema: [
1730
+ {
1731
+ type: "object",
1732
+ properties: {
1733
+ regex: { type: "string" },
1734
+ min: { type: "number" },
1735
+ max: { type: "number" },
1736
+ message: { type: "string" },
1737
+ single_word: { type: "boolean" },
1738
+ single_word_len: { type: "number" },
1739
+ styles: { type: "array", items: { type: "string" } },
1740
+ },
1741
+ additionalProperties: false,
1742
+ },
1743
+ ],
1744
+ },
1745
+ create(context) {
1746
+ const options = context.options[0] || {};
1747
+ return {
1748
+ MethodDefinition(node) {
1749
+ const method_name = node.key.name;
1750
+ if (method_name.startsWith("_")) {
1751
+ const detector = new Detector({ "private-method-naming": options });
1752
+ const result = detector.checkName(
1753
+ method_name,
1754
+ "private-method-naming",
1755
+ );
1756
+
1757
+ if (!result.valid) {
1758
+ result.errors.forEach((error) => {
1759
+ context.report({
1760
+ node: node.key,
1761
+ message: `私有方法名"${method_name}"不符合规范: ${error}`,
1762
+ });
1763
+ });
1764
+ }
1765
+ }
1766
+ },
1767
+ };
1768
+ },
1769
+ };
1770
+
1771
+ /**
1772
+ * 私有变量命名规则
1773
+ */
1774
+ const private_variable_rule = {
1775
+ meta: {
1776
+ type: "suggestion",
1777
+ docs: {
1778
+ description: "私有变量必须以单下划线开头,后跟小写蛇形命名或小写单词",
1779
+ category: "Stylistic Issues",
1780
+ recommended: true,
1781
+ },
1782
+ schema: [
1783
+ {
1784
+ type: "object",
1785
+ properties: {
1786
+ regex: { type: "string" },
1787
+ min: { type: "number" },
1788
+ max: { type: "number" },
1789
+ message: { type: "string" },
1790
+ single_word: { type: "boolean" },
1791
+ single_word_len: { type: "number" },
1792
+ styles: { type: "array", items: { type: "string" } },
1793
+ },
1794
+ additionalProperties: false,
1795
+ },
1796
+ ],
1797
+ },
1798
+ create(context) {
1799
+ const options = context.options[0] || {};
1800
+ return {
1801
+ Property(node) {
1802
+ if (node.key.type === "Identifier" && node.key.name.startsWith("_")) {
1803
+ const private_name = node.key.name;
1804
+ const detector = new Detector({ "private-variable-naming": options });
1805
+ const result = detector.checkName(
1806
+ private_name,
1807
+ "private-variable-naming",
1808
+ );
1809
+
1810
+ if (!result.valid) {
1811
+ result.errors.forEach((error) => {
1812
+ context.report({
1813
+ node: node.key,
1814
+ message: `私有属性名"${private_name}"不符合规范: ${error}`,
1815
+ });
1816
+ });
1817
+ }
1818
+ }
1819
+ },
1820
+ };
1821
+ },
1822
+ };
1823
+
1824
+ /**
1825
+ * 实例属性赋值命名规则(检测 this.property = value 模式)
1826
+ */
1827
+ const instance_property_rule = {
1828
+ meta: {
1829
+ type: "suggestion",
1830
+ docs: {
1831
+ description: "实例属性赋值必须符合命名规范",
1832
+ category: "Stylistic Issues",
1833
+ recommended: true,
1834
+ },
1835
+ schema: [
1836
+ {
1837
+ type: "object",
1838
+ properties: {
1839
+ regex: { type: "string" },
1840
+ min: { type: "number" },
1841
+ max: { type: "number" },
1842
+ message: { type: "string" },
1843
+ single_word: { type: "boolean" },
1844
+ single_word_len: { type: "number" },
1845
+ styles: { type: "array", items: { type: "string" } },
1846
+ },
1847
+ additionalProperties: false,
1848
+ },
1849
+ ],
1850
+ },
1851
+ create(context) {
1852
+ const options = context.options[0] || {};
1853
+ return {
1854
+ AssignmentExpression(node) {
1855
+ // 检测 this.property = value 模式
1856
+ if (
1857
+ node.left.type === "MemberExpression" &&
1858
+ node.left.object.type === "ThisExpression" &&
1859
+ node.left.property.type === "Identifier" &&
1860
+ !node.left.property.name.startsWith("_")
1861
+ ) {
1862
+ const prop_name = node.left.property.name;
1863
+ const detector = new Detector({ "property-name": options });
1864
+ const result = detector._checkPropName(prop_name, node.right);
1865
+
1866
+ if (!result.valid) {
1867
+ result.errors.forEach((error) => {
1868
+ context.report({
1869
+ node: node.left.property,
1870
+ message: `实例属性名"${prop_name}"不符合规范: ${error}`,
1871
+ });
1872
+ });
1873
+ }
1874
+ }
1875
+ },
1876
+ };
1877
+ },
1878
+ };
1879
+
1880
+ /**
1881
+ * 命名规范ESLint插件
1882
+ */
1883
+ const naming_rules = {
1884
+ "class-name": class_name_rule,
1885
+ "function-name": function_name_rule,
1886
+ "method-name": method_name_rule,
1887
+ "variable-name": variable_name_rule,
1888
+ "constant-name": constant_name_rule,
1889
+ "private-method-naming": private_method_rule,
1890
+ "private-variable-naming": private_variable_rule,
1891
+ "param-name": param_name_rule,
1892
+ "property-name": prop_name_rule,
1893
+ "instance-property": instance_property_rule,
1894
+ };
1895
+
1896
+ module.exports = {
1897
+ Detector,
1898
+ rules: naming_rules,
1899
+ };