blob: 7d278ee05fef08b7c86e6873bd71c00fdf7747f6 [file]
/*
* 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 Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility;
using Lucene.Net.CodeAnalysis.Dev.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev1001_FloatingPointFormattingCSCodeFixProvider)), Shared]
public class LuceneDev1001_FloatingPointFormattingCSCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds =>
[Descriptors.LuceneDev1001_FloatingPointFormatting.Id];
public override FixAllProvider GetFixAllProvider() =>
WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic? diagnostic = context.Diagnostics.FirstOrDefault();
if (diagnostic is null)
return;
SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null)
return;
// the diagnostic in the analyzer is reported on the member access (e.g. "x.ToString")
// but we need the whole invocation (e.g. "x.ToString(...)"). So find the invocation
// by walking ancestors if needed.
SyntaxNode? node = root.FindNode(diagnostic.Location.SourceSpan);
if (node is null)
return;
if (node is not ExpressionSyntax exprNode)
return;
SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (semanticModel is null)
return;
if (!TryGetJ2NTypeAndMember(semanticModel, exprNode, out var j2nTypeName, out var memberAccess))
return;
string codeElement = $"J2N.Numerics.{j2nTypeName}.ToString(...)";
context.RegisterCodeFix(
CodeActionHelper.CreateFromResource(
CodeFixResources.UseX,
c => ReplaceWithJ2NToStringAsync(context.Document, memberAccess, c),
"UseJ2NToString",
codeElement),
diagnostic);
}
private async Task<Document> ReplaceWithJ2NToStringAsync(
Document document,
MemberAccessExpressionSyntax memberAccess,
CancellationToken cancellationToken)
{
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
if (semanticModel is null)
return document;
if (!TryGetJ2NTypeAndMember(semanticModel, memberAccess, out var j2nTypeName, out _))
return document;
// Build J2N.Numerics.Single/Double.ToString
MemberAccessExpressionSyntax j2nToStringAccess = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("J2N"),
SyntaxFactory.IdentifierName("Numerics")),
SyntaxFactory.IdentifierName(j2nTypeName))
.WithAdditionalAnnotations(Formatter.Annotation);
MemberAccessExpressionSyntax fullAccess = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
j2nToStringAccess,
SyntaxFactory.IdentifierName("ToString"));
// Build invocation: J2N.Numerics.<Single|Double>.ToString(<expr>, <original args...>)
if (memberAccess.Parent is not InvocationExpressionSyntax invocation)
return document;
var newArgs = new List<ArgumentSyntax> { SyntaxFactory.Argument(memberAccess.Expression) };
if (invocation.ArgumentList != null)
newArgs.AddRange(invocation.ArgumentList.Arguments);
InvocationExpressionSyntax newInvocation = SyntaxFactory.InvocationExpression(
fullAccess,
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArgs)))
.WithTriviaFrom(invocation) // safe now
.WithAdditionalAnnotations(Formatter.Annotation);
DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
editor.ReplaceNode(memberAccess.Parent, newInvocation);
return editor.GetChangedDocument();
}
private static bool TryGetJ2NTypeAndMember(
SemanticModel semanticModel,
ExpressionSyntax expr,
out string j2nTypeName,
out MemberAccessExpressionSyntax memberAccess)
{
memberAccess = expr as MemberAccessExpressionSyntax
?? expr.AncestorsAndSelf().OfType<MemberAccessExpressionSyntax>().FirstOrDefault();
if (memberAccess is null)
{
j2nTypeName = null!; // we always return false when the value is null, so we can ignore it here.
return false;
}
var typeInfo = semanticModel.GetTypeInfo(memberAccess.Expression);
var type = typeInfo.Type;
j2nTypeName = type?.SpecialType switch
{
SpecialType.System_Single => "Single",
SpecialType.System_Double => "Double",
_ => null! // we always return false when the value is null, so we can ignore it here.
};
if (j2nTypeName is null)
return false;
return true;
}
}
}