A JavaScript text diff implementation based on the Myers algorithm for comparing text at different granularities.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Apply unified diff patches to source text with fuzzy matching and error handling. Supports both single patches and batch patch application with configurable tolerance for mismatches.
Applies a unified diff patch to source text with intelligent matching and fuzzy logic.
/**
* Apply a unified diff patch to source text
* @param source - Original text to patch
* @param patch - Unified diff patch string or structured patch object
* @param options - Configuration options
* @returns Patched text on success, false on failure
*/
function applyPatch(source, patch, options);Applies multiple patches using callback-based file loading and saving.
/**
* Apply multiple patches with callback-based file operations
* @param patches - Array of patches or multi-file patch string
* @param options - Configuration with callback functions
*/
function applyPatches(patches, options);interface ApplyPatchOptions {
fuzzFactor?: number; // Maximum line mismatches allowed (default: 0)
autoConvertLineEndings?: boolean; // Auto-convert line endings (default: true)
compareLine?: (lineNumber: number, line: string, operation: string, patchContent: string) => boolean;
}
interface ApplyPatchesOptions {
loadFile: (patch: any, callback: (error: Error | null, content?: string) => void) => void;
patched: (patch: any, content: string | false, callback: (error?: Error) => void) => void;
complete: (error?: Error) => void;
}import { applyPatch, createPatch } from "diff";
// Create a patch
const originalText = "Hello World\nLine 2\nLine 3";
const modifiedText = "Hello Universe\nLine 2\nLine 3";
const patch = createPatch("file.txt", originalText, modifiedText);
// Apply the patch
const result = applyPatch(originalText, patch);
console.log(result === modifiedText); // true
// Failed application returns false
const badResult = applyPatch("completely different text", patch);
console.log(badResult); // falseimport { applyPatch } from "diff";
// Source text has slight differences from when patch was created
const sourceText = `
function hello() {
console.log("Hello World");
return true;
}
function goodbye() {
console.log("Goodbye");
}
`.trim();
// Patch expects slightly different context
const patch = `--- file.js
+++ file.js
@@ -1,4 +1,4 @@
function hello() {
- console.log("Hello World");
+ console.log("Hello Universe");
return true;
}`;
// Apply with fuzzy matching
const result = applyPatch(sourceText, patch, {
fuzzFactor: 2 // Allow up to 2 mismatched context lines
});
console.log(result); // Successfully patched despite context differencesimport { applyPatch } from "diff";
// Source has Unix line endings
const unixSource = "line1\nline2\nline3\n";
// Patch was created from Windows line endings
const windowsPatch = `--- file.txt
+++ file.txt
@@ -1,3 +1,3 @@
line1\r
-line2\r
+modified line2\r
line3\r`;
// Auto-convert line endings (default behavior)
const result = applyPatch(unixSource, windowsPatch, {
autoConvertLineEndings: true
});
console.log(result); // Successfully applied despite line ending differencesimport { applyPatches } from "diff";
import fs from "fs";
// Multi-file patch string
const multiPatch = `--- file1.txt
+++ file1.txt
@@ -1,2 +1,2 @@
-old content
+new content
unchanged line
--- file2.txt
+++ file2.txt
@@ -1,1 +1,2 @@
existing line
+added line`;
applyPatches(multiPatch, {
loadFile: (patch, callback) => {
try {
const content = fs.readFileSync(patch.oldFileName, 'utf8');
callback(null, content);
} catch (error) {
callback(error);
}
},
patched: (patch, patchedContent, callback) => {
if (patchedContent === false) {
callback(new Error(`Failed to apply patch to ${patch.oldFileName}`));
return;
}
try {
fs.writeFileSync(patch.oldFileName, patchedContent);
console.log(`Successfully patched ${patch.oldFileName}`);
callback();
} catch (error) {
callback(error);
}
},
complete: (error) => {
if (error) {
console.error("Patch application failed:", error);
} else {
console.log("All patches applied successfully");
}
}
});import { applyPatch } from "diff";
// Custom comparison function for case-insensitive matching
const result = applyPatch(source, patch, {
compareLine: (lineNumber, line, operation, patchContent) => {
// Case-insensitive comparison
return line.toLowerCase() === patchContent.toLowerCase();
}
});
// Whitespace-tolerant comparison
const whitespaceResult = applyPatch(source, patch, {
compareLine: (lineNumber, line, operation, patchContent) => {
return line.trim() === patchContent.trim();
}
});import { applyPatches } from "diff";
import path from "path";
function applyPatchesToDirectory(patchContent, baseDirectory) {
const results = {
successful: [],
failed: [],
errors: []
};
return new Promise((resolve, reject) => {
applyPatches(patchContent, {
loadFile: (patch, callback) => {
const filePath = path.join(baseDirectory, patch.oldFileName);
fs.readFile(filePath, 'utf8', (error, content) => {
if (error) {
results.errors.push({ file: patch.oldFileName, error: error.message });
callback(error);
} else {
callback(null, content);
}
});
},
patched: (patch, patchedContent, callback) => {
if (patchedContent === false) {
results.failed.push(patch.oldFileName);
callback(new Error(`Patch failed for ${patch.oldFileName}`));
return;
}
const filePath = path.join(baseDirectory, patch.oldFileName);
fs.writeFile(filePath, patchedContent, 'utf8', (error) => {
if (error) {
results.errors.push({ file: patch.oldFileName, error: error.message });
callback(error);
} else {
results.successful.push(patch.oldFileName);
callback();
}
});
},
complete: (error) => {
if (error && results.successful.length === 0) {
reject(error);
} else {
resolve(results);
}
}
});
});
}
// Usage
applyPatchesToDirectory(largePatch, './src')
.then(results => {
console.log(`Applied patches to ${results.successful.length} files`);
if (results.failed.length > 0) {
console.log(`Failed to patch: ${results.failed.join(', ')}`);
}
})
.catch(error => {
console.error("Batch patch application failed:", error);
});import { applyPatch } from "diff";
function validatePatch(source, patch, options = {}) {
const validation = {
canApply: false,
requiredFuzzFactor: 0,
issues: []
};
// Try applying with increasing fuzz factors
for (let fuzz = 0; fuzz <= 10; fuzz++) {
const result = applyPatch(source, patch, {
...options,
fuzzFactor: fuzz
});
if (result !== false) {
validation.canApply = true;
validation.requiredFuzzFactor = fuzz;
validation.result = result;
break;
}
}
if (!validation.canApply) {
validation.issues.push("Patch cannot be applied even with maximum fuzz factor");
} else if (validation.requiredFuzzFactor > 0) {
validation.issues.push(`Requires fuzz factor of ${validation.requiredFuzzFactor}`);
}
return validation;
}
// Usage
const patchValidation = validatePatch(sourceCode, patch);
if (patchValidation.canApply) {
console.log("Patch can be applied");
if (patchValidation.requiredFuzzFactor > 0) {
console.log(`Warning: Requires fuzzy matching (${patchValidation.requiredFuzzFactor})`);
}
} else {
console.log("Patch cannot be applied:", patchValidation.issues);
}import { applyPatch } from "diff";
function robustPatchApplication(source, patch, options = {}) {
const attempts = [
// First attempt: strict application
{ ...options, fuzzFactor: 0 },
// Second attempt: allow minor mismatches
{ ...options, fuzzFactor: 2 },
// Third attempt: more tolerant matching
{
...options,
fuzzFactor: 5,
compareLine: (lineNumber, line, operation, patchContent) => {
return line.trim() === patchContent.trim();
}
},
// Last attempt: very fuzzy with case insensitive
{
...options,
fuzzFactor: 10,
compareLine: (lineNumber, line, operation, patchContent) => {
return line.toLowerCase().trim() === patchContent.toLowerCase().trim();
}
}
];
for (let i = 0; i < attempts.length; i++) {
const result = applyPatch(source, patch, attempts[i]);
if (result !== false) {
return {
success: true,
result: result,
attempt: i + 1,
strategy: attempts[i]
};
}
}
return {
success: false,
error: "All patch application strategies failed"
};
}
// Usage
const robustResult = robustPatchApplication(sourceText, problemPatch);
if (robustResult.success) {
console.log(`Patch applied using strategy ${robustResult.attempt}`);
} else {
console.error("Patch could not be applied:", robustResult.error);
}import { applyPatch } from "diff";
import { execSync } from "child_process";
function applyGitPatch(patchFile, options = {}) {
try {
// Try using git apply first
execSync(`git apply --check ${patchFile}`);
execSync(`git apply ${patchFile}`);
return { success: true, method: 'git' };
} catch (gitError) {
console.log("Git apply failed, trying manual application...");
// Fallback to manual patch application
const patchContent = fs.readFileSync(patchFile, 'utf8');
const patches = parsePatch(patchContent);
const results = [];
for (const patch of patches) {
const sourceContent = fs.readFileSync(patch.oldFileName, 'utf8');
const result = applyPatch(sourceContent, patch, {
fuzzFactor: 3,
...options
});
if (result !== false) {
fs.writeFileSync(patch.oldFileName, result);
results.push({ file: patch.oldFileName, success: true });
} else {
results.push({ file: patch.oldFileName, success: false });
}
}
return {
success: results.every(r => r.success),
method: 'manual',
results: results
};
}
}