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

Merge from 'Prerelease' into master #423

Merged
merged 33 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e38bb42
Add
Nov 29, 2023
7b1fb34
Add Rule0036EmptyCaptionLocked diagnostic analyzer
Nov 29, 2023
40ae640
Add Rule0037AutoCalcFieldsOnNormalFields
Nov 29, 2023
50b423a
Fix File Name for Rule 0036
Nov 29, 2023
5f21dfb
Merge branch 'development' into ComCopPR-Captions_CalcFields_Internal…
Arthurvdv Dec 4, 2023
e7bb1d5
DrillDownPageId and LookupPageId support on LC0039
Arthurvdv Dec 5, 2023
3193f92
Remove unused method
Arthurvdv Dec 5, 2023
8e36325
Correct naming (Table vs Record)
Arthurvdv Dec 5, 2023
29bedde
Merge pull request #402 from StefanMaron/development
Arthurvdv Dec 5, 2023
6c99403
Merge pull request #383 from tinestaric/ComCopPR-Captions_CalcFields_…
Arthurvdv Dec 5, 2023
c988f22
Renumber
Arthurvdv Dec 5, 2023
90bd7a7
Adapt rule to RunTrigger and include ModifyAll()
Arthurvdv Dec 6, 2023
189a126
Expand to more objects and controls
Arthurvdv Dec 6, 2023
e9b4c29
Housekeeping
Arthurvdv Dec 6, 2023
59416ca
Use IsSameName method
Arthurvdv Dec 6, 2023
5dcd58b
Set description to match with AA0211 rule
Arthurvdv Dec 6, 2023
d0aa20f
Raise default severity to Warning
Arthurvdv Dec 6, 2023
d597b88
Add new rules to the documentation
Arthurvdv Dec 6, 2023
c43d3fa
Merge pull request #403 from StefanMaron/development
Arthurvdv Dec 6, 2023
9ea8a77
New rule0034 Use SecretText type
Arthurvdv Dec 7, 2023
b34a478
Merge pull request #404 from StefanMaron/development
Arthurvdv Dec 7, 2023
0a3ee67
verify method cal from Record variable
Arthurvdv Dec 7, 2023
d767e25
Merge pull request #407 from StefanMaron/development
Arthurvdv Dec 7, 2023
a75d1f8
Bound rule to objetcs of type record
Arthurvdv Dec 7, 2023
ea02859
Merge pull request #408 from StefanMaron/development
Arthurvdv Dec 7, 2023
a6f87a5
Resolve InvalidCastException
Arthurvdv Dec 7, 2023
a49cf27
Merge pull request #411 from StefanMaron/development
Arthurvdv Dec 7, 2023
1dc88e4
Also raise the rule on RecordRef objects
Arthurvdv Dec 7, 2023
8f889f2
Merge pull request #412 from StefanMaron/development
Arthurvdv Dec 7, 2023
44e2c81
typo
Arthurvdv Dec 8, 2023
97f438e
Merge pull request #415 from StefanMaron/development
Arthurvdv Dec 8, 2023
a4d5f53
Resolve System.InvalidCastException
Arthurvdv Dec 8, 2023
e50a7c8
Merge pull request #418 from StefanMaron/development
Arthurvdv Dec 8, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@
namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0039PageRunTableMismatch : DiagnosticAnalyzer
public class Rule0039ArgumentDifferentTypeThenExpected : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected);

private static readonly List<PropertyKind> referencePageProviders = new List<PropertyKind>
{
PropertyKind.LookupPageId,
PropertyKind.DrillDownPageId
};

public override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeRunPageArguments), OperationKind.InvocationExpression);
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeSetRecordArgument), OperationKind.InvocationExpression);
context.RegisterSymbolAction(new Action<SymbolAnalysisContext>(this.AnalyzeTableReferencePageProvider), SymbolKind.Table);
context.RegisterSymbolAction(new Action<SymbolAnalysisContext>(this.AnalyzeTableExtensionReferencePageProvider), SymbolKind.TableExtension);
}

private void AnalyzeRunPageArguments(OperationAnalysisContext ctx)
Expand Down Expand Up @@ -74,6 +82,43 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 1, operand.GetSymbol().GetTypeSymbol().ToString(), pageSourceTable.GetNavTypeKindSafe() + " \"" + pageSourceTable.Name + "\"" }));
}

