Files
puaros/packages/guardian/src/infrastructure/scanners/FileScanner.ts
imfozilbek a34ca85241 chore: refactor hardcoded values to constants (v0.5.1)
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
2025-11-24 20:12:08 +05:00

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)}`)
}
}
}