checkCircularDependencies detects cycles in the dependency graph nolint:gocognit // TODO: refactor to reduce complexity
(tasks []*model.Task, taskMap map[string]*model.Task, result *ValidationResult)
| 247 | // |
| 248 | //nolint:gocognit // TODO: refactor to reduce complexity |
| 249 | func (v *Validator) checkCircularDependencies(tasks []*model.Task, taskMap map[string]*model.Task, result *ValidationResult) { |
| 250 | // Build adjacency list |
| 251 | graph := make(map[string][]string) |
| 252 | for _, task := range tasks { |
| 253 | graph[task.ID] = task.Dependencies |
| 254 | } |
| 255 | |
| 256 | // Track visit states: 0 = unvisited, 1 = visiting, 2 = visited |
| 257 | visitState := make(map[string]int) |
| 258 | path := []string{} |
| 259 | |
| 260 | var hasCycle func(string) bool |
| 261 | hasCycle = func(taskID string) bool { |
| 262 | if visitState[taskID] == 1 { |
| 263 | // Found a cycle - build cycle path |
| 264 | cycleStart := -1 |
| 265 | for i, id := range path { |
| 266 | if id == taskID { |
| 267 | cycleStart = i |
| 268 | break |
| 269 | } |
| 270 | } |
| 271 | if cycleStart >= 0 { |
| 272 | cyclePath := append(path[cycleStart:], taskID) |
| 273 | result.AddIssue(LevelError, taskID, taskMap[taskID].FilePath, |
| 274 | fmt.Sprintf("circular dependency detected: %s", strings.Join(cyclePath, " -> "))) |
| 275 | } |
| 276 | return true |
| 277 | } |
| 278 | |
| 279 | if visitState[taskID] == 2 { |
| 280 | return false // Already fully processed |
| 281 | } |
| 282 | |
| 283 | visitState[taskID] = 1 |
| 284 | path = append(path, taskID) |
| 285 | |
| 286 | for _, depID := range graph[taskID] { |
| 287 | if _, exists := taskMap[depID]; !exists { |
| 288 | continue // Skip missing dependencies (already reported) |
| 289 | } |
| 290 | if hasCycle(depID) { |
| 291 | visitState[taskID] = 2 |
| 292 | path = path[:len(path)-1] |
| 293 | return true |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | visitState[taskID] = 2 |
| 298 | path = path[:len(path)-1] |
| 299 | return false |
| 300 | } |
| 301 | |
| 302 | // Check each task for cycles |
| 303 | for taskID := range taskMap { |
| 304 | if visitState[taskID] == 0 { |
| 305 | hasCycle(taskID) |
| 306 | } |