mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
Refactored three largest detectors to improve maintainability and reduce complexity: - AggregateBoundaryDetector: 381 → 162 lines (57% reduction) - HardcodeDetector: 459 → 89 lines (81% reduction) - RepositoryPatternDetector: 479 → 106 lines (78% reduction) Added 13 new strategy classes: - FolderRegistry - centralized DDD folder name management - AggregatePathAnalyzer - path parsing and aggregate extraction - ImportValidator - import validation logic - BraceTracker - brace and bracket counting - ConstantsFileChecker - constants file detection - ExportConstantAnalyzer - export const analysis - MagicNumberMatcher - magic number detection - MagicStringMatcher - magic string detection - OrmTypeMatcher - ORM type matching - MethodNameValidator - repository method validation - RepositoryFileAnalyzer - file role detection - RepositoryViolationDetector - violation detection logic All 519 tests passing, zero ESLint errors, no breaking changes.
213 lines
5.8 KiB
TypeScript
213 lines
5.8 KiB
TypeScript
import { HardcodedValue } from "../../domain/value-objects/HardcodedValue"
|
|
import { DETECTION_KEYWORDS } from "../constants/defaults"
|
|
import { HARDCODE_TYPES } from "../../shared/constants"
|
|
import { ExportConstantAnalyzer } from "./ExportConstantAnalyzer"
|
|
|
|
/**
|
|
* Detects magic strings in code
|
|
*
|
|
* Identifies hardcoded string values that should be extracted
|
|
* to constants, excluding test code, console logs, and type contexts.
|
|
*/
|
|
export class MagicStringMatcher {
|
|
private readonly stringRegex = /(['"`])(?:(?!\1).)+\1/g
|
|
|
|
private readonly allowedPatterns = [/^[a-z]$/i, /^\/$/, /^\\$/, /^\s+$/, /^,$/, /^\.$/]
|
|
|
|
private readonly typeContextPatterns = [
|
|
/^\s*type\s+\w+\s*=/i,
|
|
/^\s*interface\s+\w+/i,
|
|
/^\s*\w+\s*:\s*['"`]/,
|
|
/\s+as\s+['"`]/,
|
|
/Record<.*,\s*import\(/,
|
|
/typeof\s+\w+\s*===\s*['"`]/,
|
|
/['"`]\s*===\s*typeof\s+\w+/,
|
|
]
|
|
|
|
constructor(private readonly exportAnalyzer: ExportConstantAnalyzer) {}
|
|
|
|
/**
|
|
* Detects magic strings in code
|
|
*/
|
|
public detect(code: string): HardcodedValue[] {
|
|
const results: HardcodedValue[] = []
|
|
const lines = code.split("\n")
|
|
|
|
lines.forEach((line, lineIndex) => {
|
|
if (this.shouldSkipLine(line, lines, lineIndex)) {
|
|
return
|
|
}
|
|
|
|
this.detectStringsInLine(line, lineIndex, results)
|
|
})
|
|
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* Checks if line should be skipped
|
|
*/
|
|
private shouldSkipLine(line: string, lines: string[], lineIndex: number): boolean {
|
|
if (
|
|
line.trim().startsWith("//") ||
|
|
line.trim().startsWith("*") ||
|
|
line.includes("import ") ||
|
|
line.includes("from ")
|
|
) {
|
|
return true
|
|
}
|
|
|
|
return this.exportAnalyzer.isInExportedConstant(lines, lineIndex)
|
|
}
|
|
|
|
/**
|
|
* Detects strings in a single line
|
|
*/
|
|
private detectStringsInLine(line: string, lineIndex: number, results: HardcodedValue[]): void {
|
|
let match
|
|
const regex = new RegExp(this.stringRegex)
|
|
|
|
while ((match = regex.exec(line)) !== null) {
|
|
const fullMatch = match[0]
|
|
const value = fullMatch.slice(1, -1)
|
|
|
|
if (this.shouldDetectString(fullMatch, value, line)) {
|
|
results.push(
|
|
HardcodedValue.create(
|
|
value,
|
|
HARDCODE_TYPES.MAGIC_STRING,
|
|
lineIndex + 1,
|
|
match.index,
|
|
line.trim(),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if string should be detected
|
|
*/
|
|
private shouldDetectString(fullMatch: string, value: string, line: string): boolean {
|
|
if (fullMatch.startsWith("`") || value.includes("${")) {
|
|
return false
|
|
}
|
|
|
|
if (this.isAllowedString(value)) {
|
|
return false
|
|
}
|
|
|
|
return this.looksLikeMagicString(line, value)
|
|
}
|
|
|
|
/**
|
|
* Checks if string is allowed (short strings, single chars, etc.)
|
|
*/
|
|
private isAllowedString(str: string): boolean {
|
|
if (str.length <= 1) {
|
|
return true
|
|
}
|
|
|
|
return this.allowedPatterns.some((pattern) => pattern.test(str))
|
|
}
|
|
|
|
/**
|
|
* Checks if line context suggests a magic string
|
|
*/
|
|
private looksLikeMagicString(line: string, value: string): boolean {
|
|
const lowerLine = line.toLowerCase()
|
|
|
|
if (this.isTestCode(lowerLine)) {
|
|
return false
|
|
}
|
|
|
|
if (this.isConsoleLog(lowerLine)) {
|
|
return false
|
|
}
|
|
|
|
if (this.isInTypeContext(line)) {
|
|
return false
|
|
}
|
|
|
|
if (this.isInSymbolCall(line, value)) {
|
|
return false
|
|
}
|
|
|
|
if (this.isInImportCall(line, value)) {
|
|
return false
|
|
}
|
|
|
|
if (this.isUrlOrApi(value)) {
|
|
return true
|
|
}
|
|
|
|
if (/^\d{2,}$/.test(value)) {
|
|
return false
|
|
}
|
|
|
|
return value.length > 3
|
|
}
|
|
|
|
/**
|
|
* Checks if line is test code
|
|
*/
|
|
private isTestCode(lowerLine: string): boolean {
|
|
return (
|
|
lowerLine.includes(DETECTION_KEYWORDS.TEST) ||
|
|
lowerLine.includes(DETECTION_KEYWORDS.DESCRIBE)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Checks if line is console log
|
|
*/
|
|
private isConsoleLog(lowerLine: string): boolean {
|
|
return (
|
|
lowerLine.includes(DETECTION_KEYWORDS.CONSOLE_LOG) ||
|
|
lowerLine.includes(DETECTION_KEYWORDS.CONSOLE_ERROR)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Checks if line is in type context
|
|
*/
|
|
private isInTypeContext(line: string): boolean {
|
|
const trimmedLine = line.trim()
|
|
|
|
if (this.typeContextPatterns.some((pattern) => pattern.test(trimmedLine))) {
|
|
return true
|
|
}
|
|
|
|
if (trimmedLine.includes("|") && /['"`][^'"`]+['"`]\s*\|/.test(trimmedLine)) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Checks if string is inside Symbol() call
|
|
*/
|
|
private isInSymbolCall(line: string, stringValue: string): boolean {
|
|
const symbolPattern = new RegExp(
|
|
`Symbol\\s*\\(\\s*['"\`]${stringValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"\`]\\s*\\)`,
|
|
)
|
|
return symbolPattern.test(line)
|
|
}
|
|
|
|
/**
|
|
* Checks if string is inside import() call
|
|
*/
|
|
private isInImportCall(line: string, stringValue: string): boolean {
|
|
const importPattern = /import\s*\(\s*['"`][^'"`]+['"`]\s*\)/
|
|
return importPattern.test(line) && line.includes(stringValue)
|
|
}
|
|
|
|
/**
|
|
* Checks if string contains URL or API reference
|
|
*/
|
|
private isUrlOrApi(value: string): boolean {
|
|
return value.includes(DETECTION_KEYWORDS.HTTP) || value.includes(DETECTION_KEYWORDS.API)
|
|
}
|
|
}
|