Skip to content

Commit 9484dd4

Browse files
refactor: convert load tasks to static classes and remove LoadTaskRegistry
Replace ILoadTask interface and LoadTaskRegistry with static Execute methods on each load task class. Move type processing orchestration from DomainResolver into ArchBuilder.ProcessTypes() with explicit phased execution. Remove RegexUtils (property matching now uses dictionary lookup in AddMethodDependencies). Remove LoadBaseTask/LoadNonBaseTasks from DomainResolver since type processing is now driven externally by ArchBuilder. Remove RegexUtils tests (only BackingFieldExamples test fixture class retained). Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>
1 parent 6bf3332 commit 9484dd4

18 files changed

Lines changed: 703 additions & 927 deletions

ArchUnitNET/Loader/ArchBuilder.cs

Lines changed: 196 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,82 @@
99

1010
namespace 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

Comments
 (0)