Add unit tests for Router configuration and transport layers
- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly. - Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified. - Created tests for ConfigValidationResult to check success and error scenarios. - Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig. - Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport. - Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace StellaOps.Microservice.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic descriptors for the source generator.
|
||||
/// </summary>
|
||||
internal static class DiagnosticDescriptors
|
||||
{
|
||||
private const string Category = "StellaOps.Microservice";
|
||||
|
||||
/// <summary>
|
||||
/// Class with [StellaEndpoint] must implement IStellaEndpoint or IRawStellaEndpoint.
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor MissingHandlerInterface = new(
|
||||
id: "STELLA001",
|
||||
title: "Missing handler interface",
|
||||
messageFormat: "Class '{0}' with [StellaEndpoint] must implement IStellaEndpoint<TRequest, TResponse> or IRawStellaEndpoint",
|
||||
category: Category,
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate endpoint detected.
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor DuplicateEndpoint = new(
|
||||
id: "STELLA002",
|
||||
title: "Duplicate endpoint",
|
||||
messageFormat: "Duplicate endpoint: {0} {1} is defined in both '{2}' and '{3}'",
|
||||
category: Category,
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
/// <summary>
|
||||
/// [StellaEndpoint] on abstract class is ignored.
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor AbstractClassIgnored = new(
|
||||
id: "STELLA003",
|
||||
title: "Abstract class ignored",
|
||||
messageFormat: "[StellaEndpoint] on abstract class '{0}' is ignored",
|
||||
category: Category,
|
||||
defaultSeverity: DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
/// <summary>
|
||||
/// Informational: endpoints generated.
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor EndpointsGenerated = new(
|
||||
id: "STELLA004",
|
||||
title: "Endpoints generated",
|
||||
messageFormat: "Generated {0} endpoint descriptors",
|
||||
category: Category,
|
||||
defaultSeverity: DiagnosticSeverity.Info,
|
||||
isEnabledByDefault: false);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace StellaOps.Microservice.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// Holds extracted endpoint information from a [StellaEndpoint] decorated class.
|
||||
/// </summary>
|
||||
internal sealed record EndpointInfo(
|
||||
string Namespace,
|
||||
string ClassName,
|
||||
string FullyQualifiedName,
|
||||
string Method,
|
||||
string Path,
|
||||
int TimeoutSeconds,
|
||||
bool SupportsStreaming,
|
||||
string[] RequiredClaims,
|
||||
string? RequestTypeName,
|
||||
string? ResponseTypeName,
|
||||
bool IsRaw);
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace StellaOps.Microservice.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder type for the source generator project.
|
||||
/// This will be replaced with actual source generator implementation in a later sprint.
|
||||
/// </summary>
|
||||
public static class Placeholder
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the source generator is not yet implemented.
|
||||
/// </summary>
|
||||
public const string Status = "NotImplemented";
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Polyfills for netstandard2.0 compatibility
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows use of init accessors in netstandard2.0.
|
||||
/// </summary>
|
||||
internal static class IsExternalInit { }
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace StellaOps.Microservice.SourceGen;
|
||||
|
||||
/// <summary>
|
||||
/// Incremental source generator for [StellaEndpoint] decorated classes.
|
||||
/// Generates endpoint descriptors and DI registration at compile time.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public sealed class StellaEndpointGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string StellaEndpointAttributeName = "StellaOps.Microservice.StellaEndpointAttribute";
|
||||
private const string IStellaEndpointName = "StellaOps.Microservice.IStellaEndpoint";
|
||||
private const string IRawStellaEndpointName = "StellaOps.Microservice.IRawStellaEndpoint";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// Find all class declarations with attributes
|
||||
var classDeclarations = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
|
||||
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
|
||||
.Where(static m => m is not null);
|
||||
|
||||
// Combine all endpoints and generate
|
||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
||||
|
||||
context.RegisterSourceOutput(
|
||||
compilationAndClasses,
|
||||
static (spc, source) => Execute(source.Left, source.Right!, spc));
|
||||
}
|
||||
|
||||
private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
|
||||
{
|
||||
return node is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDecl
|
||||
&& !classDecl.Modifiers.Any(SyntaxKind.AbstractKeyword);
|
||||
}
|
||||
|
||||
private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
|
||||
{
|
||||
var classDeclaration = (ClassDeclarationSyntax)context.Node;
|
||||
|
||||
foreach (var attributeList in classDeclaration.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
var symbolInfo = context.SemanticModel.GetSymbolInfo(attribute);
|
||||
var symbol = symbolInfo.Symbol;
|
||||
|
||||
if (symbol is not IMethodSymbol attributeSymbol)
|
||||
continue;
|
||||
|
||||
var attributeContainingType = attributeSymbol.ContainingType;
|
||||
var fullName = attributeContainingType.ToDisplayString();
|
||||
|
||||
if (fullName == StellaEndpointAttributeName)
|
||||
{
|
||||
return classDeclaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void Execute(
|
||||
Compilation compilation,
|
||||
ImmutableArray<ClassDeclarationSyntax?> classes,
|
||||
SourceProductionContext context)
|
||||
{
|
||||
if (classes.IsDefaultOrEmpty)
|
||||
return;
|
||||
|
||||
var distinctClasses = classes.Where(c => c is not null).Distinct().Cast<ClassDeclarationSyntax>();
|
||||
var endpoints = new List<EndpointInfo>();
|
||||
|
||||
foreach (var classDeclaration in distinctClasses)
|
||||
{
|
||||
var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
|
||||
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
|
||||
|
||||
if (classSymbol is null)
|
||||
continue;
|
||||
|
||||
var endpoint = ExtractEndpointInfo(classSymbol, context);
|
||||
if (endpoint is not null)
|
||||
{
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (endpoints.Count == 0)
|
||||
return;
|
||||
|
||||
// Check for duplicates
|
||||
var seen = new Dictionary<(string Method, string Path), EndpointInfo>();
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
var key = (endpoint.Method, endpoint.Path);
|
||||
if (seen.TryGetValue(key, out var existing))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.DuplicateEndpoint,
|
||||
Location.None,
|
||||
endpoint.Method,
|
||||
endpoint.Path,
|
||||
existing.ClassName,
|
||||
endpoint.ClassName));
|
||||
}
|
||||
else
|
||||
{
|
||||
seen[key] = endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the source
|
||||
var source = GenerateEndpointsClass(endpoints);
|
||||
context.AddSource("StellaEndpoints.g.cs", SourceText.From(source, Encoding.UTF8));
|
||||
|
||||
// Generate the provider class
|
||||
var providerSource = GenerateProviderClass();
|
||||
context.AddSource("GeneratedEndpointProvider.g.cs", SourceText.From(providerSource, Encoding.UTF8));
|
||||
}
|
||||
|
||||
private static EndpointInfo? ExtractEndpointInfo(INamedTypeSymbol classSymbol, SourceProductionContext context)
|
||||
{
|
||||
// Find StellaEndpoint attribute
|
||||
AttributeData? stellaAttribute = null;
|
||||
foreach (var attr in classSymbol.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass?.ToDisplayString() == StellaEndpointAttributeName)
|
||||
{
|
||||
stellaAttribute = attr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stellaAttribute is null)
|
||||
return null;
|
||||
|
||||
// Check for abstract class
|
||||
if (classSymbol.IsAbstract)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.AbstractClassIgnored,
|
||||
Location.None,
|
||||
classSymbol.Name));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract constructor arguments: method and path
|
||||
if (stellaAttribute.ConstructorArguments.Length < 2)
|
||||
return null;
|
||||
|
||||
var method = stellaAttribute.ConstructorArguments[0].Value as string ?? "GET";
|
||||
var path = stellaAttribute.ConstructorArguments[1].Value as string ?? "/";
|
||||
|
||||
// Extract named arguments
|
||||
var timeoutSeconds = 30;
|
||||
var supportsStreaming = false;
|
||||
var requiredClaims = Array.Empty<string>();
|
||||
|
||||
foreach (var namedArg in stellaAttribute.NamedArguments)
|
||||
{
|
||||
switch (namedArg.Key)
|
||||
{
|
||||
case "TimeoutSeconds":
|
||||
timeoutSeconds = (int)(namedArg.Value.Value ?? 30);
|
||||
break;
|
||||
case "SupportsStreaming":
|
||||
supportsStreaming = (bool)(namedArg.Value.Value ?? false);
|
||||
break;
|
||||
case "RequiredClaims":
|
||||
if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
|
||||
{
|
||||
requiredClaims = namedArg.Value.Values
|
||||
.Select(v => v.Value as string)
|
||||
.Where(s => s is not null)
|
||||
.Cast<string>()
|
||||
.ToArray();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find handler interface implementation
|
||||
string? requestTypeName = null;
|
||||
string? responseTypeName = null;
|
||||
bool isRaw = false;
|
||||
|
||||
foreach (var iface in classSymbol.AllInterfaces)
|
||||
{
|
||||
var fullName = iface.OriginalDefinition.ToDisplayString();
|
||||
|
||||
if (fullName.StartsWith(IStellaEndpointName) && iface.TypeArguments.Length == 2)
|
||||
{
|
||||
requestTypeName = iface.TypeArguments[0].ToDisplayString();
|
||||
responseTypeName = iface.TypeArguments[1].ToDisplayString();
|
||||
isRaw = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fullName == IRawStellaEndpointName)
|
||||
{
|
||||
isRaw = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no handler interface found, report error
|
||||
if (!isRaw && requestTypeName is null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.MissingHandlerInterface,
|
||||
Location.None,
|
||||
classSymbol.Name));
|
||||
return null;
|
||||
}
|
||||
|
||||
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: classSymbol.ContainingNamespace.ToDisplayString();
|
||||
|
||||
return new EndpointInfo(
|
||||
Namespace: ns,
|
||||
ClassName: classSymbol.Name,
|
||||
FullyQualifiedName: classSymbol.ToDisplayString(),
|
||||
Method: method.ToUpperInvariant(),
|
||||
Path: path,
|
||||
TimeoutSeconds: timeoutSeconds,
|
||||
SupportsStreaming: supportsStreaming,
|
||||
RequiredClaims: requiredClaims,
|
||||
RequestTypeName: requestTypeName,
|
||||
ResponseTypeName: responseTypeName,
|
||||
IsRaw: isRaw);
|
||||
}
|
||||
|
||||
private static string GenerateEndpointsClass(List<EndpointInfo> endpoints)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("// <auto-generated/>");
|
||||
sb.AppendLine("#nullable enable");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace StellaOps.Microservice.Generated");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Auto-generated endpoint metadata and registration.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" [global::System.CodeDom.Compiler.GeneratedCode(\"StellaOps.Microservice.SourceGen\", \"1.0.0\")]");
|
||||
sb.AppendLine(" internal static class StellaEndpoints");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
// GetEndpoints method
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Gets all discovered endpoint descriptors.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static global::System.Collections.Generic.IReadOnlyList<global::StellaOps.Router.Common.Models.EndpointDescriptor> GetEndpoints()");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" return new global::StellaOps.Router.Common.Models.EndpointDescriptor[]");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
for (int i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var ep = endpoints[i];
|
||||
sb.AppendLine(" new global::StellaOps.Router.Common.Models.EndpointDescriptor");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" ServiceName = string.Empty, // Set by SDK at registration");
|
||||
sb.AppendLine(" Version = string.Empty, // Set by SDK at registration");
|
||||
sb.AppendLine($" Method = \"{EscapeString(ep.Method)}\",");
|
||||
sb.AppendLine($" Path = \"{EscapeString(ep.Path)}\",");
|
||||
sb.AppendLine($" DefaultTimeout = global::System.TimeSpan.FromSeconds({ep.TimeoutSeconds}),");
|
||||
sb.AppendLine($" SupportsStreaming = {(ep.SupportsStreaming ? "true" : "false")},");
|
||||
sb.Append(" RequiringClaims = ");
|
||||
if (ep.RequiredClaims.Length == 0)
|
||||
{
|
||||
sb.AppendLine("new global::System.Collections.Generic.List<global::StellaOps.Router.Common.Models.ClaimRequirement>(),");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("new global::System.Collections.Generic.List<global::StellaOps.Router.Common.Models.ClaimRequirement>");
|
||||
sb.AppendLine(" {");
|
||||
foreach (var claim in ep.RequiredClaims)
|
||||
{
|
||||
sb.AppendLine($" new global::StellaOps.Router.Common.Models.ClaimRequirement {{ Type = \"{EscapeString(claim)}\", Value = null }},");
|
||||
}
|
||||
sb.AppendLine(" },");
|
||||
}
|
||||
sb.AppendLine($" HandlerType = typeof(global::{ep.FullyQualifiedName})");
|
||||
sb.Append(" }");
|
||||
if (i < endpoints.Count - 1)
|
||||
{
|
||||
sb.AppendLine(",");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
|
||||
// RegisterHandlers method
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Registers all endpoint handlers with the service collection.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static void RegisterHandlers(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
foreach (var ep in endpoints)
|
||||
{
|
||||
sb.AppendLine($" global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient<global::{ep.FullyQualifiedName}>(services);");
|
||||
}
|
||||
|
||||
sb.AppendLine(" }");
|
||||
|
||||
// GetHandlerTypes method
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Gets all handler types for endpoint discovery.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" public static global::System.Collections.Generic.IReadOnlyList<global::System.Type> GetHandlerTypes()");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" return new global::System.Type[]");
|
||||
sb.AppendLine(" {");
|
||||
|
||||
for (int i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
var ep = endpoints[i];
|
||||
sb.Append($" typeof(global::{ep.FullyQualifiedName})");
|
||||
if (i < endpoints.Count - 1)
|
||||
{
|
||||
sb.AppendLine(",");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine(" }");
|
||||
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateProviderClass()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("// <auto-generated/>");
|
||||
sb.AppendLine("#nullable enable");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace StellaOps.Microservice.Generated");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine(" /// <summary>");
|
||||
sb.AppendLine(" /// Generated implementation of IGeneratedEndpointProvider.");
|
||||
sb.AppendLine(" /// </summary>");
|
||||
sb.AppendLine(" [global::System.CodeDom.Compiler.GeneratedCode(\"StellaOps.Microservice.SourceGen\", \"1.0.0\")]");
|
||||
sb.AppendLine(" internal sealed class GeneratedEndpointProvider : global::StellaOps.Microservice.IGeneratedEndpointProvider");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" /// <inheritdoc />");
|
||||
sb.AppendLine(" public global::System.Collections.Generic.IReadOnlyList<global::StellaOps.Router.Common.Models.EndpointDescriptor> GetEndpoints()");
|
||||
sb.AppendLine(" => StellaEndpoints.GetEndpoints();");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <inheritdoc />");
|
||||
sb.AppendLine(" public void RegisterHandlers(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services)");
|
||||
sb.AppendLine(" => StellaEndpoints.RegisterHandlers(services);");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" /// <inheritdoc />");
|
||||
sb.AppendLine(" public global::System.Collections.Generic.IReadOnlyList<global::System.Type> GetHandlerTypes()");
|
||||
sb.AppendLine(" => StellaEndpoints.GetHandlerTypes();");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string EscapeString(string value)
|
||||
{
|
||||
return value
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<!-- Source generators must target netstandard2.0 for Roslyn compatibility -->
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
||||
<!-- Source generator specific settings -->
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
|
||||
<!-- Package settings for distribution -->
|
||||
<PackageId>StellaOps.Microservice.SourceGen</PackageId>
|
||||
<Description>Source generator for Stella microservice endpoints</Description>
|
||||
<DevelopmentDependency>true</DevelopmentDependency>
|
||||
<IsPackable>true</IsPackable>
|
||||
<NoWarn>$(NoWarn);NU5128;RS2008</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Pack the analyzer as an analyzer -->
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user