UNPKG

@dkoul/auto-testid-core

Version:

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

305 lines 12.5 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 __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; 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.DEFAULT_CONFIG = void 0; exports.createAutoTestID = createAutoTestID; // Export modules __exportStar(require("./parsers"), exports); __exportStar(require("./generators"), exports); __exportStar(require("./transformers"), exports); __exportStar(require("./utils"), exports); // Factory function function createAutoTestID(config) { return new AutoTestIDCoreImpl(config); } // Default configuration exports.DEFAULT_CONFIG = { frameworks: ['react', 'vue', 'angular', 'html'], namingStrategy: { type: 'kebab-case' }, prefix: 'test', attributeName: 'data-testid', exclude: [ '**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.*', '**/*.spec.*' ], includeElementTypes: ['button', 'input', 'select', 'textarea', 'form', 'a', 'div', 'span'], customRules: [], conflictResolution: 'suffix', maxIdLength: 50, preserveExisting: true, }; // Core implementation class AutoTestIDCoreImpl { constructor(config) { this.config = { ...exports.DEFAULT_CONFIG, ...config }; } async processFile(filePath, options) { const startTime = Date.now(); const mergedOptions = { dryRun: false, backup: false, ...options }; const effectiveConfig = { ...this.config, ...mergedOptions.config }; // Import utilities (dynamic imports to avoid circular dependencies) const { fileScanner } = await Promise.resolve().then(() => __importStar(require('./utils/file-scanner'))); const { configLoader } = await Promise.resolve().then(() => __importStar(require('./utils/config-loader'))); const { detectParser } = await Promise.resolve().then(() => __importStar(require('./parsers'))); const { idGenerator } = await Promise.resolve().then(() => __importStar(require('./generators'))); try { // Validate file path if (!fileScanner.isSupported(filePath)) { return { filePath, success: false, transformations: [], errors: [{ message: `Unsupported file type: ${filePath}`, severity: 'error', }], metrics: { elementsFound: 0, elementsTransformed: 0, idGenerated: 0, conflictsResolved: 0, processingTime: Date.now() - startTime, }, }; } // Detect framework and get parser const framework = fileScanner.detectFramework(filePath); const parser = detectParser(filePath); if (!parser) { return { filePath, success: false, transformations: [], errors: [{ message: `No parser available for framework: ${framework}`, severity: 'error', }], metrics: { elementsFound: 0, elementsTransformed: 0, idGenerated: 0, conflictsResolved: 0, processingTime: Date.now() - startTime, }, }; } // Read file content const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const content = await fs.readFile(filePath, 'utf-8'); // Parse file to extract elements const parseResult = parser.parse(content, filePath); if (parseResult.errors.length > 0) { return { filePath, success: false, transformations: [], errors: parseResult.errors.map(e => ({ message: e.message, position: e.position, severity: e.severity, })), metrics: { elementsFound: parseResult.elements.length, elementsTransformed: 0, idGenerated: 0, conflictsResolved: 0, processingTime: Date.now() - startTime, }, }; } // Filter elements that need test IDs const { ValidationUtils } = await Promise.resolve().then(() => __importStar(require('./utils/validation'))); const elementsToProcess = parseResult.elements.filter(element => ValidationUtils.shouldAddTestId(element, effectiveConfig.includeElementTypes)); // Generate transformations const transformations = []; const existingIds = new Set(); let conflictsResolved = 0; // Extract existing test IDs from all elements parseResult.elements.forEach(element => { const testId = element.attributes[effectiveConfig.attributeName]; if (testId) { existingIds.add(testId); } }); // Generate test IDs for elements that need them elementsToProcess.forEach(element => { if (element.position) { const context = { filePath, existingIds, namingStrategy: effectiveConfig.namingStrategy, prefix: effectiveConfig.prefix, framework: framework, }; const generatedId = idGenerator.generate(element, context); // Check if this resolved a conflict const baseId = generatedId.replace(/-\d+$/, ''); // Remove numeric suffix if (generatedId !== baseId) { conflictsResolved++; } existingIds.add(generatedId); transformations.push({ type: 'add-attribute', element, attribute: effectiveConfig.attributeName, value: generatedId, position: element.position, }); } }); // Apply transformations if not dry run let transformResult; if (!mergedOptions.dryRun && transformations.length > 0) { transformResult = parser.transform(content, transformations); if (!transformResult.errors.length) { // Write back to file if (mergedOptions.backup) { await fs.writeFile(`${filePath}.bak`, content, 'utf-8'); } await fs.writeFile(filePath, transformResult.code, 'utf-8'); } } const metrics = { elementsFound: parseResult.elements.length, elementsTransformed: transformations.length, idGenerated: transformations.length, conflictsResolved, processingTime: Date.now() - startTime, }; return { filePath, success: true, transformations, errors: transformResult?.errors || [], metrics, diff: mergedOptions.dryRun ? this.generateDiff(content, transformResult?.code || content) : undefined, }; } catch (error) { return { filePath, success: false, transformations: [], errors: [{ message: `Processing error: ${error}`, severity: 'error', }], metrics: { elementsFound: 0, elementsTransformed: 0, idGenerated: 0, conflictsResolved: 0, processingTime: Date.now() - startTime, }, }; } } async processFiles(filePaths, options) { const results = []; for (let i = 0; i < filePaths.length; i++) { const filePath = filePaths[i]; if (options?.onProgress) { options.onProgress({ total: filePaths.length, completed: i, current: filePath, stage: 'parsing', }); } try { const result = await this.processFile(filePath, options); results.push(result); } catch (error) { results.push({ filePath, success: false, transformations: [], errors: [{ message: `Processing error: ${error}`, severity: 'error', }], metrics: { elementsFound: 0, elementsTransformed: 0, idGenerated: 0, conflictsResolved: 0, processingTime: 0, }, }); } } return results; } async scanDirectory(directory, options) { const { fileScanner } = await Promise.resolve().then(() => __importStar(require('./utils/file-scanner'))); return fileScanner.scan(directory, options); } validateConfiguration(config) { // Import and use the config loader's validation const { configLoader } = require('./utils/config-loader'); return configLoader.validate(config); } generateDiff(original, modified) { // Simple diff implementation for dry-run mode if (original === modified) { return 'No changes'; } const originalLines = original.split('\n'); const modifiedLines = modified.split('\n'); const diff = []; const maxLines = Math.max(originalLines.length, modifiedLines.length); for (let i = 0; i < maxLines; i++) { const origLine = originalLines[i] || ''; const modLine = modifiedLines[i] || ''; if (origLine !== modLine) { if (origLine) { diff.push(`- ${origLine}`); } if (modLine) { diff.push(`+ ${modLine}`); } } } return diff.join('\n'); } } //# sourceMappingURL=index.js.map