Skip to content

Commit 25076db

Browse files
committed
No preprocessor dirs in configurationkeys
1 parent 6a1097f commit 25076db

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// <copyright file="ConfigKeysNoPreprocessorDirsAnalyzer.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+
#nullable enable
7+
using System.Collections.Immutable;
8+
using System.Linq;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
14+
namespace Datadog.Trace.Tools.Analyzers.ConfigurationAnalyzers;
15+
16+
/// <summary>
17+
/// DD0011: Preprocessor directives are forbidden in ConfigurationKeys.
18+
///
19+
/// Forbids any preprocessor directives (e.g., #if/#endif/#define/#undef/#pragma/#region) inside
20+
/// the Datadog.Trace.Configuration.ConfigurationKeys partial class (across all partial files).
21+
/// </summary>
22+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
23+
public sealed class ConfigKeysNoPreprocessorDirsAnalyzer : DiagnosticAnalyzer
24+
{
25+
private const string DiagnosticId = "DD0011";
26+
27+
private static readonly DiagnosticDescriptor Rule = new(
28+
DiagnosticId,
29+
title: "Preprocessor directives are not allowed in ConfigurationKeys",
30+
messageFormat: "Preprocessor directives '{0}' is not allowed inside Datadog.Trace.Configuration.ConfigurationKeys",
31+
category: "CodeQuality",
32+
defaultSeverity: DiagnosticSeverity.Error,
33+
isEnabledByDefault: true,
34+
description: "Do not use preprocessor directives inside the ConfigurationKeys partial class. Use runtime configuration or source generators instead.");
35+
36+
/// <summary>
37+
/// Gets SupportedDiagnostics
38+
/// </summary>
39+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
40+
41+
/// <summary>
42+
/// Initialize
43+
/// </summary>
44+
/// <param name="context">context</param>
45+
public override void Initialize(AnalysisContext context)
46+
{
47+
context.EnableConcurrentExecution();
48+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
49+
context.RegisterSyntaxNodeAction(AnalyzeClass, SyntaxKind.ClassDeclaration);
50+
}
51+
52+
private static void AnalyzeClass(SyntaxNodeAnalysisContext context)
53+
{
54+
var classDecl = (ClassDeclarationSyntax)context.Node;
55+
56+
// Quick name check
57+
if (classDecl.Identifier.ValueText != "ConfigurationKeys")
58+
{
59+
return;
60+
}
61+
62+
// Ensure it's in the Datadog.Trace.Configuration namespace
63+
var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl, context.CancellationToken);
64+
if (symbol is null)
65+
{
66+
return;
67+
}
68+
69+
var containingNs = symbol.ContainingNamespace?.ToDisplayString();
70+
if (containingNs != "Datadog.Trace.Configuration")
71+
{
72+
return;
73+
}
74+
75+
// Look for any preprocessor directives within this class declaration span
76+
// We consider any DirectiveTriviaSyntax as forbidden
77+
foreach (var trivia in classDecl.DescendantTrivia(descendIntoTrivia: true))
78+
{
79+
if (!trivia.IsDirective)
80+
{
81+
continue;
82+
}
83+
84+
if (trivia.GetStructure() is DirectiveTriviaSyntax directive)
85+
{
86+
// Report on the directive keyword
87+
var tokenText = directive.ToFullString().Trim();
88+
var location = directive.GetLocation();
89+
var diagnostic = Diagnostic.Create(Rule, location, tokenText.Split('\n').FirstOrDefault() ?? tokenText);
90+
context.ReportDiagnostic(diagnostic);
91+
}
92+
}
93+
}
94+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// <copyright file="ConfigKeysNoPreprocessorDirsAnalyzerTests.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 Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Testing;
9+
using Xunit;
10+
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<
11+
Datadog.Trace.Tools.Analyzers.ConfigurationAnalyzers.ConfigKeysNoPreprocessorDirsAnalyzer,
12+
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
13+
14+
namespace Datadog.Trace.Tools.Analyzers.Tests.ConfigurationAnalyzers;
15+
16+
public class ConfigKeysNoPreprocessorDirsAnalyzerTests
17+
{
18+
private const string DiagnosticId = "DD0011"; // Matches analyzer's DiagnosticId
19+
20+
[Fact]
21+
public async Task NoDirectivesInsideConfigurationKeys_ShouldHaveNoDiagnostics()
22+
{
23+
var code = """
24+
#nullable enable
25+
namespace Datadog.Trace.Configuration;
26+
27+
public static partial class ConfigurationKeys
28+
{
29+
public const string A = "DD_SERVICE";
30+
public const string B = "DD_ENV";
31+
}
32+
""";
33+
34+
await Verifier.VerifyAnalyzerAsync(code);
35+
}
36+
37+
[Fact]
38+
public async Task DirectivesInsideConfigurationKeys_ShouldReportDiagnostics_ForEachDirective()
39+
{
40+
var code = """
41+
#nullable enable
42+
namespace Datadog.Trace.Configuration;
43+
44+
public static partial class ConfigurationKeys
45+
{
46+
{|#0:#if DEBUG|}
47+
public const string A = "DD_SERVICE";
48+
{|#1:#elif RELEASE|}
49+
public const string B = "DD_ENV";
50+
{|#2:#else|}
51+
public const string C = "DD_VERSION";
52+
{|#3:#endif|}
53+
54+
{|#4:#region MyRegion|}
55+
public const string D = "DD_TRACE_ENABLED";
56+
{|#5:#endregion|}
57+
58+
{|#6:#pragma warning disable CS0168|}
59+
public const string E = "DD_AGENT_HOST";
60+
{|#7:#pragma warning restore CS0168|}
61+
}
62+
""";
63+
64+
var expected0 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
65+
.WithLocation(0)
66+
.WithArguments("#if DEBUG");
67+
var expected1 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
68+
.WithLocation(1)
69+
.WithArguments("#elif RELEASE");
70+
var expected2 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
71+
.WithLocation(2)
72+
.WithArguments("#else");
73+
var expected3 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
74+
.WithLocation(3)
75+
.WithArguments("#endif");
76+
var expected4 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
77+
.WithLocation(4)
78+
.WithArguments("#region MyRegion");
79+
var expected5 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
80+
.WithLocation(5)
81+
.WithArguments("#endregion");
82+
var expected6 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
83+
.WithLocation(6)
84+
.WithArguments("#pragma warning disable CS0168");
85+
var expected7 = new DiagnosticResult(DiagnosticId, DiagnosticSeverity.Error)
86+
.WithLocation(7)
87+
.WithArguments("#pragma warning restore CS0168");
88+
89+
await Verifier.VerifyAnalyzerAsync(
90+
code,
91+
expected0,
92+
expected1,
93+
expected2,
94+
expected3,
95+
expected4,
96+
expected5,
97+
expected6,
98+
expected7);
99+
}
100+
101+
[Fact]
102+
public async Task DirectivesInWrongNamespace_ShouldHaveNoDiagnostics()
103+
{
104+
var code = """
105+
#nullable enable
106+
namespace Some.Other.Namespace;
107+
108+
public static partial class ConfigurationKeys
109+
{
110+
#if DEBUG
111+
public const string A = "DD_SERVICE";
112+
#endif
113+
}
114+
""";
115+
116+
await Verifier.VerifyAnalyzerAsync(code);
117+
}
118+
119+
[Fact]
120+
public async Task DirectivesInWrongClass_ShouldHaveNoDiagnostics()
121+
{
122+
var code = """
123+
#nullable enable
124+
namespace Datadog.Trace.Configuration;
125+
126+
public static class OtherClass
127+
{
128+
#if NET8_0_OR_GREATER
129+
public const string X = "DD_TRACE_ENABLED";
130+
#endif
131+
}
132+
""";
133+
134+
await Verifier.VerifyAnalyzerAsync(code);
135+
}
136+
}

0 commit comments

Comments
 (0)