C#: Move source code generators to dedicated classes

This commit is contained in:
Tamas Vajk
2024-04-15 14:46:25 +02:00
parent 3105697c7f
commit 13a71a4f6d
7 changed files with 216 additions and 114 deletions

View File

@@ -149,24 +149,20 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
logger.LogDebug($"Unresolved reference {r.Key} in project {r.Value}");
}
var webViewExtractionOption = Environment.GetEnvironmentVariable(EnvironmentVariableNames.WebViewGeneration);
if (webViewExtractionOption == null ||
bool.TryParse(webViewExtractionOption, out var shouldExtractWebViews) &&
shouldExtractWebViews)
var sourceGenerators = new ISourceGenerator[]
{
CompilationInfos.Add(("WebView extraction enabled", "1"));
GenerateSourceFilesFromWebViews();
}
else
new ImplicitUsingsGenerator(fileContent, logger, tempWorkingDirectory),
new WebViewGenerator(fileProvider, fileContent, dotnet, this, logger, tempWorkingDirectory, usedReferences.Keys)
};
foreach (var sourceGenerator in sourceGenerators)
{
CompilationInfos.Add(("WebView extraction enabled", "0"));
this.generatedSources.AddRange(sourceGenerator.Generate());
}
CompilationInfos.Add(("UseWPF set", fileContent.UseWpf ? "1" : "0"));
CompilationInfos.Add(("UseWindowsForms set", fileContent.UseWindowsForms ? "1" : "0"));
GenerateSourceFileFromImplicitUsings();
const int align = 6;
logger.LogInfo("");
logger.LogInfo("Build analysis summary:");
@@ -365,14 +361,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
private bool IsAspNetCoreDetected()
{
return fileContent.IsNewProjectStructureUsed && fileContent.UseAspNetCoreDlls;
}
private void AddAspNetCoreFrameworkDlls(ISet<AssemblyLookupLocation> dllLocations, ISet<string> frameworkLocations)
{
if (!IsAspNetCoreDetected())
if (!fileContent.IsAspNetCoreDetected)
{
return;
}
@@ -413,103 +404,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
.FullName;
}
private void GenerateSourceFileFromImplicitUsings()
{
var usings = new HashSet<string>();
if (!fileContent.UseImplicitUsings)
{
return;
}
// Hardcoded values from https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives
usings.UnionWith([ "System", "System.Collections.Generic", "System.IO", "System.Linq", "System.Net.Http", "System.Threading",
"System.Threading.Tasks" ]);
if (fileContent.UseAspNetCoreDlls)
{
usings.UnionWith([ "System.Net.Http.Json", "Microsoft.AspNetCore.Builder", "Microsoft.AspNetCore.Hosting",
"Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Routing", "Microsoft.Extensions.Configuration",
"Microsoft.Extensions.DependencyInjection", "Microsoft.Extensions.Hosting", "Microsoft.Extensions.Logging" ]);
}
if (fileContent.UseWindowsForms)
{
usings.UnionWith(["System.Drawing", "System.Windows.Forms"]);
}
usings.UnionWith(fileContent.CustomImplicitUsings);
logger.LogInfo($"Generating source file for implicit usings. Namespaces: {string.Join(", ", usings.OrderBy(u => u))}");
if (usings.Count > 0)
{
var tempDir = GetTemporaryWorkingDirectory("implicitUsings");
var path = Path.Combine(tempDir, "GlobalUsings.g.cs");
using (var writer = new StreamWriter(path))
{
writer.WriteLine("// <auto-generated/>");
writer.WriteLine("");
foreach (var u in usings.OrderBy(u => u))
{
writer.WriteLine($"global using global::{u};");
}
}
this.generatedSources.Add(path);
}
}
private void GenerateSourceFilesFromWebViews()
{
var views = fileProvider.RazorViews;
if (views.Count == 0)
{
return;
}
logger.LogInfo($"Found {views.Count} cshtml and razor files.");
if (!IsAspNetCoreDetected())
{
logger.LogInfo("Generating source files from cshtml files is only supported for new (SDK-style) project files");
return;
}
logger.LogInfo("Generating source files from cshtml and razor files...");
var sdk = new Sdk(dotnet).GetNewestSdk();
if (sdk != null)
{
try
{
var razor = new Razor(sdk, dotnet, logger);
var targetDir = GetTemporaryWorkingDirectory("razor");
var generatedFiles = razor.GenerateFiles(views, usedReferences.Keys, targetDir);
this.generatedSources.AddRange(generatedFiles);
}
catch (Exception ex)
{
// It's okay, we tried our best to generate source files from cshtml files.
logger.LogInfo($"Failed to generate source files from cshtml files: {ex.Message}");
}
}
}
/// <summary>
/// Creates a temporary directory with the given subfolder name.
/// The created directory might be inside the repo folder, and it is deleted when the object is disposed.
/// </summary>
/// <param name="subfolder"></param>
/// <returns></returns>
private string GetTemporaryWorkingDirectory(string subfolder)
{
var temp = Path.Combine(tempWorkingDirectory.ToString(), subfolder);
Directory.CreateDirectory(temp);
return temp;
}
/// <summary>
/// Resolves conflicts between all of the resolved references.
/// If the same assembly name is duplicated with different versions,

View File

