Add CS1066 suppressor for MCP server methods with optional parameters (#1110)
This commit is contained in:
parent
8b73a42c9d
commit
8316aa0895
5 changed files with 429 additions and 20 deletions
148
src/ModelContextProtocol.Analyzers/CS1066Suppressor.cs
Normal file
148
src/ModelContextProtocol.Analyzers/CS1066Suppressor.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
|
||||
namespace ModelContextProtocol.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Suppresses CS1066 warnings for MCP server methods that have optional parameters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// CS1066 is issued when a partial method's implementing declaration has default parameter values.
|
||||
/// For partial methods, only the defining declaration's defaults are used by callers,
|
||||
/// making the implementing declaration's defaults redundant.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// However, for MCP tool, prompt, and resource methods, users often want to specify default values
|
||||
/// in their implementing declaration for documentation purposes. The XmlToDescriptionGenerator
|
||||
/// automatically copies these defaults to the generated defining declaration, making them functional.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This suppressor suppresses CS1066 for methods marked with [McpServerTool], [McpServerPrompt],
|
||||
/// or [McpServerResource] attributes, allowing users to specify defaults in their code without warnings.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class CS1066Suppressor : DiagnosticSuppressor
|
||||
{
|
||||
private static readonly SuppressionDescriptor McpToolSuppression = new(
|
||||
id: "MCP_CS1066_TOOL",
|
||||
suppressedDiagnosticId: "CS1066",
|
||||
justification: "Default values on MCP tool method implementing declarations are copied to the generated defining declaration by the source generator.");
|
||||
|
||||
private static readonly SuppressionDescriptor McpPromptSuppression = new(
|
||||
id: "MCP_CS1066_PROMPT",
|
||||
suppressedDiagnosticId: "CS1066",
|
||||
justification: "Default values on MCP prompt method implementing declarations are copied to the generated defining declaration by the source generator.");
|
||||
|
||||
private static readonly SuppressionDescriptor McpResourceSuppression = new(
|
||||
id: "MCP_CS1066_RESOURCE",
|
||||
suppressedDiagnosticId: "CS1066",
|
||||
justification: "Default values on MCP resource method implementing declarations are copied to the generated defining declaration by the source generator.");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions =>
|
||||
ImmutableArray.Create(McpToolSuppression, McpPromptSuppression, McpResourceSuppression);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ReportSuppressions(SuppressionAnalysisContext context)
|
||||
{
|
||||
// Cache semantic models and attribute symbols per syntax tree/compilation to avoid redundant calls
|
||||
Dictionary<SyntaxTree, SemanticModel>? semanticModelCache = null;
|
||||
INamedTypeSymbol? mcpToolAttribute = null;
|
||||
INamedTypeSymbol? mcpPromptAttribute = null;
|
||||
INamedTypeSymbol? mcpResourceAttribute = null;
|
||||
bool attributesResolved = false;
|
||||
|
||||
foreach (Diagnostic diagnostic in context.ReportedDiagnostics)
|
||||
{
|
||||
Location? location = diagnostic.Location;
|
||||
SyntaxTree? tree = location.SourceTree;
|
||||
if (tree is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SyntaxNode root = tree.GetRoot(context.CancellationToken);
|
||||
SyntaxNode? node = root.FindNode(location.SourceSpan);
|
||||
|
||||
// Find the containing method declaration
|
||||
MethodDeclarationSyntax? method = node.FirstAncestorOrSelf<MethodDeclarationSyntax>();
|
||||
if (method is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get or cache the semantic model for this tree
|
||||
semanticModelCache ??= new Dictionary<SyntaxTree, SemanticModel>();
|
||||
if (!semanticModelCache.TryGetValue(tree, out SemanticModel? semanticModel))
|
||||
{
|
||||
semanticModel = context.GetSemanticModel(tree);
|
||||
semanticModelCache[tree] = semanticModel;
|
||||
}
|
||||
|
||||
// Resolve attribute symbols once per compilation
|
||||
if (!attributesResolved)
|
||||
{
|
||||
mcpToolAttribute = semanticModel.Compilation.GetTypeByMetadataName(McpAttributeNames.McpServerToolAttribute);
|
||||
mcpPromptAttribute = semanticModel.Compilation.GetTypeByMetadataName(McpAttributeNames.McpServerPromptAttribute);
|
||||
mcpResourceAttribute = semanticModel.Compilation.GetTypeByMetadataName(McpAttributeNames.McpServerResourceAttribute);
|
||||
attributesResolved = true;
|
||||
}
|
||||
|
||||
// Check for MCP attributes
|
||||
SuppressionDescriptor? suppression = GetSuppressionForMethod(method, semanticModel, mcpToolAttribute, mcpPromptAttribute, mcpResourceAttribute, context.CancellationToken);
|
||||
if (suppression is not null)
|
||||
{
|
||||
context.ReportSuppression(Suppression.Create(suppression, diagnostic));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SuppressionDescriptor? GetSuppressionForMethod(
|
||||
MethodDeclarationSyntax method,
|
||||
SemanticModel semanticModel,
|
||||
INamedTypeSymbol? mcpToolAttribute,
|
||||
INamedTypeSymbol? mcpPromptAttribute,
|
||||
INamedTypeSymbol? mcpResourceAttribute,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
IMethodSymbol? methodSymbol = semanticModel.GetDeclaredSymbol(method, cancellationToken);
|
||||
|
||||
if (methodSymbol is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (AttributeData attribute in methodSymbol.GetAttributes())
|
||||
{
|
||||
INamedTypeSymbol? attributeClass = attribute.AttributeClass;
|
||||
if (attributeClass is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mcpToolAttribute is not null && SymbolEqualityComparer.Default.Equals(attributeClass, mcpToolAttribute))
|
||||
{
|
||||
return McpToolSuppression;
|
||||
}
|
||||
|
||||
if (mcpPromptAttribute is not null && SymbolEqualityComparer.Default.Equals(attributeClass, mcpPromptAttribute))
|
||||
{
|
||||
return McpPromptSuppression;
|
||||
}
|
||||
|
||||
if (mcpResourceAttribute is not null && SymbolEqualityComparer.Default.Equals(attributeClass, mcpResourceAttribute))
|
||||
{
|
||||
return McpResourceSuppression;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
12
src/ModelContextProtocol.Analyzers/McpAttributeNames.cs
Normal file
12
src/ModelContextProtocol.Analyzers/McpAttributeNames.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace ModelContextProtocol.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the fully qualified metadata names for MCP server attributes.
|
||||
/// </summary>
|
||||
internal static class McpAttributeNames
|
||||
{
|
||||
public const string McpServerToolAttribute = "ModelContextProtocol.Server.McpServerToolAttribute";
|
||||
public const string McpServerPromptAttribute = "ModelContextProtocol.Server.McpServerPromptAttribute";
|
||||
public const string McpServerResourceAttribute = "ModelContextProtocol.Server.McpServerResourceAttribute";
|
||||
public const string DescriptionAttribute = "System.ComponentModel.DescriptionAttribute";
|
||||
}
|
||||
|
|
@ -18,18 +18,14 @@ namespace ModelContextProtocol.Analyzers;
|
|||
public sealed class XmlToDescriptionGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string GeneratedFileName = "ModelContextProtocol.Descriptions.g.cs";
|
||||
private const string McpServerToolAttributeName = "ModelContextProtocol.Server.McpServerToolAttribute";
|
||||
private const string McpServerPromptAttributeName = "ModelContextProtocol.Server.McpServerPromptAttribute";
|
||||
private const string McpServerResourceAttributeName = "ModelContextProtocol.Server.McpServerResourceAttribute";
|
||||
private const string DescriptionAttributeName = "System.ComponentModel.DescriptionAttribute";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// Extract method information for all MCP tools, prompts, and resources.
|
||||
// The transform extracts all necessary data upfront so the output doesn't depend on the compilation.
|
||||
var allMethods = CreateProviderForAttribute(context, McpServerToolAttributeName).Collect()
|
||||
.Combine(CreateProviderForAttribute(context, McpServerPromptAttributeName).Collect())
|
||||
.Combine(CreateProviderForAttribute(context, McpServerResourceAttributeName).Collect())
|
||||
var allMethods = CreateProviderForAttribute(context, McpAttributeNames.McpServerToolAttribute).Collect()
|
||||
.Combine(CreateProviderForAttribute(context, McpAttributeNames.McpServerPromptAttribute).Collect())
|
||||
.Combine(CreateProviderForAttribute(context, McpAttributeNames.McpServerResourceAttribute).Collect())
|
||||
.Select(static (tuple, _) =>
|
||||
{
|
||||
var ((tools, prompts), resources) = tuple;
|
||||
|
|
@ -84,7 +80,7 @@ public sealed class XmlToDescriptionGenerator : IIncrementalGenerator
|
|||
Compilation compilation)
|
||||
{
|
||||
bool isPartial = methodDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword);
|
||||
var descriptionAttribute = compilation.GetTypeByMetadataName(DescriptionAttributeName);
|
||||
var descriptionAttribute = compilation.GetTypeByMetadataName(McpAttributeNames.DescriptionAttribute);
|
||||
|
||||
// Try to extract XML documentation
|
||||
var (xmlDocs, hasInvalidXml) = TryExtractXmlDocumentation(methodSymbol);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
using Xunit;
|
||||
|
||||
namespace ModelContextProtocol.Analyzers.Tests;
|
||||
|
||||
public class CS1066SuppressorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Suppressor_WithMcpServerToolAttribute_SuppressesCS1066()
|
||||
{
|
||||
var result = RunSuppressor("""
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace Test;
|
||||
|
||||
[McpServerToolType]
|
||||
public partial class TestTools
|
||||
{
|
||||
[McpServerTool]
|
||||
public partial string TestMethod(string input = "default");
|
||||
}
|
||||
|
||||
public partial class TestTools
|
||||
{
|
||||
public partial string TestMethod(string input = "default")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// Check we have the CS1066 diagnostics from compiler
|
||||
var cs1066FromCompiler = result.CompilerDiagnostics.Where(d => d.Id == "CS1066").ToList();
|
||||
|
||||
// CS1066 should be suppressed in the final diagnostics
|
||||
var unsuppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && !d.IsSuppressed).ToList();
|
||||
var suppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && d.IsSuppressed).ToList();
|
||||
|
||||
Assert.True(cs1066FromCompiler.Count > 0 || suppressedCs1066.Count > 0,
|
||||
$"Expected CS1066 diagnostics. Compiler diagnostics: {string.Join(", ", result.CompilerDiagnostics.Select(d => d.Id))}");
|
||||
Assert.Empty(unsuppressedCs1066);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Suppressor_WithMcpServerPromptAttribute_SuppressesCS1066()
|
||||
{
|
||||
var result = RunSuppressor("""
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace Test;
|
||||
|
||||
[McpServerPromptType]
|
||||
public partial class TestPrompts
|
||||
{
|
||||
[McpServerPrompt]
|
||||
public partial string TestPrompt(string input = "default");
|
||||
}
|
||||
|
||||
public partial class TestPrompts
|
||||
{
|
||||
public partial string TestPrompt(string input = "default")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// Check we have the CS1066 diagnostics from compiler
|
||||
var cs1066FromCompiler = result.CompilerDiagnostics.Where(d => d.Id == "CS1066").ToList();
|
||||
|
||||
// CS1066 should be suppressed in the final diagnostics
|
||||
var unsuppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && !d.IsSuppressed).ToList();
|
||||
var suppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && d.IsSuppressed).ToList();
|
||||
|
||||
Assert.True(cs1066FromCompiler.Count > 0 || suppressedCs1066.Count > 0,
|
||||
$"Expected CS1066 diagnostics. Compiler diagnostics: {string.Join(", ", result.CompilerDiagnostics.Select(d => d.Id))}");
|
||||
Assert.Empty(unsuppressedCs1066);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Suppressor_WithMcpServerResourceAttribute_SuppressesCS1066()
|
||||
{
|
||||
var result = RunSuppressor("""
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace Test;
|
||||
|
||||
[McpServerResourceType]
|
||||
public partial class TestResources
|
||||
{
|
||||
[McpServerResource("test://resource")]
|
||||
public partial string TestResource(string input = "default");
|
||||
}
|
||||
|
||||
public partial class TestResources
|
||||
{
|
||||
public partial string TestResource(string input = "default")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// Check we have the CS1066 diagnostics from compiler
|
||||
var cs1066FromCompiler = result.CompilerDiagnostics.Where(d => d.Id == "CS1066").ToList();
|
||||
|
||||
// CS1066 should be suppressed in the final diagnostics
|
||||
var unsuppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && !d.IsSuppressed).ToList();
|
||||
var suppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && d.IsSuppressed).ToList();
|
||||
|
||||
Assert.True(cs1066FromCompiler.Count > 0 || suppressedCs1066.Count > 0,
|
||||
$"Expected CS1066 diagnostics. Compiler diagnostics: {string.Join(", ", result.CompilerDiagnostics.Select(d => d.Id))}");
|
||||
Assert.Empty(unsuppressedCs1066);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Suppressor_WithoutMcpAttribute_DoesNotSuppressCS1066()
|
||||
{
|
||||
var result = RunSuppressor("""
|
||||
namespace Test;
|
||||
|
||||
public partial class TestTools
|
||||
{
|
||||
public partial string TestMethod(string input = "default");
|
||||
}
|
||||
|
||||
public partial class TestTools
|
||||
{
|
||||
public partial string TestMethod(string input = "default")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// CS1066 should NOT be suppressed (no MCP attribute)
|
||||
// Check we have the CS1066 diagnostic from compiler
|
||||
var cs1066FromCompiler = result.CompilerDiagnostics.Where(d => d.Id == "CS1066").ToList();
|
||||
Assert.NotEmpty(cs1066FromCompiler);
|
||||
|
||||
// It should NOT be suppressed in the final diagnostics (still present as unsuppressed)
|
||||
var unsuppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && !d.IsSuppressed).ToList();
|
||||
Assert.NotEmpty(unsuppressedCs1066);
|
||||
Assert.DoesNotContain(result.Diagnostics, d => d.Id == "CS1066" && d.IsSuppressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Suppressor_WithMultipleParameters_SuppressesAllCS1066()
|
||||
{
|
||||
var result = RunSuppressor("""
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace Test;
|
||||
|
||||
[McpServerToolType]
|
||||
public partial class TestTools
|
||||
{
|
||||
[McpServerTool]
|
||||
public partial string TestMethod(string input = "default", int count = 42, bool flag = false);
|
||||
}
|
||||
|
||||
public partial class TestTools
|
||||
{
|
||||
public partial string TestMethod(string input = "default", int count = 42, bool flag = false)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
// Check we have CS1066 diagnostics from compiler (one per parameter with default)
|
||||
var cs1066FromCompiler = result.CompilerDiagnostics.Where(d => d.Id == "CS1066").ToList();
|
||||
Assert.Equal(3, cs1066FromCompiler.Count); // Three parameters with defaults
|
||||
|
||||
// All CS1066 warnings should be suppressed
|
||||
var unsuppressedCs1066 = result.Diagnostics.Where(d => d.Id == "CS1066" && !d.IsSuppressed).ToList();
|
||||
Assert.Empty(unsuppressedCs1066);
|
||||
}
|
||||
|
||||
private SuppressorResult RunSuppressor(string source)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
// Get reference assemblies
|
||||
List<MetadataReference> referenceList =
|
||||
[
|
||||
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(System.ComponentModel.DescriptionAttribute).Assembly.Location),
|
||||
];
|
||||
|
||||
// Add all necessary runtime assemblies
|
||||
var runtimePath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
|
||||
referenceList.Add(MetadataReference.CreateFromFile(Path.Combine(runtimePath, "System.Runtime.dll")));
|
||||
referenceList.Add(MetadataReference.CreateFromFile(Path.Combine(runtimePath, "netstandard.dll")));
|
||||
|
||||
// Add ModelContextProtocol.Core if available
|
||||
var coreAssemblyPath = Path.Combine(AppContext.BaseDirectory, "ModelContextProtocol.Core.dll");
|
||||
if (File.Exists(coreAssemblyPath))
|
||||
{
|
||||
referenceList.Add(MetadataReference.CreateFromFile(coreAssemblyPath));
|
||||
}
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
"TestAssembly",
|
||||
[syntaxTree],
|
||||
referenceList,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
// Get compilation diagnostics first (includes CS1066)
|
||||
var compilerDiagnostics = compilation.GetDiagnostics();
|
||||
|
||||
// Run the suppressor
|
||||
var analyzers = ImmutableArray.Create<DiagnosticAnalyzer>(new CS1066Suppressor());
|
||||
var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers);
|
||||
var allDiagnostics = compilationWithAnalyzers.GetAllDiagnosticsAsync().GetAwaiter().GetResult();
|
||||
|
||||
return new SuppressorResult
|
||||
{
|
||||
Diagnostics = allDiagnostics.ToList(),
|
||||
CompilerDiagnostics = compilerDiagnostics.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private class SuppressorResult
|
||||
{
|
||||
public List<Diagnostic> Diagnostics { get; set; } = [];
|
||||
public List<Diagnostic> CompilerDiagnostics { get; set; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -347,7 +349,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -374,7 +376,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -405,7 +407,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -434,7 +436,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -552,7 +554,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -583,7 +585,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -614,7 +616,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP002");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Empty(result.GeneratedSources);
|
||||
|
|
@ -695,7 +697,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
return input;
|
||||
}
|
||||
}
|
||||
""");
|
||||
""", "MCP001");
|
||||
|
||||
// Should not throw, generates partial implementation without Description attributes
|
||||
Assert.True(result.Success);
|
||||
|
|
@ -1717,7 +1719,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source)
|
||||
private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
|
||||
|
|
@ -1755,15 +1757,34 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
|
||||
var driver = (CSharpGeneratorDriver)CSharpGeneratorDriver
|
||||
.Create(new XmlToDescriptionGenerator())
|
||||
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
|
||||
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics);
|
||||
|
||||
var runResult = driver.GetRunResult();
|
||||
|
||||
// Run the suppressor to check that CS1066 warnings for MCP methods are suppressed
|
||||
var analyzers = ImmutableArray.Create<DiagnosticAnalyzer>(new CS1066Suppressor());
|
||||
var compilationWithAnalyzers = outputCompilation.WithAnalyzers(analyzers);
|
||||
var allDiagnostics = compilationWithAnalyzers.GetAllDiagnosticsAsync().GetAwaiter().GetResult();
|
||||
|
||||
// Check for any unsuppressed CS1066 warnings - these should be suppressed by our suppressor
|
||||
var unsuppressedCs1066 = allDiagnostics
|
||||
.Where(d => d.Id == "CS1066" && !d.IsSuppressed)
|
||||
.ToList();
|
||||
|
||||
// Collect all diagnostics from the generator (any verbosity level)
|
||||
var allGeneratorDiagnostics = generatorDiagnostics.Concat(unsuppressedCs1066).ToList();
|
||||
|
||||
// Check for unexpected diagnostics - any diagnostic that isn't in the expected list
|
||||
var expectedSet = new HashSet<string>(expectedDiagnosticIds);
|
||||
var unexpectedDiagnostics = allGeneratorDiagnostics
|
||||
.Where(d => !expectedSet.Contains(d.Id))
|
||||
.ToList();
|
||||
|
||||
return new GeneratorRunResult
|
||||
{
|
||||
Success = !diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error),
|
||||
Success = unexpectedDiagnostics.Count == 0,
|
||||
GeneratedSources = runResult.GeneratedTrees.Select(t => (t.FilePath, t.GetText())).ToList(),
|
||||
Diagnostics = diagnostics.ToList(),
|
||||
Diagnostics = allGeneratorDiagnostics,
|
||||
Compilation = outputCompilation
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue