Skip to content

Commit

Permalink
feat: use Microsoft.CodeAnalysis.Diagnostics for error messages (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mielek authored Nov 22, 2024
1 parent 2c5ef98 commit a765313
Show file tree
Hide file tree
Showing 53 changed files with 1,123 additions and 305 deletions.
2 changes: 1 addition & 1 deletion example/source/ApiOperationPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Contoso.Apis;

[Document("echo-api.retrieve-resource")]
[Document("echo-api_retrieve-resource")]
public class ApiOperationPolicy : IDocument
{
public void Inbound(IInboundContext c)
Expand Down
2 changes: 2 additions & 0 deletions src/Compiling/CompilerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class CompilerOptions
public string SourceFolder { get; }
public string OutputFolder { get; }
public bool Format { get; }
public string FileExtension { get; }

public XmlWriterSettings XmlWriterSettings => new XmlWriterSettings()
{
Expand All @@ -27,6 +28,7 @@ public CompilerOptions(IConfigurationRoot configuration)
configuration["out"] ??
throw new NullReferenceException("Output folder not provided");

FileExtension = configuration["ext"] ?? "xml";
Format = bool.TryParse(configuration["format"] ?? "false", out var fmt) && fmt;
}
}
84 changes: 42 additions & 42 deletions src/Compiling/Compiling.csproj
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Azure.ApiManagement.PolicyToolkit.Compiling</PackageId>
<PackageVersion>0.0.1</PackageVersion>
<FileVersion>0.0.1</FileVersion>
<AssemblyVersion>0.0.1</AssemblyVersion>
<InformationalVersion>0.0.1</InformationalVersion>
<Authors>Microsoft</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageProjectUrl>https://github.com/Azure/azure-api-management-policy-toolkit</PackageProjectUrl>
<RepositoryUrl>https://github.com/Azure/azure-api-management-policy-toolkit</RepositoryUrl>
<Description>
Azure API Management Policy Toolkit Compiling is a dotnet tool allowing you to transform policy document(s) from C# code to XML.
Read more about it at https://github.com/Azure/azure-api-management-policy-toolkit
</Description>
<PackageTags>Azure;Azure API Management;API Gateway;API Management;Policy;Policies;Policy Toolkit;Compiling;Policy Toolkit Compiling</PackageTags>
</PropertyGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>.net8</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackAsTool>true</PackAsTool>
<ToolCommandName>azure-apim-policy-compiler</ToolCommandName>
<PackageOutputPath>..\..\output</PackageOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj"/>
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Azure.ApiManagement.PolicyToolkit.Compiling</PackageId>
<PackageVersion>0.0.1</PackageVersion>
<FileVersion>0.0.1</FileVersion>
<AssemblyVersion>0.0.1</AssemblyVersion>
<InformationalVersion>0.0.1</InformationalVersion>
<Authors>Microsoft</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageProjectUrl>https://github.com/Azure/azure-api-management-policy-toolkit</PackageProjectUrl>
<RepositoryUrl>https://github.com/Azure/azure-api-management-policy-toolkit</RepositoryUrl>
<Description>
Azure API Management Policy Toolkit Compiling is a dotnet tool allowing you to transform policy document(s) from C# code to XML.
Read more about it at https://github.com/Azure/azure-api-management-policy-toolkit
</Description>
<PackageTags>Azure;Azure API Management;API Gateway;API Management;Policy;Policies;Policy Toolkit;Compiling;Policy Toolkit Compiling</PackageTags>
</PropertyGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>.net8</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackAsTool>true</PackAsTool>
<ToolCommandName>azure-apim-policy-compiler</ToolCommandName>
<PackageOutputPath>..\..\output</PackageOutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj"/>
</ItemGroup>
</Project>
21 changes: 16 additions & 5 deletions src/Compiling/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Azure.ApiManagement.PolicyToolkit.Compiling;
using Azure.ApiManagement.PolicyToolkit.Serialization;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Configuration;
Expand All @@ -19,12 +20,14 @@
var options = new CompilerOptions(config);
var files = Directory.GetFiles(options.SourceFolder, "*.cs", SearchOption.AllDirectories).Where(p => !Regex.IsMatch(p, @".*[\\/](obj|bin)[\\/].*"));

int numberOfErrors = 0;

foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file);
Console.Out.WriteLine($"File {fileName} Processing");
var code = File.ReadAllText(file);
var syntax = CSharpSyntaxTree.ParseText(code);
var syntax = CSharpSyntaxTree.ParseText(code, path: file);

