package engine import ( "slices" "sort" ) type historySelection struct { path Path phase historyPhase payload historyPayloadRef reportedPath Path role historyRole before bool after bool attribution historyAttribution hasAttribution bool previous []Origin } type historySensitivityCandidate struct { path Path sensitive bool dependencies []Path } func (model *historyModel) selectHistory(path Path) []historySelection { if model == nil || !path.valid() || !model.hasObservedShape(path) { return nil } candidateSet := make(map[int]struct{}) for _, observationID := range model.observationsByPointer[path.String()] { candidateSet[observationID] = struct{}{} } model.reportedPathIndex.visitAncestors(path.String(), func(observationID int) { candidateSet[observationID] = struct{}{} }) candidates := make([]int, 0, len(candidateSet)) for observationID := range candidateSet { candidates = append(candidates, observationID) } slices.Sort(candidates) selections := make([]historySelection, 0, len(candidates)) selectionByPayload := make(map[historyPayloadRef]int) for _, observationID := range candidates { observation := model.observations[observationID] if observation.role == historyRoleInventory || !matchesPathShape(path, observation.path) { continue } selectionIndex, found := selectionByPayload[observation.payload] if !found { selectionIndex = len(selections) selectionByPayload[observation.payload] = selectionIndex selection := historySelection{ path: path.clone(), phase: observation.phase, payload: observation.payload, reportedPath: observation.reportedPath.clone(), role: observation.role, } if attribution, ok := model.resolveAttribution(observation.attributionRef); ok { selection.attribution = attribution selection.hasAttribution = true } selections = append(selections, selection) } selection := &selections[selectionIndex] selection.before = selection.before || observation.beforePresent selection.after = selection.after || observation.afterPresent } if len(selections) == 0 { return nil } var previous *Origin for index := range selections { selection := &selections[index] if selection.hasAttribution && metadataHistoryRole(selection.role) { if previous != nil { selection.previous = []Origin{cloneOrigin(*previous)} } origin := cloneOrigin(selection.attribution.origin) previous = &origin } } return selections } func (model *historyModel) hasObservedShape(path Path) bool { if model == nil || !path.valid() { return false } for _, candidate := range model.shapesByPointer[path.String()] { if matchesPathShape(path, candidate) { return true } } return false } func (model *historyModel) concreteShapes(pointer string) []Path { if model == nil { return nil } shapes := model.shapesByPointer[pointer] cloned := make([]Path, len(shapes)) for index, path := range shapes { cloned[index] = path.clone() } return cloned } func (model *historyModel) observedPaths() []Path { if model == nil { return nil } paths := make([]Path, len(model.shapeOrder)) for index, path := range model.shapeOrder { paths[index] = path.clone() } return paths } func (model *historyModel) historicalPaths() []Path { if model == nil { return []Path{} } pointers := make([]string, 0, len(model.shapesByPointer)) for pointer := range model.shapesByPointer { pointers = append(pointers, pointer) } sort.Strings(pointers) paths := make([]Path, 0, len(pointers)) for _, pointer := range pointers { path, err := ParsePath(pointer) if err == nil { paths = append(paths, path) } } return paths } func (model *historyModel) latestHistoricalLocation(path Path) (Location, bool) { selections := model.selectHistory(path) for selectionIndex := len(selections) - 1; selectionIndex >= 0; selectionIndex-- { selection := selections[selectionIndex] if selection.hasAttribution { if location, found := latestOriginDocumentLocation(selection.attribution.origin); found { return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true } } for previousIndex := len(selection.previous) - 1; previousIndex >= 0; previousIndex-- { if location, found := latestOriginDocumentLocation(selection.previous[previousIndex]); found { return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true } } } return Location{}, false } func (model *historyModel) sensitivityCandidates() []historySensitivityCandidate { if model == nil { return nil } candidates := make([]historySensitivityCandidate, 0, len(model.observations)) for _, observation := range model.observations { candidate := historySensitivityCandidate{path: observation.path.clone(), sensitive: observation.sensitive} if attribution, found := model.resolveAttribution(observation.attributionRef); found { candidate.dependencies = attribution.dependencies } candidates = append(candidates, candidate) } return candidates } func (model *historyModel) resolveAttribution(ref historyAttributionRef) (historyAttribution, bool) { if model == nil || ref < 0 || int(ref) >= len(model.attributions) { return historyAttribution{}, false } attribution := model.attributions[ref] return historyAttribution{ origin: cloneOrigin(attribution.origin), reasons: append([]string(nil), attribution.reasons...), dependencies: clonePaths(attribution.dependencies), }, true } func metadataHistoryRole(role historyRole) bool { switch role { case historyRoleDefinition, historyRoleCompositeDefinition, historyRoleOperation, historyRoleControl: return true default: return false } }