diff --git a/AppLibDotnet.sln b/AppLibDotnet.sln index 826edaf71..e5d00f268 100644 --- a/AppLibDotnet.sln +++ b/AppLibDotnet.sln @@ -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 @@ -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 @@ -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} diff --git a/src/Altinn.App.Analyzers/Altinn.App.Analyzers.csproj b/src/Altinn.App.Analyzers/Altinn.App.Analyzers.csproj new file mode 100644 index 000000000..b607cda2a --- /dev/null +++ b/src/Altinn.App.Analyzers/Altinn.App.Analyzers.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + enable + enable + true + + false + true + true + true + + + + + + + + + + + + + + + + diff --git a/src/Altinn.App.Analyzers/AnalyzerReleases.Shipped.md b/src/Altinn.App.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 000000000..60c1edfa5 --- /dev/null +++ b/src/Altinn.App.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/Altinn.App.Analyzers/AnalyzerReleases.Unshipped.md b/src/Altinn.App.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 000000000..6ee9f5d64 --- /dev/null +++ b/src/Altinn.App.Analyzers/AnalyzerReleases.Unshipped.md @@ -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 diff --git a/src/Altinn.App.Analyzers/TestAnalyzer.cs b/src/Altinn.App.Analyzers/TestAnalyzer.cs new file mode 100644 index 000000000..db0dc041f --- /dev/null +++ b/src/Altinn.App.Analyzers/TestAnalyzer.cs @@ -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); + } +} + +/// +/// Analyzer +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class TestAnalyzer : DiagnosticAnalyzer +{ + /// + /// Supported diagnostics + /// + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(Diagnostics.UnknownError); + + /// + /// Initializer + /// + /// + 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) + ); + } + } +} diff --git a/src/Altinn.App.Core/Altinn.App.Core.csproj b/src/Altinn.App.Core/Altinn.App.Core.csproj index 6d1b313b3..c9765b2e3 100644 --- a/src/Altinn.App.Core/Altinn.App.Core.csproj +++ b/src/Altinn.App.Core/Altinn.App.Core.csproj @@ -24,6 +24,11 @@ + + + + + diff --git a/test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj b/test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj new file mode 100644 index 000000000..540ac461e --- /dev/null +++ b/test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/test/Altinn.App.Analyzers.Tests/UnitTest1.cs b/test/Altinn.App.Analyzers.Tests/UnitTest1.cs new file mode 100644 index 000000000..c9d7f3937 --- /dev/null +++ b/test/Altinn.App.Analyzers.Tests/UnitTest1.cs @@ -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; + +namespace Altinn.App.Analyzers.Tests; + +public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + /// + public static DiagnosticResult Diagnostic() => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) => + CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) => + CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + 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 + { + 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 NullableWarnings { get; } = + GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary 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); + } +}