sicua
Version:
A tool for analyzing project structure and dependencies
190 lines (189 loc) • 9.42 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeAnalyzer = void 0;
const typescript_1 = __importDefault(require("typescript"));
const typeCollector_1 = require("./analyzers/typeCollector");
const componentTypeAnalyzer_1 = require("./analyzers/componentTypeAnalyzer");
const complexTypeAnalyzer_1 = require("./analyzers/complexTypeAnalyzer");
const typeUsageAnalyzer_1 = require("./analyzers/typeUsageAnalyzer");
const duplicateTypeAnalyzer_1 = require("./analyzers/duplicateTypeAnalyzer");
const typeStatisticsAnalyzer_1 = require("./analyzers/typeStatisticsAnalyzer");
const typeSimilarityUtils_1 = require("./utils/typeSimilarityUtils");
const unifiedTypeGenerator_1 = require("./generators/unifiedTypeGenerator");
/**
* Main analyzer for TypeScript types in a project
*/
class TypeAnalyzer {
constructor(scanResult, typeChecker, options = {}) {
this.scanResult = scanResult;
this.typeChecker = typeChecker;
this.options = {
analyzeDuplicates: true,
analyzeComplexTypes: true,
analyzeComponentProps: true,
analyzeUsage: true,
generateUnifiedTypes: true,
maxFilesToAnalyze: 1000,
maxTypesToAnalyze: 5000,
batchSize: 50,
maxReportedIssues: 100,
maxDuplicateSuggestions: 20,
maxUnifiedTypeSuggestions: 10,
...options,
};
}
/**
* Run the type analysis
*/
async analyze() {
// Initialize the result
const result = {
interfacesCount: 0,
typesCount: 0,
componentsWithPropTypes: [],
componentsWithoutPropTypes: [],
regularFunctionsWithoutReturnType: 0,
suggestedImprovements: [],
anyUsageCount: 0,
broadUnionTypesCount: 0,
complexTypes: [],
typeSimplificationSuggestions: [],
duplicatedTypes: [],
unifiedTypesSuggestions: [],
typesByDirectory: {},
typesWithoutNamespace: [],
};
// Step 1: Collect all types
const typeCollector = new typeCollector_1.TypeCollector(this.scanResult);
const typeData = await typeCollector.collectAllTypes();
const { typeRegistry, signatureToTypes, typesByDirectory } = typeData;
// Step 2: Analyze component types and props
if (this.options.analyzeComponentProps) {
const componentAnalyzer = new componentTypeAnalyzer_1.ComponentTypeAnalyzer(this.scanResult);
const componentAnalysis = await componentAnalyzer.analyzeComponentTypes();
result.componentsWithPropTypes =
componentAnalysis.componentsWithPropTypes || [];
result.componentsWithoutPropTypes =
componentAnalysis.componentsWithoutPropTypes || [];
result.regularFunctionsWithoutReturnType =
componentAnalysis.regularFunctionsWithoutReturnType || 0;
result.suggestedImprovements.push(...(componentAnalysis.suggestedImprovements || []));
}
// Step 3: Analyze type usage
if (this.options.analyzeUsage) {
const typeUsageAnalyzer = new typeUsageAnalyzer_1.TypeUsageAnalyzer(this.scanResult, typeRegistry);
const relevantFiles = Array.from(this.scanResult.sourceFiles.keys()).slice(0, this.options.maxFilesToAnalyze);
const usageResult = await typeUsageAnalyzer.analyzeTypeUsages(relevantFiles);
const typesWithoutNamespace = typeUsageAnalyzer.identifyTypesWithoutNamespace();
result.anyUsageCount = usageResult.anyUsageCount;
result.suggestedImprovements.push(...usageResult.suggestedImprovements);
result.typesWithoutNamespace = typesWithoutNamespace;
}
// Step 4: Analyze duplicate types
if (this.options.analyzeDuplicates) {
const duplicateAnalyzer = new duplicateTypeAnalyzer_1.DuplicateTypeAnalyzer(typeRegistry, signatureToTypes);
result.duplicatedTypes = duplicateAnalyzer.detectTypeDuplications(this.options.maxDuplicateSuggestions);
}
// Step 5: Generate unified type suggestions
if (this.options.generateUnifiedTypes) {
// Get the most frequently used types
const typeCandidates = Array.from(typeRegistry.values())
.filter((t) => t.usages.size > 1 ||
t.name.includes("Props") ||
t.name.includes("State"))
.slice(0, 100);
// Find similar types and generate groups
const similarTypesGroups = typeSimilarityUtils_1.TypeSimilarityUtils.groupSimilarTypes(typeCandidates).map((group) => ({
types: group.types,
similarityScore: group.similarity,
}));
// Generate unified type suggestions
result.unifiedTypesSuggestions =
unifiedTypeGenerator_1.UnifiedTypeGenerator.generateAllUnifiedTypes(similarTypesGroups, this.options.maxUnifiedTypeSuggestions);
}
// Step 6: Analyze complex types
if (this.options.analyzeComplexTypes) {
const complexResults = await this.analyzeComplexTypes();
result.complexTypes = complexResults.complexTypes;
result.typeSimplificationSuggestions =
complexResults.typeSimplificationSuggestions;
result.broadUnionTypesCount = complexResults.broadUnionTypesCount;
}
// Step 7: Calculate type statistics
const typeStatisticsAnalyzer = new typeStatisticsAnalyzer_1.TypeStatisticsAnalyzer(typesByDirectory);
const statistics = typeStatisticsAnalyzer.calculateTypeStatistics();
result.interfacesCount = statistics.interfacesCount;
result.typesCount = statistics.typesCount;
result.typesByDirectory = typesByDirectory;
return result;
}
/**
* Analyze complex types
*/
async analyzeComplexTypes() {
const complexTypeAnalyzer = new complexTypeAnalyzer_1.ComplexTypeAnalyzer(this.options.maxReportedIssues || 100);
const filePaths = Array.from(this.scanResult.sourceFiles.keys());
const batchSize = this.options.batchSize || 50;
// Process files in batches
for (let i = 0; i < filePaths.length; i += batchSize) {
const batch = filePaths.slice(i, i + batchSize);
await Promise.all(batch.map(async (filePath) => {
const sourceFile = this.scanResult.sourceFiles.get(filePath);
if (sourceFile) {
this.visitNodesForComplexTypes(sourceFile, filePath, complexTypeAnalyzer);
}
}));
}
return complexTypeAnalyzer.getResults();
}
/**
* Visit nodes to find and analyze complex types
*/
visitNodesForComplexTypes(sourceFile, filePath, complexTypeAnalyzer, context = "") {
const visit = (node, currentContext = "") => {
// Process potential complex types
if (typescript_1.default.isTypeNode(node)) {
complexTypeAnalyzer.analyzeComplexType(node, filePath, currentContext);
}
// Extract context for better reporting
let newContext = currentContext;
if (typescript_1.default.isInterfaceDeclaration(node) || typescript_1.default.isTypeAliasDeclaration(node)) {
newContext = node.name.text;
}
else if (typescript_1.default.isPropertySignature(node) && typescript_1.default.isIdentifier(node.name)) {
newContext = currentContext
? `${currentContext}.${node.name.text}`
: node.name.text;
}
else if (typescript_1.default.isMethodSignature(node) && typescript_1.default.isIdentifier(node.name)) {
newContext = currentContext
? `${currentContext}.${node.name.text}()`
: `${node.name.text}()`;
}
else if (typescript_1.default.isFunctionDeclaration(node) && node.name) {
newContext = `${node.name.text}()`;
}
else if (typescript_1.default.isParameter(node) && typescript_1.default.isIdentifier(node.name)) {
newContext = currentContext
? `${currentContext}(${node.name.text})`
: `param:${node.name.text}`;
}
// Only visit certain node types to save time
if (typescript_1.default.isTypeNode(node) ||
typescript_1.default.isInterfaceDeclaration(node) ||
typescript_1.default.isTypeAliasDeclaration(node) ||
typescript_1.default.isMethodSignature(node) ||
typescript_1.default.isPropertySignature(node) ||
typescript_1.default.isFunctionDeclaration(node) ||
typescript_1.default.isParameter(node) ||
typescript_1.default.isSourceFile(node)) {
typescript_1.default.forEachChild(node, (child) => visit(child, newContext));
}
};
visit(sourceFile, context);
}
}
exports.TypeAnalyzer = TypeAnalyzer;