sicua
Version:
A tool for analyzing project structure and dependencies
221 lines (220 loc) • 8.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.findCommonStructure = findCommonStructure;
exports.calculateStructureSimilarity = calculateStructureSimilarity;
exports.calculateChildComponentSimilarity = calculateChildComponentSimilarity;
exports.calculateStyleSimilarity = calculateStyleSimilarity;
exports.calculateStructureComplexity = calculateStructureComplexity;
exports.countJSXNodes = countJSXNodes;
/**
* Finds common JSX structure between two component structures
* @param struct1 First JSX structure
* @param struct2 Second JSX structure
* @returns Array of common JSX structures
*/
function findCommonStructure(struct1, struct2) {
if (!struct1 || !struct2)
return [];
// Check for similar structure even with different root names
if (isStructurallySimilar(struct1, struct2)) {
// Return common structure recursively
return [
{
tagName: struct1.tagName,
props: [], // Props will be handled by prop comparison utilities
children: struct1.children.map((child, index) =>
// For each child in struct1, find common structure with corresponding child in struct2
findCommonStructure(child, struct2.children[index])[0] || child),
},
];
}
return [];
}
/**
* Calculates similarity between two JSX structures
* @param common Common JSX structure
* @param originals Array of original JSX structures
* @returns Similarity score between 0 and 1
*/
function calculateStructureSimilarity(common, originals) {
if (!common.length || originals.some((o) => !o))
return 0;
const commonCount = countJSXNodes(common);
const originalCounts = originals.map((o) => countJSXNodes([o]));
return commonCount / Math.max(...originalCounts);
}
/**
* Calculates similarity of child components between structures
* @param struct1 First JSX structure
* @param struct2 Second JSX structure
* @returns Similarity score between 0 and 1
*/
function calculateChildComponentSimilarity(struct1, struct2) {
if (!struct1 || !struct2)
return 0;
// Get all nodes in a flat structure with their depth
const nodes1 = flattenStructure(struct1);
const nodes2 = flattenStructure(struct2);
// Count components at each depth level
const depthMap1 = getDepthMap(nodes1);
const depthMap2 = getDepthMap(nodes2);
// Compare structures at each depth
const maxDepth = Math.max(...Array.from(depthMap1.keys()), ...Array.from(depthMap2.keys()));
let similaritySum = 0;
let depthCount = 0;
for (let depth = 0; depth <= maxDepth; depth++) {
const map1 = depthMap1.get(depth) || new Map();
const map2 = depthMap2.get(depth) || new Map();
const allTags = new Set([...map1.keys(), ...map2.keys()]);
let depthSimilarity = 0;
allTags.forEach((tag) => {
const count1 = map1.get(tag) || 0;
const count2 = map2.get(tag) || 0;
// If both structures have this tag at this depth
if (count1 > 0 && count2 > 0) {
depthSimilarity += Math.min(count1, count2) / Math.max(count1, count2);
}
});
if (allTags.size > 0) {
similaritySum += depthSimilarity / allTags.size;
depthCount++;
}
}
return depthCount > 0 ? similaritySum / depthCount : 0;
}
/**
* Calculates style similarity between two JSX structures based on className attributes
* @param struct1 First JSX structure
* @param struct2 Second JSX structure
* @returns Similarity score between 0 and 1
*/
function calculateStyleSimilarity(struct1, struct2) {
if (!struct1 || !struct2)
return 0;
// Extract and compare className attributes
const classes1 = getClassNames(struct1);
const classes2 = getClassNames(struct2);
if (classes1.size === 0 && classes2.size === 0)
return 1; // Both have no classes, consider them similar
const commonClasses = [...classes1].filter((c) => classes2.has(c));
const totalClasses = new Set([...classes1, ...classes2]);
return commonClasses.length / totalClasses.size;
}
/**
* Calculates the complexity of a JSX structure
* @param struct JSX structure to analyze
* @returns Structure complexity information
*/
function calculateStructureComplexity(struct) {
if (!struct) {
return { totalNodes: 0, maxDepth: 0, componentCount: 0, complexity: 0 };
}
let totalNodes = 0;
let maxDepth = 0;
let componentCount = 0;
let complexity = 0;
// Traverse the structure to calculate metrics
const traverse = (node, depth) => {
totalNodes++;
maxDepth = Math.max(maxDepth, depth);
// Add base complexity for the node
let nodeComplexity = 1;
// Add complexity for props
nodeComplexity += node.props.length * 0.5;
// Add complexity for custom components (uppercase first letter)
if (node.tagName[0] === node.tagName[0].toUpperCase()) {
nodeComplexity += 1;
componentCount++;
}
// Add complexity for styling
if (node.props.some((p) => p.name === "className")) {
nodeComplexity += 0.5;
}
complexity += nodeComplexity;
// Recursively process children
node.children.forEach((child) => traverse(child, depth + 1));
};
traverse(struct, 0);
return { totalNodes, maxDepth, componentCount, complexity };
}
/**
* Counts the total number of nodes in a JSX structure
* @param structure Array of JSX structures
* @returns Total node count
*/
function countJSXNodes(structure) {
return structure.reduce((count, node) => count + 1 + countJSXNodes(node.children), 0);
}
/**
* Checks if two JSX structures are similar in basic shape
* @param s1 First JSX structure
* @param s2 Second JSX structure
* @returns Boolean indicating structural similarity
*/
function isStructurallySimilar(s1, s2) {
// Both are likely React components if they start with uppercase
const bothComponents = s1.tagName[0] === s1.tagName[0].toUpperCase() &&
s2.tagName[0] === s2.tagName[0].toUpperCase();
return (s1.tagName === s2.tagName ||
(bothComponents && s1.children.length === s2.children.length));
}
/**
* Flattens a JSX structure into an array of nodes with depth information
* @param struct JSX structure to flatten
* @returns Array of flattened nodes
*/
function flattenStructure(struct) {
const nodes = [];
const traverse = (node, depth, path = "") => {
const className = node.props.find((p) => p.name === "className")?.type;
nodes.push({
tagName: node.tagName,
depth,
path,
className: className?.replace(/['"]/g, ""),
props: node.props.map((p) => ({ name: p.name, value: p.type })),
});
node.children.forEach((child, index) => {
traverse(child, depth + 1, `${path}${path ? "." : ""}children[${index}]`);
});
};
traverse(struct, 0);
return nodes;
}
/**
* Creates a map of tag counts by depth
* @param nodes Flattened JSX nodes
* @returns Map of depth to tag counts
*/
function getDepthMap(nodes) {
const map = new Map();
nodes.forEach(({ tagName, depth }) => {
if (!map.has(depth)) {
map.set(depth, new Map());
}
const depthMap = map.get(depth);
depthMap.set(tagName, (depthMap.get(tagName) || 0) + 1);
});
return map;
}
/**
* Extracts all class names from a JSX structure
* @param struct JSX structure
* @returns Set of class names
*/
function getClassNames(struct) {
const classes = new Set();
const addClasses = (s) => {
const className = s.props.find((p) => p.name === "className");
if (className?.type) {
const classNames = className.type
.replace(/['"]/g, "")
.split(" ")
.filter(Boolean);
classNames.forEach((c) => classes.add(c));
}
s.children.forEach(addClasses);
};
addClasses(struct);
return classes;
}