From 77c83f09017583d7ca767de391e9605d7913cbf4 Mon Sep 17 00:00:00 2001 From: Tine Staric Date: Tue, 12 Dec 2023 23:25:31 +0200 Subject: [PATCH 1/2] Add Rule0044 AnalyzeTransferFields --- Design/Rule0044AnalyzeTransferField.cs | 618 +++++++++++++++++++++++++ LinterCopAnalyzers.Generated.cs | 2 + LinterCopAnalyzers.resx | 18 + 3 files changed, 638 insertions(+) create mode 100644 Design/Rule0044AnalyzeTransferField.cs diff --git a/Design/Rule0044AnalyzeTransferField.cs b/Design/Rule0044AnalyzeTransferField.cs new file mode 100644 index 00000000..a9b8c27d --- /dev/null +++ b/Design/Rule0044AnalyzeTransferField.cs @@ -0,0 +1,618 @@ +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; +using System.Collections.Immutable; +using System.Reflection; + +namespace BusinessCentral.LinterCop.Design +{ + public delegate bool Filter(IGrouping fieldGroup); + + [DiagnosticAnalyzer] + public class Rule0044AnalyzeTransferFields : DiagnosticAnalyzer + { + private List> tablePairs = new List>(); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0044AnalyzeTransferFields); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(new Action(AnalyzeTableExtension), SyntaxKind.TableExtensionObject); + context.RegisterOperationAction(new Action(AnalyzeTransferFields), OperationKind.InvocationExpression); + LoadTablePairs(); + } + + private void AnalyzeTableExtension(SyntaxNodeAnalysisContext ctx) + { + TableExtensionSyntax tableExtensionSyntax = (TableExtensionSyntax)ctx.Node; + string? baseObject = GetIdentifierName(tableExtensionSyntax.BaseObject.Identifier); + + if (baseObject == null) + return; + + IEnumerable> tables = tablePairs.Where(x => x.Item1.Equals(baseObject) || x.Item2.Equals(baseObject)); + + if (tables.Count() == 0) + return; + + Table table1 = new Table(baseObject); + table1.PopulateFields(tableExtensionSyntax.Fields); + + Dictionary tableExtensions = GetTableExtensions(ctx.SemanticModel.Compilation); + + foreach (Tuple tablePair in tables) + { + string tableName = baseObject.Equals(tablePair.Item1) ? tablePair.Item2 : tablePair.Item1; + + Table table2 = GetTableWithFieldsByTableName(ctx.SemanticModel.Compilation, tableName, tableExtensions); + + List> fieldGroups = GetFieldsWithSameIDAndApplyFilter(table1.Fields, table2.Fields, DifferentNameAndTypeFilter); + + ReportFieldDiagnostics(ctx, table1, fieldGroups); + } + } + + private async void AnalyzeTransferFields(OperationAnalysisContext ctx) + { + if (ctx.Operation.Syntax.GetType() != typeof(InvocationExpressionSyntax)) + return; + + InvocationExpressionSyntax invocationExpression = (InvocationExpressionSyntax)ctx.Operation.Syntax; + + Tuple? records = GetInvokingRecordNames(invocationExpression); + + if (records == null) + return; + + Task localVariablesTask = ctx.ContainingSymbol.DeclaringSyntaxReference.GetSyntaxAsync(); + Task globalVariablesTask = ctx.ContainingSymbol.ContainingSymbol.DeclaringSyntaxReference.GetSyntaxAsync(); + + List variables = new List(); + + SyntaxNode localVariables = await localVariablesTask; + variables.AddRange(FindLocalVariables(localVariables)); + SyntaxNode globalVariables = await globalVariablesTask; + variables.AddRange(FindGlobalVariables(globalVariables)); + + string? tableName1 = GetObjectName(variables.FirstOrDefault(x => + { + string? name = x.GetNameStringValue(); + + if (name == null) + return false; + + return name.Equals(records.Item1); + })); + + string? tableName2 = GetObjectName(variables.FirstOrDefault(x => + { + string? name = x.GetNameStringValue(); + + if (name == null) + return false; + + return name.Equals(records.Item2); + })); + + if (tableName1 == null && (records.Item1.ToLower().Equals("rec") || records.Item1.ToLower().Equals("xrec"))) + tableName1 = GetObjectSourceTable(globalVariables, ctx.Compilation); + + if (tableName2 == null && (records.Item2.ToLower().Equals("rec") || records.Item2.ToLower().Equals("xrec"))) + tableName2 = GetObjectSourceTable(globalVariables, ctx.Compilation); + + if (tableName1 == tableName2 || tableName1 == null || tableName2 == null) + return; + + Dictionary tableExtensions = GetTableExtensions(ctx.Compilation); + + Table table1 = GetTableWithFieldsByTableName(ctx.Compilation, tableName1); + Table table2 = GetTableWithFieldsByTableName(ctx.Compilation, tableName2); + + List> fieldGroups = GetFieldsWithSameIDAndApplyFilter(table1.Fields, table2.Fields, DifferentNameAndTypeFilter); + + if (fieldGroups.Any()) + { + ReportFieldDiagnostics(ctx, table1, fieldGroups); + ReportFieldDiagnostics(ctx, table2, fieldGroups); + + if (table1.Fields.Any(x => x.Location != null) || table2.Fields.Any(x => x.Location != null)) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0044AnalyzeTransferFields, invocationExpression.GetLocation(), table1.Name, table2.Name)); + } + } + + private void ReportFieldDiagnostics(OperationAnalysisContext ctx, Table table, List> fieldGroups) + { + foreach (IGrouping fieldGroup in fieldGroups) + { + IEnumerable fieldGroupValues = fieldGroup.ToList(); + + Field field = fieldGroupValues.First(x => x.Table.Equals(table)); + + if (field.Location == null) + continue; + + foreach (Field fieldGroupValue in fieldGroupValues) + { + if (fieldGroupValue.Equals(field)) + continue; + + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0044AnalyzeTableExtension, field.Location, fieldGroupValue.Table.Name)); + } + } + } + + private void ReportFieldDiagnostics(SyntaxNodeAnalysisContext ctx, Table table, List> fieldGroups) + { + foreach (IGrouping fieldGroup in fieldGroups) + { + IEnumerable fieldGroupValues = fieldGroup.ToList(); + + Field field = fieldGroupValues.First(x => x.Table.Equals(table)); + + if (field.Location == null) + continue; + + foreach(Field fieldGroupValue in fieldGroupValues) + { + if (fieldGroupValue.Equals(field)) + continue; + + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0044AnalyzeTableExtension, field.Location, fieldGroupValue.Table.Name)); + } + } + } + + private string? GetObjectSourceTable(SyntaxNode node, Compilation compilation) + { + Type nodeType = node.GetType(); + + if(nodeType == typeof(TableSyntax)) + { + TableSyntax tableSyntax = (TableSyntax)node; + + return tableSyntax.Name.Identifier.ToString().Replace("\"", ""); + } + + if(nodeType == typeof(TableExtensionSyntax)) + { + TableExtensionSyntax tableExtensionSyntax = (TableExtensionSyntax)node; + + return GetIdentifierName(tableExtensionSyntax.BaseObject.Identifier); + } + + if(nodeType == typeof(PageSyntax)) + { + PageSyntax pageSyntax = (PageSyntax)node; + + foreach(PropertySyntaxOrEmpty property in pageSyntax.PropertyList.Properties) + { + if (property.GetType() != typeof(PropertySyntax)) + continue; + + PropertySyntax propertySyntax = (PropertySyntax)property; + + if (!propertySyntax.Name.Identifier.ToString().Equals("SourceTable")) + continue; + + if (propertySyntax.Value.GetType() != typeof(ObjectReferencePropertyValueSyntax)) + continue; + + ObjectReferencePropertyValueSyntax objectReferencePropertyValueSyntax = (ObjectReferencePropertyValueSyntax) propertySyntax.Value; + + return GetIdentifierName(objectReferencePropertyValueSyntax.ObjectNameOrId.Identifier); + } + + return null; + } + + if(nodeType == typeof(PageExtensionSyntax)) + { + PageExtensionSyntax pageExtensionSyntax = (PageExtensionSyntax) node; + + string? pageExtensionName = GetIdentifierName(pageExtensionSyntax.BaseObject.Identifier); + + if (pageExtensionName == null) + return null; + + return GetSourceTableByPageName(compilation, pageExtensionName); + } + + + return null; + } + + private string? GetIdentifierName(SyntaxNode identifier) + { + if (identifier.GetType() != typeof(IdentifierNameSyntax)) + return null; + + IdentifierNameSyntax identifierNameSyntax = (IdentifierNameSyntax)identifier; + + return identifierNameSyntax.ToString().Replace("\"", ""); + } + + private Tuple? GetInvokingRecordNames(InvocationExpressionSyntax invocationExpression) + { + if (invocationExpression.ArgumentList.Arguments.Count < 1) + return null; + + if (invocationExpression.Expression.GetType() == typeof(MemberAccessExpressionSyntax)) + { + MemberAccessExpressionSyntax memberAccessExpression = (MemberAccessExpressionSyntax)invocationExpression.Expression; + if (!memberAccessExpression.Name.ToString().Equals("TransferFields")) + return null; + + return new Tuple(memberAccessExpression.Expression.ToString(), invocationExpression.ArgumentList.Arguments[0].ToString()); + } + + if(invocationExpression.Expression.GetType() == typeof(IdentifierNameSyntax)) + { + IdentifierNameSyntax identifierNameSyntax = (IdentifierNameSyntax)invocationExpression.Expression; + + if (!identifierNameSyntax.Identifier.ToString().Equals("TransferFields")) + return null; + + return new Tuple("Rec", invocationExpression.ArgumentList.Arguments[0].ToString()); + } + + return null; + } + + private Dictionary GetTableExtensions(Compilation compilation) + { + Dictionary tableExtensions = new Dictionary(); + + foreach (SyntaxTree syntaxTree in compilation.SyntaxTrees) + { + foreach(SyntaxNode node in syntaxTree.GetRoot().ChildNodes()) + { + if (node.GetType() != typeof(TableExtensionSyntax)) + continue; + + TableExtensionSyntax tableExtension = (TableExtensionSyntax) node; + + string? extendedTable = GetIdentifierName(tableExtension.BaseObject.Identifier); + + if (extendedTable == null) + continue; + + try + { + tableExtensions.Add(extendedTable, tableExtension); + } + catch { } + } + } + + return tableExtensions; + } + + private Table GetTableWithFieldsByTableName(Compilation compilation, string tableName, Dictionary? tableExtensions = null) + { + IApplicationObjectTypeSymbol tableSymbol = compilation.GetApplicationObjectTypeSymbolsByNameAcrossModules(SymbolKind.Table, tableName).FirstOrDefault(); + + Table table = new Table(tableName); + + if(tableSymbol != null) + { + SyntaxReference? syntaxReference = tableSymbol.DeclaringSyntaxReference; + + if (syntaxReference != null) + { + TableSyntax tableSyntax = (TableSyntax) syntaxReference.GetSyntax(); + + table.PopulateFields(tableSyntax.Fields); + } + else + table.PopulateFields(tableSymbol); + } + + if (tableExtensions == null) + return table; + + TableExtensionSyntax? tableExtension; + if (tableExtensions.TryGetValue(tableName, out tableExtension)) + { + table.PopulateFields(tableExtension.Fields); + } + + return table; + } + + private string? GetSourceTableByPageName(Compilation compilation, string pageName) + { + IApplicationObjectTypeSymbol pageSymbol = compilation.GetApplicationObjectTypeSymbolsByNameAcrossModules(SymbolKind.Page, pageName).FirstOrDefault(); + + if (pageSymbol != null) + { + SyntaxReference? syntaxReference = pageSymbol.DeclaringSyntaxReference; + + if (syntaxReference != null) + { + PageSyntax pageSyntax = (PageSyntax)syntaxReference.GetSyntax(); + + foreach (PropertySyntaxOrEmpty property in pageSyntax.PropertyList.Properties) + { + if (property.GetType() != typeof(PropertySyntax)) + continue; + + PropertySyntax propertySyntax = (PropertySyntax)property; + + if (!propertySyntax.Name.Identifier.ToString().Equals("SourceTable")) + continue; + + if (propertySyntax.Value.GetType() != typeof(ObjectReferencePropertyValueSyntax)) + continue; + + ObjectReferencePropertyValueSyntax objectReferencePropertyValueSyntax = (ObjectReferencePropertyValueSyntax)propertySyntax.Value; + + return GetIdentifierName(objectReferencePropertyValueSyntax.ObjectNameOrId.Identifier); + } + } + else + { + Assembly assembly = typeof(Microsoft.Dynamics.Nav.CodeAnalysis.Symbols.VariableKind).Assembly; + + Type type = assembly.GetType(pageSymbol.GetType().ToString()); + + MethodInfo method = type.GetMethod("GetRelatedTable", BindingFlags.NonPublic | BindingFlags.Instance); + + object obj = method.Invoke(pageSymbol, null); + + if (obj == null) + return null; + + type = assembly.GetType(obj.GetType().ToString()); + + method = type.GetMethod("get_Name", BindingFlags.Public | BindingFlags.Instance); + + return (string) method.Invoke(obj, null); + } + } + + return null; + } + + private List> GetFieldsWithSameIDAndApplyFilter(List fields1, List fields2, Filter filter) + { + List result = new List(fields1); + result.AddRange(fields2); + + return result.GroupBy(x => x.Id).Where(x => x.Count() > 1 && filter(x)).ToList(); + } + + private bool DifferentNameAndTypeFilter(IGrouping fieldGroup) + { + string name = fieldGroup.First().Name; + string type = fieldGroup.First().Type; + + foreach(Field field in fieldGroup) { + if (!(name.Equals(field.Name) && type.Equals(field.Type))) + return true; + } + + return false; + } + + private string? GetObjectName(VariableDeclarationBaseSyntax variable) + { + if (variable == null) + return null; + + SubtypedDataTypeSyntax subtypedData = (SubtypedDataTypeSyntax) variable.Type.DataType; + return GetIdentifierName(subtypedData.Subtype.Identifier); + } + + private List FindLocalVariables(SyntaxNode node) + { + VarSectionSyntax varSection = (VarSectionSyntax) node.DescendantNodes().FirstOrDefault(x => x.Kind == SyntaxKind.VarSection); + + if (varSection == null) + return new List(); + + return varSection.Variables.ToList(); + } + + private List FindGlobalVariables(SyntaxNode node) + { + GlobalVarSectionSyntax varSection = (GlobalVarSectionSyntax)node.DescendantNodes().FirstOrDefault(x => x.Kind == SyntaxKind.GlobalVarSection); + + if (varSection == null) + return new List(); + + return varSection.Variables.ToList(); + } + + private void LoadTablePairs() + { + tablePairs = new List> + { + new Tuple("Contact", "Customer"), + new Tuple("Contact", "Vendor"), + new Tuple("Contact", "Employee"), + new Tuple("Contact", "BankAccount"), + + new Tuple("Deferral Line", "Deferral Line Archive"), + new Tuple("Deferral Line", "Posted Deferral Line"), + new Tuple("Deferral Header", "Deferral Header Archive"), + new Tuple("Deferral Header", "Posted Deferral Header"), + + new Tuple("Gen. Journal Line", "Posted Gen. Journal Line"), + new Tuple("Gen. Journal Line", "Standard General Journal Line"), + + new Tuple("Item Journal Line", "Standard Item Journal Line"), + + new Tuple("Whse. Item Tracking Line", "Tracking Specification"), + new Tuple("Whse. Item Tracking Line", "Reservation Entry"), + new Tuple("Reservation Entry", "Tracking Specification"), + + new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Cust. Ledg. Entry"), + new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Vendor Ledg. Entry"), + new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Employee Ledger Entry"), + + new Tuple("CV Ledger Entry Buffer", "Cust. Ledger Entry"), + new Tuple("CV Ledger Entry Buffer", "Cust. Ledger Entry"), + + new Tuple("Tracking Specification", "Reservation Entry"), + new Tuple("Assembly Line", "Posted Assembly Line"), + new Tuple("Assembly Header", "Posted Assembly Header"), + + new Tuple("Invt. Document Header", "Invt. Receipt Header"), + new Tuple("Invt. Document Header", "Invt. Shipment Header"), + + new Tuple("Bank Acc. Reconciliation", "Bank Account Statement"), + new Tuple("Bank Acc. Reconciliation Line", "Bank Account Statement Line"), + + new Tuple("Posted Payment Recon. Line", "Bank Acc. Reconciliation Line"), + new Tuple("Posted Payment Recon. Hdr", "Posted Payment Recon. Hdr"), + + new Tuple("Prod. Order Rtng Comment Line", "Routing Comment Line"), + new Tuple("Prod. Order Routing Personnel", "Routing Personnel"), + new Tuple("Prod. Order Rtng Qlty Meas.", "Prod. Order Rtng Qlty Meas."), + new Tuple("Prod. Order Routing Tool", "Routing Tool"), + + new Tuple("Posted Approval Entry", "Posted Approval Entry"), + new Tuple("Posted Approval Comment Line", "Approval Comment Line"), + + new Tuple("Sales Header", "Sales Shipment Header"), + new Tuple("Sales Header", "Sales Invoice Header"), + new Tuple("Sales Header", "Sales Cr.Memo Header"), + new Tuple("Sales Header", "Return Receipt Header"), + new Tuple("Sales Header", "Sales Header Archive"), + new Tuple("Sales Comment Line", "Sales Comment Line Archive"), + + new Tuple("Purchase Header", "Purch. Rcpt. Header"), + new Tuple("Purchase Header", "Purch. Inv. Header"), + new Tuple("Purchase Header", "Purch. Cr. Memo Hdr."), + new Tuple("Purchase Header", "Return Shipment Header"), + new Tuple("Purchase Header", "Purchase Header Archive"), + new Tuple("Purchase Comment Line", "Purchase Comment Line Archive"), + + new Tuple("Sales Line", "Sales Shipment Line"), + new Tuple("Sales Line", "Sales Invoice Line"), + new Tuple("Sales Line", "Sales Cr.Memo Line"), + new Tuple("Sales Line", "Return Receipt Line"), + new Tuple("Sales Line", "Sales Line Archive"), + + new Tuple("Purchase Line", "Purch. Rcpt. Line"), + new Tuple("Purchase Line", "Purch. Inv. Line"), + new Tuple("Purchase Line", "Purch. Cr. Memo Line"), + new Tuple("Purchase Line", "Return Shipment Line"), + new Tuple("Purchase Line", "Purchase Line Archive"), + + new Tuple("Warehouse Activity Header", "Registered Whse. Activity Hdr."), + new Tuple("Warehouse Activity Header", "Registered Invt. Movement Hdr."), + new Tuple("Warehouse Activity Header", "Posted Invt. Pick Header"), + new Tuple("Warehouse Activity Header", "Posted Invt. Put-away Header"), + + new Tuple("Warehouse Activity Line", "Registered Whse. Activity Line"), + new Tuple("Warehouse Activity Line", "Registered Invt. Movement Line"), + new Tuple("Warehouse Activity Line", "Posted Invt. Pick Line"), + new Tuple("Warehouse Activity Line", "Posted Invt. Put-away Line"), + + new Tuple("Time Sheet Header", "Time Sheet Header Archive"), + new Tuple("Time Sheet Line", "Time Sheet Line Archive"), + new Tuple("Time Sheet Detail Archive", "Time Sheet Detail"), + new Tuple("Time Sheet Comment Line", "Time Sheet Cmt. Line Archive") + }; + } + + public class Field + { + public int Id { get; } + public string Name { get; } + public string Type { get; } + public Microsoft.Dynamics.Nav.CodeAnalysis.Text.Location? Location { get; } + public Table Table { get; } + + public Field(int id, string name, string type, Microsoft.Dynamics.Nav.CodeAnalysis.Text.Location? location, Table table) + { + Id = id; + Name = name; + Type = type; + Location = location; + Table = table; + } + } + + public class Table + { + public string Name { get; } + public List Fields { get; } + + public Table(string name) + { + Name = name; + Fields = new List(); + } + + public void PopulateFields(IApplicationObjectTypeSymbol table) + { + if (table == null) + return; + + Assembly assembly = typeof(Microsoft.Dynamics.Nav.CodeAnalysis.Symbols.VariableKind).Assembly; + + Type type = assembly.GetType(table.GetType().ToString()); + + MethodInfo method = type.GetMethod("GetFields", BindingFlags.NonPublic | BindingFlags.Instance); + + var collection = (System.Collections.IEnumerable)method.Invoke(table, null); + + foreach (var field in collection) + { + type = assembly.GetType(field.GetType().ToString()); + + PropertyInfo idprop = type.GetProperty("Id", BindingFlags.Instance | BindingFlags.Public); + PropertyInfo nameprop = type.GetProperty("Name", BindingFlags.Instance | BindingFlags.Public); + PropertyInfo typeprop = type.GetProperty("Type", BindingFlags.Instance | BindingFlags.Public); + + int id = (int)idprop.GetValue(field); + string name = (string)nameprop.GetValue(field); + string objtype = typeprop.GetValue(field).ToString(); + + if (id < 2000000000) + Fields.Add(new Field(id, name, objtype, null, this)); + } + } + + public void PopulateFields(FieldExtensionListSyntax fieldList) + { + foreach (FieldSyntax field in fieldList.Fields) + { + if(!FieldIsObsolete(field)) + Fields.Add(new Field((int)field.No.Value, field.Name.Identifier.ToString().Replace("\"", ""), field.Type.ToString(), field.GetLocation(), this)); + } + } + + public void PopulateFields(FieldListSyntax fieldList) + { + + foreach (FieldSyntax field in fieldList.Fields) + { + if(!FieldIsObsolete(field)) + Fields.Add(new Field((int)field.No.Value, field.Name.Identifier.ToString().Replace("\"", ""), field.Type.ToString(), field.GetLocation(), this)); + } + } + + private bool FieldIsObsolete(FieldSyntax field) + { + foreach(PropertySyntax property in field.PropertyList.Properties) + { + if (!property.Name.Identifier.ToString().Equals("ObsoleteState")) + continue; + + if (property.Value.GetType() != typeof(EnumPropertyValueSyntax)) + continue; + + EnumPropertyValueSyntax enumPropertyValueSyntax = (EnumPropertyValueSyntax)property.Value; + + if (enumPropertyValueSyntax.Value.Identifier.ToString().Equals("Removed")) + return true; + } + + return false; + } + } + } +} \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index ad028c97..2bce87f9 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -49,5 +49,7 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0041EmptyCaptionLocked = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0041", (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0041EmptyCaptionLockedDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0041"); public static readonly DiagnosticDescriptor Rule0042AutoCalcFieldsOnNormalFields = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0042", (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0042AutoCalcFieldsOnNormalFieldsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0042"); public static readonly DiagnosticDescriptor Rule0043SecretText = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0043", (LocalizableString)new LocalizableResourceString("Rule0043SecretTextTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0043SecretTextFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0043SecretTextDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0043"); + public static readonly DiagnosticDescriptor Rule0044AnalyzeTableExtension = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0044", (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTableExtensionTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTableExtensionFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTableExtensionDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0044"); + public static readonly DiagnosticDescriptor Rule0044AnalyzeTransferFields = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0044", (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTransferFieldsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTransferFieldsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0044AnalyzeTransferFieldsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0044"); } } \ No newline at end of file diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 340034ff..d178e82a 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -492,4 +492,22 @@ Use SecretText type to protect credentials and sensitive textual values from being revealed. + + Conflicting ID, Name or Type with Table '{0}' + + + Conflicting ID, Name or Type with Table '{0}' + + + Conflicting ID, Name or Type with Table '{0}' + + + Records '{0}' and '{1}' have conflicting fields with the same ID + + + Records '{0}' and '{1}' have conflicting fields with the same ID + + + Records '{0}' and '{1}' have conflicting fields with the same ID + \ No newline at end of file From cf81cb52e5761028caf88367f6aba4e12153b7a6 Mon Sep 17 00:00:00 2001 From: Tine Staric Date: Wed, 13 Dec 2023 11:08:38 +0200 Subject: [PATCH 2/2] Add Additional Table Pairs --- Design/Rule0044AnalyzeTransferField.cs | 144 ++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/Design/Rule0044AnalyzeTransferField.cs b/Design/Rule0044AnalyzeTransferField.cs index a9b8c27d..2eb54396 100644 --- a/Design/Rule0044AnalyzeTransferField.cs +++ b/Design/Rule0044AnalyzeTransferField.cs @@ -432,6 +432,8 @@ private void LoadTablePairs() new Tuple("Contact", "Employee"), new Tuple("Contact", "BankAccount"), + new Tuple("Contact Business Relation", "Office Contact Details"), + new Tuple("Deferral Line", "Deferral Line Archive"), new Tuple("Deferral Line", "Posted Deferral Line"), new Tuple("Deferral Header", "Deferral Header Archive"), @@ -441,6 +443,12 @@ private void LoadTablePairs() new Tuple("Gen. Journal Line", "Standard General Journal Line"), new Tuple("Item Journal Line", "Standard Item Journal Line"), + new Tuple("Item Application Entry History", "Item Application Entry"), + + new Tuple("Item Reference", "Item Cross Reference"), + new Tuple("Price List Line", "Price Worksheet Line"), + new Tuple("Copy Item Buffer", "Copy Item Parameters"), + new Tuple("Item Templ.", "Item"), new Tuple("Whse. Item Tracking Line", "Tracking Specification"), new Tuple("Whse. Item Tracking Line", "Reservation Entry"), @@ -449,9 +457,9 @@ private void LoadTablePairs() new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Cust. Ledg. Entry"), new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Vendor Ledg. Entry"), new Tuple("Detailed CV Ledg. Entry Buffer", "Detailed Employee Ledger Entry"), - - new Tuple("CV Ledger Entry Buffer", "Cust. Ledger Entry"), new Tuple("CV Ledger Entry Buffer", "Cust. Ledger Entry"), + new Tuple("Vendor Ledger Entry", "Vendor Ledger Entry Buffer"), + new Tuple("Vendor Payment Buffer", "Payment Buffer"), new Tuple("Tracking Specification", "Reservation Entry"), new Tuple("Assembly Line", "Posted Assembly Line"), @@ -464,7 +472,11 @@ private void LoadTablePairs() new Tuple("Bank Acc. Reconciliation Line", "Bank Account Statement Line"), new Tuple("Posted Payment Recon. Line", "Bank Acc. Reconciliation Line"), - new Tuple("Posted Payment Recon. Hdr", "Posted Payment Recon. Hdr"), + new Tuple("Posted Payment Recon. Hdr", "Bank Acc. Reconciliation"), + new Tuple("Cash Flow Worksheet Line", "Suggest Worksheet Lines"), + + new Tuple("Direct Debit Collection Entry", "Direct Debit Collection Buffer"), + new Tuple("Applied Payment Entry", "Payment Application Proposal"), new Tuple("Prod. Order Rtng Comment Line", "Routing Comment Line"), new Tuple("Prod. Order Routing Personnel", "Routing Personnel"), @@ -500,6 +512,37 @@ private void LoadTablePairs() new Tuple("Purchase Line", "Return Shipment Line"), new Tuple("Purchase Line", "Purchase Line Archive"), + new Tuple("Issued Fin. Charge Memo Header", "Finance Charge Memo Header"), + new Tuple("Issued Fin. Charge Memo Line", "Finance Charge Memo Line"), + new Tuple("Issued Reminder Header", "Reminder Header"), + new Tuple("Issued Reminder Line", "Reminder Line"), + new Tuple("Posted Approval Entry", "Approval Entry"), + new Tuple("Posted Approval Comment Line", "Approval Comment Line"), + + new Tuple("VAT Entry Posting Preview", "VAT Entry"), + new Tuple("VAT Posting Setup", "VAT Setup Posting Groups"), + new Tuple("VAT Business Posting Group", "Tax Area Buffer"), + new Tuple("Tax Area", "Tax Area Buffer"), + new Tuple("VAT Product Posting Group", "Tax Group Buffer"), + new Tuple("Tax Group", "Tax Group Buffer"), + + new Tuple("Phys. Invt. Order Header", "Pstd. Phys. Invt. Order Hdr"), + new Tuple("Pstd. Phys. Invt. Order Line", "Phys. Invt. Comment Line"), + new Tuple("Pstd. Exp. Phys. Invt. Track", "Exp. Phys. Invt. Tracking"), + new Tuple("Phys. Invt. Comment Line", "Phys. Invt. Comment Line"), + new Tuple("Pstd. Phys. Invt. Record Hdr", "Phys. Invt. Record Header"), + new Tuple("Pstd. Phys. Invt. Record Line", "Phys. Invt. Record Line"), + new Tuple("Invt. Document Header", "Invt. Receipt Header"), + new Tuple("Invt. Document Header", "Invt. Shipment Header"), + new Tuple("Invt. Document Line", "Invt. Receipt Line"), + new Tuple("Invt. Document Line", "Invt. Shipment Line"), + + new Tuple("Prod. Order Rtng Comment Line", "Routing Comment Line"), + new Tuple("Prod. Order Routing Personnel", "Routing Personnel"), + new Tuple("Prod. Order Rtng Qlty Meas.", "Routing Quality Measure"), + new Tuple("Prod. Order Routing Tool", "Routing Tool"), + new Tuple("Prod. Order Comp. Cmt Line", "Production BOM Comment Line"), + new Tuple("Warehouse Activity Header", "Registered Whse. Activity Hdr."), new Tuple("Warehouse Activity Header", "Registered Invt. Movement Hdr."), new Tuple("Warehouse Activity Header", "Posted Invt. Pick Header"), @@ -510,10 +553,103 @@ private void LoadTablePairs() new Tuple("Warehouse Activity Line", "Posted Invt. Pick Line"), new Tuple("Warehouse Activity Line", "Posted Invt. Put-away Line"), + new Tuple("Posted Whse. Receipt Header", "Warehouse Receipt Header"), + new Tuple("Posted Whse. Shipment Line", "Warehouse Shipment Line"), + new Tuple("Whse. Item Entry Relation", "Item Entry Relation"), + new Tuple("Time Sheet Header", "Time Sheet Header Archive"), new Tuple("Time Sheet Line", "Time Sheet Line Archive"), new Tuple("Time Sheet Detail Archive", "Time Sheet Detail"), - new Tuple("Time Sheet Comment Line", "Time Sheet Cmt. Line Archive") + new Tuple("Time Sheet Comment Line", "Time Sheet Cmt. Line Archive"), + new Tuple("Employee Time Reg Buffer", "Time Sheet Detail"), + + new Tuple("Service Shipment Header", "Service Header"), + new Tuple("Service Shipment Item Line", "Service Item Line"), + new Tuple("Service Shipment Line", "Service Line"), + new Tuple("Service Invoice Header", "Service Header"), + new Tuple("Service Invoice Line", "Service Line"), + new Tuple("Service Cr.Memo Header", "Service Header"), + new Tuple("Service Cr.Memo Line", "Service Line"), + new Tuple("Filed Service Contract Header", "Service Contract Header"), + new Tuple("Filed Contract Line", "Service Contract Line"), + + new Tuple("Workflow Step Instance Archive", "Workflow Step Instance"), + new Tuple("Workflow Record Change Archive", "Workflow - Record Change"), + new Tuple("Workflow Step Argument Archive", "Workflow Step Argument"), + + new Tuple("Purchase Header", "Purch. Inv. Entity Aggregate"), + new Tuple("Purch. Inv. Header", "Purch. Inv. Entity Aggregate"), + new Tuple("Purch. Inv. Line Aggregate", "Purch. Inv. Line"), + new Tuple("Purch. Inv. Line Aggregate", "Purchase Line"), + new Tuple("Purch. Inv. Line Aggregate", "Purch. Cr. Memo Line"), + new Tuple("Sales Header", "Sales Invoice Entity Aggregate"), + new Tuple("Sales Invoice Header", "Sales Invoice Entity Aggregate"), + new Tuple("Sales Invoice Line Aggregate", "Sales Invoice Line"), + new Tuple("Sales Invoice Line Aggregate", "Sales Line"), + new Tuple("Sales Invoice Line Aggregate", "Sales Cr.Memo Line"), + new Tuple("Unlinked Attachment", "Attachment Entity Buffer"), + new Tuple("Attachment Entity Buffer", "Incoming Document Attachment"), + new Tuple("Purchase Header", "Purch. Cr. Memo Entity Buffer"), + new Tuple("Purch. Cr. Memo Hdr.", "Purch. Cr. Memo Entity Buffer"), + new Tuple("Purchase Order Entity Buffer", "Purchase Header"), + new Tuple("Sales Cr. Memo Entity Buffer", "Sales Header"), + new Tuple("Sales Cr. Memo Entity Buffer", "Sales Cr.Memo Header"), + new Tuple("Sales Order Entity Buffer", "Sales Header"), + new Tuple("Sales Quote Entity Buffer", "Sales Header"), + + new Tuple("IC Outbox Sales Header", "Sales Header"), + new Tuple("IC Outbox Sales Line", "Sales Line"), + new Tuple("IC Outbox Sales Header", "Sales Invoice Header"), + new Tuple("IC Outbox Sales Line", "Sales Invoice Line"), + new Tuple("IC Outbox Sales Header", "Sales Cr.Memo Header"), + new Tuple("IC Outbox Sales Line", "Sales Cr.Memo Line"), + new Tuple("IC Outbox Purchase Header", "Purchase Header"), + new Tuple("IC Outbox Purchase Line", "Purchase Line"), + new Tuple("Handled IC Inbox Jnl. Line", "IC Inbox Jnl. Line"), + new Tuple("Handled IC Inbox Sales Header", "IC Inbox Sales Header"), + new Tuple("Handled IC Inbox Sales Line", "IC Inbox Sales Line"), + new Tuple("IC Inbox Sales Line", "Sales Line"), + new Tuple("Handled IC Inbox Purch. Header", "IC Inbox Purchase Header"), + new Tuple("Handled IC Inbox Purch. Line", "IC Inbox Purchase Line"), + new Tuple("IC Inbox Purchase Line", "Purchase Line"), + new Tuple("Handled IC Inbox Jnl. Line", "IC Inbox Jnl. Line"), + new Tuple("IC Outbox Jnl. Line", "Handled IC Outbox Jnl. Line"), + new Tuple("IC Outbox Sales Header", "Handled IC Outbox Sales Header"), + new Tuple("IC Outbox Sales Line", "Handled IC Outbox Sales Line"), + new Tuple("IC Outbox Purchase Header", "Handled IC Outbox Purch. Hdr"), + new Tuple("IC Outbox Purchase Line", "Handled IC Outbox Purch. Line"), + new Tuple("IC Outbox Jnl. Line", "IC Inbox Jnl. Line"), + new Tuple("IC Outbox Sales Header", "IC Inbox Sales Header"), + new Tuple("IC Outbox Sales Line", "IC Inbox Sales Line"), + new Tuple("IC Outbox Purchase Header", "IC Inbox Purchase Header"), + new Tuple("IC Outbox Purchase Line", "IC Inbox Purchase Line"), + new Tuple("Handled IC Outbox Trans.", "IC Outbox Transaction"), + new Tuple("IC Inbox Transaction", "Buffer IC Inbox Transaction"), + new Tuple("IC Inbox Jnl. Line", "Buffer IC Inbox Jnl. Line"), + new Tuple("IC Inbox Purchase Header", "Buffer IC Inbox Purch Header"), + new Tuple("IC Inbox Purchase Line", "Buffer IC Inbox Purchase Line"), + new Tuple("IC Inbox Sales Header", "Buffer IC Inbox Purchase Line"), // Not a mistake, see: ICDataExchangeAPI - PostICSalesHeaderToICPartnerInbox + new Tuple("IC Inbox Sales Line", "Buffer IC Inbox Sales Line"), + new Tuple("IC Inbox/Outbox Jnl. Line Dim.", "Buffer IC InOut Jnl. Line Dim."), + new Tuple("IC Document Dimension", "Buffer IC Document Dimension"), + new Tuple("IC Comment Line", "Buffer IC Comment Line"), + + new Tuple("Config. Setup", "Company Information"), + new Tuple("Object Options", "Report Settings"), + + + new Tuple("Analysis by Dim. Parameters", "Analysis by Dim. User Param."), + new Tuple("G/L Account (Analysis View)", "G/L Account"), + new Tuple("Acc. Schedule Line", "Acc. Sched. KPI Buffer"), + new Tuple("G/L Entry Posting Preview", "G/L Entry"), + + new Tuple("Sales Invoice Header", "O365 Sales Document"), + new Tuple("Sales Header", "O365 Sales Document"), + + new Tuple("Incoming Document Attachment", "Inc. Doc. Attachment Overview"), + new Tuple("Config. Field Mapping", "Config. Field Map"), + + new Tuple("Onboarding Signal", "Onboarding Signal Buffer") }; }