var documents = syntax.GetRoot()
.DescendantNodes()
Expand All @@ -34,9 +37,11 @@
{
var result = new CSharpPolicyCompiler(document).Compile();

foreach (var error in result.Errors)
var formatter = new DiagnosticFormatter();
numberOfErrors += result.Diagnostics.Count;
foreach (var error in result.Diagnostics)
{
Console.Out.WriteLine(error);
Console.Out.WriteLine(formatter.Format(error));
}

var codeBuilder = new StringBuilder();
Expand All @@ -54,7 +59,11 @@
}

var policyFileName = document.ExtractDocumentFileName();
policyFileName = policyFileName.EndsWith(".xml") ? policyFileName : $"{policyFileName}.xml";
var extension = Path.GetExtension(policyFileName);
if (string.IsNullOrWhiteSpace(extension))
{
policyFileName = $"{policyFileName}.{options.FileExtension}";
}

string targetFolder = Path.GetFullPath(Path.Combine(options.OutputFolder, Path.GetFullPath(file).Split(Path.GetFullPath(options.SourceFolder))[1].Replace(Path.GetFileName(file), "")));
var targetFile = Path.Combine(targetFolder, policyFileName);
Expand All @@ -69,4 +78,6 @@
}

Console.Out.WriteLine($"File {fileName} processed");
}
}

return numberOfErrors;
8 changes: 7 additions & 1 deletion src/Core/Compiling/CSharpPolicyCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using System.Xml.Linq;

