@dkoul/auto-testid-core
Version:
Core AST parsing and transformation logic for React and Vue.js attribute generation
305 lines • 12.5 kB
JavaScript
;
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