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

Resolve casing for Xmlport identifier #781

Merged
merged 1 commit into from
Oct 6, 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
2 changes: 2 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0005.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public void Setup()
[Test]
[TestCase("1")]
[TestCase("2")]
[TestCase("OptionAccessExpression")]
public async Task HasDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
Expand All @@ -25,6 +26,7 @@ public async Task HasDiagnostic(string testCase)

[Test]
[TestCase("1")]
[TestCase("OptionAccessExpression")]
public async Task NoDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
codeunit 50100 OptionAccessExpression
{
procedure MyProcedure()
var
i: Integer;
begin
i := [|CODEUNIT|]::MyCodeunit;
i := [|codeunit|]::MyCodeunit;

i := [|DATABASE|]::MyTable;
i := [|Database|]::MyTable;

i := [|ENUM|]::MyEnum::MyValue;
i := [|enum|]::MyEnum::MyValue;

i := [|PAGE|]::MyPage;
i := [|page|]::MyPage;

i := [|QUERY|]::MyQuery;
i := [|query|]::MyQuery;

i := [|REPORT|]::MyReport;
i := [|report|]::MyReport;

i := [|XMLPORT|]::MyXmlport;
i := [|XmlPort|]::MyXmlport;
end;
}

codeunit 50101 MyCodeunit { }
enum 50100 MyEnum { value(0; MyValue) { } }
page 50100 MyPage { }
query 50100 MyQuery { elements { dataitem(MyTable; MyTable) { column(Myfield; Myfield) { } } } }
report 50100 MyReport { }
table 50100 MyTable { fields { field(1; Myfield; Code[10]) { } } }
xmlport 50100 MyXmlport { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
codeunit 50100 OptionAccessExpression
{
procedure MyProcedure()
var
i: Integer;
begin
i := [|Codeunit|]::MyCodeunit;
i := [|Database|]::MyTable;
i := [|Enum|]::MyEnum::MyValue;
i := [|Page|]::MyPage;
i := [|Query|]::MyQuery;
i := [|Report|]::MyReport;
i := [|Xmlport|]::MyXmlport;
end;
}

codeunit 50101 MyCodeunit { }
enum 50100 MyEnum { value(0; MyValue) { } }
page 50100 MyPage { }
query 50100 MyQuery { elements { dataitem(MyTable; MyTable) { column(Myfield; Myfield) { } } } }
report 50100 MyReport { }
table 50100 MyTable { fields { field(1; Myfield; Code[10]) { } } }
xmlport 50100 MyXmlport { }
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#nullable disable // TODO: Enable nullable and review rule
using BusinessCentral.LinterCop.AnalysisContextExtension;
using BusinessCentral.LinterCop.AnalysisContextExtension;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
Expand All @@ -21,7 +20,7 @@ public class Rule0005VariableCasingShouldNotDifferFromDeclaration : DiagnosticAn
private static readonly string[] _labelPropertyString = LabelPropertyHelper.GetAllLabelProperties();
private static readonly string[] _navTypeKindStrings = GenerateNavTypeKindArray();
private static readonly string[] _propertyKindStrings = Enum.GetValues(typeof(PropertyKind)).Cast<PropertyKind>().Select(x => x.ToString()).ToArray();
private static readonly string[] _symbolKinds = Enum.GetValues(typeof(SymbolKind)).Cast<SymbolKind>().Select(x => x.ToString()).ToArray();
private static readonly string[] _symbolKinds = GenerateSymbolKindArray();
private static readonly Dictionary<TriggerTypeKind, string> _triggerTypeKinds = GenerateNTriggerTypeKindMappings();


Expand All @@ -37,6 +36,7 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(new Action<SyntaxNodeAnalysisContext>(this.AnalyzeQualifiedName), SyntaxKind.QualifiedName);
context.RegisterSyntaxNodeAction(new Action<SyntaxNodeAnalysisContext>(this.AnalyzeQualifiedNameWithoutNamespace), SyntaxKind.QualifiedName);
context.RegisterSyntaxNodeAction(new Action<SyntaxNodeAnalysisContext>(this.AnalyzeLengthDataType), SyntaxKind.LengthDataType);
context.RegisterSyntaxNodeAction(new Action<SyntaxNodeAnalysisContext>(this.AnalyzeOptionAccessExpression), SyntaxKind.OptionAccessExpression);

context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.CheckForBuiltInMethodsWithCasingMismatch), new OperationKind[] {
OperationKind.InvocationExpression,
Expand Down Expand Up @@ -73,11 +73,33 @@ public override void Initialize(AnalysisContext context)

private static string[] GenerateNavTypeKindArray()
{
var navTypeKinds = Enum.GetValues(typeof(NavTypeKind)).Cast<NavTypeKind>().Select(s => s.ToString()).ToList();
var navTypeKinds = Enum.GetValues(typeof(NavTypeKind))
.Cast<NavTypeKind>()
.Select(s => s.ToString())
.ToList();

navTypeKinds.Add("Database"); // for Database::"G/L Entry" (there is no NavTypeKind for this)
return navTypeKinds.ToArray();
}

private static string[] GenerateSymbolKindArray()
{
var symbolKinds = Enum.GetValues(typeof(SymbolKind))
.Cast<SymbolKind>()
.Select(x => x.ToString())
.ToList();

// Find the index of "XmlPort" and update it to "Xmlport"
int index = symbolKinds.FindIndex(s => s == "XmlPort");
if (index != -1)
{
symbolKinds[index] = "Xmlport";
}

symbolKinds.Add("Database"); // for Database::"G/L Entry" (there is no SymbolKind for this)
return symbolKinds.ToArray();
}

private static Dictionary<TriggerTypeKind, string> GenerateNTriggerTypeKindMappings()
{
var mappings = new Dictionary<TriggerTypeKind, string>();
Expand Down Expand Up @@ -177,14 +199,13 @@ private void AnalyzeActionAreaSectionName(SyntaxNodeAnalysisContext ctx)

private void AnalyzeTriggerDeclaration(SyntaxNodeAnalysisContext ctx)
{
TriggerDeclarationSyntax syntax = ctx.Node as TriggerDeclarationSyntax;
if (syntax == null)
if (ctx.Node is not TriggerDeclarationSyntax syntax)
return;

ISymbolWithTriggers symbolWithTriggers = ctx.ContainingSymbol.ContainingSymbol as ISymbolWithTriggers;
if (ctx.ContainingSymbol.ContainingSymbol is not ISymbolWithTriggers symbolWithTriggers)
return;

TriggerTypeInfo triggerTypeInfo = symbolWithTriggers.GetTriggerTypeInfo(syntax.Name.Identifier.ValueText);
if (triggerTypeInfo == null)
if (symbolWithTriggers.GetTriggerTypeInfo(syntax.Name.Identifier.ValueText) is not TriggerTypeInfo triggerTypeInfo)
return;

if (!_triggerTypeKinds.TryGetValue(triggerTypeInfo.Kind, out string targetName))
Expand All @@ -202,8 +223,7 @@ private void AnalyzeIdentifierName(SyntaxNodeAnalysisContext ctx)
if (node.Parent.Kind == SyntaxKind.PragmaWarningDirectiveTrivia)
return;

ISymbol fieldSymbol = ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol;
if (fieldSymbol == null)
if (ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol is not ISymbol fieldSymbol)
return;

// TODO: Support more SymbolKinds
Expand All @@ -221,8 +241,7 @@ private void AnalyzeQualifiedName(SyntaxNodeAnalysisContext ctx)
if (ctx.Node is not QualifiedNameSyntax node)
return;

ISymbol fieldSymbol = ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol;
if (fieldSymbol == null)
if (ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol is not ISymbol fieldSymbol)
return;

string identifierName = StringExtensions.UnquoteIdentifier(node.Right.Identifier.ValueText);
Expand All @@ -239,8 +258,7 @@ private void AnalyzeQualifiedNameWithoutNamespace(SyntaxNodeAnalysisContext ctx)
if (node.Left.Kind != SyntaxKind.IdentifierName)
return;

ISymbol fieldSymbol = ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol;
if (fieldSymbol == null)
if (ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol is not ISymbol fieldSymbol)
return;

if (fieldSymbol.ContainingSymbol is not IObjectTypeSymbol objectTypeSymbol)
Expand All @@ -249,7 +267,11 @@ private void AnalyzeQualifiedNameWithoutNamespace(SyntaxNodeAnalysisContext ctx)
if (fieldSymbol.ContainingSymbol.Kind == SymbolKind.TableExtension)
{
ITableExtensionTypeSymbol tableExtension = (ITableExtensionTypeSymbol)fieldSymbol.ContainingSymbol;
objectTypeSymbol = tableExtension.Target as IObjectTypeSymbol;
if (tableExtension.Target is not IObjectTypeSymbol tableExtensionTypeSymbol)
{
return;
}
objectTypeSymbol = tableExtensionTypeSymbol;
}

IdentifierNameSyntax identifierNameSyntax = (IdentifierNameSyntax)node.Left;
Expand All @@ -275,6 +297,25 @@ private void AnalyzeLengthDataType(SyntaxNodeAnalysisContext ctx)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, identifierToken.GetLocation(), new object[] { targetName, "" }));
}

