Skip to content

Commit

Permalink
Merge pull request #1201 from RalfKoban/codefix_extensions_refactoring
Browse files Browse the repository at this point in the history
SyntaxNode method moved into SyntaxNode extensions for code fixes
  • Loading branch information
RalfKoban authored Feb 16, 2025
2 parents ee8226e + e60dc2c commit c6e3b11
Show file tree
Hide file tree
Showing 28 changed files with 2,175 additions and 2,122 deletions.
329 changes: 329 additions & 0 deletions MiKo.Analyzer.Shared/Extensions/SyntaxNodeExtensions.CodeFixes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

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

// ncrunch: rdi off
// ReSharper disable once CheckNamespace
#pragma warning disable IDE0130
#pragma warning disable CA1506
namespace MiKoSolutions.Analyzers
{
/// <summary>
/// Provides extensions for <see cref="SyntaxNode"/>s that can be used in code fixes where the <see cref="Document"/> class is available.
/// </summary>
internal static partial class SyntaxNodeExtensions
{
internal static SemanticModel GetSemanticModel(this Document document)
{
if (document.TryGetSemanticModel(out var result))
{
return result;
}

return document.GetSemanticModelAsync(CancellationToken.None).GetAwaiter().GetResult();
}

internal static ISymbol GetSymbol(this SyntaxNode syntax, Document document) => syntax.GetSymbolAsync(document, CancellationToken.None).GetAwaiter().GetResult();

internal static async Task<ISymbol> GetSymbolAsync(this SyntaxNode syntax, Document document, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return null;
}

if (document.TryGetSemanticModel(out var semanticModel) is false)
{
semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
}

if (syntax is TypeSyntax typeSyntax)
{
return semanticModel?.GetTypeInfo(typeSyntax, cancellationToken).Type;
}

return semanticModel?.GetDeclaredSymbol(syntax, cancellationToken);
}

internal static ITypeSymbol GetTypeSymbol(this ArgumentSyntax value, Document document) => value?.Expression.GetTypeSymbol(GetSemanticModel(document));

internal static ITypeSymbol GetTypeSymbol(this ExpressionSyntax value, Document document)
{
if (value is null)
{
return null;
}

var semanticModel = GetSemanticModel(document);
var typeInfo = semanticModel.GetTypeInfo(value);

return typeInfo.Type;
}

internal static ITypeSymbol GetTypeSymbol(this MemberAccessExpressionSyntax value, Document document) => value?.Expression.GetTypeSymbol(GetSemanticModel(document));

internal static ITypeSymbol GetTypeSymbol(this BaseTypeSyntax value, Document document) => value?.Type.GetTypeSymbol(GetSemanticModel(document));

internal static ITypeSymbol GetTypeSymbol(this ClassDeclarationSyntax value, Document document) => value?.Identifier.GetSymbol(GetSemanticModel(document)) as ITypeSymbol;

internal static ITypeSymbol GetTypeSymbol(this RecordDeclarationSyntax value, Document document) => value?.Identifier.GetSymbol(GetSemanticModel(document)) as ITypeSymbol;

internal static ITypeSymbol GetTypeSymbol(this VariableDeclarationSyntax value, Document document) => value?.Type.GetTypeSymbol(GetSemanticModel(document));

internal static ITypeSymbol GetTypeSymbol(this TypeSyntax value, Document document)
{
if (value is null)
{
return null;
}

var semanticModel = GetSemanticModel(document);
var typeInfo = semanticModel.GetTypeInfo(value);

return typeInfo.Type;
}

internal static bool HasMinimumCSharpVersion(this Document document, LanguageVersion wantedVersion) => document.TryGetSyntaxTree(out var syntaxTree) && syntaxTree.HasMinimumCSharpVersion(wantedVersion);

internal static bool IsConst(this ArgumentSyntax syntax, Document document)
{
var identifierName = syntax.Expression.GetName();

var method = syntax.GetEnclosingMethod(document.GetSemanticModel());
var type = method.FindContainingType();

var isConst = type.GetFields(identifierName).Any(_ => _.IsConst);

if (isConst)
{
// const value inside class
return true;
}

// local const variable
var isLocalConst = method.GetSyntax().DescendantNodes<LocalDeclarationStatementSyntax>(_ => _.IsConst)
.Any(_ => _.Declaration.Variables.Any(__ => __.GetName() == identifierName));

return isLocalConst;
}

internal static bool IsNullable(this IsPatternExpressionSyntax pattern, Document document) => pattern.Expression.GetSymbol(document) is ITypeSymbol typeSymbol && typeSymbol.IsNullable();

internal static bool IsEnum(this ArgumentSyntax syntax, Document document)
{
var expression = (MemberAccessExpressionSyntax)syntax.Expression;

if (expression.Expression.GetSymbol(document) is ITypeSymbol type)
{
return type.IsEnum();
}

return false;
}

internal static bool IsSeeCref(this SyntaxNode value)
{
switch (value)
{
case XmlEmptyElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.Attributes);
}

case XmlElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.StartTag.Attributes);
}

