Skip to content

Commit 5dd0572

Browse files
committed
Error if Datadog.Trace types aren't found by the WellKnownTypeProvider per Andrew's comments
1 parent de53524 commit 5dd0572

File tree

9 files changed

+370
-149
lines changed

9 files changed

+370
-149
lines changed

tracer/src/Datadog.Trace.Tools.Analyzers/ConfigurationAnalyzers/ConfigurationBuilderWithKeysAnalyzer.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
55

6+
#nullable enable
67
using System.Collections.Immutable;
78
using Datadog.Trace.Tools.Analyzers.Helpers;
89
using Microsoft.CodeAnalysis;
@@ -47,7 +48,7 @@ public class ConfigurationBuilderWithKeysAnalyzer : DiagnosticAnalyzer
4748
/// Gets the supported diagnostics
4849
/// </summary>
4950
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
50-
[UseConfigurationConstantsRule, UseConfigurationConstantsNotVariablesRule];
51+
[UseConfigurationConstantsRule, UseConfigurationConstantsNotVariablesRule, Diagnostics.MissingRequiredType];
5152

5253
/// <summary>
5354
/// Initialize the analyzer
@@ -60,10 +61,26 @@ public override void Initialize(AnalysisContext context)
6061
context.RegisterCompilationStartAction(compilationContext =>
6162
{
6263
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilationContext.Compilation);
63-
var targetTypes = new TargetTypeSymbols(
64-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationBuilder),
65-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationKeys),
66-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.PlatformKeys));
64+
65+
var configurationBuilder = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationBuilder);
66+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, configurationBuilder, nameof(ConfigurationBuilderWithKeysAnalyzer), WellKnownTypeNames.ConfigurationBuilder))
67+
{
68+
return;
69+
}
70+
71+
var configurationKeys = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationKeys);
72+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, configurationKeys, nameof(ConfigurationBuilderWithKeysAnalyzer), WellKnownTypeNames.ConfigurationKeys))
73+
{
74+
return;
75+
}
76+
77+
var platformKeys = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.PlatformKeys);
78+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, platformKeys, nameof(ConfigurationBuilderWithKeysAnalyzer), WellKnownTypeNames.PlatformKeys))
79+
{
80+
return;
81+
}
82+
83+
var targetTypes = new TargetTypeSymbols(configurationBuilder, configurationKeys, platformKeys);
6784

6885
compilationContext.RegisterSyntaxNodeAction(
6986
c => AnalyzeInvocationExpression(c, targetTypes),
@@ -98,7 +115,7 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
98115

99116
// Analyze the first argument
100117
var argumentList = invocation.ArgumentList;
101-
if (argumentList.Arguments.Count > 0)
118+
if (argumentList?.Arguments.Count > 0)
102119
{
103120
var argument = argumentList.Arguments[0];
104121
AnalyzeConfigurationArgument(context, argument, WellKnownTypeNames.WithKeysMethodName, targetTypes);

tracer/src/Datadog.Trace.Tools.Analyzers/ConfigurationAnalyzers/EnvironmentGetEnvironmentVariableAnalyzer.cs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
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>
5-
5+
#nullable enable
66
using System.Collections.Immutable;
77
using Datadog.Trace.Tools.Analyzers.Helpers;
88
using Microsoft.CodeAnalysis;
@@ -62,7 +62,7 @@ public class EnvironmentGetEnvironmentVariableAnalyzer : DiagnosticAnalyzer
6262
/// Gets the supported diagnostics
6363
/// </summary>
6464
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
65-
ImmutableArray.Create(UseConfigurationKeysRule, UseConfigurationKeysNotVariablesRule);
65+
ImmutableArray.Create(UseConfigurationKeysRule, UseConfigurationKeysNotVariablesRule, Diagnostics.MissingRequiredType);
6666

6767
/// <summary>
6868
/// Initialize the analyzer
@@ -75,12 +75,38 @@ public override void Initialize(AnalysisContext context)
7575
context.RegisterCompilationStartAction(compilationContext =>
7676
{
7777
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilationContext.Compilation);
78-
var targetTypes = new TargetTypeSymbols(
79-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.EnvironmentHelpers),
80-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.EnvironmentHelpersNoLogging),
81-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.IValueProvider),
82-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationKeys),
83-
wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.PlatformKeys));
78+
79+
var environmentHelpers = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.EnvironmentHelpers);
80+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, environmentHelpers, nameof(EnvironmentGetEnvironmentVariableAnalyzer), WellKnownTypeNames.EnvironmentHelpers))
81+
{
82+
return;
83+
}
84+
85+
var environmentHelpersNoLogging = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.EnvironmentHelpersNoLogging);
86+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, environmentHelpersNoLogging, nameof(EnvironmentGetEnvironmentVariableAnalyzer), WellKnownTypeNames.EnvironmentHelpersNoLogging))
87+
{
88+
return;
89+
}
90+
91+
var iValueProvider = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.IValueProvider);
92+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, iValueProvider, nameof(EnvironmentGetEnvironmentVariableAnalyzer), WellKnownTypeNames.IValueProvider))
93+
{
94+
return;
95+
}
96+
97+
var configurationKeys = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.ConfigurationKeys);
98+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, configurationKeys, nameof(EnvironmentGetEnvironmentVariableAnalyzer), WellKnownTypeNames.ConfigurationKeys))
99+
{
100+
return;
101+
}
102+
103+
var platformKeys = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.PlatformKeys);
104+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, platformKeys, nameof(EnvironmentGetEnvironmentVariableAnalyzer), WellKnownTypeNames.PlatformKeys))
105+
{
106+
return;
107+
}
108+
109+
var targetTypes = new TargetTypeSymbols(environmentHelpers, environmentHelpersNoLogging, iValueProvider, configurationKeys, platformKeys);
84110

