Skip to content

Commit

Permalink
Merge pull request #444 from dannoe/development
Browse files Browse the repository at this point in the history
Optimized LC0005
  • Loading branch information
Arthurvdv authored Dec 18, 2023
2 parents 2152f4b + 1b222e1 commit c934bca
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 85 deletions.
2 changes: 1 addition & 1 deletion BusinessCentral.LinterCop.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>Latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
Expand Down
248 changes: 164 additions & 84 deletions Design/Rule0005VariableCasingShouldNotDifferFromDeclaration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ namespace BusinessCentral.LinterCop.Design
[DiagnosticAnalyzer]
public class Rule0005VariableCasingShouldNotDifferFromDeclaration : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration);

private static readonly HashSet<SyntaxKind> _validTokens = new();
private static string[] _navTypeKindStrings;

public override void Initialize(AnalysisContext context)
{
GenerateNavTypeKindArray();
GenerateValidTokenArray();

context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.CheckForBuiltInMethodsWithCasingMismatch), new OperationKind[] {
OperationKind.InvocationExpression,
OperationKind.FieldAccess,
Expand Down Expand Up @@ -43,124 +50,197 @@ public override void Initialize(AnalysisContext context)
});
}

private void CheckForBuiltInTypeCasingMismatch(SymbolAnalysisContext ctx)
private static void GenerateNavTypeKindArray()
{
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)
_navTypeKindStrings = navTypeKinds.ToArray();
}

private static void GenerateValidTokenArray()
{
foreach (var node in ctx.Symbol.DeclaringSyntaxReference.GetSyntax().DescendantNodesAndTokens().Where(n => IsValidToken(n)))
_validTokens.Clear();
var allKinds = Enum.GetValues(typeof(SyntaxKind)).Cast<SyntaxKind>();
foreach (var kind in allKinds)
{
if (node.Kind.ToString().StartsWith("DotNet"))
var kindSpan = kind.ToString().AsSpan();

if ((kindSpan.Contains("Keyword", StringComparison.Ordinal) &&
!kindSpan.StartsWith("Codeunit") &&
!kindSpan.StartsWith("Enum") &&
!kindSpan.StartsWith("Label") &&
!kindSpan.StartsWith("Action") &&
!kindSpan.StartsWith("Page") &&
!kindSpan.StartsWith("Interface") &&
!kindSpan.StartsWith("Report") &&
!kindSpan.StartsWith("Query") &&
!kindSpan.StartsWith("XmlPort") &&
!kindSpan.StartsWith("DotNet")) ||
kindSpan.Contains("DataType", StringComparison.Ordinal)
)
{
_validTokens.Add(kind);
continue;
}

switch (kind)
{
case SyntaxKind.SimpleTypeReference:
case SyntaxKind.OptionAccessExpression:
_validTokens.Add(kind);
continue;
}

}
}

private void CheckForBuiltInTypeCasingMismatch(SymbolAnalysisContext ctx)
{
foreach (var node in ctx.Symbol.DeclaringSyntaxReference.GetSyntax().DescendantNodesAndTokens().Where(n => _validTokens.Contains(n.Kind)))
{
ctx.CancellationToken.ThrowIfCancellationRequested();

var syntaxNodeKindSpan = node.Kind.ToString().AsSpan();

if (node.IsToken)
if (SyntaxFactory.Token(node.Kind).ToString() != node.ToString())
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { SyntaxFactory.Token(node.Kind), "" }));
if (node.IsNode && !node.AsNode().ToString().StartsWith("array"))
{
if ((node.AsNode().IsKind(SyntaxKind.SimpleTypeReference) || node.Kind.ToString().Contains("DataType")) && !node.Kind.ToString().StartsWith("Codeunit") && !node.Kind.ToString().StartsWith("Enum") && !node.Kind.ToString().StartsWith("Label"))
var syntaxToken = SyntaxFactory.Token(node.Kind);
if (!syntaxToken.ToString().AsSpan().Equals(node.ToString().AsSpan(), StringComparison.Ordinal))
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { syntaxToken, "" }));
continue;
}
}

var syntaxNode = node.AsNode();
if (syntaxNode == null)
continue;

if (!node.IsNode)
continue;

