-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Application metadata validation in analyzer
- Loading branch information
1 parent
9afb50b
commit 9f70ad3
Showing
8 changed files
with
179 additions
and
216 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
{ | ||
All = ImmutableArray.CreateRange( | ||
typeof(Diagnostics) | ||
.GetFields(BindingFlags.NonPublic | BindingFlags.Static) | ||
.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) => | ||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
var configFlags = GeneratedCodeAnalysisFlags.None; | ||
context.ConfigureGeneratedCodeAnalysis(configFlags); | ||
context.EnableConcurrentExecution(); | ||
|
||
context.RegisterCompilationAction(OnCompilation); | ||
// System.Diagnostics.Debugger.Launch(); | ||
} | ||
|
||
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'"); | ||
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 | ||
// Location.Create(file, TextSpan.FromBounds(..), LinePositionSpan); | ||
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 notice Code scanning / CodeQL Generic catch clause Note
Generic catch clause.
|
||
} | ||
} |
This file was deleted.
Oops, something went wrong.
28 changes: 0 additions & 28 deletions
28
test/Altinn.App.Analyzers.Tests/Altinn.App.Analyzers.Tests.csproj
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.