diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs index a996a41288d50..72efbf595644d 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.ChangeSignature; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities.ChangeSignature; -using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ChangeSignature @@ -412,5 +411,26 @@ public override int M(string x, int newIntegerParameter, int y) }"; await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/53091"), Trait(Traits.Feature, Traits.Features.ChangeSignature)] + public async Task AddParameter_Cascade_Record() + { + var markup = @" +record $$BaseR(int A, int B); + +record DerivedR() : BaseR(0, 1);"; + var permutation = new AddedParameterOrExistingIndex[] + { + new(1), + new(new AddedParameter(null, "int", "C", CallSiteKind.Value, "3"), "int"), + new(0) + }; + var updatedCode = @" +record BaseR(int B, int C, int A); + +record DerivedR() : BaseR(1, 3, 0);"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs index 136592635bd9a..11fecb5f73ea8 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs @@ -1261,5 +1261,39 @@ public void M() }"; await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)] + [WorkItem(44558, "https://github.com/dotnet/roslyn/issues/44558")] + public async Task AddParameters_Record() + { + var markup = @" +/// +/// +/// +record $$R(int First, int Second, int Third) +{ + static R M() => new R(1, 2, 3); +} +"; + var updatedSignature = new AddedParameterOrExistingIndex[] + { + new(0), + new(2), + new(1), + new(new AddedParameter(null, "int", "Forth", CallSiteKind.Value, "12345"), "System.Int32") + }; + var updatedCode = @" +/// +/// +/// +/// +record R(int First, int Third, int Second, int Forth) +{ + static R M() => new R(1, 3, 2, 12345); +} +"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs index 90bf56923b963..0fb298efc3e11 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs @@ -951,5 +951,29 @@ class D : C, I await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)] + public async Task ReorderParamTagsInDocComments_Record() + { + var markup = @" +/// +/// +/// +record $$R(int A, int B, int C) +{ + public static R Instance = new(0, 1, 2); +}"; + var permutation = new[] { 2, 1, 0 }; + var updatedCode = @" +/// +/// +/// +record R(int C, int B, int A) +{ + public static R Instance = new(2, 1, 0); +}"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 8a0a6aa11c25a..52cbea5299e13 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -1012,5 +1012,27 @@ static void Goo() await VerifyItemExistsAsync(text, "term"); await VerifyItemExistsAsync(text, "description"); } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task RecordParam() + { + await VerifyItemsExistAsync(@" +/// $$ +public record Goo(string MyParameter); +", "param name=\"MyParameter\"", "typeparam name=\"T\""); + } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task RecordParamRef() + { + await VerifyItemsExistAsync(@" +/// +/// $$ +/// +public record Goo(string MyParameter); +", "paramref name=\"MyParameter\"", "typeparamref name=\"T\""); + } } } diff --git a/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs b/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs index eebb1502ff7bc..9f9adf39a2826 100644 --- a/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -785,5 +786,29 @@ public void Fizz(int i, int j, int k) {} await TestAsync(initial, expected); } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)] + public async Task AddsParamTag_Record() + { + var initial = @" +/// +/// +/// +/// +record R(int [|First|], int Second, int Third); +"; + + var expected = @" +/// +/// +/// +/// +/// +/// +record R(int First, int Second, int Third); +"; + await TestAsync(initial, expected); + } } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb index ee1cc0729587b..c329a73fe8b35 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb @@ -765,6 +765,25 @@ class c End Using End Function + + Public Async Function CommitParam_Record(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + Await state.AssertCompletionSession() + Await state.AssertSelectedCompletionItem(displayText:="param name=""I""") + state.SendReturn() + Await state.AssertNoCompletionSession() + + ' /// Public Async Function CommitParamNoOpenAngle(showCompletionInArgumentLists As Boolean) As Task diff --git a/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb b/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb index 358f561f10ea0..0053746e98b52 100644 --- a/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb @@ -825,5 +825,36 @@ End Class]]>.NormalizedValue() Await TestChangeSignatureViaCommandAsync(LanguageNames.VisualBasic, markup, updatedSignature:=permutation, expectedUpdatedInvocationDocumentCode:=updatedCode) End Function + + + + Public Async Function TestAddParameter_NoLastWhitespaceTrivia() As Task + + Dim markup = +''' +''' +Sub $$M(a As Integer) +End Sub +End Class]]>.NormalizedValue() + Dim permutation = + { + New AddedParameterOrExistingIndex(0), + New AddedParameterOrExistingIndex(New AddedParameter(Nothing, "Integer", "b", CallSiteKind.Value), "Integer") + } + + Dim updatedCode = + ''' + ''' + ''' + Sub M(a As Integer, b As Integer) + End Sub +End Class]]>.NormalizedValue() + + Await TestChangeSignatureViaCommandAsync(LanguageNames.VisualBasic, markup, updatedSignature:=permutation, expectedUpdatedInvocationDocumentCode:=updatedCode) + End Function End Class End Namespace diff --git a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs index 5ebd9cb87ab0b..e350faa680817 100644 --- a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs +++ b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,7 +43,9 @@ internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureServ SyntaxKind.DelegateDeclaration, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, - SyntaxKind.LocalFunctionStatement); + SyntaxKind.LocalFunctionStatement, + SyntaxKind.RecordStructDeclaration, + SyntaxKind.RecordDeclaration); private static readonly ImmutableArray _declarationAndInvocableKinds = _declarationKinds.Concat(ImmutableArray.Create( @@ -85,7 +88,9 @@ internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureServ SyntaxKind.NameMemberCref, SyntaxKind.AnonymousMethodExpression, SyntaxKind.ParenthesizedLambdaExpression, - SyntaxKind.SimpleLambdaExpression); + SyntaxKind.SimpleLambdaExpression, + SyntaxKind.RecordStructDeclaration, + SyntaxKind.RecordDeclaration); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -270,7 +275,9 @@ public override async Task ChangeSignatureAsync( if (updatedNode.IsKind(SyntaxKind.MethodDeclaration) || updatedNode.IsKind(SyntaxKind.ConstructorDeclaration) || updatedNode.IsKind(SyntaxKind.IndexerDeclaration) || - updatedNode.IsKind(SyntaxKind.DelegateDeclaration)) + updatedNode.IsKind(SyntaxKind.DelegateDeclaration) || + updatedNode.IsKind(SyntaxKind.RecordStructDeclaration) || + updatedNode.IsKind(SyntaxKind.RecordDeclaration)) { var updatedLeadingTrivia = UpdateParamTagsInLeadingTrivia(document, updatedNode, declarationSymbol, signaturePermutation); if (updatedLeadingTrivia != default && !updatedLeadingTrivia.IsEmpty) @@ -286,6 +293,12 @@ public override async Task ChangeSignatureAsync( return method.WithParameterList(method.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation)); } + if (updatedNode is RecordDeclarationSyntax { ParameterList: not null } record) + { + var updatedParameters = UpdateDeclaration(record.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax); + return record.WithParameterList(record.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation)); + } + if (updatedNode.IsKind(SyntaxKind.LocalFunctionStatement, out LocalFunctionStatementSyntax? localFunction)) { var updatedParameters = UpdateDeclaration(localFunction.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax); @@ -745,13 +758,14 @@ private ImmutableArray UpdateParamTagsInLeadingTrivia(Document doc return GetPermutedDocCommentTrivia(document, node, permutedParamNodes); } - private static ImmutableArray VerifyAndPermuteParamNodes(IEnumerable paramNodes, ISymbol declarationSymbol, SignatureChange updatedSignature) + private ImmutableArray VerifyAndPermuteParamNodes(IEnumerable paramNodes, ISymbol declarationSymbol, SignatureChange updatedSignature) { // Only reorder if count and order match originally. var originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters(); var reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters(); - var declaredParameters = declarationSymbol.GetParameters(); + var declaredParameters = GetParameters(declarationSymbol); + if (paramNodes.Count() != declaredParameters.Length) { return ImmutableArray.Empty; @@ -875,5 +889,20 @@ protected override bool SupportsOptionalAndParamsArrayParametersSimultaneously() protected override SyntaxToken CommaTokenWithElasticSpace() => Token(SyntaxKind.CommaToken).WithTrailingTrivia(ElasticSpace); + + protected override bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) + => typeSymbol.TryGetRecordPrimaryConstructor(out primaryConstructor); + + protected override ImmutableArray GetParameters(ISymbol declarationSymbol) + { + var declaredParameters = declarationSymbol.GetParameters(); + if (declarationSymbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.TryGetRecordPrimaryConstructor(out var primaryConstructor)) + { + declaredParameters = primaryConstructor.Parameters; + } + + return declaredParameters; + } } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index 57a8d38828ad3..f096d239f8fff 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -365,6 +366,18 @@ private string GetAttributeValue(XmlAttributeSyntax attribute) } } + protected override ImmutableArray GetParameters(ISymbol declarationSymbol) + { + var declaredParameters = declarationSymbol.GetParameters(); + if (declarationSymbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.TryGetRecordPrimaryConstructor(out var primaryConstructor)) + { + declaredParameters = primaryConstructor.Parameters; + } + + return declaredParameters; + } + private static readonly CompletionItemRules s_defaultRules = CompletionItemRules.Create( filterCharacterRules: FilterRules, diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 80ddbceb3bc97..dabab4bd296c8 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -74,6 +75,13 @@ public abstract Task ChangeSignatureAsync( /// protected abstract bool SupportsOptionalAndParamsArrayParametersSimultaneously(); + protected abstract bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor); + + /// + /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed. + /// + protected abstract ImmutableArray GetParameters(ISymbol declarationSymbol); + protected abstract SyntaxGenerator Generator { get; } protected abstract ISyntaxFacts SyntaxFacts { get; } @@ -124,6 +132,10 @@ internal async Task GetChangeSignatureContextAsy { symbol = typeSymbol.DelegateInvokeMethod; } + else if (TryGetRecordPrimaryConstructor(typeSymbol, out var primaryConstructor)) + { + symbol = primaryConstructor; + } } if (!symbol.MatchesKind(SymbolKind.Method, SymbolKind.Property)) @@ -164,7 +176,7 @@ internal async Task GetChangeSignatureContextAsy } var parameterConfiguration = ParameterConfiguration.Create( - symbol.GetParameters().Select(p => new ExistingParameter(p)).ToImmutableArray(), + GetParameters(symbol).Select(p => new ExistingParameter(p)).ToImmutableArray(), symbol.IsExtensionMethod(), selectedIndex); return new ChangeSignatureAnalysisSucceededContext( @@ -245,7 +257,7 @@ private static async Task> FindChangeSignatureR var symbols = await FindChangeSignatureReferencesAsync( declaredSymbol, context.Solution, cancellationToken).ConfigureAwait(false); - var declaredSymbolParametersCount = declaredSymbol.GetParameters().Length; + var declaredSymbolParametersCount = GetParameters(declaredSymbol).Length; var telemetryNumberOfDeclarationsToUpdate = 0; var telemetryNumberOfReferencesToUpdate = 0; @@ -441,14 +453,14 @@ private static bool TryGetNodeWithEditableSignatureOrAttributes(Location locatio return nodeToUpdate != null; } - protected static ImmutableArray PermuteArguments( + protected ImmutableArray PermuteArguments( ISymbol declarationSymbol, ImmutableArray arguments, SignatureChange updatedSignature, bool isReducedExtensionMethod = false) { // 1. Determine which parameters are permutable - var declarationParameters = declarationSymbol.GetParameters(); + var declarationParameters = GetParameters(declarationSymbol); var declarationParametersToPermute = GetParametersToPermute(arguments, declarationParameters, isReducedExtensionMethod); var argumentsToPermute = arguments.Take(declarationParametersToPermute.Length).ToList(); @@ -552,14 +564,14 @@ protected static ImmutableArray PermuteArguments( /// delegate Invoke methods (m) and delegate BeginInvoke methods (n = m + 2). This method adds on those extra parameters /// to the base . /// - private static SignatureChange UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(ISymbol declarationSymbol, SignatureChange updatedSignature) + private SignatureChange UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(ISymbol declarationSymbol, SignatureChange updatedSignature) { - if (declarationSymbol.GetParameters().Length > updatedSignature.OriginalConfiguration.ToListOfParameters().Length) + var realParameters = GetParameters(declarationSymbol); + if (realParameters.Length > updatedSignature.OriginalConfiguration.ToListOfParameters().Length) { var originalConfigurationParameters = updatedSignature.OriginalConfiguration.ToListOfParameters(); var updatedConfigurationParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters(); - var realParameters = declarationSymbol.GetParameters(); var bonusParameters = realParameters.Skip(originalConfigurationParameters.Length); var originalConfigurationParametersWithExtraParameters = originalConfigurationParameters.AddRange(bonusParameters.Select(p => new ExistingParameter(p))); @@ -759,13 +771,14 @@ protected virtual async Task> AddNewArgumentsToL if (updatedParameters[i] != signaturePermutation.UpdatedConfiguration.ThisParameter || !isReducedExtensionMethod) { + var parameters = GetParameters(declarationSymbol); if (updatedParameters[i] is AddedParameter addedParameter) { // Omitting an argument only works in some languages, depending on whether // there is a params array. We sometimes need to reinterpret an requested // omitted parameter as one with a TODO requested. var forcedCallsiteErrorDueToParamsArray = addedParameter.CallSiteKind == CallSiteKind.Omitted && - declarationSymbol.GetParameters().LastOrDefault()?.IsParams == true && + parameters.LastOrDefault()?.IsParams == true && !SupportsOptionalAndParamsArrayParametersSimultaneously(); var isCallsiteActuallyOmitted = addedParameter.CallSiteKind == CallSiteKind.Omitted && !forcedCallsiteErrorDueToParamsArray; @@ -808,7 +821,6 @@ protected virtual async Task> AddNewArgumentsToL } else { - var parameters = declarationSymbol.GetParameters(); if (indexInListOfPreexistingArguments == parameters.Length - 1 && parameters[indexInListOfPreexistingArguments].IsParams) { @@ -1001,7 +1013,6 @@ protected ImmutableArray GetPermutedDocCommentTrivia(Document docu node.GetTrailingTrivia(), lastWhiteSpaceTrivia, document.Project.Solution.Options.GetOption(FormattingOptions.NewLine, document.Project.Language)); - var newTrivia = Generator.Trivia(extraDocComments); updatedLeadingTrivia.Add(newTrivia); diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 84866db580dd2..06471715b2b8b 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -97,6 +97,13 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) protected abstract IEnumerable GetExistingTopLevelAttributeValues(TSyntax syntax, string tagName, string attributeName); + protected abstract IEnumerable GetKeywordNames(); + + /// + /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed. + /// + protected abstract ImmutableArray GetParameters(ISymbol symbol); + private CompletionItem GetItem(string name) { if (s_tagMap.TryGetValue(name, out var values)) @@ -152,7 +159,7 @@ protected IEnumerable GetNestedItems(ISymbol symbol, bool includ private IEnumerable GetParamRefItems(ISymbol symbol) { - var names = symbol.GetParameters().Select(p => p.Name); + var names = GetParameters(symbol).Select(p => p.Name); return names.Select(p => CreateCompletionItem( displayText: FormatParameter(ParameterReferenceElementName, p), @@ -176,7 +183,7 @@ protected IEnumerable GetAttributeValueItems(ISymbol symbol, str { if (tagName is ParameterElementName or ParameterReferenceElementName) { - return symbol.GetParameters() + return GetParameters(symbol) .Select(parameter => CreateCompletionItem(parameter.Name)); } else if (tagName == TypeParameterElementName) @@ -202,8 +209,6 @@ protected IEnumerable GetAttributeValueItems(ISymbol symbol, str return SpecializedCollections.EmptyEnumerable(); } - protected abstract IEnumerable GetKeywordNames(); - protected ImmutableArray GetTopLevelItems(ISymbol symbol, TSyntax syntax) { using var _1 = ArrayBuilder.GetInstance(out var items); @@ -216,7 +221,7 @@ protected ImmutableArray GetTopLevelItems(ISymbol symbol, TSynta if (symbol != null) { - items.AddRange(GetParameterItems(symbol.GetParameters(), syntax, ParameterElementName)); + items.AddRange(GetParameterItems(GetParameters(symbol), syntax, ParameterElementName)); items.AddRange(GetParameterItems(symbol.GetTypeParameters(), syntax, TypeParameterElementName)); if (symbol is IPropertySymbol && !existingTopLevelTags.Contains(ValueElementName)) diff --git a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb index da9d187d8a6a3..ce65e2060dbc7 100644 --- a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb +++ b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb @@ -604,13 +604,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Return GetPermutedDocCommentTrivia(document, node, permutedParamNodes) End Function - Private Shared Function VerifyAndPermuteParamNodes(paramNodes As ImmutableArray(Of XmlElementSyntax), declarationSymbol As ISymbol, updatedSignature As SignatureChange) As ImmutableArray(Of SyntaxNode) + Private Function VerifyAndPermuteParamNodes(paramNodes As ImmutableArray(Of XmlElementSyntax), declarationSymbol As ISymbol, updatedSignature As SignatureChange) As ImmutableArray(Of SyntaxNode) ' Only reorder if count and order match originally. Dim originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters() Dim reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters() - Dim declaredParameters = declarationSymbol.GetParameters() + Dim declaredParameters = GetParameters(declarationSymbol) If paramNodes.Length <> declaredParameters.Length Then Return ImmutableArray(Of SyntaxNode).Empty End If @@ -755,5 +755,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Protected Overrides Function CommaTokenWithElasticSpace() As SyntaxToken Return Token(SyntaxKind.CommaToken).WithTrailingTrivia(ElasticSpace) End Function + + Protected Overrides Function TryGetRecordPrimaryConstructor(typeSymbol As INamedTypeSymbol, ByRef primaryConstructor As IMethodSymbol) As Boolean + Return False + End Function + + Protected Overrides Function GetParameters(declarationSymbol As ISymbol) As ImmutableArray(Of IParameterSymbol) + Return declarationSymbol.GetParameters() + End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 29c5befe0a147..192e23a5d194a 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -338,6 +338,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return nameSyntax?.LocalName.ValueText End Function + Protected Overrides Function GetParameters(symbol As ISymbol) As ImmutableArray(Of IParameterSymbol) + Return symbol.GetParameters() + End Function + Private Shared ReadOnly s_defaultRules As CompletionItemRules = CompletionItemRules.Create( filterCharacterRules:=FilterRules, diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index ceaff08c190dc..b0e04e3c7cfe1 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -87,10 +87,13 @@ internal override SyntaxNode DocumentationCommentTrivia(IEnumerable SyntaxFactory.List(nodes), SyntaxFactory.Token(SyntaxKind.EndOfDocumentationCommentToken)); - return docTrivia - .WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")) - .WithTrailingTrivia(trailingTrivia) - .WithTrailingTrivia( + docTrivia = docTrivia.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")) + .WithTrailingTrivia(trailingTrivia); + + if (lastWhitespaceTrivia == default) + return docTrivia.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString)); + + return docTrivia.WithTrailingTrivia( SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs index 9b0cadc0c9d4c..73c1a9ff9649d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -177,5 +179,26 @@ public static bool IsIntrinsicType(this ITypeSymbol typeSymbol) return false; } } + + public static bool TryGetRecordPrimaryConstructor(this INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) + { + if (typeSymbol.IsRecord) + { + Debug.Assert(typeSymbol.GetParameters().IsDefaultOrEmpty, "If GetParameters extension handles record, we can remove the handling here."); + + // A bit hacky to determine the parameters of primary constructor associated with a given record. + // Simplifying is tracked by: https://github.com/dotnet/roslyn/issues/53092. + // Note: When the issue is handled, we can remove the logic here and handle things in GetParameters extension. BUT + // if GetParameters extension method gets updated to handle records, we need to test EVERY usage + // of the extension method and make sure the change is applicable to all these usages. + + primaryConstructor = typeSymbol.InstanceConstructors.FirstOrDefault( + c => c.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is RecordDeclarationSyntax); + return primaryConstructor is not null; + } + + primaryConstructor = null; + return false; + } } } diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 9d1659d957012..4bf5bf06f207b 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -67,9 +67,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Friend Overrides Function DocumentationCommentTrivia(nodes As IEnumerable(Of SyntaxNode), trailingTrivia As SyntaxTriviaList, lastWhitespaceTrivia As SyntaxTrivia, endOfLineString As String) As SyntaxNode Dim node = SyntaxFactory.DocumentationCommentTrivia(SyntaxFactory.List(nodes)) - Return node.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExteriorTrivia("''' ")). - WithTrailingTrivia(node.GetTrailingTrivia()). - WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia) + node = node.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExteriorTrivia("''' ")). + WithTrailingTrivia(node.GetTrailingTrivia()) + + If lastWhitespaceTrivia = Nothing Then + Return node.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString)) + End If + + Return node.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia) End Function Friend Overrides Function DocumentationCommentTriviaWithUpdatedContent(trivia As SyntaxTrivia, content As IEnumerable(Of SyntaxNode)) As SyntaxNode