mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 07:16:53 +05:00
160 lines
5.0 KiB
TypeScript
160 lines
5.0 KiB
TypeScript
import Parser from "tree-sitter"
|
|
import { NamingViolation } from "../../../domain/value-objects/NamingViolation"
|
|
import {
|
|
AST_FIELD_NAMES,
|
|
AST_FIELD_TYPES,
|
|
AST_MODIFIER_TYPES,
|
|
AST_PATTERN_TYPES,
|
|
AST_STATEMENT_TYPES,
|
|
AST_VARIABLE_TYPES,
|
|
} from "../../../shared/constants"
|
|
import { NAMING_VIOLATION_TYPES } from "../../../shared/constants/rules"
|
|
import { NAMING_ERROR_MESSAGES } from "../../constants/detectorPatterns"
|
|
|
|
/**
|
|
* AST-based analyzer for detecting variable naming violations
|
|
*
|
|
* Analyzes variable declarations to ensure proper naming conventions:
|
|
* - Regular variables: camelCase
|
|
* - Constants (exported UPPER_CASE): UPPER_SNAKE_CASE
|
|
* - Class properties: camelCase
|
|
* - Private properties with underscore prefix are allowed
|
|
*/
|
|
export class AstVariableNameAnalyzer {
|
|
/**
|
|
* Analyzes a variable declaration node
|
|
*/
|
|
public analyze(
|
|
node: Parser.SyntaxNode,
|
|
layer: string,
|
|
filePath: string,
|
|
_lines: string[],
|
|
): NamingViolation | null {
|
|
const variableNodeTypes = [
|
|
AST_VARIABLE_TYPES.VARIABLE_DECLARATOR,
|
|
AST_VARIABLE_TYPES.REQUIRED_PARAMETER,
|
|
AST_VARIABLE_TYPES.OPTIONAL_PARAMETER,
|
|
AST_VARIABLE_TYPES.PUBLIC_FIELD_DEFINITION,
|
|
AST_VARIABLE_TYPES.PROPERTY_SIGNATURE,
|
|
] as const
|
|
|
|
if (!(variableNodeTypes as readonly string[]).includes(node.type)) {
|
|
return null
|
|
}
|
|
|
|
const nameNode = node.childForFieldName(AST_FIELD_NAMES.NAME)
|
|
if (!nameNode) {
|
|
return null
|
|
}
|
|
|
|
if (this.isDestructuringPattern(nameNode)) {
|
|
return null
|
|
}
|
|
|
|
const variableName = nameNode.text
|
|
const lineNumber = nameNode.startPosition.row + 1
|
|
|
|
if (variableName.startsWith("_")) {
|
|
return null
|
|
}
|
|
|
|
const isConstant = this.isConstantVariable(node)
|
|
|
|
if (isConstant) {
|
|
if (!/^[A-Z][A-Z0-9_]*$/.test(variableName)) {
|
|
return NamingViolation.create(
|
|
variableName,
|
|
NAMING_VIOLATION_TYPES.WRONG_CASE,
|
|
layer,
|
|
`${filePath}:${String(lineNumber)}`,
|
|
NAMING_ERROR_MESSAGES.CONSTANT_UPPER_SNAKE_CASE,
|
|
variableName,
|
|
NAMING_ERROR_MESSAGES.USE_UPPER_SNAKE_CASE_CONSTANT,
|
|
)
|
|
}
|
|
} else {
|
|
if (!/^[a-z][a-zA-Z0-9]*$/.test(variableName)) {
|
|
return NamingViolation.create(
|
|
variableName,
|
|
NAMING_VIOLATION_TYPES.WRONG_CASE,
|
|
layer,
|
|
`${filePath}:${String(lineNumber)}`,
|
|
NAMING_ERROR_MESSAGES.VARIABLE_CAMEL_CASE,
|
|
variableName,
|
|
NAMING_ERROR_MESSAGES.USE_CAMEL_CASE_VARIABLE,
|
|
)
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Checks if node is a destructuring pattern (object or array)
|
|
*/
|
|
private isDestructuringPattern(node: Parser.SyntaxNode): boolean {
|
|
return (
|
|
node.type === AST_PATTERN_TYPES.OBJECT_PATTERN ||
|
|
node.type === AST_PATTERN_TYPES.ARRAY_PATTERN
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Checks if a variable is a constant (exported UPPER_CASE)
|
|
*/
|
|
private isConstantVariable(node: Parser.SyntaxNode): boolean {
|
|
const variableName = node.childForFieldName(AST_FIELD_NAMES.NAME)?.text
|
|
if (!variableName || !/^[A-Z]/.test(variableName)) {
|
|
return false
|
|
}
|
|
|
|
if (
|
|
node.type === AST_VARIABLE_TYPES.PUBLIC_FIELD_DEFINITION ||
|
|
node.type === AST_FIELD_TYPES.FIELD_DEFINITION
|
|
) {
|
|
return this.hasConstModifiers(node)
|
|
}
|
|
|
|
let current: Parser.SyntaxNode | null = node.parent
|
|
|
|
while (current) {
|
|
if (current.type === AST_STATEMENT_TYPES.LEXICAL_DECLARATION) {
|
|
const firstChild = current.child(0)
|
|
if (firstChild?.type === AST_MODIFIER_TYPES.CONST) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if (
|
|
current.type === AST_VARIABLE_TYPES.PUBLIC_FIELD_DEFINITION ||
|
|
current.type === AST_FIELD_TYPES.FIELD_DEFINITION
|
|
) {
|
|
return this.hasConstModifiers(current)
|
|
}
|
|
|
|
current = current.parent
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Checks if field has readonly or static modifiers (indicating a constant)
|
|
*/
|
|
private hasConstModifiers(fieldNode: Parser.SyntaxNode): boolean {
|
|
for (let i = 0; i < fieldNode.childCount; i++) {
|
|
const child = fieldNode.child(i)
|
|
const childText = child?.text
|
|
if (
|
|
child?.type === AST_MODIFIER_TYPES.READONLY ||
|
|
child?.type === AST_MODIFIER_TYPES.STATIC ||
|
|
childText === AST_MODIFIER_TYPES.READONLY ||
|
|
childText === AST_MODIFIER_TYPES.STATIC
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|