sela-core 0.1.0-alpha.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/README.md +103 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +158 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +166 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/showReport.d.ts +3 -0
- package/dist/cli/commands/showReport.d.ts.map +1 -0
- package/dist/cli/commands/showReport.js +82 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +131 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +94 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +77 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +295 -0
- package/dist/config/ConfigLoader.d.ts +16 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +185 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +80 -0
- package/dist/config/PathScopeGuard.d.ts +42 -0
- package/dist/config/PathScopeGuard.d.ts.map +1 -0
- package/dist/config/PathScopeGuard.js +192 -0
- package/dist/config/SelaConfig.d.ts +178 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +141 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +99 -0
- package/dist/engine/SelaEngine.d.ts +57 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -0
- package/dist/engine/SelaEngine.js +1146 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/errors/SelaError.d.ts +146 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +168 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +246 -0
- package/dist/fixtures/index.d.ts +30 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +765 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +54 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/reporter/SelaReporter.d.ts +165 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +517 -0
- package/dist/services/ASTSourceUpdater.d.ts +143 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3514 -0
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +107 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +570 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +135 -0
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +366 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +217 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealBudget.d.ts +32 -0
- package/dist/services/HealBudget.d.ts.map +1 -0
- package/dist/services/HealBudget.js +85 -0
- package/dist/services/HealReportService.d.ts +161 -0
- package/dist/services/HealReportService.d.ts.map +1 -0
- package/dist/services/HealReportService.js +1039 -0
- package/dist/services/HealingAdvisory.d.ts +30 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +51 -0
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +404 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +73 -0
- package/dist/services/IntentAuditor.d.ts +56 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +326 -0
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +128 -0
- package/dist/services/LLMService.d.ts +121 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +475 -0
- package/dist/services/MutationApplier.d.ts +69 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +360 -0
- package/dist/services/PRAutomationService.d.ts +50 -0
- package/dist/services/PRAutomationService.d.ts.map +1 -0
- package/dist/services/PRAutomationService.js +760 -0
- package/dist/services/PendingPromptLedger.d.ts +51 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +205 -0
- package/dist/services/ReportGenerator.d.ts +156 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +278 -0
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +112 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +87 -0
- package/dist/services/Sanitizer.d.ts +28 -0
- package/dist/services/Sanitizer.d.ts.map +1 -0
- package/dist/services/Sanitizer.js +142 -0
- package/dist/services/SanitizingAnthropic.d.ts +19 -0
- package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
- package/dist/services/SanitizingAnthropic.js +84 -0
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +352 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +165 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1091 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +334 -0
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/services/safety/PipelineContext.d.ts +63 -0
- package/dist/services/safety/PipelineContext.d.ts.map +1 -0
- package/dist/services/safety/PipelineContext.js +34 -0
- package/dist/services/safety/roleClassifier.d.ts +4 -0
- package/dist/services/safety/roleClassifier.d.ts.map +1 -0
- package/dist/services/safety/roleClassifier.js +233 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
- package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
- package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
- package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
- package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
- package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
- package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
- package/dist/services/safety/rules/VisibilityRule.js +53 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
- package/dist/services/safety/rules/index.d.ts +3 -0
- package/dist/services/safety/rules/index.d.ts.map +1 -0
- package/dist/services/safety/rules/index.js +34 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +32 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +49 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +466 -0
- package/dist/utils/FileLock.d.ts +44 -0
- package/dist/utils/FileLock.d.ts.map +1 -0
- package/dist/utils/FileLock.js +189 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/utils/SummaryTable.d.ts +17 -0
- package/dist/utils/SummaryTable.d.ts.map +1 -0
- package/dist/utils/SummaryTable.js +37 -0
- package/dist/utils/atomicWrite.d.ts +9 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +83 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +123 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +71 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/FileLock.ts
|
|
3
|
+
//
|
|
4
|
+
// Cross-process mutex built on atomic `mkdir`, extracted from the proven
|
|
5
|
+
// three-layer model in HealingCacheService so every shared-write site reuses
|
|
6
|
+
// ONE audited primitive instead of re-rolling its own lock.
|
|
7
|
+
//
|
|
8
|
+
// Why mkdir (not a lock library): `mkdir` of an existing directory fails with
|
|
9
|
+
// EEXIST atomically on every OS and filesystem Node supports — no extra
|
|
10
|
+
// dependency, no supply-chain surface, works identically across POSIX and
|
|
11
|
+
// Windows and across separate Playwright worker processes.
|
|
12
|
+
//
|
|
13
|
+
// Lock identity IS the directory path. Callers derive it from the resource
|
|
14
|
+
// being guarded (e.g. `<file>.sela.lock`), so locks on different files never
|
|
15
|
+
// contend — per-file granularity preserves worker scalability.
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.FileLock = void 0;
|
|
51
|
+
exports.lockDirFor = lockDirFor;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const logger_1 = require("./logger");
|
|
55
|
+
const DEFAULT_DELAYS_MS = [50, 100, 200, 400, 800, 800];
|
|
56
|
+
const DEFAULT_SYNC_DELAYS_MS = [10, 25, 50, 100];
|
|
57
|
+
class FileLock {
|
|
58
|
+
lockDir;
|
|
59
|
+
opts;
|
|
60
|
+
constructor(lockDir, opts = {}) {
|
|
61
|
+
this.lockDir = lockDir;
|
|
62
|
+
this.opts = opts;
|
|
63
|
+
}
|
|
64
|
+
warn(msg) {
|
|
65
|
+
(this.opts.logger?.warn ?? ((m) => logger_1.logger.warn(m)))(msg);
|
|
66
|
+
}
|
|
67
|
+
/** Ensure the lock dir's PARENT exists (never the lock dir itself — that
|
|
68
|
+
* would defeat the EEXIST mutex). */
|
|
69
|
+
ensureParent() {
|
|
70
|
+
const parent = path.dirname(this.lockDir);
|
|
71
|
+
if (!fs.existsSync(parent)) {
|
|
72
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
reclaimIfStale() {
|
|
76
|
+
const staleMs = this.opts.staleMs ?? 0;
|
|
77
|
+
if (staleMs <= 0)
|
|
78
|
+
return false;
|
|
79
|
+
try {
|
|
80
|
+
const age = Date.now() - fs.statSync(this.lockDir).mtimeMs;
|
|
81
|
+
if (age > staleMs) {
|
|
82
|
+
fs.rmdirSync(this.lockDir);
|
|
83
|
+
this.warn(`[FileLock] reclaimed stale lock ${this.lockDir} (age ${Math.round(age)}ms)`);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// stat/rmdir lost a race with the real holder — fall back to a retry.
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
/** One attempt, no wait. True iff this call now holds the lock. */
|
|
93
|
+
tryAcquire() {
|
|
94
|
+
this.ensureParent();
|
|
95
|
+
try {
|
|
96
|
+
fs.mkdirSync(this.lockDir);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const code = err.code;
|
|
101
|
+
if (code === "EEXIST") {
|
|
102
|
+
if (this.reclaimIfStale()) {
|
|
103
|
+
try {
|
|
104
|
+
fs.mkdirSync(this.lockDir);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
this.warn(`[FileLock] acquire failed at ${this.lockDir}: ${err.message}`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Async acquire with exponential backoff. */
|
|
118
|
+
async acquire() {
|
|
119
|
+
const delays = this.opts.delaysMs ?? DEFAULT_DELAYS_MS;
|
|
120
|
+
for (let attempt = 0; attempt <= delays.length; attempt++) {
|
|
121
|
+
if (this.tryAcquire())
|
|
122
|
+
return true;
|
|
123
|
+
if (attempt === delays.length)
|
|
124
|
+
break;
|
|
125
|
+
await sleep(delays[attempt]);
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
release() {
|
|
130
|
+
try {
|
|
131
|
+
fs.rmdirSync(this.lockDir);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// best-effort — a missing lock dir is already the desired end state.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run `fn` while holding the lock. Returns `fn`'s result, or `undefined`
|
|
139
|
+
* (and warns) when the lock could not be acquired within the backoff
|
|
140
|
+
* window. Mirrors the cache contract: a contended write is skipped, never
|
|
141
|
+
* thrown — the lock guards an optimisation/idempotent write, not a
|
|
142
|
+
* correctness invariant the caller cannot recover from.
|
|
143
|
+
*/
|
|
144
|
+
async withLock(fn) {
|
|
145
|
+
if (!(await this.acquire())) {
|
|
146
|
+
this.warn(`[FileLock] could not acquire ${this.lockDir} — skipped`);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
return await fn();
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
this.release();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Synchronous variant — busy-waits between attempts. */
|
|
157
|
+
withLockSync(fn) {
|
|
158
|
+
const delays = this.opts.syncDelaysMs ?? DEFAULT_SYNC_DELAYS_MS;
|
|
159
|
+
for (let attempt = 0; attempt <= delays.length; attempt++) {
|
|
160
|
+
if (this.tryAcquire()) {
|
|
161
|
+
try {
|
|
162
|
+
return fn();
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
this.release();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (attempt === delays.length)
|
|
169
|
+
break;
|
|
170
|
+
sleepBusy(delays[attempt]);
|
|
171
|
+
}
|
|
172
|
+
this.warn(`[FileLock] could not acquire ${this.lockDir} (sync) — skipped`);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.FileLock = FileLock;
|
|
177
|
+
/** Derive the conventional lock-dir path for a guarded file. */
|
|
178
|
+
function lockDirFor(filePath) {
|
|
179
|
+
return `${filePath}.sela.lock`;
|
|
180
|
+
}
|
|
181
|
+
function sleep(ms) {
|
|
182
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
183
|
+
}
|
|
184
|
+
function sleepBusy(ms) {
|
|
185
|
+
const end = Date.now() + ms;
|
|
186
|
+
while (Date.now() < end) {
|
|
187
|
+
// intentional spin — sync callers cannot await
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface IsolatedDiffOptions {
|
|
2
|
+
/** Lines of unchanged context around each hunk. Default 3 (git default). */
|
|
3
|
+
context?: number;
|
|
4
|
+
/**
|
|
5
|
+
* Path to render in the `--- a/<path>` / `+++ b/<path>` preamble. When
|
|
6
|
+
* omitted the preamble is dropped entirely (hunks only).
|
|
7
|
+
*/
|
|
8
|
+
filePath?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generate a `git`-style unified diff between two in-memory strings.
|
|
12
|
+
* Returns `""` when the two buffers are identical.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateUnifiedDiff(before: string, after: string, opts?: IsolatedDiffOptions): string;
|
|
15
|
+
export interface ChangeBlock {
|
|
16
|
+
/** Old text (newlines preserved). Empty string for pure additions. */
|
|
17
|
+
oldBlock: string;
|
|
18
|
+
/** New text (newlines preserved). Empty string for pure deletions. */
|
|
19
|
+
newBlock: string;
|
|
20
|
+
/** 1-based line in `before` where the block starts (0 if pure add). */
|
|
21
|
+
oldLineStart: number;
|
|
22
|
+
/** 1-based line in `after` where the block starts (0 if pure delete). */
|
|
23
|
+
newLineStart: number;
|
|
24
|
+
/**
|
|
25
|
+
* For pure additions: a non-empty line of unchanged anchor text immediately
|
|
26
|
+
* PRECEDING the new block in the `before` buffer. The replayer uses this
|
|
27
|
+
* to locate the insertion point. `null` when adding at the very top.
|
|
28
|
+
*/
|
|
29
|
+
anchorBefore: string | null;
|
|
30
|
+
}
|
|
31
|
+
export declare function extractChangeBlocks(before: string, after: string): ChangeBlock[];
|
|
32
|
+
export interface ApplyBlocksResult {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
result?: string;
|
|
35
|
+
/** Human-readable failure reason - surfaced in the PR-automation log. */
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Replay a list of change blocks against a fresh base buffer (typically
|
|
40
|
+
* the file's `HEAD` blob). Each block is applied via UNIQUE substring
|
|
41
|
+
* replacement - when the old block is missing or ambiguous in the base,
|
|
42
|
+
* we fail loudly rather than silently mutating the wrong spot.
|
|
43
|
+
*/
|
|
44
|
+
export declare function applyChangeBlocks(base: string, blocks: ChangeBlock[]): ApplyBlocksResult;
|
|
45
|
+
//# sourceMappingURL=IsolatedDiff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IsolatedDiff.d.ts","sourceRoot":"","sources":["../../src/utils/IsolatedDiff.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,mBAAwB,GAC7B,MAAM,CAsBR;AA6PD,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,WAAW,EAAE,CAoDf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EAAE,GACpB,iBAAiB,CAuDnB"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utils/IsolatedDiff.ts
|
|
3
|
+
//
|
|
4
|
+
// Pure-JS unified-diff generator. No external `diff` package, no shell-out to
|
|
5
|
+
// `git diff` - the result is guaranteed to be a function ONLY of the two
|
|
6
|
+
// in-memory buffers passed in, never of any global working-tree state.
|
|
7
|
+
//
|
|
8
|
+
// This is the foundation of the Clean-Room Architecture: the HTML report
|
|
9
|
+
// renders diffs computed here, so a developer's pre-existing uncommitted
|
|
10
|
+
// edits CANNOT leak into Sela's report.
|
|
11
|
+
//
|
|
12
|
+
// Algorithm: classic LCS over lines, then a unified-diff emitter with
|
|
13
|
+
// configurable context. Memory is O(N*M) which is fine for the spec files
|
|
14
|
+
// Sela mutates (typically a few thousand lines max).
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.generateUnifiedDiff = generateUnifiedDiff;
|
|
17
|
+
exports.extractChangeBlocks = extractChangeBlocks;
|
|
18
|
+
exports.applyChangeBlocks = applyChangeBlocks;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a `git`-style unified diff between two in-memory strings.
|
|
21
|
+
* Returns `""` when the two buffers are identical.
|
|
22
|
+
*/
|
|
23
|
+
function generateUnifiedDiff(before, after, opts = {}) {
|
|
24
|
+
if (before === after)
|
|
25
|
+
return "";
|
|
26
|
+
const context = Math.max(0, opts.context ?? 3);
|
|
27
|
+
const a = splitKeepEol(before);
|
|
28
|
+
const b = splitKeepEol(after);
|
|
29
|
+
const ops = diffLines(a, b);
|
|
30
|
+
if (ops.length === 0)
|
|
31
|
+
return "";
|
|
32
|
+
const hunks = groupHunks(ops, { a, b, context });
|
|
33
|
+
if (hunks.length === 0)
|
|
34
|
+
return "";
|
|
35
|
+
const out = [];
|
|
36
|
+
if (opts.filePath) {
|
|
37
|
+
const p = opts.filePath.split(/[\\/]/).join("/");
|
|
38
|
+
out.push(`diff --git a/${p} b/${p}`);
|
|
39
|
+
out.push(`--- a/${p}`);
|
|
40
|
+
out.push(`+++ b/${p}`);
|
|
41
|
+
}
|
|
42
|
+
for (const hunk of hunks) {
|
|
43
|
+
out.push(formatHunkHeader(hunk));
|
|
44
|
+
for (const row of hunk.rows)
|
|
45
|
+
out.push(row);
|
|
46
|
+
}
|
|
47
|
+
return out.join("\n") + "\n";
|
|
48
|
+
}
|
|
49
|
+
// ────────────────────────────────────────────────────────────────────
|
|
50
|
+
// LINE SPLITTER - preserves trailing newline / CRLF so we can emit the
|
|
51
|
+
// canonical `` marker when needed.
|
|
52
|
+
// ────────────────────────────────────────────────────────────────────
|
|
53
|
+
function splitKeepEol(text) {
|
|
54
|
+
if (text.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
const out = [];
|
|
57
|
+
let start = 0;
|
|
58
|
+
for (let i = 0; i < text.length; i++) {
|
|
59
|
+
if (text.charCodeAt(i) === 10 /* \n */) {
|
|
60
|
+
out.push(text.slice(start, i + 1));
|
|
61
|
+
start = i + 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (start < text.length)
|
|
65
|
+
out.push(text.slice(start));
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function stripEol(line) {
|
|
69
|
+
if (line.endsWith("\r\n"))
|
|
70
|
+
return line.slice(0, -2);
|
|
71
|
+
if (line.endsWith("\n"))
|
|
72
|
+
return line.slice(0, -1);
|
|
73
|
+
return line;
|
|
74
|
+
}
|
|
75
|
+
function hasEol(line) {
|
|
76
|
+
return line.endsWith("\n");
|
|
77
|
+
}
|
|
78
|
+
function diffLines(a, b) {
|
|
79
|
+
const m = a.length;
|
|
80
|
+
const n = b.length;
|
|
81
|
+
const w = n + 1;
|
|
82
|
+
const dp = new Uint32Array((m + 1) * w);
|
|
83
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
84
|
+
const ai = a[i];
|
|
85
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
86
|
+
if (ai === b[j]) {
|
|
87
|
+
dp[i * w + j] = dp[(i + 1) * w + (j + 1)] + 1;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const down = dp[(i + 1) * w + j];
|
|
91
|
+
const right = dp[i * w + (j + 1)];
|
|
92
|
+
dp[i * w + j] = down >= right ? down : right;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const ops = [];
|
|
97
|
+
let i = 0;
|
|
98
|
+
let j = 0;
|
|
99
|
+
while (i < m && j < n) {
|
|
100
|
+
if (a[i] === b[j]) {
|
|
101
|
+
ops.push({ kind: "eq", a: i, b: j });
|
|
102
|
+
i++;
|
|
103
|
+
j++;
|
|
104
|
+
}
|
|
105
|
+
else if (dp[(i + 1) * w + j] >= dp[i * w + (j + 1)]) {
|
|
106
|
+
ops.push({ kind: "del", a: i });
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
ops.push({ kind: "add", b: j });
|
|
111
|
+
j++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
while (i < m) {
|
|
115
|
+
ops.push({ kind: "del", a: i });
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
while (j < n) {
|
|
119
|
+
ops.push({ kind: "add", b: j });
|
|
120
|
+
j++;
|
|
121
|
+
}
|
|
122
|
+
return ops;
|
|
123
|
+
}
|
|
124
|
+
function groupHunks(ops, ctx) {
|
|
125
|
+
const hunks = [];
|
|
126
|
+
const ctxN = ctx.context;
|
|
127
|
+
let i = 0;
|
|
128
|
+
while (i < ops.length) {
|
|
129
|
+
// Locate the next non-equal op.
|
|
130
|
+
let firstChange = -1;
|
|
131
|
+
for (let k = i; k < ops.length; k++) {
|
|
132
|
+
if (ops[k].kind !== "eq") {
|
|
133
|
+
firstChange = k;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (firstChange === -1)
|
|
138
|
+
break;
|
|
139
|
+
// Leading-context start, bounded by previous hunk end.
|
|
140
|
+
let hunkStart = firstChange - ctxN;
|
|
141
|
+
if (hunkStart < i)
|
|
142
|
+
hunkStart = i;
|
|
143
|
+
if (hunkStart < 0)
|
|
144
|
+
hunkStart = 0;
|
|
145
|
+
let hunkEnd = firstChange;
|
|
146
|
+
// Extend by merging change groups separated by <= 2*ctxN equality lines.
|
|
147
|
+
// Avoid merging across the very end of `ops`.
|
|
148
|
+
while (true) {
|
|
149
|
+
let cursor = hunkEnd + 1;
|
|
150
|
+
let runEqs = 0;
|
|
151
|
+
let nextChange = -1;
|
|
152
|
+
while (cursor < ops.length) {
|
|
153
|
+
if (ops[cursor].kind === "eq") {
|
|
154
|
+
runEqs++;
|
|
155
|
+
cursor++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
nextChange = cursor;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (nextChange !== -1 && runEqs <= 2 * ctxN) {
|
|
162
|
+
hunkEnd = nextChange;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Trailing context.
|
|
169
|
+
let trailing = 0;
|
|
170
|
+
while (hunkEnd + 1 < ops.length &&
|
|
171
|
+
ops[hunkEnd + 1].kind === "eq" &&
|
|
172
|
+
trailing < ctxN) {
|
|
173
|
+
hunkEnd++;
|
|
174
|
+
trailing++;
|
|
175
|
+
}
|
|
176
|
+
// Materialise rows + compute header coordinates.
|
|
177
|
+
const rows = [];
|
|
178
|
+
let oldStartIdx = -1;
|
|
179
|
+
let newStartIdx = -1;
|
|
180
|
+
let oldLen = 0;
|
|
181
|
+
let newLen = 0;
|
|
182
|
+
for (let k = hunkStart; k <= hunkEnd; k++) {
|
|
183
|
+
const op = ops[k];
|
|
184
|
+
if (op.kind === "eq") {
|
|
185
|
+
if (oldStartIdx === -1)
|
|
186
|
+
oldStartIdx = op.a;
|
|
187
|
+
if (newStartIdx === -1)
|
|
188
|
+
newStartIdx = op.b;
|
|
189
|
+
const line = ctx.a[op.a];
|
|
190
|
+
rows.push(" " + stripEol(line));
|
|
191
|
+
oldLen++;
|
|
192
|
+
newLen++;
|
|
193
|
+
if (!hasEol(line))
|
|
194
|
+
rows.push("\");
|
|
195
|
+
}
|
|
196
|
+
else if (op.kind === "del") {
|
|
197
|
+
if (oldStartIdx === -1)
|
|
198
|
+
oldStartIdx = op.a;
|
|
199
|
+
if (newStartIdx === -1) {
|
|
200
|
+
// Anchor on the corresponding `b` position. For pure-deletions at
|
|
201
|
+
// the top of file `op.a === 0` ⇒ newStart should be 0 (special
|
|
202
|
+
// header form `+0,0`). For mid-file deletions use the running b
|
|
203
|
+
// cursor - derive it from prior ops.
|
|
204
|
+
newStartIdx = computeBAnchor(ops, k);
|
|
205
|
+
}
|
|
206
|
+
const line = ctx.a[op.a];
|
|
207
|
+
rows.push("-" + stripEol(line));
|
|
208
|
+
oldLen++;
|
|
209
|
+
if (!hasEol(line))
|
|
210
|
+
rows.push("\");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
if (newStartIdx === -1)
|
|
214
|
+
newStartIdx = op.b;
|
|
215
|
+
if (oldStartIdx === -1)
|
|
216
|
+
oldStartIdx = computeAAnchor(ops, k);
|
|
217
|
+
const line = ctx.b[op.b];
|
|
218
|
+
rows.push("+" + stripEol(line));
|
|
219
|
+
newLen++;
|
|
220
|
+
if (!hasEol(line))
|
|
221
|
+
rows.push("\");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
hunks.push({
|
|
225
|
+
oldStart: oldLen === 0 ? oldStartIdx : oldStartIdx + 1,
|
|
226
|
+
oldLen,
|
|
227
|
+
newStart: newLen === 0 ? newStartIdx : newStartIdx + 1,
|
|
228
|
+
newLen,
|
|
229
|
+
rows,
|
|
230
|
+
});
|
|
231
|
+
i = hunkEnd + 1;
|
|
232
|
+
}
|
|
233
|
+
return hunks;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* For a pure-add op at position `k`, walk backwards to find the prior
|
|
237
|
+
* `a` index (so we know which line of the old file the hunk starts at).
|
|
238
|
+
*/
|
|
239
|
+
function computeAAnchor(ops, k) {
|
|
240
|
+
for (let p = k - 1; p >= 0; p--) {
|
|
241
|
+
const op = ops[p];
|
|
242
|
+
if (op.kind === "eq")
|
|
243
|
+
return op.a + 1;
|
|
244
|
+
if (op.kind === "del")
|
|
245
|
+
return op.a + 1;
|
|
246
|
+
}
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
function computeBAnchor(ops, k) {
|
|
250
|
+
for (let p = k - 1; p >= 0; p--) {
|
|
251
|
+
const op = ops[p];
|
|
252
|
+
if (op.kind === "eq")
|
|
253
|
+
return op.b + 1;
|
|
254
|
+
if (op.kind === "add")
|
|
255
|
+
return op.b + 1;
|
|
256
|
+
}
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
function formatHunkHeader(hunk) {
|
|
260
|
+
const oldPart = hunk.oldLen === 1 ? `${hunk.oldStart}` : `${hunk.oldStart},${hunk.oldLen}`;
|
|
261
|
+
const newPart = hunk.newLen === 1 ? `${hunk.newStart}` : `${hunk.newStart},${hunk.newLen}`;
|
|
262
|
+
return `@@ -${oldPart} +${newPart} @@`;
|
|
263
|
+
}
|
|
264
|
+
function extractChangeBlocks(before, after) {
|
|
265
|
+
if (before === after)
|
|
266
|
+
return [];
|
|
267
|
+
const a = splitKeepEol(before);
|
|
268
|
+
const b = splitKeepEol(after);
|
|
269
|
+
const ops = diffLines(a, b);
|
|
270
|
+
const blocks = [];
|
|
271
|
+
let i = 0;
|
|
272
|
+
while (i < ops.length) {
|
|
273
|
+
if (ops[i].kind === "eq") {
|
|
274
|
+
i++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const start = i;
|
|
278
|
+
while (i < ops.length && ops[i].kind !== "eq")
|
|
279
|
+
i++;
|
|
280
|
+
const oldLines = [];
|
|
281
|
+
const newLines = [];
|
|
282
|
+
let oldStart = -1;
|
|
283
|
+
let newStart = -1;
|
|
284
|
+
for (let k = start; k < i; k++) {
|
|
285
|
+
const op = ops[k];
|
|
286
|
+
if (op.kind === "del") {
|
|
287
|
+
if (oldStart === -1)
|
|
288
|
+
oldStart = op.a;
|
|
289
|
+
oldLines.push(a[op.a]);
|
|
290
|
+
}
|
|
291
|
+
else if (op.kind === "add") {
|
|
292
|
+
if (newStart === -1)
|
|
293
|
+
newStart = op.b;
|
|
294
|
+
newLines.push(b[op.b]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
let anchorBefore = null;
|
|
298
|
+
if (oldLines.length === 0) {
|
|
299
|
+
// Pure addition - find preceding unchanged anchor line.
|
|
300
|
+
for (let k = start - 1; k >= 0; k--) {
|
|
301
|
+
const op = ops[k];
|
|
302
|
+
if (op.kind === "eq") {
|
|
303
|
+
anchorBefore = a[op.a];
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
blocks.push({
|
|
309
|
+
oldBlock: oldLines.join(""),
|
|
310
|
+
newBlock: newLines.join(""),
|
|
311
|
+
oldLineStart: oldStart >= 0 ? oldStart + 1 : 0,
|
|
312
|
+
newLineStart: newStart >= 0 ? newStart + 1 : 0,
|
|
313
|
+
anchorBefore,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return blocks;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Replay a list of change blocks against a fresh base buffer (typically
|
|
320
|
+
* the file's `HEAD` blob). Each block is applied via UNIQUE substring
|
|
321
|
+
* replacement - when the old block is missing or ambiguous in the base,
|
|
322
|
+
* we fail loudly rather than silently mutating the wrong spot.
|
|
323
|
+
*/
|
|
324
|
+
function applyChangeBlocks(base, blocks) {
|
|
325
|
+
let working = base;
|
|
326
|
+
for (const block of blocks) {
|
|
327
|
+
if (block.oldBlock.length === 0) {
|
|
328
|
+
// Pure addition.
|
|
329
|
+
if (block.anchorBefore === null) {
|
|
330
|
+
if (working.length === 0) {
|
|
331
|
+
// Empty file - prepend.
|
|
332
|
+
working = block.newBlock + working;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
// Prepend at top of file.
|
|
336
|
+
working = block.newBlock + working;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const idx = working.indexOf(block.anchorBefore);
|
|
340
|
+
if (idx === -1) {
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
reason: `pure-add anchor not found in clean base: "${snippet(block.anchorBefore)}"`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const lastIdx = working.lastIndexOf(block.anchorBefore);
|
|
347
|
+
if (idx !== lastIdx) {
|
|
348
|
+
return {
|
|
349
|
+
ok: false,
|
|
350
|
+
reason: `pure-add anchor is not unique in clean base: "${snippet(block.anchorBefore)}"`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const insertAt = idx + block.anchorBefore.length;
|
|
354
|
+
working =
|
|
355
|
+
working.slice(0, insertAt) + block.newBlock + working.slice(insertAt);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const idx = working.indexOf(block.oldBlock);
|
|
359
|
+
if (idx === -1) {
|
|
360
|
+
return {
|
|
361
|
+
ok: false,
|
|
362
|
+
reason: `block not found in clean base - developer's dirty state likely overlaps Sela's mutation site: "${snippet(block.oldBlock)}"`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const lastIdx = working.lastIndexOf(block.oldBlock);
|
|
366
|
+
if (idx !== lastIdx) {
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
reason: `block matches multiple locations in clean base - refusing to guess: "${snippet(block.oldBlock)}"`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
working =
|
|
373
|
+
working.slice(0, idx) +
|
|
374
|
+
block.newBlock +
|
|
375
|
+
working.slice(idx + block.oldBlock.length);
|
|
376
|
+
}
|
|
377
|
+
return { ok: true, result: working };
|
|
378
|
+
}
|
|
379
|
+
function snippet(s) {
|
|
380
|
+
const flat = s.replace(/\r?\n/g, "\\n");
|
|
381
|
+
return flat.length > 80 ? flat.slice(0, 77) + "..." : flat;
|
|
382
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class StackUtils {
|
|
2
|
+
static getCallerInfo(stackOverride?: string): {
|
|
3
|
+
filePath: string;
|
|
4
|
+
fileName: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
locationKey: string;
|
|
8
|
+
};
|
|
9
|
+
static getActionKey(testTitle: string, action: string, index: number): string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=StackUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StackUtils.d.ts","sourceRoot":"","sources":["../../src/utils/StackUtils.ts"],"names":[],"mappings":"AAGA,qBAAa,UAAU;IACrB,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG;QAC5C,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;KACrB;IAmED,MAAM,CAAC,YAAY,CACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM;CA6BV"}
|