arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
180 lines • 6.17 kB
JavaScript
/**
* Main drift detection function
*/
export function detectDrift(matches, unmatchedCalls, unmatchedEndpoints) {
const issues = [];
// Check for mismatches in matched pairs
for (const match of matches) {
for (const call of match.calls) {
const driftInPair = detectPairDrift(call, match.endpoint);
issues.push(...driftInPair);
}
}
// Add issues for unmatched calls
for (const call of unmatchedCalls) {
issues.push({
type: 'endpoint-not-found',
severity: 'critical',
call,
message: `Frontend calls ${call.method} ${call.url} but endpoint not found in backend`,
suggestion: `Check if endpoint exists or if path differs from frontend call`,
});
}
// Add issues for unmatched endpoints
for (const endpoint of unmatchedEndpoints) {
// Only report if it's an API endpoint (not deprecated or internal)
if (!endpoint.path.includes('_internal') && !endpoint.path.includes('deprecated')) {
issues.push({
type: 'endpoint-not-found',
severity: 'high',
endpoint,
message: `Backend defines ${endpoint.method} ${endpoint.path} but no frontend calls found`,
suggestion: `Endpoint may be unused or frontend may be using different path`,
});
}
}
return issues;
}
/**
* Detect drift between a specific call and endpoint pair
*/
function detectPairDrift(call, endpoint) {
const issues = [];
// Check method mismatch
if (call.method !== endpoint.method) {
issues.push({
type: 'method-mismatch',
severity: 'critical',
call,
endpoint,
message: `Method mismatch: frontend calls ${call.method} but backend defines ${endpoint.method}`,
suggestion: `Update either frontend call or backend endpoint to use ${endpoint.method}`,
});
}
// Check path mismatch (exact vs fuzzy match)
const pathDrift = detectPathDrift(call.url, endpoint.path);
if (pathDrift) {
issues.push(pathDrift);
}
return issues;
}
/**
* Detect path drift between call URL and endpoint path
*/
function detectPathDrift(callUrl, endpointPath) {
// Normalize both for comparison
const normalizedCall = normalizeForComparison(callUrl);
const normalizedEndpoint = normalizeForComparison(endpointPath);
// Exact match is good
if (normalizedCall === normalizedEndpoint) {
return null;
}
// Check for common naming issues
const singular = singularize(endpointPath);
const plural = pluralize(endpointPath);
const callNormalized = normalizeForComparison(callUrl);
// Check singular/plural mismatch
if (normalizeForComparison(plural) === callNormalized) {
return {
type: 'path-mismatch',
severity: 'high',
message: `Path mismatch: frontend calls ${callUrl} but backend defines ${endpointPath} (singular/plural)`,
suggestion: `Standardize endpoint naming to use plural form consistently`,
};
}
if (normalizeForComparison(singular) === callNormalized) {
return {
type: 'path-mismatch',
severity: 'high',
message: `Path mismatch: frontend calls ${callUrl} but backend defines ${endpointPath} (singular/plural)`,
suggestion: `Standardize endpoint naming to use singular form consistently`,
};
}
// Check camelCase vs snake_case
const callCamelCase = toCamelCase(callUrl);
const endpointCamelCase = toCamelCase(endpointPath);
if (callCamelCase === endpointCamelCase) {
return {
type: 'path-mismatch',
severity: 'medium',
message: `Path case mismatch: frontend calls ${callUrl} but backend defines ${endpointPath}`,
suggestion: `Standardize naming convention: use snake_case or camelCase consistently`,
};
}
// Generic path mismatch
return {
type: 'path-mismatch',
severity: 'medium',
message: `Path mismatch: frontend calls ${callUrl} but backend defines ${endpointPath}`,
suggestion: `Verify endpoint path is correct on both frontend and backend`,
};
}
/**
* Normalize path for comparison
*/
function normalizeForComparison(path) {
return path
.toLowerCase()
.replace(/[{}$]/g, '') // Remove template literal markers
.replace(/\//g, '-') // Convert to dash-separated
.replace(/[_-]+/g, '-'); // Normalize separators
}
/**
* Convert string to camelCase
*/
function toCamelCase(str) {
return str
.split(/[-_/]/)
.map((word, i) => i === 0
? word.toLowerCase()
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
}
/**
* Simple pluralization
*/
function pluralize(word) {
const lastWord = word.split('/').pop() || word;
if (!lastWord)
return word;
// Check common patterns
if (lastWord.endsWith('y')) {
return word.replace(/y$/, 'ies');
}
if (lastWord.endsWith('s') || lastWord.endsWith('x') || lastWord.endsWith('z')) {
return word + 'es';
}
return word + 's';
}
/**
* Simple singularization
*/
function singularize(word) {
const lastWord = word.split('/').pop() || word;
if (!lastWord)
return word;
// Check common patterns
if (lastWord.endsWith('ies')) {
return word.replace(/ies$/, 'y');
}
if (lastWord.endsWith('es')) {
return word.slice(0, -2);
}
if (lastWord.endsWith('s')) {
return word.slice(0, -1);
}
return word;
}
/**
* Get severity counts
*/
export function getDriftSummary(issues) {
return {
total: issues.length,
critical: issues.filter(i => i.severity === 'critical').length,
high: issues.filter(i => i.severity === 'high').length,
medium: issues.filter(i => i.severity === 'medium').length,
low: issues.filter(i => i.severity === 'low').length,
};
}
//# sourceMappingURL=drift-detector.js.map