Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,36 @@ protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() =>

[Theory]
[EmbeddedFileData("DacWithMultipleFieldTypeAttributes.cs")]
public virtual Task PropertyWithMultipleFieldTypeAttributes(string source) =>
public virtual Task Property_WithMultiple_FieldTypeAttributes(string source) =>
VerifyCSharpDiagnosticAsync(source,
Descriptors.PX1023_MultipleTypeAttributesOnProperty.CreateFor(line: 24, column: 4),
Descriptors.PX1023_MultipleTypeAttributesOnProperty.CreateFor(line: 25, column: 4));

[Theory]
[EmbeddedFileData("DacWithMultipleCalcedOnDbSideAttributes.cs")]
public virtual Task PropertyWithMultipleCalcedOnDbSideAttributes(string source) =>
public virtual Task Property_WithMultiple_CalcedOnDbSideAttributes(string source) =>
VerifyCSharpDiagnosticAsync(source,
Descriptors.PX1023_MultipleCalcedOnDbSideAttributesOnProperty.CreateFor(line: 16, column: 4),
Descriptors.PX1023_MultipleCalcedOnDbSideAttributesOnProperty.CreateFor(line: 17, column: 4));

[Theory]
[EmbeddedFileData("DacWithMultipleFieldTypeAttributes_Expected.cs")]
public virtual Task MultipleFieldTypeAttributes_ShouldNotShowDiagnostic(string source) =>
public virtual Task Multiple_FieldTypeAttributes_ShouldNotShowDiagnostic(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacWithMultipleCalcedOnDbSideAttributes_Expected.cs")]
public virtual Task MultipleCalcedOnDbSideAttributes_ShouldNotShowDiagnostic(string source) =>
public virtual Task Multiple_CalcedOnDbSideAttributes_ShouldNotShowDiagnostic(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacFieldAttributesTypeMismatch_Expected.cs")]
public virtual Task DacPropertyTypeNotMatchingAttributeType_ShouldNotShowDiagnostic(string source) =>
public virtual Task DacPropertyType_NotMatching_AttributeType_ShouldNotShowDiagnostic(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacFieldAttributesTypeMismatch.cs")]
public virtual Task DacPropertyTypeNotMatchingAttributeType(string source) =>
public virtual Task DacPropertyType_NotMatching_AttributeType(string source) =>
VerifyCSharpDiagnosticAsync(source,
Descriptors.PX1021_PXDBFieldAttributeNotMatchingDacProperty.CreateFor((Line: 24, Column: 4),
extraLocation: (Line: 26, Column: 10)),
Expand All @@ -74,24 +74,29 @@ public virtual Task DacPropertyTypeNotMatchingAttributeType(string source) =>

[Theory]
[EmbeddedFileData("DacWithInvalidAggregatorAttributes.cs")]
public virtual Task DacPropertyWithInvalidAggregatorAttributes(string source) =>
public virtual Task DacProperty_WithInvalid_AggregatorAttributes(string source) =>
VerifyCSharpDiagnosticAsync(source,
Descriptors.PX1023_MultipleCalcedOnDbSideAttributesOnAggregators.CreateFor(line: 41, column: 4),
Descriptors.PX1023_MultipleTypeAttributesOnAggregators.CreateFor(line: 55, column: 4));

[Theory]
[EmbeddedFileData("DacFieldTypeMismatchPXDBScalarAttr.cs")]
public virtual Task DacPropertyWithPXDBScalarAttribute(string source) =>
public virtual Task DacProperty_WithPXDBScalarAttribute(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacWithValidAggregatorAttributes.cs")]
public virtual Task DacWithValidAggregatorAttributes(string source) =>
public virtual Task Dac_WithValid_AggregatorAttributes(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacWithPXDBPackedIntegerArrayAttribute.cs")]
public virtual Task Dac_WithPXDBPackedIntegerArrayAttribute(string source) =>
VerifyCSharpDiagnosticAsync(source);

[Theory]
[EmbeddedFileData("DacFieldWithINUnitAttribute.cs")]
public virtual Task DacFieldWithINUnitAttribute(string source) =>
public virtual Task DacField_WithINUnitAttribute(string source) =>
VerifyCSharpDiagnosticAsync(source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using PX.Data;

namespace PX.Objects.HackathonDemo
{
// Acuminator disable once PX1069 MissingMandatoryDacFields No need in unit test
[PXHidden]
public class SomeDac : PXBqlTable, IBqlTable
{
#region DacID
public abstract class dacID : PX.Data.BQL.BqlInt.Field<dacID> { }

[PXDBInt]
public virtual int? DacID
{
get;
set;
}
#endregion

#region DiscountsAppliedToLine
public abstract class discountsAppliedToLine : IBqlField { }

[PXDBPackedIntegerArray]
public virtual ushort[]? DiscountsAppliedToLine { get; set; }
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

using Acuminator.Utilities.Common;
using Acuminator.Utilities.Roslyn.Semantic;
using Acuminator.Utilities.Roslyn.Semantic.Attribute;

using Microsoft.CodeAnalysis;

Expand All @@ -30,6 +29,8 @@ public class FieldTypeAttributesMetadataProvider

public ImmutableArray<ITypeSymbol> WellKnownNonDataTypeAttributes { get; }

public ImmutableDictionary<ITypeSymbol, DataTypeAttributeInfo> MetadataForWellKnownSpecialDataTypeAttributes { get; }

private readonly INamedTypeSymbol _pxDBCalcedAttribute;
private readonly INamedTypeSymbol _pxDBScalarAttribute;
private readonly INamedTypeSymbol _pxDBFieldAttribute;
Expand Down Expand Up @@ -59,6 +60,7 @@ public FieldTypeAttributesMetadataProvider(PXContext pxContext)
.ToImmutableArray();

WellKnownNonDataTypeAttributes = GetWellKnownNonDataTypeAttributes(_pxContext);
MetadataForWellKnownSpecialDataTypeAttributes = GetMetadataForWellKnownSpecialDataTypeAttributes(_pxContext);
}

public bool IsWellKnownNonDataTypeAttribute(ITypeSymbol attribute)
Expand Down Expand Up @@ -90,6 +92,11 @@ internal IReadOnlyCollection<DataTypeAttributeInfo> GetDacFieldTypeAttributeInfo
if (flattenedAttributes.Count == 0)
return [];

var metadataForWellKnownSpecialDataTypeAttribute = TryGetMetadataForWellKnownSpecialDataTypeAttribute(originalAttribute);

if (metadataForWellKnownSpecialDataTypeAttribute != null)
return [metadataForWellKnownSpecialDataTypeAttribute];

var mixedDbBoundnessAttributeInfos = GetMixedDbBoundnessAttributeInfosInFlattenedSet(originalAttribute, flattenedAttributes);
bool hasMixedBoundnessAttributes = mixedDbBoundnessAttributeInfos?.Count > 0;
var typeAttributeInfos = hasMixedBoundnessAttributes
Expand Down Expand Up @@ -127,6 +134,27 @@ internal IReadOnlyCollection<DataTypeAttributeInfo> GetDacFieldTypeAttributeInfo
return typeAttributeInfos as IReadOnlyCollection<DataTypeAttributeInfo> ?? [];
}

private DataTypeAttributeInfo? TryGetMetadataForWellKnownSpecialDataTypeAttribute(ITypeSymbol attribute)
{
if (MetadataForWellKnownSpecialDataTypeAttributes.Count == 0)
return null;
else if (MetadataForWellKnownSpecialDataTypeAttributes.TryGetValue(attribute, out var metadata))
return metadata;

var baseAttributeTypes = attribute.GetBaseTypes();

foreach (ITypeSymbol baseAttributeType in baseAttributeTypes)
{
if (baseAttributeType.SpecialType == SpecialType.System_Object)
return null;

if (MetadataForWellKnownSpecialDataTypeAttributes.TryGetValue(baseAttributeType, out var metadata))
return metadata;
}

return null;
}

/// <summary>
/// Gets mixed database boundness attribute infos in the flattened set with consideration of type hierarchy.<br/>
/// If there are multiple mixed boundness attributes in the type hierarchy then only the most derived one should be included into results <br/>
Expand Down Expand Up @@ -255,11 +283,11 @@ private bool DacFieldTypeAttributeCapturedByMixedBoundnessAttributesTypeHierarch
{ pxContext.FieldAttributes.PXDBDataLengthAttribute, pxContext.SystemTypes.Int64 },
};

var packagedIntegerAttribute = pxContext.FieldAttributes.PXDBPackedIntegerArrayAttribute;
var packedIntegerAttribute = pxContext.FieldAttributes.PXDBPackedIntegerArrayAttribute;

if (packagedIntegerAttribute != null)
if (packedIntegerAttribute != null)
{
types.Add(packagedIntegerAttribute, pxContext.SystemTypes.UInt16Array);
types.Add(packedIntegerAttribute, pxContext.SystemTypes.UInt16Array);
}

return types;
Expand Down Expand Up @@ -290,16 +318,42 @@ private static IEnumerable<MixedDbBoundnessAttributeInfo> GetDacFieldTypeAttribu

private static ImmutableArray<ITypeSymbol> GetWellKnownNonDataTypeAttributes(PXContext pxContext)
{
var wellKnownNonDataTypeAttributes = ImmutableArray.CreateBuilder<ITypeSymbol>(initialCapacity: 6);
var wellKnownNonDataTypeAttributes = ImmutableArray.CreateBuilder<ITypeSymbol>(initialCapacity: 9);

wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXUIFieldAttribute.Type);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXDefaultAttribute);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXStringListAttribute.Type);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXIntListAttribute.Type);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXSelectorAttribute.Type);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXForeignReferenceAttribute);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXParentAttribute);
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.PXDBDefaultAttribute);