85111
compilationContext.RegisterSyntaxNodeAction(
86112
c => AnalyzeInvocationExpression(c, targetTypes),
@@ -117,7 +143,7 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
117143
}
118144

119145
// Check the first argument (the environment variable name)
120-
if (argumentList.Arguments.Count > 0)
146+
if (argumentList?.Arguments.Count > 0)
121147
{
122148
var argument = argumentList.Arguments[0];
123149
AnalyzeEnvironmentVariableArgument(context, argument, targetTypes);

tracer/src/Datadog.Trace.Tools.Analyzers/ConfigurationAnalyzers/PlatformKeysAnalyzer.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public sealed class PlatformKeysAnalyzer : DiagnosticAnalyzer
4545
description: "Constants in PlatformKeys class should not start with OTEL, DD_, or _DD_ prefixes as these are reserved for OpenTelemetry and Datadog configuration keys. Platform keys should represent environment variables from external platforms and services.");
4646

4747
/// <inheritdoc />
48-
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [Rule];
48+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [Rule, Helpers.Diagnostics.MissingRequiredType];
4949

5050
/// <inheritdoc />
5151
public override void Initialize(AnalysisContext context)
@@ -57,12 +57,14 @@ public override void Initialize(AnalysisContext context)
5757
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilationContext.Compilation);
5858
var platformKeysType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.PlatformKeys);
5959

60-
if (platformKeysType != null)
60+
if (Helpers.Diagnostics.IsTypeNullAndReportForDatadogTrace(compilationContext, platformKeysType, nameof(PlatformKeysAnalyzer), WellKnownTypeNames.PlatformKeys))
6161
{
62-
compilationContext.RegisterSymbolAction(
63-
c => AnalyzeNamedType(c, platformKeysType),
64-
SymbolKind.NamedType);
62+
return;
6563
}
64+
65+
compilationContext.RegisterSymbolAction(
66+
c => AnalyzeNamedType(c, platformKeysType),
67+
SymbolKind.NamedType);
6668
});
6769
}
6870

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// <copyright file="Diagnostics.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+
#nullable enable
6+
using System.Diagnostics.CodeAnalysis;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
namespace Datadog.Trace.Tools.Analyzers.Helpers
11+
{
12+
internal static class Diagnostics
13+
{
14+
public static readonly DiagnosticDescriptor MissingRequiredType = new(
15+
id: "DD0009",
16+
title: "Required type not found for analyzer",
17+
messageFormat: "Analyzer {0} could not find required type '{1}' in Datadog.Trace. The type may have been renamed or removed. Please make sure the types haven't been renamed, and if the renaming was justified, update the analyzer.",
18+
category: "Usage",
19+
defaultSeverity: DiagnosticSeverity.Error,
20+
isEnabledByDefault: true,
21+
description: "The analyzer requires certain types to be present in the compilation of Datadog.Trace. If they are missing, they may have been renamed or removed.",
22+
customTags: WellKnownDiagnosticTags.CompilationEnd);
23+
24+
#pragma warning disable RS1013 // The CompilationStartAction in callers also registers other actions (SyntaxNodeAction/SymbolAction), so this is not a start/end-only pair
25+
/// <summary>
26+
/// Checks if a type is null and reports a diagnostic if compiling the Datadog.Trace assembly.
27+
/// Returns true if the type is null (caller should bail out).
28+
/// </summary>
29+
internal static bool IsTypeNullAndReportForDatadogTrace(
30+
CompilationStartAnalysisContext context,
31+
[NotNullWhen(false)] INamedTypeSymbol? type,
32+
string analyzerName,
33+
string typeName)
34+
{
35+
if (type is not null)
36+
{
37+
return false;
38+
}
39+
40+
if (context.Compilation.AssemblyName == "Datadog.Trace")
41+
{
42+
var diagnostic = Diagnostic.Create(
43+
MissingRequiredType,
44+
Location.None,
45+
analyzerName,
46+
typeName);
47+
context.RegisterCompilationEndAction(c => c.ReportDiagnostic(diagnostic));
48+
}
49+
50+
return true;
51+
}
52+
#pragma warning restore RS1013
53+
}
54+
}