private void AnalyzeOptionAccessExpression(SyntaxNodeAnalysisContext ctx)
{
if (ctx.Node is not OptionAccessExpressionSyntax node)
return;

switch (node.Expression)
{
case IdentifierNameSyntax identifierNameSyntax:
int result = Array.FindIndex(_symbolKinds, t => t.Equals(identifierNameSyntax.Identifier.ValueText, StringComparison.OrdinalIgnoreCase));
if (result == -1)
return;

if (!identifierNameSyntax.Identifier.ValueText.AsSpan().Equals(_symbolKinds[result].ToString().AsSpan(), StringComparison.Ordinal))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, identifierNameSyntax.Identifier.GetLocation(), new object[] { _symbolKinds[result].ToString(), "" }));

break;
}
}

private void CheckForBuiltInTypeCasingMismatch(SymbolAnalysisContext ctx)
{
AnalyseTokens(ctx);
Expand All @@ -283,12 +324,12 @@ private void CheckForBuiltInTypeCasingMismatch(SymbolAnalysisContext ctx)

private void AnalyseTokens(SymbolAnalysisContext ctx)
{
IEnumerable<SyntaxToken> descendantTokens = ctx.Symbol.DeclaringSyntaxReference.GetSyntax().DescendantTokens()
IEnumerable<SyntaxToken>? descendantTokens = ctx.Symbol.DeclaringSyntaxReference?.GetSyntax().DescendantTokens()
.Where(t => t.Kind.IsKeyword())
.Where(t => !_dataTypeSyntaxKinds.Contains(t.Parent.Kind))
.Where(t => !string.IsNullOrEmpty(t.ToString()));

foreach (SyntaxToken descendantToken in descendantTokens)
foreach (SyntaxToken descendantToken in descendantTokens ?? Enumerable.Empty<SyntaxToken>())
{
ctx.CancellationToken.ThrowIfCancellationRequested();

Expand All @@ -303,11 +344,11 @@ private void AnalyseTokens(SymbolAnalysisContext ctx)

private void AnalyseNodes(SymbolAnalysisContext ctx)
{
IEnumerable<SyntaxNode> descendantNodes = ctx.Symbol.DeclaringSyntaxReference.GetSyntax().DescendantNodes()
IEnumerable<SyntaxNode>? descendantNodes = ctx.Symbol.DeclaringSyntaxReference?.GetSyntax().DescendantNodes()
.Where(t => t.Kind != SyntaxKind.LengthDataType) // handeld on AnalyzeLengthDataType method
.Where(n => !n.ToString().AsSpan().StartsWith("array"));

foreach (SyntaxNode descendantNode in descendantNodes)
foreach (SyntaxNode descendantNode in descendantNodes ?? Enumerable.Empty<SyntaxNode>())
{
ctx.CancellationToken.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -370,7 +411,6 @@ private static bool IsValidKind(SyntaxKind kind)
{
case SyntaxKind.SubtypedDataType:
case SyntaxKind.GenericDataType:
case SyntaxKind.OptionAccessExpression:
case SyntaxKind.SimpleTypeReference:
return true;
}
Expand Down