Skip to content

Commit

Permalink
Merge pull request #540 from StefanMaron/prerelease
Browse files Browse the repository at this point in the history
Merge 'prerelease' into master
  • Loading branch information
Arthurvdv authored Feb 12, 2024
2 parents fe8dc2c + 1850183 commit 0b9c8fe
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 10 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 7.0.*
dotnet-version: 8.0.*
- name: Restore dependencies
run: dotnet restore
- name: GetALVsixVersionAndURL
Expand Down Expand Up @@ -96,7 +96,7 @@ jobs:
Remove-Item -Path "ms-dynamics-smb.al-latest" -Force -Recurse -Verbose;
7z x "ALLanguage_next.vsix" "-oms-dynamics-smb.al-latest" extension\bin\Analyzers -r;
- name: Build next
run: dotnet build --no-restore --configuration Release
run: dotnet build /p:DefineConstants=PreRelease --no-restore --configuration Release

- name: Upload a Build next
uses: actions/[email protected]
Expand Down
20 changes: 16 additions & 4 deletions Design/Rule0033AppManifestRuntimeBehind.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Packaging;
#if PreRelease
using Microsoft.Dynamics.Nav.Analyzers.Common; // AL Language v13
#else
using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; // AL Language v12
#endif
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
Expand All @@ -15,13 +19,21 @@ class Rule0033AppManifestRuntimeBehind : DiagnosticAnalyzer

private void CheckAppManifestRuntime(CompilationAnalysisContext ctx)
{
NavAppManifest manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation);
#if PreRelease
NavAppManifest manifest = ManifestHelper.GetManifest(ctx.Compilation); // AL Language v13
#else
NavAppManifest manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation); // AL Language v12
#endif

if (manifest == null) return;
if (manifest.Runtime == (Version)null) return;
if (manifest.Runtime == null) return;
if (manifest.Application == null && manifest.Platform == null) return;

GetTargetProperty(manifest, out string propertyName, out Version propertyVersion);

Version supportedRuntime = FindValueOfFirstValueLessThan(GetSupportedRuntimeVersions(), propertyVersion);
if (supportedRuntime == null) return;

if (manifest.Runtime < supportedRuntime)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0033AppManifestRuntimeBehind, manifest.GetDiagnosticLocation("runtime"), new object[] { propertyName, propertyVersion, manifest.Runtime, supportedRuntime }));
}
Expand Down Expand Up @@ -60,7 +72,7 @@ private static SortedList<Version, Version> GetSupportedRuntimeVersions()
private static Version FindValueOfFirstValueLessThan(SortedList<Version, Version> sortedList, Version version)
{
int index = FindIndexOfFirstValueLessThan(sortedList.Keys.ToList(), version);
return sortedList.ElementAt(index).Value;
return sortedList.ElementAtOrDefault(index).Value;
}

