Add CS1066 suppressor for MCP server methods with optional parameters (#1110)
This commit is contained in:
parent
8b73a42c9d
commit
8316aa0895
1 changed files with 429 additions and 20 deletions
|
|
@ -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; } = [];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue