Retain import inclusion graph during acquisition
This commit is contained in:
@@ -35,6 +35,7 @@ func defaultImportLimits() importLimits {
|
||||
type importRoute struct {
|
||||
canonical string
|
||||
locations []Location
|
||||
key importNodeKey
|
||||
}
|
||||
|
||||
type importAccounting struct {
|
||||
@@ -59,6 +60,7 @@ type importTraversal struct {
|
||||
active map[resourceIdentity]int
|
||||
visited map[resourceIdentity]importRoute
|
||||
stack []importRoute
|
||||
importGraph importInclusionGraph
|
||||
}
|
||||
|
||||
func loadImportedDocuments(source Source, context *FilesystemContext, main parsedSource, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
|
||||
@@ -139,11 +141,12 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
if mainPath != "" {
|
||||
mainInfo.Path = mainPath
|
||||
}
|
||||
mainKey := access.identity(mainPath)
|
||||
mainResource := access.identity(mainPath)
|
||||
mainNodeKey := importKeyFor(access, mainPath, mainPath == "")
|
||||
if mainPath == "" {
|
||||
mainKey = access.syntheticIdentity()
|
||||
mainResource = access.syntheticIdentity()
|
||||
}
|
||||
if previous, repeated := visited[mainKey]; repeated {
|
||||
if previous, repeated := visited[mainResource]; repeated {
|
||||
_ = access.close()
|
||||
return nil, sourceInfos(main.source), []Diagnostic{importRepeatedDiagnostic(Location{SourceID: main.source.ID}, previous, importRoute{})}
|
||||
}
|
||||
@@ -152,9 +155,10 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
accounting.nodes += countInputNodes(main.document)
|
||||
state := importTraversal{
|
||||
access: access, accounting: accounting, limits: limits, rule: rule, versionState: versionState,
|
||||
scope: acquisitionScope{destination: destination.clone()},
|
||||
scope: acquisitionScope{owner: mainNodeKey, destination: destination.clone()},
|
||||
sources: []SourceInfo{mainInfo},
|
||||
active: make(map[resourceIdentity]int), visited: visited,
|
||||
importGraph: newImportInclusionGraph(),
|
||||
}
|
||||
if mainIsItem {
|
||||
state.itemScope = true
|
||||
@@ -176,8 +180,10 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
mainIdentity = "<selected-source>"
|
||||
}
|
||||
mainRoute := importRoute{canonical: mainIdentity}
|
||||
state.active[mainKey] = 0
|
||||
state.visited[mainKey] = mainRoute
|
||||
state.importGraph.addNode(importGraphNode{key: mainNodeKey, canonical: mainIdentity, sourceID: mainInfo.ID, role: importRoleMain})
|
||||
mainRoute.key = mainNodeKey
|
||||
state.active[mainResource] = 0
|
||||
state.visited[mainResource] = mainRoute
|
||||
state.stack = append(state.stack, mainRoute)
|
||||
var body *document.Node
|
||||
var declarations []importDeclaration
|
||||
@@ -191,7 +197,8 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
if len(diagnostics) == 0 {
|
||||
if mainIsItem {
|
||||
state.scope.contributions = append(state.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(mainInfo), payload: body, document: body, processing: processing,
|
||||
source: cloneSourceInfo(mainInfo), identity: mainNodeKey, role: importRoleMain,
|
||||
scopeOwner: mainNodeKey, payload: body, document: body, processing: processing,
|
||||
})
|
||||
diagnostics = state.walkDeclarations(basePath, declarations, 0, Path{})
|
||||
} else {
|
||||
@@ -205,12 +212,16 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
accounting.nodes += placementNodes
|
||||
state.scope.contributions = append(state.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(mainInfo), payload: body, document: placed,
|
||||
identity: mainNodeKey, role: importRoleMain, scopeOwner: mainNodeKey,
|
||||
destination: destination.clone(), processing: processing,
|
||||
})
|
||||
diagnostics = state.walkDeclarations(basePath, declarations, 0, destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = state.composePendingItems(&state.scope)
|
||||
}
|
||||
|
||||
closeErr := access.close()
|
||||
if len(diagnostics) != 0 {
|
||||
@@ -267,13 +278,16 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
if composed != nil {
|
||||
synthetic := acquisitionScope{contributions: []acquiredContribution{{
|
||||
source: cloneSourceInfo(mainInfo), payload: completed.root, document: placed,
|
||||
identity: mainNodeKey, role: importRoleMain, scopeOwner: mainNodeKey,
|
||||
destination: destination.clone(), processing: processing, nested: completed,
|
||||
}}}
|
||||
synthetic.owner = mainNodeKey
|
||||
projectNestedEvidence(composed, synthetic)
|
||||
projectAcquiredDestinations(composed, synthetic, state.sources)
|
||||
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(synthetic)}
|
||||
composed.inputBytes = accounting.bytes
|
||||
composed.inputNodes = accounting.nodes
|
||||
composed.importGraph = state.importGraph.clone()
|
||||
}
|
||||
return composed, cloneSourceInfos(state.sources), failures
|
||||
}
|
||||
@@ -293,6 +307,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(state.scope)}
|
||||
composed.inputBytes = accounting.bytes
|
||||
composed.inputNodes = accounting.nodes
|
||||
composed.importGraph = state.importGraph.clone()
|
||||
}
|
||||
return composed, cloneSourceInfos(state.sources), diagnostics
|
||||
}
|
||||
@@ -386,11 +401,18 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
|
||||
currentRoute := s.currentRoute(location)
|
||||
identity := s.access.identity(canonical)
|
||||
nodeKey := importKeyFor(s.access, canonical, false)
|
||||
importerKey := s.currentImportNode()
|
||||
if activeIndex, ok := s.active[identity]; ok {
|
||||
closingEdge := s.importGraph.addEdge(importerKey, nodeKey, location)
|
||||
if diagnostic, ok := s.importGraph.scheduleCycle(closingEdge, location); ok {
|
||||
return []Diagnostic{diagnostic}
|
||||
}
|
||||
cycle := append([]importRoute(nil), s.stack[activeIndex:]...)
|
||||
return []Diagnostic{importCycleDiagnostic(canonical, location, cycle)}
|
||||
}
|
||||
if previous, ok := s.visited[identity]; ok {
|
||||
s.importGraph.addEdge(importerKey, nodeKey, location)
|
||||
return []Diagnostic{importRepeatedDiagnostic(location, previous, currentRoute)}
|
||||
}
|
||||
if s.accounting.sources+1 > s.limits.sources {
|
||||
@@ -467,12 +489,18 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
if declaration.placement == placementModeSequenceItem {
|
||||
mode = placementModeSequenceItem
|
||||
}
|
||||
role := importRoleMapping
|
||||
if mode == placementModeSequenceItem {
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations}
|
||||
role = importRoleItem
|
||||
}
|
||||
s.importGraph.addNode(importGraphNode{key: nodeKey, canonical: canonical, sourceID: sourceID, role: role})
|
||||
s.importGraph.addEdge(importerKey, nodeKey, location)
|
||||
if mode == placementModeSequenceItem {
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
|
||||
s.active[identity] = len(s.stack)
|
||||
s.visited[identity] = route
|
||||
s.stack = append(s.stack, route)
|
||||
diagnostics := s.visitItemSource(canonical, declaration, destination, relativeDestination, depth, info, body, declarations, processing)
|
||||
diagnostics := s.visitItemSource(canonical, nodeKey, declaration, destination, relativeDestination, depth, info, body, declarations, processing)
|
||||
delete(s.active, identity)
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
return diagnostics
|
||||
@@ -486,9 +514,10 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations}
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: placed,
|
||||
identity: nodeKey, role: role, scopeOwner: s.scope.owner,
|
||||
destination: placementDestination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
})
|
||||
s.active[identity] = len(s.stack)
|
||||
@@ -500,6 +529,17 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func (s *importTraversal) currentImportNode() importNodeKey {
|
||||
if len(s.stack) != 0 {
|
||||
return s.stack[len(s.stack)-1].key
|
||||
}
|
||||
key := s.scope.owner
|
||||
if _, exists := s.importGraph.index[key]; !exists {
|
||||
s.importGraph.addNode(importGraphNode{key: key, canonical: "<selected-source>", role: importRoleMain})
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (s *importTraversal) sourceDestination(destination Path) Path {
|
||||
if !s.itemScope {
|
||||
return destination.clone()
|
||||
@@ -509,10 +549,10 @@ func (s *importTraversal) sourceDestination(destination Path) Path {
|
||||
return s.itemFallback.clone()
|
||||
}
|
||||
|
||||
func (s *importTraversal) visitItemSource(canonical string, declaration importDeclaration, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclaration, processing *sourceVersionProcessingResult) []Diagnostic {
|
||||
func (s *importTraversal) visitItemSource(canonical string, identity importNodeKey, declaration importDeclaration, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclaration, processing *sourceVersionProcessingResult) []Diagnostic {
|
||||
parentScope := s.scope
|
||||
parentItemScope, parentPrefix, parentFallback := s.itemScope, s.itemPrefix, s.itemFallback
|
||||
s.scope = acquisitionScope{}
|
||||
s.scope = acquisitionScope{owner: identity}
|
||||
s.itemScope = true
|
||||
if parentItemScope {
|
||||
s.itemPrefix = appendDestination(parentPrefix, destination)
|
||||
@@ -522,6 +562,7 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
|
||||
}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: body,
|
||||
identity: identity, role: importRoleItem, scopeOwner: identity,
|
||||
processing: processing,
|
||||
})
|
||||
diagnostics := s.walkDeclarations(s.access.dir(canonical), declarations, depth, Path{})
|
||||
@@ -530,35 +571,53 @@ func (s *importTraversal) visitItemSource(canonical string, declaration importDe
|
||||
if len(diagnostics) != 0 {
|
||||
return projectItemDiagnosticPaths(diagnostics, parentScope, destination)
|
||||
}
|
||||
documents := make([]*document.Node, 0, len(itemScope.contributions))
|
||||
for _, contribution := range itemScope.contributions {
|
||||
documents = append(documents, contribution.document)
|
||||
}
|
||||
completed, diagnostics := composeDocuments(documents...)
|
||||
if len(diagnostics) != 0 {
|
||||
return projectItemDiagnosticPaths(diagnostics, parentScope, destination)
|
||||
}
|
||||
itemScope.destination = Path{}
|
||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(itemScope)}
|
||||
projectNestedEvidence(completed, itemScope)
|
||||
pl := destination
|
||||
_, placed, err := placeDocumentWithMode(completed.root, pl, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &declaration.location)}
|
||||
}
|
||||
placementNodes := destinationWrapperNodeCountForMode(pl, placementModeSequenceItem)
|
||||
if s.accounting.nodes+placementNodes > s.limits.nodes {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &declaration.location)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: completed.root, document: placed,
|
||||
destination: pl.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
nested: completed,
|
||||
source: cloneSourceInfo(info), payload: body,
|
||||
identity: identity, role: importRoleItem, scopeOwner: parentScope.owner,
|
||||
destination: destination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
pendingItem: &itemScope,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnostic {
|
||||
for index := range scope.contributions {
|
||||
contribution := &scope.contributions[index]
|
||||
if contribution.pendingItem == nil {
|
||||
continue
|
||||
}
|
||||
itemScope := contribution.pendingItem
|
||||
if diagnostics := s.composePendingItems(itemScope); len(diagnostics) != 0 {
|
||||
return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination)
|
||||
}
|
||||
documents := make([]*document.Node, 0, len(itemScope.contributions))
|
||||
for _, nested := range itemScope.contributions {
|
||||
documents = append(documents, nested.document)
|
||||
}
|
||||
completed, diagnostics := composeDocuments(documents...)
|
||||
if len(diagnostics) != 0 {
|
||||
return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination)
|
||||
}
|
||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)}
|
||||
projectNestedEvidence(completed, *itemScope)
|
||||
_, placed, err := placeDocumentWithMode(completed.root, contribution.destination, placementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)}
|
||||
}
|
||||
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem)
|
||||
if s.accounting.nodes+placementNodes > s.limits.nodes {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)}
|
||||
}
|
||||
s.accounting.nodes += placementNodes
|
||||
contribution.payload = completed.root
|
||||
contribution.document = placed
|
||||
contribution.nested = completed
|
||||
contribution.pendingItem = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importedVersionClaim(documents []*document.Node, composed *composedDocument, rule *VersionRule, mainIndex int) *Diagnostic {
|
||||
if composed == nil || rule == nil {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user