Skip to content

Commit

Permalink
Start of Roslyn analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar committed May 15, 2024
1 parent d228eaf commit 9afb50b
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 0 deletions.
14 changes: 14 additions & 0 deletions AppLibDotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7AD5FADE-607
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.Core", "src\Altinn.App.Core\Altinn.App.Core.csproj", "{1745B251-BD5C-43B7-BA7D-9C4BFAB37535}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.App.Analyzers", "src\Altinn.App.Analyzers\Altinn.App.Analyzers.csproj", "{9F956488-F123-48A2-B21A-2C2918E8B416}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.App.Analyzers.Tests", "test\Altinn.App.Analyzers.Tests\Altinn.App.Analyzers.Tests.csproj", "{34E40EB8-DFEA-432C-9F53-371932E21D8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,6 +44,14 @@ Global
{1745B251-BD5C-43B7-BA7D-9C4BFAB37535}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1745B251-BD5C-43B7-BA7D-9C4BFAB37535}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1745B251-BD5C-43B7-BA7D-9C4BFAB37535}.Release|Any CPU.Build.0 = Release|Any CPU
{9F956488-F123-48A2-B21A-2C2918E8B416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F956488-F123-48A2-B21A-2C2918E8B416}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F956488-F123-48A2-B21A-2C2918E8B416}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F956488-F123-48A2-B21A-2C2918E8B416}.Release|Any CPU.Build.0 = Release|Any CPU
{34E40EB8-DFEA-432C-9F53-371932E21D8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34E40EB8-DFEA-432C-9F53-371932E21D8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34E40EB8-DFEA-432C-9F53-371932E21D8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34E40EB8-DFEA-432C-9F53-371932E21D8A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -49,6 +61,8 @@ Global
{17D7DCE9-7797-4BC1-B448-D0529FD6FB3D} = {6C8EB054-1747-4BAC-A637-754F304BCAFA}
{2FD56505-1DB2-4AE1-8911-E076E535EAC6} = {6C8EB054-1747-4BAC-A637-754F304BCAFA}
{1745B251-BD5C-43B7-BA7D-9C4BFAB37535} = {7AD5FADE-607F-4D5F-8511-6647D0C1AA1C}
{9F956488-F123-48A2-B21A-2C2918E8B416} = {7AD5FADE-607F-4D5F-8511-6647D0C1AA1C}
{34E40EB8-DFEA-432C-9F53-371932E21D8A} = {6C8EB054-1747-4BAC-A637-754F304BCAFA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4584C6E1-D5B4-40B1-A8C4-CF4620EB0896}
Expand Down
28 changes: 28 additions & 0 deletions src/Altinn.App.Analyzers/Altinn.App.Analyzers.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>

<IncludeBuildOutput>false</IncludeBuildOutput>
<NoPackageAnalysis>true</NoPackageAnalysis>
<DevelopmentDependency>true</DevelopmentDependency>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

<ItemGroup>
<Using Include="System.Collections.Immutable" />
<Using Include="Microsoft.CodeAnalysis" />
<Using Include="Microsoft.CodeAnalysis.CSharp" />
<Using Include="Microsoft.CodeAnalysis.Diagnostics" />
<Using Include="Microsoft.CodeAnalysis.CSharp.Syntax" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Pack="false" PrivateAssets="all" Version="4.8.0" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions src/Altinn.App.Analyzers/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

8 changes: 8 additions & 0 deletions src/Altinn.App.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ALTINN999 | General | Warning | TestAnalyzer
84 changes: 84 additions & 0 deletions src/Altinn.App.Analyzers/TestAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Altinn.App.Analyzers;

internal static class Diagnostics
{
internal static readonly DiagnosticDescriptor UnknownError = Warning(
"ALTINN999",
"Unknown analyzer error",
"Unknown error occurred during analysis: '{0}' {1}"
);

private const string DocsRoot = "https://docs.altinn.studio/app/development/analysis/";
private const string RulesRoot = DocsRoot + "rules/";

private static DiagnosticDescriptor Warning(string id, string title, string messageFormat) =>
Create(id, title, messageFormat, Category.General, DiagnosticSeverity.Warning);

private static DiagnosticDescriptor Create(
string id,
string title,
string messageFormat,
string category,
DiagnosticSeverity severity
) => new(id, title, messageFormat, category, severity, true, helpLinkUri: RulesRoot + id);

private static class Category
{
public const string General = nameof(General);
}
}

/// <summary>
/// Analyzer
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class TestAnalyzer : DiagnosticAnalyzer
{
/// <summary>
/// Supported diagnostics
/// </summary>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Diagnostics.UnknownError);

/// <summary>
/// Initializer
/// </summary>
/// <param name="context"></param>
public override void Initialize(AnalysisContext context)
{
// var configFlags = GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics;
var configFlags = GeneratedCodeAnalysisFlags.None;
context.ConfigureGeneratedCodeAnalysis(configFlags);
context.EnableConcurrentExecution();

// context.RegisterCompilationStartAction(OnCompilationStart);
context.RegisterCompilationAction(OnCompilation);
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleMemberAccessExpression);
}

// private void OnCompilationStart(CompilationStartAnalysisContext context)
// {
// }

private void OnCompilation(CompilationAnalysisContext context) { }

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
try
{
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
return;

if (memberAccess.Name.Identifier.Text != "OpenTelemetry")
return;

var symbol = context.SemanticModel.GetSymbolInfo(memberAccess.Expression, context.CancellationToken).Symbol;
}
catch (Exception ex)
{
context.ReportDiagnostic(
Diagnostic.Create(Diagnostics.UnknownError, context.Node.GetLocation(), ex.Message, ex.StackTrace)
);
}
}
}
5 changes: 5 additions & 0 deletions src/Altinn.App.Core/Altinn.App.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Altinn.App.Analyzers\Altinn.App.Analyzers.csproj" PrivateAssets="all" ReferenceOutputAssembly="false" OutputItemType="None" />
<None Include="$(OutputPath)\Altinn.App.Analyzers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="true" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Altinn.App.Core.Tests" />
<InternalsVisibleTo Include="Altinn.App.Api.Tests" />
Expand Down
28 changes: 28 additions & 0 deletions test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Altinn.App.Analyzers\Altinn.App.Analyzers.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
96 changes: 96 additions & 0 deletions test/Altinn.App.Analyzers.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Verifier = Altinn.App.Analyzers.Tests.CSharpAnalyzerVerifier<Altinn.App.Analyzers.TestAnalyzer>;

