Files
puaros/packages/guardian/src/infrastructure/strategies/AstBooleanAnalyzer.ts

93 lines
2.6 KiB
TypeScript

import Parser from "tree-sitter"
import { HardcodedValue, HardcodeType } from "../../domain/value-objects/HardcodedValue"
import { DETECTION_VALUES, HARDCODE_TYPES } from "../../shared/constants/rules"
import { AstContextChecker } from "./AstContextChecker"
/**
* AST-based analyzer for detecting magic booleans
*
* Detects boolean literals used as arguments without clear meaning.
* Example: doSomething(true, false, true) - hard to understand
* Better: doSomething({ sync: true, validate: false, cache: true })
*/
export class AstBooleanAnalyzer {
constructor(private readonly contextChecker: AstContextChecker) {}
/**
* Analyzes a boolean node and returns a violation if it's a magic boolean
*/
public analyze(node: Parser.SyntaxNode, lines: string[]): HardcodedValue | null {
if (!this.shouldDetect(node)) {
return null
}
const value = node.text === DETECTION_VALUES.BOOLEAN_TRUE
return this.createViolation(node, value, lines)
}
/**
* Checks if boolean should be detected
*/
private shouldDetect(node: Parser.SyntaxNode): boolean {
if (this.contextChecker.isInExportedConstant(node)) {
return false
}
if (this.contextChecker.isInTypeContext(node)) {
return false
}
if (this.contextChecker.isInTestDescription(node)) {
return false
}
const parent = node.parent
if (!parent) {
return false
}
if (parent.type === "arguments") {
return this.isInFunctionCallWithMultipleBooleans(parent)
}
return false
}
/**
* Checks if function call has multiple boolean arguments
*/
private isInFunctionCallWithMultipleBooleans(argsNode: Parser.SyntaxNode): boolean {
let booleanCount = 0
for (const child of argsNode.children) {
if (child.type === "true" || child.type === "false") {
booleanCount++
}
}
return booleanCount >= 2
}
/**
* Creates a HardcodedValue violation from a boolean node
*/
private createViolation(
node: Parser.SyntaxNode,
value: boolean,
lines: string[],
): HardcodedValue {
const lineNumber = node.startPosition.row + 1
const column = node.startPosition.column
const context = lines[node.startPosition.row]?.trim() ?? ""
return HardcodedValue.create(
value,
HARDCODE_TYPES.MAGIC_BOOLEAN as HardcodeType,
lineNumber,
column,
context,
)
}
}