Skip to content

Commit

Permalink
Application metadata validation in analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar committed May 16, 2024
1 parent 9afb50b commit 9f70ad3
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 216 deletions.
7 changes: 0 additions & 7 deletions AppLibDotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.App.Core", "src\Alti
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 @@ -48,10 +46,6 @@ Global
{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 @@ -62,7 +56,6 @@ Global
{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
11 changes: 11 additions & 0 deletions src/Altinn.App.Analyzers/Altinn.App.Analyzers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
<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" />

<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" GeneratePathProperty="true" />
<None Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNewtonsoft_Json)\lib\netstandard2.0\Newtonsoft.Json.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>
6 changes: 5 additions & 1 deletion src/Altinn.App.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ALTINN999 | General | Warning | TestAnalyzer
ALTINN001 | General | Error | Project not found
ALTINN002 | General | Error | App metadata file not found
ALTINN003 | General | Error | App metadata file parsing
ALTINN004 | General | Error | Classref not resolved
ALTINN999 | General | Error | Unknown error
70 changes: 70 additions & 0 deletions src/Altinn.App.Analyzers/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Reflection;

namespace Altinn.App.Analyzers;

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

internal static readonly DiagnosticDescriptor ProjectNotFoundError = Error(
"ALTINN001",
"Altinn app project not found",
"While starting analysis, we couldn't find the project directory - contact support"
);

internal static readonly DiagnosticDescriptor ApplicationMetadataFileNotFoundError = Error(
"ALTINN002",
"Altinn app metadata file not found",
"Could not find application metadata file at 'config/applicationmetadata.json'"
);

internal static readonly DiagnosticDescriptor FailedToParseApplicationMetadataError = Error(
"ALTINN003",
"Altinn app metadata file couldn't be parsed",
"Could not parse application metadata file at 'config/applicationmetadata.json': '{0}' {1}"
);

internal static readonly DiagnosticDescriptor DataTypeClassRefInvalidError = Error(
"ALTINN004",
"Data type class reference could not be found",
"Class reference '{0}' for data type '{1}' could not be found"
);

internal static readonly ImmutableArray<DiagnosticDescriptor> All;

static Diagnostics()

Check warning on line 39 in src/Altinn.App.Analyzers/Diagnostics.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Initialize all 'static fields' inline and remove the 'static constructor'. (https://rules.sonarsource.com/csharp/RSPEC-3963)
{
All = ImmutableArray.CreateRange(
typeof(Diagnostics)
.GetFields(BindingFlags.NonPublic | BindingFlags.Static)

Check warning on line 43 in src/Altinn.App.Analyzers/Diagnostics.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Make sure that this accessibility bypass is safe here. (https://rules.sonarsource.com/csharp/RSPEC-3011)
.Where(f => f.FieldType == typeof(DiagnosticDescriptor))
.Select(f => (DiagnosticDescriptor)f.GetValue(null))
);
}

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) =>

Check warning on line 52 in src/Altinn.App.Analyzers/Diagnostics.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Remove the unused private method 'Warning'. (https://rules.sonarsource.com/csharp/RSPEC-1144)
Create(id, title, messageFormat, Category.General, DiagnosticSeverity.Warning);

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

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);
}
}
93 changes: 93 additions & 0 deletions src/Altinn.App.Analyzers/MetadataAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Altinn.App.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class MetadataAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => Diagnostics.All;

public override void Initialize(AnalysisContext context)
{
// var configFlags = GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics;

Check warning on line 13 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
var configFlags = GeneratedCodeAnalysisFlags.None;
context.ConfigureGeneratedCodeAnalysis(configFlags);
context.EnableConcurrentExecution();

context.RegisterCompilationAction(OnCompilation);
// System.Diagnostics.Debugger.Launch();

Check warning on line 19 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
}

private void OnCompilation(CompilationAnalysisContext context)
{
if (
!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue(
"build_property.projectdir",
out var projectDir
)
)
{
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.ProjectNotFoundError, Location.None));
return;
}

#pragma warning disable RS1035 // Do not use APIs banned for analyzers
// On compilation end, it is not totally unreasonable to read a file
var file = Path.Combine(projectDir, "config/applicationmetadata.json");
if (!File.Exists(file))
{
context.ReportDiagnostic(
Diagnostic.Create(Diagnostics.ApplicationMetadataFileNotFoundError, Location.None)
);
return;
}

try
{
var jsonText = File.ReadAllText(file);
#pragma warning restore RS1035 // Do not use APIs banned for analyzers
var metadata = JObject.Parse(jsonText) ?? throw new Exception("Deserialization returned 'null'");

Check warning on line 50 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

'System.Exception' should not be thrown by user code. (https://rules.sonarsource.com/csharp/RSPEC-112)
var dataTypes =
(metadata.GetValue("dataTypes") as JArray) ?? throw new JsonException("Failed to parse 'dataTypes'");

foreach (var dataTypeToken in dataTypes)
{
var dataType = (dataTypeToken as JObject) ?? throw new JsonException("Could not parse 'dataType'");
var dataTypeId =
(dataType.GetValue("id") as JValue)?.Value as string
?? throw new JsonException("Could not parse 'id' from 'dataType'");

var appLogic = dataType.GetValue("appLogic") as JObject;
if (appLogic is null)
continue;

var classRefToken = appLogic.GetValue("classRef");
var classRef =
(classRefToken as JValue)?.Value as string ?? throw new JsonException("Could not parse 'classRef'");
var classRefSymbol = context.Compilation.GetTypeByMetadataName(classRef);

if (classRefSymbol is null)
{
// TODO: create location

Check warning on line 72 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
// Location.Create(file, TextSpan.FromBounds(..), LinePositionSpan);

Check warning on line 73 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
context.ReportDiagnostic(
Diagnostic.Create(Diagnostics.DataTypeClassRefInvalidError, Location.None, classRef, dataTypeId)
);
}
}
}
catch (Exception ex)
{
context.ReportDiagnostic(
Diagnostic.Create(
Diagnostics.FailedToParseApplicationMetadataError,
Location.None,
ex.Message,
ex.StackTrace
)
);
return;

Check warning on line 90 in src/Altinn.App.Analyzers/MetadataAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Static code analysis

Remove this redundant jump. (https://rules.sonarsource.com/csharp/RSPEC-3626)
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}
84 changes: 0 additions & 84 deletions src/Altinn.App.Analyzers/TestAnalyzer.cs

This file was deleted.

28 changes: 0 additions & 28 deletions test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj

This file was deleted.

Loading

0 comments on commit 9f70ad3

Please sign in to comment.