private static int FindIndexOfFirstValueLessThan<T>(List<T> sortedList, T value, IComparer<T> comparer = null)
Expand Down
2 changes: 1 addition & 1 deletion Design/Rule0039ArgumentDifferentTypeThenExpected.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private void AnalyzeSetRecordArgument(OperationAnalysisContext ctx)
.Where(x => x.Type.GetNavTypeKindSafe() == NavTypeKind.Page)
.SingleOrDefault();
if (pageReference == null) return;
IVariableSymbol variableSymbol = (IVariableSymbol)pageReference.GetSymbol().OriginalDefinition;
ISymbol variableSymbol = pageReference.GetSymbol().OriginalDefinition;
IPageTypeSymbol pageTypeSymbol = (IPageTypeSymbol)variableSymbol.GetTypeSymbol().OriginalDefinition;
if (pageTypeSymbol.RelatedTable == null)
{
Expand Down
244 changes: 244 additions & 0 deletions Design/Rule0052and0053InternalProceduresNotReferenced.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
using BusinessCentral.LinterCop.Helpers;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.InternalSyntax;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using Microsoft.Dynamics.Nav.CodeAnalysis.Packaging;
#if PreRelease
using Microsoft.Dynamics.Nav.Analyzers.Common; // AL Language v13
#else
using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration; // AL Language v12
#endif

using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0052InternalProceduresNotReferencedAnalyzer : DiagnosticAnalyzer
{

private class MethodSymbolAnalyzer : IDisposable
{
private readonly PooledDictionary<IMethodSymbol, string> methodSymbols = PooledDictionary<IMethodSymbol, string>.GetInstance();

private readonly PooledDictionary<IMethodSymbol, string> internalMethodsUnused = PooledDictionary<IMethodSymbol, string>.GetInstance();
private readonly PooledDictionary<IMethodSymbol, string> internalMethodsUsedInCurrentObject = PooledDictionary<IMethodSymbol, string>.GetInstance();
private readonly PooledDictionary<IMethodSymbol, string> internalMethodsUsedInOtherObjects = PooledDictionary<IMethodSymbol, string>.GetInstance();

private readonly AttributeKind[] attributeKindsOfMethodsToSkip = new AttributeKind[] { AttributeKind.ConfirmHandler, AttributeKind.FilterPageHandler, AttributeKind.HyperlinkHandler, AttributeKind.MessageHandler, AttributeKind.ModalPageHandler, AttributeKind.PageHandler, AttributeKind.RecallNotificationHandler, AttributeKind.ReportHandler, AttributeKind.RequestPageHandler, AttributeKind.SendNotificationHandler, AttributeKind.SessionSettingsHandler, AttributeKind.StrMenuHandler, AttributeKind.Test };

public MethodSymbolAnalyzer(CompilationAnalysisContext compilationAnalysisContext)
{
#if PreRelease
NavAppManifest manifest = ManifestHelper.GetManifest(compilationAnalysisContext.Compilation); // AL Language v13
#else
NavAppManifest manifest = AppSourceCopConfigurationProvider.GetManifest(compilationAnalysisContext.Compilation); // AL Language v12
#endif

if (manifest.InternalsVisibleTo != null && manifest.InternalsVisibleTo.Any())
{
return;
}

ImmutableArray<IApplicationObjectTypeSymbol>.Enumerator objectEnumerator = compilationAnalysisContext.Compilation.GetDeclaredApplicationObjectSymbols().GetEnumerator();
while (objectEnumerator.MoveNext())
{
IApplicationObjectTypeSymbol applicationSymbol = objectEnumerator.Current;
ImmutableArray<ISymbol>.Enumerator objectMemberEnumerator = applicationSymbol.GetMembers().GetEnumerator();
while (objectMemberEnumerator.MoveNext())
{
ISymbol objectMember = objectMemberEnumerator.Current;
if (objectMember.Kind == SymbolKind.Method)
{
IMethodSymbol methodSymbol = objectMember as IMethodSymbol;
if (MethodNeedsReferenceCheck(methodSymbol))
{
methodSymbols.Add(methodSymbol, methodSymbol.Name.ToLowerInvariant());
internalMethodsUnused.Add(methodSymbol, methodSymbol.Name.ToLowerInvariant());
}
}
}
}
}

private bool MethodNeedsReferenceCheck(IMethodSymbol methodSymbol)
{
if (methodSymbol.MethodKind != MethodKind.Method)
{
return false;
}
if (methodSymbol.IsObsoletePending)
{
return false;
}
if (methodSymbol.Attributes.Any(attr => attributeKindsOfMethodsToSkip.Contains(attr.AttributeKind)))
{
return false;
}
if (!methodSymbol.IsInternal)
{
// Check if public procedure in internal object
if (methodSymbol.DeclaredAccessibility == Accessibility.Public && methodSymbol.ContainingSymbol is IApplicationObjectTypeSymbol)
{
var objectSymbol = methodSymbol.GetContainingApplicationObjectTypeSymbol();

// If the containing object is not an internal object, then we do not need to check for references for this public procedure.
if (objectSymbol.DeclaredAccessibility != Accessibility.Internal)
{
return false;
}

if (HelperFunctions.MethodImplementsInterfaceMethod(objectSymbol, methodSymbol))
{
return false;
}
}
else
{
return false;
}
}

// If the procedure has signature ProcedureName(HostNotification: Notification) or ProcedureName(ErrorInfo: ErrorInfo), then the procedure does not need a reference check
if (methodSymbol.Parameters.Length == 1)
{
ITypeSymbol firstParameterTypeSymbol = methodSymbol.Parameters[0].ParameterType;
if (firstParameterTypeSymbol.GetNavTypeKindSafe() == NavTypeKind.Notification || firstParameterTypeSymbol.GetNavTypeKindSafe() == NavTypeKind.ErrorInfo)
{
return false;
}
}

return true;
}

public void AnalyzeObjectSyntax(CompilationAnalysisContext compilationAnalysisContext)
{
if (methodSymbols.Count == 0)
{
return;
}

Compilation compilation = compilationAnalysisContext.Compilation;
ImmutableArray<SyntaxTree>.Enumerator enumerator = compilation.SyntaxTrees.GetEnumerator();
while (enumerator.MoveNext())
{
if (methodSymbols.Count == 0)
{
break;
}

SyntaxTree syntaxTree = enumerator.Current;
SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);
syntaxTree.GetRoot().WalkDescendantsAndPerformAction(delegate (SyntaxNode syntaxNode)
{
if (methodSymbols.Count == 0)
{
return;
}
if (syntaxNode.Parent.IsKind(SyntaxKind.MethodDeclaration) || !syntaxNode.IsKind(SyntaxKind.IdentifierName))
{
return;
}
IdentifierNameSyntax identifierNameSyntax = (IdentifierNameSyntax)syntaxNode;
if (methodSymbols.ContainsValue(identifierNameSyntax.Identifier.ValueText.ToLowerInvariant()) && TryGetSymbolFromIdentifier(semanticModel, (IdentifierNameSyntax)syntaxNode, SymbolKind.Method, out var methodSymbol))
{
if (methodSymbol.IsInternal)
{
var objectSyntax = syntaxNode.GetContainingObjectSyntax();
var objectSyntaxName = objectSyntax.Name.Identifier.ValueText.ToLowerInvariant();

var methodObjectSymbol = methodSymbol.GetContainingApplicationObjectTypeSymbol();
var methodObjectSymbolName = methodObjectSymbol.Name.ToLowerInvariant();

if (
(methodObjectSymbolName == objectSyntaxName) &&
(objectSyntax.Kind.ToString().Replace("Object", "").ToLowerInvariant() == methodObjectSymbol.Kind.ToString().ToLowerInvariant())
)
{
internalMethodsUsedInCurrentObject[methodSymbol] = methodSymbol.Name.ToLowerInvariant();
}
else
{
internalMethodsUsedInOtherObjects[methodSymbol] = methodSymbol.Name.ToLowerInvariant();
}
}

internalMethodsUnused.Remove(methodSymbol);
}
});
}
}

internal static bool TryGetSymbolFromIdentifier(SemanticModel semanticModel, IdentifierNameSyntax identifierName, SymbolKind symbolKind, out IMethodSymbol methodSymbol)
{
methodSymbol = null;
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(identifierName);
ISymbol symbol = symbolInfo.Symbol;
if (symbol == null || symbol.Kind != symbolKind)
{
return false;
}
methodSymbol = symbolInfo.Symbol as IMethodSymbol;
if (methodSymbol == null)
{
return false;
}
return true;
}

public void ReportUnchangedReferencePassedParameters(Action<Diagnostic> action)
{
if (internalMethodsUnused.Count == 0)
{
return;
}
foreach (KeyValuePair<IMethodSymbol, string> unusedInternalMethod in internalMethodsUnused)
{
IMethodSymbol methodSymbol = unusedInternalMethod.Key;
IApplicationObjectTypeSymbol objectSymbol = methodSymbol.GetContainingApplicationObjectTypeSymbol();

Diagnostic diagnostic = Diagnostic.Create(DiagnosticDescriptors.Rule0052InternalProceduresNotReferencedAnalyzerDescriptor, methodSymbol.OriginalDefinition.GetLocation(), methodSymbol.DeclaredAccessibility.ToString().ToLowerInvariant(), methodSymbol.Name, objectSymbol.NavTypeKind, objectSymbol.Name, objectSymbol.DeclaredAccessibility);
action(diagnostic);
}
}

public void ReportInternalMethodOnlyReferencedInCurrentObject(Action<Diagnostic> action)
{
var internalMethodsUsedOnlyInCurrentObject = internalMethodsUsedInCurrentObject.Except(internalMethodsUsedInOtherObjects);

foreach (KeyValuePair<IMethodSymbol, string> internalMethodPair in internalMethodsUsedOnlyInCurrentObject)
{
IMethodSymbol methodSymbol = internalMethodPair.Key;
IApplicationObjectTypeSymbol objectSymbol = methodSymbol.GetContainingApplicationObjectTypeSymbol();

Diagnostic diagnostic = Diagnostic.Create(DiagnosticDescriptors.Rule0053InternalProcedureOnlyUsedInCurrentObjectAnalyzerDescriptor, methodSymbol.OriginalDefinition.GetLocation(), methodSymbol.DeclaredAccessibility.ToString().ToLowerInvariant(), methodSymbol.Name, objectSymbol.NavTypeKind, objectSymbol.Name, objectSymbol.DeclaredAccessibility);
action(diagnostic);
}
}

public void Dispose()
{
methodSymbols.Free();
}
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0052InternalProceduresNotReferencedAnalyzerDescriptor, DiagnosticDescriptors.Rule0053InternalProcedureOnlyUsedInCurrentObjectAnalyzerDescriptor);


public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationAction(CheckApplicationObjects);
}