using Azure.ApiManagement.PolicyToolkit.Authoring;
using Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;
using Azure.ApiManagement.PolicyToolkit.Compiling.Policy;
using Azure.ApiManagement.PolicyToolkit.Compiling.Syntax;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Azure.ApiManagement.PolicyToolkit.Compiling;
Expand Down Expand Up @@ -109,7 +111,11 @@ private void CompileSection(ICompilationContext context, string section, MethodD
{
if (method.Body is null)
{
context.ReportError($"Method {section} is not allowed as expression. ({method.GetLocation()})");
context.Report(Diagnostic.Create(
CompilationErrors.PolicySectionCannotBeExpression,
method.GetLocation(),
method.Identifier.ValueText
));
return;
}

Expand Down
7 changes: 3 additions & 4 deletions src/Core/Compiling/CompilationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Azure.ApiManagement.PolicyToolkit.Compiling;

public class CompilationContext : ICompilationContext, ICompilationResult
{
private readonly IList<string> _errors = new List<string>();
private readonly IList<Diagnostic> _diagnostics = new List<Diagnostic>();
private readonly XElement _rootElement;

public CompilationContext(SyntaxNode syntaxRoot, XElement rootElement)
Expand All @@ -19,12 +19,11 @@ public CompilationContext(SyntaxNode syntaxRoot, XElement rootElement)
}

public void AddPolicy(XNode element) => _rootElement.Add(element);

public void ReportError(string message) => _errors.Add(message);
public void Report(Diagnostic diagnostic) => _diagnostics.Add(diagnostic);

public SyntaxNode SyntaxRoot { get; }

public XElement Document => _rootElement;

public IReadOnlyList<string> Errors => _errors.AsReadOnly();
public IReadOnlyList<Diagnostic> Diagnostics => _diagnostics.AsReadOnly();
}
51 changes: 42 additions & 9 deletions src/Core/Compiling/CompilerUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;

using Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -26,13 +28,19 @@ public static string ProcessParameter(this ExpressionSyntax expression, ICompila
$"{{context.Variables[\"{interpolation.Expression.ToString()}\"]}}",
_ => ""
});
var interpolationExpression = CSharpSyntaxTree.ParseText($"context => $\"{string.Join("", interpolationParts)}\"").GetRoot();
var lambda = interpolationExpression.DescendantNodesAndSelf().OfType<LambdaExpressionSyntax>().FirstOrDefault();
var interpolationExpression = CSharpSyntaxTree
.ParseText($"context => $\"{string.Join("", interpolationParts)}\"").GetRoot();
var lambda = interpolationExpression.DescendantNodesAndSelf().OfType<LambdaExpressionSyntax>()
.FirstOrDefault();
lambda = Normalize(lambda!);
return $"@({lambda.ExpressionBody})";
case InvocationExpressionSyntax syntax:
return FindCode(syntax, context);
default:
context.Report(Diagnostic.Create(
CompilationErrors.NotSupportedParameter,
expression.GetLocation()
));
return "";
}
}
Expand All @@ -41,8 +49,10 @@ public static string FindCode(this InvocationExpressionSyntax syntax, ICompilati
{
if (syntax.Expression is not IdentifierNameSyntax identifierSyntax)
{
context.ReportError(
$"Invalid expression. It should be IdentifierNameSyntax but was {syntax.Expression.GetType()}. {syntax.GetLocation()}");
context.Report(Diagnostic.Create(
CompilationErrors.InvalidExpression,
syntax.GetLocation()
));
return "";
}

Expand Down Expand Up @@ -72,11 +82,22 @@ public static InitializerValue Process(
ICompilationContext context)
{
var result = new Dictionary<string, InitializerValue>();
if (creationSyntax.Initializer is null)
{
context.Report(Diagnostic.Create(
CompilationErrors.PolicyObjectCreationDoesNotContainInitializerSection,
creationSyntax.GetLocation()
));
}

foreach (var expression in creationSyntax.Initializer?.Expressions ?? [])
{
if (expression is not AssignmentExpressionSyntax assignment)
{
context.ReportError($"Is not AssignmentExpressionSyntax. {expression.GetLocation()}");
context.Report(Diagnostic.Create(
CompilationErrors.ObjectInitializerContainsNotAnAssigmentExpression,
expression.GetLocation()
));
continue;
}

Expand Down Expand Up @@ -168,7 +189,10 @@ public static bool TryExtractingConfigParameter<T>(

if (node.ArgumentList.Arguments.Count != 1)
{
context.ReportError($"Wrong argument count for {policy} policy. {node.GetLocation()}");
context.Report(Diagnostic.Create(
CompilationErrors.ArgumentCountMissMatchForPolicy,
node.ArgumentList.GetLocation(),
policy));
return false;
}

Expand All @@ -183,15 +207,24 @@ public static bool TryExtractingConfig<T>(this ExpressionSyntax syntax,
values = null;
if (syntax is not ObjectCreationExpressionSyntax config)
{
context.ReportError(
$"{policy} policy argument must be an object creation expression. {syntax.GetLocation()}");
context.Report(Diagnostic.Create(
CompilationErrors.PolicyArgumentIsNotAnObjectCreation,
syntax.GetLocation(),
policy,
typeof(T).Name
));
return false;
}

var initializer = config.Process(context);
if (!initializer.TryGetValues<T>(out var result))
{
context.ReportError($"{policy} policy argument must be of type {typeof(T).Name}. {syntax.GetLocation()}");
context.Report(Diagnostic.Create(
CompilationErrors.PolicyArgumentIsNotOfRequiredType,
syntax.GetLocation(),
policy,
typeof(T).Name
));
return false;
}

Expand Down
Loading

0 comments on commit a765313

Please sign in to comment.