namespace Altinn.App.Analyzers.Tests;

public static partial class CSharpAnalyzerVerifier<TAnalyzer>
where TAnalyzer : DiagnosticAnalyzer, new()
{
/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.Diagnostic()"/>
public static DiagnosticResult Diagnostic() => CSharpAnalyzerVerifier<TAnalyzer, XUnitVerifier>.Diagnostic();

/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.Diagnostic(string)"/>
public static DiagnosticResult Diagnostic(string diagnosticId) =>
CSharpAnalyzerVerifier<TAnalyzer, XUnitVerifier>.Diagnostic(diagnosticId);

/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.Diagnostic(DiagnosticDescriptor)"/>
public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) =>
CSharpAnalyzerVerifier<TAnalyzer, XUnitVerifier>.Diagnostic(descriptor);

/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/>
public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
{
var test = new Test { TestCode = source, };

test.ExpectedDiagnostics.AddRange(expected);
await test.RunAsync(CancellationToken.None);
}

public class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier>
{
public Test()
{
SolutionTransforms.Add(
(solution, projectId) =>
{
var compilationOptions = solution.GetProject(projectId).CompilationOptions;
compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(
compilationOptions.SpecificDiagnosticOptions.SetItems(NullableWarnings)
);
solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);
return solution;
}
);
}
}

internal static ImmutableDictionary<string, ReportDiagnostic> NullableWarnings { get; } =
GetNullableWarningsFromCompiler();

private static ImmutableDictionary<string, ReportDiagnostic> GetNullableWarningsFromCompiler()
{
string[] args = { "/warnaserror:nullable" };
var commandLineArguments = CSharpCommandLineParser.Default.Parse(
args,
baseDirectory: Environment.CurrentDirectory,
sdkDirectory: Environment.CurrentDirectory
);
var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;

// Workaround for https://github.com/dotnet/roslyn/issues/41610
nullableWarnings = nullableWarnings
.SetItem("CS8632", ReportDiagnostic.Error)
.SetItem("CS8669", ReportDiagnostic.Error);

return nullableWarnings;
}
}

public class UnitTest1
{
[Fact]
public async Task Test1()
{
var code = """
using System;

class Program
{
static void Main()
{
int i = 0;
Console.WriteLine(i++);
}
}
""";

await Verifier.VerifyAnalyzerAsync(code);
}
}

0 comments on commit 9afb50b

Please sign in to comment.