-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathLuceneDev6001_StringComparisonCodeFixProvider.cs
More file actions
124 lines (106 loc) · 5.8 KB
/
LuceneDev6001_StringComparisonCodeFixProvider.cs
File metadata and controls
124 lines (106 loc) · 5.8 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.CodeAnalysis.Dev.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.LuceneDev6xxx
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev6001_StringComparisonCodeFixProvider)), Shared]
public sealed class LuceneDev6001_StringComparisonCodeFixProvider : CodeFixProvider
{
private const string TitleOrdinal = "Use StringComparison.Ordinal";
private const string TitleOrdinalIgnoreCase = "Use StringComparison.OrdinalIgnoreCase";
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(
Descriptors.LuceneDev6001_MissingStringComparison.Id,
Descriptors.LuceneDev6001_InvalidStringComparison.Id);
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root == null) return;
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var invocation = root.FindToken(diagnosticSpan.Start)
.Parent?
.AncestorsAndSelf()
.OfType<InvocationExpressionSyntax>()
.FirstOrDefault();
if (invocation == null) return;
// Offer both Ordinal and OrdinalIgnoreCase fixes
context.RegisterCodeFix(CodeAction.Create(
title: TitleOrdinal,
createChangedDocument: c => FixInvocationAsync(context.Document, invocation, "Ordinal", c),
equivalenceKey: TitleOrdinal),
diagnostic);
context.RegisterCodeFix(CodeAction.Create(
title: TitleOrdinalIgnoreCase,
createChangedDocument: c => FixInvocationAsync(context.Document, invocation, "OrdinalIgnoreCase", c),
equivalenceKey: TitleOrdinalIgnoreCase),
diagnostic);
}
private static async Task<Document> FixInvocationAsync(Document document, InvocationExpressionSyntax invocation, string comparisonMember, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root == null) return document;
// Create the StringComparison expression
var stringComparisonExpr = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("StringComparison"),
SyntaxFactory.IdentifierName(comparisonMember));
var newArg = SyntaxFactory.Argument(stringComparisonExpr);
// Check if a StringComparison argument already exists
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var stringComparisonType = semanticModel?.Compilation.GetTypeByMetadataName("System.StringComparison");
var existingArg = invocation.ArgumentList.Arguments.FirstOrDefault(arg =>
semanticModel != null &&
(SymbolEqualityComparer.Default.Equals(semanticModel.GetTypeInfo(arg.Expression).Type, stringComparisonType) ||
(semanticModel.GetSymbolInfo(arg.Expression).Symbol is IFieldSymbol f && SymbolEqualityComparer.Default.Equals(f.ContainingType, stringComparisonType))));
// Replace existing argument or add new one
var newInvocation = existingArg != null
? invocation.ReplaceNode(existingArg, newArg)
: invocation.WithArgumentList(invocation.ArgumentList.AddArguments(newArg));
// Combine adding 'using System;' and replacing invocation in a single root
var newRoot = EnsureSystemUsing(root).ReplaceNode(invocation, newInvocation);
return document.WithSyntaxRoot(newRoot);
}
private static SyntaxNode EnsureSystemUsing(SyntaxNode root)
{
if (root is CompilationUnitSyntax compilationUnit)
{
var hasSystemUsing = compilationUnit.Usings.Any(u =>
u.Name is IdentifierNameSyntax id && id.Identifier.ValueText == "System");
if (!hasSystemUsing)
{
var systemUsing = SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System"))
.WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed);
return compilationUnit.AddUsings(systemUsing);
}
}
return root;
}
}
}