autosnippet 3.0.13 → 3.1.1
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/bin/api-server.js +2 -0
- package/bin/cli.js +24 -19
- package/config/default.json +1 -1
- package/lib/bootstrap.js +4 -4
- package/lib/cli/SetupService.js +29 -29
- package/lib/cli/UpgradeService.js +3 -2
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/ast/ensure-grammars.js +1 -1
- package/lib/core/ast/index.js +62 -11
- package/lib/core/ast/lang-dart.js +27 -21
- package/lib/core/ast/lang-go.js +6 -20
- package/lib/core/ast/lang-rust.js +53 -28
- package/lib/core/ast/parser-init.js +9 -5
- package/lib/core/discovery/DartDiscoverer.js +4 -10
- package/lib/core/discovery/GoDiscoverer.js +45 -25
- package/lib/core/discovery/NodeDiscoverer.js +1 -3
- package/lib/core/discovery/PythonDiscoverer.js +7 -1
- package/lib/core/discovery/RustDiscoverer.js +111 -38
- package/lib/core/discovery/index.js +2 -2
- package/lib/core/enhancement/django-enhancement.js +10 -4
- package/lib/core/enhancement/fastapi-enhancement.js +16 -9
- package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
- package/lib/core/enhancement/go-web-enhancement.js +3 -6
- package/lib/core/enhancement/ml-enhancement.js +6 -3
- package/lib/core/enhancement/nextjs-enhancement.js +17 -7
- package/lib/core/enhancement/node-server-enhancement.js +4 -2
- package/lib/core/enhancement/react-enhancement.js +6 -3
- package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
- package/lib/core/enhancement/rust-web-enhancement.js +13 -7
- package/lib/core/enhancement/vue-enhancement.js +10 -5
- package/lib/external/ai/AiFactory.js +3 -1
- package/lib/external/ai/AiProvider.js +3 -1
- package/lib/external/mcp/McpServer.js +2 -0
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
- package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
- package/lib/external/mcp/handlers/bootstrap.js +4 -10
- package/lib/external/mcp/handlers/browse.js +6 -2
- package/lib/external/mcp/handlers/guard.js +6 -2
- package/lib/external/mcp/handlers/skill.js +6 -2
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/routes/candidates.js +3 -1
- package/lib/http/routes/extract.js +4 -5
- package/lib/http/routes/guardRules.js +1 -1
- package/lib/http/routes/modules.js +9 -3
- package/lib/http/routes/skills.js +54 -6
- package/lib/http/routes/violations.js +4 -3
- package/lib/infrastructure/external/ClipboardManager.js +24 -7
- package/lib/infrastructure/external/NativeUi.js +3 -1
- package/lib/infrastructure/external/OpenBrowser.js +1 -0
- package/lib/infrastructure/external/XcodeAutomation.js +5 -5
- package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
- package/lib/injection/ServiceContainer.js +34 -11
- package/lib/platform/ios/index.js +20 -25
- package/lib/platform/ios/routes/spm.js +6 -3
- package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
- package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
- package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
- package/lib/platform/ios/spm/SpmService.js +3 -1
- package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
- package/lib/service/automation/FileWatcher.js +1 -3
- package/lib/service/automation/handlers/CreateHandler.js +3 -5
- package/lib/service/automation/handlers/GuardHandler.js +11 -32
- package/lib/service/automation/handlers/SearchHandler.js +9 -9
- package/lib/service/chat/CandidateGuardrail.js +11 -6
- package/lib/service/chat/ChatAgent.js +31 -22
- package/lib/service/chat/HandoffProtocol.js +5 -2
- package/lib/service/chat/tools/composite.js +3 -2
- package/lib/service/chat/tools/index.js +60 -71
- package/lib/service/chat/tools/infrastructure.js +9 -4
- package/lib/service/chat/tools/lifecycle.js +22 -5
- package/lib/service/chat/tools/project-access.js +5 -9
- package/lib/service/chat/tools.js +1 -2
- package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
- package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
- package/lib/service/cursor/KnowledgeCompressor.js +16 -7
- package/lib/service/guard/ComplianceReporter.js +5 -2
- package/lib/service/guard/GuardCheckEngine.js +53 -26
- package/lib/service/guard/GuardCodeChecks.js +217 -188
- package/lib/service/guard/GuardCrossFileChecks.js +203 -184
- package/lib/service/guard/GuardPatternUtils.js +17 -10
- package/lib/service/module/ModuleService.js +180 -56
- package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
- package/lib/service/snippet/SnippetFactory.js +3 -3
- package/lib/service/snippet/SnippetInstaller.js +35 -11
- package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
- package/lib/service/wiki/WikiGenerator.js +67 -40
- package/lib/service/wiki/WikiRenderers.js +105 -80
- package/lib/service/wiki/WikiUtils.js +217 -80
- package/lib/shared/LanguageService.js +111 -53
- package/lib/shared/PathGuard.js +0 -8
- package/package.json +3 -9
- package/scripts/bench-real-projects.mjs +29 -29
- package/scripts/generate-recipe-drafts.js +17 -27
- package/scripts/init-snippets.js +43 -24
- package/scripts/install-vscode-copilot.js +3 -19
- package/scripts/setup-mcp-config.js +0 -4
|
@@ -30,7 +30,11 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
30
30
|
// ── ObjC ──
|
|
31
31
|
if (language === 'objc') {
|
|
32
32
|
// KVO 观察者未移除检查
|
|
33
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
!isDisabled('objc-kvo-missing-remove') &&
|
|
35
|
+
code.includes('addObserver') &&
|
|
36
|
+
!code.includes('removeObserver')
|
|
37
|
+
) {
|
|
34
38
|
const lineIdx = lines.findIndex((l) => /addObserver/.test(l));
|
|
35
39
|
violations.push({
|
|
36
40
|
ruleId: 'objc-kvo-missing-remove',
|
|
@@ -44,35 +48,35 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
44
48
|
|
|
45
49
|
// ObjC Category 重名检查 (同文件)
|
|
46
50
|
if (!isDisabled('objc-duplicate-category')) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
for (const [key, occs] of Object.entries(categories)) {
|
|
62
|
-
if (occs.length <= 1) {
|
|
63
|
-
continue;
|
|
51
|
+
const categoryRegex = /@interface\s+(\w+)\s*\(\s*(\w+)\s*\)/g;
|
|
52
|
+
const categories = {};
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
categoryRegex.lastIndex = 0;
|
|
55
|
+
const m = categoryRegex.exec(lines[i]);
|
|
56
|
+
if (!m) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const key = `${m[1]}(${m[2]})`;
|
|
60
|
+
if (!categories[key]) {
|
|
61
|
+
categories[key] = [];
|
|
62
|
+
}
|
|
63
|
+
categories[key].push({ line: i + 1, snippet: lines[i].trim().slice(0, 120) });
|
|
64
64
|
}
|
|
65
|
-
for (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
for (const [key, occs] of Object.entries(categories)) {
|
|
66
|
+
if (occs.length <= 1) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (let j = 1; j < occs.length; j++) {
|
|
70
|
+
violations.push({
|
|
71
|
+
ruleId: 'objc-duplicate-category',
|
|
72
|
+
message: `同文件内 Category 重名:${key},首次在第 ${occs[0].line} 行`,
|
|
73
|
+
severity: 'warning',
|
|
74
|
+
line: occs[j].line,
|
|
75
|
+
snippet: occs[j].snippet,
|
|
76
|
+
dimension: 'file',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
|
-
}
|
|
76
80
|
} // end isDisabled('objc-duplicate-category')
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -80,7 +84,12 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
80
84
|
if (language === 'javascript' || language === 'typescript') {
|
|
81
85
|
// Promise 未处理 rejection 检查
|
|
82
86
|
// 文件中存在 .then() 但没有对应的 .catch() 或 try-catch
|
|
83
|
-
if (
|
|
87
|
+
if (
|
|
88
|
+
!isDisabled('js-unhandled-promise') &&
|
|
89
|
+
code.includes('.then(') &&
|
|
90
|
+
!code.includes('.catch(') &&
|
|
91
|
+
!code.includes('try')
|
|
92
|
+
) {
|
|
84
93
|
const thenLines = [];
|
|
85
94
|
for (let i = 0; i < lines.length; i++) {
|
|
86
95
|
if (/\.then\s*\(/.test(lines[i])) {
|
|
@@ -104,28 +113,28 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
104
113
|
if (language === 'go') {
|
|
105
114
|
// defer 在循环内检查 — defer 在函数结束时才执行,循环内 defer 可能资源泄露
|
|
106
115
|
if (!isDisabled('go-defer-in-loop')) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
let inLoop = false;
|
|
117
|
+
for (let i = 0; i < lines.length; i++) {
|
|
118
|
+
const trimmed = lines[i].trim();
|
|
119
|
+
if (/^for\s/.test(trimmed) || /^for\s*\{/.test(trimmed)) {
|
|
120
|
+
inLoop = true;
|
|
121
|
+
}
|
|
122
|
+
if (inLoop && /^\s*defer\s/.test(lines[i])) {
|
|
123
|
+
violations.push({
|
|
124
|
+
ruleId: 'go-defer-in-loop',
|
|
125
|
+
message: 'defer 在循环内会延迟到函数返回时才执行,可能导致资源泄露或大量堆积',
|
|
126
|
+
severity: 'warning',
|
|
127
|
+
line: i + 1,
|
|
128
|
+
snippet: lines[i].trim().slice(0, 120),
|
|
129
|
+
dimension: 'file',
|
|
130
|
+
fixSuggestion: '将循环体提取到独立函数中,或手动调用 Close()',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// 简化: 遇到 } 且缩进回到顶层,认为循环结束
|
|
134
|
+
if (inLoop && trimmed === '}' && (lines[i].match(/^\t/) || lines[i].match(/^}/))) {
|
|
135
|
+
inLoop = false;
|
|
136
|
+
}
|
|
127
137
|
}
|
|
128
|
-
}
|
|
129
138
|
} // end isDisabled('go-defer-in-loop')
|
|
130
139
|
}
|
|
131
140
|
|
|
@@ -133,22 +142,26 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
133
142
|
if (language === 'python') {
|
|
134
143
|
// 文件中同时存在 tab 和 space 缩进
|
|
135
144
|
if (!isDisabled('py-mixed-indentation')) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
let hasTab = false;
|
|
146
|
+
let hasSpace = false;
|
|
147
|
+
for (let i = 0; i < Math.min(lines.length, 200); i++) {
|
|
148
|
+
if (/^\t/.test(lines[i])) {
|
|
149
|
+
hasTab = true;
|
|
150
|
+
}
|
|
151
|
+
if (/^ {2,}/.test(lines[i]) && !/^\t/.test(lines[i])) {
|
|
152
|
+
hasSpace = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (hasTab && hasSpace) {
|
|
156
|
+
violations.push({
|
|
157
|
+
ruleId: 'py-mixed-indentation',
|
|
158
|
+
message: '文件混用 tab 和 space 缩进,Python 对此敏感,请统一使用 space',
|
|
159
|
+
severity: 'warning',
|
|
160
|
+
line: 1,
|
|
161
|
+
snippet: '',
|
|
162
|
+
dimension: 'file',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
152
165
|
} // end isDisabled('py-mixed-indentation')
|
|
153
166
|
}
|
|
154
167
|
|
|
@@ -156,27 +169,29 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
156
169
|
if (language === 'swift') {
|
|
157
170
|
// 强制解包滥用检查: 连续多行使用 ! 强制解包(单行已被正则规则覆盖,这里检查文件级滥用)
|
|
158
171
|
if (!isDisabled('swift-excessive-force-unwrap')) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
let forceUnwrapCount = 0;
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
// 排除 != 和 !== 运算符, 以及注释行
|
|
175
|
+
const trimmed = lines[i].trimStart();
|
|
176
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*')) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// 匹配 variable! 或 expression!. 形式,但排除 !=
|
|
180
|
+
if (/\w!(?!=)[.\s,)\]]/.test(lines[i]) || /\w!$/.test(lines[i].trim())) {
|
|
181
|
+
forceUnwrapCount++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (forceUnwrapCount > threshold('swift-excessive-force-unwrap', 5)) {
|
|
185
|
+
violations.push({
|
|
186
|
+
ruleId: 'swift-excessive-force-unwrap',
|
|
187
|
+
message: `文件包含 ${forceUnwrapCount} 处强制解包 (!),建议使用 guard let / if let 安全解包`,
|
|
188
|
+
severity: 'warning',
|
|
189
|
+
line: 1,
|
|
190
|
+
snippet: `${forceUnwrapCount} force unwraps detected`,
|
|
191
|
+
dimension: 'file',
|
|
192
|
+
fixSuggestion: '使用 guard let value = optional else { return } 替代 optional!',
|
|
193
|
+
});
|
|
167
194
|
}
|
|
168
|
-
}
|
|
169
|
-
if (forceUnwrapCount > threshold('swift-excessive-force-unwrap', 5)) {
|
|
170
|
-
violations.push({
|
|
171
|
-
ruleId: 'swift-excessive-force-unwrap',
|
|
172
|
-
message: `文件包含 ${forceUnwrapCount} 处强制解包 (!),建议使用 guard let / if let 安全解包`,
|
|
173
|
-
severity: 'warning',
|
|
174
|
-
line: 1,
|
|
175
|
-
snippet: `${forceUnwrapCount} force unwraps detected`,
|
|
176
|
-
dimension: 'file',
|
|
177
|
-
fixSuggestion: '使用 guard let value = optional else { return } 替代 optional!',
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
195
|
} // end isDisabled('swift-excessive-force-unwrap')
|
|
181
196
|
}
|
|
182
197
|
|
|
@@ -184,46 +199,49 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
184
199
|
if (language === 'java') {
|
|
185
200
|
// 资源泄露检查: new InputStream/Connection/Reader 未在 try-with-resources 或 finally 中关闭
|
|
186
201
|
if (!isDisabled('java-resource-leak')) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
const resourcePatterns =
|
|
203
|
+
/new\s+(FileInputStream|FileOutputStream|BufferedReader|BufferedWriter|Connection|Socket|FileReader|FileWriter|Scanner)\s*\(/;
|
|
204
|
+
const hasResourceAlloc = lines.some((l) => resourcePatterns.test(l));
|
|
205
|
+
const hasTryWithResource = code.includes('try (') || code.includes('try(');
|
|
206
|
+
const hasFinallyClose = code.includes('finally') && code.includes('.close()');
|
|
207
|
+
if (hasResourceAlloc && !hasTryWithResource && !hasFinallyClose) {
|
|
208
|
+
const lineIdx = lines.findIndex((l) => resourcePatterns.test(l));
|
|
209
|
+
violations.push({
|
|
210
|
+
ruleId: 'java-resource-leak',
|
|
211
|
+
message: '资源分配后未使用 try-with-resources 或 finally/close(),可能造成资源泄露',
|
|
212
|
+
severity: 'warning',
|
|
213
|
+
line: lineIdx >= 0 ? lineIdx + 1 : 1,
|
|
214
|
+
snippet: lineIdx >= 0 ? lines[lineIdx].trim().slice(0, 120) : '',
|
|
215
|
+
dimension: 'file',
|
|
216
|
+
fixSuggestion: '使用 try (var res = new Resource()) { ... } 自动关闭资源',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
203
219
|
} // end isDisabled('java-resource-leak')
|
|
204
220
|
|
|
205
221
|
// synchronized 在非 final 字段上 — 可能导致锁对象被替换
|
|
206
222
|
if (!isDisabled('java-sync-non-final')) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const syncRegex = /synchronized\s*\(\s*(\w+)\s*\)/;
|
|
224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
225
|
+
const m = syncRegex.exec(lines[i]);
|
|
226
|
+
if (m && m[1] !== 'this' && !m[1].endsWith('.class')) {
|
|
227
|
+
// 检查该变量是否声明为 final
|
|
228
|
+
const varName = m[1];
|
|
229
|
+
const declaredFinal = lines.some((l) =>
|
|
230
|
+
new RegExp(`final\\s+\\w+.*\\b${varName}\\b`).test(l)
|
|
231
|
+
);
|
|
232
|
+
if (!declaredFinal) {
|
|
233
|
+
violations.push({
|
|
234
|
+
ruleId: 'java-sync-non-final',
|
|
235
|
+
message: `synchronized 使用了非 final 变量 "${varName}",锁对象可能被重新赋值`,
|
|
236
|
+
severity: 'warning',
|
|
237
|
+
line: i + 1,
|
|
238
|
+
snippet: lines[i].trim().slice(0, 120),
|
|
239
|
+
dimension: 'file',
|
|
240
|
+
fixSuggestion: `将 ${varName} 声明为 private final`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
224
243
|
}
|
|
225
244
|
}
|
|
226
|
-
}
|
|
227
245
|
} // end isDisabled('java-sync-non-final')
|
|
228
246
|
}
|
|
229
247
|
|
|
@@ -231,19 +249,19 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
231
249
|
if (language === 'kotlin') {
|
|
232
250
|
// GlobalScope.launch — 生命周期泄露风险
|
|
233
251
|
if (!isDisabled('kotlin-global-scope')) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
for (let i = 0; i < lines.length; i++) {
|
|
253
|
+
if (/GlobalScope\s*\.\s*(launch|async)/.test(lines[i])) {
|
|
254
|
+
violations.push({
|
|
255
|
+
ruleId: 'kotlin-global-scope',
|
|
256
|
+
message: 'GlobalScope.launch/async 不绑定生命周期,可能导致协程泄露',
|
|
257
|
+
severity: 'warning',
|
|
258
|
+
line: i + 1,
|
|
259
|
+
snippet: lines[i].trim().slice(0, 120),
|
|
260
|
+
dimension: 'file',
|
|
261
|
+
fixSuggestion: '使用 viewModelScope、lifecycleScope 或自定义 CoroutineScope 替代',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
245
264
|
}
|
|
246
|
-
}
|
|
247
265
|
} // end isDisabled('kotlin-global-scope')
|
|
248
266
|
|
|
249
267
|
// runBlocking 在 main/UI 线程 — 可能冻结 UI
|
|
@@ -267,65 +285,76 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
267
285
|
if (language === 'rust') {
|
|
268
286
|
// .unwrap() 滥用检查 — 生产代码应使用 ? 或 expect()
|
|
269
287
|
if (!isDisabled('rust-excessive-unwrap')) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (
|
|
288
|
+
let unwrapCount = 0;
|
|
289
|
+
const unwrapLines = [];
|
|
290
|
+
for (let i = 0; i < lines.length; i++) {
|
|
291
|
+
const trimmed = lines[i].trimStart();
|
|
292
|
+
// 跳过测试代码和注释
|
|
293
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#[test]')) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (/\.unwrap\(\)/.test(lines[i])) {
|
|
297
|
+
unwrapCount++;
|
|
298
|
+
if (unwrapLines.length < 3) {
|
|
299
|
+
unwrapLines.push(i);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (unwrapCount > threshold('rust-excessive-unwrap', 3)) {
|
|
304
|
+
violations.push({
|
|
305
|
+
ruleId: 'rust-excessive-unwrap',
|
|
306
|
+
message: `文件包含 ${unwrapCount} 处 .unwrap(),生产代码建议使用 ? 操作符或 .expect("reason")`,
|
|
307
|
+
severity: 'warning',
|
|
308
|
+
line: unwrapLines[0] + 1,
|
|
309
|
+
snippet: lines[unwrapLines[0]].trim().slice(0, 120),
|
|
310
|
+
dimension: 'file',
|
|
311
|
+
fixSuggestion: '使用 ? 操作符向上传播错误,或 .expect("具体原因") 提供崩溃上下文',
|
|
312
|
+
});
|
|
279
313
|
}
|
|
280
|
-
}
|
|
281
|
-
if (unwrapCount > threshold('rust-excessive-unwrap', 3)) {
|
|
282
|
-
violations.push({
|
|
283
|
-
ruleId: 'rust-excessive-unwrap',
|
|
284
|
-
message: `文件包含 ${unwrapCount} 处 .unwrap(),生产代码建议使用 ? 操作符或 .expect("reason")`,
|
|
285
|
-
severity: 'warning',
|
|
286
|
-
line: unwrapLines[0] + 1,
|
|
287
|
-
snippet: lines[unwrapLines[0]].trim().slice(0, 120),
|
|
288
|
-
dimension: 'file',
|
|
289
|
-
fixSuggestion: '使用 ? 操作符向上传播错误,或 .expect("具体原因") 提供崩溃上下文',
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
314
|
} // end isDisabled('rust-excessive-unwrap')
|
|
293
315
|
|
|
294
316
|
// unsafe 块数量检查
|
|
295
317
|
if (!isDisabled('rust-excessive-unsafe')) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
318
|
+
let unsafeCount = 0;
|
|
319
|
+
for (let i = 0; i < lines.length; i++) {
|
|
320
|
+
if (/\bunsafe\s*\{/.test(lines[i]) || /\bunsafe\s+fn\b/.test(lines[i])) {
|
|
321
|
+
unsafeCount++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (unsafeCount > threshold('rust-excessive-unsafe', 3)) {
|
|
325
|
+
violations.push({
|
|
326
|
+
ruleId: 'rust-excessive-unsafe',
|
|
327
|
+
message: `文件包含 ${unsafeCount} 处 unsafe 块/函数,请审查是否都必要`,
|
|
328
|
+
severity: 'warning',
|
|
329
|
+
line: 1,
|
|
330
|
+
snippet: `${unsafeCount} unsafe blocks detected`,
|
|
331
|
+
dimension: 'file',
|
|
332
|
+
fixSuggestion: '尽量使用 safe abstraction 封装 unsafe 代码,减少 unsafe 暴露面',
|
|
333
|
+
});
|
|
300
334
|
}
|
|
301
|
-
}
|
|
302
|
-
if (unsafeCount > threshold('rust-excessive-unsafe', 3)) {
|
|
303
|
-
violations.push({
|
|
304
|
-
ruleId: 'rust-excessive-unsafe',
|
|
305
|
-
message: `文件包含 ${unsafeCount} 处 unsafe 块/函数,请审查是否都必要`,
|
|
306
|
-
severity: 'warning',
|
|
307
|
-
line: 1,
|
|
308
|
-
snippet: `${unsafeCount} unsafe blocks detected`,
|
|
309
|
-
dimension: 'file',
|
|
310
|
-
fixSuggestion: '尽量使用 safe abstraction 封装 unsafe 代码,减少 unsafe 暴露面',
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
335
|
} // end isDisabled('rust-excessive-unsafe')
|
|
314
336
|
}
|
|
315
337
|
|
|
316
338
|
// ── Dart ──
|
|
317
339
|
if (language === 'dart') {
|
|
318
340
|
// setState after dispose — Flutter 常见内存泄露
|
|
319
|
-
if (
|
|
341
|
+
if (
|
|
342
|
+
!isDisabled('dart-setstate-after-dispose') &&
|
|
343
|
+
code.includes('setState') &&
|
|
344
|
+
code.includes('dispose')
|
|
345
|
+
) {
|
|
320
346
|
// 检查 dispose 方法后是否还有 async 回调中的 setState
|
|
321
|
-
const disposeIdx = lines.findIndex(
|
|
347
|
+
const disposeIdx = lines.findIndex(
|
|
348
|
+
(l) => /void\s+dispose\s*\(/.test(l) || /\bsuper\.dispose\(\)/.test(l)
|
|
349
|
+
);
|
|
322
350
|
if (disposeIdx >= 0) {
|
|
323
351
|
// 检查是否有 mounted 检查保护
|
|
324
352
|
const hasMountedCheck = code.includes('if (mounted)') || code.includes('if (!mounted)');
|
|
325
353
|
if (!hasMountedCheck) {
|
|
326
354
|
violations.push({
|
|
327
355
|
ruleId: 'dart-setstate-after-dispose',
|
|
328
|
-
message:
|
|
356
|
+
message:
|
|
357
|
+
'存在 setState 调用但未检查 mounted 状态,异步回调可能在 dispose 后触发 setState',
|
|
329
358
|
severity: 'warning',
|
|
330
359
|
line: disposeIdx + 1,
|
|
331
360
|
snippet: lines[disposeIdx].trim().slice(0, 120),
|
|
@@ -338,23 +367,23 @@ export function runCodeLevelChecks(code, language, lines, options = {}) {
|
|
|
338
367
|
|
|
339
368
|
// late 变量未初始化风险
|
|
340
369
|
if (!isDisabled('dart-excessive-late')) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
370
|
+
let lateCount = 0;
|
|
371
|
+
for (let i = 0; i < lines.length; i++) {
|
|
372
|
+
if (/\blate\s+(?!final\b)\w+/.test(lines[i]) && !lines[i].includes('=')) {
|
|
373
|
+
lateCount++;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (lateCount > threshold('dart-excessive-late', 3)) {
|
|
377
|
+
violations.push({
|
|
378
|
+
ruleId: 'dart-excessive-late',
|
|
379
|
+
message: `文件有 ${lateCount} 个 late 非 final 变量且无初始值,访问未初始化变量会抛出 LateInitializationError`,
|
|
380
|
+
severity: 'warning',
|
|
381
|
+
line: 1,
|
|
382
|
+
snippet: `${lateCount} late variables without initializer`,
|
|
383
|
+
dimension: 'file',
|
|
384
|
+
fixSuggestion: '考虑使用可空类型 + null 检查,或 late final + 初始化赋值',
|
|
385
|
+
});
|
|
345
386
|
}
|
|
346
|
-
}
|
|
347
|
-
if (lateCount > threshold('dart-excessive-late', 3)) {
|
|
348
|
-
violations.push({
|
|
349
|
-
ruleId: 'dart-excessive-late',
|
|
350
|
-
message: `文件有 ${lateCount} 个 late 非 final 变量且无初始值,访问未初始化变量会抛出 LateInitializationError`,
|
|
351
|
-
severity: 'warning',
|
|
352
|
-
line: 1,
|
|
353
|
-
snippet: `${lateCount} late variables without initializer`,
|
|
354
|
-
dimension: 'file',
|
|
355
|
-
fixSuggestion: '考虑使用可空类型 + null 检查,或 late final + 初始化赋值',
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
387
|
} // end isDisabled('dart-excessive-late')
|
|
359
388
|
}
|
|
360
389
|
|