From a964192f328b414b19d4e479022966a32bf5cece Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 26 Nov 2023 15:50:45 +0100 Subject: [PATCH 01/23] Rule0035 Explicitly set AllowInCustomizations --- ...ule0035ExplicitSetAllowInCustomizations.cs | 104 ++++++++++++++++++ LinterCop.ruleset.json | 5 + LinterCopAnalyzers.Generated.cs | 1 + LinterCopAnalyzers.resx | 9 ++ README.md | 1 + 5 files changed, 120 insertions(+) create mode 100644 Design/Rule0035ExplicitSetAllowInCustomizations.cs diff --git a/Design/Rule0035ExplicitSetAllowInCustomizations.cs b/Design/Rule0035ExplicitSetAllowInCustomizations.cs new file mode 100644 index 00000000..102bae2c --- /dev/null +++ b/Design/Rule0035ExplicitSetAllowInCustomizations.cs @@ -0,0 +1,104 @@ +using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; +using System.Collections.Immutable; +using System.Collections.ObjectModel; + +namespace BusinessCentral.LinterCop.Design +{ + [DiagnosticAnalyzer] + public class Rule0035ExplicitSetAllowInCustomizations : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0035ExplicitSetAllowInCustomizations); + + public override void Initialize(AnalysisContext context) + => context.RegisterSymbolAction(new Action(this.AnalyzeAllowInCustomization), new SymbolKind[] { + SymbolKind.Table, + SymbolKind.TableExtension, + }); + + private void AnalyzeAllowInCustomization(SymbolAnalysisContext ctx) + { + // The AllowInCustomizations property is supported from runtime versions 12.0 or greater. + var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); + if (manifest.Runtime < RuntimeVersion.Fall2023) return; + + if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; + if (ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; + + ICollection tableFields = GetTableFields(ctx.Symbol).Where(x => x.Id > 0 && x.Id < 2000000000) + .Where(x => x.GetProperty(PropertyKind.AllowInCustomizations) is null) + .ToList(); + if (!tableFields.Any()) return; + + IEnumerable relatedPages = GetRelatedPages(ctx); + if (!relatedPages.Any()) return; + + NavTypeKind navTypeKind = ctx.Symbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe(); + ICollection pageFields = GetPageFields(navTypeKind, relatedPages); + ICollection fieldsNotReferencedOnPage = tableFields.Except(pageFields).ToList(); + foreach (IFieldSymbol field in fieldsNotReferencedOnPage) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0035ExplicitSetAllowInCustomizations, field.Location)); + } + + private static ICollection GetTableFields(ISymbol symbol) + { + switch (symbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe()) + { + case NavTypeKind.Table: + return ((ITableTypeSymbol)symbol).Fields; + case NavTypeKind.TableExtension: + return ((ITableExtensionTypeSymbol)symbol).AddedFields; + default: + return new Collection(); + } + } + + private static ICollection GetPageFields(NavTypeKind navTypeKind, IEnumerable relatedPages) + { + ICollection pageFields = new Collection(); + switch (navTypeKind) + { + case NavTypeKind.Table: + foreach (IPageTypeSymbol page in relatedPages.Cast()) + { + IEnumerable fields = page.FlattenedControls.Where(x => x.ControlKind == ControlKind.Field && x.RelatedFieldSymbol != null) + .Select(x => (IFieldSymbol)x.RelatedFieldSymbol.OriginalDefinition); + + pageFields = pageFields.Union(fields).Distinct().ToList(); + } + return pageFields; + case NavTypeKind.TableExtension: + foreach (IPageExtensionTypeSymbol page in relatedPages.Cast()) + { + IEnumerable fields = page.AddedControlsFlattened.Where(x => x.ControlKind == ControlKind.Field && x.RelatedFieldSymbol != null) + .Select(x => (IFieldSymbol)x.RelatedFieldSymbol.OriginalDefinition); + + pageFields = pageFields.Union(fields).Distinct().ToList(); + } + return pageFields; + default: + return pageFields; + } + } + + private static IEnumerable GetRelatedPages(SymbolAnalysisContext ctx) + { + switch (ctx.Symbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe()) + { + case NavTypeKind.Table: + return ctx.Compilation.GetDeclaredApplicationObjectSymbols() + .Where(x => x.GetNavTypeKindSafe() == NavTypeKind.Page) + .Where(x => ((IPageTypeSymbol)x.GetTypeSymbol()).PageType != PageTypeKind.API) + .Where(x => ((IPageTypeSymbol)x.GetTypeSymbol()).RelatedTable == (ITableTypeSymbol)ctx.Symbol); + case NavTypeKind.TableExtension: + return ctx.Compilation.GetDeclaredApplicationObjectSymbols() + .Where(x => x.GetNavTypeKindSafe() == NavTypeKind.PageExtension) + .Where(x => ((IPageTypeSymbol)((IApplicationObjectExtensionTypeSymbol)x).Target.GetTypeSymbol()).RelatedTable == ((IApplicationObjectExtensionTypeSymbol)ctx.Symbol).Target); + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index 27b1e8ab..40dbc7ea 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -171,6 +171,11 @@ "id": "LC0034", "action": "Hidden", "justification": "The property Extensible should be explicitly set for public objects." + }, + { + "id": "LC0035", + "action": "Info", + "justification": "Explicitly set AllowInCustomizations for fields omitted on pages." } ] } \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index 82c244e9..27731d88 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -41,5 +41,6 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0032ClearCodeunitSingleInstance = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0032", (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0032"); public static readonly DiagnosticDescriptor Rule0033AppManifestRuntimeBehind = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0033", (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindTitleDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0033"); public static readonly DiagnosticDescriptor Rule0034ExtensiblePropertyShouldAlwaysBeSet = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0034", (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Hidden, true, (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0034"); + public static readonly DiagnosticDescriptor Rule0035ExplicitSetAllowInCustomizations = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0035", (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0035"); } } \ No newline at end of file diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 236e6e58..20cac5c9 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -411,4 +411,13 @@ The property Extensible should be explicitly set for {0} objects. + + Explicitly set AllowInCustomizations for fields omitted on pages. + + + Explicitly set AllowInCustomizations for fields omitted on pages. + + + Explicitly set AllowInCustomizations for fields omitted on pages. + \ No newline at end of file diff --git a/README.md b/README.md index 8b9e48e7..aae368df 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0032](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0032)|Clear(All) does not affect or change values for global variables in single instance codeunits.|Warning| |[LC0033](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0033)|The specified runtime version in app.json is falling behind.|Info| |[LC0034](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0034)|The property `Extensible` should be explicitly set for public objects.|Disabled| +|[LC0035](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0035)|Explicitly set `AllowInCustomizations` for fields omitted on pages.|Info| ## Configuration From 7ab27742afd59465a4a5e05adac612df1b39f2bd Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 26 Nov 2023 19:38:15 +0100 Subject: [PATCH 02/23] Optimize performance --- ...e0028CodeNavigabilityOnEventSubscribers.cs | 19 +++++++++---------- .../Rule0031RecordInstanceIsolationLevel.cs | 13 +++++++------ ...0034ExtensiblePropertyShouldAlwaysBeSet.cs | 13 +++++++------ ...ule0035ExplicitSetAllowInCustomizations.cs | 6 +++--- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs index e713d91f..4164564a 100644 --- a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs +++ b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs @@ -15,21 +15,20 @@ public class Rule0028CodeNavigabilityOnEventSubscribers : DiagnosticAnalyzer private void CodeNavigabilityOnEventSubscribers(CodeBlockAnalysisContext context) { - // Support for using Identifiers instead of Literals in event subscribers is supported from runtime versions: '11.0' or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); - if (manifest.Runtime < RuntimeVersion.Spring2023) return; - if (context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; if (context.OwningSymbol.IsObsoletePending || context.OwningSymbol.IsObsoleteRemoved) return; if (!context.CodeBlock.IsKind(SyntaxKind.MethodDeclaration)) return; - var SyntaxList = ((MethodDeclarationSyntax)context.CodeBlock).Attributes.Where(value => value.GetIdentifierOrLiteralValue().ToUpper() == "EVENTSUBSCRIBER"); - foreach (var Syntax in SyntaxList) - { - if (Syntax.ArgumentList.Arguments[2].IsKind(SyntaxKind.LiteralAttributeArgument)) - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028CodeNavigabilityOnEventSubscribers, context.OwningSymbol.GetLocation())); - } + IEnumerable MemberAttributeSyntaxList = ((MethodDeclarationSyntax)context.CodeBlock).Attributes.Where(value => SemanticFacts.IsSameName(value.GetIdentifierOrLiteralValue(), "EventSubscriber")); + if (!MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[2]).Where(syntax => syntax.IsKind(SyntaxKind.LiteralAttributeArgument)).Any() && + !MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[3]).Where(syntax => syntax.IsKind(SyntaxKind.LiteralAttributeArgument)).Any()) return; + + // Support for using Identifiers instead of Literals in event subscribers is supported from runtime versions: '11.0' or greater. + var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); + if (manifest.Runtime < RuntimeVersion.Spring2023) return; + + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028CodeNavigabilityOnEventSubscribers, context.OwningSymbol.GetLocation())); } } } \ No newline at end of file diff --git a/Design/Rule0031RecordInstanceIsolationLevel.cs b/Design/Rule0031RecordInstanceIsolationLevel.cs index e18e0d88..9d2099e5 100644 --- a/Design/Rule0031RecordInstanceIsolationLevel.cs +++ b/Design/Rule0031RecordInstanceIsolationLevel.cs @@ -14,18 +14,19 @@ public class Rule0031RecordInstanceIsolationLevel : DiagnosticAnalyzer private void CheckLockTable(OperationAnalysisContext ctx) { - // ReadIsolation is supported from runtime versions 11.0 or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); - if (manifest.Runtime < RuntimeVersion.Spring2023) return; - 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 (SemanticFacts.IsSameName(operation.TargetMethod.Name, "LockTable")) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0031RecordInstanceIsolationLevel, ctx.Operation.Syntax.GetLocation())); + if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "LockTable")) return; + + // ReadIsolation is supported from runtime versions 11.0 or greater. + var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); + if (manifest.Runtime < RuntimeVersion.Spring2023) return; + + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0031RecordInstanceIsolationLevel, ctx.Operation.Syntax.GetLocation())); } } } \ No newline at end of file diff --git a/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs b/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs index f0baf03b..81253a06 100644 --- a/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs +++ b/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs @@ -20,17 +20,18 @@ public override void Initialize(AnalysisContext context) private void CheckForMissingExtensibleProperty(SymbolAnalysisContext ctx) { - // The Extensible property (and DeclaredAccessibility) is supported from runtime versions 4.0 or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); - if (manifest.Runtime < RuntimeVersion.Fall2019) return; - if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; if (ctx.Symbol.GetTypeSymbol().Kind == SymbolKind.Table && ctx.Symbol.DeclaredAccessibility != Accessibility.Public) return; if (ctx.Symbol.GetTypeSymbol().Kind == SymbolKind.Page && ((IPageTypeSymbol)ctx.Symbol.GetTypeSymbol()).PageType == PageTypeKind.API) return; - if (ctx.Symbol.GetProperty(PropertyKind.Extensible) is null) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0034ExtensiblePropertyShouldAlwaysBeSet, ctx.Symbol.GetLocation(), new object[] { Accessibility.Public.ToString().ToLower() })); + if (ctx.Symbol.GetProperty(PropertyKind.Extensible) != null) return; + + // The Extensible property (and DeclaredAccessibility) is supported from runtime versions 4.0 or greater. + var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); + if (manifest.Runtime < RuntimeVersion.Fall2019) return; + + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0034ExtensiblePropertyShouldAlwaysBeSet, ctx.Symbol.GetLocation(), new object[] { Accessibility.Public.ToString().ToLower() })); } } } \ No newline at end of file diff --git a/Design/Rule0035ExplicitSetAllowInCustomizations.cs b/Design/Rule0035ExplicitSetAllowInCustomizations.cs index 102bae2c..7beabc37 100644 --- a/Design/Rule0035ExplicitSetAllowInCustomizations.cs +++ b/Design/Rule0035ExplicitSetAllowInCustomizations.cs @@ -20,13 +20,13 @@ public override void Initialize(AnalysisContext context) private void AnalyzeAllowInCustomization(SymbolAnalysisContext ctx) { + if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; + if (ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; + // The AllowInCustomizations property is supported from runtime versions 12.0 or greater. var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); if (manifest.Runtime < RuntimeVersion.Fall2023) return; - if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; - if (ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; - ICollection tableFields = GetTableFields(ctx.Symbol).Where(x => x.Id > 0 && x.Id < 2000000000) .Where(x => x.GetProperty(PropertyKind.AllowInCustomizations) is null) .ToList(); From ea1018ca6f94827e963cb0b6c668ad4c6df80d1d Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 26 Nov 2023 20:49:58 +0100 Subject: [PATCH 03/23] Extend rule0026 with guidelines for tooltip text --- Design/Rule0026ToolTipPunctuation.cs | 18 +++++++++++---- LinterCop.ruleset.json | 2 +- LinterCopAnalyzers.Generated.cs | 5 ++++- LinterCopAnalyzers.resx | 33 +++++++++++++++++++++++++--- README.md | 2 +- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Design/Rule0026ToolTipPunctuation.cs b/Design/Rule0026ToolTipPunctuation.cs index 13b9a4fc..37f33867 100644 --- a/Design/Rule0026ToolTipPunctuation.cs +++ b/Design/Rule0026ToolTipPunctuation.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; namespace BusinessCentral.LinterCop.Design @@ -8,7 +9,7 @@ namespace BusinessCentral.LinterCop.Design [DiagnosticAnalyzer] public class Rule0026ToolTipPunctuation : DiagnosticAnalyzer { - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0026ToolTipPunctuation); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0026ToolTipMustEndWithDot, DiagnosticDescriptors.Rule0026ToolTipMaximumLength, DiagnosticDescriptors.Rule0026ToolTipDoNotUseLineBreaks, DiagnosticDescriptors.Rule0026ToolTipShouldStartWithSpecifies); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(new Action(this.AnalyzeToolTipPunctuation), SyntaxKind.PageField, SyntaxKind.PageAction); @@ -18,12 +19,21 @@ private void AnalyzeToolTipPunctuation(SyntaxNodeAnalysisContext ctx) if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; PropertyValueSyntax tooltipProperty = ctx.Node.GetPropertyValue(PropertyKind.ToolTip); - if (tooltipProperty == null) - return; + if (tooltipProperty == null) return; StringLiteralValueSyntax tooltipLabel = ((LabelPropertyValueSyntax)tooltipProperty).Value.LabelText; if (!tooltipLabel.Value.ToString().EndsWith(".'")) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipPunctuation, tooltipProperty.GetLocation())); + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipMustEndWithDot, tooltipProperty.GetLocation())); + + if (tooltipLabel.Value.ToString().Count() > 200) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipMaximumLength, tooltipProperty.GetLocation())); + + if (tooltipLabel.Value.ToString().Contains("\\")) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipDoNotUseLineBreaks, tooltipProperty.GetLocation())); + + if (((IControlSymbol)ctx.ContainingSymbol).ControlKind == ControlKind.Field) + if (!tooltipLabel.Value.ToString().StartsWith("Specifies")) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipShouldStartWithSpecifies, tooltipProperty.GetLocation())); } } } \ No newline at end of file diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index 40dbc7ea..22e28e69 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -130,7 +130,7 @@ { "id": "LC0026", "action": "Info", - "justification": "ToolTip must end with a dot." + "justification": "Guidelines for ToolTip text." }, { "id": "LC0027", diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index 27731d88..db3f5650 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -32,7 +32,10 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0023AlwaysSpecifyFieldgroups = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0023", (LocalizableString)new LocalizableResourceString("Rule0023AlwaysSpecifyFieldgroups", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0023AlwaysSpecifyFieldgroups", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0023AlwaysSpecifyFieldgroups", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0023"); public static readonly DiagnosticDescriptor Rule0024SemicolonAfterProcedureDeclaration = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0024", (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0024"); public static readonly DiagnosticDescriptor Rule0025InternalProcedureModifier = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0025", (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Hidden, true, (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0025"); - public static readonly DiagnosticDescriptor Rule0026ToolTipPunctuation = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipPunctuationTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipPunctuationFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipPunctuationDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); + public static readonly DiagnosticDescriptor Rule0026ToolTipMustEndWithDot = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); + public static readonly DiagnosticDescriptor Rule0026ToolTipMaximumLength = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); + public static readonly DiagnosticDescriptor Rule0026ToolTipDoNotUseLineBreaks = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); + public static readonly DiagnosticDescriptor Rule0026ToolTipShouldStartWithSpecifies = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); public static readonly DiagnosticDescriptor Rule0027RunPageImplementPageManagement = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0027", (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0027"); public static readonly DiagnosticDescriptor Rule0028CodeNavigabilityOnEventSubscribers = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0028", (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028"); public static readonly DiagnosticDescriptor Rule0029CompareDateTimeThroughCodeunit = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0029", (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0029"); diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 20cac5c9..5e214cf0 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -336,15 +336,42 @@ Procedure must be either local or internal. - + ToolTip must end with a dot. - + ToolTip must end with a dot. - + ToolTip must end with a dot. + + Try to not exceed 200 characters including spaces. + + + Try to not exceed 200 characters including spaces. + + + Try to not exceed 200 characters including spaces. + + + Do not use line breaks in ToolTip. + + + Do not use line breaks in ToolTip. + + + Do not use line breaks in ToolTip. + + + For fields specifically, the ToolTip must start with the verb "Specifies". + + + For fields specifically, the ToolTip must start with the verb "Specifies". + + + For fields specifically, the ToolTip must start with the verb "Specifies". + Utilize the "Page Management" codeunit for launching page. diff --git a/README.md b/README.md index aae368df..e1c3ec81 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0023](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0023)|Always provide fieldsgroups `DropDown` and `Brick` on tables.|Info| |[LC0024](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0024)|Procedure declaration should not end with semicolon.|Info| |[LC0025](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0025)|Procedure must be either local or internal.|Info| -|[LC0026](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026)|ToolTip must end with a dot.|Info| +|[LC0026](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026)|Guidelines for ToolTip text.|Info| |[LC0027](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0027)|Utilize the `Page Management` codeunit for launching page.|Info| |[LC0028](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028)|Event subscriber arguments now use identifier syntax instead of string literals.|Info| |[LC0029](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0029)|Use `CompareDateTime` method in `Type Helper` codeunit for DateTime variable comparisons.|Info| From 08f9ebeea60fc5eb3de6b7fd8ecf2e73fa631686 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 26 Nov 2023 21:15:44 +0100 Subject: [PATCH 04/23] Bugfix on rule 0026 --- Design/Rule0026ToolTipPunctuation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Design/Rule0026ToolTipPunctuation.cs b/Design/Rule0026ToolTipPunctuation.cs index 37f33867..e4db7124 100644 --- a/Design/Rule0026ToolTipPunctuation.cs +++ b/Design/Rule0026ToolTipPunctuation.cs @@ -31,8 +31,8 @@ private void AnalyzeToolTipPunctuation(SyntaxNodeAnalysisContext ctx) if (tooltipLabel.Value.ToString().Contains("\\")) ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipDoNotUseLineBreaks, tooltipProperty.GetLocation())); - if (((IControlSymbol)ctx.ContainingSymbol).ControlKind == ControlKind.Field) - if (!tooltipLabel.Value.ToString().StartsWith("Specifies")) + if (ctx.ContainingSymbol.Kind == SymbolKind.Control && ((IControlSymbol)ctx.ContainingSymbol).ControlKind == ControlKind.Field) + if (!tooltipLabel.Value.ToString().StartsWith("'Specifies")) ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipShouldStartWithSpecifies, tooltipProperty.GetLocation())); } } From d12f8d0441fdb4b1f8a89974c01a11e95d27fe60 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 09:25:44 +0100 Subject: [PATCH 05/23] Resolve false positive --- Design/Rule0028CodeNavigabilityOnEventSubscribers.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs index 4164564a..7ef50a06 100644 --- a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs +++ b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs @@ -21,8 +21,12 @@ private void CodeNavigabilityOnEventSubscribers(CodeBlockAnalysisContext context if (!context.CodeBlock.IsKind(SyntaxKind.MethodDeclaration)) return; IEnumerable MemberAttributeSyntaxList = ((MethodDeclarationSyntax)context.CodeBlock).Attributes.Where(value => SemanticFacts.IsSameName(value.GetIdentifierOrLiteralValue(), "EventSubscriber")); - if (!MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[2]).Where(syntax => syntax.IsKind(SyntaxKind.LiteralAttributeArgument)).Any() && - !MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[3]).Where(syntax => syntax.IsKind(SyntaxKind.LiteralAttributeArgument)).Any()) return; + + AttributeArgumentSyntax eventName = MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[2]).FirstOrDefault(); + AttributeArgumentSyntax eventElement = MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[3]).FirstOrDefault(); + bool isEventNameStringLiteral = eventName.IsKind(SyntaxKind.LiteralAttributeArgument); + bool isEventElementStringLiteral = !(eventElement.GetIdentifierOrLiteralValue() == "") && eventElement.IsKind(SyntaxKind.LiteralAttributeArgument); + if (!isEventNameStringLiteral && !isEventElementStringLiteral) return; // Support for using Identifiers instead of Literals in event subscribers is supported from runtime versions: '11.0' or greater. var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); From 1809a703d469110da96ddf89477feb55f5a20726 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 11:05:14 +0100 Subject: [PATCH 06/23] Rule0035 Check for Enabled and Obsolete --- Design/Rule0035ExplicitSetAllowInCustomizations.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Design/Rule0035ExplicitSetAllowInCustomizations.cs b/Design/Rule0035ExplicitSetAllowInCustomizations.cs index 7beabc37..1767a4b9 100644 --- a/Design/Rule0035ExplicitSetAllowInCustomizations.cs +++ b/Design/Rule0035ExplicitSetAllowInCustomizations.cs @@ -28,7 +28,9 @@ private void AnalyzeAllowInCustomization(SymbolAnalysisContext ctx) if (manifest.Runtime < RuntimeVersion.Fall2023) return; ICollection tableFields = GetTableFields(ctx.Symbol).Where(x => x.Id > 0 && x.Id < 2000000000) + .Where(x => x.GetBooleanPropertyValue(PropertyKind.Enabled) != false) .Where(x => x.GetProperty(PropertyKind.AllowInCustomizations) is null) + .Where(x => x.GetProperty(PropertyKind.ObsoleteState) is null) .ToList(); if (!tableFields.Any()) return; @@ -46,7 +48,7 @@ private static ICollection GetTableFields(ISymbol symbol) { switch (symbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe()) { - case NavTypeKind.Table: + case NavTypeKind.Record: return ((ITableTypeSymbol)symbol).Fields; case NavTypeKind.TableExtension: return ((ITableExtensionTypeSymbol)symbol).AddedFields; @@ -60,7 +62,7 @@ private static ICollection GetPageFields(NavTypeKind navTypeKind, ICollection pageFields = new Collection(); switch (navTypeKind) { - case NavTypeKind.Table: + case NavTypeKind.Record: foreach (IPageTypeSymbol page in relatedPages.Cast()) { IEnumerable fields = page.FlattenedControls.Where(x => x.ControlKind == ControlKind.Field && x.RelatedFieldSymbol != null) @@ -87,7 +89,7 @@ private static IEnumerable GetRelatedPages(SymbolA { switch (ctx.Symbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe()) { - case NavTypeKind.Table: + case NavTypeKind.Record: return ctx.Compilation.GetDeclaredApplicationObjectSymbols() .Where(x => x.GetNavTypeKindSafe() == NavTypeKind.Page) .Where(x => ((IPageTypeSymbol)x.GetTypeSymbol()).PageType != PageTypeKind.API) From f332dc4fbcf9ab9a171354244bb33dd1ca977175 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 15:55:23 +0100 Subject: [PATCH 07/23] Exclude FlowFilter and specific types --- ...Rule0035ExplicitSetAllowInCustomizations.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Design/Rule0035ExplicitSetAllowInCustomizations.cs b/Design/Rule0035ExplicitSetAllowInCustomizations.cs index 1767a4b9..047049a0 100644 --- a/Design/Rule0035ExplicitSetAllowInCustomizations.cs +++ b/Design/Rule0035ExplicitSetAllowInCustomizations.cs @@ -31,6 +31,8 @@ private void AnalyzeAllowInCustomization(SymbolAnalysisContext ctx) .Where(x => x.GetBooleanPropertyValue(PropertyKind.Enabled) != false) .Where(x => x.GetProperty(PropertyKind.AllowInCustomizations) is null) .Where(x => x.GetProperty(PropertyKind.ObsoleteState) is null) + .Where(x => x.FieldClass != FieldClassKind.FlowFilter) + .Where(x => IsSupportedType(x.OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe())) .ToList(); if (!tableFields.Any()) return; @@ -102,5 +104,21 @@ private static IEnumerable GetRelatedPages(SymbolA return null; } } + + private static bool IsSupportedType(NavTypeKind navTypeKind) + { + switch (navTypeKind) + { + case NavTypeKind.Blob: + case NavTypeKind.Media: + case NavTypeKind.MediaSet: + case NavTypeKind.RecordId: + case NavTypeKind.TableFilter: + return false; + + default: + return true; + } + } } } \ No newline at end of file From 8ccc24f261b23d373eb2d433be87c0ea537769ae Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 16:37:33 +0100 Subject: [PATCH 08/23] Mitigate NullReferenceException --- Design/Rule0028CodeNavigabilityOnEventSubscribers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs index 7ef50a06..8d846c44 100644 --- a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs +++ b/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs @@ -30,7 +30,7 @@ private void CodeNavigabilityOnEventSubscribers(CodeBlockAnalysisContext context // Support for using Identifiers instead of Literals in event subscribers is supported from runtime versions: '11.0' or greater. var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); - if (manifest.Runtime < RuntimeVersion.Spring2023) return; + if (manifest is null || manifest.Runtime < RuntimeVersion.Spring2023) return; context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028CodeNavigabilityOnEventSubscribers, context.OwningSymbol.GetLocation())); } From 3819edb71895a03abceb652c3abed27a75bb3817 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 18:15:16 +0100 Subject: [PATCH 09/23] Match name of rule with feature --- ...scribers.cs => Rule0028IdentifiersInEventSubscribers.cs} | 4 ++-- LinterCopAnalyzers.Generated.cs | 2 +- LinterCopAnalyzers.resx | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename Design/{Rule0028CodeNavigabilityOnEventSubscribers.cs => Rule0028IdentifiersInEventSubscribers.cs} (93%) diff --git a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs b/Design/Rule0028IdentifiersInEventSubscribers.cs similarity index 93% rename from Design/Rule0028CodeNavigabilityOnEventSubscribers.cs rename to Design/Rule0028IdentifiersInEventSubscribers.cs index 8d846c44..220a1b7f 100644 --- a/Design/Rule0028CodeNavigabilityOnEventSubscribers.cs +++ b/Design/Rule0028IdentifiersInEventSubscribers.cs @@ -9,7 +9,7 @@ namespace BusinessCentral.LinterCop.Design [DiagnosticAnalyzer] public class Rule0028CodeNavigabilityOnEventSubscribers : DiagnosticAnalyzer { - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0028CodeNavigabilityOnEventSubscribers); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers); public override void Initialize(AnalysisContext context) => context.RegisterCodeBlockAction(new Action(this.CodeNavigabilityOnEventSubscribers)); @@ -32,7 +32,7 @@ private void CodeNavigabilityOnEventSubscribers(CodeBlockAnalysisContext context var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); if (manifest is null || manifest.Runtime < RuntimeVersion.Spring2023) return; - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028CodeNavigabilityOnEventSubscribers, context.OwningSymbol.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers, context.OwningSymbol.GetLocation())); } } } \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index db3f5650..f95bc87a 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -37,7 +37,7 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0026ToolTipDoNotUseLineBreaks = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); public static readonly DiagnosticDescriptor Rule0026ToolTipShouldStartWithSpecifies = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); public static readonly DiagnosticDescriptor Rule0027RunPageImplementPageManagement = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0027", (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0027"); - public static readonly DiagnosticDescriptor Rule0028CodeNavigabilityOnEventSubscribers = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0028", (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028"); + public static readonly DiagnosticDescriptor Rule0028IdentifiersInEventSubscribers = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0028", (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028"); public static readonly DiagnosticDescriptor Rule0029CompareDateTimeThroughCodeunit = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0029", (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0029"); public static readonly DiagnosticDescriptor Rule0030AccessInternalForInstallAndUpgradeCodeunits = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0030", (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0030"); public static readonly DiagnosticDescriptor Rule0031RecordInstanceIsolationLevel = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0031", (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0031"); diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 5e214cf0..78b59bd7 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -375,13 +375,13 @@ Utilize the "Page Management" codeunit for launching page. - + Event subscriber arguments now use identifier syntax instead of string literals. - + Event subscriber arguments now use identifier syntax instead of string literals. - + Event subscriber arguments now use identifier syntax instead of string literals. From 3521df35911dfa13b21249e7ea9ba78cca6b2116 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 18:16:43 +0100 Subject: [PATCH 10/23] Implement VersionChecker --- .../Rule0028IdentifiersInEventSubscribers.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Design/Rule0028IdentifiersInEventSubscribers.cs b/Design/Rule0028IdentifiersInEventSubscribers.cs index 220a1b7f..e9837292 100644 --- a/Design/Rule0028IdentifiersInEventSubscribers.cs +++ b/Design/Rule0028IdentifiersInEventSubscribers.cs @@ -1,4 +1,3 @@ -using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; @@ -11,28 +10,30 @@ public class Rule0028CodeNavigabilityOnEventSubscribers : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers); - public override void Initialize(AnalysisContext context) => context.RegisterCodeBlockAction(new Action(this.CodeNavigabilityOnEventSubscribers)); + public override void Initialize(AnalysisContext context) => context.RegisterCodeBlockAction(new Action(this.AnalyzeIdentifiersInEventSubscribers)); - private void CodeNavigabilityOnEventSubscribers(CodeBlockAnalysisContext context) + private void AnalyzeIdentifiersInEventSubscribers(CodeBlockAnalysisContext context) { + if (!VersionChecker.IsSupported(context.OwningSymbol, Feature.IdentifiersInEventSubscribers)) return; + if (context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; if (context.OwningSymbol.IsObsoletePending || context.OwningSymbol.IsObsoleteRemoved) return; if (!context.CodeBlock.IsKind(SyntaxKind.MethodDeclaration)) return; - IEnumerable MemberAttributeSyntaxList = ((MethodDeclarationSyntax)context.CodeBlock).Attributes.Where(value => SemanticFacts.IsSameName(value.GetIdentifierOrLiteralValue(), "EventSubscriber")); - - AttributeArgumentSyntax eventName = MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[2]).FirstOrDefault(); - AttributeArgumentSyntax eventElement = MemberAttributeSyntaxList.Select(value => value.ArgumentList.Arguments[3]).FirstOrDefault(); - bool isEventNameStringLiteral = eventName.IsKind(SyntaxKind.LiteralAttributeArgument); - bool isEventElementStringLiteral = !(eventElement.GetIdentifierOrLiteralValue() == "") && eventElement.IsKind(SyntaxKind.LiteralAttributeArgument); - if (!isEventNameStringLiteral && !isEventElementStringLiteral) return; + var SyntaxList = ((MethodDeclarationSyntax)context.CodeBlock).Attributes.Where(value => SemanticFacts.IsSameName(value.GetIdentifierOrLiteralValue(), "EventSubscriber")); - // Support for using Identifiers instead of Literals in event subscribers is supported from runtime versions: '11.0' or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(context.SemanticModel.Compilation); - if (manifest is null || manifest.Runtime < RuntimeVersion.Spring2023) return; + if (SyntaxList.Where(value => value.ArgumentList.Arguments[2].IsKind(SyntaxKind.LiteralAttributeArgument)).Any()) + { + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers, context.OwningSymbol.GetLocation())); + return; + } - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers, context.OwningSymbol.GetLocation())); + if (SyntaxList.Where(value => !(value.ArgumentList.Arguments[3].GetIdentifierOrLiteralValue() == "") && value.ArgumentList.Arguments[3].IsKind(SyntaxKind.LiteralAttributeArgument)).Any()) + { + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0028IdentifiersInEventSubscribers, context.OwningSymbol.GetLocation())); + return; + } } } } \ No newline at end of file From cce56e368ad164899302b6127830600621f5d566 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 27 Nov 2023 19:49:17 +0100 Subject: [PATCH 11/23] Refactor GetManifest to VersionChecker --- Design/Rule0011AccessPropertyShouldAlwaysBeSet.cs | 6 ++---- Design/Rule0031RecordInstanceIsolationLevel.cs | 7 ++----- Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs | 7 ++----- Design/Rule0035ExplicitSetAllowInCustomizations.cs | 7 ++----- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Design/Rule0011AccessPropertyShouldAlwaysBeSet.cs b/Design/Rule0011AccessPropertyShouldAlwaysBeSet.cs index 7fb2ab63..f19a0736 100644 --- a/Design/Rule0011AccessPropertyShouldAlwaysBeSet.cs +++ b/Design/Rule0011AccessPropertyShouldAlwaysBeSet.cs @@ -1,5 +1,4 @@ -using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; -using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using BusinessCentral.LinterCop.Helpers; @@ -16,8 +15,7 @@ public override void Initialize(AnalysisContext context) private void CheckForMissingAccessProperty(SymbolAnalysisContext context) { - var manifest = AppSourceCopConfigurationProvider.GetManifest(context.Compilation); - if (manifest.Runtime < RuntimeVersion.Spring2021 && (context.Symbol.Kind == SymbolKind.Enum || context.Symbol.Kind == SymbolKind.Interface)) + if (!VersionChecker.IsSupported(context.Symbol, VersionCompatibility.Spring2021OrGreater) && (context.Symbol.Kind == SymbolKind.Enum || context.Symbol.Kind == SymbolKind.Interface)) return; if (context.Symbol.IsObsoletePending || context.Symbol.IsObsoleteRemoved) return; diff --git a/Design/Rule0031RecordInstanceIsolationLevel.cs b/Design/Rule0031RecordInstanceIsolationLevel.cs index 9d2099e5..fa14bb89 100644 --- a/Design/Rule0031RecordInstanceIsolationLevel.cs +++ b/Design/Rule0031RecordInstanceIsolationLevel.cs @@ -1,4 +1,3 @@ -using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using System.Collections.Immutable; @@ -14,6 +13,8 @@ public class Rule0031RecordInstanceIsolationLevel : DiagnosticAnalyzer private void CheckLockTable(OperationAnalysisContext ctx) { + if (!VersionChecker.IsSupported(ctx.ContainingSymbol, VersionCompatibility.Spring2023OrGreater)) return; + if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return; @@ -22,10 +23,6 @@ private void CheckLockTable(OperationAnalysisContext ctx) if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "LockTable")) return; - // ReadIsolation is supported from runtime versions 11.0 or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); - if (manifest.Runtime < RuntimeVersion.Spring2023) return; - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0031RecordInstanceIsolationLevel, ctx.Operation.Syntax.GetLocation())); } } diff --git a/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs b/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs index 81253a06..99a70041 100644 --- a/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs +++ b/Design/Rule0034ExtensiblePropertyShouldAlwaysBeSet.cs @@ -1,4 +1,3 @@ -using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; @@ -20,6 +19,8 @@ public override void Initialize(AnalysisContext context) private void CheckForMissingExtensibleProperty(SymbolAnalysisContext ctx) { + if (!VersionChecker.IsSupported(ctx.Symbol, VersionCompatibility.Fall2019OrGreater)) return; + if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; if (ctx.Symbol.GetTypeSymbol().Kind == SymbolKind.Table && ctx.Symbol.DeclaredAccessibility != Accessibility.Public) return; @@ -27,10 +28,6 @@ private void CheckForMissingExtensibleProperty(SymbolAnalysisContext ctx) if (ctx.Symbol.GetProperty(PropertyKind.Extensible) != null) return; - // The Extensible property (and DeclaredAccessibility) is supported from runtime versions 4.0 or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); - if (manifest.Runtime < RuntimeVersion.Fall2019) return; - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0034ExtensiblePropertyShouldAlwaysBeSet, ctx.Symbol.GetLocation(), new object[] { Accessibility.Public.ToString().ToLower() })); } } diff --git a/Design/Rule0035ExplicitSetAllowInCustomizations.cs b/Design/Rule0035ExplicitSetAllowInCustomizations.cs index 047049a0..a08a8d8f 100644 --- a/Design/Rule0035ExplicitSetAllowInCustomizations.cs +++ b/Design/Rule0035ExplicitSetAllowInCustomizations.cs @@ -1,4 +1,3 @@ -using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; @@ -20,13 +19,11 @@ public override void Initialize(AnalysisContext context) private void AnalyzeAllowInCustomization(SymbolAnalysisContext ctx) { + if (!VersionChecker.IsSupported(ctx.Symbol, Feature.AddPageControlInPageCustomization)) return; + if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return; if (ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.Symbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return; - // The AllowInCustomizations property is supported from runtime versions 12.0 or greater. - var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); - if (manifest.Runtime < RuntimeVersion.Fall2023) return; - ICollection tableFields = GetTableFields(ctx.Symbol).Where(x => x.Id > 0 && x.Id < 2000000000) .Where(x => x.GetBooleanPropertyValue(PropertyKind.Enabled) != false) .Where(x => x.GetProperty(PropertyKind.AllowInCustomizations) is null) From 175d7de2a9b9d71d508d783117a268a6165f8a9d Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Tue, 28 Nov 2023 10:06:42 +0100 Subject: [PATCH 12/23] Resolve InvalidCastException on pageextension --- Design/Rule0016CheckForMissingCaptions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Design/Rule0016CheckForMissingCaptions.cs b/Design/Rule0016CheckForMissingCaptions.cs index debdcbb9..d005fb06 100644 --- a/Design/Rule0016CheckForMissingCaptions.cs +++ b/Design/Rule0016CheckForMissingCaptions.cs @@ -126,6 +126,7 @@ private bool CaptionIsMissing(ISymbol Symbol, SymbolAnalysisContext context) private static bool SuppressCaptionWarning(SymbolAnalysisContext context) { + if (context.Symbol.GetContainingObjectTypeSymbol().GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return false; IPageTypeSymbol pageTypeSymbol = (IPageTypeSymbol)context.Symbol.GetContainingObjectTypeSymbol(); if (pageTypeSymbol.GetNavTypeKindSafe() != NavTypeKind.Page || pageTypeSymbol.PageType != PageTypeKind.API) return false; LinterSettings.Create(context.Compilation.FileSystem.GetDirectoryPath()); From 6af0e233a644e04b2e61d2b01283944cd2d08916 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Tue, 28 Nov 2023 10:53:23 +0100 Subject: [PATCH 13/23] Divide rules of ToolTips --- Design/Rule0026ToolTipPunctuation.cs | 14 ++++---- LinterCop.ruleset.json | 17 ++++++++- LinterCopAnalyzers.Generated.cs | 6 ++-- LinterCopAnalyzers.resx | 54 ++++++++++++++-------------- README.md | 6 +++- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/Design/Rule0026ToolTipPunctuation.cs b/Design/Rule0026ToolTipPunctuation.cs index e4db7124..818e1080 100644 --- a/Design/Rule0026ToolTipPunctuation.cs +++ b/Design/Rule0026ToolTipPunctuation.cs @@ -9,7 +9,7 @@ namespace BusinessCentral.LinterCop.Design [DiagnosticAnalyzer] public class Rule0026ToolTipPunctuation : DiagnosticAnalyzer { - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0026ToolTipMustEndWithDot, DiagnosticDescriptors.Rule0026ToolTipMaximumLength, DiagnosticDescriptors.Rule0026ToolTipDoNotUseLineBreaks, DiagnosticDescriptors.Rule0026ToolTipShouldStartWithSpecifies); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0026ToolTipMustEndWithDot, DiagnosticDescriptors.Rule0036ToolTipShouldStartWithSpecifies, DiagnosticDescriptors.Rule0037ToolTipDoNotUseLineBreaks, DiagnosticDescriptors.Rule0038ToolTipMaximumLength); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(new Action(this.AnalyzeToolTipPunctuation), SyntaxKind.PageField, SyntaxKind.PageAction); @@ -25,15 +25,15 @@ private void AnalyzeToolTipPunctuation(SyntaxNodeAnalysisContext ctx) if (!tooltipLabel.Value.ToString().EndsWith(".'")) ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipMustEndWithDot, tooltipProperty.GetLocation())); - if (tooltipLabel.Value.ToString().Count() > 200) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipMaximumLength, tooltipProperty.GetLocation())); + if (ctx.ContainingSymbol.Kind == SymbolKind.Control && ((IControlSymbol)ctx.ContainingSymbol).ControlKind == ControlKind.Field) + if (!tooltipLabel.Value.ToString().StartsWith("'Specifies")) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0036ToolTipShouldStartWithSpecifies, tooltipProperty.GetLocation())); if (tooltipLabel.Value.ToString().Contains("\\")) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipDoNotUseLineBreaks, tooltipProperty.GetLocation())); + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0037ToolTipDoNotUseLineBreaks, tooltipProperty.GetLocation())); - if (ctx.ContainingSymbol.Kind == SymbolKind.Control && ((IControlSymbol)ctx.ContainingSymbol).ControlKind == ControlKind.Field) - if (!tooltipLabel.Value.ToString().StartsWith("'Specifies")) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0026ToolTipShouldStartWithSpecifies, tooltipProperty.GetLocation())); + if (tooltipLabel.Value.ToString().Length > 202) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0038ToolTipMaximumLength, tooltipProperty.GetLocation())); } } } \ No newline at end of file diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index 22e28e69..b7f1f6b6 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -130,7 +130,7 @@ { "id": "LC0026", "action": "Info", - "justification": "Guidelines for ToolTip text." + "justification": "ToolTip must end with a dot." }, { "id": "LC0027", @@ -176,6 +176,21 @@ "id": "LC0035", "action": "Info", "justification": "Explicitly set AllowInCustomizations for fields omitted on pages." + }, + { + "id": "LC0036", + "action": "Info", + "justification": "For fields specifically, the ToolTip must start with the verb Specifies." + }, + { + "id": "LC0037", + "action": "Info", + "justification": "Do not use line breaks in ToolTip." + }, + { + "id": "LC0038", + "action": "Info", + "justification": "Try to not exceed 200 characters (including spaces)." } ] } \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index f95bc87a..dfa6588d 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -33,9 +33,6 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0024SemicolonAfterProcedureDeclaration = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0024", (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0024SemicolonAfterProcedureDeclarationDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0024"); public static readonly DiagnosticDescriptor Rule0025InternalProcedureModifier = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0025", (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Hidden, true, (LocalizableString)new LocalizableResourceString("Rule0025InternalProcedureModifierDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0025"); public static readonly DiagnosticDescriptor Rule0026ToolTipMustEndWithDot = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMustEndWithDotDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); - public static readonly DiagnosticDescriptor Rule0026ToolTipMaximumLength = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipMaximumLengthDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); - public static readonly DiagnosticDescriptor Rule0026ToolTipDoNotUseLineBreaks = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipDoNotUseLineBreaksDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); - public static readonly DiagnosticDescriptor Rule0026ToolTipShouldStartWithSpecifies = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0026", (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0026ToolTipShouldStartWithSpecifiesDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0026"); public static readonly DiagnosticDescriptor Rule0027RunPageImplementPageManagement = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0027", (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0027RunPageImplementPageManagement", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0027"); public static readonly DiagnosticDescriptor Rule0028IdentifiersInEventSubscribers = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0028", (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0028IdentifiersInEventSubscribersDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028"); public static readonly DiagnosticDescriptor Rule0029CompareDateTimeThroughCodeunit = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0029", (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0029"); @@ -45,5 +42,8 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0033AppManifestRuntimeBehind = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0033", (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0033AppManifestRuntimeBehindTitleDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0033"); public static readonly DiagnosticDescriptor Rule0034ExtensiblePropertyShouldAlwaysBeSet = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0034", (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Hidden, true, (LocalizableString)new LocalizableResourceString("Rule0034ExtensiblePropertyShouldAlwaysBeSetDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0034"); public static readonly DiagnosticDescriptor Rule0035ExplicitSetAllowInCustomizations = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0035", (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0035ExplicitSetAllowInCustomizationsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0035"); + public static readonly DiagnosticDescriptor Rule0036ToolTipShouldStartWithSpecifies = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0036", (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036"); + 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"); } } \ No newline at end of file diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 78b59bd7..2263c04c 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -345,33 +345,6 @@ ToolTip must end with a dot. - - Try to not exceed 200 characters including spaces. - - - Try to not exceed 200 characters including spaces. - - - Try to not exceed 200 characters including spaces. - - - Do not use line breaks in ToolTip. - - - Do not use line breaks in ToolTip. - - - Do not use line breaks in ToolTip. - - - For fields specifically, the ToolTip must start with the verb "Specifies". - - - For fields specifically, the ToolTip must start with the verb "Specifies". - - - For fields specifically, the ToolTip must start with the verb "Specifies". - Utilize the "Page Management" codeunit for launching page. @@ -447,4 +420,31 @@ Explicitly set AllowInCustomizations for fields omitted on pages. + + For fields specifically, the ToolTip must start with the verb "Specifies". + + + For fields specifically, the ToolTip must start with the verb "Specifies". + + + For fields specifically, the ToolTip must start with the verb "Specifies". + + + Do not use line breaks in ToolTip. + + + Do not use line breaks in ToolTip. + + + Do not use line breaks in ToolTip. + + + Try to not exceed 200 characters (including spaces). + + + Try to not exceed 200 characters (including spaces). + + + Try to not exceed 200 characters (including spaces). + \ No newline at end of file diff --git a/README.md b/README.md index e1c3ec81..3f38db5f 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,11 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0032](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0032)|Clear(All) does not affect or change values for global variables in single instance codeunits.|Warning| |[LC0033](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0033)|The specified runtime version in app.json is falling behind.|Info| |[LC0034](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0034)|The property `Extensible` should be explicitly set for public objects.|Disabled| -|[LC0035](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0035)|Explicitly set `AllowInCustomizations` for fields omitted on pages.|Info| +|[LC0035](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|Explicitly set `AllowInCustomizations` for fields omitted on pages.|Info| +|[LC0036](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|For fields specifically, the ToolTip must start with the verb Specifies.|Info| +|[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| + ## Configuration From bc2c82649b0e528c126af5363eb97d03bfeab6c8 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 29 Nov 2023 09:06:57 +0100 Subject: [PATCH 14/23] New rule LC0039 --- Design/Rule0039PageRunTableMismatch.cs | 42 ++++++++++++++++++++++++++ LinterCop.ruleset.json | 5 +++ LinterCopAnalyzers.Generated.cs | 1 + LinterCopAnalyzers.resx | 9 ++++++ README.md | 1 + 5 files changed, 58 insertions(+) create mode 100644 Design/Rule0039PageRunTableMismatch.cs diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs new file mode 100644 index 00000000..0d8ab00d --- /dev/null +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -0,0 +1,42 @@ +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; +using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; +using System.Collections.Immutable; + +namespace BusinessCentral.LinterCop.Design +{ + [DiagnosticAnalyzer] + public class Rule0039PageRunTableMismatch : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected); + + public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action(this.AnalyzeRunPageArguments), OperationKind.InvocationExpression); + + private void AnalyzeRunPageArguments(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.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; + if (operation.Arguments.Count() < 2) return; + + if (operation.Arguments[0].Syntax.Kind != SyntaxKind.OptionAccessExpression) return; + if (operation.Arguments[1].Syntax.Kind != SyntaxKind.IdentifierName) return; + + IApplicationObjectTypeSymbol applicationObjectTypeSymbol = ((IApplicationObjectAccess)operation.Arguments[0].Value).ApplicationObjectTypeSymbol; + if (applicationObjectTypeSymbol.GetNavTypeKindSafe() != NavTypeKind.Page) return; + ITableTypeSymbol pageObjSourceTable = ((IPageTypeSymbol)applicationObjectTypeSymbol.GetTypeSymbol()).RelatedTable; + if (pageObjSourceTable == null) return; + + IOperation operand = ((IConversionExpression)operation.Arguments[1].Value).Operand; + ITableTypeSymbol recordArgument = ((IRecordTypeSymbol)operand.GetSymbol().GetTypeSymbol()).BaseTable; + + if (recordArgument != pageObjSourceTable) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 2, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageObjSourceTable.Name + "\"" })); + } + } +} \ No newline at end of file diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index b7f1f6b6..c63181d6 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -191,6 +191,11 @@ "id": "LC0038", "action": "Info", "justification": "Try to not exceed 200 characters (including spaces)." + }, + { + "id": "LC0039", + "action": "Warning", + "justification": "The given argument has a different type from the one expected." } ] } \ No newline at end of file diff --git a/LinterCopAnalyzers.Generated.cs b/LinterCopAnalyzers.Generated.cs index dfa6588d..997c842f 100644 --- a/LinterCopAnalyzers.Generated.cs +++ b/LinterCopAnalyzers.Generated.cs @@ -45,5 +45,6 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor Rule0036ToolTipShouldStartWithSpecifies = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0036", (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0036ToolTipShouldStartWithSpecifiesDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036"); 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"); } } \ No newline at end of file diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 2263c04c..989e26a7 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -447,4 +447,13 @@ Try to not exceed 200 characters (including spaces). + + Argument {0}: cannot convert from '{1}' to '{2}'. + + + Argument {0}: cannot convert from '{1}' to '{2}'. + + + Argument {0}: cannot convert from '{1}' to '{2}'. + \ No newline at end of file diff --git a/README.md b/README.md index 3f38db5f..6e42b015 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0036](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|For fields specifically, the ToolTip must start with the verb Specifies.|Info| |[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| ## Configuration From 220801c3b6802d57aba5d6069c35aecafc292ae6 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 29 Nov 2023 11:55:08 +0100 Subject: [PATCH 15/23] Extend rule 0039 with SetRecord() --- Design/Rule0039PageRunTableMismatch.cs | 44 +++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs index 0d8ab00d..7ff972e5 100644 --- a/Design/Rule0039PageRunTableMismatch.cs +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -3,6 +3,7 @@ using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; using System.Collections.Immutable; +using System.Reflection; namespace BusinessCentral.LinterCop.Design { @@ -11,7 +12,11 @@ public class Rule0039PageRunTableMismatch : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected); - public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action(this.AnalyzeRunPageArguments), OperationKind.InvocationExpression); + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationAction(new Action(this.AnalyzeRunPageArguments), OperationKind.InvocationExpression); + context.RegisterOperationAction(new Action(this.AnalyzeSetRecordArgument), OperationKind.InvocationExpression); + } private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) { @@ -22,6 +27,7 @@ private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return; if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; + if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "Run") && !SemanticFacts.IsSameName(operation.TargetMethod.Name, "RunModal")) return; if (operation.Arguments.Count() < 2) return; if (operation.Arguments[0].Syntax.Kind != SyntaxKind.OptionAccessExpression) return; @@ -29,14 +35,42 @@ private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) IApplicationObjectTypeSymbol applicationObjectTypeSymbol = ((IApplicationObjectAccess)operation.Arguments[0].Value).ApplicationObjectTypeSymbol; if (applicationObjectTypeSymbol.GetNavTypeKindSafe() != NavTypeKind.Page) return; - ITableTypeSymbol pageObjSourceTable = ((IPageTypeSymbol)applicationObjectTypeSymbol.GetTypeSymbol()).RelatedTable; - if (pageObjSourceTable == null) return; + ITableTypeSymbol pageSourceTable = ((IPageTypeSymbol)applicationObjectTypeSymbol.GetTypeSymbol()).RelatedTable; + if (pageSourceTable == null) return; IOperation operand = ((IConversionExpression)operation.Arguments[1].Value).Operand; ITableTypeSymbol recordArgument = ((IRecordTypeSymbol)operand.GetSymbol().GetTypeSymbol()).BaseTable; - if (recordArgument != pageObjSourceTable) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 2, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageObjSourceTable.Name + "\"" })); + if (recordArgument != pageSourceTable) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 2, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageSourceTable.Name + "\"" })); + } + + private void AnalyzeSetRecordArgument(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.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; + if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "SetRecord")) return; + if (operation.Arguments.Count() != 1) return; + + if (operation.Arguments[0].Syntax.Kind != SyntaxKind.IdentifierName) return; + + IOperation pageReference = ctx.Operation.DescendantsAndSelf().Where(x => x.GetSymbol() != null) + .Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.Page) + .First(); + IVariableSymbol variableSymbol = (IVariableSymbol)pageReference.GetSymbol().OriginalDefinition; + IPageTypeSymbol pageTypeSymbol = (IPageTypeSymbol)variableSymbol.GetTypeSymbol().OriginalDefinition; + ITableTypeSymbol pageSourceTable = pageTypeSymbol.RelatedTable; + + IOperation operand = ((IConversionExpression)operation.Arguments[0].Value).Operand; + ITableTypeSymbol recordArgument = ((IRecordTypeSymbol)operand.GetSymbol().GetTypeSymbol()).BaseTable; + + if (recordArgument != pageSourceTable) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 1, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageSourceTable.Name + "\"" })); } } } \ No newline at end of file From 391c840490893a04ef23aa48a13a6ccdb17c441f Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 29 Nov 2023 12:10:42 +0100 Subject: [PATCH 16/23] Resolve InvalidCastException --- Design/Rule0039PageRunTableMismatch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs index 7ff972e5..8a66aa08 100644 --- a/Design/Rule0039PageRunTableMismatch.cs +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -58,6 +58,7 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) if (operation.Arguments.Count() != 1) return; if (operation.Arguments[0].Syntax.Kind != SyntaxKind.IdentifierName) return; + if (operation.Arguments[0].Value.Kind != OperationKind.ConversionExpression) return; IOperation pageReference = ctx.Operation.DescendantsAndSelf().Where(x => x.GetSymbol() != null) .Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.Page) From afdee002f5f3caa6e337aa1b8ba27f007fe22ae7 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Wed, 29 Nov 2023 12:41:39 +0100 Subject: [PATCH 17/23] Resolve false positieve on Qualified Names --- Design/Rule0012DoNotUseObjectIdInSystemFunctions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Design/Rule0012DoNotUseObjectIdInSystemFunctions.cs b/Design/Rule0012DoNotUseObjectIdInSystemFunctions.cs index 33bd2041..9c0fb908 100644 --- a/Design/Rule0012DoNotUseObjectIdInSystemFunctions.cs +++ b/Design/Rule0012DoNotUseObjectIdInSystemFunctions.cs @@ -47,7 +47,7 @@ private void CheckForObjectIdsInFunctionInvocations(OperationAnalysisContext con catch (System.InvalidOperationException) { } - SyntaxKind[] AllowedParameterKinds = { SyntaxKind.MemberAccessExpression, SyntaxKind.IdentifierName, SyntaxKind.InvocationExpression }; + SyntaxKind[] AllowedParameterKinds = { SyntaxKind.MemberAccessExpression, SyntaxKind.IdentifierName, SyntaxKind.InvocationExpression, SyntaxKind.QualifiedName }; if (CurrentFunction != null && operation.TargetMethod.Parameters.Length != 0 && !AllowedParameterKinds.Contains(operation.Arguments[0].Syntax.Kind) && (operation.Arguments[0].Syntax.ToString() != "0" || !CurrentFunction.ZeroIDAllowed)) { From c63a9ba065ca62773e8e3bd5fa71b597f11aa614 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Thu, 30 Nov 2023 09:01:26 +0100 Subject: [PATCH 18/23] Resolve false positive with use of namespace --- Design/Rule0003DoNotUseObjectIDsInVariablesOrProperties.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Design/Rule0003DoNotUseObjectIDsInVariablesOrProperties.cs b/Design/Rule0003DoNotUseObjectIDsInVariablesOrProperties.cs index 764be2f8..156fc2a7 100644 --- a/Design/Rule0003DoNotUseObjectIDsInVariablesOrProperties.cs +++ b/Design/Rule0003DoNotUseObjectIDsInVariablesOrProperties.cs @@ -111,10 +111,10 @@ private void CheckForObjectIDsInVariablesOrProperties(SyntaxNodeAnalysisContext { correctName = returnValue.ReturnType.Name; - if (ctx.Node.ToString().Trim('"').ToUpper() != correctName.ToUpper()) + if (ctx.Node.GetLastToken().ToString().Trim('"').ToUpper() != correctName.ToUpper()) ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0003DoNotUseObjectIDsInVariablesOrProperties, ctx.Node.GetLocation(), new object[] { ctx.Node.ToString().Trim('"'), correctName })); - if (ctx.Node.ToString().Trim('"') != correctName) + if (ctx.Node.GetLastToken().ToString().Trim('"') != correctName) ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, ctx.Node.GetLocation(), new object[] { correctName, "" })); } } From b90b846775d37c78b41ef9eef33b4aeebf16fcc5 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Thu, 30 Nov 2023 15:10:29 +0100 Subject: [PATCH 19/23] Improve comparisation between objects --- Design/Rule0039PageRunTableMismatch.cs | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs index 8a66aa08..2a01d8d6 100644 --- a/Design/Rule0039PageRunTableMismatch.cs +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -1,9 +1,7 @@ using Microsoft.Dynamics.Nav.CodeAnalysis; using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols; -using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; using System.Collections.Immutable; -using System.Reflection; namespace BusinessCentral.LinterCop.Design { @@ -27,11 +25,12 @@ private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return; if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; - if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "Run") && !SemanticFacts.IsSameName(operation.TargetMethod.Name, "RunModal")) return; + string[] procedureNames = ["Run", "RunModal"]; + if (!procedureNames.Contains(operation.TargetMethod.Name)) return; if (operation.Arguments.Count() < 2) return; if (operation.Arguments[0].Syntax.Kind != SyntaxKind.OptionAccessExpression) return; - if (operation.Arguments[1].Syntax.Kind != SyntaxKind.IdentifierName) return; + if (operation.Arguments[1].Syntax.Kind != SyntaxKind.IdentifierName || operation.Arguments[1].Value.Kind != OperationKind.ConversionExpression) return; IApplicationObjectTypeSymbol applicationObjectTypeSymbol = ((IApplicationObjectAccess)operation.Arguments[0].Value).ApplicationObjectTypeSymbol; if (applicationObjectTypeSymbol.GetNavTypeKindSafe() != NavTypeKind.Page) return; @@ -41,8 +40,8 @@ private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) IOperation operand = ((IConversionExpression)operation.Arguments[1].Value).Operand; ITableTypeSymbol recordArgument = ((IRecordTypeSymbol)operand.GetSymbol().GetTypeSymbol()).BaseTable; - if (recordArgument != pageSourceTable) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 2, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageSourceTable.Name + "\"" })); + if (!AreTheSameNavObjects(recordArgument, pageSourceTable)) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 2, operand.GetSymbol().GetTypeSymbol().ToString(), pageSourceTable.GetNavTypeKindSafe() + " \"" + pageSourceTable.Name + "\"" })); } private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) @@ -54,11 +53,11 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return; if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; - if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "SetRecord")) return; + string[] procedureNames = ["GetRecord", "SetRecord", "SetSelectionFilter", "SetTableView"]; + if (!procedureNames.Contains(operation.TargetMethod.Name)) return; if (operation.Arguments.Count() != 1) return; - if (operation.Arguments[0].Syntax.Kind != SyntaxKind.IdentifierName) return; - if (operation.Arguments[0].Value.Kind != OperationKind.ConversionExpression) return; + if (operation.Arguments[0].Syntax.Kind != SyntaxKind.IdentifierName || operation.Arguments[0].Value.Kind != OperationKind.ConversionExpression) return; IOperation pageReference = ctx.Operation.DescendantsAndSelf().Where(x => x.GetSymbol() != null) .Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.Page) @@ -70,8 +69,16 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) IOperation operand = ((IConversionExpression)operation.Arguments[0].Value).Operand; ITableTypeSymbol recordArgument = ((IRecordTypeSymbol)operand.GetSymbol().GetTypeSymbol()).BaseTable; - if (recordArgument != pageSourceTable) - ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 1, operand.GetSymbol().GetTypeSymbol().ToString(), "Record \"" + pageSourceTable.Name + "\"" })); + if (!AreTheSameNavObjects(recordArgument, pageSourceTable)) + ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0039ArgumentDifferentTypeThenExpected, ctx.Operation.Syntax.GetLocation(), new object[] { 1, operand.GetSymbol().GetTypeSymbol().ToString(), pageSourceTable.GetNavTypeKindSafe() + " \"" + pageSourceTable.Name + "\"" })); + } + + private static bool AreTheSameNavObjects(ITableTypeSymbol left, ITableTypeSymbol right) + { + if (left.GetNavTypeKindSafe() != right.GetNavTypeKindSafe()) return false; + if (((INamespaceSymbol)left.ContainingSymbol).QualifiedName != ((INamespaceSymbol)right.ContainingSymbol).QualifiedName) return false; + if (left.Name != right.Name) return false; + return true; } } } \ No newline at end of file From 110de49804449a1baae4deea563f4a52f112b134 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Thu, 30 Nov 2023 15:16:56 +0100 Subject: [PATCH 20/23] Resolve build error: The feature 'collection literals' is currently in Preview and *unsupported*. --- Design/Rule0039PageRunTableMismatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs index 2a01d8d6..573aca22 100644 --- a/Design/Rule0039PageRunTableMismatch.cs +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -25,7 +25,7 @@ private void AnalyzeRunPageArguments(OperationAnalysisContext ctx) if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return; if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; - string[] procedureNames = ["Run", "RunModal"]; + string[] procedureNames = { "Run", "RunModal" }; if (!procedureNames.Contains(operation.TargetMethod.Name)) return; if (operation.Arguments.Count() < 2) return; @@ -53,7 +53,7 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return; if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return; - string[] procedureNames = ["GetRecord", "SetRecord", "SetSelectionFilter", "SetTableView"]; + string[] procedureNames = { "GetRecord", "SetRecord", "SetSelectionFilter", "SetTableView" }; if (!procedureNames.Contains(operation.TargetMethod.Name)) return; if (operation.Arguments.Count() != 1) return; From 5486dc3d2922e9dec01a21b7f5322c195d5faa5f Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Fri, 1 Dec 2023 12:46:03 +0100 Subject: [PATCH 21/23] Resolve exception InvalidOperationException --- Design/Rule0039PageRunTableMismatch.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Design/Rule0039PageRunTableMismatch.cs b/Design/Rule0039PageRunTableMismatch.cs index 573aca22..31aebd0a 100644 --- a/Design/Rule0039PageRunTableMismatch.cs +++ b/Design/Rule0039PageRunTableMismatch.cs @@ -61,7 +61,8 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx) IOperation pageReference = ctx.Operation.DescendantsAndSelf().Where(x => x.GetSymbol() != null) .Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.Page) - .First(); + .SingleOrDefault(); + if (pageReference == null) return; IVariableSymbol variableSymbol = (IVariableSymbol)pageReference.GetSymbol().OriginalDefinition; IPageTypeSymbol pageTypeSymbol = (IPageTypeSymbol)variableSymbol.GetTypeSymbol().OriginalDefinition; ITableTypeSymbol pageSourceTable = pageTypeSymbol.RelatedTable; From 28270e17af58945da43ce9ad56581ec6699cd611 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 3 Dec 2023 16:21:02 +0100 Subject: [PATCH 22/23] Better description for rule0036 --- LinterCop.ruleset.json | 2 +- LinterCopAnalyzers.resx | 6 +++--- README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LinterCop.ruleset.json b/LinterCop.ruleset.json index c63181d6..ab409fc9 100644 --- a/LinterCop.ruleset.json +++ b/LinterCop.ruleset.json @@ -180,7 +180,7 @@ { "id": "LC0036", "action": "Info", - "justification": "For fields specifically, the ToolTip must start with the verb Specifies." + "justification": "ToolTip must start with the verb Specifies." }, { "id": "LC0037", diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index 989e26a7..f815d6a6 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -421,13 +421,13 @@ Explicitly set AllowInCustomizations for fields omitted on pages. - For fields specifically, the ToolTip must start with the verb "Specifies". + ToolTip must start with the verb "Specifies". - For fields specifically, the ToolTip must start with the verb "Specifies". + ToolTip must start with the verb "Specifies". - For fields specifically, the ToolTip must start with the verb "Specifies". + ToolTip must start with the verb "Specifies". Do not use line breaks in ToolTip. diff --git a/README.md b/README.md index 6e42b015..7de633c9 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Further note that you should have BcContainerHelper version 2.0.16 (or newer) in |[LC0033](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0033)|The specified runtime version in app.json is falling behind.|Info| |[LC0034](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0034)|The property `Extensible` should be explicitly set for public objects.|Disabled| |[LC0035](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|Explicitly set `AllowInCustomizations` for fields omitted on pages.|Info| -|[LC0036](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|For fields specifically, the ToolTip must start with the verb Specifies.|Info| +|[LC0036](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0036)|ToolTip must start with the verb "Specifies".|Info| |[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| From 2204dd16bbbf7a5b499ce9917ac57c25728e6a50 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Mon, 4 Dec 2023 09:29:09 +0100 Subject: [PATCH 23/23] More friendly description --- LinterCopAnalyzers.resx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LinterCopAnalyzers.resx b/LinterCopAnalyzers.resx index f815d6a6..a09a1f4e 100644 --- a/LinterCopAnalyzers.resx +++ b/LinterCopAnalyzers.resx @@ -394,13 +394,13 @@ Clear(All) does not affect or change values for global variables in '{0}: Codeunit {1}'. - The specified runtime version in app.json is falling behind. The project targets {0} version {1}, but the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. + The specified runtime version in app.json is falling behind. The project targets {0} version {1} and the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. - The specified runtime version in app.json is falling behind. The project targets {0} version {1}, but the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. + The specified runtime version in app.json is falling behind. The project targets {0} version {1} and the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. - The specified runtime version in app.json is falling behind. The project targets {0} version {1}, but the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. + The specified runtime version in app.json is falling behind. The project targets {0} version {1} and the current runtime is {2}. Update the runtime to {3} for compatibility with the latest runtime features. The property Extensible should be explicitly set for {0} objects.