smart-review 1.0.1 → 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.
- package/README.en-US.md +580 -0
- package/README.md +93 -54
- package/bin/install.js +419 -280
- 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 +42 -32
- package/lib/reviewer.js +289 -97
- package/lib/segmented-analyzer.js +102 -126
- package/lib/utils/git-diff-parser.js +9 -8
- package/lib/utils/i18n.js +980 -0
- package/package.json +2 -10
- package/templates/rules/en-US/best-practices.js +123 -0
- package/templates/rules/en-US/performance.js +136 -0
- package/templates/rules/en-US/security.js +345 -0
- package/templates/rules/zh-CN/best-practices.js +123 -0
- package/templates/rules/zh-CN/performance.js +136 -0
- package/templates/rules/zh-CN/security.js +345 -0
- package/templates/smart-review.json +5 -2
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: {
|
|
@@ -123,81 +125,89 @@ export const defaultRules = {
|
|
|
123
125
|
security: [
|
|
124
126
|
{
|
|
125
127
|
id: 'SEC001',
|
|
126
|
-
name: '
|
|
128
|
+
name: t(undefined, 'rule_SEC001_name'),
|
|
127
129
|
pattern: '(password|pwd|pass)\\s*=\\s*[\'"][^\'"]+[\'"]',
|
|
128
130
|
risk: 'high',
|
|
129
|
-
message: '
|
|
130
|
-
suggestion: '
|
|
131
|
-
flags: 'gi'
|
|
131
|
+
message: t(undefined, 'rule_SEC001_message'),
|
|
132
|
+
suggestion: t(undefined, 'rule_SEC001_suggestion'),
|
|
133
|
+
flags: 'gi',
|
|
134
|
+
extensions: ['.js', '.ts', '.py', '.java', '.rb', '.php', '.cs', '.go']
|
|
132
135
|
},
|
|
133
136
|
{
|
|
134
137
|
id: 'SEC002',
|
|
135
|
-
name: '
|
|
138
|
+
name: t(undefined, 'rule_SEC002_name'),
|
|
136
139
|
pattern: '(execute|query)\\s*\\(\\s*[fF]?[\'"][^\']*\\+.*[\'"]',
|
|
137
140
|
risk: 'critical',
|
|
138
|
-
message: '
|
|
139
|
-
suggestion: '
|
|
140
|
-
flags: 'gi'
|
|
141
|
+
message: t(undefined, 'rule_SEC002_message'),
|
|
142
|
+
suggestion: t(undefined, 'rule_SEC002_suggestion'),
|
|
143
|
+
flags: 'gi',
|
|
144
|
+
extensions: ['.js', '.ts', '.java', '.cs', '.php', '.py', '.rb', '.go']
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
id: 'SEC003',
|
|
144
|
-
name: '
|
|
148
|
+
name: t(undefined, 'rule_SEC003_name'),
|
|
145
149
|
pattern: 'innerHTML\\s*=|document\\.write\\s*\\(',
|
|
146
150
|
risk: 'high',
|
|
147
|
-
message: '
|
|
148
|
-
suggestion: '
|
|
149
|
-
flags: 'gi'
|
|
151
|
+
message: t(undefined, 'rule_SEC003_message'),
|
|
152
|
+
suggestion: t(undefined, 'rule_SEC003_suggestion'),
|
|
153
|
+
flags: 'gi',
|
|
154
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte']
|
|
150
155
|
}
|
|
151
156
|
],
|
|
152
157
|
|
|
153
158
|
performance: [
|
|
154
159
|
{
|
|
155
160
|
id: 'PERF001',
|
|
156
|
-
name: '
|
|
161
|
+
name: t(undefined, 'rule_PERF001_name'),
|
|
157
162
|
pattern: 'for\\s*\\([^)]*\\)\\s*\\{[^}]*\\.(find|query|select)[^}]*\\}',
|
|
158
163
|
risk: 'medium',
|
|
159
|
-
message: '
|
|
160
|
-
suggestion: '
|
|
161
|
-
flags: 'gi'
|
|
164
|
+
message: t(undefined, 'rule_PERF001_message'),
|
|
165
|
+
suggestion: t(undefined, 'rule_PERF001_suggestion'),
|
|
166
|
+
flags: 'gi',
|
|
167
|
+
extensions: ['.js', '.ts', '.java', '.py', '.rb', '.php', '.cs', '.go']
|
|
162
168
|
},
|
|
163
169
|
{
|
|
164
170
|
id: 'PERF002',
|
|
165
|
-
name: '
|
|
171
|
+
name: t(undefined, 'rule_PERF002_name'),
|
|
166
172
|
pattern: 'setInterval\\s*\\([^)]*\\)|setTimeout\\s*\\([^)]*\\)',
|
|
167
173
|
risk: 'medium',
|
|
168
|
-
message: '
|
|
169
|
-
suggestion: '
|
|
170
|
-
flags: 'gi'
|
|
174
|
+
message: t(undefined, 'rule_PERF002_message'),
|
|
175
|
+
suggestion: t(undefined, 'rule_PERF002_suggestion'),
|
|
176
|
+
flags: 'gi',
|
|
177
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte']
|
|
171
178
|
}
|
|
172
179
|
],
|
|
173
180
|
|
|
174
181
|
'best-practices': [
|
|
175
182
|
{
|
|
176
183
|
id: 'BP001',
|
|
177
|
-
name: '
|
|
184
|
+
name: t(undefined, 'rule_BP001_name'),
|
|
178
185
|
pattern: 'console\\.log|print\\(|alert\\(',
|
|
179
186
|
risk: 'low',
|
|
180
|
-
message: '
|
|
181
|
-
suggestion: '
|
|
182
|
-
flags: 'gi'
|
|
187
|
+
message: t(undefined, 'rule_BP001_message'),
|
|
188
|
+
suggestion: t(undefined, 'rule_BP001_suggestion'),
|
|
189
|
+
flags: 'gi',
|
|
190
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.py', '.php', '.rb']
|
|
183
191
|
},
|
|
184
192
|
{
|
|
185
193
|
id: 'BP002',
|
|
186
|
-
name: '
|
|
194
|
+
name: t(undefined, 'rule_BP002_name'),
|
|
187
195
|
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
196
|
risk: 'low',
|
|
189
|
-
message: '
|
|
190
|
-
suggestion: '
|
|
191
|
-
flags: 'g'
|
|
197
|
+
message: t(undefined, 'rule_BP002_message'),
|
|
198
|
+
suggestion: t(undefined, 'rule_BP002_suggestion'),
|
|
199
|
+
flags: 'g',
|
|
200
|
+
extensions: ['.js', '.ts', '.java', '.cs', '.php', '.py', '.rb', '.go']
|
|
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: '
|
|
200
|
-
flags: 'gi'
|
|
207
|
+
message: t(undefined, 'rule_BP013_message'),
|
|
208
|
+
suggestion: t(undefined, 'rule_BP013_suggestion'),
|
|
209
|
+
flags: 'gi',
|
|
210
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx']
|
|
201
211
|
}
|
|
202
212
|
]
|
|
203
213
|
};
|