frontend-standards-checker
Version:
A comprehensive frontend standards validation tool with TypeScript support
165 lines ⢠6.99 kB
JavaScript
import path from 'path';
// Detect if a file is a Jest (test/spec)
function isJestFile(filePath) {
if (!filePath || typeof filePath !== 'string')
return false;
const lowerPath = filePath.toLowerCase();
return (/\.(test|spec)\.[jt]sx?$/.test(lowerPath) ||
/__tests__/.test(lowerPath) ||
lowerPath.includes('jest'));
}
export async function loadAndLogConfig(configLoader, options, logger) {
const config = await configLoader.load(options.config);
if (options.debug) {
logger.debug('Configuration loaded:', JSON.stringify(config, null, 2));
}
return config;
}
export async function analyzeProject(projectAnalyzer, config, logger, options) {
const projectInfo = await projectAnalyzer.analyze(config.zones);
logger.info(`š Project type: ${projectInfo.projectType}`);
logger.info(`šļø Monorepo: ${projectInfo.isMonorepo ? 'Yes' : 'No'}`);
if (options.debug) {
logger.debug('Project analysis result:', projectInfo);
}
return projectInfo;
}
export async function getChangedFiles(fileScanner, logger, config) {
const pipelineMode = config?.pipelineMode || false;
if (pipelineMode) {
logger.info('š Using pipeline mode to detect changed files');
}
else {
logger.info('š Only checking files staged for commit or recently changed');
}
const changedFiles = await fileScanner.getFilesInCommitOrPipeline(pipelineMode);
logger.info(`Found ${changedFiles.length} files to check`);
return changedFiles;
}
export function returnEarly(startTime) {
return {
success: true,
totalFiles: 0,
totalErrors: 0,
totalWarnings: 0,
zones: [],
summary: {
errorsByCategory: {},
errorsByRule: {},
processingTime: Date.now() - startTime,
},
};
}
export function createSummary(zoneResults, totalFiles, totalErrors, totalWarnings, startTime) {
const errorsByCategory = {};
const errorsByRule = {};
zoneResults.forEach((zone) => {
zone.errors.forEach((error) => {
const categoryKey = error.category ?? 'uncategorized';
errorsByCategory[categoryKey] = (errorsByCategory[categoryKey] ?? 0) + 1;
errorsByRule[error.rule] = (errorsByRule[error.rule] ?? 0) + 1;
});
});
return {
success: totalErrors === 0,
totalFiles,
totalErrors,
totalWarnings,
zones: zoneResults,
summary: {
errorsByCategory,
errorsByRule,
processingTime: Date.now() - startTime,
},
};
}
export async function generateReport(reporter, logger, zoneResults, projectInfo, config) {
const zoneErrors = {};
zoneResults.forEach((zone) => {
zoneErrors[zone.zone] = zone.errors;
logger.debug(`š Zone ${zone.zone}: ${zone.errors.length} errors before reporter`);
});
const totalErrorsToReporter = Object.values(zoneErrors).reduce((sum, errors) => sum + errors.length, 0);
logger.debug(`š Total errors being passed to reporter: ${totalErrorsToReporter}`);
return await reporter.generate(zoneErrors, projectInfo, config);
}
export function logSummary(logger, summary, totalFiles, totalErrors, totalWarnings, zoneSummary) {
logger.info(`\nš Validation completed in ${summary.processingTime}ms`);
logger.info(`š Total files: ${totalFiles}`);
logger.info(`ā Total errors: ${totalErrors}`);
logger.info(`ā ļø Total warnings: ${totalWarnings}`);
if (zoneSummary) {
logger.info(`\n----------------\nRESULTS BY ZONE:\n----------------`);
Object.keys(zoneSummary.errorsByZone).forEach((zone) => {
logger.info(`\nš Zone: ${zone}`);
logger.info(` Errors: ${zoneSummary.errorsByZone[zone]}`);
logger.info(` Warnings: ${zoneSummary.warningsByZone[zone]}`);
if (zoneSummary.infosByZone?.[zone] !== undefined) {
logger.info(` Info suggestions: ${zoneSummary.infosByZone[zone]}`);
}
logger.info(` Status: ${(zoneSummary.errorsByZone[zone] ?? 0) > 0 ? 'ā FAILED' : 'ā
PASSED'}`);
logger.info('----------------------------------------');
});
}
}
export function filterChangedFiles(files, changedFiles, rootDir) {
return files.filter((file) => {
const fileFullPath = file.fullPath ?? path.join(rootDir, file.path);
return changedFiles.some((cf) => fileFullPath === cf ||
(fileFullPath.endsWith(cf) ?? cf.endsWith(file.path)));
});
}
export async function processZone({ zone, config, changedFiles, hasOnlyZone, options, rootDir, logger, fileScanner, ruleEngine, projectInfo, }) {
logger.info(`\nš Processing zone: ${zone}`);
let files = await fileScanner.scanZone(zone, {
extensions: config.extensions || ['.js', '.ts', '.jsx', '.tsx'],
ignorePatterns: config.ignorePatterns || [],
zones: [zone],
includePackages: config.zones?.includePackages || false,
customZones: config.zones?.customZones || [],
});
if ((options.onlyChangedFiles || config.onlyChangedFiles) &&
!hasOnlyZone &&
changedFiles.length > 0) {
const originalCount = files.length;
files = filterChangedFiles(files, changedFiles, rootDir);
logger.debug(`Filtered ${originalCount} files to ${files.length} changed files in zone ${zone}`);
}
if (options.debug) {
logger.debug(`š Debug: Files found in zone "${zone}":`, files.map((f) => f.path));
}
const zoneErrors = [];
const validFiles = files.filter((file) => {
const isConfigFile = ruleEngine.isConfigurationFile(file.path);
if (isConfigFile && options.verbose) {
logger.debug(`Skipping configuration file: ${file.path}`);
}
return !isConfigFile;
});
for (const file of validFiles) {
if (options.verbose) {
logger.info(` š Validating: ${file.path}`);
}
const fileErrors = await ruleEngine.validate(file.content, file.path, {
filePath: file.path,
content: file.content,
projectInfo,
config,
});
zoneErrors.push(...fileErrors);
}
// Exclude Jest files from error counts
const zoneErrorsCount = zoneErrors.filter((e) => e.severity === 'error' && !isJestFile(e.filePath)).length;
const zoneWarningsCount = zoneErrors.filter((e) => e.severity === 'warning' && !isJestFile(e.filePath)).length;
logger.info(` ā
Files processed: ${files.length}`);
logger.info(` ā Errors found: ${zoneErrorsCount}`);
logger.info(` ā ļø Warnings found: ${zoneWarningsCount}`);
return {
zone,
filesProcessed: validFiles.length,
errors: zoneErrors,
errorsCount: zoneErrorsCount,
warningsCount: zoneWarningsCount,
};
}
//# sourceMappingURL=general.helper.js.map