sicua
Version:
A tool for analyzing project structure and dependencies
164 lines (163 loc) • 6.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findCommonProps = findCommonProps;
exports.extractPropTypes = extractPropTypes;
exports.findPropDefaultValue = findPropDefaultValue;
exports.calculatePropsSimilarity = calculatePropsSimilarity;
const typescript_1 = __importDefault(require("typescript"));
const ASTUtils_1 = require("../../../utils/ast/ASTUtils");
/**
* Finds common props between two sets of props
* @param props1 First set of props
* @param props2 Second set of props
* @returns Array of common props
*/
function findCommonProps(props1, props2) {
if (!props1 || !props2)
return [];
return props1.filter((prop1) => props2.some((prop2) => prop1.name === prop2.name && prop1.type === prop2.type));
}
/**
* Extracts prop types from a component node
* @param node The component AST node
* @param sourceFile Source file containing the component
* @returns Array of prop signatures
*/
function extractPropTypes(node, sourceFile) {
const props = [];
// Handle arrow functions with destructured props
if (typescript_1.default.isVariableDeclaration(node) &&
node.initializer &&
typescript_1.default.isArrowFunction(node.initializer)) {
extractPropsFromArrowFunction(node.initializer, props);
}
// Handle function declarations
else if (typescript_1.default.isFunctionDeclaration(node) && node.parameters.length > 0) {
const firstParam = node.parameters[0];
if (typescript_1.default.isParameter(firstParam)) {
extractPropsFromParameter(firstParam, props);
}
}
// Check for interface declarations
extractPropsFromInterfaces(sourceFile, props);
return props;
}
/**
* Extracts props from an arrow function
* @param arrowFunc Arrow function node
* @param props Props array to populate
*/
function extractPropsFromArrowFunction(arrowFunc, props) {
// Get the first parameter
const firstParam = arrowFunc.parameters[0];
if (firstParam && typescript_1.default.isParameter(firstParam)) {
extractPropsFromParameter(firstParam, props);
}
}
/**
* Extracts props from a parameter
* @param param Parameter node
* @param props Props array to populate
*/
function extractPropsFromParameter(param, props) {
// Handle destructured parameter
if (typescript_1.default.isObjectBindingPattern(param.name)) {
const bindingPattern = param.name;
// Get the type annotation of the parameter
const typeAnnotation = param.type;
if (typeAnnotation && typescript_1.default.isTypeLiteralNode(typeAnnotation)) {
// Extract props from type literal
typeAnnotation.members.forEach((member) => {
if (typescript_1.default.isPropertySignature(member)) {
const propName = member.name.getText();
const propType = member.type?.getText() ?? "any";
const isOptional = !!member.questionToken;
props.push({
name: propName,
type: propType,
required: !isOptional,
});
}
});
}
// Also check binding elements for default values
bindingPattern.elements.forEach((element) => {
if (typescript_1.default.isBindingElement(element) && typescript_1.default.isIdentifier(element.name)) {
const existingProp = props.find((p) => p.name === element.name.getText());
if (existingProp && element.initializer) {
// If there's a default value, mark as optional
existingProp.required = false;
}
}
});
}
}
/**
* Extracts props from interface declarations in the source file
* @param sourceFile The source file
* @param props Props array to populate
*/
function extractPropsFromInterfaces(sourceFile, props) {
ASTUtils_1.ASTUtils.findNodes(sourceFile, typescript_1.default.isInterfaceDeclaration).forEach((interfaceDecl) => {
if (interfaceDecl.name.text.includes("Props")) {
interfaceDecl.members.forEach((member) => {
if (typescript_1.default.isPropertySignature(member)) {
props.push({
name: member.name.getText(),
type: member.type?.getText() ?? "any",
required: !member.questionToken,
});
}
});
}
});
}
/**
* Finds the default value for a prop
* @param propName The name of the prop
* @param components Components to search for default values
* @returns The default value string if found
*/
function findPropDefaultValue(propName, components) {
for (const comp of components) {
const { componentNode, sourceFile } = comp;
if (!componentNode || !sourceFile)
continue;
if (typescript_1.default.isVariableDeclaration(componentNode) &&
componentNode.initializer &&
typescript_1.default.isArrowFunction(componentNode.initializer)) {
const param = componentNode.initializer.parameters[0];
if (param &&
typescript_1.default.isParameter(param) &&
typescript_1.default.isObjectBindingPattern(param.name)) {
const element = param.name.elements.find((el) => typescript_1.default.isBindingElement(el) &&
typescript_1.default.isIdentifier(el.name) &&
el.name.text === propName &&
el.initializer);
if (element && typescript_1.default.isBindingElement(element) && element.initializer) {
return element.initializer.getText();
}
}
}
}
return undefined;
}
/**
* Calculates the similarity score for props
* @param commonProps Common props between components
* @param components Components being compared
* @returns Similarity score between 0 and 1
*/
function calculatePropsSimilarity(commonProps, components) {
// If all components have no props, they're considered similar
const bothHaveNoProps = components.every((c) => !c.props || c.props.length === 0);
if (bothHaveNoProps)
return 1;
// Find the maximum number of props in any component
const maxPropCount = Math.max(...components.map((c) => c.props?.length || 0), 1 // Avoid division by zero
);
return commonProps.length / maxPropCount;
}