提交 9fedb65a 编写于 作者: C CyrusNajmabadi

Support coalescing nullable expessions.

上级 96e0734c
......@@ -280,6 +280,7 @@
<Compile Include="Formatting\Indentation\SmartTokenFormatterFormatTokenTests.cs" />
<Compile Include="InlineDeclaration\CSharpInlineDeclarationTests_FixAllTests.cs" />
<Compile Include="InlineDeclaration\CSharpInlineDeclarationTests.cs" />
<Compile Include="UseCoalesceExpression\UseCoalesceExpressionForNullableTests.cs" />
<Compile Include="UseCoalesceExpression\UseCoalesceExpressionTests.cs" />
<Compile Include="UsePatternMatching\CSharpIsAndCastCheckTests_FixAllTests.cs" />
<Compile Include="UsePatternMatching\CSharpIsAndCastCheckTests.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.UseCoalesceExpression;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCoalesceExpression
{
public class UseCoalesceExpressionForNullableTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override Tuple<DiagnosticAnalyzer, CodeFixProvider> CreateDiagnosticProviderAndFixer(Workspace workspace)
{
return new Tuple<DiagnosticAnalyzer, CodeFixProvider>(
new CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer(),
new UseCoalesceExpressionForNullableCodeFixProvider());
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestOnLeft_Equals()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y)
{
var z = [||]!x.HasValue ? y : x.Value;
}
}",
@"using System;
class C
{
void M(int? x, int? y)
{
var z = x ?? y;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestOnLeft_NotEquals()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y)
{
var z = [||]x.HasValue ? x.Value : y;
}
}",
@"using System;
class C
{
void M(int? x, int? y)
{
var z = x ?? y;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestComplexExpression()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y)
{
var z = [||]!(x + y).HasValue ? y : (x + y).Value;
}
}",
@"using System;
class C
{
void M(int? x, int? y)
{
var z = (x + y) ?? y;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestParens1()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y)
{
var z = [||](x.HasValue) ? x.Value : y;
}
}",
@"using System;
class C
{
void M(int? x, int? y)
{
var z = x ?? y;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestFixAll1()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y)
{
var z1 = {|FixAllInDocument:x|}.HasValue ? x.Value : y;
var z2 = !x.HasValue ? y : x.Value;
}
}",
@"using System;
class C
{
void M(int? x, int? y)
{
var z1 = x ?? y;
var z2 = x ?? y;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestFixAll2()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y, int? z)
{
var w = {|FixAllInDocument:x|}.HasValue ? x.Value : y.ToString(z.HasValue ? z.Value : y);
}
}",
@"using System;
class C
{
void M(int? x, int? y, int? z)
{
var w = x ?? y.ToString(z ?? y);
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
public async Task TestFixAll3()
{
await TestAsync(
@"
using System;
class C
{
void M(int? x, int? y, int? z)
{
var w = {|FixAllInDocument:x|}.HasValue ? x.Value : y.HasValue ? y.Value : z;
}
}",
@"using System;
class C
{
void M(int? x, int? y, int? z)
{
var w = x ?? y ?? z;
}
}");
}
}
}
\ No newline at end of file
......@@ -55,6 +55,7 @@
<Compile Include="..\..\..\Compilers\CSharp\Portable\Syntax\LambdaUtilities.cs">
<Link>InternalUtilities\LambdaUtilities.cs</Link>
</Compile>
<Compile Include="UseCoalesceExpression\CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs" />
<Compile Include="UseCoalesceExpression\CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs" />
<Compile Include="UseExpressionBody\Accessors\UseExpressionBodyForAccessorsCodeFixProvider.cs" />
<Compile Include="UseExpressionBody\Accessors\UseExpressionBodyForAccessorsDiagnosticAnalyzer.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.UseCoalesceExpression;
namespace Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer :
AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
SyntaxKind,
ExpressionSyntax,
ConditionalExpressionSyntax,
BinaryExpressionSyntax,
MemberAccessExpressionSyntax,
PrefixUnaryExpressionSyntax>
{
protected override ISyntaxFactsService GetSyntaxFactsService()
=> CSharpSyntaxFactsService.Instance;
protected override SyntaxKind GetSyntaxKindToAnalyze()
=> SyntaxKind.ConditionalExpression;
}
}
\ No newline at end of file
......@@ -34,6 +34,7 @@ internal static class IDEDiagnosticIds
public const string UseExpressionBodyForAccessorsDiagnosticId = "IDE0026";
public const string UseCoalesceExpressionDiagnosticId = "IDE0028";
public const string UseCoalesceExpressionForNullableDiagnosticId = "IDE0029";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
......
......@@ -121,6 +121,8 @@
<Compile Include="Remote\RemoteArguments.cs" />
<Compile Include="Structure\BlockStructureOptions.cs" />
<Compile Include="AddImport\CodeActions\SymbolReference.SymbolReferenceCodeAction.cs" />
<Compile Include="UseCoalesceExpression\AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs" />
<Compile Include="UseCoalesceExpression\UseCoalesceExpressionForNullableCodeFixProvider.cs" />
<Compile Include="UseCoalesceExpression\UseCoalesceExpressionCodeFixProvider.cs" />
<Compile Include="UseCoalesceExpression\AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs" />
<Compile Include="UseThrowExpression\AbstractUseThrowExpressionDiagnosticAnalyzer.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
namespace Microsoft.CodeAnalysis.UseCoalesceExpression
{
internal abstract class AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
TSyntaxKind,
TExpressionSyntax,
TConditionalExpressionSyntax,
TBinaryExpressionSyntax,
TMemberAccessExpression,
TPrefixUnaryExpressionSyntax> : AbstractCodeStyleDiagnosticAnalyzer
where TSyntaxKind : struct
where TExpressionSyntax : SyntaxNode
where TConditionalExpressionSyntax : TExpressionSyntax
where TBinaryExpressionSyntax : TExpressionSyntax
where TMemberAccessExpression : TExpressionSyntax
where TPrefixUnaryExpressionSyntax : TExpressionSyntax
{
protected AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseCoalesceExpressionForNullableDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Use_coalesce_expression), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
{
}
protected abstract TSyntaxKind GetSyntaxKindToAnalyze();
protected abstract ISyntaxFactsService GetSyntaxFactsService();
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeSyntax, GetSyntaxKindToAnalyze());
}
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
var optionSet = context.Options.GetOptionSet();
var option = optionSet.GetOption(CodeStyleOptions.PreferCoalesceExpression, conditionalExpression.Language);
if (!option.Value)
{
return;
}
var syntaxFacts = this.GetSyntaxFactsService();
SyntaxNode conditionNode, whenTrueNodeHigh, whenFalseNodeHigh;
syntaxFacts.GetPartsOfConditionalExpression(
conditionalExpression, out conditionNode, out whenTrueNodeHigh, out whenFalseNodeHigh);
conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);
var notHasValueExpression = false;
if (syntaxFacts.IsLogicalNotExpression(conditionNode))
{
notHasValueExpression = true;
conditionNode = syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode);
}
var conditionMemberAccess = conditionNode as TMemberAccessExpression;
if (conditionMemberAccess == null)
{
return;
}
SyntaxNode conditionExpression, conditionSimpleName;
syntaxFacts.GetPartsOfMemberAccessExpression(conditionMemberAccess, out conditionExpression, out conditionSimpleName);
string conditionName; int unused;
syntaxFacts.GetNameAndArityOfSimpleName(conditionSimpleName, out conditionName, out unused);
if (conditionName != nameof(Nullable<int>.HasValue))
{
return;
}
var whenPartToCheck = notHasValueExpression ? whenFalseNodeLow : whenTrueNodeLow;
var whenPartMemberAccess = whenPartToCheck as TMemberAccessExpression;
if (whenPartMemberAccess == null)
{
return;
}
SyntaxNode whenPartExpression, whenPartSimpleName;
syntaxFacts.GetPartsOfMemberAccessExpression(whenPartMemberAccess, out whenPartExpression, out whenPartSimpleName);
string whenPartName;
syntaxFacts.GetNameAndArityOfSimpleName(whenPartSimpleName, out whenPartName, out unused);
if (whenPartName != nameof(Nullable<int>.Value))
{
return;
}
if (!syntaxFacts.AreEquivalent(conditionExpression, whenPartExpression))
{
return;
}
// Syntactically this looks like something we can simplify. Make sure we're
// actually looking at something Nullable (and not some type that uses a similar
// syntactic pattern).
var semanticModel = context.SemanticModel;
var nullableType = semanticModel.Compilation.GetTypeByMetadataName("System.Nullable`1");
if (nullableType == null)
{
return;
}
var cancellationToken = context.CancellationToken;
var type = semanticModel.GetTypeInfo(conditionExpression, cancellationToken);
if (!nullableType.Equals(type.Type?.OriginalDefinition))
{
return;
}
var whenPartToKeep = notHasValueExpression ? whenTrueNodeHigh : whenFalseNodeHigh;
var locations = ImmutableArray.Create(
conditionalExpression.GetLocation(),
conditionExpression.GetLocation(),
whenPartToKeep.GetLocation());
context.ReportDiagnostic(Diagnostic.Create(
this.CreateDescriptor(this.DescriptorId, option.Notification.Value),
conditionalExpression.GetLocation(),
locations));
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeFixes.FixAllOccurrences;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseCoalesceExpression
{
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
internal class UseCoalesceExpressionForNullableCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseCoalesceExpressionForNullableDiagnosticId);
public override FixAllProvider GetFixAllProvider() => new UseCoalesceExpressionForNullableFixAllProvider(this);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(new MyCodeAction(
c => FixAsync(context.Document, context.Diagnostics[0], c)),
context.Diagnostics);
return SpecializedTasks.EmptyTask;
}
private Task<Document> FixAsync(
Document document,
Diagnostic diagnostic,
CancellationToken cancellationToken)
{
return FixAllAsync(document, ImmutableArray.Create(diagnostic), cancellationToken);
}
private async Task<Document> FixAllAsync(
Document document,
ImmutableArray<Diagnostic> diagnostics,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
var generator = editor.Generator;
foreach (var diagnostic in diagnostics)
{
var conditionalExpression = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
var conditionExpression = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan);
var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan);
SyntaxNode condition, whenTrue, whenFalse;
syntaxFacts.GetPartsOfConditionalExpression(
conditionalExpression, out condition, out whenTrue, out whenFalse);
editor.ReplaceNode(conditionalExpression,
(c, g) => {
SyntaxNode currentCondition, currentWhenTrue, currentWhenFalse;
syntaxFacts.GetPartsOfConditionalExpression(
c, out currentCondition, out currentWhenTrue, out currentWhenFalse);
return whenPart == whenTrue
? g.CoalesceExpression(conditionExpression, syntaxFacts.WalkDownParentheses(currentWhenTrue))
: g.CoalesceExpression(conditionExpression, syntaxFacts.WalkDownParentheses(currentWhenFalse));
});
}
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
}
private class UseCoalesceExpressionForNullableFixAllProvider : DocumentBasedFixAllProvider
{
private readonly UseCoalesceExpressionForNullableCodeFixProvider _provider;
public UseCoalesceExpressionForNullableFixAllProvider(UseCoalesceExpressionForNullableCodeFixProvider provider)
{
_provider = provider;
}
protected override Task<Document> FixDocumentAsync(
Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
{
var filteredDiagnostics = diagnostics.WhereAsArray(
d => !d.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary));
return _provider.FixAllAsync(document, filteredDiagnostics, cancellationToken);
}
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Use_coalesce_expression, createChangedDocument)
{
}
}
}
}
\ No newline at end of file
......@@ -1892,6 +1892,19 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond
public SyntaxNode WalkDownParentheses(SyntaxNode node)
=> (node as ExpressionSyntax)?.WalkDownParentheses() ?? node;
public bool IsLogicalNotExpression(SyntaxNode node)
=> node.Kind() == SyntaxKind.LogicalNotExpression;
public SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node)
=> ((PrefixUnaryExpressionSyntax)node).Operand;
public void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode name)
{
var memberAccessExpression = (MemberAccessExpressionSyntax)node;
expression = memberAccessExpression.Expression;
name = memberAccessExpression.Name;
}
private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter
{
private readonly SyntaxNode _contextNode;
......
......@@ -65,6 +65,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsExpressionOfAwaitExpression(SyntaxNode node);
SyntaxNode GetExpressionOfAwaitExpression(SyntaxNode node);
bool IsLogicalNotExpression(SyntaxNode node);
SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node);
// Left side of = assignment.
bool IsLeftSideOfAssignment(SyntaxNode node);
......@@ -87,9 +90,10 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsNameOfMemberAccessExpression(SyntaxNode node);
bool IsExpressionOfMemberAccessExpression(SyntaxNode node);
SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode memberAccessExpression);
SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode memberAccessExpression);
SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode memberAccessExpression);
SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode node);
SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node);
SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode node);
void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode name);
bool IsSimpleMemberAccessExpression(SyntaxNode node);
bool IsPointerMemberAccessExpression(SyntaxNode node);
......
......@@ -1567,5 +1567,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Public Function WalkDownParentheses(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.WalkDownParentheses
Return If(TryCast(node, ExpressionSyntax)?.WalkDownParentheses(), node)
End Function
Public Function IsLogicalNotExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLogicalNotExpression
Return node.IsKind(SyntaxKind.NotExpression)
Throw New NotImplementedException()
End Function
Public Function GetOperandOfPrefixUnaryExpression(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetOperandOfPrefixUnaryExpression
Return DirectCast(node, UnaryExpressionSyntax).Operand
End Function
Public Sub GetPartsOfMemberAccessExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef name As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfMemberAccessExpression
Dim memberAccess = DirectCast(node, MemberAccessExpressionSyntax)
expression = memberAccess.Expression
name = memberAccess.Name
End Sub
End Class
End Namespace
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册