Files
puaros/packages/guardian/src/infrastructure/analyzers/DuplicateValueTracker.ts
imfozilbek af094eb54a refactor: migrate hardcode detector from regex to AST-based analysis
- Replace regex-based matchers with tree-sitter AST traversal
- Add duplicate value tracking across files
- Implement boolean literal detection
- Add value type classification (email, url, ip, api_key, etc.)
- Improve context awareness with AST node analysis
- Reduce false positives with better constant detection

Breaking changes removed:
- BraceTracker.ts
- ExportConstantAnalyzer.ts
- MagicNumberMatcher.ts
- MagicStringMatcher.ts

New components added:
- AstTreeTraverser for AST walking
- DuplicateValueTracker for cross-file tracking
- AstContextChecker for node context analysis
- AstNumberAnalyzer, AstStringAnalyzer, AstBooleanAnalyzer
- ValuePatternMatcher for type detection

Test coverage: 87.97% statements, 96.75% functions
2025-11-26 17:38:30 +05:00

123 lines
3.3 KiB
TypeScript

import { HardcodedValue } from "../../domain/value-objects/HardcodedValue"
import type {
DuplicateInfo,
IDuplicateValueTracker,
ValueLocation,
} from "../../domain/services/IDuplicateValueTracker"
/**
* Tracks duplicate hardcoded values across files
*
* Helps identify values that are used in multiple places
* and should be extracted to a shared constant.
*/
export class DuplicateValueTracker implements IDuplicateValueTracker {
private readonly valueMap = new Map<string, ValueLocation[]>()
/**
* Adds a hardcoded value to tracking
*/
public track(violation: HardcodedValue, filePath: string): void {
const key = this.createKey(violation.value, violation.type)
const location: ValueLocation = {
file: filePath,
line: violation.line,
context: violation.context,
}
const locations = this.valueMap.get(key)
if (!locations) {
this.valueMap.set(key, [location])
} else {
locations.push(location)
}
}
/**
* Gets all duplicate values (values used in 2+ places)
*/
public getDuplicates(): DuplicateInfo[] {
const duplicates: DuplicateInfo[] = []
for (const [key, locations] of this.valueMap.entries()) {
if (locations.length >= 2) {
const { value } = this.parseKey(key)
duplicates.push({
value,
locations,
count: locations.length,
})
}
}
return duplicates.sort((a, b) => b.count - a.count)
}
/**
* Gets duplicate locations for a specific value
*/
public getDuplicateLocations(
value: string | number | boolean,
type: string,
): ValueLocation[] | null {
const key = this.createKey(value, type)
const locations = this.valueMap.get(key)
if (!locations || locations.length < 2) {
return null
}
return locations
}
/**
* Checks if a value is duplicated
*/
public isDuplicate(value: string | number | boolean, type: string): boolean {
const key = this.createKey(value, type)
const locations = this.valueMap.get(key)
return locations ? locations.length >= 2 : false
}
/**
* Creates a unique key for a value
*/
private createKey(value: string | number | boolean, type: string): string {
return `${type}:${String(value)}`
}
/**
* Parses a key back to value and type
*/
private parseKey(key: string): { value: string; type: string } {
const [type, ...valueParts] = key.split(":")
return { value: valueParts.join(":"), type }
}
/**
* Gets statistics about duplicates
*/
public getStats(): {
totalValues: number
duplicateValues: number
duplicatePercentage: number
} {
const totalValues = this.valueMap.size
const duplicateValues = this.getDuplicates().length
const duplicatePercentage = totalValues > 0 ? (duplicateValues / totalValues) * 100 : 0
return {
totalValues,
duplicateValues,
duplicatePercentage,
}
}
/**
* Clears all tracked values
*/
public clear(): void {
this.valueMap.clear()
}
}