private static void CheckApplicationObjects(CompilationAnalysisContext compilationAnalysisContext)
{
MethodSymbolAnalyzer methodSymbolAnalyzer = new MethodSymbolAnalyzer(compilationAnalysisContext);
methodSymbolAnalyzer.AnalyzeObjectSyntax(compilationAnalysisContext);
methodSymbolAnalyzer.ReportUnchangedReferencePassedParameters(compilationAnalysisContext.ReportDiagnostic);
methodSymbolAnalyzer.ReportInternalMethodOnlyReferencedInCurrentObject(compilationAnalysisContext.ReportDiagnostic);
}
}
}
62 changes: 62 additions & 0 deletions Helpers/HelperFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;

namespace BusinessCentral.LinterCop.Helpers {
public class HelperFunctions {
public static bool MethodImplementsInterfaceMethod(IMethodSymbol methodSymbol)
{
return MethodImplementsInterfaceMethod(methodSymbol.GetContainingApplicationObjectTypeSymbol(), methodSymbol);
}

public static bool MethodImplementsInterfaceMethod(IApplicationObjectTypeSymbol objectSymbol, IMethodSymbol methodSymbol)
{
if (!(objectSymbol is ICodeunitTypeSymbol))
{
return false;
}

var codeunitSymbol = objectSymbol as ICodeunitTypeSymbol;
foreach (var implementedInterface in codeunitSymbol.ImplementedInterfaces)
{
if (implementedInterface.GetMembers().OfType<IMethodSymbol>().Any(interfaceMethodSymbol => MethodImplementsInterfaceMethod(methodSymbol, interfaceMethodSymbol)))
{
return true;
}
}

return false;
}

public static bool MethodImplementsInterfaceMethod(IMethodSymbol methodSymbol, IMethodSymbol interfaceMethodSymbol)
{
if (methodSymbol.Name != interfaceMethodSymbol.Name)
{
return false;
}
if (methodSymbol.Parameters.Length != interfaceMethodSymbol.Parameters.Length)
{
return false;
}
var methodReturnValType = methodSymbol.ReturnValueSymbol?.ReturnType.NavTypeKind ?? NavTypeKind.None;
var interfaceMethodReturnValType = interfaceMethodSymbol.ReturnValueSymbol?.ReturnType.NavTypeKind ?? NavTypeKind.None;
if (methodReturnValType != interfaceMethodReturnValType)
{
return false;
}
for (int i = 0; i < methodSymbol.Parameters.Length; i++)
{
var methodParameter = methodSymbol.Parameters[i];
var interfaceMethodParameter = interfaceMethodSymbol.Parameters[i];

if (methodParameter.IsVar != interfaceMethodParameter.IsVar)
{
return false;
}
if (!methodParameter.ParameterType.Equals(interfaceMethodParameter.ParameterType))
{
return false;
}
}
return true;
}
}
}
10 changes: 10 additions & 0 deletions LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@
"id": "LC0051",
"action": "Warning",
"justification": "Do not assign a text to a target with smaller size."
},
{
"id": "LC0052",
"action": "Info",
"justification": "The internal procedure is declared but never used."
},
{
"id": "LC0053",
"action": "Info",
"justification": "The internal procedure is only used in the object in which it is declared. Consider making the procedure local."
}
]
}
Loading

0 comments on commit 0b9c8fe

Please sign in to comment.