未验证 提交 5902f691 编写于 作者: J Julien Couvreur 提交者: GitHub

Add a DeclareAsNullable fixer (#26630)

上级 20da0f74
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeFixes.DeclareAsNullable;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.DeclareAsNullable
{
[Trait(Traits.Feature, Traits.Features.CodeActionsDeclareAsNullable)]
public class CSharpDeclareAsNullableCodeFixTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (null, new CSharpDeclareAsNullableCodeFixProvider());
private static readonly TestParameters s_nullableFeature = new TestParameters(parseOptions: new CSharpParseOptions(LanguageVersion.CSharp8));
[Fact]
public async Task FixAll()
{
await TestInRegularAndScript1Async(
@"class Program
{
static string M()
{
return {|FixAllInDocument:null|};
}
static string M2(bool b)
{
if (b)
return null;
else
return null;
}
}",
@"class Program
{
static string? M()
{
return null;
}
static string? M2(bool b)
{
if (b)
return null;
else
return null;
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixReturnType()
{
await TestInRegularAndScript1Async(
@"class Program
{
static string M()
{
return [|null|];
}
}",
@"class Program
{
static string? M()
{
return null;
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixReturnType_WithTrivia()
{
await TestInRegularAndScript1Async(
@"class Program
{
static /*before*/ string /*after*/ M()
{
return [|null|];
}
}",
@"class Program
{
static /*before*/ string? /*after*/ M()
{
return null;
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixReturnType_ArrowBody()
{
await TestInRegularAndScript1Async(
@"class Program
{
static string M() => [|null|];
}",
@"class Program
{
static string? M() => null;
}", parameters: s_nullableFeature);
}
[Fact]
[WorkItem(26639, "https://github.com/dotnet/roslyn/issues/26639")]
public async Task FixReturnType_LocalFunction_ArrowBody()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
static void M()
{
string local() => [|null|];
}
}", parameters: s_nullableFeature);
}
[Fact]
[WorkItem(26639, "https://github.com/dotnet/roslyn/issues/26639")]
public async Task FixLocalFunctionReturnType()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
void M()
{
string local()
{
return [|null|];
}
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task NoFixAlreadyNullableReturnType()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
static string? M()
{
return [|null|];
}
}", parameters: s_nullableFeature);
}
[Fact]
[WorkItem(26628, "https://github.com/dotnet/roslyn/issues/26628")]
public async Task FixField()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
string x = [|null|];
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixLocalDeclaration()
{
await TestInRegularAndScript1Async(
@"class Program
{
static void M()
{
string x = [|null|];
}
}",
@"class Program
{
static void M()
{
string? x = null;
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixLocalDeclaration_WithVar()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
static void M()
{
var x = [|null|];
}
}", parameters: s_nullableFeature);
}
[Fact]
public async Task NoFixMultiDeclaration()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
static void M()
{
string x = [|null|], y = null;
}
}", parameters: s_nullableFeature);
}
[Fact]
[WorkItem(26628, "https://github.com/dotnet/roslyn/issues/26628")]
public async Task FixPropertyDeclaration()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
string x { get; set; } = [|null|];
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixPropertyDeclaration_WithReturnNull()
{
await TestInRegularAndScript1Async(
@"class Program
{
string x { get { return [|null|]; } }
}",
@"class Program
{
string? x { get { return null; } }
}", parameters: s_nullableFeature);
}
[Fact]
public async Task FixPropertyDeclaration_ArrowBody()
{
await TestInRegularAndScript1Async(
@"class Program
{
string x => [|null|];
}",
@"class Program
{
string? x => null;
}", parameters: s_nullableFeature);
}
[Fact]
[WorkItem(26626, "https://github.com/dotnet/roslyn/issues/26626")]
public async Task FixOptionalParameter()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
static void M(string x = [|null|]) { }
}", parameters: s_nullableFeature);
}
}
}
......@@ -314,6 +314,15 @@ internal class CSharpFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Declare as nullable.
/// </summary>
internal static string Declare_as_nullable {
get {
return ResourceManager.GetString("Declare_as_nullable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to deconstruction.
/// </summary>
......
......@@ -162,6 +162,9 @@
<data name="Remove_Unnecessary_Cast" xml:space="preserve">
<value>Remove Unnecessary Cast</value>
</data>
<data name="Declare_as_nullable" xml:space="preserve">
<value>Declare as nullable</value>
</data>
<data name="Cast_is_redundant" xml:space="preserve">
<value>Cast is redundant</value>
</data>
......
// 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.Generic;
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.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.DeclareAsNullable
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.DeclareAsNullable), Shared]
internal class CSharpDeclareAsNullableCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
// warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter.
// warning CS8600: Converting null literal or possible null value to non-nullable type.
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create("CS8625", "CS8600");
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.First();
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var declarationTypeToFix = TryGetDeclarationTypeToFix(node);
if (declarationTypeToFix == null)
{
return;
}
context.RegisterCodeFix(new MyCodeAction(
c => FixAsync(context.Document, diagnostic, c)),
context.Diagnostics);
}
protected override Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var root = editor.OriginalRoot;
// a method can have multiple `return null;` statements, but we should only fix its return type once
var alreadyHandled = PooledHashSet<TypeSyntax>.GetInstance();
foreach (var diagnostic in diagnostics)
{
var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken);
MakeDeclarationNullable(document, editor, node, alreadyHandled);
}
alreadyHandled.Free();
return Task.CompletedTask;
}
private static void MakeDeclarationNullable(Document document, SyntaxEditor editor, SyntaxNode node, HashSet<TypeSyntax> alreadyHandled)
{
var declarationTypeToFix = TryGetDeclarationTypeToFix(node);
if (declarationTypeToFix != null && alreadyHandled.Add(declarationTypeToFix))
{
var fixedDeclaration = SyntaxFactory.NullableType(declarationTypeToFix.WithoutTrivia()).WithTriviaFrom(declarationTypeToFix);
editor.ReplaceNode(declarationTypeToFix, fixedDeclaration);
}
}
private static TypeSyntax TryGetDeclarationTypeToFix(SyntaxNode node)
{
if (!node.IsKind(SyntaxKind.NullLiteralExpression))
{
return null;
}
if (node.IsParentKind(SyntaxKind.ReturnStatement))
{
var containingMember = node.GetAncestors().FirstOrDefault(a => a.IsKind(
SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression,
SyntaxKind.LocalFunctionStatement, SyntaxKind.AnonymousMethodExpression, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration,
SyntaxKind.OperatorDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.EventDeclaration));
if (containingMember == null)
{
return null;
}
switch (containingMember)
{
case MethodDeclarationSyntax method:
// string M() { return null; }
return method.ReturnType;
case PropertyDeclarationSyntax property:
// string x { get { return null; } }
return property.Type;
default:
return null;
}
}
// string x = null;
if (node.Parent?.Parent?.IsParentKind(SyntaxKind.VariableDeclaration) == true)
{
var variableDeclaration = (VariableDeclarationSyntax)node.Parent.Parent.Parent;
if (variableDeclaration.Variables.Count != 1)
{
// string x = null, y = null;
return null;
}
return variableDeclaration.Type;
}
// string x { get; set; } = null;
if (node.Parent.IsParentKind(SyntaxKind.PropertyDeclaration) == true)
{
var propertyDeclaration = (PropertyDeclarationSyntax)node.Parent.Parent;
return propertyDeclaration.Type;
}
// void M(string x = null) { }
if (node.Parent.IsParentKind(SyntaxKind.Parameter) == true)
{
var parameter = (ParameterSyntax)node.Parent.Parent;
return parameter.Type;
}
// static string M() => null;
if (node.IsParentKind(SyntaxKind.ArrowExpressionClause) && node.Parent.IsParentKind(SyntaxKind.MethodDeclaration))
{
var arrowMethod = (MethodDeclarationSyntax)node.Parent.Parent;
return arrowMethod.ReturnType;
}
return null;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument) :
base(CSharpFeaturesResources.Declare_as_nullable,
createChangedDocument,
CSharpFeaturesResources.Declare_as_nullable)
{
}
}
}
}
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -642,6 +642,11 @@
<target state="new">Convert to 'for'</target>
<note />
</trans-unit>
<trans-unit id="Declare_as_nullable">
<source>Declare as nullable</source>
<target state="new">Declare as nullable</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
\ No newline at end of file
......@@ -39,6 +39,7 @@ internal static class PredefinedCodeFixProviderNames
public const string PopulateSwitch = nameof(PopulateSwitch);
public const string QualifyMemberAccess = nameof(QualifyMemberAccess);
public const string RemoveUnnecessaryCast = nameof(RemoveUnnecessaryCast);
public const string DeclareAsNullable = nameof(DeclareAsNullable);
public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports);
public const string RemoveUnreachableCode = nameof(RemoveUnreachableCode);
public const string RemoveUnusedLocalFunction = nameof(RemoveUnusedLocalFunction);
......
......@@ -100,6 +100,7 @@ public static class Features
public const string CodeActionsRemoveByVal = "CodeActions.RemoveByVal";
public const string CodeActionsRemoveDocCommentNode = "CodeActions.RemoveDocCommentNode";
public const string CodeActionsRemoveUnnecessaryCast = "CodeActions.RemoveUnnecessaryCast";
public const string CodeActionsDeclareAsNullable = "CodeActions.DeclareAsNullable";
public const string CodeActionsRemoveUnusedLocalFunction = "CodeActions.RemoveUnusedLocalFunction";
public const string CodeActionsRemoveUnusedVariable = "CodeActions.RemoveUnusedVariable";
public const string CodeActionsRemoveUnnecessaryImports = "CodeActions.RemoveUnnecessaryImports";
......
......@@ -100,6 +100,17 @@ public static bool IsKind(this SyntaxNode node, SyntaxKind kind1, SyntaxKind kin
return csharpKind == kind1 || csharpKind == kind2 || csharpKind == kind3 || csharpKind == kind4 || csharpKind == kind5 || csharpKind == kind6;
}
public static bool IsKind(this SyntaxNode node, SyntaxKind kind1, SyntaxKind kind2, SyntaxKind kind3, SyntaxKind kind4, SyntaxKind kind5, SyntaxKind kind6, SyntaxKind kind7, SyntaxKind kind8, SyntaxKind kind9, SyntaxKind kind10, SyntaxKind kind11)
{
if (node == null)
{
return false;
}
var csharpKind = node.Kind();
return csharpKind == kind1 || csharpKind == kind2 || csharpKind == kind3 || csharpKind == kind4 || csharpKind == kind5 || csharpKind == kind6 || csharpKind == kind7 || csharpKind == kind8 || csharpKind == kind9 || csharpKind == kind10 || csharpKind == kind11;
}
/// <summary>
/// Returns the list of using directives that affect <paramref name="node"/>. The list will be returned in
/// top down order.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册