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

Update 'use nameof instead of typeof' to support generic types #76780

Merged
merged 2 commits into from
Jan 16, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using Microsoft.CodeAnalysis.ConvertTypeOfToNameOf;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

Expand All @@ -13,13 +14,13 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertTypeOfToNameOf;
/// Finds code like typeof(someType).Name and determines whether it can be changed to nameof(someType), if yes then it offers a diagnostic
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpConvertTypeOfToNameOfDiagnosticAnalyzer : AbstractConvertTypeOfToNameOfDiagnosticAnalyzer
internal sealed class CSharpConvertTypeOfToNameOfDiagnosticAnalyzer()
: AbstractConvertTypeOfToNameOfDiagnosticAnalyzer(s_title)
{
private static readonly string s_title = CSharpAnalyzersResources.typeof_can_be_converted_to_nameof;

public CSharpConvertTypeOfToNameOfDiagnosticAnalyzer() : base(s_title)
{
}
protected override bool SupportsUnboundGenerics(ParseOptions options)
=> options.LanguageVersion().IsCSharp14OrAbove();

protected override bool IsValidTypeofAction(OperationAnalysisContext context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.ConvertTypeOfToNameOf;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.UseUnboundGenericTypeInNameOf;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

Expand All @@ -17,12 +19,17 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertTypeOfToNameOf;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ConvertTypeOfToNameOf), Shared]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpConvertTypeOfToNameOfCodeFixProvider() : AbstractConvertTypeOfToNameOfCodeFixProvider<
MemberAccessExpressionSyntax>
internal sealed class CSharpConvertTypeOfToNameOfCodeFixProvider()
: AbstractConvertTypeOfToNameOfCodeFixProvider<MemberAccessExpressionSyntax>
{
protected override string GetCodeFixTitle()
=> CSharpCodeFixesResources.Convert_typeof_to_nameof;

protected override SyntaxNode ConvertToUnboundGeneric(ParseOptions options, SyntaxNode nameOfSyntax)
=> options.LanguageVersion().IsCSharp14OrAbove()
? CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.ConvertToUnboundGenericNameof(nameOfSyntax)
: nameOfSyntax;

protected override SyntaxNode GetSymbolTypeExpression(SemanticModel model, MemberAccessExpressionSyntax node, CancellationToken cancellationToken)
{
// The corresponding analyzer (CSharpConvertTypeOfToNameOfDiagnosticAnalyzer) validated the syntax
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,24 @@ private static void FixOne(
if (!nameofInvocation.IsNameOfInvocation())
return;

foreach (var typeArgumentList in nameofInvocation.DescendantNodes().OfType<TypeArgumentListSyntax>().OrderByDescending(t => t.SpanStart))
{
if (typeArgumentList.Arguments.Any(a => a.Kind() != SyntaxKind.OmittedTypeArgument))
editor.ReplaceNode(nameofInvocation, ConvertToUnboundGenericNameof(nameofInvocation));
}

public static TSyntax ConvertToUnboundGenericNameof<TSyntax>(TSyntax syntax)
where TSyntax : SyntaxNode
{
return syntax.ReplaceNodes(
syntax.DescendantNodes().OfType<TypeArgumentListSyntax>(),
(original, current) =>
{
var list = NodeOrTokenList(typeArgumentList.Arguments.GetWithSeparators().Select(
t => t.IsToken ? t.AsToken().WithoutTrivia() : s_omittedArgument));
editor.ReplaceNode(typeArgumentList, typeArgumentList.WithArguments(SeparatedList<TypeSyntax>(list)));
}
}
if (current.Arguments.Any(a => a.Kind() != SyntaxKind.OmittedTypeArgument))
{
var list = NodeOrTokenList(current.Arguments.GetWithSeparators().Select(
t => t.IsToken ? t.AsToken().WithoutTrivia() : s_omittedArgument));
return current.WithArguments(SeparatedList<TypeSyntax>(list));
}

return current;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertTypeOfToNameOf;
[Trait(Traits.Feature, Traits.Features.ConvertTypeOfToNameOf)]
public partial class ConvertTypeOfToNameOfTests
{
private static readonly LanguageVersion CSharp14 = LanguageVersion.Preview;

[Fact]
public async Task BasicType()
{
Expand Down Expand Up @@ -233,21 +235,111 @@ void Method()
}

[Fact]
public async Task NotInGenericType()
public async Task GenericType_CSharp13()
{
var text = """
class Test
{
class Goo<T>
{
void M()
{
_ = typeof(Goo<int>).Name;
await new VerifyCS.Test
{
TestCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = typeof(Goo<int>).Name;
}
}
}
}
""";
await VerifyCS.VerifyCodeFixAsync(text, text);
""",
LanguageVersion = LanguageVersion.CSharp13,
}.RunAsync();
}

[Fact]
public async Task GenericType_CSharp14()
{
await new VerifyCS.Test
{
TestCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = [|typeof(Goo<int>).Name|];
}
}
}
""",
FixedCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = nameof(Goo<>);
}
}
}
""",
LanguageVersion = CSharp14,
}.RunAsync();
}

[Fact]
public async Task UnboundGenericType_CSharp13()
{
await new VerifyCS.Test
{
TestCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = typeof(Goo<>).Name;
}
}
}
""",
LanguageVersion = LanguageVersion.CSharp13,
}.RunAsync();
}

[Fact]
public async Task UnboundGenericType_CSharp14()
{
await new VerifyCS.Test
{
TestCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = [|typeof(Goo<>).Name|];
}
}
}
""",
FixedCode = """
class Test
{
class Goo<T>
{
void M()
{
_ = nameof(Goo<>);
}
}
}
""",
LanguageVersion = CSharp14,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")]
Expand Down Expand Up @@ -287,33 +379,123 @@ void M()
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")]
public async Task NestedInGenericType2()
public async Task NestedInGenericType2_CSharp13()
{
var text = """
using System;
using System.Collections.Generic;
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
class Test
{
Console.WriteLine([|typeof(List<int>.Enumerator).Name|]);
public void M()
{
Console.WriteLine([|typeof(List<int>.Enumerator).Name|]);
}
}
}
""";
var expected = """
using System;
using System.Collections.Generic;
""",
FixedCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
class Test
{
Console.WriteLine(nameof(List<Int32>.Enumerator));
public void M()
{
Console.WriteLine(nameof(List<Int32>.Enumerator));
}
}
}
""";
await VerifyCS.VerifyCodeFixAsync(text, expected);
""",
LanguageVersion = LanguageVersion.CSharp13,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")]
public async Task NestedInGenericType2_CSharp14()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
{
Console.WriteLine([|typeof(List<int>.Enumerator).Name|]);
}
}
""",
FixedCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
{
Console.WriteLine(nameof(List<>.Enumerator));
}
}
""",
LanguageVersion = CSharp14,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")]
public async Task NestedInGenericType_UnboundTypeof_CSharp13()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
{
Console.WriteLine([|typeof(List<>.Enumerator).Name|]);
}
}
""",
LanguageVersion = LanguageVersion.CSharp13,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")]
public async Task NestedInGenericType_UnboundTypeof_CSharp14()
{
await new VerifyCS.Test
{
TestCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
{
Console.WriteLine([|typeof(List<>.Enumerator).Name|]);
}
}
""",
FixedCode = """
using System;
using System.Collections.Generic;

class Test
{
public void M()
{
Console.WriteLine(nameof(List<>.Enumerator));
}
}
""",
LanguageVersion = CSharp14,
}.RunAsync();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/54233")]
Expand Down
Loading
Loading