var syntaxNodeAsString = syntaxNode.ToString();
if (!syntaxNodeAsString.StartsWith("array"))
{
if ((syntaxNode.IsKind(SyntaxKind.SimpleTypeReference) ||
syntaxNodeKindSpan.Contains("DataType", StringComparison.Ordinal)) &&
!syntaxNodeKindSpan.StartsWith("Codeunit") &&
!syntaxNodeKindSpan.StartsWith("Enum") &&
!syntaxNodeKindSpan.StartsWith("Label"))
{
var targetName = Enum.GetValues(typeof(NavTypeKind)).Cast<NavTypeKind>().FirstOrDefault(Kind => Kind.ToString().ToUpper() == node.AsNode().ToString().ToUpper() && Kind.ToString() != node.AsNode().ToString());
if (targetName != NavTypeKind.None)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { targetName, "" }));
var targetName = _navTypeKindStrings.FirstOrDefault(Kind =>
{
var kindSpan = Kind.AsSpan();
return kindSpan.Equals(syntaxNodeAsString.AsSpan(), StringComparison.OrdinalIgnoreCase) &&
!kindSpan.Equals(syntaxNodeAsString.AsSpan(), StringComparison.Ordinal);
});

if (targetName != null)
{
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration,
node.GetLocation(), new object[] { targetName, "" }));
continue;
}
}
if (node.AsNode().IsKind(SyntaxKind.SubtypedDataType) || node.AsNode().IsKind(SyntaxKind.GenericDataType) || node.AsNode().IsKind(SyntaxKind.OptionAccessExpression) ||
(node.AsNode().IsKind(SyntaxKind.SimpleTypeReference) && (node.Kind.ToString().StartsWith("Codeunit") || !node.Kind.ToString().StartsWith("Enum") || !node.Kind.ToString().StartsWith("Label"))))

if (IsValidKind(syntaxNode.Kind))
{
var targetName = Enum.GetValues(typeof(NavTypeKind)).Cast<NavTypeKind>().FirstOrDefault(Kind => node.AsNode().ToString().ToUpper().StartsWith(Kind.ToString().ToUpper()) && !node.AsNode().ToString().StartsWith(Kind.ToString()));
if (targetName != NavTypeKind.None)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { targetName, "" }));
if (syntaxNodeKindSpan.StartsWith("Codeunit") ||
!syntaxNodeKindSpan.StartsWith("Enum") ||
!syntaxNodeKindSpan.StartsWith("Label"))
{
var targetName = _navTypeKindStrings.FirstOrDefault(Kind =>
{
var kindSpan = Kind.AsSpan();
var readOnlySpan = syntaxNodeAsString.AsSpan();
return readOnlySpan.StartsWith(kindSpan, StringComparison.OrdinalIgnoreCase) &&
!readOnlySpan.StartsWith(kindSpan, StringComparison.Ordinal);
});
if (targetName != null)
{
var firstToken = syntaxNode.GetFirstToken();
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration,
firstToken.GetLocation(), new object[] { targetName, "" }));

}
}
}
}
}
}

private static bool IsValidToken(Microsoft.Dynamics.Nav.CodeAnalysis.Syntax.SyntaxNodeOrToken n)
private static bool IsValidKind(SyntaxKind kind)
{
if (n.Kind.ToString().Contains("Keyword") &&
!n.Kind.ToString().StartsWith("Codeunit") &&
!n.Kind.ToString().StartsWith("Enum") &&
!n.Kind.ToString().StartsWith("Label") &&
!n.Kind.ToString().StartsWith("Action") &&
!n.Kind.ToString().StartsWith("Page") &&
!n.Kind.ToString().StartsWith("Interface") &&
!n.Kind.ToString().StartsWith("Report") &&
!n.Kind.ToString().StartsWith("Query") &&
!n.Kind.ToString().StartsWith("XmlPort") &&
!n.Kind.ToString().StartsWith("DotNet")
)
return true;
if (n.Kind.ToString().Contains("DataType"))
return true;
if (n.Kind == SyntaxKind.SimpleTypeReference)
return true;
if (n.Kind == SyntaxKind.SubtypedDataType)
return true;
if (n.Kind == SyntaxKind.GenericDataType)
return true;
if (n.Kind == SyntaxKind.OptionAccessExpression)
return true;
switch (kind)
{
case SyntaxKind.SubtypedDataType:
case SyntaxKind.GenericDataType:
case SyntaxKind.OptionAccessExpression:
case SyntaxKind.SimpleTypeReference:
return true;
}

return false;
}

