Calendar/CYCLOMATIC_COMPLEXITY_ANALYSIS.md
Janus C. H. Knudsen 5fae433afb Allows dynamic drag clone replacement
Introduces a polymorphic `createClone` method on base event elements to customize clone generation.
Adds a `replaceClone` delegate to drag event payloads, enabling subscribers to dynamically swap the active dragged clone.
This supports scenarios like converting a standard event clone to an all-day event clone when dragging to the all-day header.
2025-10-04 23:10:09 +02:00

21 KiB

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():

// 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():

// 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():

// 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:

// 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:

// ✅ 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)

  1. Refactor SimpleEventOverlapManager.restackEventsInContainer() (11 → 3-4)
  2. 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)

  1. Review and simplify medium complexity methods (complexity 6-7)
  2. Add unit tests for extracted methods

Expected Impact:

  • All methods under complexity threshold of 10
  • Comprehensive test coverage

Phase 4: Continuous Improvement

  1. Establish cyclomatic complexity checks in CI/CD
  2. Set max complexity threshold to 10
  3. Regular code reviews focusing on complexity

Tools & Resources

  • TypeScript ESLint with complexity rule
  • SonarQube for continuous code quality monitoring
  • CodeClimate for maintainability scoring

Suggested ESLint Configuration:

{
  "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