未验证 提交 3b3e771a 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #47732 from allisonchou/GenerateConstructorNaming

Fix for generate constructor naming bug
......@@ -2527,7 +2527,7 @@ class C
{
void M(IEnumerable<int> nums)
{
IEnumerable<int> queryables()
IEnumerable<int> queryable()
{
foreach (int n1 in nums.AsQueryable())
{
......@@ -2535,7 +2535,7 @@ IEnumerable<int> queryables()
}
}
IEnumerable<int> q = queryables();
IEnumerable<int> q = queryable();
}
}";
......
......@@ -4215,6 +4215,162 @@ public A(int* a, int b) : this(a)
}
public A(int* a, int b, int c) : this(a, b) { }
}");
}
[WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)]
public async Task TestGenerateNameFromTypeArgument()
{
await TestInRegularAndScriptAsync(
@"using System.Collections.Generic;
class Frog { }
class C
{
C M() => new [||]C(new List<Frog>());
}",
@"using System.Collections.Generic;
class Frog { }
class C
{
private List<Frog> frogs;
public C(List<Frog> frogs)
{
this.frogs = frogs;
}
C M() => new C(new List<Frog>());
}");
}
[WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)]
public async Task TestDoNotGenerateNameFromTypeArgumentIfNotEnumerable()
{
await TestInRegularAndScriptAsync(
@"class Frog<T> { }
class C
{
C M()
{
return new [||]C(new Frog<int>());
}
}",
@"class Frog<T> { }
class C
{
private Frog<int> frog;
public C(Frog<int> frog)
{
this.frog = frog;
}
C M()
{
return new C(new Frog<int>());
}
}");
}
[WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)]
public async Task TestGenerateNameFromTypeArgumentForErrorType()
{
await TestInRegularAndScriptAsync(
@"using System.Collections.Generic;
class Frog { }
class C
{
C M() => new [||]C(new List<>());
}",
@"using System.Collections.Generic;
class Frog { }
class C
{
private List<T> ts;
public C(List<T> ts)
{
this.ts = ts;
}
C M() => new C(new List<>());
}");
}
[WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)]
public async Task TestGenerateNameFromTypeArgumentForTupleType()
{
await TestInRegularAndScriptAsync(
@"using System.Collections.Generic;
class Frog { }
class C
{
C M() => new [||]C(new List<(int, string)>());
}",
@"using System.Collections.Generic;
class Frog { }
class C
{
private List<(int, string)> list;
public C(List<(int, string)> list)
{
this.list = list;
}
C M() => new C(new List<(int, string)>());
}");
}
[WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)]
public async Task TestGenerateNameFromTypeArgumentInNamespace()
{
await TestInRegularAndScriptAsync(
@"using System.Collections.Generic;
namespace N {
class Frog { }
class C
{
C M() => new [||]C(new List<Frog>());
}
}",
@"using System.Collections.Generic;
namespace N {
class Frog { }
class C
{
private List<Frog> frogs;
public C(List<Frog> frogs)
{
this.frogs = frogs;
}
C M() => new C(new List<Frog>());
}
}");
}
}
......
......@@ -1634,8 +1634,8 @@ End Class")
End Module",
"Module Program
Sub Main()
Dim {|Rename:v|} As Integer() = New Integer() {}
Return v
Dim {|Rename:vs|} As Integer() = New Integer() {}
Return vs
End Sub
End Module")
End Function
......@@ -1953,8 +1953,8 @@ End Class",
Class C
Shared Sub Main()
Dim {|Rename:v|} As Integer() = New C().Goo()
Dim x = v(0)
Dim {|Rename:vs|} As Integer() = New C().Goo()
Dim x = vs(0)
End Sub
Function Goo() As Integer()
End Function
......
......@@ -2635,7 +2635,7 @@ Class M1
sub1(Of Integer, String)(New Integer() {1, 2, 3}, New String() {"a", "b"})
End Sub
Private Sub sub1(Of T1, T2)(v1() As T1, v2() As T2)
Private Sub sub1(Of T1, T2)(vs1() As T1, vs2() As T2)
Throw New NotImplementedException()
End Sub
End Class
......
......@@ -1434,21 +1434,22 @@ End Enum
Public Class MyAttribute
Inherits System.Attribute
Private v1 As Short()
Private vs As Short()
Private a1 As A
Private v2 As Boolean
Private v3 As Integer
Private v4 As Char
Private v5 As Short
Private v6 As Integer
Private v7 As Long
Private v8 As Double
Private v9 As Single
Private v10 As String
Public Sub New(v1() As Short, a1 As A, v2 As Boolean, v3 As Integer, v4 As Char, v5 As Short, v6 As Integer, v7 As Long, v8 As Double, v9 As Single, v10 As String)
Me.v1 = v1
Private v1 As Boolean
Private v2 As Integer
Private v3 As Char
Private v4 As Short
Private v5 As Integer
Private v6 As Long
Private v7 As Double
Private v8 As Single
Private v9 As String
Public Sub New(vs() As Short, a1 As A, v1 As Boolean, v2 As Integer, v3 As Char, v4 As Short, v5 As Integer, v6 As Long, v7 As Double, v8 As Single, v9 As String)
Me.vs = vs
Me.a1 = a1
Me.v1 = v1
Me.v2 = v2
Me.v3 = v3
Me.v4 = v4
......@@ -1457,7 +1458,6 @@ Public Class MyAttribute
Me.v7 = v7
Me.v8 = v8
Me.v9 = v9
Me.v10 = v10
End Sub
End Class
<MyAttribute(New Short(1) {1, 2, 3}, A.A1, True, 1, ""Z""c, 5S, 1I, 5L, 6.0R, 2.1F, ""abc"")>
......@@ -1984,6 +1984,70 @@ Class Test
Me.v = v
End Sub
End Class
")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)>
<WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")>
Public Async Function TestGenerateNameFromTypeArgument() As Task
Await TestInRegularAndScriptAsync(
"Imports System.Collections.Generic
Class Frog
End Class
Class C
Private Function M() As C
Return New C([||]New List(Of Frog)())
End Function
End Class
",
"Imports System.Collections.Generic
Class Frog
End Class
Class C
Private frogs As List(Of Frog)
Public Sub New(frogs As List(Of Frog))
Me.frogs = frogs
End Sub
Private Function M() As C
Return New C(New List(Of Frog)())
End Function
End Class
")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructor)>
<WorkItem(44708, "https://github.com/dotnet/roslyn/issues/44708")>
Public Async Function TestDoNotGenerateNameFromTypeArgumentIfNotEnumerable() As Task
Await TestInRegularAndScriptAsync(
"Class Frog(Of T)
End Class
Class C
Private Function M() As C
Return New C([||]New Frog(Of Integer)())
End Function
End Class
",
"Class Frog(Of T)
End Class
Class C
Private frog As Frog(Of Integer)
Public Sub New(frog As Frog(Of Integer))
Me.frog = frog
End Sub
Private Function M() As C
Return New C(New Frog(Of Integer)())
End Function
End Class
")
End Function
End Class
......
......@@ -14,11 +14,14 @@
using Humanizer;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageServices;
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
internal static partial class SemanticModelExtensions
{
private const string DefaultParameterName = "p";
public static ImmutableArray<ParameterName> GenerateParameterNames(
this SemanticModel semanticModel,
ArgumentListSyntax argumentList,
......@@ -219,25 +222,12 @@ private static ImmutableArray<ParameterName> GenerateNames(IList<string> reserve
// Otherwise, figure out the type of the expression and generate a name from that
// instead.
var info = semanticModel.GetTypeInfo(expression, cancellationToken);
if (info.Type == null)
{
return DefaultParameterName;
}
// If we can't determine the type, then fallback to some placeholders.
var type = info.Type;
var pluralize = Pluralize(semanticModel, type);
var parameterName = type.CreateParameterName(capitalize);
return pluralize ? parameterName.Pluralize() : parameterName;
}
private static bool Pluralize(SemanticModel semanticModel, ITypeSymbol type)
{
if (type == null)
return false;
if (type.SpecialType == SpecialType.System_String)
return false;
var enumerableType = semanticModel.Compilation.IEnumerableOfTType();
return type.AllInterfaces.Any(i => i.OriginalDefinition.Equals(enumerableType));
return semanticModel.GenerateNameFromType(info.Type, CSharpSyntaxFacts.Instance, capitalize);
}
private static string TryGenerateNameForArgumentExpression(
......
......@@ -51,7 +51,7 @@
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.BuildManager.UnitTests" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.Orchestrator" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.Orchestrator.UnitTests" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.Test.Utilities" Partner="UnitTesting" Key="$(UnitTestingKey)"/>
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.LiveUnitTesting.Test.Utilities" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LanguageServer.Protocol.UnitTests" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator" />
......@@ -59,7 +59,7 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.ServiceHub" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.Workspaces" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery.UnitTests" Partner="UnitTesting" Key="$(UnitTestingKey)"/>
<RestrictedInternalsVisibleTo Include="Microsoft.CodeAnalysis.UnitTesting.SourceBasedTestDiscovery.UnitTests" Partner="UnitTesting" Key="$(UnitTestingKey)" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.EditorFeatures" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.Features" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" />
......@@ -136,6 +136,9 @@
<ItemGroup>
<EmbeddedResource Update="WorkspacesResources.resx" GenerateSource="true" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="$(HumanizerCoreVersion)" PrivateAssets="compile" />
</ItemGroup>
<ItemGroup>
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
......
......@@ -6,10 +6,13 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Humanizer;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
......@@ -171,5 +174,64 @@ public static SemanticMap GetSemanticMap(this SemanticModel semanticModel, Synta
return new TokenSemanticInfo(declaredSymbol, aliasSymbol, allSymbols, type, convertedType, token.Span);
}
public static string GenerateNameFromType(this SemanticModel semanticModel, ITypeSymbol type, ISyntaxFacts syntaxFacts, bool capitalize)
{
var pluralize = semanticModel.ShouldPluralize(type);
var typeArguments = type.GetAllTypeArguments();
// We may be able to use the type's arguments to generate a name if we're working with an enumerable type.
if (pluralize && TryGeneratePluralizedNameFromTypeArgument(syntaxFacts, typeArguments, capitalize, out var typeArgumentParameterName))
{
return typeArgumentParameterName;
}
// If there's no type argument and we have an array type, we should pluralize, e.g. using 'frogs' for 'new Frog[]' instead of 'frog'
if (type.TypeKind == TypeKind.Array && typeArguments.IsEmpty)
{
return type.CreateParameterName(capitalize).Pluralize();
}
// Otherwise assume no pluralization, e.g. using 'immutableArray', 'list', etc. instead of their
// plural forms
return type.CreateParameterName(capitalize);
}
private static bool ShouldPluralize(this SemanticModel semanticModel, ITypeSymbol type)
{
if (type == null)
return false;
// string implements IEnumerable<char>, so we need to specifically exclude it.
if (type.SpecialType == SpecialType.System_String)
return false;
var enumerableType = semanticModel.Compilation.IEnumerableOfTType();
return type.AllInterfaces.Any(i => i.OriginalDefinition.Equals(enumerableType));
}
private static bool TryGeneratePluralizedNameFromTypeArgument(
ISyntaxFacts syntaxFacts,
ImmutableArray<ITypeSymbol> typeArguments,
bool capitalize,
[NotNullWhen(true)] out string? parameterName)
{
// We only consider generating a name if there's one type argument.
// This logic can potentially be expanded upon in the future.
if (typeArguments.Length == 1)
{
// We only want the last part of the type, i.e. we don't want namespaces.
var typeArgument = typeArguments.Single().ToDisplayParts().Last().ToString();
if (syntaxFacts.IsValidIdentifier(typeArgument))
{
typeArgument = typeArgument.Pluralize();
parameterName = capitalize ? typeArgument.ToPascalCase() : typeArgument.ToCamelCase();
return true;
}
}
parameterName = null;
return false;
}
}
}
......@@ -10,9 +10,6 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Partial Friend Module SemanticModelExtensions
Private Const s_defaultParameterName = "p"
<Extension()>
Public Function LookupTypeRegardlessOfArity(semanticModel As SemanticModel,
name As SyntaxToken,
......@@ -78,97 +75,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Return semanticModel.GetSymbolInfo(expression)
End Function
<Extension()>
Public Function GenerateNameForArgument(semanticModel As SemanticModel,
argument As ArgumentSyntax,
cancellationToken As CancellationToken) As String
Dim result = GenerateNameForArgumentWorker(semanticModel, argument, cancellationToken)
Return If(String.IsNullOrWhiteSpace(result), s_defaultParameterName, result)
End Function
Private Function GenerateNameForArgumentWorker(semanticModel As SemanticModel,
argument As ArgumentSyntax,
cancellationToken As CancellationToken) As String
If argument.IsNamed Then
Return DirectCast(argument, SimpleArgumentSyntax).NameColonEquals.Name.Identifier.ValueText
ElseIf Not argument.IsOmitted Then
Return semanticModel.GenerateNameForExpression(
argument.GetExpression(), capitalize:=False, cancellationToken:=cancellationToken)
Else
Return s_defaultParameterName
End If
End Function
''' <summary>
''' Given an expression node, tries to generate an appropriate name that can be used for
''' that expression.
''' </summary>
<Extension()>
Public Function GenerateNameForExpression(semanticModel As SemanticModel,
expression As ExpressionSyntax,
capitalize As Boolean,
cancellationToken As CancellationToken) As String
' Try to find a usable name node that we can use to name the
' parameter. If we have an expression that has a name as part of it
' then we try to use that part.
Dim current = expression
While True
current = current.WalkDownParentheses()
If current.Kind = SyntaxKind.IdentifierName Then
Return (DirectCast(current, IdentifierNameSyntax)).Identifier.ValueText.ToCamelCase()
ElseIf TypeOf current Is MemberAccessExpressionSyntax Then
Return (DirectCast(current, MemberAccessExpressionSyntax)).Name.Identifier.ValueText.ToCamelCase()
ElseIf TypeOf current Is CastExpressionSyntax Then
current = (DirectCast(current, CastExpressionSyntax)).Expression
Else
Exit While
End If
End While
' there was nothing in the expression to signify a name. If we're in an argument
' location, then try to choose a name based on the argument name.
Dim argumentName = TryGenerateNameForArgumentExpression(
semanticModel, expression, cancellationToken)
If argumentName IsNot Nothing Then
Return If(capitalize, argumentName.ToPascalCase(), argumentName.ToCamelCase())
End If
' Otherwise, figure out the type of the expression and generate a name from that
' instead.
Dim info = semanticModel.GetTypeInfo(expression, cancellationToken)
' If we can't determine the type, then fallback to some placeholders.
Dim [type] = info.Type
Return [type].CreateParameterName(capitalize)
End Function
Private Function TryGenerateNameForArgumentExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, cancellationToken As CancellationToken) As String
Dim topExpression = expression.WalkUpParentheses()
If TypeOf topExpression.Parent Is ArgumentSyntax Then
Dim argument = DirectCast(topExpression.Parent, ArgumentSyntax)
Dim simpleArgument = TryCast(argument, SimpleArgumentSyntax)
If simpleArgument?.NameColonEquals IsNot Nothing Then
Return simpleArgument.NameColonEquals.Name.Identifier.ValueText
End If
Dim argumentList = TryCast(argument.Parent, ArgumentListSyntax)
If argumentList IsNot Nothing Then
Dim index = argumentList.Arguments.IndexOf(argument)
Dim member = TryCast(semanticModel.GetSymbolInfo(argumentList.Parent, cancellationToken).Symbol, IMethodSymbol)
If member IsNot Nothing AndAlso index < member.Parameters.Length Then
Dim parameter = member.Parameters(index)
If parameter.Type.TypeKind <> TypeKind.TypeParameter Then
Return parameter.Name
End If
End If
End If
End If
Return Nothing
End Function
<Extension()>
Public Function GetImportNamespacesInScope(semanticModel As SemanticModel, location As SyntaxNode) As ISet(Of INamespaceSymbol)
Dim q =
......
......@@ -9,10 +9,13 @@ Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
Imports Microsoft.CodeAnalysis.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Partial Friend Module SemanticModelExtensions
Private Const DefaultParameterName = "p"
<Extension()>
Public Function GenerateParameterNames(semanticModel As SemanticModel,
arguments As ArgumentListSyntax,
......@@ -95,5 +98,97 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Select(Function(name, index) New ParameterName(name, isFixed(index), parameterNamingRule)).
ToImmutableArray()
End Function
<Extension()>
Public Function GenerateNameForArgument(semanticModel As SemanticModel,
argument As ArgumentSyntax,
cancellationToken As CancellationToken) As String
Dim result = GenerateNameForArgumentWorker(semanticModel, argument, cancellationToken)
Return If(String.IsNullOrWhiteSpace(result), DefaultParameterName, result)
End Function
Private Function GenerateNameForArgumentWorker(semanticModel As SemanticModel,
argument As ArgumentSyntax,
cancellationToken As CancellationToken) As String
If argument.IsNamed Then
Return DirectCast(argument, SimpleArgumentSyntax).NameColonEquals.Name.Identifier.ValueText
ElseIf Not argument.IsOmitted Then
Return semanticModel.GenerateNameForExpression(
argument.GetExpression(), capitalize:=False, cancellationToken:=cancellationToken)
Else
Return DefaultParameterName
End If
End Function
''' <summary>
''' Given an expression node, tries to generate an appropriate name that can be used for
''' that expression.
''' </summary>
<Extension()>
Public Function GenerateNameForExpression(semanticModel As SemanticModel,
expression As ExpressionSyntax,
capitalize As Boolean,
cancellationToken As CancellationToken) As String
' Try to find a usable name node that we can use to name the
' parameter. If we have an expression that has a name as part of it
' then we try to use that part.
Dim current = expression
While True
current = current.WalkDownParentheses()
If current.Kind = SyntaxKind.IdentifierName Then
Return (DirectCast(current, IdentifierNameSyntax)).Identifier.ValueText.ToCamelCase()
ElseIf TypeOf current Is MemberAccessExpressionSyntax Then
Return (DirectCast(current, MemberAccessExpressionSyntax)).Name.Identifier.ValueText.ToCamelCase()
ElseIf TypeOf current Is CastExpressionSyntax Then
current = (DirectCast(current, CastExpressionSyntax)).Expression
Else
Exit While
End If
End While
' there was nothing in the expression to signify a name. If we're in an argument
' location, then try to choose a name based on the argument name.
Dim argumentName = TryGenerateNameForArgumentExpression(
semanticModel, expression, cancellationToken)
If argumentName IsNot Nothing Then
Return If(capitalize, argumentName.ToPascalCase(), argumentName.ToCamelCase())
End If
' Otherwise, figure out the type of the expression and generate a name from that
' instead.
Dim info = semanticModel.GetTypeInfo(expression, cancellationToken)
If info.Type Is Nothing Then
Return DefaultParameterName
End If
Return semanticModel.GenerateNameFromType(info.Type, VisualBasicSyntaxFacts.Instance, capitalize)
End Function
Private Function TryGenerateNameForArgumentExpression(semanticModel As SemanticModel, expression As ExpressionSyntax, cancellationToken As CancellationToken) As String
Dim topExpression = expression.WalkUpParentheses()
If TypeOf topExpression.Parent Is ArgumentSyntax Then
Dim argument = DirectCast(topExpression.Parent, ArgumentSyntax)
Dim simpleArgument = TryCast(argument, SimpleArgumentSyntax)
If simpleArgument?.NameColonEquals IsNot Nothing Then
Return simpleArgument.NameColonEquals.Name.Identifier.ValueText
End If
Dim argumentList = TryCast(argument.Parent, ArgumentListSyntax)
If argumentList IsNot Nothing Then
Dim index = argumentList.Arguments.IndexOf(argument)
Dim member = TryCast(semanticModel.GetSymbolInfo(argumentList.Parent, cancellationToken).Symbol, IMethodSymbol)
If member IsNot Nothing AndAlso index < member.Parameters.Length Then
Dim parameter = member.Parameters(index)
If parameter.Type.TypeKind <> TypeKind.TypeParameter Then
Return parameter.Name
End If
End If
End If
End If
Return Nothing
End Function
End Module
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册