diff --git a/src/Roslyn.Diagnostics.Analyzers/CSharp/AnalyzerReleases.Unshipped.md b/src/Roslyn.Diagnostics.Analyzers/CSharp/AnalyzerReleases.Unshipped.md
index 43ad717d7f..5a3dacc7d7 100644
--- a/src/Roslyn.Diagnostics.Analyzers/CSharp/AnalyzerReleases.Unshipped.md
+++ b/src/Roslyn.Diagnostics.Analyzers/CSharp/AnalyzerReleases.Unshipped.md
@@ -2,5 +2,6 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
RS0038 | RoslynDiagnosticsMaintainability | Warning | PreferNullLiteral
+RS0046 | RoslynDiagnosticsDesign | Warning | CSharpAvoidOptSuffixForNullableEnableCode
RS0100 | RoslynDiagnosticsMaintainability | Warning | CSharpWrapStatementsDiagnosticAnalyzer
RS0102 | RoslynDiagnosticsMaintainability | Warning | CSharpBracePlacementDiagnosticAnalyzer
diff --git a/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCode.cs b/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCode.cs
new file mode 100644
index 0000000000..71580d9ab8
--- /dev/null
+++ b/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCode.cs
@@ -0,0 +1,74 @@
+// 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 Analyzer.Utilities;
+using Analyzer.Utilities.Extensions;
+using Analyzer.Utilities.Lightup;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Roslyn.Diagnostics.Analyzers;
+
+namespace Roslyn.Diagnostics.CSharp.Analyzers
+{
+ ///
+ /// RS0046: Avoid 'Opt' suffix for nullable enable code
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class CSharpAvoidOptSuffixForNullableEnableCode : DiagnosticAnalyzer
+ {
+ internal const string OptSuffix = "Opt";
+
+ private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(RoslynDiagnosticsAnalyzersResources.AvoidOptSuffixForNullableEnableCodeTitle), RoslynDiagnosticsAnalyzersResources.ResourceManager, typeof(RoslynDiagnosticsAnalyzersResources));
+ private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(RoslynDiagnosticsAnalyzersResources.AvoidOptSuffixForNullableEnableCodeMessage), RoslynDiagnosticsAnalyzersResources.ResourceManager, typeof(RoslynDiagnosticsAnalyzersResources));
+ private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(RoslynDiagnosticsAnalyzersResources.AvoidOptSuffixForNullableEnableCodeDescription), RoslynDiagnosticsAnalyzersResources.ResourceManager, typeof(RoslynDiagnosticsAnalyzersResources));
+
+ internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
+ RoslynDiagnosticIds.AvoidOptSuffixForNullableEnableCodeRuleId,
+ s_localizableTitle,
+ s_localizableMessage,
+ DiagnosticCategory.RoslynDiagnosticsDesign,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: s_localizableDescription,
+ helpLinkUri: null,
+ customTags: WellKnownDiagnosticTags.Telemetry);
+
+ public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterSyntaxNodeAction(context =>
+ {
+ var parameter = (ParameterSyntax)context.Node;
+ ReportOnInvalidIdentifier(parameter.Identifier, context.SemanticModel, context.ReportDiagnostic);
+ }, SyntaxKind.Parameter);
+
+ context.RegisterSyntaxNodeAction(context =>
+ {
+ var variableDeclarator = (VariableDeclaratorSyntax)context.Node;
+ ReportOnInvalidIdentifier(variableDeclarator.Identifier, context.SemanticModel, context.ReportDiagnostic);
+ }, SyntaxKind.VariableDeclarator);
+ }
+
+ private static void ReportOnInvalidIdentifier(SyntaxToken identifier, SemanticModel semanticModel, Action reportAction)
+ {
+ if (!identifier.Text.EndsWith(OptSuffix, StringComparison.Ordinal) ||
+ !semanticModel.GetNullableContext(identifier.SpanStart).AnnotationsEnabled())
+ {
+ return;
+ }
+
+ var symbol = semanticModel.GetDeclaredSymbol(identifier.Parent);
+ if (symbol?.GetMemberOrLocalOrParameterType()?.NullableAnnotation() == Analyzer.Utilities.Lightup.NullableAnnotation.Annotated)
+ {
+ reportAction(identifier.CreateDiagnostic(Rule));
+ }
+ }
+ }
+}
diff --git a/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCodeCodeFixProvider.cs b/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCodeCodeFixProvider.cs
new file mode 100644
index 0000000000..c304f3c2cb
--- /dev/null
+++ b/src/Roslyn.Diagnostics.Analyzers/CSharp/CSharpAvoidOptSuffixForNullableEnableCodeCodeFixProvider.cs
@@ -0,0 +1,63 @@
+// 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.Collections.Immutable;
+using System.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Rename;
+using Roslyn.Diagnostics.Analyzers;
+
+namespace Roslyn.Diagnostics.CSharp.Analyzers
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp)]
+ [Shared]
+ public sealed class CSharpAvoidOptSuffixForNullableEnableCodeCodeFixProvider : CodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CSharpAvoidOptSuffixForNullableEnableCode.Rule.Id);
+
+ public override FixAllProvider GetFixAllProvider()
+ => WellKnownFixAllProviders.BatchFixer;
+
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var title = RoslynDiagnosticsAnalyzersResources.AvoidOptSuffixForNullableEnableCodeCodeFixTitle;
+
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var variable = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
+ if (variable == null)
+ {
+ continue;
+ }
+
+ var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
+ var variableSymbol = semanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
+ if (variableSymbol == null || variableSymbol.Name.Length <= CSharpAvoidOptSuffixForNullableEnableCode.OptSuffix.Length)
+ {
+ continue;
+ }
+
+ var newName = variableSymbol.Name.Substring(0, variableSymbol.Name.Length - CSharpAvoidOptSuffixForNullableEnableCode.OptSuffix.Length);
+
+ // There is no symbol matching the new name so we can register the codefix
+ if (semanticModel.LookupSymbols(diagnostic.Location.SourceSpan.Start, variableSymbol.ContainingType, newName).IsEmpty)
+ {
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title,
+ cancellationToken => RemoveOptSuffixOnVariableAsync(context.Document, variableSymbol, newName, cancellationToken),
+ equivalenceKey: title),
+ diagnostic);
+ }
+ }
+ }
+
+ private static async Task RemoveOptSuffixOnVariableAsync(Document document, ISymbol variableSymbol, string newName, CancellationToken cancellationToken)
+ => await Renamer.RenameSymbolAsync(document.Project.Solution, variableSymbol, newName, document.Project.Solution.Options, cancellationToken)
+ .ConfigureAwait(false);
+ }
+}
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticIds.cs b/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticIds.cs
index 235314e533..20725695a6 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticIds.cs
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticIds.cs
@@ -49,6 +49,7 @@ internal static class RoslynDiagnosticIds
public const string DoNotCallGetTestAccessorRuleId = "RS0043";
public const string CreateTestAccessorRuleId = "RS0044";
public const string ExposeMemberForTestingRuleId = "RS0045";
+ public const string AvoidOptSuffixForNullableEnableCodeRuleId = "RS0046";
public const string WrapStatementsRuleId = "RS0100";
public const string BlankLinesRuleId = "RS0101";
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticsAnalyzersResources.resx b/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticsAnalyzersResources.resx
index 2a42c9e8d2..880715070f 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticsAnalyzersResources.resx
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/RoslynDiagnosticsAnalyzersResources.resx
@@ -330,6 +330,15 @@
Expose member for testing
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+ Avoid the 'Opt' suffix
+
Braces must not have blank lines between them
@@ -348,4 +357,7 @@
Place statement on following line
+
+ Remove the 'Opt' suffix
+
\ No newline at end of file
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.cs.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.cs.xlf
index 2028407605..6bc3574365 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.cs.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.cs.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Nepoužívejte několik prázdných řádků.
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.de.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.de.xlf
index f0b76a2553..4efb21d4f5 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.de.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.de.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Vermeiden Sie mehrere Leerzeilen.
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.es.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.es.xlf
index e61ef34c85..1a72c466e1 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.es.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.es.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Evitar varias líneas en blanco
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.fr.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.fr.xlf
index 87594f2d2d..8ebd09da3f 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.fr.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.fr.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Éviter plusieurs lignes vides
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.it.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.it.xlf
index a877e13379..c256b114be 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.it.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.it.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Evitare più righe vuote
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ja.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ja.xlf
index 7e2649c4a4..d5b7350114 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ja.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ja.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
複数の空白行は使用できません
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ko.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ko.xlf
index bfcec87823..4eb2e4b8fa 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ko.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ko.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
여러 빈 줄 방지
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pl.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pl.xlf
index 1c8ff97f8d..424f30c290 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pl.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pl.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Unikaj wielu pustych wierszy
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pt-BR.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pt-BR.xlf
index e14e398da5..74c31fc1e5 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pt-BR.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.pt-BR.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Evite várias linhas em branco
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ru.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ru.xlf
index 2595fa2c96..de80fc07bc 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ru.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.ru.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Избегайте использования нескольких пустых строк
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.tr.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.tr.xlf
index ceb759712a..acf0517af0 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.tr.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.tr.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
Birden çok boş satırdan kaçının
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hans.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hans.xlf
index 17ea4877c4..031fbc81c0 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hans.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hans.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
避免出现多个空白行
diff --git a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hant.xlf b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hant.xlf
index 925f03f006..7ce91d8339 100644
--- a/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hant.xlf
+++ b/src/Roslyn.Diagnostics.Analyzers/Core/xlf/RoslynDiagnosticsAnalyzersResources.zh-Hant.xlf
@@ -2,6 +2,26 @@
+
+
+ Remove the 'Opt' suffix
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code.
+
+
+
+
+ Avoid the 'Opt' suffix in a nullable-enabled code
+
+
+
+
+ Avoid the 'Opt' suffix
+
+
避免多個空白行
diff --git a/src/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpAvoidOptSuffixForNullableEnableCodeTests.cs b/src/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpAvoidOptSuffixForNullableEnableCodeTests.cs
new file mode 100644
index 0000000000..ddd974d5b5
--- /dev/null
+++ b/src/Roslyn.Diagnostics.Analyzers/UnitTests/CSharpAvoidOptSuffixForNullableEnableCodeTests.cs
@@ -0,0 +1,268 @@
+// 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.CSharp;
+using Xunit;
+using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
+ Roslyn.Diagnostics.CSharp.Analyzers.CSharpAvoidOptSuffixForNullableEnableCode,
+ Roslyn.Diagnostics.CSharp.Analyzers.CSharpAvoidOptSuffixForNullableEnableCodeCodeFixProvider>;
+
+namespace Roslyn.Diagnostics.Analyzers.UnitTests
+{
+ public class CSharpAvoidOptSuffixForNullableEnableCodeTests
+ {
+ [Fact]
+ public async Task RS0046_CSharp8_NullableEnabledCode_Diagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public class Class1
+{
+ private Class1? [|_instanceOpt|], [|otherInstanceOpt|];
+
+ public void Method1(string? [|sOpt|])
+ {
+ string? [|localOpt|] = null, [|otherLocalOpt|] = null;
+
+ System.Console.WriteLine(""{0}, {1}, {2}, {3}, {4}"", _instanceOpt, otherInstanceOpt, sOpt, localOpt, otherLocalOpt);
+ }
+}",
+ FixedCode = @"
+#nullable enable
+
+public class Class1
+{
+ private Class1? _instance, otherInstance;
+
+ public void Method1(string? s)
+ {
+ string? local = null, otherLocal = null;
+
+ System.Console.WriteLine(""{0}, {1}, {2}, {3}, {4}"", _instance, otherInstance, s, local, otherLocal);
+ }
+}",
+
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_NullableEnabledCodeNonNullableType_NoDiagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public class Class1
+{
+ private Class1 _instanceOpt = new Class1(), otherInstanceOpt = new Class1();
+
+ public void Method1(string sOpt)
+ {
+ string localOpt, otherLocalOpt;
+ }
+}",
+
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_NullableEnabledCodeValueType_Diagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public enum MyEnum { A, }
+
+public class Class1
+{
+ private MyEnum? [|_instanceOpt|], [|otherInstanceOpt|];
+
+ public void Method1(MyEnum? [|eOpt|])
+ {
+ MyEnum? [|localOpt|] = null, [|otherLocalOpt|] = null;
+
+ System.Console.WriteLine(""{0}, {1}, {2}, {3}, {4}"", _instanceOpt, otherInstanceOpt, eOpt, localOpt, otherLocalOpt);
+ }
+}",
+ FixedCode = @"
+#nullable enable
+
+public enum MyEnum { A, }
+
+public class Class1
+{
+ private MyEnum? _instance, otherInstance;
+
+ public void Method1(MyEnum? e)
+ {
+ MyEnum? local = null, otherLocal = null;
+
+ System.Console.WriteLine(""{0}, {1}, {2}, {3}, {4}"", _instance, otherInstance, e, local, otherLocal);
+ }
+}",
+
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_NullableEnabledCodeNonNullableValueType_NoDiagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public enum MyEnum { A, }
+
+public class Class1
+{
+ private MyEnum _instanceOpt = MyEnum.A, otherInstanceOpt = MyEnum.A;
+
+ public void Method1(MyEnum eOpt)
+ {
+ MyEnum localOpt, otherLocalOpt;
+ }
+}",
+
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_NonNullableEnabledCode_NoDiagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode =
+ @"
+public class Class1
+{
+ private Class1 _instanceOpt, otherInstanceOpt;
+
+ public void Method1(string sOpt)
+ {
+ string localOpt, otherLocalOpt;
+ }
+}",
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_NullableDisabledCode_NoDiagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode =
+@"
+#nullable disable
+
+public class Class1
+{
+ private Class1 _instanceOpt, otherInstanceOpt;
+
+ public void Method1(string sOpt)
+ {
+ string localOpt, otherLocalOpt;
+ }
+}",
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_PriorToCSharp8_NoDiagnostic()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp7_3,
+ TestCode =
+ @"
+public class Class1
+{
+ private Class1 _instanceOpt, otherInstanceOpt;
+
+ public void Method1(string sOpt)
+ {
+ string localOpt, otherLocalOpt;
+ }
+}",
+ }.RunAsync();
+ }
+
+ [Fact(Skip = "https://github.com/dotnet/roslyn-analyzers/issues/3707")]
+ public async Task RS0046_CSharp8_VariableWithoutOptAlreadyExists_DiagnosticButNoCodeFix()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public class Class1
+{
+ private Class1? [|_instanceOpt|], _instance;
+
+ public void Method1(string? [|sOpt|], string? s)
+ {
+ string? [|localOpt|], local;
+ }
+}",
+ FixedCode = @"
+#nullable enable
+
+public class Class1
+{
+ private Class1? [|_instanceOpt|], _instance;
+
+ public void Method1(string? [|sOpt|], string? s)
+ {
+ string? [|localOpt|], local;
+ }
+}",
+ }.RunAsync();
+ }
+
+ [Fact]
+ public async Task RS0046_CSharp8_UnknownType_DiagnosticAndCodeFix()
+ {
+ await new VerifyCS.Test
+ {
+ LanguageVersion = LanguageVersion.CSharp8,
+ TestCode = @"
+#nullable enable
+
+public class Class1
+{
+ private {|CS0246:Class2|}? [|_instanceOpt|];
+
+ public void Method1({|CS0246:Class2|}? [|sOpt|])
+ {
+ {|CS0246:Class2|}? [|localOpt|];
+ }
+}",
+ FixedCode = @"
+#nullable enable
+
+public class Class1
+{
+ private {|CS0246:Class2|}? _instance;
+
+ public void Method1({|CS0246:Class2|}? s)
+ {
+ {|CS0246:Class2|}? local;
+ }
+}",
+ }.RunAsync();
+ }
+ }
+}