# Cyclomatic Complexity Analysis Report **Calendar Plantempus Project** Generated: 2025-10-04 --- ## Executive Summary This report analyzes the cyclomatic complexity of the Calendar Plantempus TypeScript codebase, focusing on identifying methods that exceed recommended complexity thresholds and require refactoring. ### Key Metrics | Metric | Value | |--------|-------| | **Total Files Analyzed** | 6 | | **Total Methods Analyzed** | 74 | | **Methods with Complexity >10** | 4 (5.4%) | | **Methods with Complexity 6-10** | 5 (6.8%) | | **Methods with Complexity 1-5** | 65 (87.8%) | ### Complexity Distribution ``` ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ Low (1-5): 87.8% ■■■ Medium (6-10): 6.8% ■ High (>10): 5.4% ``` ### Overall Assessment ✅ **Strengths:** - 87.8% of methods have acceptable complexity - Web Components (SwpEventElement) demonstrate excellent design - Rendering services show clean separation of concerns 🔴 **Critical Issues:** - 4 methods exceed complexity threshold of 10 - Stack management logic is overly complex (complexity 18!) - Drag & drop handlers need refactoring --- ## Detailed File Analysis ### 1. DragDropManager.ts **File:** `src/managers/DragDropManager.ts` **Overall Complexity:** HIGH ⚠️ | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `init()` | 88-133 | 7 | 🟡 Medium | Event listener setup could be extracted | | `handleMouseDown()` | 135-168 | 5 | ✅ OK | Acceptable complexity | | `handleMouseMove()` | 173-260 | **15** | 🔴 **Critical** | **NEEDS IMMEDIATE REFACTORING** | | `handleMouseUp()` | 265-310 | 4 | ✅ OK | Clean implementation | | `cleanupAllClones()` | 312-320 | 2 | ✅ OK | Simple utility method | | `cancelDrag()` | 325-350 | 3 | ✅ OK | Straightforward cleanup | | `calculateDragPosition()` | 355-364 | 2 | ✅ OK | Simple calculation | | `calculateSnapPosition()` | 369-377 | 1 | ✅ OK | Base complexity | | `checkAutoScroll()` | 383-403 | 5 | ✅ OK | Could be simplified slightly | | `startAutoScroll()` | 408-444 | 6 | 🟡 Medium | Autoscroll logic could be extracted | | `stopAutoScroll()` | 449-454 | 2 | ✅ OK | Simple cleanup | | `detectDropTarget()` | 468-483 | 4 | ✅ OK | Clear DOM traversal | | `handleHeaderMouseEnter()` | 488-516 | 4 | ✅ OK | Clean event handling | | `handleHeaderMouseLeave()` | 521-544 | 4 | ✅ OK | Clean event handling | **Decision Points in handleMouseMove():** 1. `if (event.buttons === 1)` - Check if mouse button is pressed 2. `if (!this.isDragStarted && this.draggedElement)` - Check for drag initialization 3. `if (totalMovement >= this.dragThreshold)` - Movement threshold check 4. `if (this.isDragStarted && this.draggedElement && this.draggedClone)` - Drag state validation 5. `if (!this.draggedElement.hasAttribute("data-allday"))` - Event type check 6. `if (deltaY >= this.snapDistancePx)` - Snap interval check 7. Multiple autoscroll conditionals 8. `if (newColumn == null)` - Column validation 9. `if (newColumn?.index !== this.currentColumnBounds?.index)` - Column change detection **Recommendation for handleMouseMove():** ```typescript // Current: 88 lines, complexity 15 // Suggested refactoring: private handleMouseMove(event: MouseEvent): void { this.updateMousePosition(event); if (!this.isMouseButtonPressed(event)) return; if (this.shouldStartDrag()) { this.initializeDrag(); } if (this.isDragActive()) { this.updateDragPosition(); this.handleColumnChange(); } } // Extract methods with complexity 2-4 each: // - initializeDrag() // - updateDragPosition() // - handleColumnChange() ``` --- ### 2. SwpEventElement.ts **File:** `src/elements/SwpEventElement.ts` **Overall Complexity:** LOW ✅ | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `connectedCallback()` | 84-89 | 2 | ✅ OK | Simple initialization | | `attributeChangedCallback()` | 94-98 | 2 | ✅ OK | Clean attribute handling | | `updatePosition()` | 109-128 | 2 | ✅ OK | Straightforward update logic | | `createClone()` | 133-152 | 2 | ✅ OK | Simple cloning | | `render()` | 161-171 | 1 | ✅ OK | Base complexity | | `updateDisplay()` | 176-194 | 3 | ✅ OK | Clean DOM updates | | `applyPositioning()` | 199-205 | 1 | ✅ OK | Delegates to PositionUtils | | `calculateTimesFromPosition()` | 210-230 | 1 | ✅ OK | Simple calculation | | `fromCalendarEvent()` (static) | 239-252 | 1 | ✅ OK | Factory method | | `extractCalendarEventFromElement()` (static) | 257-270 | 1 | ✅ OK | Clean extraction | | `fromAllDayElement()` (static) | 275-311 | 4 | ✅ OK | Acceptable conversion logic | | `SwpAllDayEventElement.connectedCallback()` | 319-323 | 2 | ✅ OK | Simple setup | | `SwpAllDayEventElement.createClone()` | 328-335 | 1 | ✅ OK | Base complexity | | `SwpAllDayEventElement.applyGridPositioning()` | 340-343 | 1 | ✅ OK | Simple positioning | | `SwpAllDayEventElement.fromCalendarEvent()` (static) | 348-362 | 1 | ✅ OK | Factory method | **Best Practices Demonstrated:** - ✅ Clear separation of concerns - ✅ Factory methods for object creation - ✅ Delegation to utility classes (PositionUtils, DateService) - ✅ BaseSwpEventElement abstraction reduces duplication - ✅ All methods stay within complexity threshold **This file serves as a model for good design in the codebase.** --- ### 3. SimpleEventOverlapManager.ts **File:** `src/managers/SimpleEventOverlapManager.ts` **Overall Complexity:** HIGH ⚠️ | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `resolveOverlapType()` | 33-58 | 4 | ✅ OK | Clear overlap detection | | `groupOverlappingElements()` | 64-84 | 4 | ✅ OK | Acceptable grouping logic | | `createEventGroup()` | 89-92 | 1 | ✅ OK | Simple factory | | `addToEventGroup()` | 97-113 | 2 | ✅ OK | Straightforward addition | | `createStackedEvent()` | 118-165 | 7 | 🟡 Medium | Chain traversal could be extracted | | `removeStackedStyling()` | 170-284 | **18** | 🔴 **Critical** | **MOST COMPLEX METHOD IN CODEBASE** | | `updateSubsequentStackLevels()` | 289-313 | 5 | ✅ OK | Could be simplified | | `isStackedEvent()` | 318-324 | 3 | ✅ OK | Simple boolean check | | `removeFromEventGroup()` | 329-364 | 6 | 🟡 Medium | Remaining event handling complex | | `restackEventsInContainer()` | 369-432 | **11** | 🔴 **High** | **NEEDS REFACTORING** | | `getEventGroup()` | 438-440 | 1 | ✅ OK | Simple utility | | `isInEventGroup()` | 442-444 | 1 | ✅ OK | Simple utility | | `getStackLink()` | 449-459 | 3 | ✅ OK | JSON parsing with error handling | | `setStackLink()` | 461-467 | 2 | ✅ OK | Simple setter | | `findElementById()` | 469-471 | 1 | ✅ OK | Base complexity | **Critical Issue: removeStackedStyling() - Complexity 18** **Decision Points Breakdown:** 1. `if (link)` - Check if element has stack link 2. `if (link.prev && link.next)` - Middle element in chain 3. `if (prevElement && nextElement)` - Both neighbors exist 4. `if (!actuallyOverlap)` - Chain breaking decision (CRITICAL BRANCH) 5. `if (nextLink?.next)` - Subsequent elements exist 6. `while (subsequentId)` - Loop through chain 7. `if (!subsequentElement)` - Element validation 8. `else` - Normal stacking (chain maintenance) 9. `else if (link.prev)` - Last element case 10. `if (prevElement)` - Previous element exists 11. `else if (link.next)` - First element case 12. `if (nextElement)` - Next element exists 13. `if (link.prev && link.next)` - Middle element check (duplicate) 14. `if (nextLink && nextLink.next)` - Chain continuation 15. `else` - Chain was broken 16-18. Additional nested conditions **Recommendation for removeStackedStyling():** ```typescript // Current: 115 lines, complexity 18 // Suggested refactoring: public removeStackedStyling(eventElement: HTMLElement): void { this.clearVisualStyling(eventElement); const link = this.getStackLink(eventElement); if (!link) return; // Delegate to specialized methods based on position in chain if (link.prev && link.next) { this.removeMiddleElementFromChain(eventElement, link); } else if (link.prev) { this.removeLastElementFromChain(eventElement, link); } else if (link.next) { this.removeFirstElementFromChain(eventElement, link); } this.setStackLink(eventElement, null); } // Extract to separate methods: // - clearVisualStyling() - complexity 1 // - removeMiddleElementFromChain() - complexity 5-6 // - removeLastElementFromChain() - complexity 3 // - removeFirstElementFromChain() - complexity 3 // - breakStackChain() - complexity 4 // - maintainStackChain() - complexity 4 ``` **Critical Issue: restackEventsInContainer() - Complexity 11** **Decision Points:** 1. `if (stackedEvents.length === 0)` - Early return 2. `for (const element of stackedEvents)` - Iterate events 3. `if (!eventId || processedEventIds.has(eventId))` - Validation 4. `while (rootLink?.prev)` - Find root of chain 5. `if (!prevElement)` - Break condition 6. `while (currentElement)` - Traverse chain 7. `if (!currentLink?.next)` - End of chain 8. `if (!nextElement)` - Break condition 9. `if (chain.length > 1)` - Only add multi-element chains 10. `forEach` - Restack each chain 11. `if (link)` - Update link data **Recommendation for restackEventsInContainer():** ```typescript // Current: 64 lines, complexity 11 // Suggested refactoring: public restackEventsInContainer(container: HTMLElement): void { const stackedEvents = this.getStackedEvents(container); if (stackedEvents.length === 0) return; const stackChains = this.collectStackChains(stackedEvents); stackChains.forEach(chain => this.reapplyStackStyling(chain)); } // Extract to separate methods: // - getStackedEvents() - complexity 2 // - collectStackChains() - complexity 6 // - findStackRoot() - complexity 3 // - traverseChain() - complexity 3 // - reapplyStackStyling() - complexity 2 ``` --- ### 4. EventRendererManager.ts **File:** `src/renderers/EventRendererManager.ts` **Overall Complexity:** MEDIUM 🟡 | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `renderEvents()` | 35-68 | 3 | ✅ OK | Clean rendering logic | | `setupEventListeners()` | 70-95 | 1 | ✅ OK | Simple delegation | | `handleGridRendered()` | 101-127 | 5 | ✅ OK | Could reduce conditionals | | `handleViewChanged()` | 133-138 | 1 | ✅ OK | Simple cleanup | | `setupDragEventListeners()` | 144-238 | **10** | 🔴 **High** | **NEEDS REFACTORING** | | `handleConvertToTimeEvent()` | 243-292 | 4 | ✅ OK | Acceptable conversion logic | | `clearEvents()` | 294-296 | 1 | ✅ OK | Delegates to strategy | | `refresh()` | 298-300 | 1 | ✅ OK | Simple refresh | **Issue: setupDragEventListeners() - Complexity 10** **Decision Points:** 1. `if (hasAttribute('data-allday'))` - Filter all-day events 2. `if (draggedElement && strategy.handleDragStart && columnBounds)` - Validation 3. `if (hasAttribute('data-allday'))` - Filter check 4. `if (strategy.handleDragMove)` - Strategy check 5. `if (strategy.handleDragAutoScroll)` - Strategy check 6. `if (target === 'swp-day-column' && finalColumn)` - Drop target validation 7. `if (draggedElement && draggedClone && strategy.handleDragEnd)` - Validation 8. `if (dayEventClone)` - Cleanup check 9. `if (hasAttribute('data-allday'))` - Filter check 10. `if (strategy.handleColumnChange)` - Strategy check **Recommendation:** ```typescript // Current: 95 lines, complexity 10 // Suggested refactoring: private setupDragEventListeners(): void { this.setupDragStartListener(); this.setupDragMoveListener(); this.setupDragEndListener(); this.setupDragAutoScrollListener(); this.setupColumnChangeListener(); this.setupConversionListener(); this.setupNavigationListener(); } // Each listener method: complexity 2-3 ``` --- ### 5. EventRenderer.ts **File:** `src/renderers/EventRenderer.ts` **Overall Complexity:** LOW ✅ | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `handleDragStart()` | 50-72 | 2 | ✅ OK | Clean drag initialization | | `handleDragMove()` | 77-84 | 2 | ✅ OK | Simple position update | | `handleDragAutoScroll()` | 89-97 | 2 | ✅ OK | Simple scroll handling | | `handleColumnChange()` | 102-115 | 3 | ✅ OK | Clean column switching | | `handleDragEnd()` | 120-141 | 3 | ✅ OK | Proper cleanup | | `handleNavigationCompleted()` | 146-148 | 1 | ✅ OK | Placeholder method | | `fadeOutAndRemove()` | 153-160 | 1 | ✅ OK | Simple animation | | `renderEvents()` | 163-182 | 2 | ✅ OK | Straightforward rendering | | `renderEvent()` | 184-186 | 1 | ✅ OK | Factory delegation | | `calculateEventPosition()` | 188-191 | 1 | ✅ OK | Delegates to utility | | `clearEvents()` | 193-200 | 2 | ✅ OK | Simple cleanup | | `getColumns()` | 202-205 | 1 | ✅ OK | DOM query | | `getEventsForColumn()` | 207-221 | 2 | ✅ OK | Filter logic | **Best Practices:** - ✅ All methods under complexity 4 - ✅ Clear method names - ✅ Delegation to utilities - ✅ Single responsibility per method --- ### 6. AllDayEventRenderer.ts **File:** `src/renderers/AllDayEventRenderer.ts` **Overall Complexity:** LOW ✅ | Method | Lines | Complexity | Status | Notes | |--------|-------|------------|--------|-------| | `getContainer()` | 20-32 | 3 | ✅ OK | Container initialization | | `getAllDayContainer()` | 35-37 | 1 | ✅ OK | Simple query | | `handleDragStart()` | 41-65 | 3 | ✅ OK | Clean drag setup | | `renderAllDayEventWithLayout()` | 72-83 | 2 | ✅ OK | Simple rendering | | `removeAllDayEvent()` | 89-97 | 3 | ✅ OK | Clean removal | | `clearCache()` | 102-104 | 1 | ✅ OK | Simple reset | | `renderAllDayEventsForPeriod()` | 109-116 | 1 | ✅ OK | Delegates to helper | | `clearAllDayEvents()` | 118-123 | 2 | ✅ OK | Simple cleanup | | `handleViewChanged()` | 125-127 | 1 | ✅ OK | Simple handler | **Best Practices:** - ✅ Consistent low complexity across all methods - ✅ Clear separation of concerns - ✅ Focused functionality --- ## Recommendations ### Immediate Action Required (Complexity >10) #### 1. SimpleEventOverlapManager.removeStackedStyling() - Priority: CRITICAL **Current Complexity:** 18 **Target Complexity:** 4-6 per method **Refactoring Steps:** 1. Extract `clearVisualStyling()` - Remove inline styles 2. Extract `removeMiddleElementFromChain()` - Handle middle element removal 3. Extract `removeLastElementFromChain()` - Handle last element removal 4. Extract `removeFirstElementFromChain()` - Handle first element removal 5. Extract `breakStackChain()` - Handle non-overlapping chain breaking 6. Extract `maintainStackChain()` - Handle overlapping chain maintenance **Expected Impact:** - Main method: complexity 4 - Helper methods: complexity 3-6 each - Improved testability - Easier maintenance --- #### 2. DragDropManager.handleMouseMove() - Priority: HIGH **Current Complexity:** 15 **Target Complexity:** 4-5 per method **Refactoring Steps:** 1. Extract `updateMousePosition()` - Update tracking variables 2. Extract `shouldStartDrag()` - Check movement threshold 3. Extract `initializeDrag()` - Create clone and emit start event 4. Extract `updateDragPosition()` - Handle position and autoscroll 5. Extract `handleColumnChange()` - Detect and handle column transitions **Expected Impact:** - Main method: complexity 4 - Helper methods: complexity 3-4 each - Better separation of drag lifecycle stages --- #### 3. SimpleEventOverlapManager.restackEventsInContainer() - Priority: HIGH **Current Complexity:** 11 **Target Complexity:** 3-4 per method **Refactoring Steps:** 1. Extract `getStackedEvents()` - Filter stacked events 2. Extract `collectStackChains()` - Build stack chains 3. Extract `findStackRoot()` - Find root of chain 4. Extract `traverseChain()` - Collect chain elements 5. Extract `reapplyStackStyling()` - Apply visual styling **Expected Impact:** - Main method: complexity 3 - Helper methods: complexity 2-4 each --- #### 4. EventRendererManager.setupDragEventListeners() - Priority: MEDIUM **Current Complexity:** 10 **Target Complexity:** 2-3 per method **Refactoring Steps:** 1. Extract `setupDragStartListener()` 2. Extract `setupDragMoveListener()` 3. Extract `setupDragEndListener()` 4. Extract `setupDragAutoScrollListener()` 5. Extract `setupColumnChangeListener()` 6. Extract `setupConversionListener()` 7. Extract `setupNavigationListener()` **Expected Impact:** - Main method: complexity 1 (just calls helpers) - Helper methods: complexity 2-3 each - Improved readability --- ### Medium Priority (Complexity 6-10) #### 5. SimpleEventOverlapManager.createStackedEvent() - Complexity 7 Consider extracting chain traversal logic into `findEndOfChain()` #### 6. DragDropManager.startAutoScroll() - Complexity 6 Extract scroll calculation into `calculateScrollAmount()` #### 7. SimpleEventOverlapManager.removeFromEventGroup() - Complexity 6 Extract remaining event handling into `handleRemainingEvents()` --- ## Code Quality Metrics ### Complexity by File ``` DragDropManager.ts: ████████░░ 8/10 (1 critical, 2 medium) SwpEventElement.ts: ██░░░░░░░░ 2/10 (excellent!) SimpleEventOverlapManager.ts: ██████████ 10/10 (2 critical, 2 medium) EventRendererManager.ts: ██████░░░░ 6/10 (1 critical) EventRenderer.ts: ██░░░░░░░░ 2/10 (excellent!) AllDayEventRenderer.ts: ██░░░░░░░░ 2/10 (excellent!) ``` ### Methods Requiring Attention | Priority | File | Method | Complexity | Effort | |----------|------|--------|------------|--------| | 🔴 Critical | SimpleEventOverlapManager | removeStackedStyling | 18 | High | | 🔴 Critical | DragDropManager | handleMouseMove | 15 | High | | 🔴 High | SimpleEventOverlapManager | restackEventsInContainer | 11 | Medium | | 🔴 High | EventRendererManager | setupDragEventListeners | 10 | Low | | 🟡 Medium | SimpleEventOverlapManager | createStackedEvent | 7 | Low | | 🟡 Medium | DragDropManager | startAutoScroll | 6 | Low | | 🟡 Medium | SimpleEventOverlapManager | removeFromEventGroup | 6 | Low | --- ## Positive Examples ### SwpEventElement.ts - Excellent Design Pattern This file demonstrates best practices: ```typescript // ✅ Clear, focused methods with single responsibility public updatePosition(columnDate: Date, snappedY: number): void { this.style.top = `${snappedY + 1}px`; const { startMinutes, endMinutes } = this.calculateTimesFromPosition(snappedY); const startDate = this.dateService.createDateAtTime(columnDate, startMinutes); let endDate = this.dateService.createDateAtTime(columnDate, endMinutes); if (endMinutes >= 1440) { const extraDays = Math.floor(endMinutes / 1440); endDate = this.dateService.addDays(endDate, extraDays); } this.start = startDate; this.end = endDate; } // Complexity: 2 (one if statement) ``` **Why this works:** - Single responsibility (update position) - Delegates complex calculations to helper methods - Clear variable names - Minimal branching --- ## Action Plan ### Phase 1: Critical Refactoring (Week 1-2) 1. ✅ Refactor `SimpleEventOverlapManager.removeStackedStyling()` (18 → 4-6) 2. ✅ Refactor `DragDropManager.handleMouseMove()` (15 → 4-5) **Expected Impact:** - Reduce highest complexity from 18 to 4-6 - Improve maintainability significantly - Enable easier testing ### Phase 2: High Priority (Week 3) 3. ✅ Refactor `SimpleEventOverlapManager.restackEventsInContainer()` (11 → 3-4) 4. ✅ Refactor `EventRendererManager.setupDragEventListeners()` (10 → 2-3) **Expected Impact:** - Eliminate all methods with complexity >10 - Improve overall code quality score ### Phase 3: Medium Priority (Week 4) 5. ✅ Review and simplify medium complexity methods (complexity 6-7) 6. ✅ Add unit tests for extracted methods **Expected Impact:** - All methods under complexity threshold of 10 - Comprehensive test coverage ### Phase 4: Continuous Improvement 7. ✅ Establish cyclomatic complexity checks in CI/CD 8. ✅ Set max complexity threshold to 10 9. ✅ Regular code reviews focusing on complexity --- ## Tools & Resources ### Recommended Tools for Ongoing Monitoring: - **TypeScript ESLint** with `complexity` rule - **SonarQube** for continuous code quality monitoring - **CodeClimate** for maintainability scoring ### Suggested ESLint Configuration: ```json { "rules": { "complexity": ["error", 10], "max-lines-per-function": ["warn", 50], "max-depth": ["error", 4] } } ``` --- ## Conclusion The Calendar Plantempus codebase shows **mixed code quality**: **Strengths:** - 87.8% of methods have acceptable complexity - Web Components demonstrate excellent design patterns - Clear separation of concerns in rendering services **Areas for Improvement:** - Stack management logic is overly complex - Some drag & drop handlers need refactoring - File naming could better reflect complexity (e.g., "Simple"EventOverlapManager has complexity 18!) **Overall Grade: B-** With the recommended refactoring, the codebase can easily achieve an **A grade** by reducing the 4 critical methods to acceptable complexity levels. --- **Generated by:** Claude Code Cyclomatic Complexity Analyzer **Date:** 2025-10-04 **Analyzer Version:** 1.0