private void CheckForBuiltInMethodsWithCasingMismatch(OperationAnalysisContext ctx)
{
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending ||
ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved)
return;

if (ctx.ContainingSymbol.IsObsoletePending ||
ctx.ContainingSymbol.IsObsoleteRemoved)
return;

var targetName = "";
if (ctx.Operation.Kind == OperationKind.InvocationExpression)

switch (ctx.Operation.Kind)
{
IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
targetName = operation.TargetMethod.Name;
}
if (ctx.Operation.Kind == OperationKind.FieldAccess)
{
try
{
IFieldAccess operation = (IFieldAccess)ctx.Operation;
targetName = operation.FieldSymbol.Name;
}
catch (System.InvalidCastException)
{
}
}
if (new object[] {
OperationKind.GlobalReferenceExpression,
OperationKind.LocalReferenceExpression,
OperationKind.ParameterReferenceExpression,
OperationKind.ReturnValueReferenceExpression }.Contains(ctx.Operation.Kind))
{
switch (ctx.Operation.Kind)
{
case OperationKind.GlobalReferenceExpression:
targetName = ((IGlobalReferenceExpression)ctx.Operation).GlobalVariable.Name;
break;
case OperationKind.LocalReferenceExpression:
targetName = ((ILocalReferenceExpression)ctx.Operation).LocalVariable.Name;
break;
case OperationKind.ParameterReferenceExpression:
targetName = ((IParameterReferenceExpression)ctx.Operation).Parameter.Name;
break;
case OperationKind.ReturnValueReferenceExpression:
targetName = ((IReturnValueReferenceExpression)ctx.Operation).ReturnValue.Name;
break;
}
case OperationKind.InvocationExpression:
if (ctx.Operation is IInvocationExpression invocationExpression)
targetName = invocationExpression.TargetMethod.Name;
break;
case OperationKind.FieldAccess:
if (ctx.Operation is IFieldAccess fieldAccess)
targetName = fieldAccess.FieldSymbol.Name;
break;
case OperationKind.GlobalReferenceExpression:
targetName = ((IGlobalReferenceExpression)ctx.Operation).GlobalVariable.Name;
break;
case OperationKind.LocalReferenceExpression:
targetName = ((ILocalReferenceExpression)ctx.Operation).LocalVariable.Name;
break;
case OperationKind.ParameterReferenceExpression:
targetName = ((IParameterReferenceExpression)ctx.Operation).Parameter.Name;
break;
case OperationKind.ReturnValueReferenceExpression:
targetName = ((IReturnValueReferenceExpression)ctx.Operation).ReturnValue.Name;
break;
default:
return;
}


if (OnlyDiffersInCasing(ctx.Operation.Syntax.ToString(), targetName))
if (OnlyDiffersInCasing(ctx.Operation.Syntax.ToString().AsSpan(), targetName.AsSpan()))
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, ctx.Operation.Syntax.GetLocation(), new object[] { targetName, "" }));
return;
}

var nodes = Array.Find(ctx.Operation.Syntax.DescendantNodes((SyntaxNode e) => true).ToArray(), element => OnlyDiffersInCasing(element.ToString(), targetName));
var nodes = Array.Find(ctx.Operation.Syntax.DescendantNodes((SyntaxNode e) => true).ToArray(), element => OnlyDiffersInCasing(element.ToString().AsSpan(), targetName.AsSpan()));
if (nodes != null)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, ctx.Operation.Syntax.GetLocation(), new object[] { targetName, "" }));
}
private bool OnlyDiffersInCasing(string left, string right)

private bool OnlyDiffersInCasing(ReadOnlySpan<char> left,ReadOnlySpan<char> right)
{
return left.Trim('"').ToUpper() == right.Trim('"').ToUpper() && left.Trim('"') != right.Trim('"');
}
var leftSpan = left.Trim('"');
var rightSpan = right.Trim('"');
return leftSpan.Equals(rightSpan, StringComparison.OrdinalIgnoreCase) &&
!leftSpan.Equals(rightSpan, StringComparison.Ordinal);
}
}
}
}

0 comments on commit c934bca

Please sign in to comment.