arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
126 lines • 4.32 kB
JavaScript
/**
* Calculate Levenshtein distance between two strings
*/
function levenshteinDistance(a, b) {
const matrix = [];
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
}
else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
}
}
}
return matrix[b.length][a.length];
}
/**
* Calculate similarity score between two strings (0-1)
*/
function calculateSimilarity(a, b) {
const distance = levenshteinDistance(a, b);
const maxLength = Math.max(a.length, b.length);
if (maxLength === 0) {
return 1.0;
}
return 1.0 - distance / maxLength;
}
/**
* Normalize a path by replacing parameter values with placeholders
*/
function normalizePath(path) {
// Replace :id, :uuid, :param with consistent placeholder
return path
.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, ':param')
.toLowerCase();
}
/**
* Match calls to endpoints using fuzzy matching
*/
export function matchCallsToEndpoints(calls, endpoints, threshold = 0.75) {
const matches = [];
const matchedCallIds = new Set();
for (const endpoint of endpoints) {
const candidateCalls = calls.filter(c => c.method === endpoint.method &&
!matchedCallIds.has(c.id));
if (candidateCalls.length === 0) {
continue;
}
// Normalize endpoint path
const normalizedEndpointPath = normalizePath(endpoint.path);
// Find all calls that match this endpoint
const matchingCalls = [];
for (const call of candidateCalls) {
const normalizedCallPath = normalizePath(call.url);
const similarity = calculateSimilarity(normalizedCallPath, normalizedEndpointPath);
if (similarity >= threshold) {
matchingCalls.push(call);
matchedCallIds.add(call.id);
}
}
if (matchingCalls.length > 0) {
// Calculate average similarity for all matching calls
const similarities = matchingCalls.map(c => {
const normalizedCallPath = normalizePath(c.url);
return calculateSimilarity(normalizedCallPath, normalizedEndpointPath);
});
const avgSimilarity = similarities.reduce((a, b) => a + b, 0) / similarities.length;
matches.push({
endpoint,
calls: matchingCalls,
similarity: avgSimilarity,
});
}
}
return matches;
}
/**
* Find unmatched calls (frontend calls with no matching endpoint)
*/
export function findUnmatchedCalls(calls, matches) {
const matchedCallIds = new Set();
for (const match of matches) {
for (const call of match.calls) {
matchedCallIds.add(call.id);
}
}
return calls.filter(c => !matchedCallIds.has(c.id));
}
/**
* Find unmatched endpoints (backend endpoints with no frontend calls)
*/
export function findUnmatchedEndpoints(endpoints, matches) {
const matchedEndpointIds = new Set();
for (const match of matches) {
matchedEndpointIds.add(match.endpoint.id);
}
return endpoints.filter(e => !matchedEndpointIds.has(e.id));
}
/**
* Get matching statistics
*/
export function getMatchingStats(calls, endpoints, matches) {
const unmatchedCalls = findUnmatchedCalls(calls, matches);
const unmatchedEndpoints = findUnmatchedEndpoints(endpoints, matches);
return {
totalCalls: calls.length,
totalEndpoints: endpoints.length,
matchedCount: matches.length,
unmatchedCallsCount: unmatchedCalls.length,
unmatchedEndpointsCount: unmatchedEndpoints.length,
matchPercentage: endpoints.length > 0
? (matches.length / endpoints.length) * 100
: 0,
avgSimilarity: matches.length > 0
? matches.reduce((sum, m) => sum + m.similarity, 0) / matches.length
: 0,
};
}
//# sourceMappingURL=matcher.js.map