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.
Files changed (100) hide show
  1. package/bin/api-server.js +2 -0
  2. package/bin/cli.js +24 -19
  3. package/config/default.json +1 -1
  4. package/lib/bootstrap.js +4 -4
  5. package/lib/cli/SetupService.js +29 -29
  6. package/lib/cli/UpgradeService.js +3 -2
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/ast/ensure-grammars.js +1 -1
  9. package/lib/core/ast/index.js +62 -11
  10. package/lib/core/ast/lang-dart.js +27 -21
  11. package/lib/core/ast/lang-go.js +6 -20
  12. package/lib/core/ast/lang-rust.js +53 -28
  13. package/lib/core/ast/parser-init.js +9 -5
  14. package/lib/core/discovery/DartDiscoverer.js +4 -10
  15. package/lib/core/discovery/GoDiscoverer.js +45 -25
  16. package/lib/core/discovery/NodeDiscoverer.js +1 -3
  17. package/lib/core/discovery/PythonDiscoverer.js +7 -1
  18. package/lib/core/discovery/RustDiscoverer.js +111 -38
  19. package/lib/core/discovery/index.js +2 -2
  20. package/lib/core/enhancement/django-enhancement.js +10 -4
  21. package/lib/core/enhancement/fastapi-enhancement.js +16 -9
  22. package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
  23. package/lib/core/enhancement/go-web-enhancement.js +3 -6
  24. package/lib/core/enhancement/ml-enhancement.js +6 -3
  25. package/lib/core/enhancement/nextjs-enhancement.js +17 -7
  26. package/lib/core/enhancement/node-server-enhancement.js +4 -2
  27. package/lib/core/enhancement/react-enhancement.js +6 -3
  28. package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
  29. package/lib/core/enhancement/rust-web-enhancement.js +13 -7
  30. package/lib/core/enhancement/vue-enhancement.js +10 -5
  31. package/lib/external/ai/AiFactory.js +3 -1
  32. package/lib/external/ai/AiProvider.js +3 -1
  33. package/lib/external/mcp/McpServer.js +2 -0
  34. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
  36. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
  37. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
  38. package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
  39. package/lib/external/mcp/handlers/bootstrap.js +4 -10
  40. package/lib/external/mcp/handlers/browse.js +6 -2
  41. package/lib/external/mcp/handlers/guard.js +6 -2
  42. package/lib/external/mcp/handlers/skill.js +6 -2
  43. package/lib/http/HttpServer.js +1 -1
  44. package/lib/http/routes/candidates.js +3 -1
  45. package/lib/http/routes/extract.js +4 -5
  46. package/lib/http/routes/guardRules.js +1 -1
  47. package/lib/http/routes/modules.js +9 -3
  48. package/lib/http/routes/skills.js +54 -6
  49. package/lib/http/routes/violations.js +4 -3
  50. package/lib/infrastructure/external/ClipboardManager.js +24 -7
  51. package/lib/infrastructure/external/NativeUi.js +3 -1
  52. package/lib/infrastructure/external/OpenBrowser.js +1 -0
  53. package/lib/infrastructure/external/XcodeAutomation.js +5 -5
  54. package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
  55. package/lib/injection/ServiceContainer.js +34 -11
  56. package/lib/platform/ios/index.js +20 -25
  57. package/lib/platform/ios/routes/spm.js +6 -3
  58. package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
  59. package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
  60. package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
  61. package/lib/platform/ios/spm/SpmService.js +3 -1
  62. package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
  63. package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
  64. package/lib/service/automation/FileWatcher.js +1 -3
  65. package/lib/service/automation/handlers/CreateHandler.js +3 -5
  66. package/lib/service/automation/handlers/GuardHandler.js +11 -32
  67. package/lib/service/automation/handlers/SearchHandler.js +9 -9
  68. package/lib/service/chat/CandidateGuardrail.js +11 -6
  69. package/lib/service/chat/ChatAgent.js +31 -22
  70. package/lib/service/chat/HandoffProtocol.js +5 -2
  71. package/lib/service/chat/tools/composite.js +3 -2
  72. package/lib/service/chat/tools/index.js +60 -71
  73. package/lib/service/chat/tools/infrastructure.js +9 -4
  74. package/lib/service/chat/tools/lifecycle.js +22 -5
  75. package/lib/service/chat/tools/project-access.js +5 -9
  76. package/lib/service/chat/tools.js +1 -2
  77. package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
  78. package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
  79. package/lib/service/cursor/KnowledgeCompressor.js +16 -7
  80. package/lib/service/guard/ComplianceReporter.js +5 -2
  81. package/lib/service/guard/GuardCheckEngine.js +53 -26
  82. package/lib/service/guard/GuardCodeChecks.js +217 -188
  83. package/lib/service/guard/GuardCrossFileChecks.js +203 -184
  84. package/lib/service/guard/GuardPatternUtils.js +17 -10
  85. package/lib/service/module/ModuleService.js +180 -56
  86. package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
  87. package/lib/service/snippet/SnippetFactory.js +3 -3
  88. package/lib/service/snippet/SnippetInstaller.js +35 -11
  89. package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
  90. package/lib/service/wiki/WikiGenerator.js +67 -40
  91. package/lib/service/wiki/WikiRenderers.js +105 -80
  92. package/lib/service/wiki/WikiUtils.js +217 -80
  93. package/lib/shared/LanguageService.js +111 -53
  94. package/lib/shared/PathGuard.js +0 -8
  95. package/package.json +3 -9
  96. package/scripts/bench-real-projects.mjs +29 -29
  97. package/scripts/generate-recipe-drafts.js +17 -27
  98. package/scripts/init-snippets.js +43 -24
  99. package/scripts/install-vscode-copilot.js +3 -19
  100. package/scripts/setup-mcp-config.js +0 -4
