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

Companial Cop - PR: Locked Captions, AutoCalcFields, Internal Method Parameter #403

Merged
merged 14 commits into from
Dec 6, 2023
Merged
2 changes: 1 addition & 1 deletion Design/Rule0039ArgumentDifferentTypeThenExpected.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
using System.Collections;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
Expand All @@ -10,6 +9,7 @@ namespace BusinessCentral.LinterCop.Design
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,
Expand Down
36 changes: 36 additions & 0 deletions Design/Rule0040ExplicitlySetRunTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Immutable;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;

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 (!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);
}
}
15 changes: 15 additions & 0 deletions LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@
"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."
}
]
}
4 changes: 3 additions & 1 deletion LinterCopAnalyzers.Generated.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using System;

namespace BusinessCentral.LinterCop
{
Expand Down Expand Up @@ -46,5 +45,8 @@ public static class DiagnosticDescriptors
public static readonly DiagnosticDescriptor Rule0037ToolTipDoNotUseLineBreaks = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0037", (LocalizableString)new LocalizableResourceString("Rule0037ToolTipDoNotUseLineBreaksTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0037ToolTipDoNotUseLineBreaksFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0037ToolTipDoNotUseLineBreaksDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0037");
public static readonly DiagnosticDescriptor Rule0038ToolTipMaximumLength = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0038", (LocalizableString)new LocalizableResourceString("Rule0038ToolTipMaximumLengthTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0038ToolTipMaximumLengthFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0038ToolTipMaximumLengthDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0038");
public static readonly DiagnosticDescriptor Rule0039ArgumentDifferentTypeThenExpected = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0039", (LocalizableString)new LocalizableResourceString("Rule0039ArgumentDifferentTypeThenExpectedTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0039ArgumentDifferentTypeThenExpectedFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0039ArgumentDifferentTypeThenExpectedDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0039");
public static readonly DiagnosticDescriptor Rule0040ExplicitlySetRunTrigger = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0040", (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0040ExplicitlySetRunTriggerDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0040");
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");
}
}
27 changes: 27 additions & 0 deletions LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,31 @@
<data name="Rule0039ArgumentDifferentTypeThenExpectedTitle" xml:space="preserve">
<value>Argument {0}: cannot convert from '{1}' to '{2}'.</value>
</data>
<data name="Rule0040ExplicitlySetRunTriggerTitle" xml:space="preserve">
<value>Explicitly set the RunTigger parameter on build-in methods.</value>
</data>
<data name="Rule0040ExplicitlySetRunTriggerFormat" xml:space="preserve">
<value>Explicitly set the RunTigger parameter on build-in methods.</value>
</data>
<data name="Rule0040ExplicitlySetRunTriggerDescription" xml:space="preserve">
<value>Explicitly set the RunTigger parameter on build-in methods.</value>
</data>
<data name="Rule0041EmptyCaptionLockedTitle" xml:space="preserve">
<value>Empty captions should be locked.</value>
</data>
<data name="Rule0041EmptyCaptionLockedFormat" xml:space="preserve">
<value>Empty captions should be locked.</value>
</data>
<data name="Rule0041EmptyCaptionLockedDescription" xml:space="preserve">
<value>Empty captions should be locked.</value>
</data>
<data name="Rule0042AutoCalcFieldsOnNormalFieldsTitle" xml:space="preserve">
<value>The SetAutoCalcFields method should only be used with FlowFields or fields of type Blob. The field {0} is not a FlowField or of type Blob.</value>
</data>
<data name="Rule0042AutoCalcFieldsOnNormalFieldsFormat" xml:space="preserve">
<value>The SetAutoCalcFields method should only be used with FlowFields or fields of type Blob. The field {0} is not a FlowField or of type Blob.</value>
</data>
<data name="Rule0042AutoCalcFieldsOnNormalFieldsDescription" xml:space="preserve">
<value>The SetAutoCalcFields method should only be used with FlowFields or fields of type Blob. The field {0} is not a FlowField or of type Blob.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in
|[LC0037](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0037)|Do not use line breaks in ToolTip.|Info|
|[LC0038](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0038)|Try to not exceed 200 characters (including spaces).|Info|
|[LC0039](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0039)|The given argument has a different type from the one expected.|Warning|
|[LC0040](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0040)|Explicitly set the `RunTrigger` parameter on build-in methods.|Info|
|[LC0041](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0041)|Empty Captions should be `Locked`.|Info|
|[LC0042](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0042)|`AutoCalcFields` should only be used for FlowFields or Blob fields.|Warning|


## Configuration
Expand Down