UNPKG

@dkoul/auto-testid-core

Version:

Core AST parsing and transformation logic for React and Vue.js attribute generation

228 lines 8.82 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.fileScanner = exports.FileScanner = void 0; const path = __importStar(require("path")); const glob_1 = require("glob"); const fs = __importStar(require("fs/promises")); const logger_1 = require("./logger"); class FileScanner { constructor() { this.logger = new logger_1.Logger('FileScanner'); this.FRAMEWORK_EXTENSIONS = { '.jsx': ['react'], '.tsx': ['react'], '.vue': ['vue'], '.html': ['angular', 'html'], '.ts': ['angular', 'react'], '.js': ['react', 'html'], }; this.DEFAULT_SCAN_OPTIONS = { include: ['**/*.{js,jsx,ts,tsx,vue,html}'], exclude: [ '**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**', '**/*.test.*', '**/*.spec.*', ], frameworks: ['react', 'vue', 'angular', 'html'], recursive: true, maxFiles: 10000, }; } async scan(directory, options = {}) { const scanOptions = { ...this.DEFAULT_SCAN_OPTIONS, ...options }; this.logger.info(`Scanning directory: ${directory}`); this.logger.debug(`Scan options:`, scanOptions); try { const patterns = this.buildGlobPatterns(directory, scanOptions); const allFiles = []; for (const pattern of patterns) { const files = await (0, glob_1.glob)(pattern, { ignore: scanOptions.exclude, absolute: true, nodir: true, }); allFiles.push(...files); } // Remove duplicates and apply framework filtering const uniqueFiles = Array.from(new Set(allFiles)); const filteredFiles = await this.filterByFramework(uniqueFiles, scanOptions.frameworks); // Apply max files limit const limitedFiles = scanOptions.maxFiles ? filteredFiles.slice(0, scanOptions.maxFiles) : filteredFiles; this.logger.info(`Found ${limitedFiles.length} files to process`); return limitedFiles.sort(); } catch (error) { this.logger.error(`Error scanning directory: ${error}`); throw new Error(`Failed to scan directory ${directory}: ${error}`); } } isSupported(filePath) { const ext = path.extname(filePath).toLowerCase(); return Object.keys(this.FRAMEWORK_EXTENSIONS).includes(ext); } detectFramework(filePath, packageJson) { const ext = path.extname(filePath).toLowerCase(); const filename = path.basename(filePath).toLowerCase(); // Check file extension first const possibleFrameworks = this.FRAMEWORK_EXTENSIONS[ext] || []; // Vue files are always Vue if (ext === '.vue') { return 'vue'; } // JSX/TSX files are always React if (ext === '.jsx' || ext === '.tsx') { return 'react'; } // For ambiguous extensions, check package.json dependencies if (packageJson) { const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies, }; // Check for framework-specific dependencies if (allDeps.react || allDeps['@types/react']) { return 'react'; } if (allDeps.vue || allDeps['@vue/core']) { return 'vue'; } if (allDeps['@angular/core'] || allDeps.angular) { return 'angular'; } } // Check filename patterns for Angular if (filename.includes('.component.') || filename.includes('.template.')) { return 'angular'; } // Default fallbacks based on extension if (ext === '.html') { return filename.includes('component') ? 'angular' : 'html'; } if (ext === '.ts' || ext === '.js') { // Check for JSX-like content (basic heuristic) return 'react'; // Default assumption for JS/TS files } return 'html'; // Final fallback } buildGlobPatterns(directory, options) { const basePatterns = options.include.map(pattern => { if (path.isAbsolute(pattern)) { return pattern; } return path.join(directory, pattern); }); return basePatterns; } async filterByFramework(files, frameworks) { const filteredFiles = []; for (const filePath of files) { if (!this.isSupported(filePath)) { continue; } try { // Try to read package.json from the file's directory or parent directories const packageJson = await this.findPackageJson(path.dirname(filePath)); const framework = this.detectFramework(filePath, packageJson); if (frameworks.includes(framework)) { filteredFiles.push(filePath); } } catch (error) { // If we can't determine the framework, include the file this.logger.warn(`Could not determine framework for ${filePath}: ${error}`); filteredFiles.push(filePath); } } return filteredFiles; } async findPackageJson(directory) { let currentDir = directory; while (currentDir !== path.dirname(currentDir)) { try { const packageJsonPath = path.join(currentDir, 'package.json'); const content = await fs.readFile(packageJsonPath, 'utf-8'); return JSON.parse(content); } catch { // Continue to parent directory currentDir = path.dirname(currentDir); } } return null; } async getProjectInfo(directory) { const packageJson = await this.findPackageJson(directory); const tsConfig = await this.findTsConfig(directory); let framework = 'html'; if (packageJson) { // Detect primary framework based on dependencies const deps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; if (deps.react) framework = 'react'; else if (deps.vue) framework = 'vue'; else if (deps['@angular/core']) framework = 'angular'; } return { framework, packageJson, tsConfig }; } async findTsConfig(directory) { let currentDir = directory; while (currentDir !== path.dirname(currentDir)) { try { const tsConfigPath = path.join(currentDir, 'tsconfig.json'); const content = await fs.readFile(tsConfigPath, 'utf-8'); return JSON.parse(content); } catch { currentDir = path.dirname(currentDir); } } return null; } } exports.FileScanner = FileScanner; exports.fileScanner = new FileScanner(); //# sourceMappingURL=file-scanner.js.map