private void AnalyzeTableReferencePageProvider(SymbolAnalysisContext ctx)
{
if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return;

ITableTypeSymbol table = (ITableTypeSymbol)ctx.Symbol;
foreach (PropertyKind propertyKind in referencePageProviders)
{
IPropertySymbol pageReference = table.GetProperty(propertyKind);
if (pageReference == null) continue;
IPageTypeSymbol page = (IPageTypeSymbol)pageReference.Value;
ITableTypeSymbol pageSourceTable = page.RelatedTable;
if (pageSourceTable == null) continue;

if (!AreTheSameNavObjects(table, pageSourceTable))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, pageReference.GetLocation(), new object[] { 1, table.GetTypeSymbol().GetNavTypeKindSafe() + " \"" + table.Name + "\"", pageSourceTable.GetNavTypeKindSafe() + " \"" + pageSourceTable.Name + "\"" }));
}
}

private void AnalyzeTableExtensionReferencePageProvider(SymbolAnalysisContext ctx)
{
if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return;

ITableExtensionTypeSymbol tableExtension = (ITableExtensionTypeSymbol)ctx.Symbol;
ITableTypeSymbol table = (ITableTypeSymbol)tableExtension.Target;
foreach (PropertyKind propertyKind in referencePageProviders)
{
IPropertySymbol pageReference = tableExtension.GetProperty(propertyKind);
if (pageReference == null) continue;
IPageTypeSymbol page = (IPageTypeSymbol)pageReference.Value;
ITableTypeSymbol pageSourceTable = page.RelatedTable;
if (pageSourceTable == null) continue;

if (!AreTheSameNavObjects(table, pageSourceTable))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, pageReference.GetLocation(), new object[] { 1, table.GetTypeSymbol().GetNavTypeKindSafe() + " \"" + table.Name + "\"", pageSourceTable.GetNavTypeKindSafe() + " \"" + pageSourceTable.Name + "\"" }));
}
}

private static bool AreTheSameNavObjects(ITableTypeSymbol left, ITableTypeSymbol right)
{
if (left.GetNavTypeKindSafe() != right.GetNavTypeKindSafe()) return false;
Expand Down
38 changes: 38 additions & 0 deletions Design/Rule0040ExplicitlySetRunTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Immutable;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;

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

private static readonly List<string> buildInMethodNames = new List<string>
{
"insert",
"modify",
"modifyall",
"delete",
"deleteall"
};

public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeRunTriggerParameters), OperationKind.InvocationExpression);

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

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;
if (!(operation.Instance?.GetSymbol().GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Record || operation.Instance?.GetSymbol().GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.RecordRef)) return;
if (!buildInMethodNames.Contains(operation.TargetMethod.Name.ToLowerInvariant())) return;

if (operation.Arguments.Where(args => SemanticFacts.IsSameName(args.Parameter.Name, "RunTrigger")).SingleOrDefault() == null)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0040ExplicitlySetRunTrigger, ctx.Operation.Syntax.GetLocation()));
}
}
}
62 changes: 62 additions & 0 deletions Design/Rule0041EmptyCaptionLocked.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using System.Collections.Immutable;

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

// List based on https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/properties/devenv-caption-property
public override void Initialize(AnalysisContext context)
=> context.RegisterSyntaxNodeAction(new Action<SyntaxNodeAnalysisContext>(AnalyzeCaptionProperty), new SyntaxKind[] {
SyntaxKind.TableObject,
SyntaxKind.Field, // TableField
SyntaxKind.PageField,
SyntaxKind.PageGroup,
SyntaxKind.PageObject,
SyntaxKind.RequestPage,
SyntaxKind.PageLabel,
SyntaxKind.PageGroup,
SyntaxKind.PagePart,
SyntaxKind.PageSystemPart,
SyntaxKind.PageAction,
SyntaxKind.PageActionSeparator,
SyntaxKind.PageActionGroup,
SyntaxKind.XmlPortObject,
SyntaxKind.ReportObject,
SyntaxKind.QueryObject,
SyntaxKind.QueryColumn,
SyntaxKind.QueryFilter,
SyntaxKind.ReportColumn,
SyntaxKind.EnumValue,
SyntaxKind.PageCustomAction,
SyntaxKind.PageSystemAction,
SyntaxKind.PageView,
SyntaxKind.ReportLayout,
SyntaxKind.ProfileObject,
SyntaxKind.EnumType,
SyntaxKind.PermissionSet,
SyntaxKind.TableExtensionObject,
SyntaxKind.PageExtensionObject
});

private void AnalyzeCaptionProperty(SyntaxNodeAnalysisContext ctx)
{
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;

if (ctx.Node.IsKind(SyntaxKind.EnumValue) && ctx.ContainingSymbol.Kind == SymbolKind.Enum) return; // Prevent double raising the rule on EnumValue in a EnumObject

LabelPropertyValueSyntax captionProperty = ctx.Node?.GetProperty("Caption")?.Value as LabelPropertyValueSyntax;
if (captionProperty?.Value.LabelText.GetLiteralValue() == null || captionProperty.Value.LabelText.GetLiteralValue().ToString().Trim() != "") return;

if (captionProperty.Value.Properties?.Values.Where(prop => prop.Identifier.Text.ToLowerInvariant() == "locked").FirstOrDefault() != null) return;

ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0041EmptyCaptionLocked, captionProperty.GetLocation()));
}
}
}
29 changes: 29 additions & 0 deletions Design/Rule0042AutoCalcFieldsOnNormalFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

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