tracer/src/Datadog.Trace/Util/EnvironmentHelpersNoLogging.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ public static bool IsClrProfilerAttachedSafe()
6868
public static string? ProgramData() => GetEnvironmentVariable(PlatformKeys.ProgramData);
6969

7070
#pragma warning disable RS0030
71-
// this access is allowed here as it's controlled by analyzer EnvironmentGetEnvironmentVariableAnalyzer making sure it's using a key from ConfigurationKeys/PlatformKeys
71+
// -> Don't rename unless you adapt EnvironmentGetEnvironmentVariableAnalyzer
72+
// this access is allowed here as it's controlled by analyzer EnvironmentGetEnvironmentVariableAnalyzer making sure it's using a key from ConfigurationKeys/PlatformKeys
7273
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7374
private static string? GetEnvironmentVariable(string key) => Environment.GetEnvironmentVariable(key);
7475
#pragma warning restore RS0030
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// <copyright file="AnalyzerTestHelper.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.Threading.Tasks;
7+
using Datadog.Trace.Tools.Analyzers.ConfigurationAnalyzers;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp.Testing;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using Microsoft.CodeAnalysis.Testing;
12+
13+
namespace Datadog.Trace.Tools.Analyzers.Tests.ConfigurationAnalyzers;
14+
15+
internal static class AnalyzerTestHelper
16+
{
17+
/// <summary>
18+
/// Minimal required type definitions to prevent DD0009 errors in tests.
19+
/// Does not include ConfigurationBuilder/HasKeys for tests that define these themselves.
20+
/// </summary>
21+
public const string MinimalRequiredTypes = """
22+
namespace Datadog.Trace.Configuration
23+
{
24+
public static partial class ConfigurationKeys { }
25+
public static partial class PlatformKeys { }
26+
}
27+
namespace Datadog.Trace.Util
28+
{
29+
public static partial class EnvironmentHelpers { public static string GetEnvironmentVariable(string key) => null; }
30+
public static partial class EnvironmentHelpersNoLogging { }
31+
}
32+
namespace Datadog.Trace.Ci.CiEnvironment
33+
{
34+
public interface IValueProvider { }
35+
}
36+
""";
37+
38+
/// <summary>
39+
/// Complete required type definitions including ConfigurationBuilder and HasKeys.
40+
/// Use this for tests that don't define these types themselves.
41+
/// </summary>
42+
public const string RequiredTypes = MinimalRequiredTypes + """
43+
namespace Datadog.Trace.Configuration.Telemetry
44+
{
45+
public struct ConfigurationBuilder { public HasKeys WithKeys(string key) => default; }
46+
public struct HasKeys { }
47+
}
48+
""";
49+
50+
/// <summary>
51+
/// Verifies analyzer with assembly name set to Datadog.Trace
52+
/// </summary>
53+
public static async Task VerifyDatadogAnalyzerAsync<TAnalyzer>(string source, params DiagnosticResult[] expected)
54+
where TAnalyzer : DiagnosticAnalyzer, new()
55+
{
56+
var test = new CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
57+
{
58+
TestState =
59+
{
60+
Sources = { source }
61+
}
62+
};
63+
64+
test.TestState.ExpectedDiagnostics.AddRange(expected);
65+
test.SolutionTransforms.Add((solution, projectId) => solution.WithProjectAssemblyName(projectId, "Datadog.Trace"));
66+
await test.RunAsync();
67+
}
68+
}

0 commit comments

Comments
 (0)