Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Various small improvements #759

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions Philips.CodeAnalysis.Common/AdditionalFilesHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,12 @@ private string GetRawValue(string settingKey)
SyntaxTree tree = _compilation.SyntaxTrees.First();
AnalyzerConfigOptions analyzerConfigOptions = _options.AnalyzerConfigOptionsProvider.GetOptions(tree);

#nullable enable
if (analyzerConfigOptions.TryGetValue(settingKey, out var value))
{
return value == null ? string.Empty : value.ToString();
return value.ToString();
}

return string.Empty;
#nullable disable
}

public virtual string GetValueFromEditorConfig(string diagnosticId, string settingKey)
Expand Down
5 changes: 4 additions & 1 deletion Philips.CodeAnalysis.Common/CodeFixHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public CodeFixHelper()
ForConstructors = new ConstructorSyntaxHelper();
ForGeneratedCode = new GeneratedCodeDetector(this);
ForLiterals = new LiteralHelper();
ForModifiers = new ModifiersHelper();
ForNamespaces = new NamespacesHelper();
ForTests = new TestHelper(this);
ForTypes = new TypesHelper();
Expand All @@ -28,6 +29,8 @@ public CodeFixHelper()

public LiteralHelper ForLiterals { get; }

public ModifiersHelper ForModifiers { get; }

public NamespacesHelper ForNamespaces { get; }

public TestHelper ForTests { get; }
Expand All @@ -36,7 +39,7 @@ public CodeFixHelper()

public DocumentationHelper ForDocumentationOf(SyntaxNode node)
{
return new DocumentationHelper(node);
return new DocumentationHelper(this, node);
}
}
}
15 changes: 2 additions & 13 deletions Philips.CodeAnalysis.Common/DocumentationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,12 @@ public static SyntaxNode FindAncestorThatCanHaveDocumentation(SyntaxNode node)
return node.AncestorsAndSelf().FirstOrDefault(ancestor => ancestor is BaseMethodDeclarationSyntax or PropertyDeclarationSyntax or TypeDeclarationSyntax);
}