public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(syntaxContext =>
{
if (!syntaxContext.Node.ToString().ToLowerInvariant().Contains("setautocalcfields"))
return;

IInvocationExpression operation = (IInvocationExpression)syntaxContext.SemanticModel.GetOperation(syntaxContext.Node);
IMethodSymbol targetMethod = operation.TargetMethod;
if (targetMethod == null || !SemanticFacts.IsSameName(targetMethod.Name, "setautocalcfields") || targetMethod.MethodKind != MethodKind.BuiltInMethod)
return;

foreach (IArgument obj in operation.Arguments)
{
if ((obj.Value is IConversionExpression conversionExpression2 ? conversionExpression2.Operand : (IOperation)null) is IFieldAccess fieldAccess2 && fieldAccess2.FieldSymbol.FieldClass != FieldClassKind.FlowField && fieldAccess2.Type.NavTypeKind != NavTypeKind.Blob)
syntaxContext.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0042AutoCalcFieldsOnNormalFields, fieldAccess2.Syntax.GetLocation(), (object)fieldAccess2.FieldSymbol.Name));
}
}, SyntaxKind.InvocationExpression);
}
}
74 changes: 74 additions & 0 deletions Design/Rule0043SecretText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.Immutable;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;

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

private static readonly string authorization = "Authorization";
private static readonly List<string> buildInMethodNames = new List<string>
{
"add",
"getvalues",
"tryaddwithoutvalidation"
};

public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeHttpObjects), OperationKind.InvocationExpression);

private void AnalyzeHttpObjects(OperationAnalysisContext ctx)
{
if (!VersionChecker.IsSupported(ctx.ContainingSymbol, VersionCompatibility.Fall2023OrGreater)) return;

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

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;

// We need at least two arguments
if (operation.Arguments.Count() < 2) return;

switch (operation.TargetMethod.MethodKind)
{
case MethodKind.BuiltInMethod:
if (!buildInMethodNames.Contains(operation.TargetMethod.Name.ToLowerInvariant())) return;
if (!(operation.Instance?.GetSymbol().GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.HttpHeaders || operation.Instance?.GetSymbol().GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.HttpClient)) return;
break;
case MethodKind.Method:
if (operation.TargetMethod.ContainingType.GetNavTypeKindSafe() != NavTypeKind.Codeunit) return;
ICodeunitTypeSymbol codeunitTypeSymbol = (ICodeunitTypeSymbol)operation.TargetMethod.GetContainingObjectTypeSymbol();
if (!SemanticFacts.IsSameName(((INamespaceSymbol)codeunitTypeSymbol.ContainingSymbol).QualifiedName, "System.RestClient")) return;
if (!SemanticFacts.IsSameName(codeunitTypeSymbol.Name, "Rest Client")) return;
if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "SetDefaultRequestHeader")) return;
break;
default:
return;
}

if (!IsAuthorizationArgument(operation.Arguments[0])) return;

if (operation.Arguments[1].Parameter.OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.SecretText)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0043SecretText, ctx.Operation.Syntax.GetLocation()));
}

private static bool IsAuthorizationArgument(IArgument argument)
{
switch (argument.Syntax.Kind)
{
case SyntaxKind.LiteralExpression:
return SemanticFacts.IsSameName(argument.Value.ConstantValue.Value.ToString(), authorization);
case SyntaxKind.IdentifierName:
IOperation operand = ((IConversionExpression)argument.Value).Operand;
if (operand.GetSymbol().OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Label) return false;
ILabelTypeSymbol label = (ILabelTypeSymbol)operand.GetSymbol().OriginalDefinition.GetTypeSymbol();
return SemanticFacts.IsSameName(label.GetLabelText(), authorization);
default:
return false;
}
}
}
}
20 changes: 20 additions & 0 deletions LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,26 @@
"id": "LC0039",
"action": "Warning",
"justification": "The given argument has a different type from the one expected."
},
{
"id": "LC0040",
"action": "Info",
"justification": "Explicitly set the RunTrigger parameter on build-in methods."
},
{
"id": "LC0041",
"action": "Info",
"justification": "Empty Captions should be Locked."
},
{
"id": "LC0042",
"action": "Warning",
"justification": "AutoCalcFields should only be used for FlowFields or Blob fields."
},
{
"id": "LC0043",
"action": "Info",
"justification": "Use SecretText type to protect credentials and sensitive textual values from being revealed."
}
]
}
Loading
Loading