Skip to content

Commit

Permalink
Merge pull request #36 from ByronMayne/35-compiler-error-when-using-e…
Browse files Browse the repository at this point in the history
…nforceextendedanalyzerrules-due-to-console-ussage

Created new unit tests to validate banned api analyzer
  • Loading branch information
ByronMayne authored Dec 23, 2024
2 parents 795f6df + 900b4df commit 322c3ef
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 76 deletions.
3 changes: 2 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
<PropertyGroup>
<!-- Common Settings -->
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<LangVersion>12</LangVersion>
<RootNamespace>SGF</RootNamespace>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<SGFSourceDir>$(MSBuildThisFileDirectory)</SGFSourceDir>
<LibsDir>$(SGFSourceDir)libs\</LibsDir>
<ImgDir>$(MSBuildThisFileDirectory)..\img\</ImgDir>
<SGfIco>$(MSBuildThisFileDirectory)..\img\icon.png</SGfIco>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<!-- Icons -->
<ApplicationIcon>$(MSBuildThisFileDirectory)..\img\icon.ico</ApplicationIcon>

Expand Down
26 changes: 0 additions & 26 deletions src/SourceGenerator.Foundations.Tests/BaseTest.cs

This file was deleted.

155 changes: 155 additions & 0 deletions src/SourceGenerator.Foundations.Tests/CompiliationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Reflection;
using System.Text;
using Xunit.Abstractions;

namespace SGF
{
/// <summary>
/// There is an attribute called `ExtendedAnalyzerRules` which are applied to source generators. Since
/// SGF injects code into their projects we want to make sure we don't add warnings or errors.
/// </summary>
public class CompiliationTestBase
{
public SgfAnalyzerConfigOptions AnalyzerOptions { get; }

/// <summary>
/// Gets the name of the currently running test method
/// </summary>
public string TestMethodName { get; }

private readonly List<MetadataReference> m_references;
private readonly List<DiagnosticAnalyzer> m_analyzers;
protected readonly List<IIncrementalGenerator> m_incrementalGenerators;

protected CompiliationTestBase(ITestOutputHelper outputHelper)
{
m_analyzers = new List<DiagnosticAnalyzer>();
m_references = new List<MetadataReference>();
m_incrementalGenerators = new List<IIncrementalGenerator>();

AnalyzerOptions = new SgfAnalyzerConfigOptions();
TestMethodName = "Unkonwn";
Type type = outputHelper.GetType();
FieldInfo? testField = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
ITest? test = testField?.GetValue(outputHelper) as ITest;
if (test != null)
{
TestMethodName = test.TestCase.TestMethod.ToString()!;
}

AddAssemblyReference("System.Runtime");
AddAssemblyReference("netstandard");
AddAssemblyReference("System.Console");
AddAssemblyReference("Microsoft.CodeAnalysis");
AddAssemblyReference("System.Linq");
AddMetadataReference<object>();
AddMetadataReference<SgfGeneratorAttribute>();
}



protected async Task ComposeAsync(
string[]? source = null,
Action<ImmutableArray<Diagnostic>>[]? assertDiagnostics = null,
Action<Compilation>[]? assertCompilation = null)
{

source ??= [];

CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);

CompilationWithAnalyzersOptions analyzerOptions = new CompilationWithAnalyzersOptions(
new AnalyzerOptions([], new SgfAnalyzerConfigOptionsProvider(AnalyzerOptions)),
null, true, false);

// Create a Roslyn compilation for the syntax tree.
Compilation compilation = CSharpCompilation.Create(TestMethodName)
.AddSyntaxTrees(source.Select(t => ParseSyntaxTree(t)).ToArray())
.WithOptions(compilationOptions)
.AddReferences(m_references);

// Create an instance of our EnumGenerator incremental source generator
HoistSourceGenerator generator = new HoistSourceGenerator();

// The GeneratorDriver is used to run our generator against a compilation
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);

// Run the source generator!
driver = driver.RunGenerators(compilation)
.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out var _);

CompilationWithAnalyzers analysis = compilation.WithAnalyzers([.. m_analyzers], analyzerOptions);

ImmutableArray<Diagnostic> diagnostics = await analysis.GetAllDiagnosticsAsync();
if (assertDiagnostics is not null)
{
foreach (Action<ImmutableArray<Diagnostic>> assert in assertDiagnostics)
{
assert(diagnostics);
}
}

if (assertCompilation is not null)
{
foreach (Action<Compilation> assert in assertCompilation)
{
assert(compilation);
}
}
}

/// <summary>
/// Adds a new <see cref="DiagnosticAnalyzer"/> that will be eveluated during compliation.
/// </summary>
protected void AddAnalyzer<T>() where T : DiagnosticAnalyzer, new()
=> m_analyzers.Add(new T());

/// <summary>
/// Adds a new <see cref="DiagnosticAnalyzer"/> that will be eveluated during compliation.
/// </summary>
protected void AddAnalyzer<T>(T instance) where T : DiagnosticAnalyzer
=> m_analyzers.Add(instance);

/// <summary>
/// Adds a new <see cref="IIncrementalGenerator"/> that will be executed during compliation.
/// </summary>
protected void AddGenerator<T>() where T : IIncrementalGenerator, new()
=> m_incrementalGenerators.Add(new T());

