From c874216204b37b74a8482312020f6286edeb44b5 Mon Sep 17 00:00:00 2001 From: Bakhtier Gaibulloev Date: Mon, 17 Nov 2025 12:53:18 +0000 Subject: [PATCH 1/3] refactor: reduce nextToken() cognitive complexity from 17 to 15 Refactored the nextToken() method in the Lexer class to reduce its cognitive complexity by extracting logic into three helper methods: - handleSwitchCharacters(): Handles basic punctuation and arithmetic operators - handleComparisonAndBitwiseOperators(): Handles comparison and bitwise operators - handleLiteralsAndIdentifiers(): Handles strings, numbers, and identifiers This refactoring reduces the cognitive complexity from 17 to below the SonarQube threshold of 15 while maintaining all existing functionality. All 58 lexer tests pass successfully. Fixes SonarQube cognitive complexity issue in src/lexer.ts:262 --- src/lexer.ts | 62 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/lexer.ts b/src/lexer.ts index eec21ef..e70d717 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -275,6 +275,20 @@ export class Lexer { return this.nextToken(); } + const switchResult = this.handleSwitchCharacters(char, startLine, startColumn); + if (switchResult) return switchResult; + + const operatorResult = this.handleComparisonAndBitwiseOperators(char, startLine, startColumn); + if (operatorResult) return operatorResult; + + return this.handleLiteralsAndIdentifiers(char, startLine, startColumn); + } + + private handleSwitchCharacters( + char: string, + startLine: number, + startColumn: number + ): Token | null { switch (char) { case '+': return this.handlePlusOperator(); @@ -311,22 +325,46 @@ export class Lexer { this.line++; this.column = 1; return this.createToken(TokenType.NEWLINE, '\n', startLine, startColumn); + default: + return null; } + } - if (char === '=') return this.handleEqualOperator(startLine, startColumn); - if (char === '!') return this.handleNotOperator(startLine, startColumn); - if (char === '<') return this.handleLessThanOperator(startLine, startColumn); - if (char === '>') return this.handleGreaterThanOperator(startLine, startColumn); - if (char === '&') return this.handleAmpersandOperator(startLine, startColumn); - if (char === '|') return this.handlePipeOperator(startLine, startColumn); - if (char === '^') return this.handleCaretOperator(startLine, startColumn); - if (char === '?') return this.handleQuestionOperator(startLine, startColumn); - - if (char === '~') { - this.advance(); - return this.createToken(TokenType.BITWISE_NOT, '~', startLine, startColumn); + private handleComparisonAndBitwiseOperators( + char: string, + startLine: number, + startColumn: number + ): Token | null { + switch (char) { + case '=': + return this.handleEqualOperator(startLine, startColumn); + case '!': + return this.handleNotOperator(startLine, startColumn); + case '<': + return this.handleLessThanOperator(startLine, startColumn); + case '>': + return this.handleGreaterThanOperator(startLine, startColumn); + case '&': + return this.handleAmpersandOperator(startLine, startColumn); + case '|': + return this.handlePipeOperator(startLine, startColumn); + case '^': + return this.handleCaretOperator(startLine, startColumn); + case '?': + return this.handleQuestionOperator(startLine, startColumn); + case '~': + this.advance(); + return this.createToken(TokenType.BITWISE_NOT, '~', startLine, startColumn); + default: + return null; } + } + private handleLiteralsAndIdentifiers( + char: string, + startLine: number, + startColumn: number + ): Token { if (char === '"' || char === "'") { return this.readString(char, startLine, startColumn); } From 2e0da84d43bf99ecbe67c43bb2ca4a5d58eba8be Mon Sep 17 00:00:00 2001 From: Bakhtier Gaibulloev Date: Mon, 17 Nov 2025 13:04:36 +0000 Subject: [PATCH 2/3] fix: resolve medium-priority SonarQube code quality issues - Use optional chain expressions for cleaner null checks (codegen.ts) - Mark never-reassigned class members as readonly (parser.ts, type-checker.ts, module-system files) - Use node: prefix for Node.js built-in imports (resource-limiter.ts, runtime-config.ts, structured-logger.ts, production-validator.ts) - Replace Array with Set for membership checks (config.ts, module-registry.ts, module-system.ts) - Refactor nested template literals for better readability (module-system.ts) These changes improve code maintainability, performance, and readability while adhering to modern TypeScript and Node.js best practices. Addresses SonarQube code smell issues --- src/codegen.ts | 8 ++++---- src/config.ts | 6 +++--- src/module-system/module-registry.ts | 6 +++--- src/module-system/module-system.ts | 18 +++++++++++------- src/module-system/resource-limiter.ts | 4 ++-- src/module-system/runtime-config.ts | 4 ++-- src/module-system/signal-handler.ts | 2 +- src/module-system/structured-logger.ts | 4 ++-- src/parser.ts | 6 +++--- src/production-validator.ts | 4 ++-- src/type-checker.ts | 6 +++--- 11 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/codegen.ts b/src/codegen.ts index 59eb5f9..f7ba883 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -1049,7 +1049,7 @@ export class CodeGenerator { result += this.indent(`const ${name} = {};\n`); // Generate namespace body - if (node.body && node.body.statements) { + if (node.body?.statements) { for (const stmt of node.body.statements) { const isExported = (stmt as Statement & { exported?: boolean }).exported; @@ -1127,7 +1127,7 @@ export class CodeGenerator { return (stmt as FunctionDeclaration).name.name; case 'VariableDeclaration': { const varDecl = stmt as VariableDeclaration; - if (varDecl.identifier && varDecl.identifier.type === 'Identifier') { + if (varDecl.identifier?.type === 'Identifier') { return varDecl.identifier.name; } break; @@ -1150,7 +1150,7 @@ export class CodeGenerator { let classBody = ''; // Generate class members - if (node.body && node.body.body) { + if (node.body?.body) { const members = node.body.body .map(member => { switch (member.type) { @@ -1192,7 +1192,7 @@ export class CodeGenerator { } else { // Regular methods have a body // Handle cases where body might be null or undefined - if (!node.value || !node.value.body) { + if (!node.value?.body) { return this.indent(`${isStatic}${methodName}(${params}) {}`); } const body = this.generateBlockStatement(node.value.body); diff --git a/src/config.ts b/src/config.ts index 81af91b..cfa5339 100644 --- a/src/config.ts +++ b/src/config.ts @@ -174,7 +174,7 @@ function validateModuleSystem(config: unknown, basePath = 'moduleSystem'): Confi function validateModuleSystemTopLevel(config: object, basePath: string): ConfigValidationError[] { const errors: ConfigValidationError[] = []; - const knownTop = [ + const knownTop = new Set([ 'resolution', 'loading', 'compilation', @@ -183,10 +183,10 @@ function validateModuleSystemTopLevel(config: object, basePath: string): ConfigV 'logger', 'managementServer', 'managementPort', - ]; + ]); for (const key of Object.keys(config)) { - if (!knownTop.includes(key)) { + if (!knownTop.has(key)) { errors.push({ path: `${basePath}.${key}`, message: `unknown property` }); } } diff --git a/src/module-system/module-registry.ts b/src/module-system/module-registry.ts index 5472f5e..d2ce8c9 100644 --- a/src/module-system/module-registry.ts +++ b/src/module-system/module-registry.ts @@ -424,17 +424,17 @@ export class ModuleRegistry { } // For relative paths, try to find the matching module - const possiblePaths = [ + const possiblePaths = new Set([ path.resolve(fromDir, specifier), path.resolve(fromDir, specifier + '.som'), path.resolve(fromDir, specifier + '.js'), path.resolve(fromDir, specifier, 'index.som'), path.resolve(fromDir, specifier, 'index.js'), - ]; + ]); // Find a registered module that matches one of the possible paths for (const mod of this.modules.values()) { - if (possiblePaths.includes(mod.resolvedPath)) { + if (possiblePaths.has(mod.resolvedPath)) { return mod.id; } } diff --git a/src/module-system/module-system.ts b/src/module-system/module-system.ts index c21ed1d..b49a323 100644 --- a/src/module-system/module-system.ts +++ b/src/module-system/module-system.ts @@ -259,9 +259,8 @@ export class ModuleSystem { this.validateResourceLimits(options, errors); if (errors.length > 0) { - throw new Error( - `ModuleSystem configuration validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}` - ); + const formattedErrors = errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n'); + throw new Error(`ModuleSystem configuration validation failed:\n${formattedErrors}`); } } @@ -765,7 +764,12 @@ export class ModuleSystem { const warningInfo = compilationResult.warnings.length > 0 - ? `\n\nWarnings (${compilationResult.warnings.length}):\n${compilationResult.warnings.map((w, i) => ` ${i + 1}. ${w}`).join('\n')}` + ? (() => { + const formattedWarnings = compilationResult.warnings + .map((w, i) => ` ${i + 1}. ${w}`) + .join('\n'); + return `\n\nWarnings (${compilationResult.warnings.length}):\n${formattedWarnings}`; + })() : ''; const errorMessage = `Bundle process failed with ${compilationResult.errors.length} error(s):\n\n${errorDetails}${warningInfo}`; @@ -1026,20 +1030,20 @@ export class ModuleSystem { const watcher = chokidar.watch(Array.from(watchRoots), watchConfig); - const supportedEvents: ModuleWatchEventType[] = [ + const supportedEvents = new Set([ 'add', 'change', 'unlink', 'addDir', 'unlinkDir', - ]; + ]); watcher.on('all', (event: string, changedPath: string) => { if (!options.onChange) { return; } - if (!supportedEvents.includes(event as ModuleWatchEventType)) { + if (!supportedEvents.has(event as ModuleWatchEventType)) { return; } diff --git a/src/module-system/resource-limiter.ts b/src/module-system/resource-limiter.ts index 5fb801f..2b0627b 100644 --- a/src/module-system/resource-limiter.ts +++ b/src/module-system/resource-limiter.ts @@ -2,7 +2,7 @@ * Production-grade resource limiting and monitoring * Prevents memory exhaustion and resource leaks */ -import * as process from 'process'; +import * as process from 'node:process'; export interface ResourceLimits { /** Maximum heap memory in bytes (default: 1GB) */ @@ -33,7 +33,7 @@ export type ResourceWarningCallback = (_usage: ResourceUsage, _limit: string) => export class ResourceLimiter { private readonly limits: Required; private checkIntervalId?: ReturnType; - private warningCallbacks: ResourceWarningCallback[] = []; + private readonly warningCallbacks: ResourceWarningCallback[] = []; private moduleCount = 0; private fileHandleCount = 0; diff --git a/src/module-system/runtime-config.ts b/src/module-system/runtime-config.ts index 99bec97..3fa6a0b 100644 --- a/src/module-system/runtime-config.ts +++ b/src/module-system/runtime-config.ts @@ -2,8 +2,8 @@ * Runtime configuration and health check system * Provides dynamic configuration updates and HTTP management endpoints */ -import * as http from 'http'; -import * as url from 'url'; +import * as http from 'node:http'; +import * as url from 'node:url'; import { ModuleSystemMetrics } from './metrics'; import { CircuitBreakerManager } from './circuit-breaker'; import { LoggerFactory, LogLevel } from './logger'; diff --git a/src/module-system/signal-handler.ts b/src/module-system/signal-handler.ts index 31b5d64..8038de2 100644 --- a/src/module-system/signal-handler.ts +++ b/src/module-system/signal-handler.ts @@ -20,7 +20,7 @@ export interface SignalHandlerOptions { * Graceful shutdown manager for production systems */ export class SignalHandler { - private handlers: ShutdownHandler[] = []; + private readonly handlers: ShutdownHandler[] = []; private isShuttingDown = false; private readonly shutdownTimeout: number; private readonly logger?: SignalHandlerOptions['logger']; diff --git a/src/module-system/structured-logger.ts b/src/module-system/structured-logger.ts index ce49e3d..ca3cac0 100644 --- a/src/module-system/structured-logger.ts +++ b/src/module-system/structured-logger.ts @@ -1,7 +1,7 @@ /** * Structured logging with JSON support and correlation IDs */ -import { randomBytes } from 'crypto'; +import { randomBytes } from 'node:crypto'; export interface LogContext { correlationId?: string; @@ -207,7 +207,7 @@ export class StructuredLogger { * Global logger factory with structured logging support */ export class StructuredLoggerFactory { - private static loggers = new Map(); + private static readonly loggers = new Map(); private static globalOptions: StructuredLoggerOptions = { format: process.env.LOG_FORMAT === 'json' ? 'json' : 'text', level: (process.env.LOG_LEVEL as LogLevel) || 'info', diff --git a/src/parser.ts b/src/parser.ts index 3f2b94e..57a4cbe 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -83,9 +83,9 @@ export class Parser { private tokens: Token[]; private current: number = 0; private errors: string[] = []; - private importHandler: ImportHandler; - private declarationHandler: DeclarationHandler; - private loopHandler: LoopHandler; + private readonly importHandler: ImportHandler; + private readonly declarationHandler: DeclarationHandler; + private readonly loopHandler: LoopHandler; constructor(tokens: Token[]) { this.tokens = tokens; diff --git a/src/production-validator.ts b/src/production-validator.ts index 3d85c2c..9e15acf 100644 --- a/src/production-validator.ts +++ b/src/production-validator.ts @@ -3,8 +3,8 @@ * Implements AGENTS.md principle: "Fail fast, fail clearly" */ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; export interface ValidationError { category: 'environment' | 'permissions' | 'configuration'; diff --git a/src/type-checker.ts b/src/type-checker.ts index 5e905d2..292efc5 100644 --- a/src/type-checker.ts +++ b/src/type-checker.ts @@ -92,10 +92,10 @@ export class TypeChecker { private errors: TypeCheckError[] = []; private warnings: TypeCheckError[] = []; private symbolTable: Map = new Map(); - private interfaceTable: Map = new Map(); - private typeAliasTable: Map = new Map(); + private readonly interfaceTable: Map = new Map(); + private readonly typeAliasTable: Map = new Map(); - private sourceLines: string[] = []; + private readonly sourceLines: string[] = []; constructor(source?: string) { this.sourceLines = source ? source.split(/\r?\n/) : []; From 91b8bc132ca770fb9176ecf2076ad9b20f7d3268 Mon Sep 17 00:00:00 2001 From: Bakhtier Gaibulloev Date: Mon, 17 Nov 2025 14:16:09 +0000 Subject: [PATCH 3/3] fix: resolve additional SonarQube code quality issues - Replace nested ternary operations with if-else statements (type-checker.ts, prometheus-metrics.ts) - Use optional chain expressions for cleaner null checks (type-checker.ts: 7 instances) - Use String#startsWith instead of array indexing (modular-lexer-compatible.ts) - Remove useless assignment to unused variable (application-layer.ts) These changes improve code readability and maintainability while following JavaScript/TypeScript best practices. Addresses additional SonarQube code smell issues --- src/core/application-layer.ts | 2 - src/core/modular-lexer-compatible.ts | 2 +- src/module-system/prometheus-metrics.ts | 9 ++++- src/type-checker.ts | 52 +++++++++++++------------ 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/core/application-layer.ts b/src/core/application-layer.ts index 18090e7..d614b1d 100644 --- a/src/core/application-layer.ts +++ b/src/core/application-layer.ts @@ -382,8 +382,6 @@ export class CompilationUseCase implements ICompilationUseCase { metrics, }; } catch (error) { - const totalTime = performance.now() - startTime; - errors.push({ message: `Compilation failed: ${error}`, line: 0, diff --git a/src/core/modular-lexer-compatible.ts b/src/core/modular-lexer-compatible.ts index 2589c8f..704d1ca 100644 --- a/src/core/modular-lexer-compatible.ts +++ b/src/core/modular-lexer-compatible.ts @@ -374,7 +374,7 @@ export class OperatorRecognizer extends BaseTokenRecognizer { canRecognize(input: string, position: number): boolean { const char = this.peek(input, position); // Check if any operator starts with this character - return this.sortedOperators.some(op => op[0] === char); + return this.sortedOperators.some(op => op.startsWith(char)); } recognize( diff --git a/src/module-system/prometheus-metrics.ts b/src/module-system/prometheus-metrics.ts index ee4b2bd..bc6fc23 100644 --- a/src/module-system/prometheus-metrics.ts +++ b/src/module-system/prometheus-metrics.ts @@ -213,7 +213,14 @@ export class PrometheusExporter { ); for (const [name, status] of Object.entries(cbStats)) { - const stateValue = status.state === 'closed' ? 0 : status.state === 'open' ? 1 : 2; + let stateValue: number; + if (status.state === 'closed') { + stateValue = 0; + } else if (status.state === 'open') { + stateValue = 1; + } else { + stateValue = 2; + } lines.push(`${this.prefix}_circuit_breaker_state{name="${name}"} ${stateValue} ${timestamp}`); lines.push( `${this.prefix}_circuit_breaker_failures{name="${name}"} ${status.failures} ${timestamp}` diff --git a/src/type-checker.ts b/src/type-checker.ts index 292efc5..814d6fc 100644 --- a/src/type-checker.ts +++ b/src/type-checker.ts @@ -598,7 +598,7 @@ export class TypeChecker { // Check if it's a class type const classType = this.symbolTable.get(typeName); - if (classType && classType.kind === 'class') { + if (classType?.kind === 'class') { return classType; } @@ -712,12 +712,12 @@ export class TypeChecker { private inferArrayExpressionType(arrayExpr: ArrayExpression, targetType?: Type): Type { // If target type is a tuple, use bidirectional inference - if (targetType && targetType.kind === 'tuple' && targetType.types) { + if (targetType?.kind === 'tuple' && targetType.types) { return this.inferTupleTypeFromTarget(arrayExpr, targetType.types); } // If target is an array, infer array element type - if (targetType && targetType.kind === 'array') { + if (targetType?.kind === 'array') { const elementType = targetType.elementType || { kind: 'unknown' }; arrayExpr.elements.forEach((element: Expression) => this.inferExpressionType(element, elementType) @@ -743,21 +743,23 @@ export class TypeChecker { */ private getBaseType(type: Type): Type { if (type.kind === 'literal') { - const baseTypeName = - typeof type.value === 'string' - ? 'string' - : typeof type.value === 'number' - ? 'number' - : typeof type.value === 'boolean' - ? 'boolean' - : 'null'; + let baseTypeName: string; + if (typeof type.value === 'string') { + baseTypeName = 'string'; + } else if (typeof type.value === 'number') { + baseTypeName = 'number'; + } else if (typeof type.value === 'boolean') { + baseTypeName = 'boolean'; + } else { + baseTypeName = 'null'; + } return { kind: 'primitive', name: baseTypeName }; } return type; } private inferObjectType(objExpr: ObjectExpression, targetType?: Type): Type { - if (targetType && targetType.kind === 'interface') { + if (targetType?.kind === 'interface') { return targetType; } @@ -794,10 +796,10 @@ export class TypeChecker { } private inferCallType(callExpr: CallExpression): Type { - if (callExpr.callee && callExpr.callee.type === 'Identifier') { + if (callExpr.callee?.type === 'Identifier') { const functionName = (callExpr.callee as Identifier).name; const functionType = this.symbolTable.get(functionName); - if (functionType && functionType.kind === 'function' && functionType.returnType) { + if (functionType?.kind === 'function' && functionType.returnType) { return functionType.returnType; } } @@ -805,7 +807,7 @@ export class TypeChecker { } private inferNewExpressionType(newExpr: NewExpression): Type { - if (newExpr.callee && newExpr.callee.type === 'Identifier') { + if (newExpr.callee?.type === 'Identifier') { const className = (newExpr.callee as Identifier).name; // Handle built-in generic types: Map and Set @@ -828,7 +830,7 @@ export class TypeChecker { } const classType = this.symbolTable.get(className); - if (classType && classType.kind === 'class') { + if (classType?.kind === 'class') { // Return the actual class type with all its properties and baseType return classType; } @@ -840,14 +842,16 @@ export class TypeChecker { if (source.kind !== 'literal' || target.kind !== 'primitive') { return false; } - const baseType = - typeof source.value === 'string' - ? 'string' - : typeof source.value === 'number' - ? 'number' - : typeof source.value === 'boolean' - ? 'boolean' - : 'null'; + let baseType: string; + if (typeof source.value === 'string') { + baseType = 'string'; + } else if (typeof source.value === 'number') { + baseType = 'number'; + } else if (typeof source.value === 'boolean') { + baseType = 'boolean'; + } else { + baseType = 'null'; + } return baseType === target.name; }