@@ -16,8 +16,13 @@ export function resolveImportPath(fromDir, importPath) {
16
16
  const parts = `${fromDir}/${importPath}`.split('/');
17
17
  const resolved = [];
18
18
  for (const p of parts) {
19
- if (p === '.' || p === '') continue;
20
- if (p === '..') { resolved.pop(); continue; }
19
+ if (p === '.' || p === '') {
20
+ continue;
21
+ }
22
+ if (p === '..') {
23
+ resolved.pop();
24
+ continue;
25
+ }
21
26
  resolved.push(p);
22
27
  }
23
28
  // 去掉扩展名归一化
@@ -54,253 +59,267 @@ export function runCrossFileChecks(files, options = {}) {
54
59
 
55
60
  // ── ObjC Category 跨文件重名检查 ──
56
61
  if (!isDisabled('objc-cross-file-duplicate-category')) {
57
- const categoryMap = new Map();
58
- const categoryRegex = /@interface\s+(\w+)\s*\(\s*(\w+)\s*\)/g;
62
+ const categoryMap = new Map();
63
+ const categoryRegex = /@interface\s+(\w+)\s*\(\s*(\w+)\s*\)/g;
59
64
 
60
- for (const { path: filePath, content } of files) {
61
- const ext = filePath.split('.').pop()?.toLowerCase();
62
- if (ext !== 'm' && ext !== 'mm' && ext !== 'h') {
63
- continue;
64
- }
65
+ for (const { path: filePath, content } of files) {
66
+ const ext = filePath.split('.').pop()?.toLowerCase();
67
+ if (ext !== 'm' && ext !== 'mm' && ext !== 'h') {
68
+ continue;
69
+ }
65
70
 
66
- const lines = content.split(/\r?\n/);
67
- for (let i = 0; i < lines.length; i++) {
68
- categoryRegex.lastIndex = 0;
69
- let m;
70
- while ((m = categoryRegex.exec(lines[i])) !== null) {
71
- const key = `${m[1]}(${m[2]})`;
72
- if (!categoryMap.has(key)) {
73
- categoryMap.set(key, []);
71
+ const lines = content.split(/\r?\n/);
72
+ for (let i = 0; i < lines.length; i++) {
73
+ categoryRegex.lastIndex = 0;
74
+ let m;
75
+ while ((m = categoryRegex.exec(lines[i])) !== null) {
76
+ const key = `${m[1]}(${m[2]})`;
77
+ if (!categoryMap.has(key)) {
78
+ categoryMap.set(key, []);
79
+ }
80
+ categoryMap.get(key).push({
81
+ filePath,
82
+ line: i + 1,
83
+ snippet: lines[i].trim().slice(0, 120),
84
+ });
74
85
  }
75
- categoryMap.get(key).push({
76
- filePath,
77
- line: i + 1,
78
- snippet: lines[i].trim().slice(0, 120),
79
- });
80
86
  }
81
87
  }
82
- }
83
88
 
84
- for (const [key, locations] of categoryMap) {
85
- if (locations.length <= 1) {
86
- continue;
87
- }
89
+ for (const [key, locations] of categoryMap) {
90
+ if (locations.length <= 1) {
91
+ continue;
92
+ }
88
93
 
89
- const hFiles = locations.filter((l) => l.filePath.endsWith('.h'));
90
- const mFiles = locations.filter((l) => !l.filePath.endsWith('.h'));
91
- const hasDuplicateH = hFiles.length > 1;
92
- const hasDuplicateM = mFiles.length > 1;
93
- const tooMany = locations.length > 2;
94
+ const hFiles = locations.filter((l) => l.filePath.endsWith('.h'));
95
+ const mFiles = locations.filter((l) => !l.filePath.endsWith('.h'));
96
+ const hasDuplicateH = hFiles.length > 1;
97
+ const hasDuplicateM = mFiles.length > 1;
98
+ const tooMany = locations.length > 2;
94
99
 
95
- if (hasDuplicateH || hasDuplicateM || tooMany) {
96
- const conflictLocations = tooMany
97
- ? locations
98
- : hasDuplicateH && hasDuplicateM
100
+ if (hasDuplicateH || hasDuplicateM || tooMany) {
101
+ const conflictLocations = tooMany
99
102
  ? locations
100
- : hasDuplicateH
101
- ? hFiles
102
- : mFiles;
103
+ : hasDuplicateH && hasDuplicateM
104
+ ? locations
105
+ : hasDuplicateH
106
+ ? hFiles
107
+ : mFiles;
103
108
 
104
- violations.push({
105
- ruleId: 'objc-cross-file-duplicate-category',
106
- message: `Category ${key} 在 ${conflictLocations.length} 个文件中重复声明,可能导致方法覆盖或未定义行为`,
107
- severity: 'warning',
108
- locations: conflictLocations,
109
- });
109
+ violations.push({
110
+ ruleId: 'objc-cross-file-duplicate-category',
111
+ message: `Category ${key} 在 ${conflictLocations.length} 个文件中重复声明,可能导致方法覆盖或未定义行为`,
112
+ severity: 'warning',
113
+ locations: conflictLocations,
114
+ });
115
+ }
110
116
  }
111
- }
112
117
  } // end isDisabled('objc-cross-file-duplicate-category')
113
118
 
114
119
  // ── JS/TS 循环依赖检查 ──
115
120
  // 检测 A imports B 且 B imports A 的直接循环
116
121
  if (!isDisabled('js-circular-import')) {
117
- const jsImportMap = new Map(); // filePath → Set<importedPath>
118
- const jsExts = new Set(['js', 'ts', 'jsx', 'tsx', 'mjs', 'mts']);
119
- const importRegex = /(?:import\s+.+?\s+from\s+['"](.+?)['"]|require\s*\(\s*['"](.+?)['"]\s*\))/g;
122
+ const jsImportMap = new Map(); // filePath → Set<importedPath>
123
+ const jsExts = new Set(['js', 'ts', 'jsx', 'tsx', 'mjs', 'mts']);
124
+ const importRegex =
125
+ /(?:import\s+.+?\s+from\s+['"](.+?)['"]|require\s*\(\s*['"](.+?)['"]\s*\))/g;
120
126
 
121
- for (const { path: filePath, content } of files) {
122
- const ext = filePath.split('.').pop()?.toLowerCase();
123
- if (!jsExts.has(ext)) continue;
127
+ for (const { path: filePath, content } of files) {
128
+ const ext = filePath.split('.').pop()?.toLowerCase();
129
+ if (!jsExts.has(ext)) {
130
+ continue;
131
+ }
124
132
 
125
- const imports = new Set();
126
- const lines = content.split(/\r?\n/);
127
- for (const line of lines) {
128
- importRegex.lastIndex = 0;
129
- let m;
130
- while ((m = importRegex.exec(line)) !== null) {
131
- const importPath = m[1] || m[2];
132
- if (importPath.startsWith('.')) {
133
- // 解析相对路径为归一化 key
134
- const dir = filePath.substring(0, filePath.lastIndexOf('/'));
135
- const resolved = resolveImportPath(dir, importPath);
136
- if (resolved) imports.add(resolved);
133
+ const imports = new Set();
134
+ const lines = content.split(/\r?\n/);
135
+ for (const line of lines) {
136
+ importRegex.lastIndex = 0;
137
+ let m;
138
+ while ((m = importRegex.exec(line)) !== null) {
139
+ const importPath = m[1] || m[2];
140
+ if (importPath.startsWith('.')) {
141
+ // 解析相对路径为归一化 key
142
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
143
+ const resolved = resolveImportPath(dir, importPath);
144
+ if (resolved) {
145
+ imports.add(resolved);
146
+ }
147
+ }
137
148
  }
138
149
  }
150
+ if (imports.size > 0) {
151
+ jsImportMap.set(normalizeFilePath(filePath), imports);
152
+ }
139
153
  }
140
- if (imports.size > 0) {
141
- jsImportMap.set(normalizeFilePath(filePath), imports);
142
- }
143
- }
144
154
 
145
- // 检测直接双向循环: A→B 且 B→A
146
- const reportedCycles = new Set();
147
- for (const [fileA, importsA] of jsImportMap) {
148
- for (const depB of importsA) {
149
- const importsB = jsImportMap.get(depB);
150
- if (importsB?.has(fileA)) {
151
- const cycleKey = [fileA, depB].sort().join(' <-> ');
152
- if (!reportedCycles.has(cycleKey)) {
153
- reportedCycles.add(cycleKey);
154
- violations.push({
155
- ruleId: 'js-circular-import',
156
- message: `检测到循环依赖,两个模块互相导入可能导致运行时 undefined`,
157
- severity: 'warning',
158
- locations: [
159
- { filePath: fileA, line: 1, snippet: `imports ${depB.split('/').pop()}` },
160
- { filePath: depB, line: 1, snippet: `imports ${fileA.split('/').pop()}` },
161
- ],
162
- });
155
+ // 检测直接双向循环: A→B 且 B→A
156
+ const reportedCycles = new Set();
157
+ for (const [fileA, importsA] of jsImportMap) {
158
+ for (const depB of importsA) {
159
+ const importsB = jsImportMap.get(depB);
160
+ if (importsB?.has(fileA)) {
161
+ const cycleKey = [fileA, depB].sort().join(' <-> ');
162
+ if (!reportedCycles.has(cycleKey)) {
163
+ reportedCycles.add(cycleKey);
164
+ violations.push({
165
+ ruleId: 'js-circular-import',
166
+ message: `检测到循环依赖,两个模块互相导入可能导致运行时 undefined`,
167
+ severity: 'warning',
168
+ locations: [
169
+ { filePath: fileA, line: 1, snippet: `imports ${depB.split('/').pop()}` },
170
+ { filePath: depB, line: 1, snippet: `imports ${fileA.split('/').pop()}` },
171
+ ],
172
+ });
173
+ }
163
174
  }
164
175
  }
165
176
  }
166
- }
167
177
  } // end isDisabled('js-circular-import')
168
178
 
169
179
  // ── Java/Kotlin 同名类跨文件检查 ──
170
180
  if (!isDisabled('java-duplicate-class-name')) {
171
- const classMap = new Map(); // className → [{filePath, line, snippet}]
172
- const javaClassRegex = /(?:public\s+)?(?:abstract\s+)?(?:final\s+)?class\s+(\w+)/;
173
- const jkExts = new Set(['java', 'kt']);
181
+ const classMap = new Map(); // className → [{filePath, line, snippet}]
182
+ const javaClassRegex = /(?:public\s+)?(?:abstract\s+)?(?:final\s+)?class\s+(\w+)/;
183
+ const jkExts = new Set(['java', 'kt']);
174
184
 
175
- for (const { path: filePath, content } of files) {
176
- const ext = filePath.split('.').pop()?.toLowerCase();
177
- if (!jkExts.has(ext)) continue;
185
+ for (const { path: filePath, content } of files) {
186
+ const ext = filePath.split('.').pop()?.toLowerCase();
187
+ if (!jkExts.has(ext)) {
188
+ continue;
189
+ }
178
190
 
179
- const lines = content.split(/\r?\n/);
180
- for (let i = 0; i < lines.length; i++) {
181
- const m = javaClassRegex.exec(lines[i]);
182
- if (m) {
183
- const className = m[1];
184
- if (!classMap.has(className)) {
185
- classMap.set(className, []);
191
+ const lines = content.split(/\r?\n/);
192
+ for (let i = 0; i < lines.length; i++) {
193
+ const m = javaClassRegex.exec(lines[i]);
194
+ if (m) {
195
+ const className = m[1];
196
+ if (!classMap.has(className)) {
197
+ classMap.set(className, []);
198
+ }
199
+ classMap.get(className).push({
200
+ filePath,
201
+ line: i + 1,
202
+ snippet: lines[i].trim().slice(0, 120),
203
+ });
186
204
  }
187
- classMap.get(className).push({
188
- filePath,
189
- line: i + 1,
190
- snippet: lines[i].trim().slice(0, 120),
191
- });
192
205
  }
193
206
  }
194
- }
195
207
 
196
- for (const [className, locations] of classMap) {
197
- if (locations.length > 1) {
198
- violations.push({
199
- ruleId: 'java-duplicate-class-name',
200
- message: `类名 "${className}" 在 ${locations.length} 个文件中定义,可能导致导入歧义`,
201
- severity: 'info',
202
- locations,
203
- });
208
+ for (const [className, locations] of classMap) {
209
+ if (locations.length > 1) {
210
+ violations.push({
211
+ ruleId: 'java-duplicate-class-name',
212
+ message: `类名 "${className}" 在 ${locations.length} 个文件中定义,可能导致导入歧义`,
213
+ severity: 'info',
214
+ locations,
215
+ });
216
+ }
204
217
  }
205
- }
206
218
  } // end isDisabled('java-duplicate-class-name')
207
219
 
208
220
  // ── Go 多文件 init() 函数检查 ──
209
221
  // 同一 package 下多个文件都有 init(),执行顺序依赖文件名排序,容易出错
210
222
  if (!isDisabled('go-multiple-init')) {
211
- const goInitMap = new Map(); // dirPath → [{filePath, line}]
223
+ const goInitMap = new Map(); // dirPath → [{filePath, line}]
212
224
 
213
- for (const { path: filePath, content } of files) {
214
- if (!filePath.endsWith('.go')) continue;
225
+ for (const { path: filePath, content } of files) {
226
+ if (!filePath.endsWith('.go')) {
227
+ continue;
228
+ }
215
229
 
216
- const lines = content.split(/\r?\n/);
217
- for (let i = 0; i < lines.length; i++) {
218
- if (/^func\s+init\s*\(\s*\)/.test(lines[i].trim())) {
219
- const dir = filePath.substring(0, filePath.lastIndexOf('/'));
220
- if (!goInitMap.has(dir)) {
221
- goInitMap.set(dir, []);
230
+ const lines = content.split(/\r?\n/);
231
+ for (let i = 0; i < lines.length; i++) {
232
+ if (/^func\s+init\s*\(\s*\)/.test(lines[i].trim())) {
233
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
234
+ if (!goInitMap.has(dir)) {
235
+ goInitMap.set(dir, []);
236
+ }
237
+ goInitMap.get(dir).push({
238
+ filePath,
239
+ line: i + 1,
240
+ snippet: lines[i].trim().slice(0, 120),
241
+ });
242
+ break; // 每个文件只记录一次
222
243
  }
223
- goInitMap.get(dir).push({
224
- filePath,
225
- line: i + 1,
226
- snippet: lines[i].trim().slice(0, 120),
227
- });
228
- break; // 每个文件只记录一次
229
244
  }
230
245
  }
231
- }
232
246
 
233
- for (const [dir, locations] of goInitMap) {
234
- if (locations.length > 2) {
235
- violations.push({
236
- ruleId: 'go-multiple-init',
237
- message: `同一 package (${dir.split('/').pop()}) 中 ${locations.length} 个文件都定义了 init(),执行顺序依赖文件名排序`,
238
- severity: 'info',
239
- locations,
240
- });
247
+ for (const [dir, locations] of goInitMap) {
248
+ if (locations.length > 2) {
249
+ violations.push({
250
+ ruleId: 'go-multiple-init',
251
+ message: `同一 package (${dir.split('/').pop()}) 中 ${locations.length} 个文件都定义了 init(),执行顺序依赖文件名排序`,
252
+ severity: 'info',
253
+ locations,
254
+ });
255
+ }
241
256
  }
242
- }
243
257
  } // end isDisabled('go-multiple-init')
244
258
 
245
259
  // ── Swift Extension 方法跨文件冲突检查 ──
246
260
  if (!isDisabled('swift-cross-file-extension-conflict')) {
247
- const swiftExtMethodMap = new Map(); // "TypeName.methodName" → [{filePath, line}]
248
- const swiftExtRegex = /extension\s+(\w+)/;
249
- const swiftFuncRegex = /func\s+(\w+)\s*\(/;
250
-
251
- for (const { path: filePath, content } of files) {
252
- if (!filePath.endsWith('.swift')) continue;
253
-
254
- const lines = content.split(/\r?\n/);
255
- let currentExt = null;
256
- let braceDepth = 0;
261
+ const swiftExtMethodMap = new Map(); // "TypeName.methodName" → [{filePath, line}]
262
+ const swiftExtRegex = /extension\s+(\w+)/;
263
+ const swiftFuncRegex = /func\s+(\w+)\s*\(/;
257
264
 
258
- for (let i = 0; i < lines.length; i++) {
259
- const extMatch = swiftExtRegex.exec(lines[i]);
260
- if (extMatch && !currentExt) {
261
- currentExt = extMatch[1];
262
- braceDepth = 0;
265
+ for (const { path: filePath, content } of files) {
266
+ if (!filePath.endsWith('.swift')) {
267
+ continue;
263
268
  }
264
269
 
265
- if (currentExt) {
266
- for (const ch of lines[i]) {
267
- if (ch === '{') braceDepth++;
268
- else if (ch === '}') braceDepth--;
270
+ const lines = content.split(/\r?\n/);
271
+ let currentExt = null;
272
+ let braceDepth = 0;
273
+
274
+ for (let i = 0; i < lines.length; i++) {
275
+ const extMatch = swiftExtRegex.exec(lines[i]);
276
+ if (extMatch && !currentExt) {
277
+ currentExt = extMatch[1];
278
+ braceDepth = 0;
269
279
  }
270
280
 
271
- const funcMatch = swiftFuncRegex.exec(lines[i]);
272
- if (funcMatch && braceDepth >= 1) {
273
- const key = `${currentExt}.${funcMatch[1]}`;
274
- if (!swiftExtMethodMap.has(key)) {
275
- swiftExtMethodMap.set(key, []);
281
+ if (currentExt) {
282
+ for (const ch of lines[i]) {
283
+ if (ch === '{') {
284
+ braceDepth++;
285
+ } else if (ch === '}') {
286
+ braceDepth--;
287
+ }
288
+ }
289
+
290
+ const funcMatch = swiftFuncRegex.exec(lines[i]);
291
+ if (funcMatch && braceDepth >= 1) {
292
+ const key = `${currentExt}.${funcMatch[1]}`;
293
+ if (!swiftExtMethodMap.has(key)) {
294
+ swiftExtMethodMap.set(key, []);
295
+ }
296
+ swiftExtMethodMap.get(key).push({
297
+ filePath,
298
+ line: i + 1,
299
+ snippet: lines[i].trim().slice(0, 120),
300
+ });
276
301
  }
277
- swiftExtMethodMap.get(key).push({
278
- filePath,
279
- line: i + 1,
280
- snippet: lines[i].trim().slice(0, 120),
281
- });
282
- }
283
302
 
284
- if (braceDepth <= 0) {
285
- currentExt = null;
303
+ if (braceDepth <= 0) {
304
+ currentExt = null;
305
+ }
286
306
  }
287
307
  }
288
308
  }
289
- }
290
309
 
291
- for (const [key, locations] of swiftExtMethodMap) {
292
- if (locations.length > 1) {
293
- const uniqueFiles = new Set(locations.map((l) => l.filePath));
294
- if (uniqueFiles.size > 1) {
295
- violations.push({
296
- ruleId: 'swift-cross-file-extension-conflict',
297
- message: `Extension 方法 ${key} 在 ${uniqueFiles.size} 个文件中定义,可能导致方法冲突`,
298
- severity: 'warning',
299
- locations,
300
- });
310
+ for (const [key, locations] of swiftExtMethodMap) {
311
+ if (locations.length > 1) {
312
+ const uniqueFiles = new Set(locations.map((l) => l.filePath));
313
+ if (uniqueFiles.size > 1) {
314
+ violations.push({
315
+ ruleId: 'swift-cross-file-extension-conflict',
316
+ message: `Extension 方法 ${key} 在 ${uniqueFiles.size} 个文件中定义,可能导致方法冲突`,
317
+ severity: 'warning',
318
+ locations,
319
+ });
320
+ }
301
321
  }
302
322
  }
303
- }
304
323
  } // end isDisabled('swift-cross-file-extension-conflict')
305
324
 
306
325
  return violations;
@@ -50,7 +50,9 @@ export function buildTestBlockMask(lines, language) {
50
50
  const mask = new Array(lines.length).fill(false);
51
51
 
52
52
  // 目前仅 Rust 需要 — #[cfg(test)] 内联测试模块
53
- if (language !== 'rust') return mask;
53
+ if (language !== 'rust') {
54
+ return mask;
55
+ }
54
56
 
55
57
  let inTestBlock = false;
56
58
  let braceDepth = 0;
@@ -71,11 +73,16 @@ export function buildTestBlockMask(lines, language) {
71
73
  braceDepth = 0;
72
74
  // 计算本行的花括号
73
75
  for (const ch of lines[i]) {
74
- if (ch === '{') braceDepth++;
75
- else if (ch === '}') braceDepth--;
76
+ if (ch === '{') {
77
+ braceDepth++;
78
+ } else if (ch === '}') {
79
+ braceDepth--;
80
+ }
76
81
  }
77
82
  mask[i] = true;
78
- if (braceDepth <= 0) inTestBlock = false; // 单行 mod 声明 (mod tests;)
83
+ if (braceDepth <= 0) {
84
+ inTestBlock = false; // 单行 mod 声明 (mod tests;)
85
+ }
79
86
  continue;
80
87
  }
81
88
  // 检查下一行是否是 mod xxx {
@@ -83,8 +90,6 @@ export function buildTestBlockMask(lines, language) {
83
90
  mask[i] = true; // #[cfg(test)] 行本身也标记
84
91
  inTestBlock = true;
85
92
  braceDepth = 0;
86
- // 下一行会在循环中处理
87
- continue;
88
93
  }
89
94
  // 单行 #[cfg(test)] 但后面不是 mod — 不处理
90
95
  }
@@ -92,8 +97,11 @@ export function buildTestBlockMask(lines, language) {
92
97
  // 正在测试块内 — 追踪花括号深度
93
98
  mask[i] = true;
94
99
  for (const ch of lines[i]) {
95
- if (ch === '{') braceDepth++;
96
- else if (ch === '}') braceDepth--;
100
+ if (ch === '{') {
101
+ braceDepth++;
102
+ } else if (ch === '}') {
103
+ braceDepth--;
104
+ }
97
105
  }
98
106
  if (braceDepth <= 0) {
99
107
  inTestBlock = false; // 测试块结束
@@ -122,7 +130,7 @@ export function buildCommentMask(lines, language) {
122
130
  let inBlock = false; // 是否在 /* ... */ 块内
123
131
 
124
132
  const usesHash = language === 'python'; // Python 用 # 注释
125
- const usesSlash = !usesHash; // 其他语言用 //
133
+ const usesSlash = !usesHash; // 其他语言用 //
126
134
 
127
135
  for (let i = 0; i < lines.length; i++) {
128
136
  const trimmed = lines[i].trimStart();
@@ -160,7 +168,6 @@ export function buildCommentMask(lines, language) {
160
168
  // Python docstring 行 (简化: 整行以 """ 或 ''' 开头)
161
169
  if (usesHash && /^\s*("""|''')/.test(lines[i])) {
162
170
  mask[i] = true;
163
- continue;
164
171
  }
165
172
  }
166
173