UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

122 lines (103 loc) 3.56 kB
/** * Utilities for calculating and displaying diffs between files * Used for security change analysis in the watch command */ export interface DiffChange { value: string; added?: boolean; removed?: boolean; } /** * Compute diff between two strings * @param oldStr Previous string content * @param newStr New string content * @returns Array of diff changes */ export function diff(oldStr: string, newStr: string): DiffChange[] { if (oldStr === newStr) { return [{ value: newStr }]; } // Convert strings to arrays of lines const oldLines = oldStr.split('\n'); const newLines = newStr.split('\n'); const result: DiffChange[] = []; // Myers diff algorithm simplified for line-by-line comparison const MAX_EDIT_LENGTH = Math.max(oldLines.length, newLines.length); // Identify unchanged lines let i = 0; while (i < oldLines.length && i < newLines.length && oldLines[i] === newLines[i]) { i++; } if (i > 0) { // Add context of unchanged lines at the beginning (limited to a few lines) const contextLines = oldLines.slice(Math.max(0, i - 3), i); if (contextLines.length > 0) { result.push({ value: contextLines.join('\n') }); } } // Find trailing same lines let j = 0; while ( oldLines.length - j - 1 >= 0 && newLines.length - j - 1 >= 0 && oldLines[oldLines.length - j - 1] === newLines[newLines.length - j - 1] && oldLines.length - j - 1 > i && newLines.length - j - 1 > i ) { j++; } // Add removed lines if (i < oldLines.length - j) { const removedLines = oldLines.slice(i, oldLines.length - j); result.push({ value: removedLines.join('\n'), removed: true }); } // Add added lines if (i < newLines.length - j) { const addedLines = newLines.slice(i, newLines.length - j); result.push({ value: addedLines.join('\n'), added: true }); } // Add unchanged lines at the end for context if (j > 0) { const contextLines = oldLines.slice(oldLines.length - Math.min(j, 3), oldLines.length); if (contextLines.length > 0) { result.push({ value: contextLines.join('\n') }); } } return result; } /** * Check if a line contains security-sensitive changes * @param line Line of code to check * @returns Whether line contains security-sensitive keywords */ export function isSecuritySensitiveChange(line: string): boolean { const securityKeywords = [ 'auth', 'password', 'token', 'secret', 'key', 'credential', 'encrypt', 'decrypt', 'hash', 'salt', 'security', 'permission', 'XSS', 'CSRF', 'SQL', 'injection', 'sanitize', 'validate', 'CORS', 'header', 'certificate', 'SSL', 'TLS', 'HTTPS' ]; const lowerLine = line.toLowerCase(); return securityKeywords.some(keyword => lowerLine.includes(keyword.toLowerCase())); } /** * Highlight security-sensitive changes in diff output * @param changes Diff changes to analyze * @returns Formatted string with highlighted security changes */ export function highlightSecurityChanges(changes: DiffChange[]): string { let highlightedOutput = ''; for (const change of changes) { const lines = change.value.split('\n'); for (const line of lines) { const isSecurityLine = isSecuritySensitiveChange(line); const indicator = change.added ? '+' : change.removed ? '-' : ' '; if (isSecurityLine) { highlightedOutput += `🔒 ${indicator} ${line}\n`; } else { highlightedOutput += `${indicator} ${line}\n`; } } } return highlightedOutput; }