Skip to content

Commit a567850

Browse files
authored
feat: Introduce project mode compilation (#147)
1 parent 6d48239 commit a567850

File tree

98 files changed

+560
-290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+560
-290
lines changed

src/Analyzers/Analyzers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0"/>
15-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0"/>
15+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
1616
</ItemGroup>
1717

1818
</Project>

src/Compiling/CompilerOptions.cs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,49 @@ namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling;
99

1010
public class CompilerOptions
1111
{
12-
public string SourceFolder { get; }
13-
public string OutputFolder { get; }
12+
public string SourcePath { get; }
13+
public string OutputPath { get; }
1414
public bool Format { get; }
1515
public string FileExtension { get; }
1616

17-
public XmlWriterSettings XmlWriterSettings => new XmlWriterSettings()
17+
public XmlWriterSettings XmlWriterSettings => new()
1818
{
1919
OmitXmlDeclaration = true, ConformanceLevel = ConformanceLevel.Fragment, Indent = Format
2020
};
2121

2222
public CompilerOptions(IConfigurationRoot configuration)
2323
{
24-
SourceFolder = configuration["s"] ??
25-
configuration["source"] ??
26-
throw new NullReferenceException("Source folder not provided");
27-
SourceFolder = Path.GetFullPath(SourceFolder);
28-
OutputFolder = configuration["o"] ??
29-
configuration["out"] ??
30-
throw new NullReferenceException("Output folder not provided");
31-
OutputFolder = Path.GetFullPath(OutputFolder);
24+
SourcePath = configuration["s"] ??
25+
configuration["source"] ??
26+
throw new NullReferenceException("Source path not provided");
27+
SourcePath = Path.GetFullPath(SourcePath);
28+
OutputPath = configuration["o"] ??
29+
configuration["out"] ??
30+
throw new NullReferenceException("Output path not provided");
31+
OutputPath = Path.GetFullPath(OutputPath);
3232

3333
FileExtension = configuration["ext"] ?? "xml";
3434
Format = bool.TryParse(configuration["format"] ?? "true", out var fmt) && fmt;
3535
}
36+
37+
public bool IsProjectSource =>
38+
Path.GetExtension(SourcePath)?.Equals(".csproj", StringComparison.OrdinalIgnoreCase) == true;
39+
40+
public DirectoryCompilerOptions ToDirectoryCompilerOptions() => new()
41+
{
42+
SourceFolder = SourcePath,
43+
OutputFolder = OutputPath,
44+
FormatCode = Format,
45+
FileExtension = FileExtension,
46+
XmlWriterSettings = XmlWriterSettings,
47+
};
48+
49+
public ProjectCompilerOptions ToProjectCompilerOptions() => new()
50+
{
51+
ProjectPath = SourcePath,
52+
OutputFolder = OutputPath,
53+
FormatCode = Format,
54+
FileExtension = FileExtension,
55+
XmlWriterSettings = XmlWriterSettings,
56+
};
3657
}

src/Compiling/Compiling.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636

3737
<ItemGroup>
3838
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.1"/>
39-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0"/>
4039
</ItemGroup>
4140

4241
<ItemGroup>

src/Compiling/Program.cs

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Reflection;
5-
using System.Text;
6-
using System.Text.RegularExpressions;
7-
using System.Xml.Linq;
8-
94
using Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling;
105
using Microsoft.Azure.ApiManagement.PolicyToolkit.IoC;
11-
using Microsoft.Azure.ApiManagement.PolicyToolkit.Serialization;
12-
using Microsoft.CodeAnalysis;
13-
using Microsoft.CodeAnalysis.CSharp;
14-
using Microsoft.CodeAnalysis.CSharp.Syntax;
156
using Microsoft.Extensions.Configuration;
167
using Microsoft.Extensions.DependencyInjection;
178

@@ -20,74 +11,22 @@
2011
.Build();
2112
var options = new CompilerOptions(config);
2213

23-
var files = Directory.GetFiles(options.SourceFolder, "*.cs", SearchOption.AllDirectories)
24-
.Where(p => !Regex.IsMatch(p, @".*[\\/](obj|bin)[\\/].*"));
25-
26-
ServiceCollection serviceCollection = new();
27-
using ServiceProvider serviceProvider = serviceCollection
14+
await using ServiceProvider serviceProvider = new ServiceCollection()
2815
.SetupCompiler()
2916
.BuildServiceProvider();
30-
CSharpPolicyCompiler compiler = serviceProvider.GetRequiredService<CSharpPolicyCompiler>();
3117

32-
int numberOfErrors = 0;
33-
34-
foreach (var file in files)
18+
if (options.IsProjectSource)
3519
{
36-
Console.Out.WriteLine($"File '{file}' Processing");
37-
var code = File.ReadAllText(file);
38-
var syntax = CSharpSyntaxTree.ParseText(code, path: file);
39-
40-
var documents = syntax.GetRoot()
41-
.DescendantNodes()
42-
.OfType<ClassDeclarationSyntax>()
43-
.Where(c => c.AttributeLists.ContainsAttributeOfType("Document"));
44-
foreach (var document in documents)
45-
{
46-
ICompilationResult result = compiler.Compile(document);
47-
48-
var formatter = new DiagnosticFormatter();
49-
numberOfErrors += result.Diagnostics.Count;
50-
foreach (var error in result.Diagnostics)
51-
{
52-
Console.Error.WriteLine(formatter.Format(error));
53-
}
54-
55-
var codeBuilder = new StringBuilder();
56-
using (var writer = CustomXmlWriter.Create(codeBuilder, options.XmlWriterSettings))
57-
{
58-
writer.Write(new XComment(" This file is generated by the Azure API Management Policy Toolkit "));
59-
writer.Write(new XComment($" Version: {Assembly.GetExecutingAssembly().GetName().Version} "));
60-
writer.Write(result.Document);
61-
}
62-
63-
var xml = codeBuilder.ToString();
64-
if (options.Format)
65-
{
66-
xml = RazorCodeFormatter.Format(xml);
67-
}
68-
69-
var policyFileName = document.ExtractDocumentFileName();
70-
var extension = Path.GetExtension(policyFileName);
71-
if (string.IsNullOrWhiteSpace(extension))
72-
{
73-
policyFileName = $"{policyFileName}.{options.FileExtension}";
74-
}
75-
76-
var fileRelativePath = Path.GetDirectoryName(Path.GetRelativePath(options.SourceFolder, file))!;
77-
var targetFolder = Path.Combine(options.OutputFolder, fileRelativePath);
78-
var targetFile = Path.Combine(targetFolder, policyFileName);
79-
var directoryPath = Path.GetDirectoryName(targetFile);
80-
81-
if (directoryPath is not null && !Directory.Exists(directoryPath))
82-
{
83-
Directory.CreateDirectory(directoryPath);
84-
}
85-
86-
File.WriteAllText(targetFile, xml);
87-
Console.Out.WriteLine($"File '{targetFile}' created");
88-
}
89-
90-
Console.Out.WriteLine($"File '{file}' processed");
20+
await Console.Out.WriteLineAsync("Project mode");
21+
var compiler = serviceProvider.GetRequiredService<ProjectCompiler>();
22+
var result = await compiler.Compile(options.ToProjectCompilerOptions());
23+
return result.DocumentResults.Sum(r => r.Diagnostics.Count);
9124
}
92-
93-
return numberOfErrors;
25+
else
26+
{
27+
await Console.Error.WriteLineAsync(
28+
"Directory mode is deprecated. Please use project mode by pointing source parameter to .csproj file");
29+
var compiler = serviceProvider.GetRequiredService<DirectoryCompiler>();
30+
var result = await compiler.Compile(options.ToDirectoryCompilerOptions());
31+
return result.DocumentResults.Sum(r => r.Diagnostics.Count);
32+
}

src/Core/Compiling/CompilationContext.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/Core/Compiling/CompilerUtils.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling;
1212

1313
public static class CompilerUtils
1414
{
15-
public static string ProcessParameter(this ExpressionSyntax expression, ICompilationContext context)
15+
public static string ProcessParameter(this ExpressionSyntax expression, IDocumentCompilationContext context)
1616
{
1717
switch (expression)
1818
{
@@ -43,7 +43,7 @@ public static string ProcessParameter(this ExpressionSyntax expression, ICompila
4343
}
4444
}
4545

46-
public static string FindCode(this InvocationExpressionSyntax syntax, ICompilationContext context)
46+
public static string FindCode(this InvocationExpressionSyntax syntax, IDocumentCompilationContext context)
4747
{
4848
if (syntax.Expression is not IdentifierNameSyntax identifierSyntax)
4949
{
@@ -77,7 +77,7 @@ public static string FindCode(this InvocationExpressionSyntax syntax, ICompilati
7777

7878
public static InitializerValue Process(
7979
this ObjectCreationExpressionSyntax creationSyntax,
80-
ICompilationContext context)
80+
IDocumentCompilationContext context)
8181
{
8282
var result = new Dictionary<string, InitializerValue>();
8383
if (creationSyntax.Initializer is null)
@@ -113,7 +113,7 @@ public static InitializerValue Process(
113113

114114
public static InitializerValue Process(
115115
this ArrayCreationExpressionSyntax creationSyntax,
116-
ICompilationContext context)
116+
IDocumentCompilationContext context)
117117
{
118118
var expressions = creationSyntax.Initializer?.Expressions ?? [];
119119
var result = expressions
@@ -130,7 +130,7 @@ public static InitializerValue Process(
130130

131131
public static InitializerValue Process(
132132
this CollectionExpressionSyntax collectionSyntax,
133-
ICompilationContext context)
133+
IDocumentCompilationContext context)
134134
{
135135
var result = collectionSyntax.Elements
136136
.OfType<ExpressionElementSyntax>()
@@ -142,7 +142,7 @@ public static InitializerValue Process(
142142

143143
public static InitializerValue Process(
144144
this ImplicitArrayCreationExpressionSyntax creationSyntax,
145-
ICompilationContext context)
145+
IDocumentCompilationContext context)
146146
{
147147
var result = creationSyntax.Initializer.Expressions
148148
.Select(expression => expression.ProcessExpression(context))
@@ -153,7 +153,7 @@ public static InitializerValue Process(
153153

154154
public static InitializerValue ProcessExpression(
155155
this ExpressionSyntax expression,
156-
ICompilationContext context)
156+
IDocumentCompilationContext context)
157157
{
158158
return expression switch
159159
{
@@ -179,7 +179,7 @@ public static bool AddAttribute(this XElement element, IReadOnlyDictionary<strin
179179

180180
public static bool TryExtractingConfigParameter<T>(
181181
this InvocationExpressionSyntax node,
182-
ICompilationContext context,
182+
IDocumentCompilationContext context,
183183
string policy,
184184
[NotNullWhen(true)] out IReadOnlyDictionary<string, InitializerValue>? values)
185185
{
@@ -198,7 +198,7 @@ public static bool TryExtractingConfigParameter<T>(
198198
}
199199

200200
public static bool TryExtractingConfig<T>(this ExpressionSyntax syntax,
201-
ICompilationContext context,
201+
IDocumentCompilationContext context,
202202
string policy,
203203
[NotNullWhen(true)] out IReadOnlyDictionary<string, InitializerValue>? values)
204204
{
@@ -230,12 +230,19 @@ public static bool TryExtractingConfig<T>(this ExpressionSyntax syntax,
230230
return true;
231231
}
232232

233-
public static string ExtractDocumentFileName(this ClassDeclarationSyntax document)
233+
public static string ExtractDocumentFileName(this ClassDeclarationSyntax document, string extension)
234234
{
235235
var attributeSyntax = document.AttributeLists.GetFirstAttributeOfType("Document");
236236
var attributeArgumentExpression =
237237
attributeSyntax?.ArgumentList?.Arguments.FirstOrDefault()?.Expression as LiteralExpressionSyntax;
238-
return attributeArgumentExpression?.Token.ValueText ?? document.Identifier.ValueText;
238+
var path = attributeArgumentExpression?.Token.ValueText ?? document.Identifier.ValueText;
239+
var currentExtension = Path.GetExtension(path);
240+
if (string.IsNullOrWhiteSpace(currentExtension))
241+
{
242+
path = $"{path}.{extension}";
243+
}
244+
245+
return path;
239246
}
240247

241248
public static T Normalize<T>(T node) where T : SyntaxNode
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Xml.Linq;
5+
6+
using Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
10+
namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Compiling;
11+
12+
public class DirectoryCompiler(DocumentCompiler compiler)
13+
{
14+
private static readonly IEnumerable<MetadataReference> References =
15+
[
16+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
17+
MetadataReference.CreateFromFile(typeof(XElement).Assembly.Location),
18+
MetadataReference.CreateFromFile(typeof(IDocument).Assembly.Location)
19+
];
20+
21+
public Task<DirectoryCompilerResult> Compile(DirectoryCompilerOptions options)
22+
{
23+
var files = Directory.GetFiles(options.SourceFolder, "*.cs", SearchOption.AllDirectories)
24+
.Where(p => !FileUtils.InObjOrBinFolder.IsMatch(p));
25+
26+
DirectoryCompilerResult result = new();
27+
foreach (var file in files)
28+
{
29+
Console.Out.WriteLine($"File '{file}' processing");
30+
var code = File.ReadAllText(file);
31+
var syntax = CSharpSyntaxTree.ParseText(code, path: file);
32+
var compilation = CSharpCompilation.Create(
33+
file,
34+
syntaxTrees: [syntax],
35+
references: References);
36+
37+
var documents = syntax.GetRoot()
38+
.GetDocumentAttributedClasses();
39+
40+
foreach (var document in documents)
41+
{
42+
var policyFileName = document.ExtractDocumentFileName(options.FileExtension);
43+
IDocumentCompilationResult documentResult = compiler.Compile(compilation, document);
44+
result.DocumentResults.Add(documentResult);
45+
46+
foreach (var error in documentResult.Diagnostics)
47+
{
48+
Console.Error.WriteLine(error.ToString());
49+
}
50+
51+
var targetFile = FileUtils.WriteToFile(new FileUtils.Data()
52+
{
53+
Element = documentResult.Document,
54+
SourceFolder = options.SourceFolder,
55+
SourceFilePath = file,
56+
OutputFolder = options.OutputFolder,
57+
OutputFilePath = policyFileName,
58+
FormatCode = options.FormatCode,
59+
XmlWriterSettings = options.XmlWriterSettings,
60+
});
61+
Console.Out.WriteLine($"File '{targetFile}' created");
62+
}
63+
64+
Console.Out.WriteLine($"File '{file}' processed");
65+
}
66+
67+
return Task.FromResult(result);
68+
}
69+
}

0 commit comments

Comments
 (0)