Skip to content

Commit 7af054a

Browse files
bouwkastclaude
andcommitted
Extract Datadog.AutoInstrumentation.Generator.Core shared library
Extract the generation engine, configuration models, and resource templates from the GUI into a standalone Core library. The GUI now delegates to Core, reducing MainViewModel.Editor.cs from ~300 lines of inline logic to ~109 lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f05ab32 commit 7af054a

20 files changed

+764
-333
lines changed

Datadog.Trace.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datadog.FeatureFlags.OpenFe
637637
EndProject
638638
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.LifetimeManager.TerminationSignals", "tracer\test\test-applications\integrations\Samples.LifetimeManager.TerminationSignals\Samples.LifetimeManager.TerminationSignals.csproj", "{0E3E1069-80FF-99C9-D29F-936D96D5F516}"
639639
EndProject
640+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datadog.AutoInstrumentation.Generator.Core", "tracer\src\Datadog.AutoInstrumentation.Generator.Core\Datadog.AutoInstrumentation.Generator.Core.csproj", "{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}"
641+
EndProject
640642
Global
641643
GlobalSection(SolutionConfigurationPlatforms) = preSolution
642644
Debug|Any CPU = Debug|Any CPU
@@ -1549,6 +1551,10 @@ Global
15491551
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Debug|Any CPU.Build.0 = Debug|Any CPU
15501552
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Release|Any CPU.ActiveCfg = Release|Any CPU
15511553
{0E3E1069-80FF-99C9-D29F-936D96D5F516}.Release|Any CPU.Build.0 = Release|Any CPU
1554+
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1555+
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Debug|Any CPU.Build.0 = Debug|Any CPU
1556+
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Release|Any CPU.ActiveCfg = Release|Any CPU
1557+
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22}.Release|Any CPU.Build.0 = Release|Any CPU
15521558
EndGlobalSection
15531559
GlobalSection(SolutionProperties) = preSolution
15541560
HideSolutionNode = FALSE
@@ -1801,6 +1807,7 @@ Global
18011807
{64258238-DE63-4A0F-A618-DF51735BA22A} = {8CEC2042-F11C-49F5-A674-2355793B600A}
18021808
{CFECF8D4-3A46-35A8-7CB1-BA359974A1A9} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
18031809
{0E3E1069-80FF-99C9-D29F-936D96D5F516} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
1810+
{B3A07CA3-3536-4D43-81A3-A5A73FB76F22} = {9E5F0022-0A50-40BF-AC6A-C3078585ECAB}
18041811
EndGlobalSection
18051812
GlobalSection(ExtensibilityGlobals) = postSolution
18061813
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// <copyright file="AssemblyBrowser.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Linq;
10+
using dnlib.DotNet;
11+
12+
namespace Datadog.AutoInstrumentation.Generator.Core;
13+
14+
/// <summary>
15+
/// Thin wrapper around dnlib for resolving methods from assemblies.
16+
/// Assembly browsing (type listing, etc.) is handled by dotnet-inspect.
17+
/// </summary>
18+
public class AssemblyBrowser : IDisposable
19+
{
20+
private readonly AssemblyDef _assemblyDef;
21+
22+
public AssemblyBrowser(string path)
23+
{
24+
_assemblyDef = AssemblyDef.Load(path);
25+
}
26+
27+
public AssemblyBrowser(Stream stream)
28+
{
29+
_assemblyDef = AssemblyDef.Load(stream);
30+
}
31+
32+
/// <summary>
33+
/// Resolves a method by type full name and method name.
34+
/// Supports overload disambiguation via parameter types or overload index.
35+
/// </summary>
36+
/// <param name="typeFullName">Fully qualified type name (e.g., "MyLib.MyClass")</param>
37+
/// <param name="methodName">Method name</param>
38+
/// <param name="parameterTypes">Optional parameter type full names for overload disambiguation</param>
39+
/// <param name="overloadIndex">Optional 0-based index among matching overloads</param>
40+
/// <returns>The resolved MethodDef, or null if not found</returns>
41+
public MethodDef? ResolveMethod(string typeFullName, string methodName, string[]? parameterTypes = null, int? overloadIndex = null)
42+
{
43+
TypeDef? typeDef = null;
44+
45+
foreach (var module in _assemblyDef.Modules)
46+
{
47+
typeDef = FindType(module, typeFullName);
48+
if (typeDef is not null)
49+
{
50+
break;
51+
}
52+
}
53+
54+
if (typeDef is null)
55+
{
56+
return null;
57+
}
58+
59+
var methods = typeDef.Methods.Where(m => m.Name.String == methodName).ToList();
60+
61+
if (methods.Count == 0)
62+
{
63+
return null;
64+
}
65+
66+
// Apply disambiguation
67+
if (parameterTypes is { Length: > 0 })
68+
{
69+
return methods.FirstOrDefault(m => MatchesParameterTypes(m, parameterTypes));
70+
}
71+
72+
if (overloadIndex.HasValue)
73+
{
74+
if (overloadIndex.Value >= 0 && overloadIndex.Value < methods.Count)
75+
{
76+
return methods[overloadIndex.Value];
77+
}
78+
79+
return null;
80+
}
81+
82+
if (methods.Count == 1)
83+
{
84+
return methods[0];
85+
}
86+
87+
// Multiple overloads with no disambiguation — return null to trigger helpful error
88+
return null;
89+
}
90+
91+
/// <summary>
92+
/// Lists all available overloads for a method, useful for disambiguation.
93+
/// </summary>
94+
public IReadOnlyList<MethodDef> ListOverloads(string typeFullName, string methodName)
95+
{
96+
TypeDef? typeDef = null;
97+
98+
foreach (var module in _assemblyDef.Modules)
99+
{
100+
typeDef = FindType(module, typeFullName);
101+
if (typeDef is not null)
102+
{
103+
break;
104+
}
105+
}
106+
107+
if (typeDef is null)
108+
{
109+
return Array.Empty<MethodDef>();
110+
}
111+
112+
return typeDef.Methods.Where(m => m.Name == methodName).ToList();
113+
}
114+
115+
public void Dispose()
116+
{
117+
// AssemblyDef doesn't implement IDisposable, but we free module contexts
118+
foreach (var module in _assemblyDef.Modules)
119+
{
120+
module?.Dispose();
121+
}
122+
}
123+
124+
private static TypeDef? FindType(ModuleDef module, string typeFullName)
125+
{
126+
// Handle nested types: "Outer+Inner" or "Outer/Inner"
127+
var parts = typeFullName.Split('+', '/');
128+
129+
TypeDef? current = null;
130+
foreach (var part in parts)
131+
{
132+
if (current is null)
133+
{
134+
current = module.Find(part, isReflectionName: false)
135+
?? module.Types.FirstOrDefault(t => t.FullName == part);
136+
}
137+
else
138+
{
139+
current = current.NestedTypes.FirstOrDefault(t => t.Name == part);
140+
}
141+
142+
if (current is null)
143+
{
144+
return null;
145+
}
146+
}
147+
148+
return current;
149+
}
150+
151+
private static bool MatchesParameterTypes(MethodDef method, string[] parameterTypes)
152+
{
153+
var parameters = method.Parameters.Where(p => !p.IsHiddenThisParameter).ToArray();
154+
if (parameters.Length != parameterTypes.Length)
155+
{
156+
return false;
157+
}
158+
159+
for (var i = 0; i < parameters.Length; i++)
160+
{
161+
var paramFullName = parameters[i].Type.FullName;
162+
if (!string.Equals(paramFullName, parameterTypes[i], StringComparison.Ordinal))
163+
{
164+
return false;
165+
}
166+
}
167+
168+
return true;
169+
}
170+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<LangVersion>latest</LangVersion>
4+
<TargetFrameworks>net10.0</TargetFrameworks>
5+
<SignAssembly>false</SignAssembly>
6+
<Nullable>enable</Nullable>
7+
<!-- Suppress documentation warnings for internal tooling -->
8+
<NoWarn>$(NoWarn);CS1591;SA1600;SA1601;SA1602;SA1611;SA1615;SA1618</NoWarn>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="dnlib" Version="3.6.0" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<EmbeddedResource Include="Resources\Data\**\*" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<Compile Remove="Resources\Data\**\*.cs" />
21+
</ItemGroup>
22+
</Project>

tracer/src/Datadog.AutoInstrumentation.Generator/EditorHelper.cs renamed to tracer/src/Datadog.AutoInstrumentation.Generator.Core/EditorHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="EditorHelper.cs" company="Datadog">
1+
// <copyright file="EditorHelper.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
@@ -12,7 +12,7 @@
1212
using dnlib.DotNet;
1313
// ReSharper disable InconsistentNaming
1414

15-
namespace Datadog.AutoInstrumentation.Generator;
15+
namespace Datadog.AutoInstrumentation.Generator.Core;
1616

1717
internal static class EditorHelper
1818
{
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// <copyright file="GenerationConfiguration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
using System;
7+
using System.Linq;
8+
using System.Threading.Tasks;
9+
using dnlib.DotNet;
10+
11+
namespace Datadog.AutoInstrumentation.Generator.Core;
12+
13+
/// <summary>
14+
/// Configuration for code generation. Plain POCO with all boolean flags
15+
/// that control what gets generated.
16+
/// </summary>
17+
public class GenerationConfiguration
18+
{
19+
// OnMethod handlers
20+
public bool CreateOnMethodBegin { get; set; } = true;
21+
22+
public bool CreateOnMethodEnd { get; set; } = true;
23+
24+
public bool CreateOnAsyncMethodEnd { get; set; }
25+
26+
// Duck typing general
27+
public bool UseDuckCopyStruct { get; set; }
28+
29+
// Duck type: Instance
30+
public bool CreateDucktypeInstance { get; set; }
31+
32+
public bool DucktypeInstanceFields { get; set; }
33+
34+
public bool DucktypeInstanceProperties { get; set; } = true;
35+
36+
public bool DucktypeInstanceMethods { get; set; }
37+
38+
public bool DucktypeInstanceDuckChaining { get; set; }
39+
40+
// Duck type: Arguments
41+
public bool CreateDucktypeArguments { get; set; }
42+
43+
public bool DucktypeArgumentsFields { get; set; }
44+
45+
public bool DucktypeArgumentsProperties { get; set; } = true;
46+
47+
public bool DucktypeArgumentsMethods { get; set; }
48+
49+
public bool DucktypeArgumentsDuckChaining { get; set; }
50+
51+
// Duck type: Return Value
52+
public bool CreateDucktypeReturnValue { get; set; }
53+
54+
public bool DucktypeReturnValueFields { get; set; }
55+
56+
public bool DucktypeReturnValueProperties { get; set; } = true;
57+
58+
public bool DucktypeReturnValueMethods { get; set; }
59+
60+
public bool DucktypeReturnValueDuckChaining { get; set; }
61+
62+
// Duck type: Async Return Value
63+
public bool CreateDucktypeAsyncReturnValue { get; set; }
64+
65+
public bool DucktypeAsyncReturnValueFields { get; set; }
66+
67+
public bool DucktypeAsyncReturnValueProperties { get; set; } = true;
68+
69+
public bool DucktypeAsyncReturnValueMethods { get; set; }
70+
71+
public bool DucktypeAsyncReturnValueDuckChaining { get; set; }
72+
73+
/// <summary>
74+
/// Creates a configuration with smart defaults based on the method being instrumented.
75+
/// Mirrors the GUI auto-detection logic from MainViewModel.Configuration.cs.
76+
/// </summary>
77+
public static GenerationConfiguration CreateForMethod(MethodDef methodDef)
78+
{
79+
var config = new GenerationConfiguration();
80+
81+
var isAsync = methodDef.ReturnType.FullName.StartsWith(typeof(Task).FullName!, StringComparison.Ordinal) ||
82+
methodDef.ReturnType.FullName.StartsWith(typeof(ValueTask).FullName!, StringComparison.Ordinal);
83+
84+
if (isAsync)
85+
{
86+
config.CreateOnMethodEnd = false;
87+
config.CreateDucktypeReturnValue = false;
88+
config.CreateOnAsyncMethodEnd = true;
89+
}
90+
else
91+
{
92+
config.CreateOnMethodEnd = true;
93+
config.CreateOnAsyncMethodEnd = false;
94+
config.CreateDucktypeAsyncReturnValue = false;
95+
}
96+
97+
if (methodDef.IsStatic || (!config.CreateOnMethodBegin && !config.CreateOnMethodEnd && !config.CreateOnAsyncMethodEnd))
98+
{
99+
config.CreateDucktypeInstance = false;
100+
}
101+
102+
return config;
103+
}
104+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// <copyright file="GenerationResult.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
namespace Datadog.AutoInstrumentation.Generator.Core;
7+
8+
/// <summary>
9+
/// Result of code generation.
10+
/// </summary>
11+
public class GenerationResult
12+
{
13+
public bool Success { get; set; }
14+
15+
public string? ErrorMessage { get; set; }
16+
17+
public string? SourceCode { get; set; }
18+
19+
public string? FileName { get; set; }
20+
21+
public string? Namespace { get; set; }
22+
23+
public InstrumentMethodMetadata? Metadata { get; set; }
24+
25+
public static GenerationResult Error(string message) => new() { Success = false, ErrorMessage = message };
26+
}

0 commit comments

Comments
 (0)