diff --git a/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/HasDiagnostic/TableExt_NamespaceCasingMismatch.al b/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/HasDiagnostic/TableExt_NamespaceCasingMismatch.al new file mode 100644 index 00000000..1904243f --- /dev/null +++ b/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/HasDiagnostic/TableExt_NamespaceCasingMismatch.al @@ -0,0 +1,37 @@ +// The stored relation is Microsoft.Sales.History/"Sales Cr.Memo Header" -> +// Microsoft.EServices.EDocument/"E-Invoice Export Header". Here the namespace is declared +// with different literal casing ("microsoft.sales.history"). AL namespaces are +// case-insensitive, so the relation must still match and the diagnostic must still fire. +namespace microsoft.sales.history; + +table 50140 "Sales Cr.Memo Header" +{ + fields + { + field(1; "No."; Code[20]) { } + } +} + +table 50141 "E-Invoice Export Header" +{ + fields + { + field(1; "No."; Code[20]) { } + } +} + +tableextension 50142 MyCrMemoExt extends "Sales Cr.Memo Header" +{ + fields + { + [|field(50100; MyFieldA; Integer) { }|] + } +} + +tableextension 50143 MyEInvExt extends "E-Invoice Export Header" +{ + fields + { + [|field(50100; MyFieldB; Integer) { }|] // Same ID (50100) as in MyCrMemoExt, different name + } +} diff --git a/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/TransferFieldsNameMismatch.cs b/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/TransferFieldsNameMismatch.cs index cce6120f..f7088e4a 100644 --- a/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/TransferFieldsNameMismatch.cs +++ b/src/ALCops.PlatformCop.Test/Rules/TransferFieldsNameMismatch/TransferFieldsNameMismatch.cs @@ -33,10 +33,11 @@ public void Setup() [TestCase("InvocationWithTableExtension")] [TestCase("TableExt_Multiple_SameBase")] [TestCase("TableExtension")] + [TestCase("TableExt_NamespaceCasingMismatch")] public async Task HasDiagnostic(string testCase) { SkipTestIfVersionIsTooLow( - ["InvocationWithTableExtension", "TableExt_Multiple_SameBase", "TableExtension", "TableExtensionTypeWithLength"], + ["InvocationWithTableExtension", "TableExt_Multiple_SameBase", "TableExtension", "TableExtensionTypeWithLength", "TableExt_NamespaceCasingMismatch"], testCase, "13.0", "No support for tableextensions when target itself is already declared in the same module"); diff --git a/src/ALCops.PlatformCop/Analyzers/TransferFieldsRelations.cs b/src/ALCops.PlatformCop/Analyzers/TransferFieldsRelations.cs index 1553edf9..8eba2cdf 100644 --- a/src/ALCops.PlatformCop/Analyzers/TransferFieldsRelations.cs +++ b/src/ALCops.PlatformCop/Analyzers/TransferFieldsRelations.cs @@ -38,13 +38,16 @@ private static bool Matches(ObjectName configured, ITableTypeSymbol table) var ns = table.GetContainingNamespaceQualifiedNameWithReflection() ?? string.Empty; var name = table.Name ?? string.Empty; - if (StringComparer.Ordinal.Equals(configured.Name, name) && - StringComparer.Ordinal.Equals(configured.Namespace, ns)) + // AL namespaces and object identifiers are case-insensitive, but the runtime-resolved + // namespace casing (symbol.ContainingNamespace.QualifiedName) is not stable across + // compilations, so both comparisons must be case-insensitive. + if (SemanticFacts.IsSameName(configured.Name, name) && + SemanticFacts.IsSameName(configured.Namespace, ns)) return true; - // Keep backwards comptibility for objects without namespace + // Keep backwards compatibility for objects without namespace if (string.IsNullOrEmpty(ns) && - StringComparer.Ordinal.Equals(configured.Name, name)) + SemanticFacts.IsSameName(configured.Name, name)) return true; return false; diff --git a/src/ALCops.PlatformCop/Analyzers/TransferFieldsSchemaCompatibility.cs b/src/ALCops.PlatformCop/Analyzers/TransferFieldsSchemaCompatibility.cs index d4b9194f..dd599944 100644 --- a/src/ALCops.PlatformCop/Analyzers/TransferFieldsSchemaCompatibility.cs +++ b/src/ALCops.PlatformCop/Analyzers/TransferFieldsSchemaCompatibility.cs @@ -220,12 +220,12 @@ private static void AnalyzeTableExtensionForRelation(SymbolAnalysisContext ctx, var sourceTableExtensions = tableExtensions - .Where(te => te.Target is not null && te.Target.Name.Equals(relation.Source.Name)) + .Where(te => te.Target is not null && SemanticFacts.IsSameName(te.Target.Name, relation.Source.Name)) .SelectMany(x => x.AddedFields); var targetTableExtensions = tableExtensions - .Where(te => te.Target is not null && te.Target.Name.Equals(relation.Target.Name)) + .Where(te => te.Target is not null && SemanticFacts.IsSameName(te.Target.Name, relation.Target.Name)) .SelectMany(x => x.AddedFields); var sourceById = BuildFieldMapById(sourceTableExtensions);