/// <summary>
/// Adds a new <see cref="IIncrementalGenerator"/> that will be executed during compliation.
/// </summary>
protected void AddGenerator<T>(T instance) where T : IIncrementalGenerator
=> m_incrementalGenerators.Add(instance);

/// <summary>
/// Adds a new <see cref="MetadataReference"/> for the given assembly that the type belongs in
/// </summary>
protected void AddMetadataReference<T>()
=> m_references.Add(MetadataReference.CreateFromFile(typeof(T).Assembly.Location));

protected void AddAssemblyReference(string assemblyName)
{
#pragma warning disable RS1035 // Do not use APIs banned for analyzers
Assembly assembly = Assembly.Load(assemblyName);
if (assembly is not null)
{
m_references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
#pragma warning restore RS1035 // Do not use APIs banned for analyzers
}

protected static SyntaxTree ParseSyntaxTree(string source, string fileName = "TestClass.cs")
{
SourceText sourceText = SourceText.From(source, Encoding.UTF8);
CSharpParseOptions parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12);
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, path: fileName);
return syntaxTree;
}
}
}
46 changes: 46 additions & 0 deletions src/SourceGenerator.Foundations.Tests/DiagnosticAsserts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Text;

namespace SourceGenerator.Foundations.Tests
{
/// <summary>
/// Contains asserts for checking if diagnostics match expected values.
/// </summary>
internal static class DiagnosticAsserts
{
public static Action<ImmutableArray<Diagnostic>> NoErrors()
=> NoneOfSeverity(DiagnosticSeverity.Error);

public static Action<ImmutableArray<Diagnostic>> NoErrorsOrWarnings()
=> NoneOfSeverity(DiagnosticSeverity.Warning, DiagnosticSeverity.Error);

public static Action<ImmutableArray<Diagnostic>> NoneOfSeverity(params DiagnosticSeverity[] severity)
{
HashSet<DiagnosticSeverity> set = new HashSet<DiagnosticSeverity>(severity);

return diagnostics =>
{
List<Diagnostic> filtered = diagnostics
.Where(d => set.Contains(d.Severity))
.ToList();

if(filtered.Count > 0)
{
StringBuilder errorBuilder = new StringBuilder();

foreach(var dia in filtered)
{
Location location = dia.Location;
string? filePath = Path.GetFileName(location.SourceTree?.FilePath);

string error = $"{filePath} {location.SourceSpan.Start} {dia.Severity} {dia.Descriptor.Id}: {dia.GetMessage()}";

errorBuilder.AppendLine(error);
}
Assert.Fail(errorBuilder.ToString());
}
};
}
}
}
48 changes: 48 additions & 0 deletions src/SourceGenerator.Foundations.Tests/ExtendedAnalyzerRuleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.CodeAnalysis.CSharp.Analyzers;
using SGF;
using Xunit.Abstractions;

namespace SourceGenerator.Foundations.Tests
{
/// <summary>
/// Runs Roslyns analyzers to validate that code that was added
/// by the <see cref="HoistSourceGenerator"/> does not cause
/// errors due to `EnforceExtendedAnalyzerRules`
/// </summary>
public class ExtendedAnalyzerRuleTests : CompiliationTestBase
{
public ExtendedAnalyzerRuleTests(ITestOutputHelper outputHelper) : base(outputHelper)
{
AnalyzerOptions.EnforceExtendedAnalyzerRules = true;

AddGenerator<HoistSourceGenerator>();
AddAnalyzer<CSharpSymbolIsBannedInAnalyzersAnalyzer>();
}

[Fact]
public Task No_Banned_Apis_In_Source()
{
string source = """
using SGF;
namespace Yellow
{
[SgfGenerator]
public class CustomGenerator : IncrementalGenerator
{
public CustomGenerator() : base("Generator")
{}
public override void OnInitialize(SgfInitializationContext context)
{}
}
}
""";

return ComposeAsync([source],
assertDiagnostics: [
DiagnosticAsserts.NoErrors()
]);
}
}
}
45 changes: 0 additions & 45 deletions src/SourceGenerator.Foundations.Tests/GeneratorTest.cs

This file was deleted.

40 changes: 40 additions & 0 deletions src/SourceGenerator.Foundations.Tests/SgfAnalyzerConfigOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.CodeAnalysis.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace SGF
{

public class SgfAnalyzerConfigOptions : AnalyzerConfigOptions
{
private const string BUILD_PROP_EXTENDED_RULES = "build_property.EnforceExtendedAnalyzerRules";

private readonly Dictionary<string, string?> m_values;

public bool EnforceExtendedAnalyzerRules
{
get => m_values.TryGetValue(BUILD_PROP_EXTENDED_RULES, out string? rawValue) &&
bool.TryParse(rawValue, out bool parsedValue) &&
parsedValue;
set => m_values[BUILD_PROP_EXTENDED_RULES] = value
? "true" // has to be lower
: "false";
}

public SgfAnalyzerConfigOptions()
{
m_values = new Dictionary<string, string?>();

EnforceExtendedAnalyzerRules = false;
}

public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
if (m_values.TryGetValue(key, out value))
{
value ??= "";
return true;
}
return false;
}
}
}
Loading

0 comments on commit 322c3ef

Please sign in to comment.