autosnippet 1.7.3 → 1.7.4

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.
@@ -16,6 +16,17 @@ const ImportDecisionEngine = require('./ImportDecisionEngine');
16
16
 
17
17
  const importDecisionEngine = new ImportDecisionEngine();
18
18
 
19
+ /**
20
+ * 依赖被阻止异常(循环依赖、反向依赖、用户取消)
21
+ */
22
+ class DependencyBlockedError extends Error {
23
+ constructor(message, reason) {
24
+ super(message);
25
+ this.name = 'DependencyBlockedError';
26
+ this.reason = reason;
27
+ }
28
+ }
29
+
19
30
  function buildDependencySuggestionNote(ensureResult) {
20
31
  try {
21
32
  const suggestion = ensureResult && ensureResult.suggestion ? String(ensureResult.suggestion) : '';
@@ -81,25 +92,46 @@ const shownPolicyAlerts = new Set();
81
92
  function maybeAlertPolicyBlocked(fromModule, toModule, ensureResult) {
82
93
  try {
83
94
  const reason = ensureResult && ensureResult.reason ? String(ensureResult.reason) : '';
84
- if (reason !== 'cycleBlocked') return;
95
+
96
+ // 只对 cycleBlocked 和 downwardDependency 弹窗(阻止策略)
97
+ if (reason !== 'cycleBlocked' && reason !== 'downwardDependency') return;
98
+
85
99
  const key = `${fromModule}=>${toModule}`;
86
100
  if (shownPolicyAlerts.has(key)) return;
87
101
  shownPolicyAlerts.add(key);
88
102
 
89
103
  const extra = ensureResult && ensureResult.suggestion ? `\n\n${String(ensureResult.suggestion)}` : '';
104
+ const moduleLine = `${fromModule} -> ${toModule}`;
105
+ let detail = ensureResult && ensureResult.message ? `\n${String(ensureResult.message)}` : '';
106
+ if (reason === 'cycleBlocked' && detail.includes('Package.swift')) {
107
+ detail = '\n跨包循环依赖';
108
+ }
109
+
110
+ let title = 'AutoSnippet SPM 依赖策略';
111
+ if (reason === 'cycleBlocked') {
112
+ title = 'AutoSnippet SPM - 循环依赖';
113
+ } else if (reason === 'downwardDependency') {
114
+ title = 'AutoSnippet SPM - 反向依赖';
115
+ }
116
+
90
117
  notifier.alert(
91
- `已阻止依赖注入(形成反向引入/循环)\n\n${fromModule} -> ${toModule}${extra}`,
92
- { title: 'AutoSnippet SPM 依赖策略', givingUpAfterSeconds: 12 }
118
+ `已阻止依赖注入\n\n${moduleLine}${detail}${extra}`,
119
+ { title, givingUpAfterSeconds: 12 }
93
120
  );
94
121
  } catch {
95
122
  // ignore
96
123
  }
97
124
  }
98
125
 
99
- async function handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift) {
126
+ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift, options = {}) {
127
+ const result = { suggestionNote: null, decisionAction: null };
128
+
100
129
  if (isSwift) {
101
- await handleHeaderLineSwift(specFile, updateFile, headerLine, importArray);
102
- return;
130
+ const swiftResult = await handleHeaderLineSwift(specFile, updateFile, headerLine, importArray, options);
131
+ if (swiftResult && swiftResult.suggestionNote) {
132
+ result.suggestionNote = swiftResult.suggestionNote;
133
+ }
134
+ return result;
103
135
  }
104
136
 
105
137
  const projectRoot = getProjectRoot(specFile);
@@ -109,7 +141,7 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
109
141
  const importWriter = getImportWriter(projectRoot);
110
142
 
111
143
  const header = directiveParser.createHeader(headerLine);
112
- if (!header) return;
144
+ if (!header) return result;
113
145
 
114
146
  // 判断引号格式:moduleName 为空字符串表示引号格式 "Header.h"
115
147
  // 尖括号格式:moduleName 有值,例如 <Module/Header.h>
@@ -118,22 +150,28 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
118
150
  if (isQuotedHeader) {
119
151
  // 引号格式:设置 relativePathToCurrentFile 以便 _buildImportLine 构建正确的 import 语句
120
152
  header.relativePathToCurrentFile = header.headerName;
121
- await importWriter.addHeaderToFile(updateFile, header, false);
122
- return;
153
+ if (!options.preflight) {
154
+ await importWriter.addHeaderToFile(updateFile, header, false);
155
+ }
156
+ return result;
123
157
  }
124
158
 
125
159
  const updateFileDir = path.dirname(updateFile);
126
160
  const currentPackagePath = await packageParser.findPackageSwiftPath(updateFileDir);
127
161
 
128
162
  if (!currentPackagePath) {
129
- await importWriter.handleModuleHeader(specFile, updateFile, header, importArray, true);
130
- return;
163
+ if (!options.preflight) {
164
+ await importWriter.handleModuleHeader(specFile, updateFile, header, importArray, true);
165
+ }
166
+ return result;
131
167
  }
132
168
 
133
169
  const currentPackageInfo = await packageParser.parsePackageSwift(currentPackagePath);
134
170
  if (!currentPackageInfo) {
135
- await importWriter.handleModuleHeader(specFile, updateFile, header, importArray, true);
136
- return;
171
+ if (!options.preflight) {
172
+ await importWriter.handleModuleHeader(specFile, updateFile, header, importArray, true);
173
+ }
174
+ return result;
137
175
  }
138
176
 
139
177
  const currentModuleName = moduleResolver.determineCurrentModule(updateFile, currentPackageInfo);
@@ -248,11 +286,37 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
248
286
  // policy=block:提前拦截,禁止写入头文件/import
249
287
  if (decision.action === 'block') {
250
288
  maybeAlertPolicyBlocked(currentModuleName, headerInfo.moduleName, ensureResult);
251
- return;
289
+ throw new DependencyBlockedError(
290
+ `依赖被阻止:${currentModuleName} -> ${headerInfo.moduleName} (${ensureResult.reason})`,
291
+ ensureResult.reason
292
+ );
252
293
  }
253
294
 
254
295
  // allowActions 弹窗决策
255
296
  if (decision.action === 'review' && ensureResult.allowActions && ensureResult.allowActions.length > 1) {
297
+ if (options.skipPrompt) {
298
+ const selectedAction = options.decisionAction || 'insertAnyway';
299
+ result.decisionAction = selectedAction;
300
+ if (selectedAction === 'cancel') {
301
+ throw new DependencyBlockedError(
302
+ `用户取消操作:${currentModuleName} -> ${headerInfo.moduleName}`,
303
+ 'userCanceled'
304
+ );
305
+ }
306
+ if (selectedAction === 'suggestPatch') {
307
+ const note = options.suggestionNote || buildDependencySuggestionNote(ensureResult);
308
+ if (note) result.suggestionNote = note;
309
+ }
310
+ if (selectedAction === 'autoFix') {
311
+ const deps2 = getSpmDepsService(projectRoot);
312
+ const fixResult = await deps2.ensureDependency(specFile, currentPackagePath, currentModuleName, headerInfo.moduleName, { forceFix: true });
313
+ if (fixResult && fixResult.changed) {
314
+ console.log(`✅ [AutoSnippet][SPM] 已自动补齐依赖:${currentModuleName} -> ${headerInfo.moduleName}`);
315
+ notifier.notify(`已补齐依赖:${currentModuleName} -> ${headerInfo.moduleName}`, { title: 'AutoSnippet SPM' });
316
+ header.dependencyNote = buildDependencyFixedNote(fixResult);
317
+ }
318
+ }
319
+ } else {
256
320
  const buttonMap = {
257
321
  'insertAnyway': '直接插入(信任架构)',
258
322
  'suggestPatch': '显示建议补丁',
@@ -276,13 +340,19 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
276
340
  '取消操作': 'cancel'
277
341
  };
278
342
  const selectedAction = actionMap[userChoice] || 'cancel';
343
+ result.decisionAction = selectedAction;
279
344
 
280
- if (selectedAction === 'cancel') return;
345
+ if (selectedAction === 'cancel') {
346
+ throw new DependencyBlockedError(
347
+ `用户取消操作:${currentModuleName} -> ${headerInfo.moduleName}`,
348
+ 'userCanceled'
349
+ );
350
+ }
281
351
  if (selectedAction === 'suggestPatch') {
282
352
  if (ensureResult.suggestion) console.log(ensureResult.suggestion);
283
353
  notifier.notify(`建议补丁已在控制台输出`, { title: 'AutoSnippet' });
284
354
  const note = buildDependencySuggestionNote(ensureResult);
285
- if (note) header.dependencyNote = note;
355
+ if (note) result.suggestionNote = note;
286
356
  }
287
357
  if (selectedAction === 'autoFix') {
288
358
  // 再次调用 ensureDependency 触发修复(此次使用 fix 模式)
@@ -296,6 +366,7 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
296
366
  }
297
367
  // 其他情况(insertAnyway/suggestPatch/autoFix后)继续插入
298
368
  }
369
+ }
299
370
  } else if (ensureResult.changed) {
300
371
  console.log(`✅ [AutoSnippet][SPM] 已自动补齐依赖:${currentModuleName} -> ${headerInfo.moduleName}`);
301
372
  header.dependencyNote = buildDependencyFixedNote(ensureResult);
@@ -318,10 +389,15 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
318
389
  }
319
390
 
320
391
  const shouldUseRelativePath = isSameDirectory || isSameModule || (headFullPath && fs.existsSync(headFullPath));
392
+ if (!options.preflight) {
321
393
  await importWriter.handleModuleHeader(specFile, updateFile, header, importArray, !shouldUseRelativePath);
394
+ }
395
+ return result;
322
396
  }
