This repository has been archived by the owner on Oct 12, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Omit types that aren't referred to from a resource (#170)
* Add StripUnusedDefinitions pass to CodeGenerator.Generate Update expected output to remove unreferenced types in the expected output for AllOf_generates_wrapper_type * Add Referees method to Type and impls The name is awkward - it might be better to replace References with this. * Implement collecting referrers (I think) * Implement the connectedness check If a definition isn't connected by a series of references to any root definition (ie, a resource) then it should be filtered out. Memoised to save checking a type name twice. * Correct implementation of TypeName.Referees Originally implementation was based on a misunderstanding of how the different astmodel types worked. * Strip unused definitions in jsonast golden tests This required moving StripUnusedDefinitions out of pkg/codegen - I put it into pkg/astmodel because that's where all of the types it interacts with are. Also enabled passing in a different set of roots, since the golden tests use the Test type (which isn't a resource) as the root. * Update Type.References to return a TypeNameSet, remove Referees Returning a set makes the old behaviour simple to implement in the few places that used it. Adds a SetUnion to combine two TypeNameSets. I thought about making this a method to add all of the elements of another set to the receiver - it would be more efficient but I think it's less clean. * Don't memoise negative checkWithPath results These can come from a search that is unwinding because it detected a cycle, rather than one that didn't find a root. In that case we can't know whether a search from this node with a different path would succeed. Added a unit test to explore this problem. Made the TypeNameSet methods take TypeNames rather than *TypeNames - this simplified them a bit (at the cost of having to do some extra nil checks at the edges). * Corrected grammar on doc comments * Simplify connectedness-check thanks to a suggestion from Matt Rather than walking up the references until we find a root, walk down the references from the roots collecting up all of the types we reach. This is much simpler and doesn't need any memoisation (since the accumulated set replaces it). * Update Function.References to match Type.References * Remove unnecessary nil checks These *TypeNames can't be nil - there are a lot of earlier points in the code that do no-look dereferences.
- Loading branch information
1 parent
1794f83
commit f39f7c9
Showing
16 changed files
with
231 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package astmodel | ||
|
||
// TypeNameSet stores type names in no particular order without | ||
// duplicates. | ||
type TypeNameSet map[TypeName]struct{} | ||
|
||
// NewTypeNameSet makes a TypeNameSet containing the specified | ||
// names. If no elements are passed it might be nil. | ||
func NewTypeNameSet(initial ...TypeName) TypeNameSet { | ||
var result TypeNameSet | ||
for _, name := range initial { | ||
result = result.Add(name) | ||
} | ||
return result | ||
} | ||
|
||
// Add includes the passed name in the set and returns the updated | ||
// set, so that adding can work for a nil set - this makes it more | ||
// convenient to add to sets kept in a map (in the way you might with | ||
// a map of slices). | ||
func (ts TypeNameSet) Add(val TypeName) TypeNameSet { | ||
if ts == nil { | ||
ts = make(TypeNameSet) | ||
} | ||
ts[val] = struct{}{} | ||
return ts | ||
} | ||
|
||
// Contains returns whether this name is in the set. Works for nil | ||
// sets too. | ||
func (ts TypeNameSet) Contains(val TypeName) bool { | ||
if ts == nil { | ||
return false | ||
} | ||
_, found := ts[val] | ||
return found | ||
} | ||
|
||
// SetUnion returns a new set with all of the names in s1 or s2. | ||
func SetUnion(s1, s2 TypeNameSet) TypeNameSet { | ||
var result TypeNameSet | ||
for val := range s1 { | ||
result = result.Add(val) | ||
} | ||
for val := range s2 { | ||
result = result.Add(val) | ||
} | ||
return result | ||
} | ||
|
||
// StripUnusedDefinitions removes all types that aren't in roots or | ||
// referred to by the types in roots, for example types that are | ||
// generated as a byproduct of an allOf element. | ||
func StripUnusedDefinitions( | ||
roots TypeNameSet, | ||
definitions []TypeDefiner, | ||
) ([]TypeDefiner, error) { | ||
// Collect all the reference sets for each type. | ||
references := make(map[TypeName]TypeNameSet) | ||
|
||
for _, def := range definitions { | ||
references[*def.Name()] = def.Type().References() | ||
} | ||
|
||
graph := newReferenceGraph(roots, references) | ||
connectedTypes := graph.connected() | ||
var usedDefinitions []TypeDefiner | ||
for _, def := range definitions { | ||
if connectedTypes.Contains(*def.Name()) { | ||
usedDefinitions = append(usedDefinitions, def) | ||
} | ||
} | ||
return usedDefinitions, nil | ||
} | ||
|
||
// CollectResourceDefinitions returns a TypeNameSet of all of the | ||
// resource definitions in the definitions passed in. | ||
func CollectResourceDefinitions(definitions []TypeDefiner) TypeNameSet { | ||
resources := make(TypeNameSet) | ||
for _, def := range definitions { | ||
if _, ok := def.(*ResourceDefinition); ok { | ||
resources.Add(*def.Name()) | ||
} | ||
} | ||
return resources | ||
} | ||
|
||
func newReferenceGraph(roots TypeNameSet, references map[TypeName]TypeNameSet) *referenceGraph { | ||
return &referenceGraph{ | ||
roots: roots, | ||
references: references, | ||
} | ||
} | ||
|
||
type referenceGraph struct { | ||
roots TypeNameSet | ||
references map[TypeName]TypeNameSet | ||
} | ||
|
||
// connected returns the set of types that are reachable from the | ||
// roots. | ||
func (c referenceGraph) connected() TypeNameSet { | ||
// Make a non-nil set so we don't need to worry about passing it back down. | ||
connectedTypes := make(TypeNameSet) | ||
for node := range c.roots { | ||
c.collectTypes(node, connectedTypes) | ||
} | ||
return connectedTypes | ||
} | ||
|
||
func (c referenceGraph) collectTypes(node TypeName, collected TypeNameSet) { | ||
if collected.Contains(node) { | ||
// We can stop here - we've already visited this node. | ||
return | ||
} | ||
collected.Add(node) | ||
for child := range c.references[node] { | ||
c.collectTypes(child, collected) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
*/ | ||
|
||
package astmodel | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
) | ||
|
||
const packagePath = "test.package/v1" | ||
|
||
func TestConnectionChecker_Avoids_Cycles(t *testing.T) { | ||
g := NewGomegaWithT(t) | ||
makeName := func(name string) TypeName { | ||
return *NewTypeName(*NewPackageReference(packagePath), name) | ||
} | ||
|
||
makeSet := func(names ...string) TypeNameSet { | ||
var typeNames []TypeName | ||
for _, n := range names { | ||
typeNames = append(typeNames, makeName(n)) | ||
} | ||
return NewTypeNameSet(typeNames...) | ||
} | ||
|
||
roots := makeSet("res1", "res2") | ||
references := map[TypeName]TypeNameSet{ | ||
makeName("G1"): makeSet("G2"), | ||
makeName("G2"): makeSet("A"), | ||
makeName("res1"): makeSet("A"), | ||
makeName("A"): makeSet("B", "C"), | ||
makeName("B"): nil, | ||
makeName("C"): makeSet("D"), | ||
makeName("D"): makeSet("A"), // cyclic | ||
} | ||
|
||
graph := newReferenceGraph(roots, references) | ||
connectedSet := graph.connected() | ||
|
||
g.Expect(connectedSet).To(Equal(makeSet("res1", "res2", "A", "B", "C", "D"))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters