Skip to content

Commit

Permalink
IDE Support for Required Members (#61440)
Browse files Browse the repository at this point in the history
* Add required keyword recommender.

* Add SyntaxNormalizer test.

* Code generation support.

* Add SymbolDisplay

* F1 help service and test fix.

* Add order modifier tests and update.
  • Loading branch information
333fred authored May 27, 2022
1 parent 8452ec2 commit ba92379
Show file tree
Hide file tree
Showing 44 changed files with 724 additions and 66 deletions.
34 changes: 34 additions & 0 deletions src/Analyzers/CSharp/Tests/OrderModifiers/OrderModifiersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -431,5 +431,39 @@ static internal class C2
static internal class Nested { }
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsOrderModifiers)]
public async Task RequiredAfterAllOnProp()
{
await TestInRegularAndScriptAsync("""
class C
{
[|required|] public virtual unsafe int Prop { get; init; }
}
""",
"""
class C
{
public virtual unsafe required int Prop { get; init; }
}
""");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsOrderModifiers)]
public async Task RequiredAfterAllButVolatileOnField()
{
await TestInRegularAndScriptAsync("""
class C
{
[|required|] public unsafe volatile int Field;
}
""",
"""
class C
{
public unsafe required volatile int Field;
}
""");
}
}
}
3 changes: 3 additions & 0 deletions src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsOverride.get -> b
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsPartial.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsReadOnly.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRef.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRequired.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsSealed.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsStatic.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsUnsafe.get -> bool
Expand All @@ -56,6 +57,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsNew(bool isNe
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsOverride(bool isOverride) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsReadOnly(bool isReadOnly) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRef(bool isRef) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRequired(bool isRequired) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsSealed(bool isSealed) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsStatic(bool isStatic) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsUnsafe(bool isUnsafe) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Expand Down Expand Up @@ -84,6 +86,7 @@ static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Override.get
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Partial.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.ReadOnly.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Ref.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Required.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Sealed.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Static.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.TryParse(string value, out Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers modifiers) -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ private void VisitFieldType(IFieldSymbol symbol)

public override void VisitField(IFieldSymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddFieldModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);
AddFieldModifiersIfNeeded(symbol);

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType) &&
this.isFirstSymbolVisited &&
Expand All @@ -38,7 +38,7 @@ public override void VisitField(IFieldSymbol symbol)
VisitFieldType(symbol);
AddSpace();

AddCustomModifiersIfRequired(symbol.CustomModifiers);
AddCustomModifiersIfNeeded(symbol.CustomModifiers);
}

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeContainingType) &&
Expand Down Expand Up @@ -122,31 +122,31 @@ private static bool ShouldMethodDisplayReadOnly(IMethodSymbol method, IPropertyS

public override void VisitProperty(IPropertySymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

if (ShouldPropertyDisplayReadOnly(symbol))
{
AddReadOnlyIfRequired();
AddReadOnlyIfNeeded();
}

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType))
{
if (symbol.ReturnsByRef)
{
AddRefIfRequired();
AddRefIfNeeded();
}
else if (symbol.ReturnsByRefReadonly)
{
AddRefReadonlyIfRequired();
AddRefReadonlyIfNeeded();
}

AddCustomModifiersIfRequired(symbol.RefCustomModifiers);
AddCustomModifiersIfNeeded(symbol.RefCustomModifiers);

symbol.Type.Accept(this.NotFirstVisitor);
AddSpace();

AddCustomModifiersIfRequired(symbol.TypeCustomModifiers);
AddCustomModifiersIfNeeded(symbol.TypeCustomModifiers);
}

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeContainingType) &&
Expand Down Expand Up @@ -183,7 +183,7 @@ private void AddPropertyNameAndParameters(IPropertySymbol symbol)

if (getMemberNameWithoutInterfaceName)
{
AddExplicitInterfaceIfRequired(symbol.ExplicitInterfaceImplementations);
AddExplicitInterfaceIfNeeded(symbol.ExplicitInterfaceImplementations);
}

if (symbol.IsIndexer)
Expand All @@ -203,20 +203,20 @@ private void AddPropertyNameAndParameters(IPropertySymbol symbol)
if (this.format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeParameters) && symbol.Parameters.Any())
{
AddPunctuation(SyntaxKind.OpenBracketToken);
AddParametersIfRequired(hasThisParameter: false, isVarargs: false, parameters: symbol.Parameters);
AddParametersIfNeeded(hasThisParameter: false, isVarargs: false, parameters: symbol.Parameters);
AddPunctuation(SyntaxKind.CloseBracketToken);
}
}

public override void VisitEvent(IEventSymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

var accessor = symbol.AddMethod ?? symbol.RemoveMethod;
if (accessor is object && ShouldMethodDisplayReadOnly(accessor))
{
AddReadOnlyIfRequired();
AddReadOnlyIfNeeded();
}

if (format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeMemberKeyword))
Expand Down Expand Up @@ -245,7 +245,7 @@ private void AddEventName(IEventSymbol symbol)
{
if (symbol.Name.LastIndexOf('.') > 0)
{
AddExplicitInterfaceIfRequired(symbol.ExplicitInterfaceImplementations);
AddExplicitInterfaceIfNeeded(symbol.ExplicitInterfaceImplementations);

this.builder.Add(CreatePart(SymbolDisplayPartKind.EventName, symbol,
ExplicitInterfaceHelpers.GetMemberNameWithoutInterfaceName(symbol.Name)));
Expand Down Expand Up @@ -300,12 +300,12 @@ public override void VisitMethod(IMethodSymbol symbol)

if ((object)symbol.ContainingType != null || (symbol.ContainingSymbol is ITypeSymbol))
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

if (ShouldMethodDisplayReadOnly(symbol))
{
AddReadOnlyIfRequired();
AddReadOnlyIfNeeded();
}

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType))
Expand Down Expand Up @@ -333,14 +333,14 @@ public override void VisitMethod(IMethodSymbol symbol)

if (symbol.ReturnsByRef)
{
AddRefIfRequired();
AddRefIfNeeded();
}
else if (symbol.ReturnsByRefReadonly)
{
AddRefReadonlyIfRequired();
AddRefReadonlyIfNeeded();
}

AddCustomModifiersIfRequired(symbol.RefCustomModifiers);
AddCustomModifiersIfNeeded(symbol.RefCustomModifiers);

if (symbol.ReturnsVoid)
{
Expand All @@ -352,7 +352,7 @@ public override void VisitMethod(IMethodSymbol symbol)
}

AddSpace();
AddCustomModifiersIfRequired(symbol.ReturnTypeCustomModifiers);
AddCustomModifiersIfNeeded(symbol.ReturnTypeCustomModifiers);
break;
}
}
Expand Down Expand Up @@ -476,7 +476,7 @@ public override void VisitMethod(IMethodSymbol symbol)
}
case MethodKind.ExplicitInterfaceImplementation:
{
AddExplicitInterfaceIfRequired(symbol.ExplicitInterfaceImplementations);
AddExplicitInterfaceIfNeeded(symbol.ExplicitInterfaceImplementations);

if (!format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) &&
symbol.GetSymbol()?.OriginalDefinition is SourceUserDefinedOperatorSymbolBase sourceUserDefinedOperatorSymbolBase)
Expand Down Expand Up @@ -601,11 +601,11 @@ void visitFunctionPointerSignature(IMethodSymbol symbol)
{
AddParameterRefKind(param.RefKind);

AddCustomModifiersIfRequired(param.RefCustomModifiers);
AddCustomModifiersIfNeeded(param.RefCustomModifiers);

param.Type.Accept(this.NotFirstVisitor);

AddCustomModifiersIfRequired(param.CustomModifiers, leadingSpace: true, trailingSpace: false);
AddCustomModifiersIfNeeded(param.CustomModifiers, leadingSpace: true, trailingSpace: false);

AddPunctuation(SyntaxKind.CommaToken);
AddSpace();
Expand All @@ -620,11 +620,11 @@ void visitFunctionPointerSignature(IMethodSymbol symbol)
AddRefReadonly();
}

AddCustomModifiersIfRequired(symbol.RefCustomModifiers);
AddCustomModifiersIfNeeded(symbol.RefCustomModifiers);

symbol.ReturnType.Accept(this.NotFirstVisitor);

AddCustomModifiersIfRequired(symbol.ReturnTypeCustomModifiers, leadingSpace: true, trailingSpace: false);
AddCustomModifiersIfNeeded(symbol.ReturnTypeCustomModifiers, leadingSpace: true, trailingSpace: false);

AddPunctuation(SyntaxKind.GreaterThanToken);
}
Expand Down Expand Up @@ -723,7 +723,7 @@ private void AddParameters(IMethodSymbol symbol)
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeParameters))
{
AddPunctuation(SyntaxKind.OpenParenToken);
AddParametersIfRequired(
AddParametersIfNeeded(
hasThisParameter: symbol.IsExtensionMethod && symbol.MethodKind != MethodKind.ReducedExtension,
isVarargs: symbol.IsVararg,
parameters: symbol.Parameters);
Expand Down Expand Up @@ -755,8 +755,8 @@ public override void VisitParameter(IParameterSymbol symbol)

if (includeType)
{
AddParameterRefKindIfRequired(symbol.RefKind);
AddCustomModifiersIfRequired(symbol.RefCustomModifiers, leadingSpace: false, trailingSpace: true);
AddParameterRefKindIfNeeded(symbol.RefKind);
AddCustomModifiersIfNeeded(symbol.RefCustomModifiers, leadingSpace: false, trailingSpace: true);

if (symbol.IsParams && format.ParameterOptions.IncludesOption(SymbolDisplayParameterOptions.IncludeParamsRefOut))
{
Expand All @@ -765,7 +765,7 @@ public override void VisitParameter(IParameterSymbol symbol)
}

symbol.Type.Accept(this.NotFirstVisitor);
AddCustomModifiersIfRequired(symbol.CustomModifiers, leadingSpace: true, trailingSpace: false);
AddCustomModifiersIfNeeded(symbol.CustomModifiers, leadingSpace: true, trailingSpace: false);
}

if (includeName)
Expand Down Expand Up @@ -811,7 +811,7 @@ private static bool CanAddConstant(ITypeSymbol type, object value)
return value.GetType().GetTypeInfo().IsPrimitive || value is string || value is decimal;
}

private void AddFieldModifiersIfRequired(IFieldSymbol symbol)
private void AddFieldModifiersIfNeeded(IFieldSymbol symbol)
{
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeModifiers) &&
!IsEnumMember(symbol))
Expand All @@ -838,7 +838,7 @@ private void AddFieldModifiersIfRequired(IFieldSymbol symbol)
}
}