@@ -50,6 +50,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
public bool IsAspNetCoreDetected
{
get
{
return IsNewProjectStructureUsed && UseAspNetCoreDlls;
}
}
private bool useImplicitUsings = false;
public bool UseImplicitUsings

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
public interface ISourceGenerator
{
/// <summary>
/// Returns the paths to the generated source files.
/// </summary>
IEnumerable<string> Generate();
}
}

View File

@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal class ImplicitUsingsGenerator : SourceGeneratorBase
{
private readonly FileContent fileContent;
public ImplicitUsingsGenerator(FileContent fileContent, ILogger logger, TemporaryDirectory tempWorkingDirectory)
: base(logger, tempWorkingDirectory)
{
this.fileContent = fileContent;
}
public override IEnumerable<string> Generate()
{
var usings = new HashSet<string>();
if (!fileContent.UseImplicitUsings)
{
logger.LogDebug("Skipping implicit usings generation");
return [];
}
// Hardcoded values from https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives
usings.UnionWith([ "System", "System.Collections.Generic", "System.IO", "System.Linq", "System.Net.Http", "System.Threading",
"System.Threading.Tasks" ]);
if (fileContent.UseAspNetCoreDlls)
{
usings.UnionWith([ "System.Net.Http.Json", "Microsoft.AspNetCore.Builder", "Microsoft.AspNetCore.Hosting",
"Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Routing", "Microsoft.Extensions.Configuration",
"Microsoft.Extensions.DependencyInjection", "Microsoft.Extensions.Hosting", "Microsoft.Extensions.Logging" ]);
}
if (fileContent.UseWindowsForms)
{
usings.UnionWith(["System.Drawing", "System.Windows.Forms"]);
}
usings.UnionWith(fileContent.CustomImplicitUsings);
logger.LogInfo($"Generating source file for implicit usings. Namespaces: {string.Join(", ", usings.OrderBy(u => u))}");
if (usings.Count > 0)
{
var tempDir = GetTemporaryWorkingDirectory("implicitUsings");
var path = Path.Combine(tempDir, "GlobalUsings.g.cs");
using (var writer = new StreamWriter(path))
{
writer.WriteLine("// <auto-generated/>");
writer.WriteLine("");
foreach (var u in usings.OrderBy(u => u))
{
writer.WriteLine($"global using global::{u};");
}
}
return [path];
}
return [];
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.IO;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal abstract class SourceGeneratorBase : ISourceGenerator
{
protected readonly ILogger logger;
protected readonly TemporaryDirectory tempWorkingDirectory;
public SourceGeneratorBase(ILogger logger, TemporaryDirectory tempWorkingDirectory)
{
this.logger = logger;
this.tempWorkingDirectory = tempWorkingDirectory;
}
public abstract IEnumerable<string> Generate();
/// <summary>
/// Creates a temporary directory with the given subfolder name.
/// The created directory might be inside the repo folder, and it is deleted when the temporary working directory is disposed.
/// </summary>
protected string GetTemporaryWorkingDirectory(string subfolder)
{
var temp = Path.Combine(tempWorkingDirectory.ToString(), subfolder);
Directory.CreateDirectory(temp);
return temp;
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
internal class WebViewGenerator : SourceGeneratorBase
{
private readonly FileProvider fileProvider;
private readonly FileContent fileContent;
private readonly IDotNet dotnet;
private readonly ICompilationInfoContainer compilationInfoContainer;
private readonly IEnumerable<string> references;
public WebViewGenerator(
FileProvider fileProvider,
FileContent fileContent,
IDotNet dotnet,
ICompilationInfoContainer compilationInfoContainer,
ILogger logger,
TemporaryDirectory tempWorkingDirectory,
IEnumerable<string> references) : base(logger, tempWorkingDirectory)
{
this.fileProvider = fileProvider;
this.fileContent = fileContent;
this.dotnet = dotnet;
this.compilationInfoContainer = compilationInfoContainer;
this.references = references;
}
public override IEnumerable<string> Generate()
{
var webViewExtractionOption = Environment.GetEnvironmentVariable(EnvironmentVariableNames.WebViewGeneration);
if (webViewExtractionOption == null ||
bool.TryParse(webViewExtractionOption, out var shouldExtractWebViews) &&
shouldExtractWebViews)
{
compilationInfoContainer.CompilationInfos.Add(("WebView extraction enabled", "1"));
return GenerateSourceFilesFromWebViews();
}
compilationInfoContainer.CompilationInfos.Add(("WebView extraction enabled", "0"));
return [];
}
private IEnumerable<string> GenerateSourceFilesFromWebViews()
{
var views = fileProvider.RazorViews;
if (views.Count == 0)
{
logger.LogDebug("No cshtml or razor files found.");
return [];
}
logger.LogInfo($"Found {views.Count} cshtml and razor files.");
if (!fileContent.IsAspNetCoreDetected)
{
logger.LogInfo("Generating source files from cshtml files is only supported for new (SDK-style) project files");
return [];
}
logger.LogInfo("Generating source files from cshtml and razor files...");
var sdk = new Sdk(dotnet).GetNewestSdk();
if (sdk != null)
{
try
{
var razor = new Razor(sdk, dotnet, logger);
var targetDir = GetTemporaryWorkingDirectory("razor");
var generatedFiles = razor.GenerateFiles(views, references, targetDir);
return generatedFiles ?? [];
}
catch (Exception ex)
{
// It's okay, we tried our best to generate source files from cshtml files.
logger.LogInfo($"Failed to generate source files from cshtml files: {ex.Message}");
}
}
return [];
}
}
}