gitlab-ai-review 3.1.0 → 3.2.0
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/index.js +38 -0
- package/lib/impact-analyzer.js +91 -32
- package/lib/prompt-tools.js +27 -6
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -289,7 +289,26 @@ export class GitLabAIReview {
|
|
|
289
289
|
jsonStr = jsonStr.slice(3, -3).trim();
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// 清理非 JSON 标准的值
|
|
293
|
+
jsonStr = jsonStr
|
|
294
|
+
.replace(/:\s*undefined/g, ': null') // undefined → null
|
|
295
|
+
.replace(/:\s*NaN/g, ': null') // NaN → null
|
|
296
|
+
.replace(/:\s*Infinity/g, ': null'); // Infinity → null
|
|
297
|
+
|
|
292
298
|
const result = JSON.parse(jsonStr);
|
|
299
|
+
|
|
300
|
+
// 验证并过滤无效的 reviews
|
|
301
|
+
if (result.reviews && Array.isArray(result.reviews)) {
|
|
302
|
+
result.reviews = result.reviews.filter(review => {
|
|
303
|
+
// 过滤掉 lineNumber 无效的项
|
|
304
|
+
if (typeof review.lineNumber !== 'number' || isNaN(review.lineNumber)) {
|
|
305
|
+
console.warn(` ⚠️ 跳过无效的审查结果:lineNumber = ${review.lineNumber}`);
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
293
312
|
return result;
|
|
294
313
|
} catch (error) {
|
|
295
314
|
console.error('解析 AI 返回的 JSON 失败:', error.message);
|
|
@@ -446,7 +465,26 @@ export class GitLabAIReview {
|
|
|
446
465
|
jsonStr = jsonStr.slice(3, -3).trim();
|
|
447
466
|
}
|
|
448
467
|
|
|
468
|
+
// 清理非 JSON 标准的值
|
|
469
|
+
jsonStr = jsonStr
|
|
470
|
+
.replace(/:\s*undefined/g, ': null') // undefined → null
|
|
471
|
+
.replace(/:\s*NaN/g, ': null') // NaN → null
|
|
472
|
+
.replace(/:\s*Infinity/g, ': null'); // Infinity → null
|
|
473
|
+
|
|
449
474
|
const result = JSON.parse(jsonStr);
|
|
475
|
+
|
|
476
|
+
// 验证并过滤无效的 reviews
|
|
477
|
+
if (result.reviews && Array.isArray(result.reviews)) {
|
|
478
|
+
result.reviews = result.reviews.filter(review => {
|
|
479
|
+
// 过滤掉 lineNumber 无效的项
|
|
480
|
+
if (typeof review.lineNumber !== 'number' || isNaN(review.lineNumber)) {
|
|
481
|
+
console.warn(` ⚠️ 跳过无效的审查结果:lineNumber = ${review.lineNumber}`);
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
return true;
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
450
488
|
return result;
|
|
451
489
|
} catch (error) {
|
|
452
490
|
console.error('解析 AI 返回的 JSON 失败:', error.message);
|
package/lib/impact-analyzer.js
CHANGED
|
@@ -59,13 +59,14 @@ export function extractImportsExports(content, fileName) {
|
|
|
59
59
|
* 从 diff 中提取变更的函数/类/组件名称
|
|
60
60
|
* @param {string} diff - diff 内容
|
|
61
61
|
* @param {string} fileName - 文件名
|
|
62
|
-
* @returns {Object} 变更的符号列表 { added: [], deleted: [], modified: [] }
|
|
62
|
+
* @returns {Object} 变更的符号列表 { added: [], deleted: [], modified: [], commented: [] }
|
|
63
63
|
*/
|
|
64
64
|
export function extractChangedSymbols(diff, fileName) {
|
|
65
|
-
if (!diff) return { added: [], deleted: [], modified: [] };
|
|
65
|
+
if (!diff) return { added: [], deleted: [], modified: [], commented: [] };
|
|
66
66
|
|
|
67
67
|
const addedSymbols = [];
|
|
68
68
|
const deletedSymbols = [];
|
|
69
|
+
const commentedSymbols = []; // 新增:被注释掉的符号
|
|
69
70
|
const lines = diff.split('\n');
|
|
70
71
|
|
|
71
72
|
// 匹配函数、类、常量定义
|
|
@@ -84,55 +85,113 @@ export function extractChangedSymbols(diff, fileName) {
|
|
|
84
85
|
if (isAddition || isDeletion) {
|
|
85
86
|
const cleanLine = line.substring(1); // 移除 +/- 前缀
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
// 检查是否是"注释掉代码"的情况
|
|
89
|
+
if (isAddition && isDeletion) {
|
|
90
|
+
// 这种情况不会发生在单行中
|
|
91
|
+
} else if (isAddition) {
|
|
92
|
+
// 检查新增的行是否是注释掉的代码
|
|
93
|
+
const trimmedLine = cleanLine.trim();
|
|
94
|
+
|
|
95
|
+
// 判断是否是注释行
|
|
96
|
+
const isComment = trimmedLine.startsWith('//') ||
|
|
97
|
+
trimmedLine.startsWith('/*') ||
|
|
98
|
+
trimmedLine.startsWith('*');
|
|
99
|
+
|
|
100
|
+
if (isComment) {
|
|
101
|
+
// 提取注释后的代码
|
|
102
|
+
const codeAfterComment = trimmedLine.replace(/^\/\/\s*/, '')
|
|
103
|
+
.replace(/^\/\*\s*/, '')
|
|
104
|
+
.replace(/^[\*\s]*/, '');
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
// 检查注释后的内容是否包含定义
|
|
107
|
+
definitionPatterns.forEach(pattern => {
|
|
108
|
+
const match = codeAfterComment.match(pattern);
|
|
109
|
+
if (match) {
|
|
110
|
+
commentedSymbols.push({
|
|
111
|
+
name: match[1],
|
|
112
|
+
type: 'commented',
|
|
113
|
+
line: index + 1,
|
|
114
|
+
originalCode: codeAfterComment,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
// 正常的新增代码
|
|
120
|
+
definitionPatterns.forEach(pattern => {
|
|
121
|
+
const match = cleanLine.match(pattern);
|
|
122
|
+
if (match) {
|
|
123
|
+
addedSymbols.push({
|
|
124
|
+
name: match[1],
|
|
125
|
+
type: 'definition',
|
|
126
|
+
line: index + 1,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
114
129
|
});
|
|
130
|
+
|
|
131
|
+
// 匹配函数调用
|
|
132
|
+
const callPattern = /(\w+)\s*\(/g;
|
|
133
|
+
let callMatch;
|
|
134
|
+
while ((callMatch = callPattern.exec(cleanLine)) !== null) {
|
|
135
|
+
addedSymbols.push({
|
|
136
|
+
name: callMatch[1],
|
|
137
|
+
type: 'usage',
|
|
138
|
+
line: index + 1,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
115
141
|
}
|
|
142
|
+
} else if (isDeletion) {
|
|
143
|
+
// 删除的代码
|
|
144
|
+
definitionPatterns.forEach(pattern => {
|
|
145
|
+
const match = cleanLine.match(pattern);
|
|
146
|
+
if (match) {
|
|
147
|
+
deletedSymbols.push({
|
|
148
|
+
name: match[1],
|
|
149
|
+
type: 'definition',
|
|
150
|
+
line: index + 1,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
116
154
|
}
|
|
117
155
|
}
|
|
118
156
|
});
|
|
119
157
|
|
|
120
|
-
//
|
|
158
|
+
// 检测"注释掉"的模式:删除了代码,然后添加了注释版本
|
|
159
|
+
// 对于每个被注释的符号,检查是否有对应的删除
|
|
160
|
+
const commentedAsDeleted = [];
|
|
161
|
+
commentedSymbols.forEach(commented => {
|
|
162
|
+
const wasDeleted = deletedSymbols.find(del => del.name === commented.name);
|
|
163
|
+
if (wasDeleted) {
|
|
164
|
+
// 这个符号被注释掉了(相当于删除)
|
|
165
|
+
commentedAsDeleted.push({
|
|
166
|
+
name: commented.name,
|
|
167
|
+
type: 'commented-out',
|
|
168
|
+
line: commented.line,
|
|
169
|
+
wasDeleted: true,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 将"被注释掉"的符号也算作删除
|
|
175
|
+
const allDeleted = [...deletedSymbols, ...commentedAsDeleted];
|
|
176
|
+
|
|
177
|
+
// 找出被修改的符号(既被删除又被添加,但不是注释掉)
|
|
121
178
|
const modifiedSymbols = [];
|
|
122
179
|
const deletedNames = new Set(deletedSymbols.map(s => s.name));
|
|
123
180
|
const addedNames = new Set(addedSymbols.map(s => s.name));
|
|
181
|
+
const commentedNames = new Set(commentedAsDeleted.map(s => s.name));
|
|
124
182
|
|
|
125
183
|
deletedNames.forEach(name => {
|
|
126
|
-
if (addedNames.has(name)) {
|
|
184
|
+
if (addedNames.has(name) && !commentedNames.has(name)) {
|
|
127
185
|
modifiedSymbols.push({ name, type: 'modified' });
|
|
128
186
|
}
|
|
129
187
|
});
|
|
130
188
|
|
|
131
189
|
return {
|
|
132
190
|
added: addedSymbols,
|
|
133
|
-
deleted:
|
|
191
|
+
deleted: allDeleted, // 包含注释掉的符号
|
|
134
192
|
modified: modifiedSymbols,
|
|
135
|
-
|
|
193
|
+
commented: commentedAsDeleted, // 单独记录被注释掉的符号
|
|
194
|
+
all: [...addedSymbols, ...allDeleted], // 所有变更的符号
|
|
136
195
|
};
|
|
137
196
|
}
|
|
138
197
|
|
package/lib/prompt-tools.js
CHANGED
|
@@ -132,15 +132,18 @@ export function buildFileReviewWithImpactPrompt(fileName, meaningfulChanges, imp
|
|
|
132
132
|
|
|
133
133
|
// 变更的符号统计
|
|
134
134
|
if (impactAnalysis.changedSymbols) {
|
|
135
|
-
const { added, deleted, modified } = impactAnalysis.changedSymbols;
|
|
135
|
+
const { added, deleted, modified, commented } = impactAnalysis.changedSymbols;
|
|
136
136
|
|
|
137
|
-
if (added.length > 0 || deleted.length > 0 || modified.length > 0) {
|
|
137
|
+
if (added.length > 0 || deleted.length > 0 || modified.length > 0 || (commented && commented.length > 0)) {
|
|
138
138
|
prompt += `**符号变更统计**:\n`;
|
|
139
139
|
if (added.length > 0) {
|
|
140
140
|
prompt += `- 新增: ${added.length} 个\n`;
|
|
141
141
|
}
|
|
142
142
|
if (deleted.length > 0) {
|
|
143
|
-
prompt += `-
|
|
143
|
+
prompt += `- 删除/注释掉: ${deleted.length} 个 ⚠️\n`;
|
|
144
|
+
}
|
|
145
|
+
if (commented && commented.length > 0) {
|
|
146
|
+
prompt += ` (其中 ${commented.length} 个是被注释掉的代码)\n`;
|
|
144
147
|
}
|
|
145
148
|
if (modified.length > 0) {
|
|
146
149
|
prompt += `- 修改: ${modified.length} 个 ⚠️\n`;
|
|
@@ -150,11 +153,12 @@ export function buildFileReviewWithImpactPrompt(fileName, meaningfulChanges, imp
|
|
|
150
153
|
|
|
151
154
|
// 被删除的符号详情
|
|
152
155
|
if (deleted.length > 0) {
|
|
153
|
-
const deletedDefs = deleted.filter(s => s.type === 'definition');
|
|
156
|
+
const deletedDefs = deleted.filter(s => s.type === 'definition' || s.type === 'commented-out');
|
|
154
157
|
if (deletedDefs.length > 0) {
|
|
155
|
-
prompt += `**⚠️
|
|
158
|
+
prompt += `**⚠️ 被删除/注释掉的函数/类/组件** (${deletedDefs.length} 个):\n`;
|
|
156
159
|
deletedDefs.slice(0, 10).forEach(symbol => {
|
|
157
|
-
|
|
160
|
+
const status = symbol.type === 'commented-out' ? ' (注释掉)' : '';
|
|
161
|
+
prompt += `- \`${symbol.name}\`${status}\n`;
|
|
158
162
|
});
|
|
159
163
|
prompt += `\n`;
|
|
160
164
|
}
|
|
@@ -283,6 +287,23 @@ export function buildFileReviewWithImpactPrompt(fileName, meaningfulChanges, imp
|
|
|
283
287
|
}
|
|
284
288
|
\`\`\`
|
|
285
289
|
|
|
290
|
+
**🚨 关键规则:**
|
|
291
|
+
- lineNumber 必须是上面"代码变更"中列出的行号(如第 15 行、第 20 行等)
|
|
292
|
+
- **不要使用其他文件的行号**,即使发现其他文件受影响
|
|
293
|
+
- 如果发现其他文件受影响,在当前变更行的 comment 中说明影响范围
|
|
294
|
+
- lineNumber 必须是数字,不能是 null、undefined 或其他值
|
|
295
|
+
- 如果某个变更没有问题,可以不在 reviews 中包含它
|
|
296
|
+
|
|
297
|
+
**示例:**
|
|
298
|
+
如果删除了第 15 行的函数,且发现其他文件在使用:
|
|
299
|
+
\`\`\`json
|
|
300
|
+
{
|
|
301
|
+
"lineNumber": 15,
|
|
302
|
+
"hasIssue": true,
|
|
303
|
+
"comment": "删除函数 fetchData 会导致以下文件调用失败:components/UserList.vue (第 45 行)、pages/Dashboard.vue (第 78 行)。建议保留该函数或提供替代方案。"
|
|
304
|
+
}
|
|
305
|
+
\`\`\`
|
|
306
|
+
|
|
286
307
|
11. 如果所有代码都没有问题,返回空的 reviews 数组:\`{"reviews": []}\`
|
|
287
308
|
12. 只返回 JSON,不要包含其他文字说明
|
|
288
309
|
13. comment 字段必须使用中文,要简洁明了,重点指出影响范围`;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitlab-ai-review",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "GitLab AI Review SDK with Impact Analysis -
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "GitLab AI Review SDK with Impact Analysis - 支持影响分析、删除符号检测、注释代码识别、文件内部冲突检查、智能文件过滤的智能代码审查工具",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|