Skip to content

Commit

Permalink
Merge pull request #45880 from m-redding/feature/convertNameOf
Browse files Browse the repository at this point in the history
Feature/convert name of
  • Loading branch information
m-redding authored Jul 10, 2020
2 parents ffc47f7 + 74f94de commit 770a9a9
Show file tree
Hide file tree
Showing 4 changed files with 427 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ private void AnalyzeTypeOfAction(OperationAnalysisContext context)
return;
}

// Make sure the parent syntax is a member access expression otherwise the syntax is not the
// kind of expression that we want to analyze
if (!(node.Parent is MemberAccessExpressionSyntax))
{
return;
}

// Check if the operation is one that we want to offer the fix for
if (!IsValidOperation(context.Operation))
{
Expand All @@ -63,7 +70,7 @@ private void AnalyzeTypeOfAction(OperationAnalysisContext context)
return;
}

// Current case can be effectively changed to a nameof instance so report a diagnosti
// Current case can be effectively changed to a nameof instance so report a diagnostic
var location = parent.GetLocation();
context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor, location, ReportDiagnostic.Hidden, additionalLocations: null,
properties: null, messageArgs: null));
Expand All @@ -74,7 +81,7 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory()

private static bool IsValidOperation(IOperation operation)
{
// Cast to a typeof operation & check parent is a property reference
// Cast to a typeof operation & check parent is a property reference and member access
var typeofOperation = (ITypeOfOperation)operation;
if (!(operation.Parent is IPropertyReferenceOperation))
{
Expand All @@ -88,7 +95,9 @@ private static bool IsValidOperation(IOperation operation)
return false;
}

// If it's a generic type, do not offer the fix
// If it's a generic type, do not offer the fix because nameof(T) and typeof(T).name are not
// semantically equivalent, typeof().Name includes information about the actual type used
// by the generic while nameof loses this information during the standard identifier transformation
if (!(typeofOperation.TypeOperand is INamedTypeSymbol namedType) || namedType.IsGenericType)
{
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.ConvertTypeofToNameof
{
/// <summary>
/// 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 class CSharpConvertNameOfDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
public CSharpConvertNameOfDiagnosticAnalyzer()
: base(IDEDiagnosticIds.ConvertTypeOfToNameOfDiagnosticId,
option: null,
LanguageNames.CSharp,
new LocalizableResourceString(
nameof(CSharpAnalyzersResources.Convert_typeof_to_nameof), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
}

protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterOperationAction(AnalyzeTypeOfAction, OperationKind.TypeOf);
}

private void AnalyzeTypeOfAction(OperationAnalysisContext context)
{
var syntaxTree = context.Operation.Syntax.SyntaxTree;
var node = context.Operation.Syntax;

// Make sure that the syntax that we're looking at is actually a typeof expression
if (!(node is TypeOfExpressionSyntax))
{
return;
}

// nameof was added in CSharp 6.0, so don't offer it for any languages before that time
if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp6)
{
return;
}

// Make sure the parent syntax is a member access expression otherwise the syntax is not the
// kind of expression that we want to analyze
if (!(node.Parent is MemberAccessExpressionSyntax))
{
return;
}

// Check if the operation is one that we want to offer the fix for
if (!IsValidOperation(context.Operation))
{
return;
}

var parent = node.Parent;
// If the parent node is null then it cannot be a member access, so do not report a diagnostic
if (parent is null)
{
return;
}

// Current case can be effectively changed to a nameof instance so report a diagnostic
var location = parent.GetLocation();
context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor, location, ReportDiagnostic.Hidden, additionalLocations: null,
properties: null, messageArgs: null));
}
// Overwrite GetAnalyzerCategory
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;

private static bool IsValidOperation(IOperation operation)
{
// Cast to a typeof operation & check parent is a property reference and member access
var typeofOperation = (ITypeOfOperation)operation;
if (!(operation.Parent is IPropertyReferenceOperation))
{
return false;
}

// Check Parent is a .Name access
var operationParent = (IPropertyReferenceOperation)operation.Parent;
if (operationParent.Property.Name != nameof(System.Type.Name))
{
return false;
}

// If it's a generic type, do not offer the fix because nameof(T) and typeof(T).name are not
// semantically equivalent, typeof().Name includes information about the actual type used
// by the generic while nameof loses this information during the standard identifier transformation
if (!(typeofOperation.TypeOperand is INamedTypeSymbol namedType) || namedType.IsGenericType)
{
return false;
}
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.ConvertTypeofToNameof;
using Microsoft.CodeAnalysis.CSharp.CSharpConvertNameOfCodeFixProvider;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertTypeofToNameof
{
public partial class ConvertTypeofToNameofTests
{
[Fact]
[Trait(Traits.Feature, Traits.Features.ConvertTypeofToNameof)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task SingleDocumentBasic()
{
var input = @"class Test
{
static void Main()
{
var typeName1 = {|FixAllInDocument:typeof(Test).Name|};
var typeName2 = typeof(Test).Name;
var typeName3 = typeof(Test).Name;
}
}
";

var expected = @"class Test
{
static void Main()
{
var typeName1 = nameof(Test);
var typeName2 = nameof(Test);
var typeName3 = nameof(Test);
}
}
";

await TestInRegularAndScriptAsync(input, expected);
}

[Fact]
[Trait(Traits.Feature, Traits.Features.ConvertTypeofToNameof)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task SingleDocumentVaried()
{
var input = @"class Test
{
static void Main()
{
var typeName1 = {|FixAllInDocument:typeof(Test).Name|};
var typeName2 = typeof(int).Name;
var typeName3 = typeof(System.String).Name;
}
}
";

var expected = @"class Test
{
static void Main()
{
var typeName1 = nameof(Test);
var typeName2 = nameof(System.Int32);
var typeName3 = nameof(System.String);
}
}
";

await TestInRegularAndScriptAsync(input, expected);
}

[Fact]
[Trait(Traits.Feature, Traits.Features.Con)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task SingleDocumentVariedWithUsing()
{
var input = @"using System;
class Test
{
static void Main()
{
var typeName1 = typeof(Test).Name;
var typeName2 = typeof(int).Name;
var typeName3 = typeof(String).Name;
var typeName4 = {|FixAllInDocument:typeof(System.Double).Name|};
}
}
";

var expected = @"using System;
class Test
{
static void Main()
{
var typeName1 = nameof(Test);
var typeName2 = nameof(Int32);
var typeName3 = nameof(String);
var typeName4 = nameof(Double);
}
}
";

await TestInRegularAndScriptAsync(input, expected);
}
}
}
Loading

0 comments on commit 770a9a9

Please sign in to comment.