-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathOptionNoneInvocationCodeFix.cs
More file actions
64 lines (55 loc) · 3.22 KB
/
OptionNoneInvocationCodeFix.cs
File metadata and controls
64 lines (55 loc) · 3.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
namespace Funcky.BuiltinAnalyzers;
[Shared]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(OptionNoneInvocationCodeFix))]
public sealed class OptionNoneInvocationCodeFix : CodeFixProvider
{
private const string CS1955 = nameof(CS1955); // error CS1955: Non-invocable member 'Member' cannot be used like a method.
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(CS1955);
public override FixAllProvider GetFixAllProvider()
=> WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
if (await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is { } root
&& await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is { } semanticModel
&& semanticModel.Compilation.GetOptionOfTType() is { } optionOfTType)
{
foreach (var diagnostic in context.Diagnostics)
{
if (root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<InvocationExpressionSyntax>() is { } syntax
&& IsInvocationOfOptionNoneProperty(semanticModel, syntax, optionOfTType))
{
context.RegisterCodeFix(CreateFix(context, syntax), diagnostic);
}
}
}
}
private static bool IsInvocationOfOptionNoneProperty(SemanticModel semanticModel, InvocationExpressionSyntax syntax, INamedTypeSymbol optionOfTType)
=> semanticModel.GetSymbolInfo(syntax.Expression) is { CandidateReason: CandidateReason.NotInvocable, CandidateSymbols: var candidates }
&& candidates.Any(IsOptionNoneProperty(optionOfTType));
private static Func<ISymbol, bool> IsOptionNoneProperty(INamedTypeSymbol optionOfTType)
=> candidate
=> candidate is IPropertySymbol { Name: WellKnownMemberNames.OptionOfT.None, ContainingType: var type }
&& SymbolEqualityComparer.Default.Equals(type.ConstructedFrom, optionOfTType);
private static CodeAction CreateFix(CodeFixContext context, InvocationExpressionSyntax syntax)
=> CodeAction.Create(
"Replace with property access",
ReplaceWithPropertyAccess(context.Document, syntax),
nameof(OptionNoneInvocationCodeFix));
private static Func<CancellationToken, Task<Document>> ReplaceWithPropertyAccess(Document document, InvocationExpressionSyntax syntax)
=> async cancellationToken
=>
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var trailingTrivia = syntax.Expression.GetTrailingTrivia().AddRange(syntax.GetTrailingTrivia());
editor.ReplaceNode(syntax, syntax.Expression.WithLeadingTrivia(syntax.GetLeadingTrivia()).WithTrailingTrivia(trailingTrivia));
return editor.GetChangedDocument();
};
}