Extensions for analyzers & code fixes.
Reason there are three packages is if analyzer author writes analyzers and fixes in separate projetcs. In that case the separate Gu.Roslyn.AnalyzerExtensions
and Gu.Roslyn.CodeFixExtensions
should be used.
The Gu.Roslyn.Extensions
package is a merge of Gu.Roslyn.AnalyzerExtensions
and Gu.Roslyn.CodeFixExtensions
.
- Pooled
- StyleCopComparers
- Symbols
- Syntax
- Doc comments
- Walkers
- FixAll
- CodeStyle
- DocumentEditorExt
- Simplify
- Trivia
using (var set = PooledSet<int>.Borrow())
{
}
Or when used recursively:
// set can be null here if so a new set its returned.
using (var current = set.IncrementUsage())
{
}
## StringBuilderPool
```cs
var text = StringBuilderPool.Borrow()
.AppendLine("a")
.AppendLine()
.AppendLine("b")
.Return();
Comparers that compare member declarations with stylecop order.
For comparing with ITypeSymbol
public static readonly QualifiedType Object = new QualifiedType("System.Object", "object");
Same as QualifiedType
but for members.
Extension methods for ISymbol
.
Get the declaration if it exists. If the symbol is from a binary reference the declaration will not exist.
Extension methods for ItypeSymbol
.
For checking if C is Type
Find members by name or predicate.
Find members by name or predicate in type or base types.
Find the argument that matches the parameter.
Try get the constant string value of the argument.
Try get the matching parameter
Get the getter or setter if exists.
Check if the property is public int Value { get; }
Check if the property is an auto property.
Check if a node is executed before another node.
Helper methods for finding members by name or predicate.
if(member.TryGetDocumentationComment(out DocumentationCommentTriviaSyntax comment))
{
}
if(comment.TryGetSummary(out XmlElementSyntax comment))
{
}
if(comment.TryGetTypeParam("T", out XmlElementSyntax comment))
{
}
if(comment.TryGetParam("x", out XmlElementSyntax comment))
{
}
if(comment.TryGetReturns(out XmlElementSyntax comment))
{
}
var updated = comment.WithSummaryText("Lorem ipsum.")
var updated = comment.WithTypeParamText("T", "Lorem ipsum.")
var updated = comment.WithParamText("x", "Lorem ipsum.")
var updated = comment.WithReturnsText("x", "Lorem ipsum.")
var method = syntaxTree.FindMethodDeclaration("Bar");
var text = "/// <summary>New summary.</summary>\r\n" +
"/// <returns>New returns.</returns>";
var updated = method.WithDocumentationText(text);
Base type for a walker that walks code in execution order. Use the Search
enum to specify if walk should be recursive walking invoked methods etc.
Remember to clear locals in the Clear
method.
internal sealed class AssignmentExecutionWalker : ExecutionWalker<AssignmentExecutionWalker>
{
private readonly List<AssignmentExpressionSyntax> assignments = new List<AssignmentExpressionSyntax>();
private AssignmentExecutionWalker()
{
}
/// <summary>
/// Gets a list with all <see cref="AssignmentExpressionSyntax"/> in the scope.
/// </summary>
public IReadOnlyList<AssignmentExpressionSyntax> Assignments => this.assignments;
public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
this.assignments.Add(node);
base.VisitAssignmentExpression(node);
}
internal static AssignmentExecutionWalker Borrow(SyntaxNode node, Search search, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var walker = Borrow(() => new AssignmentExecutionWalker());
walker.SemanticModel = semanticModel;
walker.CancellationToken = cancellationToken;
walker.Search = search;
walker.Visit(node);
return walker;
}
protected override void Clear()
{
this.assignments.Clear();
base.Clear();
}
}
A pooled walker for reuse.
Remember to clear locals in the Clear
method.
internal sealed class IdentifierNameWalker : PooledWalker<IdentifierNameWalker>
{
private readonly List<IdentifierNameSyntax> identifierNames = new List<IdentifierNameSyntax>();
private IdentifierNameWalker()
{
}
public IReadOnlyList<IdentifierNameSyntax> IdentifierNames => this.identifierNames;
public static IdentifierNameWalker Borrow(SyntaxNode node) => BorrowAndVisit(node, () => new IdentifierNameWalker());
public override void VisitIdentifierName(IdentifierNameSyntax node)
{
this.identifierNames.Add(node);
base.VisitIdentifierName(node);
}
protected override void Clear()
{
this.identifierNames.Clear();
}
}
For caching expensive calls
public override void Initialize(AnalysisContext context)
{
context.CacheToCompilationEnd<SyntaxTree, SemanticModel>();
}
Extension methods for enumarebls
- TrySingle
- TryLast
- TryElementAt
A fix all provider that use document editor for batch fixes.
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseParameterCodeFixProvider))]
[Shared]
internal class UseParameterCodeFixProvider : DocumentEditorCodeFixProvider
{
/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(GU0014PreferParameter.DiagnosticId);
/// <inheritdoc/>
protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context)
{
var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
.ConfigureAwait(false);
foreach (var diagnostic in context.Diagnostics)
{
if (diagnostic.Properties.TryGetValue("Name", out var name))
{
if (syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) is MemberAccessExpressionSyntax memberAccess)
{
context.RegisterCodeFix(
"Prefer parameter.",
(editor, _) => editor.ReplaceNode(
memberAccess,
SyntaxFactory.IdentifierName(name)
.WithLeadingTriviaFrom(memberAccess)),
"Prefer parameter.",
diagnostic);
}
else if (syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) is IdentifierNameSyntax identifierName)
{
context.RegisterCodeFix(
"Prefer parameter.",
(editor, _) => editor.ReplaceNode(
identifierName,
identifierName.WithIdentifier(SyntaxFactory.Identifier(name))
.WithLeadingTriviaFrom(identifierName)),
"Prefer parameter.",
diagnostic);
}
}
}
}
}
Helpers for determinining the code style used in the project.
Figures out if backing field is placed like stylecop wants it or adjacent to the property.
Helpers for adding members sorted according to how StyleCop wants it.
Adds the using sorted and figures out if it shoul add outside or insside the namespace from the current document then project.
Adds the field at the position StyleCop wants it.
Adds the property at the position StyleCop wants it.
Adds the method at the position StyleCop wants it.
Uses a syntax rewriter that adds Simplifier.Annotation
to all QualifiedNameSyntax
Helpers for copying trivia from other nodes.
Copy trivia from a node.
Copy leading trivia from a node.
Copy trailing trivia from a node.
Gu.Roslyn.Extensions.Source is a package containing the sources for embedding in consuming analyzer. This is probably the best way to consume this library as Visual Studio and other tools do not work well when an analyzer has a binary dependency. To work it requires:
<ItemGroup>
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[5.0.0]" />
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all"/>
</ItemGroup>