323
397
 
324
- async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArray) {
398
+ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArray, options = {}) {
399
+ const result = { suggestionNote: null, decisionAction: null };
400
+
325
401
  const projectRoot = getProjectRoot(specFile);
326
402
  const packageParser = getPackageParser(projectRoot);
327
403
  const deps = getSpmDepsService(projectRoot);
@@ -331,7 +407,7 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
331
407
 
332
408
  const parsed = directiveParser.parse(headerLine);
333
409
  const moduleName = (parsed && parsed.content) ? parsed.content : '';
334
- if (!moduleName) return;
410
+ if (!moduleName) return result;
335
411
 
336
412
  // Swift import 同样需要 target 依赖;尽量与 ObjC 路径一致:跨 module 时补齐 SPM 依赖
337
413
  try {
@@ -354,11 +430,36 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
354
430
  // policy=block:提前拦截,禁止写入 import
355
431
  if (ensureResult.reason === 'cycleBlocked' || ensureResult.reason === 'downwardDependency') {
356
432
  maybeAlertPolicyBlocked(currentModuleName, moduleName, ensureResult);
357
- return;
433
+ throw new DependencyBlockedError(
434
+ `依赖被阻止:${currentModuleName} -> ${moduleName} (${ensureResult.reason})`,
435
+ ensureResult.reason
436
+ );
358
437
  }
359
438
 
360
439
  // allowActions 弹窗决策
361
440
  if (ensureResult.allowActions && ensureResult.allowActions.length > 1) {
441
+ if (options.skipPrompt) {
442
+ const selectedAction = options.decisionAction || 'insertAnyway';
443
+ result.decisionAction = selectedAction;
444
+ if (selectedAction === 'cancel') {
445
+ throw new DependencyBlockedError(
446
+ `用户取消操作:${currentModuleName} -> ${moduleName}`,
447
+ 'userCanceled'
448
+ );
449
+ }
450
+ if (selectedAction === 'suggestPatch') {
451
+ const note = options.suggestionNote || buildDependencySuggestionNote(ensureResult);
452
+ if (note) result.suggestionNote = note;
453
+ }
454
+ if (selectedAction === 'autoFix') {
455
+ const deps2 = getSpmDepsService(projectRoot);
456
+ const fixResult = await deps2.ensureDependency(specFile, currentPackagePath, currentModuleName, moduleName, { forceFix: true });
457
+ if (fixResult && fixResult.changed) {
458
+ console.log(`✅ [AutoSnippet][SPM] 已自动补齐依赖:${currentModuleName} -> ${moduleName}`);
459
+ notifier.notify(`已补齐依赖:${currentModuleName} -> ${moduleName}`, { title: 'AutoSnippet SPM' });
460
+ }
461
+ }
462
+ } else {
362
463
  const buttonMap = {
363
464
  'insertAnyway': '直接插入(信任架构)',
364
465
  'suggestPatch': '显示建议补丁',
@@ -381,11 +482,19 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
381
482
  '取消操作': 'cancel'
382
483
  };
383
484
  const selectedAction = actionMap[userChoice] || 'cancel';
485
+ result.decisionAction = selectedAction;
384
486
 
385
- if (selectedAction === 'cancel') return;
487
+ if (selectedAction === 'cancel') {
488
+ throw new DependencyBlockedError(
489
+ `用户取消操作:${currentModuleName} -> ${moduleName}`,
490
+ 'userCanceled'
491
+ );
492
+ }
386
493
  if (selectedAction === 'suggestPatch') {
387
494
  if (ensureResult.suggestion) console.log(ensureResult.suggestion);
388
495
  notifier.notify(`建议补丁已在控制台输出`, { title: 'AutoSnippet' });
496
+ const note = buildDependencySuggestionNote(ensureResult);
497
+ if (note) result.suggestionNote = note;
389
498
  }
390
499
  if (selectedAction === 'autoFix') {
391
500
  const deps2 = getSpmDepsService(projectRoot);
@@ -395,6 +504,7 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
395
504
  notifier.notify(`已补齐依赖:${currentModuleName} -> ${moduleName}`, { title: 'AutoSnippet SPM' });
396
505
  }
397
506
  }
507
+ }
398
508
  }
399
509
  } else if (ensureResult.changed) {
400
510
  console.log(`✅ [AutoSnippet][SPM] 已自动补齐依赖:${currentModuleName} -> ${moduleName}`);
@@ -417,7 +527,10 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
417
527
  }