internal DocumentationHelper(SyntaxNode node)
internal DocumentationHelper(CodeFixHelper helper, SyntaxNode node)
{
SyntaxTrivia doc = node.GetLeadingTrivia().FirstOrDefault(IsCommentTrivia);
if (doc == default)
{
if (node is MethodDeclarationSyntax method)
{
doc = method.Modifiers[0].LeadingTrivia.FirstOrDefault(IsCommentTrivia);
}
else if (node is PropertyDeclarationSyntax prop)
{
doc = prop.Modifiers[0].LeadingTrivia.FirstOrDefault(IsCommentTrivia);
}
else if (node is TypeDeclarationSyntax type)
{
doc = type.Modifiers[0].LeadingTrivia.FirstOrDefault(IsCommentTrivia);
}
doc = helper.ForModifiers.GetModifiers(node)[0].LeadingTrivia.FirstOrDefault(IsCommentTrivia);
}
if (doc != default)
{
Expand Down
12 changes: 3 additions & 9 deletions Philips.CodeAnalysis.Common/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@

namespace Philips.CodeAnalysis.Common
{
public class Helper : CodeFixHelper
public class Helper(AnalyzerOptions options, Compilation compilation) : CodeFixHelper
{
public Helper(AnalyzerOptions options, Compilation compilation)
{
ForAllowedSymbols = new AllowedSymbols(compilation);
ForAdditionalFiles = new AdditionalFilesHelper(options, compilation);
}
public AllowedSymbols ForAllowedSymbols { get; } = new AllowedSymbols(compilation);

public AllowedSymbols ForAllowedSymbols { get; }

public AdditionalFilesHelper ForAdditionalFiles { get; }
public AdditionalFilesHelper ForAdditionalFiles { get; } = new AdditionalFilesHelper(options, compilation);
}
}
10 changes: 0 additions & 10 deletions Philips.CodeAnalysis.Common/MethodPredicates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ namespace Philips.CodeAnalysis.Common
{
public static class MethodPredicates
{
public static bool IsOverridden(this MethodDeclarationSyntax methodDeclarationSyntax)
{
return methodDeclarationSyntax.Modifiers.Any(SyntaxKind.OverrideKeyword);
}

public static bool ReturnsVoid(this MethodDeclarationSyntax methodDeclarationSyntax)
{
if (methodDeclarationSyntax.ReturnType is PredefinedTypeSyntax predefinedTypeSyntax)
Expand All @@ -22,11 +17,6 @@ public static bool ReturnsVoid(this MethodDeclarationSyntax methodDeclarationSyn
return false;
}

public static bool IsCallableFromOutsideClass(this MemberDeclarationSyntax method)
{
return method.Modifiers.Any(SyntaxKind.PublicKeyword) || method.Modifiers.Any(SyntaxKind.InternalKeyword) || method.Modifiers.Any(SyntaxKind.ProtectedKeyword);
}

public static Diagnostic CreateDiagnostic(this MethodDeclarationSyntax methodDeclarationSyntax, DiagnosticDescriptor rule)
{
Location location = methodDeclarationSyntax.Identifier.GetLocation();
Expand Down
49 changes: 49 additions & 0 deletions Philips.CodeAnalysis.Common/ModifiersHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// © 2024 Koninklijke Philips N.V. See License.md in the project root for license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Philips.CodeAnalysis.Common
{
public class ModifiersHelper
{
internal ModifiersHelper()
{
// Hide the constructor.
}

public SyntaxTokenList GetModifiers(SyntaxNode node)
{
if (node is MethodDeclarationSyntax method)
{
return method.Modifiers;
}
else if (node is MemberDeclarationSyntax member)
{
return member.Modifiers;
}
else if (node is PropertyDeclarationSyntax prop)
{
return prop.Modifiers;
}
else if (node is TypeDeclarationSyntax type)
{
return type.Modifiers;
}
return new SyntaxTokenList();
}

public bool IsOverridden(SyntaxNode node)
{
return GetModifiers(node).Any(SyntaxKind.OverrideKeyword);
}

public bool IsCallableFromOutsideClass(SyntaxNode node)
{
SyntaxTokenList modifiers = GetModifiers(node);
return modifiers.Any(SyntaxKind.PublicKeyword) || modifiers.Any(SyntaxKind.InternalKeyword) || modifiers.Any(SyntaxKind.ProtectedKeyword);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public override void Analyze()
{
_ = Optional(Node)
.Filter((m) => m.ReturnsVoid())
.Filter((m) => !m.IsOverridden())
.Filter((m) => !Helper.ForModifiers.IsOverridden(m))
.Select((m) => m.CreateDiagnostic(Rule))
.Iter(Context.ReportDiagnostic);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class XmlDocumentationShouldAddValueAnalyzer : DiagnosticAnalyzerBase

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(EmptyRule, ValueRule);

private static readonly char[] separator = new[] { ' ' };
private static readonly char[] separator = [' '];

protected override void InitializeCompilation(CompilationStartAnalysisContext context)
{
Expand All @@ -55,104 +55,101 @@ protected override void InitializeCompilation(CompilationStartAnalysisContext co
private void AnalyzeClass(SyntaxNodeAnalysisContext context)
{
var cls = context.Node as ClassDeclarationSyntax;
var name = cls?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => cls?.Identifier.Text);
}

private void AnalyzeConstructor(SyntaxNodeAnalysisContext context)
{
var constructor = context.Node as ConstructorDeclarationSyntax;
var name = constructor?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => constructor?.Identifier.Text);
}

private void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var method = context.Node as MethodDeclarationSyntax;
var name = method?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => method?.Identifier.Text);
}

private void AnalyzeProperty(SyntaxNodeAnalysisContext context)
{
var prop = context.Node as PropertyDeclarationSyntax;
var name = prop?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => prop?.Identifier.Text);
}

private void AnalyzeField(SyntaxNodeAnalysisContext context)
{
var field = context.Node as FieldDeclarationSyntax;
var name = field?.Declaration.Variables.FirstOrDefault()?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => field?.Declaration.Variables.FirstOrDefault()?.Identifier.Text);
}

private void AnalyzeEvent(SyntaxNodeAnalysisContext context)
{
var evt = context.Node as EventFieldDeclarationSyntax;
var name = evt?.Declaration.Variables.FirstOrDefault()?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => evt?.Declaration.Variables.FirstOrDefault()?.Identifier.Text);
}

private void AnalyzeEnum(SyntaxNodeAnalysisContext context)
{
var cls = context.Node as EnumDeclarationSyntax;
var name = cls?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => cls?.Identifier.Text);
}

private void AnalyzeEnumMember(SyntaxNodeAnalysisContext context)
{
var member = context.Node as EnumMemberDeclarationSyntax;
var name = member?.Identifier.Text;
AnalyzeNamedNode(context, name);
AnalyzeNamedNode(context, () => member?.Identifier.Text);
}

private void AnalyzeNamedNode(SyntaxNodeAnalysisContext context, string name)
private void AnalyzeNamedNode(SyntaxNodeAnalysisContext context, Func<string> nameFunc)
{
if (string.IsNullOrEmpty(name))
{
return;
}

var lowercaseName = name.ToLowerInvariant();
IEnumerable<XmlElementSyntax> xmlElements = context.Node.GetLeadingTrivia()
.Select(i => i.GetStructure())
.OfType<DocumentationCommentTriviaSyntax>()
.SelectMany(n => n.ChildNodes().OfType<XmlElementSyntax>());
foreach (XmlElementSyntax xmlElement in xmlElements)
if (xmlElements.Any())
{
if (xmlElement.StartTag.Name.LocalName.Text != @"summary")
var name = nameFunc();
if (string.IsNullOrEmpty(name))
{
continue;
return;
}

var content = GetContent(xmlElement);

if (string.IsNullOrWhiteSpace(content))
{
Location location = xmlElement.GetLocation();
var diagnostic = Diagnostic.Create(EmptyRule, location);
context.ReportDiagnostic(diagnostic);
continue;
}
var lowercaseName = name.ToLowerInvariant();

// Find the 'value' in the XML documentation content by:
// 1. Splitting it into separate words.
// 2. Filtering a predefined and a configurable list of words that add no value.
// 3. Filter words that are part of the method name.
// 4. Throw a Diagnostic if no words remain. This boils down to the content only containing 'low value' words.
IEnumerable<string> words =
SplitInWords(content)
.Where(u => !additionalUselessWords.Contains(u) && !UselessWords.Contains(u))
.Where(s => !lowercaseName.Contains(s));

// We assume here that every remaining word adds value to the documentation text.
if (!words.Any())
foreach (XmlElementSyntax xmlElement in xmlElements)
{
var loc = Location.Create(context.Node.SyntaxTree, xmlElement.Content.FullSpan);
var diagnostic = Diagnostic.Create(ValueRule, loc);
context.ReportDiagnostic(diagnostic);
if (xmlElement.StartTag.Name.LocalName.Text != @"summary")
{
continue;
}

var content = GetContent(xmlElement);

if (string.IsNullOrWhiteSpace(content))
{
Location location = xmlElement.GetLocation();
var diagnostic = Diagnostic.Create(EmptyRule, location);
context.ReportDiagnostic(diagnostic);
continue;
}

// Find the 'value' in the XML documentation content by:
// 1. Splitting it into separate words.
// 2. Filtering a predefined and a configurable list of words that add no value.
// 3. Filter words that are part of the method name.
// 4. Throw a Diagnostic if no words remain. This boils down to the content only containing 'low value' words.
IEnumerable<string> words =
SplitInWords(content)
.Where(u => !additionalUselessWords.Contains(u) && !UselessWords.Contains(u))
.Where(s => !lowercaseName.Contains(s));

// We assume here that every remaining word adds value to the documentation text.
if (!words.Any())
{
var loc = Location.Create(context.Node.SyntaxTree, xmlElement.Content.FullSpan);
var diagnostic = Diagnostic.Create(ValueRule, loc);
context.ReportDiagnostic(diagnostic);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private void Analyze(SyntaxNodeAnalysisContext context)
}

// Empty public or protected members are acceptable, as it could be part of an API, or an interface implementation
if (blockSyntax.Parent is MemberDeclarationSyntax memberSyntax && memberSyntax.IsCallableFromOutsideClass())
if (blockSyntax.Parent is MemberDeclarationSyntax memberSyntax && Helper.ForModifiers.IsCallableFromOutsideClass(memberSyntax))
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Philips.CodeAnalysis.Common;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ protected override ExpressionSyntax GetNode(SyntaxNode root, TextSpan diagnostic
// Make sure it's part of a statement that we know how to handle.
// For example, we do not currently handle method expressions (ie =>)
StatementSyntax fullExistingExpressionSyntax = expressionNode?.FirstAncestorOrSelf<ExpressionStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode.FirstAncestorOrSelf<ReturnStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode.FirstAncestorOrSelf<LocalDeclarationStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode.FirstAncestorOrSelf<IfStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode.FirstAncestorOrSelf<WhileStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode?.FirstAncestorOrSelf<ReturnStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode?.FirstAncestorOrSelf<LocalDeclarationStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode?.FirstAncestorOrSelf<IfStatementSyntax>();
fullExistingExpressionSyntax ??= expressionNode?.FirstAncestorOrSelf<WhileStatementSyntax>();

return fullExistingExpressionSyntax == null ? null : expressionNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void AnalyzeProperty(SyntaxNodeAnalysisContext context)

private void AssertType(SyntaxNodeAnalysisContext context, TypeSyntax type, MemberDeclarationSyntax parent)
{
if (!parent.IsCallableFromOutsideClass())
if (!Helper.ForModifiers.IsCallableFromOutsideClass(parent))
{
// Private members are allowed to return mutable collections.
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context)
private void ReportDiagnostic(SyntaxNodeAnalysisContext context, SyntaxToken violation)
{
// Report the location of the nearest SyntaxNode.
Location loc = violation.Parent?.GetLocation();
Location loc = violation.Parent?.GetLocation() ?? violation.GetLocation();
var lineNumber = loc.GetLineSpan().StartLinePosition.Line + 1;
context.ReportDiagnostic(Diagnostic.Create(Rule, loc, lineNumber));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ public override void Analyze()
}

var memberAccessExpression = (MemberAccessExpressionSyntax)Node.Expression;
NamespaceResolver resolver = Helper.ForNamespaces.GetUsingAliases(Node);
var name = memberAccessExpression.Name.Identifier.Text;
if (resolver.IsOfType(memberAccessExpression, AssemblyNamespace, AssemblyTypeName) && name == "GetEntryAssembly")
if (name != "GetEntryAssembly")
{
return;
}

NamespaceResolver resolver = Helper.ForNamespaces.GetUsingAliases(Node);
if (resolver.IsOfType(memberAccessExpression, AssemblyNamespace, AssemblyTypeName))
{
Location location = memberAccessExpression.Expression.GetLocation();
ReportDiagnostic(location);
Expand Down
Loading
Loading