mm_eslint 1.0.1 → 1.0.2

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 +125 -18
  2. package/README_EN.md +121 -14
  3. package/index.js +573 -96
  4. package/package.json +5 -2
package/index.js CHANGED
@@ -3,91 +3,120 @@
3
3
  /**
4
4
  * 命名规范检测器类
5
5
  */
6
- class NamingDetector {
6
+ class Detector {
7
7
  static config = {
8
8
  forbidden_words: [
9
9
  'manager', 'handler', 'processor', 'controller',
10
10
  'Management', 'Handler', 'Processor', 'Controller',
11
11
  'LoggerManager', 'AppState', 'UserHandler'
12
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
+
13
29
  // 类名大驼峰规则
14
30
  'class-name': {
15
31
  name: '类名',
16
- message: '{type}{name}必须使用大驼峰命名法(PascalCase),长度{min}-{max}字符,且优先使用单个单词',
17
- regex: /^[A-Z][a-zA-Z]*$/, // 大写字母开头,只包含字母
32
+ message: '必须使用大驼峰命名法(PascalCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
33
+ regex: null, // 使用styles属性进行验证
18
34
  min: 1,
19
- max: 20
35
+ max: 20,
36
+ styles: ['PascalCase'],
37
+ single_word: true,
38
+ single_word_len: 8
20
39
  },
21
40
 
22
41
  // 函数名小驼峰规则
23
42
  'function-name': {
24
43
  name: '函数名',
25
- message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词',
26
- regex: /^[a-z][a-zA-Z]*$/, // 小写字母开头,只包含字母
44
+ message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
45
+ regex: null, // 使用styles属性进行验证
27
46
  min: 1,
28
47
  max: 20,
29
- single_word_preferred: true
48
+ styles: ['camelCase'],
49
+ single_word: true,
50
+ single_word_len: 8
30
51
  },
31
52
 
32
53
  // 方法名小驼峰规则
33
54
  'method-name': {
34
55
  name: '方法名',
35
- message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词',
36
- regex: /^[a-z][a-zA-Z]*$/, // 小写字母开头,只包含字母
56
+ message: '必须使用小驼峰命名法(camelCase),长度{min}-{max}字符,且优先使用单个单词,每个单词不超过{word_len}字符',
57
+ regex: null, // 使用styles属性进行验证
37
58
  min: 1,
38
59
  max: 20,
39
- single_word_preferred: true
60
+ styles: ['camelCase'],
61
+ single_word: true,
62
+ single_word_len: 8
40
63
  },
41
64
 
42
65
  // 入参名小写蛇形规则
43
66
  'param-name': {
44
67
  name: '入参名',
45
68
  message: '必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符',
46
- regex: /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写字母开头,可包含下划线
69
+ regex: null, // 使用styles属性进行验证
47
70
  min: 1,
48
- max: 20
71
+ max: 20,
72
+ styles: ['lowercase', 'snake_case']
49
73
  },
50
74
 
51
75
  // 变量名小写蛇形规则
52
76
  'variable-name': {
53
77
  name: '变量名',
54
78
  message: '必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符',
55
- regex: /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写字母开头,可包含下划线
79
+ regex: null, // 使用styles属性进行验证
56
80
  min: 1,
57
- max: 20
81
+ max: 20,
82
+ styles: ['lowercase', 'snake_case']
58
83
  },
59
84
 
60
85
  // 常量名大写蛇形规则
61
86
  'constant-name': {
62
87
  name: '常量名',
63
88
  message: '必须使用大写蛇形命名法(UPPER_SNAKE_CASE),长度{min}-{max}字符',
64
- regex: /^[A-Z][A-Z0-9_]*(_[A-Z0-9]+)*$/, // 大写字母开头,可包含下划线
89
+ regex: null, // 使用styles属性进行验证
65
90
  min: 1,
66
- max: 20
91
+ max: 20,
92
+ styles: ['UPPERCASE', 'UPPER_SNAKE_CASE']
67
93
  },
68
94
 
69
- // 属性名小写蛇形规则
95
+ // 属性名多风格规则(根据值类型应用不同规则)
70
96
  'property-name': {
71
97
  name: '属性名',
72
- message: '必须使用小写蛇形命名法(snake_case),长度{min}-{max}字符',
73
- regex: /^[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 小写字母开头,可包含下划线
98
+ message: '根据属性值类型应用不同命名规则',
74
99
  min: 1,
75
- max: 20
100
+ max: 20,
101
+ single_word: true,
102
+ single_word_len: 8,
103
+ styles: ['lowercase', '_lowercase', 'UPPERCASE', 'snake_case', '_snake_case']
76
104
  },
77
105
 
78
106
  // 私有方法/属性规则
79
107
  'private-naming': {
80
108
  name: '私有成员',
81
109
  message: '必须以单下划线开头,后跟小写蛇形命名,长度{min}-{max}字符',
82
- regex: /^_[a-z][a-z0-9_]*(_[a-z0-9]+)*$/, // 下划线开头,小写蛇形
110
+ regex: null, // 使用styles属性进行验证
83
111
  min: 2,
84
- max: 20
112
+ max: 20,
113
+ styles: ['_lowercase', '_snake_case', '_camelCase']
85
114
  }
86
115
  }
87
116
 
88
117
  constructor(config) {
89
118
  // 合并默认配置和传入配置
90
- const merged_config = { ...NamingDetector.config };
119
+ const merged_config = { ...Detector.config };
91
120
  if (config && typeof config === 'object') {
92
121
  Object.keys(config).forEach(key => {
93
122
  if (merged_config[key]) {
@@ -116,29 +145,40 @@ class NamingDetector {
116
145
  this._init_rules();
117
146
  }
118
147
 
119
- _new_model() {
120
- return {
121
- name: '',
122
- message: '',
123
- regex: null,
124
- min: 1,
125
- max: 20
126
- }
127
- }
128
148
 
129
- _init_rules() {
130
- // 绑定规则检查方法
131
- this._rules = {
132
- 'class-name': this._checkName.bind(this, 'class-name'),
133
- 'function-name': this._checkName.bind(this, 'function-name'),
134
- 'method-name': this._checkName.bind(this, 'method-name'),
135
- 'param-name': this._checkName.bind(this, 'param-name'),
136
- 'variable-name': this._checkName.bind(this, 'variable-name'),
137
- 'constant-name': this._checkName.bind(this, 'constant-name'),
138
- 'property-name': this._checkName.bind(this, 'property-name'),
139
- 'private-naming': this._checkName.bind(this, 'private-naming')
140
- };
141
- }
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
+ };
142
182
  }
143
183
 
144
184
  /**
@@ -148,7 +188,7 @@ class NamingDetector {
148
188
  * @returns {Object} 检查结果
149
189
  * @private
150
190
  */
151
- NamingDetector.prototype._checkName = function (rule_type, name) {
191
+ Detector.prototype._checkName = function (rule_type, name) {
152
192
  if (!name) {
153
193
  throw new TypeError('名称不能为空');
154
194
  }
@@ -159,6 +199,7 @@ NamingDetector.prototype._checkName = function (rule_type, name) {
159
199
  }
160
200
 
161
201
  const errors = [];
202
+ const warnings = [];
162
203
 
163
204
  // 检查长度
164
205
  if (name.length < config.min) {
@@ -168,15 +209,32 @@ NamingDetector.prototype._checkName = function (rule_type, name) {
168
209
  errors.push(`${config.name}长度不能超过${config.max}个字符`);
169
210
  }
170
211
 
171
- // 检查正则表达式
172
- if (config.regex && !config.regex.test(name)) {
173
- const error_message = config.message
174
- .replace('{name}', `"${name}"`)
175
- .replace('{type}', config.name)
176
- .replace('{min}', config.min)
177
- .replace('{max}', config.max)
178
- .replace('{regex}', config.regex.toString());
179
- errors.push(error_message);
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;
180
238
  }
181
239
 
182
240
  // 检查禁止词汇
@@ -184,26 +242,253 @@ NamingDetector.prototype._checkName = function (rule_type, name) {
184
242
  errors.push(`${config.name}包含禁止的废话词`);
185
243
  }
186
244
 
187
- // 检查是否优先使用单个单词
188
- if (config.single_word_preferred) {
189
- if (name.includes('_') || name.includes('-')) {
190
- errors.push(`${config.name}应优先使用单个单词`);
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}个字符限制`);
191
279
  }
192
280
  }
193
281
 
194
282
  return {
195
283
  valid: errors.length === 0,
196
- errors: errors
284
+ errors: errors,
285
+ warnings: warnings
197
286
  };
198
287
  }
199
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
+
200
485
  /**
201
486
  * 检查是否包含禁止的废话词
202
487
  * @param {string} name - 名称
203
488
  * @returns {boolean} 是否包含禁止词
204
489
  * @private
205
490
  */
206
- NamingDetector.prototype._hasForbiddenWords = function (name) {
491
+ Detector.prototype._hasForbiddenWords = function (name) {
207
492
  if (!name) {
208
493
  return false;
209
494
  }
@@ -221,7 +506,7 @@ NamingDetector.prototype._hasForbiddenWords = function (name) {
221
506
  * @param {string} type - 类型(class/function/variable/constant/private)
222
507
  * @returns {Object} 检测结果
223
508
  */
224
- NamingDetector.prototype.checkName = function (name, type) {
509
+ Detector.prototype.checkName = function (name, type) {
225
510
  if (typeof name !== 'string' || !name.trim()) {
226
511
  return {
227
512
  valid: false,
@@ -260,7 +545,7 @@ NamingDetector.prototype.checkName = function (name, type) {
260
545
  * @param {string} type - 类型
261
546
  * @returns {Array} 检测结果数组
262
547
  */
263
- NamingDetector.prototype.checkNames = function (names, type) {
548
+ Detector.prototype.checkNames = function (names, type) {
264
549
  if (!Array.isArray(names)) {
265
550
  throw new TypeError('名称必须是数组');
266
551
  }
@@ -275,7 +560,7 @@ NamingDetector.prototype.checkNames = function (names, type) {
275
560
  /**
276
561
  * 类名大驼峰规则
277
562
  */
278
- const classNameRule = {
563
+ const class_name_rule = {
279
564
  meta: {
280
565
  type: 'suggestion',
281
566
  docs: {
@@ -291,7 +576,9 @@ const classNameRule = {
291
576
  min: { type: 'number' },
292
577
  max: { type: 'number' },
293
578
  message: { type: 'string' },
294
- single_word_preferred: { type: 'boolean' }
579
+ single_word: { type: 'boolean' },
580
+ single_word_len: { type: 'number' },
581
+ styles: { type: 'array', items: { type: 'string' } }
295
582
  },
296
583
  additionalProperties: false
297
584
  }
@@ -302,7 +589,7 @@ const classNameRule = {
302
589
  return {
303
590
  ClassDeclaration(node) {
304
591
  const class_name = node.id.name;
305
- const detector = new NamingDetector({ 'class-name': options });
592
+ const detector = new Detector({ 'class-name': options });
306
593
  const result = detector.checkName(class_name, 'class-name');
307
594
 
308
595
  if (!result.valid) {
@@ -321,7 +608,7 @@ const classNameRule = {
321
608
  /**
322
609
  * 函数名小驼峰规则
323
610
  */
324
- const functionNameRule = {
611
+ const function_name_rule = {
325
612
  meta: {
326
613
  type: 'suggestion',
327
614
  docs: {
@@ -337,7 +624,9 @@ const functionNameRule = {
337
624
  min: { type: 'number' },
338
625
  max: { type: 'number' },
339
626
  message: { type: 'string' },
340
- single_word_preferred: { type: 'boolean' }
627
+ single_word: { type: 'boolean' },
628
+ single_word_len: { type: 'number' },
629
+ styles: { type: 'array', items: { type: 'string' } }
341
630
  },
342
631
  additionalProperties: false
343
632
  }
@@ -349,7 +638,7 @@ const functionNameRule = {
349
638
  FunctionDeclaration(node) {
350
639
  const function_name = node.id ? node.id.name : 'anonymous';
351
640
  if (function_name !== 'anonymous') {
352
- const detector = new NamingDetector({ 'function-name': options });
641
+ const detector = new Detector({ 'function-name': options });
353
642
  const result = detector.checkName(function_name, 'function-name');
354
643
 
355
644
  if (!result.valid) {
@@ -366,7 +655,7 @@ const functionNameRule = {
366
655
  FunctionExpression(node) {
367
656
  if (node.id) {
368
657
  const function_name = node.id.name;
369
- const detector = new NamingDetector({ 'function-name': options });
658
+ const detector = new Detector({ 'function-name': options });
370
659
  const result = detector.checkName(function_name, 'function-name');
371
660
 
372
661
  if (!result.valid) {
@@ -386,7 +675,7 @@ const functionNameRule = {
386
675
  /**
387
676
  * 方法名小驼峰规则
388
677
  */
389
- const methodNameRule = {
678
+ const method_name_rule = {
390
679
  meta: {
391
680
  type: 'suggestion',
392
681
  docs: {
@@ -402,7 +691,9 @@ const methodNameRule = {
402
691
  min: { type: 'number' },
403
692
  max: { type: 'number' },
404
693
  message: { type: 'string' },
405
- single_word_preferred: { type: 'boolean' }
694
+ single_word: { type: 'boolean' },
695
+ single_word_len: { type: 'number' },
696
+ styles: { type: 'array', items: { type: 'string' } }
406
697
  },
407
698
  additionalProperties: false
408
699
  }
@@ -413,7 +704,13 @@ const methodNameRule = {
413
704
  return {
414
705
  MethodDefinition(node) {
415
706
  const method_name = node.key.name;
416
- const detector = new NamingDetector({ 'method-name': options });
707
+
708
+ // 跳过私有方法的检测(私有方法由private-naming规则处理)
709
+ if (method_name.startsWith('_')) {
710
+ return;
711
+ }
712
+
713
+ const detector = new Detector({ 'method-name': options });
417
714
  const result = detector.checkName(method_name, 'method-name');
418
715
 
419
716
  if (!result.valid) {
@@ -432,7 +729,7 @@ const methodNameRule = {
432
729
  /**
433
730
  * 变量名小写蛇形规则
434
731
  */
435
- const variableNameRule = {
732
+ const variable_name_rule = {
436
733
  meta: {
437
734
  type: 'suggestion',
438
735
  docs: {
@@ -448,7 +745,9 @@ const variableNameRule = {
448
745
  min: { type: 'number' },
449
746
  max: { type: 'number' },
450
747
  message: { type: 'string' },
451
- single_word_preferred: { type: 'boolean' }
748
+ single_word: { type: 'boolean' },
749
+ single_word_len: { type: 'number' },
750
+ styles: { type: 'array', items: { type: 'string' } }
452
751
  },
453
752
  additionalProperties: false
454
753
  }
@@ -456,11 +755,39 @@ const variableNameRule = {
456
755
  },
457
756
  create(context) {
458
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
+
459
781
  return {
460
782
  VariableDeclarator(node) {
461
783
  if (node.id.type === 'Identifier') {
784
+ // 对于const声明,只对不是真正常量的应用变量命名规则
785
+ if (node.parent.kind === 'const' && isRealConstant(node)) {
786
+ return; // 这是真正的常量,由常量规则处理
787
+ }
788
+
462
789
  const variable_name = node.id.name;
463
- const detector = new NamingDetector({ 'variable-name': options });
790
+ const detector = new Detector({ 'variable-name': options });
464
791
  const result = detector.checkName(variable_name, 'variable-name');
465
792
 
466
793
  if (!result.valid) {
@@ -480,7 +807,7 @@ const variableNameRule = {
480
807
  /**
481
808
  * 常量名大写蛇形规则
482
809
  */
483
- const constantNameRule = {
810
+ const constant_name_rule = {
484
811
  meta: {
485
812
  type: 'suggestion',
486
813
  docs: {
@@ -496,7 +823,9 @@ const constantNameRule = {
496
823
  min: { type: 'number' },
497
824
  max: { type: 'number' },
498
825
  message: { type: 'string' },
499
- single_word_preferred: { type: 'boolean' }
826
+ single_word: { type: 'boolean' },
827
+ single_word_len: { type: 'number' },
828
+ styles: { type: 'array', items: { type: 'string' } }
500
829
  },
501
830
  additionalProperties: false
502
831
  }
@@ -504,18 +833,162 @@ const constantNameRule = {
504
833
  },
505
834
  create(context) {
506
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
+
507
860
  return {
508
861
  VariableDeclarator(node) {
509
862
  if (node.id.type === 'Identifier' && node.parent.kind === 'const') {
510
- const constant_name = node.id.name;
511
- const detector = new NamingDetector({ 'constant-name': options });
512
- const result = detector.checkName(constant_name, 'constant-name');
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');
513
920
 
514
921
  if (!result.valid) {
515
922
  result.errors.forEach(error => {
516
923
  context.report({
517
- node: node.id,
518
- message: `常量名"${constant_name}"不符合规范: ${error}`
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}`
519
992
  });
520
993
  });
521
994
  }
@@ -528,7 +1001,7 @@ const constantNameRule = {
528
1001
  /**
529
1002
  * 私有成员命名规则
530
1003
  */
531
- const privateNamingRule = {
1004
+ const private_naming_rule = {
532
1005
  meta: {
533
1006
  type: 'suggestion',
534
1007
  docs: {
@@ -544,7 +1017,9 @@ const privateNamingRule = {
544
1017
  min: { type: 'number' },
545
1018
  max: { type: 'number' },
546
1019
  message: { type: 'string' },
547
- single_word_preferred: { type: 'boolean' }
1020
+ single_word: { type: 'boolean' },
1021
+ single_word_len: { type: 'number' },
1022
+ styles: { type: 'array', items: { type: 'string' } }
548
1023
  },
549
1024
  additionalProperties: false
550
1025
  }
@@ -556,7 +1031,7 @@ const privateNamingRule = {
556
1031
  MethodDefinition(node) {
557
1032
  const method_name = node.key.name;
558
1033
  if (method_name.startsWith('_')) {
559
- const detector = new NamingDetector({ 'private-naming': options });
1034
+ const detector = new Detector({ 'private-naming': options });
560
1035
  const result = detector.checkName(method_name, 'private-naming');
561
1036
 
562
1037
  if (!result.valid) {
@@ -573,7 +1048,7 @@ const privateNamingRule = {
573
1048
  Property(node) {
574
1049
  if (node.key.type === 'Identifier' && node.key.name.startsWith('_')) {
575
1050
  const private_name = node.key.name;
576
- const detector = new NamingDetector({ 'private-naming': options });
1051
+ const detector = new Detector({ 'private-naming': options });
577
1052
  const result = detector.checkName(private_name, 'private-naming');
578
1053
 
579
1054
  if (!result.valid) {
@@ -593,16 +1068,18 @@ const privateNamingRule = {
593
1068
  /**
594
1069
  * 命名规范ESLint插件
595
1070
  */
596
- const namingRules = {
597
- 'class-name': classNameRule,
598
- 'function-name': functionNameRule,
599
- 'method-name': methodNameRule,
600
- 'variable-name': variableNameRule,
601
- 'constant-name': constantNameRule,
602
- 'private-naming': privateNamingRule
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
603
1080
  };
604
1081
 
605
1082
  module.exports = {
606
- NamingDetector,
607
- rules: namingRules
1083
+ Detector,
1084
+ rules: naming_rules
608
1085
  };