mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
Major internal refactoring to eliminate hardcoded values and improve maintainability. Guardian now fully passes its own quality checks! Changes: - Extract all RepositoryViolation messages to domain constants - Extract all framework leak template strings to centralized constants - Extract all layer paths to infrastructure constants - Extract all regex patterns to IMPORT_PATTERNS constant - Add 30+ new constants for better maintainability New files: - src/infrastructure/constants/paths.ts (layer paths, patterns) - src/domain/constants/Messages.ts (25+ repository messages) - src/domain/constants/FrameworkCategories.ts (framework categories) - src/shared/constants/layers.ts (layer names) Impact: - Reduced hardcoded values from 37 to 1 (97% improvement) - Guardian passes its own src/ directory checks with 0 violations - All 292 tests still passing (100% pass rate) - No breaking changes - fully backwards compatible Test results: - 292 tests passing (100% pass rate) - 96.77% statement coverage - 83.82% branch coverage
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import * as fs from "fs/promises"
|
|
import * as path from "path"
|
|
import { FileScanOptions, IFileScanner } from "../../domain/services/IFileScanner"
|
|
import { DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, FILE_ENCODING } from "../constants/defaults"
|
|
import { ERROR_MESSAGES } from "../../shared/constants"
|
|
import { TEST_FILE_EXTENSIONS, TEST_FILE_SUFFIXES } from "../constants/type-patterns"
|
|
|
|
/**
|
|
* Scans project directory for source files
|
|
*/
|
|
export class FileScanner implements IFileScanner {
|
|
private readonly defaultExcludes = [...DEFAULT_EXCLUDES]
|
|
private readonly defaultExtensions = [...DEFAULT_EXTENSIONS]
|
|
|
|
public async scan(options: FileScanOptions): Promise<string[]> {
|
|
const {
|
|
rootDir,
|
|
exclude = this.defaultExcludes,
|
|
extensions = this.defaultExtensions,
|
|
} = options
|
|
|
|
return this.scanDirectory(rootDir, exclude, extensions)
|
|
}
|
|
|
|
private async scanDirectory(
|
|
dir: string,
|
|
exclude: string[],
|
|
extensions: string[],
|
|
): Promise<string[]> {
|
|
const files: string[] = []
|
|
|
|
try {
|
|
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name)
|
|
|
|
if (this.shouldExclude(entry.name, exclude)) {
|
|
continue
|
|
}
|
|
|
|
if (entry.isDirectory()) {
|
|
const subFiles = await this.scanDirectory(fullPath, exclude, extensions)
|
|
files.push(...subFiles)
|
|
} else if (entry.isFile()) {
|
|
const ext = path.extname(entry.name)
|
|
if (extensions.includes(ext)) {
|
|
files.push(fullPath)
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
throw new Error(`${ERROR_MESSAGES.FAILED_TO_SCAN_DIR} ${dir}: ${String(error)}`)
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
private shouldExclude(name: string, excludePatterns: string[]): boolean {
|
|
const isExcludedDirectory = excludePatterns.some((pattern) => name.includes(pattern))
|
|
const isTestFile =
|
|
(TEST_FILE_EXTENSIONS as readonly string[]).some((ext) => name.includes(ext)) ||
|
|
(TEST_FILE_SUFFIXES as readonly string[]).some((suffix) => name.endsWith(suffix))
|
|
|
|
return isExcludedDirectory || isTestFile
|
|
}
|
|
|
|
public async readFile(filePath: string): Promise<string> {
|
|
try {
|
|
return await fs.readFile(filePath, FILE_ENCODING)
|
|
} catch (error) {
|
|
throw new Error(`${ERROR_MESSAGES.FAILED_TO_READ_FILE} ${filePath}: ${String(error)}`)
|
|
}
|
|
}
|
|
}
|