private void AddMemberModifiersIfRequired(ISymbol symbol)
private void AddMemberModifiersIfNeeded(ISymbol symbol)
{
INamedTypeSymbol containingType = symbol.ContainingType;

Expand All @@ -850,6 +850,7 @@ private void AddMemberModifiersIfRequired(ISymbol symbol)
(containingType.TypeKind != TypeKind.Interface && !IsEnumMember(symbol) && !IsLocalFunction(symbol))))
{
var isConst = symbol is IFieldSymbol && ((IFieldSymbol)symbol).IsConst;
var isRequired = symbol is IFieldSymbol { IsRequired: true } or IPropertySymbol { IsRequired: true };
if (symbol.IsStatic && !isConst)
{
AddKeyword(SyntaxKind.StaticKeyword);
Expand Down Expand Up @@ -885,10 +886,16 @@ private void AddMemberModifiersIfRequired(ISymbol symbol)
AddKeyword(SyntaxKind.VirtualKeyword);
AddSpace();
}

if (isRequired)
{
AddKeyword(SyntaxKind.RequiredKeyword);
AddSpace();
}
}
}

private void AddParametersIfRequired(bool hasThisParameter, bool isVarargs, ImmutableArray<IParameterSymbol> parameters)
private void AddParametersIfNeeded(bool hasThisParameter, bool isVarargs, ImmutableArray<IParameterSymbol> parameters)
{
if (format.ParameterOptions == SymbolDisplayParameterOptions.None)
{
Expand Down Expand Up @@ -948,15 +955,15 @@ private void AddAccessor(IPropertySymbol property, IMethodSymbol method, SyntaxK

if (!ShouldPropertyDisplayReadOnly(property) && ShouldMethodDisplayReadOnly(method, property))
{
AddReadOnlyIfRequired();
AddReadOnlyIfNeeded();
}

AddKeyword(keyword);
AddPunctuation(SyntaxKind.SemicolonToken);
}
}

private void AddExplicitInterfaceIfRequired<T>(ImmutableArray<T> implementedMembers) where T : ISymbol
private void AddExplicitInterfaceIfNeeded<T>(ImmutableArray<T> implementedMembers) where T : ISymbol
{
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeExplicitInterface) && !implementedMembers.IsEmpty)
{
Expand All @@ -972,7 +979,7 @@ private void AddExplicitInterfaceIfRequired<T>(ImmutableArray<T> implementedMemb
}
}

private void AddCustomModifiersIfRequired(ImmutableArray<CustomModifier> customModifiers, bool leadingSpace = false, bool trailingSpace = true)
private void AddCustomModifiersIfNeeded(ImmutableArray<CustomModifier> customModifiers, bool leadingSpace = false, bool trailingSpace = true)
{
if (this.format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers) && !customModifiers.IsEmpty)
{
Expand All @@ -997,7 +1004,7 @@ private void AddCustomModifiersIfRequired(ImmutableArray<CustomModifier> customM
}
}

private void AddRefIfRequired()
private void AddRefIfNeeded()
{
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeRef))
{
Expand All @@ -1011,7 +1018,7 @@ private void AddRef()
AddSpace();
}

private void AddRefReadonlyIfRequired()
private void AddRefReadonlyIfNeeded()
{
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeRef))
{
Expand All @@ -1027,7 +1034,7 @@ private void AddRefReadonly()
AddSpace();
}

private void AddReadOnlyIfRequired()
private void AddReadOnlyIfNeeded()
{
// 'readonly' in this context is effectively a 'ref' modifier
// because it affects whether the 'this' parameter is 'ref' or 'in'.
Expand All @@ -1038,7 +1045,7 @@ private void AddReadOnlyIfRequired()
}
}

private void AddParameterRefKindIfRequired(RefKind refKind)
private void AddParameterRefKindIfNeeded(RefKind refKind)
{
if (format.ParameterOptions.IncludesOption(SymbolDisplayParameterOptions.IncludeParamsRefOut))
{
Expand Down
Loading

0 comments on commit ba92379

Please sign in to comment.