418
528
  }
419
529
  }
420
- } catch {
530
+ } catch (err) {
531
+ if (err && err.name === 'DependencyBlockedError') {
532
+ throw err;
533
+ }
421
534
  // ignore - 依赖补齐失败不应阻断 import 注入
422
535
  }
423
536
 
@@ -431,9 +544,11 @@ async function handleHeaderLineSwift(specFile, updateFile, headerLine, importArr
431
544
  }
432
545
  }
433
546
 
434
- if (!isAddedHeader) {
547
+ if (!isAddedHeader && !options.preflight) {
435
548
  await importWriter.addImportToFileSwift(updateFile, moduleName, '自动注入 import 完成。');
436
549
  }
550
+
551
+ return result;
437
552
  }
438
553
 
439
554
  module.exports = {
@@ -4,8 +4,13 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
+ const { promisify } = require('util');
7
8
  const injection = require('../../injection/injectionService.js');
8
9
  const AutomationOrchestrator = require('../../automation/AutomationOrchestrator');
10
+ const notifier = require('../../infrastructure/notification/Notifier');
11
+
12
+ const accessAsync = promisify(fs.access);
13
+ const readFileAsync = promisify(fs.readFile);
9
14
 
10
15
  const automationOrchestrator = new AutomationOrchestrator();
11
16
 
@@ -24,38 +29,241 @@ class HeaderHandler {
24
29
  );
25
30
  }
