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.
- package/lib/injection/injectionService.js +137 -22
- package/lib/watch/handlers/HeaderHandler.js +221 -13
- package/lib/watch/handlers/SearchHandler.js +235 -206
- package/package.json +7 -3
- package/scripts/build-asd-entry.js +25 -2
- package/scripts/build-native-ui.js +32 -3
- package/scripts/postinstall-safe.js +66 -0
- package/templates/copilot-instructions.md +37 -0
- package/templates/cursor-rules/autosnippet-conventions.mdc +42 -0
- package/templates/recipes-setup/README.md +197 -0
- package/templates/recipes-setup/_template.md +106 -0
- package/templates/recipes-setup/example.md +119 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
92
|
-
{ title
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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')
|
|
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)
|
|
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
|
-
|
|
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')
|
|
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
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|