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] 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