-
Notifications
You must be signed in to change notification settings - Fork 24
Remove hidden data before validation and make clean data available in DataProcessor #1279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db23807
7665d69
195b308
05db68f
180db09
75b113a
0638aec
d9795d4
50a8bc6
25ebd7b
52cd5ff
6780a55
de9e097
f364467
f77dd10
ba1d5cb
3ca8324
6c23edd
7e35a7e
f11ac5b
2ca64b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| using Altinn.App.Analyzers.SourceTextGenerator; | ||
| using Altinn.App.Analyzers.Utils; | ||
| using NanoJsonReader; | ||
|
|
||
| namespace Altinn.App.Analyzers.IncrementalGenerator; | ||
|
|
||
| /// <summary> | ||
| /// Generate IFormDataWrapper implementations for classes in models/*.cs in the app. | ||
| /// </summary> | ||
| [Generator] | ||
| public class FormDataWrapperGenerator : IIncrementalGenerator | ||
| { | ||
| private sealed record Result<T>(T? Value, EquatableArray<EquatableDiagnostic> Diagnostics) | ||
| where T : class | ||
| { | ||
| public Result(EquatableDiagnostic diagnostics) | ||
| : this(null, new EquatableArray<EquatableDiagnostic>([diagnostics])) { } | ||
|
|
||
| public Result(T value) | ||
| : this(value, EquatableArray<EquatableDiagnostic>.Empty) { } | ||
| }; | ||
|
|
||
| private sealed record ModelClassOrDiagnostic( | ||
| string? ClassName, | ||
| Location? Location, | ||
| EquatableArray<EquatableDiagnostic> Diagnostics | ||
| ) | ||
| { | ||
| public ModelClassOrDiagnostic(EquatableDiagnostic diagnostic) | ||
| : this(null, null, new([diagnostic])) { } | ||
|
|
||
| public ModelClassOrDiagnostic(string className, Location? location) | ||
| : this(className, location, EquatableArray<EquatableDiagnostic>.Empty) { } | ||
| }; | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Initialize(IncrementalGeneratorInitializationContext context) | ||
| { | ||
| var rootClasses = context | ||
| .AdditionalTextsProvider.Where(text => | ||
| text.Path.Replace('\\', '/').EndsWith("config/applicationmetadata.json") | ||
| ) | ||
| .SelectMany(ParseModelClassOrDiagnostic); | ||
|
|
||
| var modelPathNodesProvider = rootClasses.Combine(context.CompilationProvider).Select(CreateNodeTree); | ||
|
|
||
| context.RegisterSourceOutput(modelPathNodesProvider, GenerateFromNode); | ||
| } | ||
|
|
||
| private static ImmutableArray<ModelClassOrDiagnostic> ParseModelClassOrDiagnostic( | ||
| AdditionalText text, | ||
| CancellationToken token | ||
| ) | ||
| { | ||
| try | ||
| { | ||
| var textContent = text.GetText(token)?.ToString(); | ||
| if (textContent is null) | ||
| { | ||
| return | ||
| [ | ||
| new( | ||
| new EquatableDiagnostic( | ||
| Diagnostics.FormDataWrapperGenerator.AppMetadataError, | ||
| FileLocationHelper.GetLocation(text, 0, null), | ||
| ["Failed to read applicationmetadata.json"] | ||
| ) | ||
| ), | ||
| ]; | ||
| } | ||
|
|
||
| var appMetadata = JsonValue.Parse(textContent); | ||
| if (appMetadata.Type != JsonType.Object) | ||
| { | ||
| return | ||
| [ | ||
| new( | ||
| new EquatableDiagnostic( | ||
| Diagnostics.FormDataWrapperGenerator.AppMetadataError, | ||
| FileLocationHelper.GetLocation(text, appMetadata.Start, appMetadata.End), | ||
| ["applicationmetadata.json is not a valid JSON object"] | ||
| ) | ||
| ), | ||
| ]; | ||
| } | ||
|
|
||
| var dataTypes = appMetadata.GetProperty("dataTypes"); | ||
| if (dataTypes?.Type != JsonType.Array) | ||
| { | ||
| return | ||
| [ | ||
| new( | ||
| new( | ||
| Diagnostics.FormDataWrapperGenerator.AppMetadataError, | ||
| FileLocationHelper.GetLocation(text, appMetadata.Start, appMetadata.End), | ||
| ["the property dataTypes is not a valid JSON array"] | ||
| ) | ||
| ), | ||
| ]; | ||
| } | ||
|
|
||
| List<ModelClassOrDiagnostic> rootClasses = []; | ||
| foreach (var dataType in dataTypes.GetArrayValues()) | ||
| { | ||
| if (dataType.Type != JsonType.Object) | ||
| { | ||
| continue; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch could emit a diagnostic, all the values in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I definitely could add more diagnostics. Not sure I want to do that, because diagnostics from incremental source generators is pretty buggy. I wanted to have a diagnostic for not finding
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading up on the whole "diagnostics from incremental generators" topic, it seems like there is no intention to have a create story there. Maybe we should go in the opposite direction then? Drop all diagnostics here and rather implement analyzers if we feel it is necessary? |
||
| } | ||
|
|
||
| var appLogic = dataType.GetProperty("appLogic"); | ||
| if (appLogic?.Type != JsonType.Object) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| var classRef = appLogic.GetProperty("classRef"); | ||
| if (classRef?.Type != JsonType.String) | ||
| { | ||
| continue; | ||
martinothamar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| rootClasses.Add( | ||
| new(classRef.GetString(), FileLocationHelper.GetLocation(text, classRef.Start, classRef.End)) | ||
| ); | ||
| } | ||
|
|
||
| return [.. rootClasses]; | ||
| } | ||
| catch (NanoJsonException e) | ||
| { | ||
| return | ||
| [ | ||
| new( | ||
| new EquatableDiagnostic( | ||
| Diagnostics.FormDataWrapperGenerator.AppMetadataError, | ||
| FileLocationHelper.GetLocation(text, e.StartIndex, e.EndIndex), | ||
| [e.Message] | ||
| ) | ||
| ), | ||
| ]; | ||
| } | ||
| } | ||
|
|
||
| private static Result<ModelPathNode> CreateNodeTree( | ||
| (ModelClassOrDiagnostic, Compilation) tuple, | ||
| CancellationToken _ | ||
| ) | ||
| { | ||
| var (rootSymbolFullName, compilation) = tuple; | ||
| if (rootSymbolFullName.ClassName is null) | ||
| { | ||
| return new Result<ModelPathNode>(null, rootSymbolFullName.Diagnostics); | ||
| } | ||
| var rootSymbol = compilation.GetBestTypeByMetadataName(rootSymbolFullName.ClassName); | ||
| if (rootSymbol == null) | ||
| { | ||
| return new Result<ModelPathNode>( | ||
| new EquatableDiagnostic( | ||
| Diagnostics.FormDataWrapperGenerator.AppMetadataError, | ||
| rootSymbolFullName.Location, | ||
| [$"Could not find class {rootSymbolFullName.ClassName} in the compilation"] | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| return new Result<ModelPathNode>( | ||
| new ModelPathNode("", "", SourceReaderUtils.TypeSymbolToString(rootSymbol), GetNodeProperties(rootSymbol)) | ||
| ); | ||
| } | ||
|
|
||
| private static EquatableArray<ModelPathNode>? GetNodeProperties(INamedTypeSymbol namedTypeSymbol) | ||
| { | ||
| var nodeProperties = new List<ModelPathNode>(); | ||
| foreach (var property in namedTypeSymbol.GetMembers().OfType<IPropertySymbol>()) | ||
| { | ||
| if ( | ||
| property.IsStatic | ||
| || property.IsReadOnly | ||
| || property.IsWriteOnly | ||
| || property.IsImplicitlyDeclared | ||
| || property.IsIndexer | ||
| ) | ||
| { | ||
| // Skip static, readonly, writeonly, implicitly declared and indexer properties | ||
| continue; | ||
| } | ||
| var (propertyTypeSymbol, propertyCollectionTypeSymbol) = SourceReaderUtils.GetTypeFromProperty( | ||
| property.Type | ||
| ); | ||
|
|
||
| var cSharpName = property.Name; | ||
| var jsonName = SourceReaderUtils.GetJsonName(property) ?? cSharpName; | ||
| var typeString = SourceReaderUtils.TypeSymbolToString(propertyTypeSymbol); | ||
| var collectionTypeString = propertyCollectionTypeSymbol is null | ||
| ? null | ||
| : SourceReaderUtils.TypeSymbolToString(propertyCollectionTypeSymbol); | ||
|
|
||
| if ( | ||
| propertyTypeSymbol is INamedTypeSymbol propertyNamedTypeSymbol | ||
| && !propertyNamedTypeSymbol.ContainingNamespace.ToString().StartsWith("System") | ||
| ) | ||
| { | ||
| nodeProperties.Add( | ||
| new ModelPathNode( | ||
| cSharpName, | ||
| jsonName, | ||
| typeString, | ||
| GetNodeProperties(propertyNamedTypeSymbol), | ||
| collectionTypeString | ||
| ) | ||
| ); | ||
| } | ||
| else | ||
| { | ||
| nodeProperties.Add(new ModelPathNode(cSharpName, jsonName, typeString, null, collectionTypeString)); | ||
| } | ||
| } | ||
| return nodeProperties; | ||
| } | ||
|
|
||
| private static void GenerateFromNode(SourceProductionContext context, Result<ModelPathNode> result) | ||
| { | ||
| foreach (var diagnostic in result.Diagnostics) | ||
| { | ||
| context.ReportDiagnostic(diagnostic.CreateDiagnostic()); | ||
| } | ||
| if (result is not { Value: { } node }) | ||
| return; | ||
| var sourceText = SourceTextGenerator.SourceTextGenerator.GenerateSourceText(node, "public"); | ||
| context.AddSource(node.Name + "FormDataWrapper.g.cs", sourceText); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.