smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
551 lines • 19.6 kB
JavaScript
/**
* Enhanced Scanner with AST Integration
* Phase 1: Advanced AST Parser Infrastructure
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnhancedScanner = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const ASTEnhancementEngine_1 = require("./ASTEnhancementEngine");
const PatternRecognitionEngine_1 = require("./PatternRecognitionEngine");
class EnhancedScanner {
constructor() {
this.supportedExtensions = new Map();
this.astEngine = new ASTEnhancementEngine_1.ASTEnhancementEngine();
this.patternEngine = new PatternRecognitionEngine_1.SmartPatternRecognitionEngine();
this.initializeSupportedExtensions();
}
initializeSupportedExtensions() {
// JavaScript/TypeScript
this.supportedExtensions.set('.js', 'javascript');
this.supportedExtensions.set('.jsx', 'javascript');
this.supportedExtensions.set('.ts', 'typescript');
this.supportedExtensions.set('.tsx', 'typescript');
this.supportedExtensions.set('.mjs', 'javascript');
this.supportedExtensions.set('.cjs', 'javascript');
// Python
this.supportedExtensions.set('.py', 'python');
this.supportedExtensions.set('.pyi', 'python');
// Java
this.supportedExtensions.set('.java', 'java');
// C#
this.supportedExtensions.set('.cs', 'csharp');
}
async scanProject(projectPath) {
const startTime = Date.now();
const startMemory = process.memoryUsage().heapUsed;
try {
// Get all files in the project
const files = await this.getAllFiles(projectPath);
// Categorize files
const categorizedFiles = this.categorizeFiles(files);
// Analyze each file with AST
const astResults = await this.analyzeFiles(categorizedFiles, projectPath);
// Combine results
const combinedResult = this.combineResults(astResults, categorizedFiles);
// Detect language, framework, and platform
const detection = this.detectProjectType(combinedResult);
const endTime = Date.now();
const endMemory = process.memoryUsage().heapUsed;
return {
language: detection.language,
framework: detection.framework,
platform: detection.platform,
confidence: detection.confidence,
astAnalysis: combinedResult.astAnalysis,
patternRecognition: combinedResult.patternRecognition,
files: categorizedFiles,
metadata: {
totalFiles: files.length,
totalLines: combinedResult.totalLines,
complexity: combinedResult.astAnalysis.complexity,
maintainability: combinedResult.astAnalysis.maintainability,
testability: combinedResult.astAnalysis.testability,
processingTime: endTime - startTime,
memoryUsage: endMemory - startMemory
}
};
}
catch (error) {
throw new Error(`Enhanced scanning failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async getAllFiles(projectPath) {
const files = [];
const scanDirectory = async (dir) => {
try {
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = (0, path_1.join)(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules, .git, and other common directories
if (this.shouldSkipDirectory(entry.name)) {
continue;
}
await scanDirectory(fullPath);
}
else if (entry.isFile()) {
const ext = (0, path_1.extname)(entry.name);
if (this.supportedExtensions.has(ext)) {
files.push(fullPath);
}
}
}
}
catch (error) {
// Skip directories that can't be read
console.warn(`Skipping directory ${dir}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
await scanDirectory(projectPath);
return files;
}
shouldSkipDirectory(dirName) {
const skipDirs = [
'node_modules',
'.git',
'.svn',
'.hg',
'.bzr',
'dist',
'build',
'out',
'target',
'bin',
'obj',
'.vscode',
'.idea',
'.vs',
'coverage',
'.nyc_output',
'tmp',
'temp',
'.cache',
'.parcel-cache',
'.next',
'.nuxt',
'.vuepress',
'.docusaurus'
];
return skipDirs.includes(dirName) || dirName.startsWith('.');
}
categorizeFiles(files) {
const categorized = {
source: [],
test: [],
config: [],
other: []
};
files.forEach(file => {
const fileName = file.toLowerCase();
if (this.isTestFile(fileName)) {
categorized.test.push(file);
}
else if (this.isConfigFile(fileName)) {
categorized.config.push(file);
}
else if (this.isSourceFile(fileName)) {
categorized.source.push(file);
}
else {
categorized.other.push(file);
}
});
return categorized;
}
isTestFile(fileName) {
const testPatterns = [
'.test.',
'.spec.',
'test/',
'tests/',
'__tests__/',
'test_',
'spec_',
'.test.js',
'.spec.js',
'.test.ts',
'.spec.ts',
'.test.py',
'.spec.py',
'test.java',
'spec.java'
];
return testPatterns.some(pattern => fileName.includes(pattern));
}
isConfigFile(fileName) {
const configPatterns = [
'package.json',
'tsconfig.json',
'webpack.config',
'rollup.config',
'vite.config',
'next.config',
'nuxt.config',
'vue.config',
'angular.json',
'pom.xml',
'build.gradle',
'requirements.txt',
'setup.py',
'pyproject.toml',
'csproj',
'sln',
'.eslintrc',
'.prettierrc',
'babel.config',
'jest.config',
'cypress.config',
'playwright.config',
'karma.conf',
'protractor.conf'
];
return configPatterns.some(pattern => fileName.includes(pattern));
}
isSourceFile(fileName) {
const sourceExtensions = [
'.js', '.jsx', '.ts', '.tsx',
'.py', '.pyi',
'.java',
'.cs'
];
return sourceExtensions.some(ext => fileName.endsWith(ext));
}
async analyzeFiles(categorizedFiles, projectPath) {
let totalLines = 0;
const allNodes = [];
const allPatterns = [];
// Analyze source files
for (const file of categorizedFiles.source) {
try {
const result = await this.analyzeFile(file, projectPath);
if (result) {
allNodes.push(result.ast);
allPatterns.push(result.patterns);
totalLines += this.countLines(result.ast.raw);
}
}
catch (error) {
console.warn(`Failed to analyze file ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Analyze test files
for (const file of categorizedFiles.test) {
try {
const result = await this.analyzeFile(file, projectPath);
if (result) {
allNodes.push(result.ast);
allPatterns.push(result.patterns);
totalLines += this.countLines(result.ast.raw);
}
}
catch (error) {
console.warn(`Failed to analyze test file ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Combine AST analysis results
const combinedASTAnalysis = this.combineASTAnalysis(allNodes);
// Combine pattern recognition results
const combinedPatternRecognition = this.combinePatternRecognition(allPatterns);
return {
astAnalysis: combinedASTAnalysis,
patternRecognition: combinedPatternRecognition,
totalLines
};
}
async analyzeFile(filePath, projectPath) {
try {
const code = await fs_1.promises.readFile(filePath, 'utf-8');
const ext = (0, path_1.extname)(filePath);
const language = this.supportedExtensions.get(ext);
if (!language) {
return null;
}
// Parse with AST
const astConfig = {
language,
includeComments: true,
includeWhitespace: false,
strictMode: false,
experimentalFeatures: true,
sourceType: 'module'
};
const parseResult = this.astEngine.parse(code, astConfig);
if (!parseResult.success || !parseResult.ast) {
return null;
}
// Analyze with pattern recognition
const patterns = this.patternEngine.recognize(parseResult.ast);
return {
ast: parseResult.ast,
patterns
};
}
catch (error) {
console.warn(`Failed to analyze file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
return null;
}
}
countLines(code) {
return code.split('\n').length;
}
combineASTAnalysis(nodes) {
if (nodes.length === 0) {
return this.getEmptyAnalysis();
}
const allNodes = [];
const imports = [];
const exports = [];
const functions = [];
const classes = [];
const variables = [];
const decorators = [];
const annotations = [];
const visualTests = [];
const testFiles = [];
const dependencies = [];
const frameworks = [];
const platforms = [];
nodes.forEach(node => {
allNodes.push(node);
// Categorize nodes
switch (node.type) {
case 'import':
imports.push(node);
break;
case 'export':
exports.push(node);
break;
case 'function':
case 'arrow-function':
case 'async-function':
case 'generator':
functions.push(node);
break;
case 'class':
classes.push(node);
break;
case 'variable':
variables.push(node);
break;
case 'decorator':
decorators.push(node);
break;
case 'annotation':
annotations.push(node);
break;
}
// Detect visual tests
if (this.isVisualTestNode(node)) {
visualTests.push(node);
}
// Detect test files
if (this.isTestNode(node)) {
testFiles.push(node);
}
// Extract dependencies
if (node.type === 'import' && node.raw) {
const dep = this.extractDependency(node.raw);
if (dep) {
dependencies.push(dep);
}
}
});
return {
nodes: allNodes,
imports,
exports,
functions,
classes,
variables,
decorators,
annotations,
visualTests,
testFiles,
dependencies: [...new Set(dependencies)],
frameworks: [...new Set(frameworks)],
platforms: [...new Set(platforms)],
complexity: this.calculateComplexity(allNodes),
maintainability: this.calculateMaintainability(allNodes),
testability: this.calculateTestability(allNodes)
};
}
combinePatternRecognition(results) {
if (results.length === 0) {
return {
matches: [],
frameworks: [],
platforms: [],
confidence: 0,
suggestions: [],
metadata: {
totalPatterns: 0,
matchedPatterns: 0,
averageConfidence: 0,
processingTime: 0,
memoryUsage: 0
}
};
}
const allMatches = results.flatMap(r => r.matches);
const allFrameworks = [...new Set(results.flatMap(r => r.frameworks))];
const allPlatforms = [...new Set(results.flatMap(r => r.platforms))];
const allSuggestions = [...new Set(results.flatMap(r => r.suggestions))];
const averageConfidence = results.reduce((sum, r) => sum + r.confidence, 0) / results.length;
return {
matches: allMatches,
frameworks: allFrameworks,
platforms: allPlatforms,
confidence: averageConfidence,
suggestions: allSuggestions,
metadata: {
totalPatterns: results.reduce((sum, r) => sum + r.metadata.totalPatterns, 0),
matchedPatterns: allMatches.length,
averageConfidence,
processingTime: results.reduce((sum, r) => sum + r.metadata.processingTime, 0),
memoryUsage: results.reduce((sum, r) => sum + r.metadata.memoryUsage, 0)
}
};
}
combineResults(analysis, files) {
return {
astAnalysis: analysis.astAnalysis,
patternRecognition: analysis.patternRecognition,
totalLines: analysis.totalLines
};
}
detectProjectType(combinedResult) {
// Detect language based on file extensions and AST analysis
const language = this.detectLanguage(combinedResult.astAnalysis);
// Detect framework based on pattern recognition
const framework = this.detectFramework(combinedResult.patternRecognition);
// Detect platform based on pattern recognition
const platform = this.detectPlatform(combinedResult.patternRecognition);
// Calculate overall confidence
const confidence = this.calculateDetectionConfidence(combinedResult);
return {
language,
framework,
platform,
confidence
};
}
detectLanguage(astAnalysis) {
// Simple language detection based on file analysis
// In a real implementation, this would be more sophisticated
if (astAnalysis.nodes.some(node => node.language === 'typescript')) {
return 'typescript';
}
if (astAnalysis.nodes.some(node => node.language === 'python')) {
return 'python';
}
if (astAnalysis.nodes.some(node => node.language === 'java')) {
return 'java';
}
if (astAnalysis.nodes.some(node => node.language === 'csharp')) {
return 'csharp';
}
return 'javascript';
}
detectFramework(patternRecognition) {
if (patternRecognition.frameworks.length > 0) {
return patternRecognition.frameworks[0];
}
return 'cypress'; // Default fallback
}
detectPlatform(patternRecognition) {
if (patternRecognition.platforms.length > 0) {
return patternRecognition.platforms[0];
}
return 'percy'; // Default fallback
}
calculateDetectionConfidence(combinedResult) {
const astConfidence = combinedResult.astAnalysis.nodes.length > 0 ? 0.8 : 0.2;
const patternConfidence = combinedResult.patternRecognition.confidence;
return (astConfidence + patternConfidence) / 2;
}
isVisualTestNode(node) {
const visualTestPatterns = [
/percy/i,
/applitools/i,
/eyes/i,
/sauce/i,
/visual/i,
/screenshot/i,
/snapshot/i
];
return visualTestPatterns.some(pattern => pattern.test(node.raw));
}
isTestNode(node) {
const testPatterns = [
/test/i,
/spec/i,
/describe/i,
/it\(/i,
/expect\(/i
];
return testPatterns.some(pattern => pattern.test(node.raw));
}
extractDependency(importStatement) {
const match = importStatement.match(/from\s+['"]([^'"]+)['"]/);
if (match) {
return match[1] || null;
}
const requireMatch = importStatement.match(/require\(['"]([^'"]+)['"]\)/);
if (requireMatch) {
return requireMatch[1] || null;
}
return null;
}
calculateComplexity(nodes) {
let complexity = 0;
nodes.forEach(node => {
switch (node.type) {
case 'loop':
case 'conditional':
case 'try-catch':
case 'switch':
complexity += 2;
break;
case 'function':
case 'class':
complexity += 1;
break;
}
});
return complexity;
}
calculateMaintainability(nodes) {
const totalNodes = nodes.length;
const complexNodes = nodes.filter(node => ['loop', 'conditional', 'try-catch', 'switch'].includes(node.type)).length;
return Math.max(0, 100 - (complexNodes / totalNodes) * 100);
}
calculateTestability(nodes) {
const functions = nodes.filter(node => ['function', 'arrow-function', 'async-function', 'generator'].includes(node.type)).length;
const testFiles = nodes.filter(node => this.isTestNode(node)).length;
return Math.min(100, (testFiles / Math.max(1, functions)) * 100);
}
getEmptyAnalysis() {
return {
nodes: [],
imports: [],
exports: [],
functions: [],
classes: [],
variables: [],
decorators: [],
annotations: [],
visualTests: [],
testFiles: [],
dependencies: [],
frameworks: [],
platforms: [],
complexity: 0,
maintainability: 0,
testability: 0
};
}
}
exports.EnhancedScanner = EnhancedScanner;
//# sourceMappingURL=EnhancedScanner.js.map
;