Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Altinn/altinn-studio into s…
Browse files Browse the repository at this point in the history
…tudio-table-components
  • Loading branch information
ErlingHauan committed May 13, 2024
2 parents 67f626e + b477008 commit 62ac455
Show file tree
Hide file tree
Showing 111 changed files with 18,339 additions and 976 deletions.
8 changes: 4 additions & 4 deletions backend/packagegroups/NuGet.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageReference Update="Microsoft.Azure.Services.AppAuthentication" Version="1.6.2" />
<PackageReference Update="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.24" />
<PackageReference Update="Microsoft.VisualStudio.Web.BrowserLink" Version="2.2.0" />
<PackageReference Update="HtmlAgilityPack" Version="1.11.60" />
<PackageReference Update="HtmlAgilityPack" Version="1.11.61" />
<PackageReference Update="Microsoft.DiaSymReader.Native" Version="1.7.0" />
<PackageReference Update="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.16" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
Expand All @@ -44,10 +44,10 @@
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<!-- Do not upgrade Moq version from 4.18.4 -->
<PackageReference Update="Moq" Version="4.18.4" />
<PackageReference Update="xunit" Version="2.7.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.5.8" />
<PackageReference Update="xunit" Version="2.8.0" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.8.0" />
<PackageReference Update="coverlet.collector" Version="6.0.2" />
<PackageReference Update="Basic.Reference.Assemblies" Version="1.6.0" />
<PackageReference Update="Basic.Reference.Assemblies" Version="1.7.1" />
<PackageReference Update="Fare" Version="2.2.1" />
<PackageReference Update="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,20 @@ public class CSharpGenerationSettings
public int IndentSize { get; set; } = 2;

public string ModelNamespace { get; set; } = "Altinn.App.Models";

/// <summary>
/// Create two properties on [XmlText] elements
/// one called value that is used by xml serialization
/// and one called valueNullable for json serialization.
///
/// The valueNullable property also used in a `ShouldSerialize` method
/// on the parent element
/// </summary>
public bool XmlTextValueNullableHack { get; set; } = true;

/// <summary>
/// Add a ShouldSerialize method for to the parent property for [XmlText] elements when all attibutes are fixed
/// </summary>
public bool AddShouldSerializeForTagContent { get; set; } = true;
}
}
15 changes: 8 additions & 7 deletions backend/src/DataModeling/Converter/Csharp/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ public static Assembly CompileToAssembly(string csharpCode)
{
EmitResult result = compilation.Emit(ms);

if (!result.Success)
var ignoredDiagnostics = new[]
{
"CS8019", // CS8019: Unnecessary using directive.
};
var diagnostics = result.Diagnostics.Where(d => !ignoredDiagnostics.Contains(d.Descriptor.Id)).ToArray();
if (diagnostics.Any())
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);

List<string> customErrorMessages = new();
foreach (Diagnostic diagnostic in failures)
foreach (Diagnostic diagnostic in diagnostics)
{
customErrorMessages.Add(diagnostic.GetMessage());
customErrorMessages.Add(diagnostic.Id + "" + diagnostic.GetMessage() + csharpCode[(diagnostic.Location.SourceSpan.Start - 10)..(diagnostic.Location.SourceSpan.End + 10)]);
}

