smart-review 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.
- package/README.en-US.md +580 -0
- package/README.md +93 -54
- package/bin/install.js +192 -55
- package/bin/review.js +42 -47
- package/index.js +0 -1
- package/lib/ai-client-pool.js +63 -25
- package/lib/ai-client.js +262 -415
- package/lib/config-loader.js +35 -7
- package/lib/default-config.js +33 -24
- package/lib/reviewer.js +267 -97
- package/lib/segmented-analyzer.js +102 -126
- package/lib/utils/git-diff-parser.js +9 -8
- package/lib/utils/i18n.js +986 -0
- package/package.json +2 -10
- package/templates/rules/en-US/best-practices.js +111 -0
- package/templates/rules/en-US/performance.js +123 -0
- package/templates/rules/en-US/security.js +311 -0
- package/templates/rules/zh-CN/best-practices.js +111 -0
- package/templates/rules/zh-CN/performance.js +123 -0
- package/templates/rules/zh-CN/security.js +311 -0
- package/templates/smart-review.json +2 -1
package/lib/config-loader.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { defaultConfig, defaultRules } from './default-config.js';
|
|
5
5
|
import { logger } from './utils/logger.js';
|
|
6
|
+
import { t } from './utils/i18n.js';
|
|
6
7
|
|
|
7
8
|
export class ConfigLoader {
|
|
8
9
|
constructor(projectRoot) {
|
|
@@ -51,7 +52,7 @@ export class ConfigLoader {
|
|
|
51
52
|
externalConfig = JSON.parse(configContent);
|
|
52
53
|
|
|
53
54
|
} catch (error) {
|
|
54
|
-
logger.warn('
|
|
55
|
+
logger.warn(t(undefined, 'external_config_parse_failed_warn', { error: error?.message || String(error) }));
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -72,7 +73,7 @@ export class ConfigLoader {
|
|
|
72
73
|
// 根据配置决定规则加载策略
|
|
73
74
|
if (config.useExternalRulesOnly) {
|
|
74
75
|
// 仅使用外部规则模式:只返回外部规则,不加载内置规则
|
|
75
|
-
logger.info(
|
|
76
|
+
logger.info(t(undefined, 'use_external_rules_only_info'));
|
|
76
77
|
return externalRules;
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -94,8 +95,9 @@ export class ConfigLoader {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const allRules = Array.from(ruleMap.values());
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
// 根据语言本地化规则的展示字段
|
|
99
|
+
const localizedRules = this.localizeRules(allRules, config);
|
|
100
|
+
return localizedRules;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
async loadExternalRules() {
|
|
@@ -118,7 +120,7 @@ export class ConfigLoader {
|
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
} catch (error) {
|
|
121
|
-
logger.warn('
|
|
123
|
+
logger.warn(t(undefined, 'load_external_rules_failed_warn', { error: error?.message || String(error) }));
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
return externalRules;
|
|
@@ -169,7 +171,7 @@ export class ConfigLoader {
|
|
|
169
171
|
}
|
|
170
172
|
} catch (tempError) {
|
|
171
173
|
// 如果临时文件方法失败,回退到原来的 base64 方法
|
|
172
|
-
logger.warn(
|
|
174
|
+
logger.warn(t(undefined, 'temp_file_method_failed_fallback_info', { error: tempError?.message || String(tempError) }));
|
|
173
175
|
|
|
174
176
|
// 清理可能已创建的临时文件
|
|
175
177
|
try {
|
|
@@ -201,12 +203,38 @@ export class ConfigLoader {
|
|
|
201
203
|
return config.rules || [];
|
|
202
204
|
}
|
|
203
205
|
} catch (error) {
|
|
204
|
-
logger.warn(
|
|
206
|
+
logger.warn(t(undefined, 'load_rule_file_failed_warn', { file: filePath, error: error?.message || String(error) }));
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
return [];
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 按当前语言对规则 name/message/suggestion 进行本地化。
|
|
214
|
+
* 若翻译键不存在则保留原始内容。
|
|
215
|
+
*/
|
|
216
|
+
localizeRules(rules, configOrLocale) {
|
|
217
|
+
if (!Array.isArray(rules) || rules.length === 0) return rules;
|
|
218
|
+
return rules.map((rule) => {
|
|
219
|
+
const id = rule?.id;
|
|
220
|
+
if (!id) return rule;
|
|
221
|
+
|
|
222
|
+
const nameKey = `rule_${id}_name`;
|
|
223
|
+
const msgKey = `rule_${id}_message`;
|
|
224
|
+
const sugKey = `rule_${id}_suggestion`;
|
|
225
|
+
|
|
226
|
+
const maybeName = t(configOrLocale, nameKey);
|
|
227
|
+
const maybeMsg = t(configOrLocale, msgKey);
|
|
228
|
+
const maybeSug = t(configOrLocale, sugKey);
|
|
229
|
+
|
|
230
|
+
const localized = { ...rule };
|
|
231
|
+
if (typeof maybeName === 'string' && maybeName !== nameKey) localized.name = maybeName;
|
|
232
|
+
if (typeof maybeMsg === 'string' && maybeMsg !== msgKey) localized.message = maybeMsg;
|
|
233
|
+
if (typeof maybeSug === 'string' && maybeSug !== sugKey) localized.suggestion = maybeSug;
|
|
234
|
+
return localized;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
210
238
|
deepMerge(target, source) {
|
|
211
239
|
const result = { ...target };
|
|
212
240
|
|
package/lib/default-config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { t } from './utils/i18n.js';
|
|
2
|
+
|
|
1
3
|
export const defaultConfig = {
|
|
2
4
|
// AI配置
|
|
3
5
|
ai: {
|
|
@@ -26,6 +28,13 @@ export const defaultConfig = {
|
|
|
26
28
|
contextMergeLines: 10 // 上下文合并行长度(大概值),用于在diff审查时提供足够的上下文
|
|
27
29
|
},
|
|
28
30
|
|
|
31
|
+
// Git 行为配置
|
|
32
|
+
git: {
|
|
33
|
+
// 在 merge/rebase 过程中,默认仅审查“冲突并由开发者手动处理过”的文件;
|
|
34
|
+
// 对于无冲突的自动合并文件,默认跳过审查。
|
|
35
|
+
skipNonConflictOnMergeRebase: true
|
|
36
|
+
},
|
|
37
|
+
|
|
29
38
|
// 风险等级配置
|
|
30
39
|
riskLevels: {
|
|
31
40
|
critical: { block: true },
|
|
@@ -123,29 +132,29 @@ export const defaultRules = {
|
|
|
123
132
|
security: [
|
|
124
133
|
{
|
|
125
134
|
id: 'SEC001',
|
|
126
|
-
name: '
|
|
135
|
+
name: t(undefined, 'rule_SEC001_name'),
|
|
127
136
|
pattern: '(password|pwd|pass)\\s*=\\s*[\'"][^\'"]+[\'"]',
|
|
128
137
|
risk: 'high',
|
|
129
|
-
message: '
|
|
130
|
-
suggestion: '
|
|
138
|
+
message: t(undefined, 'rule_SEC001_message'),
|
|
139
|
+
suggestion: t(undefined, 'rule_SEC001_suggestion'),
|
|
131
140
|
flags: 'gi'
|
|
132
141
|
},
|
|
133
142
|
{
|
|
134
143
|
id: 'SEC002',
|
|
135
|
-
name: '
|
|
144
|
+
name: t(undefined, 'rule_SEC002_name'),
|
|
136
145
|
pattern: '(execute|query)\\s*\\(\\s*[fF]?[\'"][^\']*\\+.*[\'"]',
|
|
137
146
|
risk: 'critical',
|
|
138
|
-
message: '
|
|
139
|
-
suggestion: '
|
|
147
|
+
message: t(undefined, 'rule_SEC002_message'),
|
|
148
|
+
suggestion: t(undefined, 'rule_SEC002_suggestion'),
|
|
140
149
|
flags: 'gi'
|
|
141
150
|
},
|
|
142
151
|
{
|
|
143
152
|
id: 'SEC003',
|
|
144
|
-
name: '
|
|
153
|
+
name: t(undefined, 'rule_SEC003_name'),
|
|
145
154
|
pattern: 'innerHTML\\s*=|document\\.write\\s*\\(',
|
|
146
155
|
risk: 'high',
|
|
147
|
-
message: '
|
|
148
|
-
suggestion: '
|
|
156
|
+
message: t(undefined, 'rule_SEC003_message'),
|
|
157
|
+
suggestion: t(undefined, 'rule_SEC003_suggestion'),
|
|
149
158
|
flags: 'gi'
|
|
150
159
|
}
|
|
151
160
|
],
|
|
@@ -153,20 +162,20 @@ export const defaultRules = {
|
|
|
153
162
|
performance: [
|
|
154
163
|
{
|
|
155
164
|
id: 'PERF001',
|
|
156
|
-
name: '
|
|
165
|
+
name: t(undefined, 'rule_PERF001_name'),
|
|
157
166
|
pattern: 'for\\s*\\([^)]*\\)\\s*\\{[^}]*\\.(find|query|select)[^}]*\\}',
|
|
158
167
|
risk: 'medium',
|
|
159
|
-
message: '
|
|
160
|
-
suggestion: '
|
|
168
|
+
message: t(undefined, 'rule_PERF001_message'),
|
|
169
|
+
suggestion: t(undefined, 'rule_PERF001_suggestion'),
|
|
161
170
|
flags: 'gi'
|
|
162
171
|
},
|
|
163
172
|
{
|
|
164
173
|
id: 'PERF002',
|
|
165
|
-
name: '
|
|
174
|
+
name: t(undefined, 'rule_PERF002_name'),
|
|
166
175
|
pattern: 'setInterval\\s*\\([^)]*\\)|setTimeout\\s*\\([^)]*\\)',
|
|
167
176
|
risk: 'medium',
|
|
168
|
-
message: '
|
|
169
|
-
suggestion: '
|
|
177
|
+
message: t(undefined, 'rule_PERF002_message'),
|
|
178
|
+
suggestion: t(undefined, 'rule_PERF002_suggestion'),
|
|
170
179
|
flags: 'gi'
|
|
171
180
|
}
|
|
172
181
|
],
|
|
@@ -174,29 +183,29 @@ export const defaultRules = {
|
|
|
174
183
|
'best-practices': [
|
|
175
184
|
{
|
|
176
185
|
id: 'BP001',
|
|
177
|
-
name: '
|
|
186
|
+
name: t(undefined, 'rule_BP001_name'),
|
|
178
187
|
pattern: 'console\\.log|print\\(|alert\\(',
|
|
179
188
|
risk: 'low',
|
|
180
|
-
message: '
|
|
181
|
-
suggestion: '
|
|
189
|
+
message: t(undefined, 'rule_BP001_message'),
|
|
190
|
+
suggestion: t(undefined, 'rule_BP001_suggestion'),
|
|
182
191
|
flags: 'gi'
|
|
183
192
|
},
|
|
184
193
|
{
|
|
185
194
|
id: 'BP002',
|
|
186
|
-
name: '
|
|
195
|
+
name: t(undefined, 'rule_BP002_name'),
|
|
187
196
|
pattern: '\\b(?<!\\.)(?!(?:0|1|10|12|24|30|60|100|200|201|300|400|401|403|404|500|503|1000|3000|5000|8080|9000)\\b)\\d{3,}(?!\\.\\d)\\b',
|
|
188
197
|
risk: 'low',
|
|
189
|
-
message: '
|
|
190
|
-
suggestion: '
|
|
198
|
+
message: t(undefined, 'rule_BP002_message'),
|
|
199
|
+
suggestion: t(undefined, 'rule_BP002_suggestion'),
|
|
191
200
|
flags: 'g'
|
|
192
201
|
},
|
|
193
202
|
{
|
|
194
203
|
id: 'BP013',
|
|
195
|
-
name: '
|
|
204
|
+
name: t(undefined, 'rule_BP013_name'),
|
|
196
205
|
pattern: '\\bvar\\s+\\w+',
|
|
197
206
|
risk: 'medium',
|
|
198
|
-
message: '
|
|
199
|
-
suggestion: '
|
|
207
|
+
message: t(undefined, 'rule_BP013_message'),
|
|
208
|
+
suggestion: t(undefined, 'rule_BP013_suggestion'),
|
|
200
209
|
flags: 'gi'
|
|
201
210
|
}
|
|
202
211
|
]
|