sicua
Version:
A tool for analyzing project structure and dependencies
882 lines (881 loc) • 33.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentExtractor = void 0;
const typescript_1 = __importDefault(require("typescript"));
const contextUtils_1 = require("../utils/contextUtils");
const ImportExtractor_1 = require("./ImportExtractor");
const FunctionExtractor_1 = require("./FunctionExtractor");
const TypeExtractor_1 = require("./TypeExtractor");
const reactSpecific_1 = require("../../../utils/ast/reactSpecific");
class ComponentExtractor {
constructor(sourceFile, content, filePath, srcDir, typeChecker) {
this.sourceFile = sourceFile;
this.content = content;
this.filePath = filePath;
this.importExtractor = new ImportExtractor_1.ImportExtractor(srcDir);
this.functionExtractor = new FunctionExtractor_1.FunctionExtractor(sourceFile, content);
this.typeExtractor = new TypeExtractor_1.TypeExtractor(typeChecker);
}
/**
* Extracts all component context from the source file
*/
extractComponentContext() {
const fileType = (0, contextUtils_1.determineFileContextType)(this.filePath, this.content, this.sourceFile);
// Only process React-related files
if (!this.isReactRelatedFile(fileType)) {
return {
components: [],
relationships: [],
patterns: this.createEmptyPatternAnalysis(),
architecture: this.createEmptyArchitecture(),
quality: this.createEmptyQualityMetrics(),
};
}
const components = this.extractComponents();
const relationships = this.extractComponentRelationships(components);
const patterns = this.analyzeComponentPatterns(components);
const architecture = this.analyzeComponentArchitecture(components);
const quality = this.calculateQualityMetrics(components);
return {
components,
relationships,
patterns,
architecture,
quality,
};
}
/**
* Extracts individual components from the file
*/
extractComponents() {
const components = [];
const functionContext = this.functionExtractor.extractFunctionContext();
const typeContext = this.typeExtractor.extractTypeContext(this.sourceFile);
const dependencyContext = this.importExtractor.extractDependencies(this.sourceFile, this.filePath);
// Find React components among functions
functionContext.functions.forEach((func) => {
if ((0, reactSpecific_1.isReactComponentDefinition)(func)) {
const component = this.buildComponentDefinition(func, functionContext, typeContext, dependencyContext);
components.push(component);
}
});
// Find class components
typescript_1.default.forEachChild(this.sourceFile, (node) => {
if (typescript_1.default.isClassDeclaration(node) && this.isReactClassComponent(node)) {
const component = this.buildClassComponentDefinition(node, typeContext, dependencyContext);
components.push(component);
}
});
return components;
}
/**
* Builds a complete component definition
*/
buildComponentDefinition(func, functionContext, typeContext, dependencyContext) {
const name = func.name;
const type = this.determineComponentType(func);
const category = this.determineComponentCategory(func);
const complexity = this.calculateComponentComplexity(func);
const reactInfo = this.extractReactComponentInfo(func);
const structure = this.extractComponentStructure(func, functionContext, typeContext);
const dependencies = this.extractComponentDependencies(dependencyContext);
const lifecycle = this.extractComponentLifecycle(func);
const performance = this.analyzePerformance(func);
const accessibility = this.analyzeAccessibility(func);
const testing = this.analyzeTestability(func);
const patterns = this.extractComponentPatterns(func);
const location = this.getComponentLocation(func);
return {
name,
type,
category,
complexity,
reactInfo,
structure,
dependencies,
lifecycle,
performance,
accessibility,
testing,
patterns,
isExported: func.isExported,
exportType: func.exportType,
description: func.description,
location,
};
}
/**
* Determines if a class is a React component
*/
isReactClassComponent(node) {
if (!node.heritageClauses)
return false;
return node.heritageClauses.some((clause) => clause.types.some((type) => {
const text = type.expression.getText();
return text.includes("Component") || text.includes("PureComponent");
}));
}
/**
* Determines component type
*/
determineComponentType(func) {
const nodeText = this.content;
// Higher-order component
if (this.isHOC(func))
return "higher-order-component";
// Render prop component
if (this.isRenderProp(func))
return "render-prop-component";
// Compound component
if (this.isCompoundComponent(func))
return "compound-component";
// Custom hook
if (func.reactSpecific?.isHook)
return "custom-hook";
// Default to functional component
return "functional-component";
}
/**
* Determines component category
*/
determineComponentCategory(func) {
const name = func.name.toLowerCase();
const nodeText = this.content.toLowerCase();
// Page components
if (name.includes("page") ||
name.includes("route") ||
name.includes("screen")) {
return "page";
}
// Layout components
if (name.includes("layout") ||
name.includes("template") ||
name.includes("wrapper")) {
return "layout";
}
// Container components (state logic)
if (func.reactSpecific?.stateUsage.hasState &&
func.complexity.level !== "low") {
return "container";
}
// Form components
if (name.includes("form") ||
nodeText.includes("onsubmit") ||
nodeText.includes("validation")) {
return "form";
}
// Navigation components
if (name.includes("nav") ||
name.includes("menu") ||
name.includes("tab") ||
name.includes("breadcrumb")) {
return "navigation";
}
// Provider components
if (name.includes("provider") || nodeText.includes("context.provider")) {
return "provider";
}
// Presentation components (no state, simple)
if (!func.reactSpecific?.stateUsage.hasState &&
func.complexity.level === "low") {
return "presentation";
}
return "utility";
}
/**
* Calculates component complexity
*/
calculateComponentComplexity(func) {
let score = 0;
// Base function complexity
switch (func.complexity.level) {
case "very-high":
score += 4;
break;
case "high":
score += 3;
break;
case "medium":
score += 2;
break;
case "low":
score += 1;
break;
}
// JSX complexity
if (func.reactSpecific?.jsxComplexity) {
score += Math.min(func.reactSpecific.jsxComplexity / 10, 3);
}
// Hooks complexity
const hooksCount = func.reactSpecific?.hooksUsed.length || 0;
score += Math.min(hooksCount / 3, 2);
// State complexity
const stateVars = func.reactSpecific?.stateUsage.stateVariables.length || 0;
score += Math.min(stateVars / 2, 2);
// Effects complexity
const effectsCount = func.reactSpecific?.effectUsage.effects.length || 0;
score += Math.min(effectsCount / 2, 2);
if (score >= 10)
return "very-high";
if (score >= 7)
return "high";
if (score >= 4)
return "medium";
if (score >= 2)
return "low";
return "very-low";
}
/**
* Extracts React-specific component information
*/
extractReactComponentInfo(func) {
const reactSpecific = func.reactSpecific;
if (!reactSpecific) {
return this.createEmptyReactComponentInfo();
}
const hooks = this.extractHookUsage(func);
const props = this.extractPropDefinitions(func);
const state = this.extractStateDefinitions(func);
const context = this.extractContextUsage(func);
const refs = this.extractRefUsage(func);
const jsxComplexity = this.calculateJSXComplexity(func);
return {
isComponent: reactSpecific.isComponent,
isFunctional: func.kind === "function-declaration" || func.kind === "arrow-function",
isClassBased: false, // Would be determined differently for class components
isHOC: this.isHOC(func),
isRenderProp: this.isRenderProp(func),
isCompoundComponent: this.isCompoundComponent(func),
hooks,
props,
state,
context,
refs,
jsxComplexity,
};
}
/**
* Extracts hook usage information
*/
extractHookUsage(func) {
const hooks = [];
const hooksUsed = func.reactSpecific?.hooksUsed || [];
hooksUsed.forEach((hookName) => {
const hookType = this.determineHookType(hookName);
const complexity = this.determineHookComplexity(hookName, func);
const dependencies = this.extractHookDependencies(hookName, func);
const purpose = this.determineHookPurpose(hookName);
const customHook = ![
"useState",
"useEffect",
"useContext",
"useReducer",
"useCallback",
"useMemo",
"useRef",
"useLayoutEffect",
].includes(hookName);
hooks.push({
name: hookName,
type: hookType,
complexity,
dependencies,
purpose,
customHook,
});
});
return hooks;
}
/**
* Extracts prop definitions
*/
extractPropDefinitions(func) {
const props = [];
const propTypes = func.reactSpecific?.propTypes;
if (propTypes && propTypes.length > 0) {
propTypes.forEach((propName) => {
// Extract from function parameters
if (func.signature.parameters.length > 0) {
const propsParam = func.signature.parameters[0];
if (propsParam.destructured) {
props.push({
name: propName,
type: "any", // Would need more sophisticated type extraction
required: !propsParam.optional,
description: `Prop extracted from component parameters`,
});
}
}
});
}
return props;
}
/**
* Calculates JSX complexity
*/
calculateJSXComplexity(func) {
const nodeText = this.content;
const elementCount = (nodeText.match(/<[A-Z]/g) || []).length;
const nestingDepth = this.calculateJSXNestingDepth(nodeText);
const conditionalRenders = (nodeText.match(/\{[^}]*\?[^}]*:/g) || []).length +
(nodeText.match(/&&[^}]*</g) || []).length;
const listRenders = (nodeText.match(/\.map\(/g) || []).length;
const dynamicProps = (nodeText.match(/\{[^}]+\}/g) || []).length;
const eventHandlers = (nodeText.match(/on[A-Z]\w*=/g) || []).length;
const complexityScore = elementCount +
nestingDepth * 2 +
conditionalRenders * 3 +
listRenders * 2 +
dynamicProps +
eventHandlers;
return {
elementCount,
nestingDepth,
conditionalRenders,
listRenders,
dynamicProps,
eventHandlers,
complexityScore,
};
}
/**
* Helper methods for component analysis
*/
isHOC(func) {
const nodeText = this.content;
// Look for HOC patterns: function that returns a function/component
return (nodeText.includes("return function") ||
nodeText.includes("return (props") ||
(func.name.startsWith("with") &&
func.name[4]?.toUpperCase() === func.name[4]));
}
isRenderProp(func) {
const nodeText = this.content;
return (nodeText.includes("children(") ||
nodeText.includes("render(") ||
func.signature.parameters.some((p) => p.name === "render" || p.name === "children"));
}
isCompoundComponent(func) {
const nodeText = this.content;
// Look for component.subcomponent patterns
return (nodeText.includes(`${func.name}.`) &&
nodeText.includes("= ") &&
nodeText.includes("function"));
}
isReactRelatedFile(fileType) {
return (["react-component", "react-hook"].includes(fileType) ||
this.content.includes("import React") ||
this.content.includes("from 'react'") ||
this.content.includes("jsx") ||
this.content.includes("tsx"));
}
calculateJSXNestingDepth(nodeText) {
let maxDepth = 0;
let currentDepth = 0;
let inJSX = false;
for (let i = 0; i < nodeText.length; i++) {
const char = nodeText[i];
const nextChar = nodeText[i + 1];
if (char === "<" && nextChar && /[A-Za-z]/.test(nextChar)) {
inJSX = true;
currentDepth++;
maxDepth = Math.max(maxDepth, currentDepth);
}
else if (char === "<" && nextChar === "/") {
currentDepth = Math.max(0, currentDepth - 1);
}
else if (char === "/" && nextChar === ">") {
currentDepth = Math.max(0, currentDepth - 1);
}
}
return maxDepth;
}
determineHookType(hookName) {
if (hookName.includes("State"))
return "state";
if (hookName.includes("Effect"))
return "effect";
if (hookName.includes("Context"))
return "context";
if (hookName.includes("Ref"))
return "ref";
if (hookName.includes("Memo"))
return "memo";
if (hookName.includes("Callback"))
return "callback";
if (hookName.includes("Reducer"))
return "reducer";
return "custom";
}
determineHookComplexity(hookName, func) {
// Simple heuristics based on hook usage
if (hookName === "useState" || hookName === "useRef")
return "simple";
if (hookName === "useEffect" || hookName === "useCallback")
return "moderate";
if (hookName === "useReducer" ||
(hookName.startsWith("use") && !hookName.includes("State")))
return "complex";
return "simple";
}
extractHookDependencies(hookName, func) {
// Extract from effect usage if available
if (func.reactSpecific?.effectUsage) {
const relatedEffect = func.reactSpecific.effectUsage.effects.find((e) => e.type.includes(hookName.toLowerCase()));
return relatedEffect?.dependencies || [];
}
return [];
}
determineHookPurpose(hookName) {
const purposes = {
useState: "Manages local component state",
useEffect: "Handles side effects and lifecycle",
useContext: "Consumes context values",
useReducer: "Manages complex state logic",
useCallback: "Memoizes functions",
useMemo: "Memoizes computed values",
useRef: "References DOM elements or values",
useLayoutEffect: "Synchronous side effects",
};
return purposes[hookName] || "Custom hook functionality";
}
// Placeholder methods for remaining functionality
buildClassComponentDefinition(node, typeContext, dependencyContext) {
// Implementation for class components
return {};
}
extractComponentStructure(func, functionContext, typeContext) {
return {};
}
extractComponentDependencies(dependencyContext) {
return {};
}
extractComponentLifecycle(func) {
return {};
}
analyzePerformance(func) {
return {};
}
analyzeAccessibility(func) {
return {};
}
analyzeTestability(func) {
return {};
}
extractComponentPatterns(func) {
return {};
}
getComponentLocation(func) {
return {};
}
extractStateDefinitions(func) {
const stateDefinitions = [];
const stateUsage = func.reactSpecific?.stateUsage;
if (!stateUsage)
return stateDefinitions;
// Extract useState variables
stateUsage.stateVariables.forEach((stateVar) => {
const updaterName = `set${stateVar.charAt(0).toUpperCase() + stateVar.slice(1)}`;
stateDefinitions.push({
name: stateVar,
type: "unknown", // Would need more sophisticated type extraction
scope: "local",
updaters: [updaterName],
});
});
// Extract reducer states
stateUsage.reducers.forEach((reducer) => {
stateDefinitions.push({
name: reducer,
type: "reducer",
scope: "local",
updaters: ["dispatch"],
});
});
// Extract context states
stateUsage.contextUsage.forEach((context) => {
stateDefinitions.push({
name: context,
type: "context",
scope: "global",
updaters: [],
});
});
return stateDefinitions;
}
extractContextUsage(func) {
const contextUsages = [];
const nodeText = this.content;
// Find context providers
const providerPattern = /(\w+)\.Provider/g;
let match;
while ((match = providerPattern.exec(nodeText)) !== null) {
contextUsages.push({
contextName: match[1],
isProvider: true,
isConsumer: false,
valuesProvided: this.extractProvidedValues(match[1]),
valuesConsumed: [],
});
}
// Find context consumers
const consumerPattern = /useContext\((\w+)\)/g;
while ((match = consumerPattern.exec(nodeText)) !== null) {
const existing = contextUsages.find((c) => c.contextName === match[1]);
if (existing) {
existing.isConsumer = true;
existing.valuesConsumed = this.extractConsumedValues(match[1]);
}
else {
contextUsages.push({
contextName: match[1],
isProvider: false,
isConsumer: true,
valuesProvided: [],
valuesConsumed: this.extractConsumedValues(match[1]),
});
}
}
return contextUsages;
}
extractRefUsage(func) {
const refUsages = [];
const nodeText = this.content;
// Find useRef calls
const refPattern = /const\s+(\w+)\s*=\s*useRef\s*\(\s*([^)]*)\s*\)/g;
let match;
while ((match = refPattern.exec(nodeText)) !== null) {
const refName = match[1];
const initialValue = match[2];
refUsages.push({
name: refName,
type: this.determineRefType(refName, initialValue),
purpose: this.determineRefPurpose(refName, nodeText),
forwardedRef: false,
});
}
// Find forwardRef usage
if (nodeText.includes("forwardRef")) {
refUsages.push({
name: "forwardedRef",
type: "element",
purpose: "Forwarded reference to child component",
forwardedRef: true,
});
}
return refUsages;
}
extractComponentRelationships(components) {
const relationships = [];
components.forEach((component) => {
// Extract render relationships from JSX
const renderedComponents = this.extractRenderedComponents(component);
renderedComponents.forEach((rendered) => {
relationships.push({
parent: component.name,
child: rendered,
relationship: "renders",
strength: "medium",
});
});
// Extract import relationships
component.dependencies.internalComponents.forEach((internal) => {
relationships.push({
parent: component.name,
child: internal.name,
relationship: "imports",
strength: internal.relationship === "child" ? "strong" : "medium",
});
});
// Extract prop passing relationships
const propRelationships = this.extractPropRelationships(component);
relationships.push(...propRelationships);
});
return relationships;
}
analyzeComponentPatterns(components) {
const patternCounts = {};
const antiPatterns = [];
components.forEach((component) => {
// Count design patterns
component.patterns.designPatterns.forEach((pattern) => {
patternCounts[pattern.name] = (patternCounts[pattern.name] || 0) + 1;
});
// Count React patterns
component.patterns.reactPatterns.forEach((pattern) => {
patternCounts[pattern] = (patternCounts[pattern] || 0) + 1;
});
// Collect anti-patterns
antiPatterns.push(...component.patterns.antiPatterns);
});
const commonPatterns = Object.entries(patternCounts)
.filter(([_, count]) => count >= 2)
.map(([pattern, _]) => pattern);
const recommendations = this.generatePatternRecommendations(patternCounts, antiPatterns);
return {
commonPatterns,
patternUsage: patternCounts,
antiPatterns,
recommendations,
};
}
analyzeComponentArchitecture(components) {
const hierarchy = this.buildComponentHierarchy(components);
const structure = this.determineArchitectureStructure(components);
const depth = this.calculateArchitectureDepth(hierarchy);
const modularity = this.analyzeModularity(components);
return {
structure,
depth,
componentHierarchy: hierarchy,
modularity,
};
}
calculateQualityMetrics(components) {
if (components.length === 0) {
return this.createEmptyQualityMetrics();
}
const complexityScores = components.map((c) => this.complexityToNumber(c.complexity));
const averageComplexity = complexityScores.reduce((a, b) => a + b, 0) / complexityScores.length;
const testabilityCoverage = components.reduce((total, c) => total + c.testing.testability.score, 0) /
components.length;
const accessibilityScore = components.reduce((total, c) => total + c.accessibility.accessibilityScore, 0) / components.length;
const performanceScore = components.reduce((total, c) => total + this.calculatePerformanceScore(c.performance), 0) / components.length;
const maintainabilityScore = this.calculateMaintainabilityScore(components);
const reusabilityScore = this.calculateReusabilityScore(components);
return {
averageComplexity,
testabilityCoverage,
accessibilityScore,
performanceScore,
maintainabilityScore,
reusabilityScore,
};
}
// Helper methods
extractProvidedValues(contextName) {
const nodeText = this.content;
const pattern = new RegExp(`${contextName}\\.Provider[^>]*value\\s*=\\s*\\{\\s*([^}]+)\\s*\\}`, "g");
const match = pattern.exec(nodeText);
if (match) {
return match[1]
.split(",")
.map((v) => v.trim())
.filter((v) => v);
}
return [];
}
extractConsumedValues(contextName) {
const nodeText = this.content;
const pattern = new RegExp(`const\\s+\\{([^}]+)\\}\\s*=\\s*useContext\\(${contextName}\\)`, "g");
const match = pattern.exec(nodeText);
if (match) {
return match[1]
.split(",")
.map((v) => v.trim())
.filter((v) => v);
}
return [];
}
determineRefType(refName, initialValue) {
if (initialValue === "null" || initialValue === "")
return "element";
if (initialValue && !initialValue.includes("null"))
return "value";
if (refName.toLowerCase().includes("ref"))
return "element";
return "value";
}
determineRefPurpose(refName, nodeText) {
if (nodeText.includes(`${refName}.current.focus`))
return "Focus management";
if (nodeText.includes(`${refName}.current.scroll`))
return "Scroll control";
if (nodeText.includes(`${refName}.current.measure`))
return "DOM measurements";
if (nodeText.includes(`${refName}.current`))
return "DOM manipulation";
return "Value persistence";
}
extractRenderedComponents(component) {
const nodeText = this.content;
const componentPattern = /<([A-Z][A-Za-z0-9]*)/g;
const rendered = [];
let match;
while ((match = componentPattern.exec(nodeText)) !== null) {
const componentName = match[1];
if (componentName !== component.name &&
!rendered.includes(componentName)) {
rendered.push(componentName);
}
}
return rendered;
}
extractPropRelationships(component) {
const relationships = [];
const nodeText = this.content;
// Look for prop passing patterns
const propPattern = /<([A-Z][A-Za-z0-9]*)[^>]*\s+(\w+)=/g;
let match;
while ((match = propPattern.exec(nodeText)) !== null) {
const childComponent = match[1];
const propName = match[2];
relationships.push({
parent: component.name,
child: childComponent,
relationship: "provides-data",
strength: "medium",
});
}
return relationships;
}
generatePatternRecommendations(patternCounts, antiPatterns) {
const recommendations = [];
// Recommend patterns that are underused
if (patternCounts["custom-hook"] === 0) {
recommendations.push("Consider extracting reusable logic into custom hooks");
}
if (patternCounts["react-memo"] === 0) {
recommendations.push("Consider using React.memo for performance optimization");
}
// Address anti-patterns
antiPatterns.forEach((antiPattern) => {
if (antiPattern.severity === "high") {
recommendations.push(`Address ${antiPattern.name}: ${antiPattern.suggestion}`);
}
});
return recommendations;
}
buildComponentHierarchy(components) {
const hierarchy = [];
components.forEach((component) => {
const children = this.extractRenderedComponents(component);
hierarchy.push({
component: component.name,
level: 0, // Would need more sophisticated calculation
children,
parent: undefined, // Would need to determine parent relationships
});
});
return hierarchy;
}
determineArchitectureStructure(components) {
const avgNesting = components.reduce((total, c) => total + c.reactInfo.jsxComplexity.nestingDepth, 0) / components.length;
if (avgNesting > 5)
return "nested";
if (components.some((c) => c.category === "page"))
return "feature-based";
if (components.every((c) => c.category === "presentation"))
return "atomic";
return "flat";
}
calculateArchitectureDepth(hierarchy) {
return Math.max(...hierarchy.map((h) => h.level), 0);
}
analyzeModularity(components) {
// Simple heuristics for modularity analysis
const avgComplexity = components.reduce((total, c) => total + this.complexityToNumber(c.complexity), 0) / components.length;
const cohesion = avgComplexity < 3 ? "high" : avgComplexity < 5 ? "medium" : "low";
const coupling = components.some((c) => c.dependencies.internalComponents.length > 5)
? "high"
: "medium";
const reusability = components.filter((c) => c.category === "presentation").length /
components.length >
0.5
? "high"
: "medium";
const maintainability = cohesion === "high" && coupling !== "high" ? "high" : "medium"; // Fixed comparison
return { cohesion, coupling, reusability, maintainability };
}
complexityToNumber(complexity) {
const map = { "very-low": 1, low: 2, medium: 3, high: 4, "very-high": 5 };
return map[complexity];
}
calculatePerformanceScore(performance) {
let score = 100;
// Deduct points for performance issues
score -= performance.expensiveOperations.length * 10;
// Add points for optimizations
if (performance.memoization.reactMemo)
score += 10;
if (performance.lazyLoading.isLazy)
score += 10;
return Math.max(0, Math.min(100, score));
}
calculateMaintainabilityScore(components) {
let score = 100;
// Deduct for high complexity
const highComplexityCount = components.filter((c) => c.complexity === "high" || c.complexity === "very-high").length;
score -= (highComplexityCount / components.length) * 50;
// Deduct for anti-patterns
const antiPatternCount = components.reduce((total, c) => total + c.patterns.antiPatterns.length, 0);
score -= antiPatternCount * 5;
return Math.max(0, Math.min(100, score));
}
calculateReusabilityScore(components) {
const presentationComponents = components.filter((c) => c.category === "presentation").length;
const utilityComponents = components.filter((c) => c.category === "utility").length;
const reusableCount = presentationComponents + utilityComponents;
return (reusableCount / components.length) * 100;
}
// Empty object creators
createEmptyReactComponentInfo() {
return {
isComponent: false,
isFunctional: false,
isClassBased: false,
isHOC: false,
isRenderProp: false,
isCompoundComponent: false,
hooks: [],
props: [],
state: [],
context: [],
refs: [],
jsxComplexity: {
elementCount: 0,
nestingDepth: 0,
conditionalRenders: 0,
listRenders: 0,
dynamicProps: 0,
eventHandlers: 0,
complexityScore: 0,
},
};
}
createEmptyPatternAnalysis() {
return {
commonPatterns: [],
patternUsage: {},
antiPatterns: [],
recommendations: [],
};
}
createEmptyArchitecture() {
return {
structure: "flat",
depth: 0,
componentHierarchy: [],
modularity: {
cohesion: "medium",
coupling: "medium",
reusability: "medium",
maintainability: "medium",
},
};
}
createEmptyQualityMetrics() {
return {
averageComplexity: 0,
testabilityCoverage: 0,
accessibilityScore: 0,
performanceScore: 0,
maintainabilityScore: 0,
reusabilityScore: 0,
};
}
}
exports.ComponentExtractor = ComponentExtractor;