Skip to content
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

Add Implicit Primary Key field to Rule0076 #845

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BusinessCentral.LinterCop.Test/Rule0076.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public void Setup()

[Test]
[TestCase("TableRelationLonger")]
[TestCase("TableRelationImplicitFieldPrimaryKey")]
#if !LessThenSpring2024
[TestCase("TableExtRelationLonger")]
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
table 50108 MyTable
{
fields
{
field(1; MyField; Code[20]) { }

field(3; [|MySecondField|]; Code[10])
{
TableRelation = MyTable;
}
}

keys
{
key(PK; MyField) { }
}
}
97 changes: 62 additions & 35 deletions BusinessCentral.LinterCop/Design/Rule0076TableRelationTooLong.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,115 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using Microsoft.Dynamics.Nav.CodeAnalysis.Utilities;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design;
[DiagnosticAnalyzer]
public class Rule0076TableRelationTooLong : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0076TableRelationTooLong);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(DiagnosticDescriptors.Rule0076TableRelationTooLong);

public override void Initialize(AnalysisContext context) =>
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field);

private void AnalyzeSymbol(SymbolAnalysisContext context)
private void AnalyzeSymbol(SymbolAnalysisContext ctx)
{
if (context.IsObsoletePendingOrRemoved())
if (ctx.IsObsoletePendingOrRemoved() || ctx.Symbol is not IFieldSymbol field)
return;

if (context.Symbol is not IFieldSymbol currentField)
if (!field.HasLength)
return;

var tableRelation = currentField
var tableRelation = field
.GetProperty(PropertyKind.TableRelation)
?.GetPropertyValueSyntax<TableRelationPropertyValueSyntax>();

if (tableRelation is null)
return;

AnalyzeTableRelations(context, currentField, tableRelation);
AnalyzeTableRelations(ctx, field, tableRelation);
}

private void AnalyzeTableRelations(SymbolAnalysisContext context, IFieldSymbol currentField, TableRelationPropertyValueSyntax? tableRelation)
private void AnalyzeTableRelations(SymbolAnalysisContext ctx, IFieldSymbol field, TableRelationPropertyValueSyntax? tableRelation)
{
while (tableRelation is not null)
{
if (tableRelation.RelatedTableField is QualifiedNameSyntax relatedField)
var relatedFieldSymbol = ResolveRelatedField(ctx, tableRelation);

if (relatedFieldSymbol is not null && ShouldReportDiagnostic(field, relatedFieldSymbol))
{
var relatedFieldSymbol = GetRelatedFieldSymbol(
relatedField.Left as IdentifierNameSyntax,
relatedField.Right as IdentifierNameSyntax,
context.Compilation);

if (relatedFieldSymbol is not null && ShouldReportDiagnostic(currentField, relatedFieldSymbol))
{
ReportLengthMismatch(context, currentField, relatedFieldSymbol, relatedField);
}
ReportLengthMismatch(ctx, field, relatedFieldSymbol);
}

tableRelation = tableRelation.ElseExpression?.ElseTableRelationCondition;
}
}

private static bool ShouldReportDiagnostic(IFieldSymbol currentField, IFieldSymbol? relatedField) =>
relatedField?.HasLength == true &&
currentField.HasLength &&
currentField.Length < relatedField.Length;
private static bool ShouldReportDiagnostic(IFieldSymbol currentField, IFieldSymbol relatedField) =>
relatedField.HasLength && currentField.Length < relatedField.Length;

private static void ReportLengthMismatch(SymbolAnalysisContext context, IFieldSymbol currentField,
IFieldSymbol relatedField, QualifiedNameSyntax relatedFieldSyntax)
private static void ReportLengthMismatch(SymbolAnalysisContext ctx, IFieldSymbol currentField, IFieldSymbol relatedField)
{
context.ReportDiagnostic(Diagnostic.Create(
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0076TableRelationTooLong,
currentField.GetLocation(),
relatedField.Length,
relatedFieldSyntax.ToString(),
relatedField.ToDisplayString().QuoteIdentifierIfNeeded(),
currentField.Length,
currentField.Name));
currentField.ToDisplayString().QuoteIdentifierIfNeeded()));
}

private IFieldSymbol? ResolveRelatedField(SymbolAnalysisContext ctx, TableRelationPropertyValueSyntax tableRelation)
{
return tableRelation.RelatedTableField switch
{
QualifiedNameSyntax qualifiedName =>
ResolveQualifiedField(qualifiedName, ctx.Compilation),

IdentifierNameSyntax identifierName =>
ResolvePrimaryKeyField(identifierName.Identifier.ValueText, ctx.Compilation),

_ => null
};
}

private IFieldSymbol? ResolveQualifiedField(QualifiedNameSyntax qualifiedName, Compilation compilation)
{
if (qualifiedName.Left is IdentifierNameSyntax tableNameSyntax &&
qualifiedName.Right is IdentifierNameSyntax fieldNameSyntax)
{
var tableName = tableNameSyntax.GetIdentifierOrLiteralValue();
var fieldName = fieldNameSyntax.GetIdentifierOrLiteralValue();

if (tableName != null && fieldName != null)
{
return GetFieldFromTable(tableName, fieldName, compilation)
?? GetFieldFromTableExtension(tableName, fieldName, compilation);
}
}

return null;
}

private IFieldSymbol? GetRelatedFieldSymbol(IdentifierNameSyntax? table, IdentifierNameSyntax? field, Compilation compilation)
private static IFieldSymbol? ResolvePrimaryKeyField(string? tableName, Compilation compilation)
{
if (table?.GetIdentifierOrLiteralValue() is not string tableName ||
field?.GetIdentifierOrLiteralValue() is not string fieldName)
if (string.IsNullOrEmpty(tableName))
return null;

return GetFieldFromTable(tableName, fieldName, compilation) ??
GetFieldFromTableExtension(tableName, fieldName, compilation);
var tableSymbols = compilation.GetApplicationObjectTypeSymbolsByNameAcrossModules(SymbolKind.Table, tableName);

return tableSymbols.FirstOrDefault() is ITableTypeSymbol table && table.PrimaryKey.Fields.Length == 1
? table.PrimaryKey.Fields[0]
: null;
}

private static IFieldSymbol? GetFieldFromTable(string tableName, string fieldName, Compilation compilation)
{
var tables = compilation.GetApplicationObjectTypeSymbolsByNameAcrossModules(SymbolKind.Table, tableName);
return tables.FirstOrDefault() is ITableTypeSymbol tableSymbol
? tableSymbol.Fields.FirstOrDefault(x => x.Name == fieldName)
var tableSymbols = compilation.GetApplicationObjectTypeSymbolsByNameAcrossModules(SymbolKind.Table, tableName);

return tableSymbols.FirstOrDefault() is ITableTypeSymbol table
? table.Fields.FirstOrDefault(f => f.Name == fieldName)
: null;
}

Expand Down
Loading