99
1010namespace ArchUnitNET . Loader
1111{
12+ /// <summary>
13+ /// Internal builder that constructs an <see cref="Architecture"/> from loaded modules.
14+ /// Coordinates type discovery (via <see cref="DomainResolver"/>), type processing
15+ /// (via the <c>LoadTasks</c> static classes), and architecture assembly. Manages the
16+ /// <see cref="ArchitectureCache"/> lookup.
17+ /// </summary>
1218 internal class ArchBuilder
1319 {
1420 private readonly ArchitectureCache _architectureCache ;
1521 private readonly ArchitectureCacheKey _architectureCacheKey ;
16- private readonly IDictionary < string , IType > _architectureTypes =
17- new Dictionary < string , IType > ( ) ;
18- private readonly LoadTaskRegistry _loadTaskRegistry ;
1922 private readonly DomainResolver _domainResolver ;
2023
24+ /// <summary>
25+ /// Non-compiler-generated types paired with their Cecil <see cref="TypeDefinition"/>s,
26+ /// collected during <see cref="LoadTypesForModule"/> and consumed by
27+ /// <see cref="ProcessTypes"/> to populate members, dependencies, and attributes.
28+ /// Keyed by assembly-qualified name for deduplication.
29+ /// </summary>
30+ private readonly Dictionary <
31+ string ,
32+ ( ITypeInstance < IType > TypeInstance , TypeDefinition Definition )
33+ > _typesToProcess =
34+ new Dictionary <
35+ string ,
36+ ( ITypeInstance < IType > TypeInstance , TypeDefinition Definition )
37+ > ( ) ;
38+
39+ /// <summary>
40+ /// Assemblies paired with their Cecil <see cref="AssemblyDefinition"/>s,
41+ /// registered via <see cref="AddAssembly"/>. Keyed by assembly full name
42+ /// for deduplication. Consumed by Phase 5 (assembly attribute collection).
43+ /// </summary>
44+ private readonly Dictionary <
45+ string ,
46+ ( Assembly Assembly , AssemblyDefinition Definition )
47+ > _assemblyData =
48+ new Dictionary < string , ( Assembly Assembly , AssemblyDefinition Definition ) > ( ) ;
49+
2150 public ArchBuilder ( )
2251 {
23- _loadTaskRegistry = new LoadTaskRegistry ( ) ;
24- _domainResolver = new DomainResolver (
25- _loadTaskRegistry
26- ) ;
52+ _domainResolver = new DomainResolver ( ) ;
2753 _architectureCacheKey = new ArchitectureCacheKey ( ) ;
2854 _architectureCache = ArchitectureCache . Instance ;
2955 }
3056
31- public IEnumerable < IType > Types => _architectureTypes . Values ;
32- public IEnumerable < Assembly > Assemblies => _domainResolver . Assemblies ;
33- public IEnumerable < Namespace > Namespaces => _domainResolver . Namespaces ;
34-
57+ /// <summary>
58+ /// Registers an assembly for attribute collection during <see cref="ProcessTypes"/>.
59+ /// Skips assemblies that have already been registered.
60+ /// </summary>
3561 public void AddAssembly ( [ NotNull ] AssemblyDefinition moduleAssembly , bool isOnlyReferenced )
3662 {
63+ if ( _assemblyData . ContainsKey ( moduleAssembly . FullName ) )
64+ {
65+ return ;
66+ }
67+
3768 var references = moduleAssembly
3869 . MainModule . AssemblyReferences . Select ( reference => reference . Name )
3970 . ToList ( ) ;
4071
41- if ( ! _domainResolver . ContainsAssembly ( moduleAssembly . FullName ) )
42- {
43- var assembly = _domainResolver . GetOrCreateAssembly (
44- moduleAssembly . Name . Name ,
45- moduleAssembly . FullName ,
46- isOnlyReferenced ,
47- references
48- ) ;
49- _loadTaskRegistry . Add (
50- typeof ( CollectAssemblyAttributes ) ,
51- new CollectAssemblyAttributes ( assembly , moduleAssembly , _domainResolver )
52- ) ;
53- }
72+ var assembly = _domainResolver . GetOrCreateAssembly (
73+ moduleAssembly . Name . Name ,
74+ moduleAssembly . FullName ,
75+ isOnlyReferenced ,
76+ references
77+ ) ;
78+ _assemblyData . Add ( moduleAssembly . FullName , ( assembly , moduleAssembly ) ) ;
5479 }
5580
81+ /// <summary>
82+ /// Discovers types in the given <paramref name="module"/>, creates domain type instances
83+ /// via <see cref="DomainResolver"/>, and records them for later processing.
84+ /// Filters out compiler-generated types, code-coverage instrumentation types,
85+ /// nullable context attributes, and types outside the optional
86+ /// <paramref name="namespaceFilter"/>.
87+ /// </summary>
5688 public void LoadTypesForModule ( ModuleDefinition module , string namespaceFilter )
5789 {
5890 _architectureCacheKey . Add ( module . Name , namespaceFilter ) ;
@@ -81,7 +113,6 @@ public void LoadTypesForModule(ModuleDefinition module, string namespaceFilter)
81113 types . AddRange ( nestedTypes ) ;
82114 }
83115
84- var currentTypes = new List < IType > ( types . Count ) ;
85116 types
86117 . Where ( typeDefinition =>
87118 (
@@ -95,46 +126,28 @@ public void LoadTypesForModule(ModuleDefinition module, string namespaceFilter)
95126 )
96127 . ForEach ( typeDefinition =>
97128 {
98- var type = _domainResolver . GetOrCreateTypeFromTypeReference ( typeDefinition ) ;
129+ var typeInstance = _domainResolver . GetOrCreateTypeInstanceFromTypeReference (
130+ typeDefinition
131+ ) ;
132+ var type = typeInstance . Type ;
99133 var assemblyQualifiedName = System . Reflection . Assembly . CreateQualifiedName (
100134 module . Assembly . Name . Name ,
101135 typeDefinition . FullName
102136 ) ;
103137 if (
104- ! _architectureTypes . ContainsKey ( assemblyQualifiedName )
138+ ! _typesToProcess . ContainsKey ( assemblyQualifiedName )
105139 && ! type . IsCompilerGenerated
106140 )
107141 {
108- currentTypes . Add ( type ) ;
109- _architectureTypes . Add ( assemblyQualifiedName , type ) ;
142+ _typesToProcess . Add ( assemblyQualifiedName , ( typeInstance , typeDefinition ) ) ;
110143 }
111144 } ) ;
112-
113- _loadTaskRegistry . Add (
114- typeof ( AddTypesToNamespaces ) ,
115- new AddTypesToNamespaces ( currentTypes )
116- ) ;
117- }
118-
119- private void UpdateTypeDefinitions ( )
120- {
121- _loadTaskRegistry . ExecuteTasks (
122- new List < System . Type >
123- {
124- typeof ( AddMembers ) ,
125- typeof ( AddGenericParameterDependencies ) ,
126- typeof ( AddAttributesAndAttributeDependencies ) ,
127- typeof ( CollectAssemblyAttributes ) ,
128- typeof ( AddFieldAndPropertyDependencies ) ,
129- typeof ( AddMethodDependencies ) ,
130- typeof ( AddGenericArgumentDependencies ) ,
131- typeof ( AddClassDependencies ) ,
132- typeof ( AddBackwardsDependencies ) ,
133- typeof ( AddTypesToNamespaces ) ,
134- }
135- ) ;
136145 }
137146
147+ /// <summary>
148+ /// Builds the <see cref="Architecture"/> from all loaded modules. Returns a cached
149+ /// instance when available.
150+ /// </summary>
138151 public Architecture Build ( )
139152 {
140153 var architecture = _architectureCache . TryGetArchitecture ( _architectureCacheKey ) ;
@@ -143,20 +156,144 @@ public Architecture Build()
143156 return architecture ;
144157 }
145158
146- UpdateTypeDefinitions ( ) ;
147- var allTypes = _domainResolver . GetAllNonCompilerGeneratedTypes ( ) . ToList ( ) ;
159+ ProcessTypes ( ) ;
160+
161+ var allTypes = _domainResolver
162+ . Types . Select ( instance => instance . Type )
163+ . Where ( type => ! type . IsCompilerGenerated )
164+ . Distinct ( )
165+ . ToList ( ) ;
166+ var types = allTypes
167+ . Where ( type => ! type . IsStub && ! ( type is GenericParameter ) )
168+ . ToList ( ) ;
148169 var genericParameters = allTypes . OfType < GenericParameter > ( ) . ToList ( ) ;
149- var referencedTypes = allTypes . Except ( Types ) . Except ( genericParameters ) ;
150- var namespaces = Namespaces . Where ( ns => ns . Types . Any ( ) ) ;
170+ var referencedTypes = allTypes
171+ . Where ( type => type . IsStub && ! ( type is GenericParameter ) )
172+ . ToList ( ) ;
173+ var namespaces = _domainResolver . Namespaces . Where ( ns => ns . Types . Any ( ) ) ;
151174 var newArchitecture = new Architecture (
152- Assemblies ,
175+ _domainResolver . Assemblies ,
153176 namespaces ,
154- Types ,
177+ types ,
155178 genericParameters ,
156179 referencedTypes
157180 ) ;
181+
158182 _architectureCache . Add ( _architectureCacheKey , newArchitecture ) ;
183+
159184 return newArchitecture ;
160185 }
186+
187+ /// <summary>
188+ /// Runs all type-processing phases in the required order across every discovered type.
189+ /// Each phase must complete for all types before the next phase begins, because later
190+ /// phases depend on data populated by earlier ones (e.g. members must exist before
191+ /// method dependencies can be resolved).
192+ /// </summary>
193+ private void ProcessTypes ( )
194+ {
195+ var typesToProcess = _typesToProcess . Values ;
196+
197+ // Phase 1: Base class dependency (non-interface types only)
198+ foreach ( var entry in typesToProcess . Where ( entry => ! entry . Definition . IsInterface ) )
199+ {
200+ AddBaseClassDependency . Execute (
201+ entry . TypeInstance . Type ,
202+ entry . Definition ,
203+ _domainResolver
204+ ) ;
205+ }
206+
207+ // Phase 2: Members (fields, properties, methods)
208+ // Collect (TypeInstance, Definition, MemberData) for use in Phases 4 and 7.
209+ var typesWithMemberData = new List < (
210+ ITypeInstance < IType > TypeInstance ,
211+ TypeDefinition TypeDef ,
212+ MemberData MemberData
213+ ) > ( _typesToProcess . Count ) ;
214+ typesWithMemberData . AddRange (
215+ from entry in typesToProcess
216+ let memberData = AddMembers . Execute (
217+ entry . TypeInstance ,
218+ entry . Definition ,
219+ _domainResolver
220+ )
221+ select ( entry . TypeInstance , entry . Definition , memberData )
222+ ) ;
223+
224+ // Phase 3: Generic parameter dependencies
225+ foreach ( var entry in typesToProcess )
226+ {
227+ AddGenericParameterDependencies . Execute ( entry . TypeInstance . Type ) ;
228+ }
229+
230+ // Phase 4: Attributes and attribute dependencies
231+ foreach ( var entry in typesWithMemberData )
232+ {
233+ AddAttributesAndAttributeDependencies . Execute (
234+ entry . TypeInstance . Type ,
235+ entry . TypeDef ,
236+ _domainResolver ,
237+ entry . MemberData . MethodPairs
238+ ) ;
239+ }
240+
241+ // Phase 5: Assembly-level attributes
242+ // Materialized to a list because CollectAssemblyAttributes can trigger
243+ // GetOrCreateAssembly in DomainResolver, which would invalidate a lazy query.
244+ var assemblyData = _assemblyData . Values . ToList ( ) ;
245+ foreach ( var entry in assemblyData )
246+ {
247+ CollectAssemblyAttributes . Execute (
248+ entry . Assembly ,
249+ entry . Definition ,
250+ _domainResolver
251+ ) ;
252+ }
253+
254+ // Phase 6: Field and property type dependencies
255+ foreach ( var entry in typesToProcess )
256+ {
257+ AddFieldAndPropertyDependencies . Execute ( entry . TypeInstance . Type ) ;
258+ }
259+
260+ // Phase 7: Method signature and body dependencies
261+ foreach ( var entry in typesWithMemberData )
262+ {
263+ AddMethodDependencies . Execute (
264+ entry . TypeInstance . Type ,
265+ _domainResolver ,
266+ entry . MemberData . MethodPairs ,
267+ entry . MemberData . PropertyByAccessor
268+ ) ;
269+ }
270+
271+ // Phase 8: Generic argument dependencies
272+ foreach ( var entry in typesToProcess )
273+ {
274+ AddGenericArgumentDependencies . Execute ( entry . TypeInstance . Type ) ;
275+ }
276+
277+ // Phase 9: Interface and member-to-type dependencies
278+ foreach ( var entry in typesToProcess )
279+ {
280+ AddClassDependencies . Execute (
281+ entry . TypeInstance . Type ,
282+ entry . Definition ,
283+ _domainResolver
284+ ) ;
285+ }
286+
287+ // Phase 10: Backwards dependencies
288+ foreach ( var entry in typesToProcess )
289+ {
290+ AddBackwardsDependencies . Execute ( entry . TypeInstance . Type ) ;
291+ }
292+
293+ // Phase 11: Register types with their namespaces
294+ AddTypesToNamespaces . Execute (
295+ _typesToProcess . Values . Select ( entry => entry . TypeInstance . Type )
296+ ) ;
297+ }
161298 }
162299}
0 commit comments