if (pxContext.AttributeTypes.AutoNumberAttribute.IsDefined)
wellKnownNonDataTypeAttributes.Add(pxContext.AttributeTypes.AutoNumberAttribute.Type!);

return wellKnownNonDataTypeAttributes.ToImmutable();
}

private static ImmutableDictionary<ITypeSymbol, DataTypeAttributeInfo> GetMetadataForWellKnownSpecialDataTypeAttributes(PXContext pxContext)
{
ImmutableDictionary<ITypeSymbol, DataTypeAttributeInfo>? metadataForWellKnownSpecialDataTypeAttributes = null;
var packedIntegerAttribute = pxContext.FieldAttributes.PXDBPackedIntegerArrayAttribute;

if (packedIntegerAttribute != null)
{
// PXDBPackedIntegerArrayAttribute is a special attribute written in a very hacky way.
// It derives from PXDBBinaryAttribute which works with byte[] but in reality PXDBPackedIntegerArrayAttribute works with ushort[].
// This breaks Acumatica design principle where derived attribute work on properties with the same property type as their base attribute.
// Thus, the attribute needs special handling in Acuminator.
var packedIntegerMetadata = new DataTypeAttributeInfo(FieldTypeAttributeKind.BoundTypeAttribute, packedIntegerAttribute,
pxContext.SystemTypes.UInt16Array);
metadataForWellKnownSpecialDataTypeAttributes =
ImmutableDictionary.CreateRange(SymbolEqualityComparer.Default,
new[] { KeyValuePair.Create(packedIntegerAttribute as ITypeSymbol, packedIntegerMetadata) });
}

return metadataForWellKnownSpecialDataTypeAttributes ?? ImmutableDictionary<ITypeSymbol, DataTypeAttributeInfo>.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public class DataTypeAttributeSymbols : SymbolsSetBase
public INamedTypeSymbol PXDBLongIdentityAttribute => Compilation.GetTypeByMetadataName(TypeFullNames.PXDBLongIdentityAttribute)!;
public INamedTypeSymbol PXDBBinaryAttribute => Compilation.GetTypeByMetadataName(TypeFullNames.PXDBBinaryAttribute)!;

public INamedTypeSymbol PXDBPackedIntegerArrayAttribute =>
Compilation.GetTypeByMetadataName(TypeFullNames.PXDBPackedIntegerArrayAttributeFullName_Acumatica2018R2)!;
public INamedTypeSymbol? PXDBPackedIntegerArrayAttribute =>
Compilation.GetTypeByMetadataName(TypeFullNames.PXDBPackedIntegerArrayAttributeFullName_Acumatica2018R2);

public INamedTypeSymbol PXDBUserPasswordAttribute => Compilation.GetTypeByMetadataName(TypeFullNames.PXDBUserPasswordAttribute)!;

Expand Down