26
31
 
27
- async _handleHeader(context) {
28
- const { specFile, updateFile, headerLine, importArray, isSwift } = context;
29
- if (isSwift || updateFile.endsWith('.h')) {
30
- await injection.handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift);
31
- return;
32
+ async handleHeadersBatch(specFile, updateFile, headersToInsert, options = {}) {
33
+ const safeHeaders = Array.isArray(headersToInsert) ? headersToInsert : [];
34
+ const isSwift = options.isSwift === true || updateFile.endsWith('.swift');
35
+ const debug = options.debug === true;
36
+ const suggestionNotes = [];
37
+ const decisions = options.decisions || {};
38
+ const preflight = options.preflight === true;
39
+
40
+ if (debug) {
41
+ console.log(`\n[HeaderDebug] 头文件处理:`);
42
+ console.log(` 总共定义头文件: ${safeHeaders.length} 个`);
43
+ if (safeHeaders.length > 0) {
44
+ safeHeaders.forEach((h, idx) => {
45
+ console.log(` [${idx + 1}] ${h}`);
46
+ });
47
+ }
48
+ }
49
+
50
+ let headerInsertCount = 0;
51
+ if (safeHeaders.length === 0) return { blocked: false, headerInsertCount, suggestionNotes, decisions };
52
+
53
+ const importArray = this._collectImportsFromFile(updateFile, isSwift);
54
+ if (!isSwift && !updateFile.endsWith('.h')) {
55
+ await this._collectImportsFromHeaderFile(updateFile, importArray);
56
+ }
57
+
58
+ for (const header of safeHeaders) {
59
+ try {
60
+ if (debug) {
61
+ console.log(` 处理头文件: ${header}`);
62
+ }
63
+ let directiveLine = header;
64
+ if (isSwift) {
65
+ directiveLine = header.replace(/^import\s+/, '// as:import ');
66
+ } else {
67
+ directiveLine = header.replace(/^#import\s+/, '// as:include ');
68
+ }
69
+ const decision = decisions[header];
70
+ const handleResult = await injection.handleHeaderLine(
71
+ specFile,
72
+ updateFile,
73
+ directiveLine,
74
+ importArray,
75
+ isSwift,
76
+ {
77
+ preflight,
78
+ skipPrompt: !!decision,
79
+ decisionAction: decision ? decision.action : null,
80
+ suggestionNote: decision ? decision.suggestionNote : null
81
+ }
82
+ );
83
+ if (handleResult && handleResult.suggestionNote) {
84
+ suggestionNotes.push(handleResult.suggestionNote);
85
+ if (preflight) {
86
+ decisions[header] = {
87
+ action: handleResult.decisionAction || 'suggestPatch',
88
+ suggestionNote: handleResult.suggestionNote
89
+ };
90
+ }
91
+ } else if (preflight) {
92
+ decisions[header] = {
93
+ action: handleResult && handleResult.decisionAction ? handleResult.decisionAction : 'insertAnyway',
94
+ suggestionNote: null
95
+ };
96
+ }
97
+ if (!importArray.includes(header)) {
98
+ importArray.push(header);
99
+ headerInsertCount++;
100
+ }
101
+ } catch (err) {
102
+ console.error(` ❌ 处理头文件失败: ${header}`, err.message);
103
+ if (err.name === 'DependencyBlockedError') {
104
+ notifier.notify(`操作已取消: ${err.message}`, { title: 'AutoSnippet - 头文件注入' });
105
+ return { blocked: true, headerInsertCount, suggestionNotes };
106
+ }
107
+ if (process.env.ASD_SPM_CHECK_STRICT === '1') {
108
+ notifier.notify(`依赖缺失: ${header}${err.message ? `\n\n${err.message}` : ''}`, { title: 'AutoSnippet' });
109
+ return { blocked: true, headerInsertCount, suggestionNotes };
110
+ }
111
+ }
112
+ }
113
+
114
+ if (debug) {
115
+ console.log(` ✅ 已处理 ${headerInsertCount} 个头文件`);
32
116
  }
33
117
 
118
+ return { blocked: false, headerInsertCount, suggestionNotes, decisions };
119
+ }
120
+
121
+ formatSuggestionNotes(suggestionNotes, indent = '') {
122
+ if (!Array.isArray(suggestionNotes) || suggestionNotes.length === 0) return [];
123
+ const lines = [];
124
+ const seen = new Set();
125
+ for (const note of suggestionNotes) {
126
+ if (!note) continue;
127
+ String(note)
128
+ .split(/\r?\n/)
129
+ .forEach((line) => {
130
+ if (!line || !line.trim()) return;
131
+ const key = line.trim();
132
+ if (seen.has(key)) return;
133
+ seen.add(key);
134
+ lines.push(indent + line);
135
+ });
136
+ }
137
+ return lines;
138
+ }
139
+
140
+ _collectImportsFromFile(filePath, isSwift) {
141
+ try {
142
+ if (!fs.existsSync(filePath)) return [];
143
+ const content = fs.readFileSync(filePath, 'utf8');
144
+ const lines = content.split(/\r?\n/);
145
+ const imports = [];
146
+ for (const line of lines) {
147
+ const trimmed = line.trim();
148
+ if (isSwift) {
149
+ if (trimmed.startsWith('import ')) {
150
+ imports.push(trimmed);
151
+ }
152
+ } else {
153
+ if (trimmed.startsWith('#import ') || trimmed.startsWith('@import ') || trimmed.startsWith('#include ')) {
154
+ imports.push(trimmed);
155
+ }
156
+ }
157
+ }
158
+ return imports;
159
+ } catch (err) {
160
+ console.warn('读取文件 imports 失败:', err.message);
161
+ return [];
162
+ }
163
+ }
164
+
165
+ async _collectImportsFromHeaderFile(updateFile, importArray) {
34
166
  const dotIndex = updateFile.lastIndexOf('.');
167
+ if (dotIndex <= 0) return;
35
168
  const mainPathFile = updateFile.substring(0, dotIndex) + '.h';
169
+ try {
170
+ await accessAsync(mainPathFile, fs.constants.F_OK);
171
+ const data = await readFileAsync(mainPathFile, 'utf8');
172
+ const lineArray = data.split('\n');
173
+ lineArray.forEach(element => {
174
+ const lineVal = element.trim();
175
+ if (this.importReg.test(lineVal)) {
176
+ importArray.push(lineVal);
177
+ }
178
+ });
179
+ } catch (err) {
180
+ console.log(` ℹ️ 无法读取 ${path.basename(mainPathFile)},使用现有 importArray`);
181
+ }
182
+ }
36
183
 
37
- fs.access(mainPathFile, fs.constants.F_OK, async (err) => {
38
- if (err) {
184
+ computePasteLineNumber(triggerLineNumber, headerInsertCount, filePath, options = {}) {
185
+ const expectedCount = Number.isFinite(options.expectedHeaderCount)
186
+ ? options.expectedHeaderCount
187
+ : headerInsertCount;
188
+ if (expectedCount > 0) {
189
+ if (options.forceOffset) {
190
+ return triggerLineNumber + expectedCount;
191
+ }
192
+ const headerInsertPosition = this._getLastImportLine(filePath);
193
+ if (headerInsertPosition > 0 && headerInsertPosition < triggerLineNumber) {
194
+ return triggerLineNumber + expectedCount;
195
+ }
196
+ }
197
+ return triggerLineNumber;
198
+ }
199
+
200
+ _getLastImportLine(filePath) {
201
+ try {
202
+ if (!fs.existsSync(filePath)) return 0;
203
+ const content = fs.readFileSync(filePath, 'utf8');
204
+ const lines = content.split(/\r?\n/);
205
+ let lastImportIdx = -1;
206
+ for (let i = 0; i < lines.length; i++) {
207
+ const trimmed = lines[i].trim();
208
+ if (trimmed.startsWith('#import ') || trimmed.startsWith('@import ') || trimmed.startsWith('#include ') || trimmed.startsWith('import ')) {
209
+ lastImportIdx = i;
210
+ }
211
+ }
212
+ return lastImportIdx >= 0 ? lastImportIdx + 1 : 0;
213
+ } catch (err) {
214
+ return 0;
215
+ }
216
+ }
217
+
218
+ async _handleHeader(context) {
219
+ const { specFile, updateFile, headerLine, importArray, isSwift } = context;
220
+
221
+ try {
222
+ if (isSwift || updateFile.endsWith('.h')) {
39
223
  await injection.handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift);
40
224
  return;
41
225
  }
42
- fs.readFile(mainPathFile, 'utf8', async (readErr, data) => {
43
- if (readErr) {
44
- await injection.handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift);
45
- return;
46
- }
47
226
 
227
+ const dotIndex = updateFile.lastIndexOf('.');
228
+ const mainPathFile = updateFile.substring(0, dotIndex) + '.h';
229
+
230
+ // 尝试读取 .h 文件中已有的 imports
231
+ try {
232
+ await accessAsync(mainPathFile, fs.constants.F_OK);
233
+ const data = await readFileAsync(mainPathFile, 'utf8');
48
234
  const lineArray = data.split('\n');
235
+
49
236
  lineArray.forEach(element => {
50
237
  const lineVal = element.trim();
51
238
  if (this.importReg.test(lineVal)) {
52
239
  importArray.push(lineVal);
53
240
  }
54
241
  });
242
+ } catch (err) {
243
+ // .h 文件不存在或读取失败,继续处理
244
+ console.log(` ℹ️ 无法读取 ${path.basename(mainPathFile)},使用现有 importArray`);
245
+ }
55
246
 
247
+ // 调用 injection 服务处理头文件
56
248
  await injection.handleHeaderLine(specFile, updateFile, headerLine, importArray, isSwift);
249
+
250
+ } catch (err) {
251
+ // DependencyBlockedError: 依赖被阻止(循环/反向/用户取消)
252
+ if (err.name === 'DependencyBlockedError') {
253
+ console.error(` ❌ 头文件注入被阻止: ${err.message}`);
254
+ notifier.notify(`操作已取消: ${err.message}`, {
255
+ title: 'AutoSnippet - 头文件注入'
57
256
  });
58
- });
257
+ // 不抛出异常,避免中断 watch 流程
258
+ return;
259
+ }
260
+
261
+ // 其他错误:记录并通知
262
+ console.error(` ❌ 头文件注入失败:`, err.message);
263
+ notifier.notify(`头文件注入失败: ${err.message}`, {
264
+ title: 'AutoSnippet'
265
+ });
266
+ }
59
267
  }
60
268
  }
61
269