Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code fix for CS8600, CS8610, CS8765, CS8767 #1333

Merged
merged 9 commits into from Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add analyzer "Add/remove blank line between switch sections" ([RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061)) ([PR](https://github.com/dotnet/roslynator/pull/1302))
- Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block`
- Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete
- Add code fix "Declare as nullable" ([PR](https://github.com/dotnet/roslynator/pull/1333))
- Applicable to: `CS8600`, `CS8610`, `CS8765` and `CS8767`

### Changed

Expand Down
4 changes: 4 additions & 0 deletions src/CSharp/CSharp/CompilerDiagnosticIdentifiers.Generated.cs
Expand Up @@ -175,10 +175,14 @@ internal static class CompilerDiagnosticIdentifiers
public const string CS8139_CannotChangeTupleElementNameWhenOverridingInheritedMember = "CS8139";
public const string CS8340_InstanceFieldsOfReadOnlyStructsMustBeReadOnly = "CS8340";
public const string CS8403_MethodWithIteratorBlockMustBeAsyncToReturnIAsyncEnumerableOfT = "CS8403";
public const string CS8600_ConvertingNullLiteralOrPossibleNullValueToNonNullableType = "CS8600";
public const string CS8602_DereferenceOfPossiblyNullReference = "CS8602";
public const string CS8604_PossibleNullReferenceArgumentForParameter = "CS8604";
public const string CS8610_NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember = "CS8610";
public const string CS8618_NonNullableMemberIsUninitialized = "CS8618";
public const string CS8625_CannotConvertNullLiteralToNonNullableReferenceType = "CS8625";
public const string CS8632_AnnotationForNullableReferenceTypesShouldOnlyBeUsedWithinNullableAnnotationsContext = "CS8632";
public const string CS8765_NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember = "CS8765";
public const string CS8767_NullabilityDoesNotMatchImplementedMember = "CS8767";
}
}
8 changes: 8 additions & 0 deletions src/CodeFixes.xml
Expand Up @@ -691,4 +691,12 @@
<Id>CS8602</Id>
</FixableDiagnosticIds>
</CodeFix>
<CodeFix Id="RCF0122" Identifier="AddNullableAnnotation" Title="Add nullable annotation">
<FixableDiagnosticIds>
<Id>CS8600</Id>
<Id>CS8610</Id>
<Id>CS8765</Id>
<Id>CS8767</Id>
</FixableDiagnosticIds>
</CodeFix>
</CodeFixes>
10 changes: 10 additions & 0 deletions src/CodeFixes/CSharp/CodeFixDescriptors.Generated.cs
Expand Up @@ -913,5 +913,15 @@ public static partial class CodeFixDescriptors
isEnabledByDefault: true,
"CS8602");

/// <summary>RCF0122 (fixes CS8600, CS8610, CS8765, CS8767)</summary>
public static readonly CodeFixDescriptor AddNullableAnnotation = new CodeFixDescriptor(
id: CodeFixIdentifiers.AddNullableAnnotation,
title: "Add nullable annotation",
isEnabledByDefault: true,
"CS8600",
"CS8610",
"CS8765",
"CS8767");

}
}
1 change: 1 addition & 0 deletions src/CodeFixes/CSharp/CodeFixIdentifiers.Generated.cs
Expand Up @@ -124,5 +124,6 @@ public static partial class CodeFixIdentifiers
public const string UseNullForgivingOperator = CodeFixIdentifier.CodeFixIdPrefix + "0119";
public const string AddAsyncModifier = CodeFixIdentifier.CodeFixIdPrefix + "0120";
public const string UseNullPropagationOperator = CodeFixIdentifier.CodeFixIdPrefix + "0121";
public const string AddNullableAnnotation = CodeFixIdentifier.CodeFixIdPrefix + "0122";
}
}
88 changes: 88 additions & 0 deletions src/CodeFixes/CSharp/CodeFixes/DeclareAsNullableCodeFixProvider.cs
@@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslynator.CodeFixes;

namespace Roslynator.CSharp.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DeclareAsNullableCodeFixProvider))]
[Shared]
public sealed class DeclareAsNullableCodeFixProvider : CompilerDiagnosticCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(CompilerDiagnosticIdentifiers.CS8600_ConvertingNullLiteralOrPossibleNullValueToNonNullableType); }
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic diagnostic = context.Diagnostics[0];

SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false);

if (!IsEnabled(diagnostic.Id, CodeFixIdentifiers.AddNullableAnnotation, context.Document, root.SyntaxTree))
return;

if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.EqualsValueClause, SyntaxKind.DeclarationExpression, SyntaxKind.SimpleAssignmentExpression)))
return;

if (node is EqualsValueClauseSyntax equalsValueClause)
{
ExpressionSyntax expression = equalsValueClause.Value;

if (expression.Span == context.Span
&& equalsValueClause.IsParentKind(SyntaxKind.VariableDeclarator)
&& equalsValueClause.Parent.Parent is VariableDeclarationSyntax variableDeclaration
&& variableDeclaration.Variables.Count == 1)
{
TryRegisterCodeFix(context, diagnostic, variableDeclaration.Type);
}
}
else if (node is DeclarationExpressionSyntax declarationExpression)
{
TryRegisterCodeFix(context, diagnostic, declarationExpression.Type);
}
else if (node is AssignmentExpressionSyntax assignmentExpression)
{
SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

var localSymbol = semanticModel.GetSymbol(assignmentExpression.Left, context.CancellationToken) as ILocalSymbol;

if (localSymbol is not null)
{
SyntaxNode declarator = await localSymbol.GetSyntaxAsync(context.CancellationToken).ConfigureAwait(false);

if (declarator.IsKind(SyntaxKind.VariableDeclarator)
&& declarator.Parent is VariableDeclarationSyntax variableDeclaration
&& variableDeclaration.Variables.Count == 1)
{
TryRegisterCodeFix(context, diagnostic, variableDeclaration.Type);
}
}
}
}

private static void TryRegisterCodeFix(CodeFixContext context, Diagnostic diagnostic, TypeSyntax type)
{
if (type.IsKind(SyntaxKind.NullableType))
return;

CodeAction codeAction = CodeAction.Create(
"Declare as nullable",
ct =>
{
NullableTypeSyntax newType = SyntaxFactory.NullableType(type.WithoutTrivia()).WithTriviaFrom(type);
return context.Document.ReplaceNodeAsync(type, newType, ct);
},
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
}
85 changes: 84 additions & 1 deletion src/CodeFixes/CSharp/CodeFixes/TokenCodeFixProvider.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
Expand Down Expand Up @@ -38,7 +39,10 @@ public override ImmutableArray<string> FixableDiagnosticIds
CompilerDiagnosticIdentifiers.CS8618_NonNullableMemberIsUninitialized,
CompilerDiagnosticIdentifiers.CS8403_MethodWithIteratorBlockMustBeAsyncToReturnIAsyncEnumerableOfT,
CompilerDiagnosticIdentifiers.CS8602_DereferenceOfPossiblyNullReference,
CompilerDiagnosticIdentifiers.CS8604_PossibleNullReferenceArgumentForParameter
CompilerDiagnosticIdentifiers.CS8604_PossibleNullReferenceArgumentForParameter,
CompilerDiagnosticIdentifiers.CS8610_NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember,
CompilerDiagnosticIdentifiers.CS8765_NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember,
CompilerDiagnosticIdentifiers.CS8767_NullabilityDoesNotMatchImplementedMember
);
}
}
Expand Down Expand Up @@ -607,6 +611,85 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
additionalKey: CodeFixIdentifiers.AddAsyncModifier);
}

break;
}
case CompilerDiagnosticIdentifiers.CS8610_NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember:
case CompilerDiagnosticIdentifiers.CS8765_NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember:
case CompilerDiagnosticIdentifiers.CS8767_NullabilityDoesNotMatchImplementedMember:
{
if (!IsEnabled(diagnostic.Id, CodeFixIdentifiers.AddNullableAnnotation, document, root.SyntaxTree))
break;

if (token.Parent is not MemberDeclarationSyntax memberDeclaration)
return;

SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

ISymbol symbol = semanticModel.GetDeclaredSymbol(memberDeclaration, context.CancellationToken);

if (symbol is null)
return;

ISymbol baseSymbol = null;
if (diagnostic.Id == CompilerDiagnosticIdentifiers.CS8767_NullabilityDoesNotMatchImplementedMember)
{
using IEnumerator<ISymbol> en = symbol.FindImplementedInterfaceMembers().GetEnumerator();

if (en.MoveNext())
{
baseSymbol = en.Current;

if (en.MoveNext())
return;
}
}
else
{
baseSymbol = symbol.OverriddenSymbol();

if (baseSymbol is null)
return;
}

SeparatedSyntaxList<ParameterSyntax> parameters = CSharpUtility.GetParameters(memberDeclaration);

ImmutableArray<IParameterSymbol> parameterSymbols = symbol.GetParameters();

if (parameters.Count != parameterSymbols.Length)
return;

ImmutableArray<IParameterSymbol> parametersSymbols2 = baseSymbol.GetParameters();

if (parameters.Count != parametersSymbols2.Length)
return;

MemberDeclarationSyntax newNode = memberDeclaration;
int offset = 0;

for (int i = parameters.Count - 1; i >= 0; i--)
{
if (!SymbolEqualityComparer.IncludeNullability.Equals(parameterSymbols[i].Type, parametersSymbols2[i].Type))
{
var parameter = (BaseParameterSyntax)newNode.FindNode(parameters[i].Span.Offset(-offset));

TypeSyntax newType = parametersSymbols2[i].Type.ToTypeSyntax()
.WithTriviaFrom(parameter.Type)
.WithSimplifierAnnotation();

if (newNode == memberDeclaration)
offset = newNode.FullSpan.Start;

newNode = newNode.ReplaceNode(parameter, parameter.WithType(newType));
}
}

context.RegisterCodeFix(
CodeAction.Create(
"Declare as nullable",
ct => document.ReplaceNodeAsync(memberDeclaration, newNode, ct),
GetEquivalenceKey(diagnostic)),
diagnostic);

break;
}
}
Expand Down
48 changes: 48 additions & 0 deletions src/CodeFixes/CSharp/CompilerDiagnosticRules.Generated.cs
Expand Up @@ -2036,6 +2036,18 @@ public static partial class CompilerDiagnosticRules
helpLinkUri: "https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs8403",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8600</summary>
public static readonly DiagnosticDescriptor ConvertingNullLiteralOrPossibleNullValueToNonNullableType = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8600_ConvertingNullLiteralOrPossibleNullValueToNonNullableType,
title: "Converting null literal or possible null value to non-nullable type.",
messageFormat: "Converting null literal or possible null value to non-nullable type",
category: "Compiler",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: null,
helpLinkUri: "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8602</summary>
public static readonly DiagnosticDescriptor DereferenceOfPossiblyNullReference = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8602_DereferenceOfPossiblyNullReference,
Expand All @@ -2060,6 +2072,18 @@ public static partial class CompilerDiagnosticRules
helpLinkUri: "",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8610</summary>
public static readonly DiagnosticDescriptor NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8610_NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember,
title: "Nullability of reference types in type of parameter doesn't match overridden member.",
messageFormat: "Nullability of reference types in type of parameter '{0}' doesn't match overridden member.",
category: "Compiler",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: null,
helpLinkUri: "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8618</summary>
public static readonly DiagnosticDescriptor NonNullableMemberIsUninitialized = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8618_NonNullableMemberIsUninitialized,
Expand Down Expand Up @@ -2096,5 +2120,29 @@ public static partial class CompilerDiagnosticRules
helpLinkUri: "",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8765</summary>
public static readonly DiagnosticDescriptor NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8765_NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember,
title: "Nullability of type of parameter doesn't match overridden member.",
messageFormat: "Nullability of type of parameter 'arg0' doesn't match overridden member.",
category: "Compiler",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: null,
helpLinkUri: "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings",
customTags: WellKnownDiagnosticTags.Compiler);

/// <summary>CS8767</summary>
public static readonly DiagnosticDescriptor NullabilityDoesNotMatchImplementedMember = new DiagnosticDescriptor(
id: CompilerDiagnosticIdentifiers.CS8767_NullabilityDoesNotMatchImplementedMember,
title: "Nullability of reference types in type of parameter doesn't match implicitly implemented member.",
messageFormat: "Nullability of reference types in type of parameter '{0}' of '{1}' doesn't match implicitly implemented member '{2}'.",
category: "Compiler",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: null,
helpLinkUri: "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings",
customTags: WellKnownDiagnosticTags.Compiler);

}
}
28 changes: 28 additions & 0 deletions src/Diagnostics.xml
Expand Up @@ -1183,6 +1183,13 @@
Title="Method with an iterator block must be 'async' to return 'IAsyncEnumerable&lt;T&lt;'."
Message="Method '{0}' with an iterator block must be 'async' to return '{1}'"
HelpUrl="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs8403" />
<Diagnostic
Id="CS8600"
Identifier="ConvertingNullLiteralOrPossibleNullValueToNonNullableType"
Severity="Warning"
Title="Converting null literal or possible null value to non-nullable type."
Message="Converting null literal or possible null value to non-nullable type"
HelpUrl="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings" />
<Diagnostic
Id="CS8602"
Identifier="DereferenceOfPossiblyNullReference"
Expand All @@ -1197,6 +1204,13 @@
Title="Possible null reference argument for parameter."
Message="Possible null reference argument for parameter '{0}' in '{1}'"
HelpUrl="" />
<Diagnostic
Id="CS8610"
Identifier="NullabilityOfReferenceTypesInTypeOfParameterDoesNotMatchOverriddenMember"
Severity="Warning"
Title="Nullability of reference types in type of parameter doesn't match overridden member."
Message="Nullability of reference types in type of parameter '{0}' doesn't match overridden member."
HelpUrl="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings" />
<Diagnostic
Id="CS8618"
Identifier="NonNullableMemberIsUninitialized"
Expand All @@ -1218,4 +1232,18 @@
Title="The annotation for nullable reference types should only be used in code within a '#nullable' annotations context."
Message="The annotation for nullable reference types should only be used in code within a '#nullable' annotations context"
HelpUrl="" />
<Diagnostic
Id="CS8765"
Identifier="NullabilityOfTypeOfParameterDoesNotMatchOverriddenMember"
Severity="Warning"
Title="Nullability of type of parameter doesn't match overridden member."
Message="Nullability of type of parameter 'arg0' doesn't match overridden member."
HelpUrl="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings" />
<Diagnostic
Id="CS8767"
Identifier="NullabilityDoesNotMatchImplementedMember"
Severity="Warning"
Title="Nullability of reference types in type of parameter doesn't match implicitly implemented member."
Message="Nullability of reference types in type of parameter '{0}' of '{1}' doesn't match implicitly implemented member '{2}'."
HelpUrl="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings" />
</Diagnostics>