Skip to content

Commit

Permalink
Add code fix for CS8600, CS8610, CS8765, CS8767 (#1333)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored Dec 9, 2023
1 parent 129ab0c commit 3c28eff
Show file tree
Hide file tree
Showing 14 changed files with 541 additions and 1 deletion.
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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>
Loading

0 comments on commit 3c28eff

Please sign in to comment.