default:
{
return false;
}
}

bool IsCref(SyntaxList<XmlAttributeSyntax> syntax) => syntax.FirstOrDefault() is XmlCrefAttributeSyntax;
}

internal static bool IsSeeCref(this SyntaxNode value, string type)
{
switch (value)
{
case XmlEmptyElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.Attributes, type);
}

case XmlElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.StartTag.Attributes, type);
}

default:
{
return false;
}
}

bool IsCref(SyntaxList<XmlAttributeSyntax> syntax, string content) => syntax.FirstOrDefault() is XmlCrefAttributeSyntax attribute && attribute.Cref.ToString() == content;
}

internal static bool IsSeeCref(this SyntaxNode value, TypeSyntax type)
{
switch (value)
{
case XmlEmptyElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.Attributes, type);
}

case XmlElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.StartTag.Attributes, type);
}

default:
{
return false;
}
}

bool IsCref(SyntaxList<XmlAttributeSyntax> syntax, TypeSyntax t)
{
if (syntax.FirstOrDefault() is XmlCrefAttributeSyntax attribute)
{
if (attribute.Cref is NameMemberCrefSyntax m)
{
return t is GenericNameSyntax
? IsSameGeneric(m.Name, t)
: IsSameName(m.Name, t);
}
}

return false;
}
}

internal static bool IsSeeCref(this SyntaxNode value, TypeSyntax type, NameSyntax member)
{
switch (value)
{
case XmlEmptyElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.Attributes, type, member);
}

case XmlElementSyntax element when element.GetName() == Constants.XmlTag.See:
{
return IsCref(element.StartTag.Attributes, type, member);
}

default:
{
return false;
}
}

bool IsCref(SyntaxList<XmlAttributeSyntax> syntax, TypeSyntax t, NameSyntax name)
{
if (syntax.FirstOrDefault() is XmlCrefAttributeSyntax attribute)
{
if (attribute.Cref is QualifiedCrefSyntax q && IsSameGeneric(q.Container, t))
{
if (q.Member is NameMemberCrefSyntax m && IsSameName(m.Name, name))
{
return true;
}
}
}

return false;
}
}

internal static bool IsSeeCrefTaskResult(this SyntaxNode value)
{
var type = SyntaxFactory.ParseTypeName("Task<TResult>");
var member = SyntaxFactory.ParseName(nameof(Task<object>.Result));

return value.IsSeeCref(type, member);
}

internal static bool IsSeeCrefTask(this SyntaxNode value)
{
if (value.IsSeeCref(SyntaxFactory.ParseTypeName("Task")))
{
return true;
}

if (value.IsSeeCref(SyntaxFactory.ParseTypeName("Task<TResult>")))
{
return true;
}

return false;
}

internal static bool IsStringCreation(this SyntaxNode node)
{
if (node is BinaryExpressionSyntax b && b.IsKind(SyntaxKind.AddExpression))
{
if (b.Left.IsStringLiteral() || b.Right.IsStringLiteral())
{
return true;
}

if (b.Left.IsStringCreation() || b.Right.IsStringCreation())
{
return true;
}
}

return false;
}

private static bool IsSameGeneric(TypeSyntax t1, TypeSyntax t2)
{
if (t1 is GenericNameSyntax g1 && t2 is GenericNameSyntax g2)
{
if (g1.Identifier.ValueText == g2.Identifier.ValueText)
{
var arguments1 = g1.TypeArgumentList.Arguments;
var arguments2 = g2.TypeArgumentList.Arguments;

// keep in local variable to avoid multiple requests (see Roslyn implementation)
var arguments1Count = arguments1.Count;
var arguments2Count = arguments2.Count;

if (arguments1Count == arguments2Count)
{
for (var i = 0; i < arguments1Count; i++)
{
if (IsSameName(arguments1[i], arguments2[i]) is false)
{
return false;
}
}

return true;
}
}
}

return false;
}

private static bool IsSameName(TypeSyntax t1, TypeSyntax t2)
{
if (t1 is IdentifierNameSyntax n1 && t2 is IdentifierNameSyntax n2)
{
return n1.Identifier.ValueText == n2.Identifier.ValueText;
}

return t1.ToString() == t2.ToString();
}
}
}
5 changes: 4 additions & 1 deletion MiKo.Analyzer.Shared/Extensions/SyntaxNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
#pragma warning disable CA1506
namespace MiKoSolutions.Analyzers
{
internal static class SyntaxNodeExtensions
/// <summary>
/// Provides extensions for <see cref="SyntaxNode"/>s.
/// </summary>
internal static partial class SyntaxNodeExtensions
{
internal static readonly SyntaxTrivia XmlCommentExterior = SyntaxFactory.DocumentationCommentExterior("/// ");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\StringExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\StringSplitExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxNodeExtensions.CodeFixes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxNodeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxNodeOrTokenExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SyntaxTokenExtensions.cs" />
Expand Down
Loading

0 comments on commit c6e3b11

Please sign in to comment.