throw new CsharpCompilationException("Csharp compilation failed.", customErrorMessages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class CsharpCompilationException : Exception
public List<string> CustomErrorMessages { get; }

/// <inheritdoc/>
public CsharpCompilationException(string message, List<string> customErrorMessages) : base(message)
public CsharpCompilationException(string message, List<string> customErrorMessages) : base(message + "\n\n" + string.Join("\n", customErrorMessages))
{
CustomErrorMessages = customErrorMessages;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public JsonMetadataToCsharpConverter(CSharpGenerationSettings generationSettings
private string Indent(int level = 1) => new string(' ', level * _generationSettings.IndentSize);

/// <inheritdoc />
public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separateNamespaces = false)
public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separateNamespaces, bool useNullableReferenceTypes)
{
Dictionary<string, string> classes = new();

Expand All @@ -31,10 +31,18 @@ public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separa
(separateNamespaces ? $".{rootElementType.TypeName}"
: string.Empty);

CreateModelFromMetadataRecursive(classes, rootElementType, serviceMetadata, serviceMetadata.TargetNamespace);
CreateModelFromMetadataRecursive(classes, rootElementType, serviceMetadata, serviceMetadata.TargetNamespace, useNullableReferenceTypes);

StringBuilder writer = new StringBuilder()
.AppendLine("using System;")
StringBuilder writer = new StringBuilder();
if (useNullableReferenceTypes)
{
writer.AppendLine("#nullable enable");
}
else
{
writer.AppendLine("#nullable disable");
}
writer.AppendLine("using System;")
.AppendLine("using System.Collections.Generic;")
.AppendLine("using System.ComponentModel.DataAnnotations;")
.AppendLine("using System.Linq;")
Expand All @@ -61,7 +69,8 @@ public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separa
/// <param name="parentElement">The parent Element</param>
/// <param name="serviceMetadata">Model metadata</param>
/// <param name="targetNamespace">Target namespace in xsd schema.</param>
private void CreateModelFromMetadataRecursive(Dictionary<string, string> classes, ElementMetadata parentElement, ModelMetadata serviceMetadata, string targetNamespace = null)
/// <param name="useNullableReferenceTypes">wheter to add nullable? to reference types</param>
private void CreateModelFromMetadataRecursive(Dictionary<string, string> classes, ElementMetadata parentElement, ModelMetadata serviceMetadata, string targetNamespace, bool useNullableReferenceTypes)
{
List<ElementMetadata> referredTypes = new List<ElementMetadata>();

Expand Down Expand Up @@ -102,15 +111,15 @@ private void CreateModelFromMetadataRecursive(Dictionary<string, string> classes

if (element.Type == ElementType.Field)
{
ParseFieldProperty(element, classBuilder, ref elementOrder, required);
ParseFieldProperty(element, classBuilder, ref elementOrder, required, useNullableReferenceTypes);
}
else if (element.Type == ElementType.Group)
{
ParseGroupProperty(element, classBuilder, referredTypes, ref elementOrder);
ParseGroupProperty(element, classBuilder, serviceMetadata, referredTypes, ref elementOrder, useNullableReferenceTypes);
}
else if (element.Type == ElementType.Attribute)
{
ParseAttributeProperty(element, classBuilder, required);
ParseAttributeProperty(element, classBuilder, required, useNullableReferenceTypes);
}
}

Expand All @@ -123,18 +132,52 @@ private void CreateModelFromMetadataRecursive(Dictionary<string, string> classes

foreach (ElementMetadata refType in referredTypes)
{
CreateModelFromMetadataRecursive(classes, refType, serviceMetadata);
CreateModelFromMetadataRecursive(classes, refType, serviceMetadata, targetNamespace: null, useNullableReferenceTypes);
}
}

private void ParseFieldProperty(ElementMetadata element, StringBuilder classBuilder, ref int elementOrder, bool required)
private void ParseFieldProperty(ElementMetadata element, StringBuilder classBuilder, ref int elementOrder, bool required, bool useNullableReferenceTypes)
{
string nullableReference = useNullableReferenceTypes ? "?" : string.Empty;
(string dataType, bool isValueType) = GetPropertyType(element.XsdValueType);

WriteRestrictionAnnotations(classBuilder, element);
if (element.IsTagContent)

// [XmlText] properties can't be nullable value types, so we need a hack so that they behave as if nullable works.
if (_generationSettings.XmlTextValueNullableHack && element.IsTagContent && isValueType)
{
if (required)
{
classBuilder.AppendLine(Indent(2) + "[Required]");
}
classBuilder.AppendLine(Indent(2) + "[XmlIgnore]");
classBuilder.AppendLine(Indent(2) + "[JsonPropertyName(\"value\")]");
classBuilder.AppendLine(Indent(2) + "[JsonProperty(PropertyName = \"value\")]");
classBuilder.AppendLine($"{Indent(2)}public {dataType}? valueNullable {{ get; set; }}");
classBuilder.AppendLine();

classBuilder.AppendLine(Indent(2) + "[XmlText]");
classBuilder.AppendLine(Indent(2) + "[System.Text.Json.Serialization.JsonIgnore]");
classBuilder.AppendLine(Indent(2) + "[Newtonsoft.Json.JsonIgnore]");
classBuilder.AppendLine(Indent(2) + "public " + dataType + " value");
classBuilder.AppendLine(Indent(2) + "{");
classBuilder.AppendLine(Indent(3) + "get => valueNullable ?? default;");
classBuilder.AppendLine(Indent(3) + "set");
classBuilder.AppendLine(Indent(3) + "{");
classBuilder.AppendLine(Indent(4) + "this.valueNullable = value;");
classBuilder.AppendLine(Indent(3) + "}");
classBuilder.AppendLine(Indent(2) + "}");
classBuilder.AppendLine();
}
else if (element.IsTagContent)
{
classBuilder.AppendLine(Indent(2) + "[XmlText()]");
if (required && isValueType) // Why [Required] only on value types?
{
classBuilder.AppendLine(Indent(2) + "[Required]");
}
classBuilder.AppendLine($"{Indent(2)}public {dataType}{nullableReference} value {{ get; set; }}");
classBuilder.AppendLine();
}
else
{
Expand All @@ -145,30 +188,39 @@ private void ParseFieldProperty(ElementMetadata element, StringBuilder classBuil
// deserialization, we need both JsonProperty and JsonPropertyName annotations.
classBuilder.AppendLine(Indent(2) + "[JsonProperty(\"" + element.XName + "\")]");
classBuilder.AppendLine(Indent(2) + "[JsonPropertyName(\"" + element.XName + "\")]");
}

if (element.MaxOccurs > 1)
{
classBuilder.AppendLine(Indent(2) + "public List<" + dataType + "> " + element.Name + " { get; set; }\n");
}
else
{
if (required && isValueType)
if (element.MaxOccurs > 1)
{
classBuilder.AppendLine(Indent(2) + "[Required]");
classBuilder.AppendLine($"{Indent(2)}public List<{dataType}>{nullableReference} {element.Name} {{ get; set; }}\n");
}

bool shouldBeNullable = isValueType && !element.IsTagContent; // Can't use complex type for XmlText.
classBuilder.AppendLine(Indent(2) + "public " + dataType + (shouldBeNullable ? "?" : string.Empty) + " " + element.Name + " { get; set; }\n");
if (shouldBeNullable && element.Nillable.HasValue && !element.Nillable.Value && element.MinOccurs == 0)
else
{
WriteShouldSerializeMethod(classBuilder, element.Name);
if (required && isValueType)
{
classBuilder.AppendLine(Indent(2) + "[Required]");
}


if (isValueType)
{
classBuilder.AppendLine($"{Indent(2)}public {dataType}? {element.Name} {{ get; set; }}\n");

if (element.Nillable.HasValue && !element.Nillable.Value && element.MinOccurs == 0)
{
WriteShouldSerializeMethod(classBuilder, element.Name);
}
}
else
{
classBuilder.AppendLine($"{Indent(2)}public {dataType}{nullableReference} {element.Name} {{ get; set; }}\n");
}
}
}
}

private void ParseGroupProperty(ElementMetadata element, StringBuilder classBuilder, List<ElementMetadata> referredTypes, ref int elementOrder)
private void ParseGroupProperty(ElementMetadata element, StringBuilder classBuilder, ModelMetadata modelMetadata, List<ElementMetadata> referredTypes, ref int elementOrder, bool useNullableReferenceTypes)
{
var nullableReference = useNullableReferenceTypes ? "?" : string.Empty;
WriteRestrictionAnnotations(classBuilder, element);
elementOrder += 1;
classBuilder.AppendLine(Indent(2) + "[XmlElement(\"" + element.XName + "\", Order = " + elementOrder + ")]");
Expand All @@ -195,11 +247,16 @@ private void ParseGroupProperty(ElementMetadata element, StringBuilder classBuil

if (element.MaxOccurs > 1)
{
classBuilder.AppendLine(Indent(2) + "public List<" + dataType + "> " + element.Name + " { get; set; }\n");
classBuilder.AppendLine($"{Indent(2)}public List<{dataType}>{nullableReference} {element.Name} {{ get; set; }}\n");
}
else
{
classBuilder.AppendLine(Indent(2) + "public " + dataType + " " + element.Name + " { get; set; }\n");
classBuilder.AppendLine($"{Indent(2)}public {dataType}{nullableReference} {element.Name} {{ get; set; }}\n");

if (_generationSettings.AddShouldSerializeForTagContent)
{
AddShouldSerializeForTagContent(element, classBuilder, modelMetadata);
}
}

if (!primitiveType)
Expand All @@ -208,8 +265,26 @@ private void ParseGroupProperty(ElementMetadata element, StringBuilder classBuil
}
}

private void ParseAttributeProperty(ElementMetadata element, StringBuilder classBuilder, bool required)
private void AddShouldSerializeForTagContent(ElementMetadata element, StringBuilder classBuilder, ModelMetadata modelMetadata)
{
var children = modelMetadata.Elements.Values.Where(metadata =>
metadata.ParentElement == element.ID);
if (children.Count(metadata => metadata.FixedValue != null) == 1 && children.Count(metadata => metadata.IsTagContent) == 1)
{
var taggedContentChild = children.Single(metadata => metadata.IsTagContent);
var value = _generationSettings.XmlTextValueNullableHack && taggedContentChild.XsdValueType.HasValue &&
GetPropertyType(taggedContentChild.XsdValueType).IsValueType
? "valueNullable is not null"
: "value is not null";

classBuilder.AppendLine($"{Indent(2)}public bool ShouldSerialize{element.Name}() => {element.Name}?.{value};");
classBuilder.AppendLine();
}
}

private void ParseAttributeProperty(ElementMetadata element, StringBuilder classBuilder, bool required, bool useNullableReferenceTypes)
{
string nullableReference = useNullableReferenceTypes ? "?" : string.Empty;
string dataType = "string";
bool isValueType = false;
if (element.XsdValueType != null)
Expand All @@ -225,7 +300,8 @@ private void ParseAttributeProperty(ElementMetadata element, StringBuilder class
classBuilder.AppendLine(Indent(2) + "[BindNever]");
if (dataType.Equals("string"))
{
classBuilder.AppendLine(Indent(2) + "public " + dataType + " " + element.Name + " { get; set; } = \"" + element.FixedValue + "\";\n");
classBuilder.AppendLine(
$"{Indent(2)}public {dataType} {element.Name} {{ get; set; }} = \"{element.FixedValue}\";\n");
}
else
{
Expand All @@ -238,7 +314,7 @@ private void ParseAttributeProperty(ElementMetadata element, StringBuilder class
{
classBuilder.AppendLine(Indent(2) + "[Required]");
}
classBuilder.AppendLine(Indent(2) + "public " + dataType + " " + element.Name + " { get; set; }\n");
classBuilder.AppendLine($"{Indent(2)}public {dataType}{nullableReference} {element.Name} {{ get; set; }}\n");
}
}

Expand Down Expand Up @@ -448,10 +524,7 @@ or BaseValueType.Date
/// </summary>
private void WriteShouldSerializeMethod(StringBuilder classBuilder, string propName)
{
classBuilder.AppendLine(Indent(2) + $"public bool ShouldSerialize{propName}()");
classBuilder.AppendLine(Indent(2) + "{");
classBuilder.AppendLine(Indent(3) + $"return {propName}.HasValue;");
classBuilder.AppendLine(Indent(2) + "}");
classBuilder.AppendLine(Indent(2) + $"public bool ShouldSerialize{propName}() => {propName}.HasValue;");
classBuilder.AppendLine();
}

Expand All @@ -468,10 +541,7 @@ private void WriteAltinnRowId(StringBuilder classBuilder)
classBuilder.AppendLine(Indent(2) + "[Newtonsoft.Json.JsonIgnore]");
classBuilder.AppendLine(Indent(2) + "public Guid AltinnRowId { get; set; }");
classBuilder.AppendLine("");
classBuilder.AppendLine(Indent(2) + "public bool ShouldSerializeAltinnRowId()");
classBuilder.AppendLine(Indent(2) + "{");
classBuilder.AppendLine(Indent(3) + "return AltinnRowId != default;");
classBuilder.AppendLine(Indent(2) + "}");
classBuilder.AppendLine(Indent(2) + "public bool ShouldSerializeAltinnRowId() => AltinnRowId != default;");
classBuilder.AppendLine();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public interface IModelMetadataToCsharpConverter
/// </summary>
/// <param name="serviceMetadata">ServiceMetadata object</param>
/// <param name="separateNamespaces">Indicates if models should be stored in the separate namespace.</param>
/// <param name="useNullableReferenceTypes">Whether to add nullable? to reference types</param>
/// <returns>The model code in C#</returns>
public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separateNamespaces = false);
public string CreateModelFromMetadata(ModelMetadata serviceMetadata, bool separateNamespaces, bool useNullableReferenceTypes);
}
}
Loading

0 comments on commit 62ac455

Please sign in to comment.