Skip to content

Commit

Permalink
Improve code fix for CS0029, CS0246
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Apr 8, 2021
1 parent 7a9c1ca commit 5557ad2
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}

if (Settings.IsEnabled(diagnostic.Id, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression)
&& expression.IsParentKind(SyntaxKind.ReturnStatement, SyntaxKind.YieldReturnStatement))
&& expression.IsParentKind(SyntaxKind.ReturnStatement, SyntaxKind.YieldReturnStatement, SyntaxKind.ArrowExpressionClause))
{
SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

Expand Down
153 changes: 106 additions & 47 deletions src/CodeFixes/CSharp/CodeFixes/SimpleNameCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslynator.CodeFixes;
using Roslynator.CSharp.Refactorings;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Roslynator.CSharp.CodeFixes
Expand All @@ -35,76 +36,134 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
if (!TryFindFirstAncestorOrSelf(root, context.Span, out SimpleNameSyntax simpleName))
return;

foreach (Diagnostic diagnostic in context.Diagnostics)
Document document = context.Document;
Diagnostic diagnostic = context.Diagnostics[0];
string diagnosticId = diagnostic.Id;

if (diagnosticId == CompilerDiagnosticIdentifiers.CannotConvertMethodGroupToNonDelegateType
|| diagnosticId == CompilerDiagnosticIdentifiers.NameIsNotValidInGivenContext)
{
if (!Settings.IsEnabled(diagnosticId, CodeFixIdentifiers.AddArgumentList))
return;

if (!simpleName.IsParentKind(SyntaxKind.SimpleMemberAccessExpression))
return;

var memberAccess = (MemberAccessExpressionSyntax)simpleName.Parent;

CodeAction codeAction = CodeAction.Create(
"Add argument list",
cancellationToken =>
{
InvocationExpressionSyntax invocationExpression = InvocationExpression(
memberAccess.WithoutTrailingTrivia(),
ArgumentList().WithTrailingTrivia(memberAccess.GetTrailingTrivia()));

return document.ReplaceNodeAsync(memberAccess, invocationExpression, cancellationToken);
},
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
else if (diagnosticId == CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)
{
switch (diagnostic.Id)
if (Settings.IsEnabled(diagnosticId, CodeFixIdentifiers.ChangeArrayType)
&& (simpleName.Parent is ArrayTypeSyntax arrayType)
&& (arrayType.Parent is ArrayCreationExpressionSyntax arrayCreation)
&& object.ReferenceEquals(simpleName, arrayType.ElementType))
{
case CompilerDiagnosticIdentifiers.CannotConvertMethodGroupToNonDelegateType:
case CompilerDiagnosticIdentifiers.NameIsNotValidInGivenContext:
{
if (!Settings.IsEnabled(diagnostic.Id, CodeFixIdentifiers.AddArgumentList))
break;
ExpressionSyntax expression = arrayCreation.Initializer?.Expressions.FirstOrDefault();

if (expression != null)
{
SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

if (!simpleName.IsParentKind(SyntaxKind.SimpleMemberAccessExpression))
break;
ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(expression, context.CancellationToken);

var memberAccess = (MemberAccessExpressionSyntax)simpleName.Parent;
if (typeSymbol?.SupportsExplicitDeclaration() == true)
{
TypeSyntax newType = typeSymbol.ToTypeSyntax()
.WithSimplifierAnnotation()
.WithTriviaFrom(simpleName);

CodeAction codeAction = CodeAction.Create(
"Add argument list",
cancellationToken =>
{
InvocationExpressionSyntax invocationExpression = InvocationExpression(
memberAccess.WithoutTrailingTrivia(),
ArgumentList().WithTrailingTrivia(memberAccess.GetTrailingTrivia()));

return context.Document.ReplaceNodeAsync(memberAccess, invocationExpression, cancellationToken);
},
$"Change element type to '{SymbolDisplay.ToMinimalDisplayString(typeSymbol, semanticModel, simpleName.SpanStart, SymbolDisplayFormats.DisplayName)}'",
cancellationToken => document.ReplaceNodeAsync(simpleName, newType, cancellationToken),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
break;
return;
}
case CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound:
{
if (!Settings.IsEnabled(diagnostic.Id, CodeFixIdentifiers.ChangeArrayType))
break;
}
}

if (!(simpleName.Parent is ArrayTypeSyntax arrayType))
break;
if (Settings.IsEnabled(diagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression))
{
ExpressionSyntax expression = GetReturnExpression(simpleName);

if (!(arrayType.Parent is ArrayCreationExpressionSyntax arrayCreation))
break;
if (expression != null)
{
SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

if (!object.ReferenceEquals(simpleName, arrayType.ElementType))
break;
ChangeMemberTypeRefactoring.ComputeCodeFix(context, diagnostic, expression, semanticModel);
}
}
}
}

ExpressionSyntax expression = arrayCreation.Initializer?.Expressions.FirstOrDefault();
private static ExpressionSyntax GetReturnExpression(SyntaxNode node)
{
switch (node.Parent)
{
case MethodDeclarationSyntax methodDeclaration:
{
if (object.ReferenceEquals(node, methodDeclaration.ReturnType))
{
ExpressionSyntax expression = methodDeclaration.ExpressionBody?.Expression;

if (expression == null)
break;
if (expression != null)
return expression;

SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);
StatementSyntax statement = methodDeclaration.Body?.SingleNonBlockStatementOrDefault();

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(expression, context.CancellationToken);
return (statement as ReturnStatementSyntax)?.Expression;
}

if (typeSymbol?.SupportsExplicitDeclaration() != true)
break;
break;
}
case LocalFunctionStatementSyntax localFunction:
{
if (object.ReferenceEquals(node, localFunction.ReturnType))
{
ExpressionSyntax expression = localFunction.ExpressionBody?.Expression;

TypeSyntax newType = typeSymbol.ToTypeSyntax()
.WithSimplifierAnnotation()
.WithTriviaFrom(simpleName);
if (expression != null)
return expression;

CodeAction codeAction = CodeAction.Create(
$"Change element type to '{SymbolDisplay.ToMinimalDisplayString(typeSymbol, semanticModel, simpleName.SpanStart, SymbolDisplayFormats.DisplayName)}'",
cancellationToken => context.Document.ReplaceNodeAsync(simpleName, newType, cancellationToken),
GetEquivalenceKey(diagnostic));
StatementSyntax statement = localFunction.Body?.SingleNonBlockStatementOrDefault();

context.RegisterCodeFix(codeAction, diagnostic);
break;
return (statement as ReturnStatementSyntax)?.Expression;
}
}

break;
}
case VariableDeclarationSyntax variableDeclaration:
{
if (object.ReferenceEquals(node, variableDeclaration.Type)
&& node.Parent.IsParentKind(SyntaxKind.FieldDeclaration))
{
return variableDeclaration
.Variables
.SingleOrDefault(shouldThrow: false)?
.Initializer?
.Value;
}

break;
}
}

return null;
}
}
}
1 change: 1 addition & 0 deletions src/CodeFixes/CodeFixes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
<Id>CS0029</Id>
<Id>CS0127</Id>
<Id>CS0201</Id>
<Id>CS0246</Id>
<Id>CS0266</Id>
<Id>CS1997</Id>
</FixableDiagnosticIds>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,43 @@ void M()
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, "RemoveAssignment"));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.CannotImplicitlyConvertType)]
public async Task Test_ChangeReturnTypeAccordingToReturnExpression()
{
await VerifyFixAsync(@"
class C
{
int M()
{
return """";
}
}
", @"
class C
{
string M()
{
return """";
}
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.CannotImplicitlyConvertType)]
public async Task Test_ChangeReturnTypeAccordingToReturnExpression_ExpressionBody()
{
await VerifyFixAsync(@"
class C
{
int M() => """";
}
", @"
class C
{
string M() => """";
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) Josef Pihrt. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Roslynator.Testing.CSharp;
using Xunit;

namespace Roslynator.CSharp.CodeFixes.Tests
{
public class CS0246TypeOrNamespaceNameCouldNotBeFoundTests : AbstractCSharpCompilerDiagnosticFixVerifier<SimpleNameCodeFixProvider>
{
public override string DiagnosticId { get; } = CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound;

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)]
public async Task Test_ChangeType_Field()
{
await VerifyFixAsync(@"
class C
{
private x F = default(C);
}
", @"
class C
{
private C F = default(C);
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)]
public async Task Test_ChangeType_Method()
{
await VerifyFixAsync(@"
class C
{
x M()
{
return default(C);
}
}
", @"
class C
{
C M()
{
return default(C);
}
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)]
public async Task Test_ChangeType_Method_ExpressionBody()
{
await VerifyFixAsync(@"
class C
{
x M() => default(C);
}
", @"
class C
{
C M() => default(C);
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)]
public async Task Test_ChangeType_LocalFunction()
{
await VerifyFixAsync(@"
class C
{
void M()
{
x LocalFunction()
{
return default(C);
}
}
}
", @"
class C
{
void M()
{
C LocalFunction()
{
return default(C);
}
}
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}

[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.TypeOrNamespaceNameCouldNotBeFound)]
public async Task Test_ChangeType_LocalFunction_ExpressionBody()
{
await VerifyFixAsync(@"
class C
{
void M()
{
x LocalFunction() => default(C);
}
}
", @"
class C
{
void M()
{
C LocalFunction() => default(C);
}
}
", equivalenceKey: EquivalenceKey.Create(DiagnosticId, CodeFixIdentifiers.ChangeMemberTypeAccordingToReturnExpression));
}
}
}

0 comments on commit 5557ad2

Please sign in to comment.