From e8c123b22f71136c2bd9d558f8ceede28b5d1f73 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 11:52:49 -0700 Subject: [PATCH 01/39] In progress --- .../Portable/CSharpFeaturesResources.resx | 3 + ...gularConstructorCodeRefactoringProvider.cs | 167 ++++++++++++++++++ .../PredefinedCodeRefactoringProviderNames.cs | 1 + 3 files changed, 171 insertions(+) create mode 100644 src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 54aece370bd69..dd01bc5648b98 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -609,4 +609,7 @@ static int Main {Locked} + + Convert to regular constructor + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..d78db200d90f7 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -0,0 +1,167 @@ +// 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; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.LanguageService; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.FindSymbols; +using System.Linq; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; + +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPrimaryToRegularConstructor), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class ConvertPrimaryToRegularConstructorCodeRefactoringProvider() + : CodeRefactoringProvider +{ + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var (document, span, cancellationToken) = context; + var typeDeclaration = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + if (typeDeclaration?.ParameterList is null) + return; + + // Converting a record to a non-primary-constructor form is a lot more work (for example, having to synthesize a + // Deconstruct method, and figure out how to specify properties, etc.). We can consider adding support for that + // scenario later if desired. + if (typeDeclaration is RecordDeclarationSyntax) + return; + + var triggerSpan = TextSpan.FromBounds(typeDeclaration.SpanStart, typeDeclaration.ParameterList.FullSpan.End); + if (!triggerSpan.Contains(span)) + return; + + context.RegisterRefactoring(CodeAction.Create( + CSharpFeaturesResources.Convert_to_regular_constructor, + cancellationToken => ConvertAsync(document, typeDeclaration, typeDeclaration.ParameterList, cancellationToken), + nameof(CSharpFeaturesResources.Convert_to_regular_constructor))); + } + + private static async Task ConvertAsync( + Document document, TypeDeclarationSyntax typeDeclaration, ParameterListSyntax parameterList, CancellationToken cancellationToken) + { + // 1. Create constructor + // 2. Remove base arguments + // 3. Add fields if necessary + // 4. Update references to parameters to be references to fields + // 5. Format as appropriate + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var namedType = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken); + + // We may have to update multiple files (in the case of a partial type). Use a solution-editor to make that simple. + var solution = document.Project.Solution; + var solutionEditor = new SolutionEditor(solution); + + var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); + + var parameterToNonDocCommentLocations = await GetParameterLocationsAsync(includeDocCommentLocations: false).ConfigureAwait(false); + var synthesizedFields = GetSynthesizedFields(); + + return solutionEditor.GetChangedSolution(); + + async Task> GetParameterLocationsAsync(bool includeDocCommentLocations) + { + var result = new MultiDictionary(); + + foreach (var parameter in parameters) + { + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, cancellationToken).ConfigureAwait(false); + foreach (var reference in references) + { + // We may hit a location multiple times due to how we do FAR for linked symbols, but each linked symbol + // is allowed to report the entire set of references it think it is compatible with. So ensure we're + // hitting each location only once. + // + // Note Use DistinctBy (.Net6) once available. + foreach (var referenceLocation in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance)) + { + if (referenceLocation.IsImplicit) + continue; + + if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) + continue; + + // Explicitly ignore references to the base-constructor. We don't need to generate fields for these. + if (identifierName.GetAncestor() != null) + continue; + + if (!includeDocCommentLocations && identifierName.GetAncestor() != null) + continue; + + var expr = identifierName.WalkUpParentheses(); + var assignedFieldOrProperty = GetAssignedFieldOrProperty(identifierName); + result.Add(parameter, (identifierName, assignedFieldOrProperty)); + } + } + } + + return result; + } + + // See if this is a reference to the parameter that is just initializing an existing field or property. + ISymbol? GetAssignedFieldOrProperty(IdentifierNameSyntax identifierName) + { + var expr = identifierName.WalkUpParentheses(); + if (expr.Parent is EqualsValueClauseSyntax equalsValue) + { + return equalsValue.Parent is PropertyDeclarationSyntax or VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } } + ? semanticModel.GetRequiredDeclaredSymbol(equalsValue.Parent, cancellationToken) + : null; + } + else if (expr.Parent is ArrowExpressionClauseSyntax arrowExpression) + { + return arrowExpression.Parent is PropertyDeclarationSyntax + ? semanticModel.GetRequiredDeclaredSymbol(arrowExpression.Parent, cancellationToken) + : arrowExpression.Parent is AccessorDeclarationSyntax { Parent: PropertyDeclarationSyntax } + ? semanticModel.GetRequiredDeclaredSymbol(arrowExpression.Parent.Parent, cancellationToken) + : null; + } + else + { + return null; + } + } + + ImmutableDictionary GetSynthesizedFields() + { + using var _ = PooledDictionary.GetInstance(out var result); + + foreach (var parameter in parameters) + { + var referencedLocations = parameterToNonDocCommentLocations[parameter]; + if (referencedLocations.Count == 0) + continue; + + // if the parameter is only referenced in a single location, and that location is initializing a + // field/property already, we don't need to create a field for it. + if (referencedLocations.Count == 1 && referencedLocations.Single().assignedFieldOrProperty != null) + continue; + + // it was referenced outside of a field/prop initializer. Need to synthesize a field for it. + + } + + return result.ToImmutableDictionary(); + } + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs index bdb55f63e0be7..3bfd0a41856c8 100644 --- a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs +++ b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs @@ -29,6 +29,7 @@ internal static class PredefinedCodeRefactoringProviderNames public const string ConvertNamespace = "Convert Namespace"; public const string ConvertNumericLiteral = nameof(ConvertNumericLiteral); public const string ConvertPlaceholderToInterpolatedString = nameof(ConvertPlaceholderToInterpolatedString); + public const string ConvertPrimaryToRegularConstructor = nameof(ConvertPrimaryToRegularConstructor); public const string ConvertToInterpolatedString = "Convert To Interpolated String Code Action Provider"; public const string ConvertToProgramMain = "Convert To Program.Main"; public const string ConvertToRawString = nameof(ConvertToRawString); From 68398ccd397de5d8c2348230780348be67fdd709 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 12:28:30 -0700 Subject: [PATCH 02/39] In progress --- ...gularConstructorCodeRefactoringProvider.cs | 101 ++++++++++++++++-- ...tyToFullPropertyCodeRefactoringProvider.cs | 1 - 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index d78db200d90f7..69cc81cbbd72f 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -24,9 +24,15 @@ using Microsoft.CodeAnalysis.FindSymbols; using System.Linq; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.CSharp.CodeGeneration; namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; +using static SyntaxFactory; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPrimaryToRegularConstructor), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -52,12 +58,12 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactoring(CodeAction.Create( CSharpFeaturesResources.Convert_to_regular_constructor, - cancellationToken => ConvertAsync(document, typeDeclaration, typeDeclaration.ParameterList, cancellationToken), + cancellationToken => ConvertAsync(document, typeDeclaration, typeDeclaration.ParameterList, context.Options, cancellationToken), nameof(CSharpFeaturesResources.Convert_to_regular_constructor))); } private static async Task ConvertAsync( - Document document, TypeDeclarationSyntax typeDeclaration, ParameterListSyntax parameterList, CancellationToken cancellationToken) + Document document, TypeDeclarationSyntax typeDeclaration, ParameterListSyntax parameterList, CodeActionOptionsProvider options, CancellationToken cancellationToken) { // 1. Create constructor // 2. Remove base arguments @@ -72,10 +78,26 @@ private static async Task ConvertAsync( var solution = document.Project.Solution; var solutionEditor = new SolutionEditor(solution); + var syntaxGenerator = CSharpSyntaxGenerator.Instance; var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); var parameterToNonDocCommentLocations = await GetParameterLocationsAsync(includeDocCommentLocations: false).ConfigureAwait(false); - var synthesizedFields = GetSynthesizedFields(); + var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); + + var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; + var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); + var constructorDeclaration = CreateConstructorDeclaration(); + + // Now start editing the document + var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + + mainDocumentEditor.RemoveNode(parameterList); + if (baseType != null) + mainDocumentEditor.ReplaceNode(baseType, SimpleBaseType(baseType.Type).WithTriviaFrom(baseType)); + + foreach (var attributeList in methodTargetingAttributes) + mainDocumentEditor.RemoveNode(attributeList); + return solutionEditor.GetChangedSolution(); @@ -142,26 +164,83 @@ private static async Task ConvertAsync( } } - ImmutableDictionary GetSynthesizedFields() + async Task> GetSynthesizedFieldsAsync() { using var _ = PooledDictionary.GetInstance(out var result); foreach (var parameter in parameters) { - var referencedLocations = parameterToNonDocCommentLocations[parameter]; - if (referencedLocations.Count == 0) + var existingField = namedType.GetMembers().OfType().FirstOrDefault( + f => f.IsImplicitlyDeclared && parameter.Locations.Contains(f.Locations.FirstOrDefault()!)); + if (existingField == null) continue; - // if the parameter is only referenced in a single location, and that location is initializing a - // field/property already, we don't need to create a field for it. - if (referencedLocations.Count == 1 && referencedLocations.Single().assignedFieldOrProperty != null) - continue; + var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( + existingField, + name: await MakeFieldNameAsync(parameter.Name).ConfigureAwait(false)); + + result.Add(parameter, synthesizedField); + //var referencedLocations = parameterToNonDocCommentLocations[parameter]; + //if (referencedLocations.Count == 0) + // continue; + + //// if the parameter is only referenced in a single location, and that location is initializing a + //// field/property already, we don't need to create a field for it. + //if (referencedLocations.Count == 1 && referencedLocations.Single().assignedFieldOrProperty != null) + // continue; - // it was referenced outside of a field/prop initializer. Need to synthesize a field for it. + //// it was referenced outside of a field/prop initializer. Need to synthesize a field for it. + //result.Add( + // parameter, + // CodeGenerationSymbolFactory.CreateFieldSymbol( + // ) } return result.ToImmutableDictionary(); } + + async Task MakeFieldNameAsync(string parameterName) + { + var rule = await document.GetApplicableNamingRuleAsync( + new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field), + DeclarationModifiers.None, + Accessibility.Private, + options, + cancellationToken).ConfigureAwait(false); + + var fieldName = rule.NamingStyle.MakeCompliant(parameterName).First(); + return NameGenerator.GenerateUniqueName(fieldName, n => namedType.Name != n && !namedType.GetMembers(n).Any()); + } + + ConstructorDeclarationSyntax CreateConstructorDeclaration() + { + var attributes = List(methodTargetingAttributes); + var modifiers = typeDeclaration.Modifiers + .Where(m => SyntaxFacts.IsAccessibilityModifier(m.Kind())) + .Select(m => m.WithoutTrivia().WithAppendedTrailingTrivia(Space)); + + using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); + foreach (var parameter in parameters) + { + if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + continue; + + var fieldName = field.Name.ToIdentifierName(); + var left = parameter.Name == field.Name + ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), fieldName) + : (ExpressionSyntax)fieldName; + var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, parameter.Name.ToIdentifierName()); + assignmentStatements.Add(ExpressionStatement(assignment)); + } + + return ConstructorDeclaration( + attributes, + TokenList(modifiers), + typeDeclaration.Identifier.WithoutTrivia().WithAppendedTrailingTrivia(Space), + parameterList.WithoutTrivia(), + baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), + Block(assignmentStatements)); + } } } diff --git a/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs index 57c1b116cfba1..eeb84b630bbb0 100644 --- a/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertAutoPropertyToFullProperty/AbstractConvertAutoPropertyToFullPropertyCodeRefactoringProvider.cs @@ -78,7 +78,6 @@ private async Task ExpandToFullPropertyAsync( CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - Contract.ThrowIfNull(document.DocumentState.ParseOptions); var editor = new SyntaxEditor(root, document.Project.Solution.Services); From 0684d781edb989b54bd0d62fd57c444647eab809 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 12:50:30 -0700 Subject: [PATCH 03/39] in progress --- ...gularConstructorCodeRefactoringProvider.cs | 108 ++++++++++++------ 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 69cc81cbbd72f..83a93b8a9d710 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -28,6 +28,7 @@ using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.Utilities; namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; @@ -63,7 +64,11 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte } private static async Task ConvertAsync( - Document document, TypeDeclarationSyntax typeDeclaration, ParameterListSyntax parameterList, CodeActionOptionsProvider options, CancellationToken cancellationToken) + Document document, + TypeDeclarationSyntax typeDeclaration, + ParameterListSyntax parameterList, + CodeActionOptionsProvider optionsProvider, + CancellationToken cancellationToken) { // 1. Create constructor // 2. Remove base arguments @@ -78,6 +83,7 @@ private static async Task ConvertAsync( var solution = document.Project.Solution; var solutionEditor = new SolutionEditor(solution); + var codeGenService = document.GetRequiredLanguageService(); var syntaxGenerator = CSharpSyntaxGenerator.Instance; var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); @@ -90,14 +96,61 @@ private static async Task ConvertAsync( // Now start editing the document var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); + // Now, update all locations that reference the parameters to reference the new fields. + + // Remove the parameter list, and any base argument passing from the type declaration header itself. mainDocumentEditor.RemoveNode(parameterList); if (baseType != null) - mainDocumentEditor.ReplaceNode(baseType, SimpleBaseType(baseType.Type).WithTriviaFrom(baseType)); + mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseType)current).Type).WithTriviaFrom(baseType)); + // Remove all the attributes from the type decl that were moved to the constructor. foreach (var attributeList in methodTargetingAttributes) mainDocumentEditor.RemoveNode(attributeList); + // Now add all the fields. + mainDocumentEditor.ReplaceNode( + typeDeclaration, + (current, _) => + { + var currentTypeDeclaration = (TypeDeclarationSyntax)current; + var fieldsInOrder = parameters + .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) + .WhereNotNull(); + return codeGenService.AddMembers( + currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); + }); + + // Now add the constructor + mainDocumentEditor.ReplaceNode( + typeDeclaration, + (current, _) => + { + // If there is an existing non-static constructor, place it before that + var currentTypeDeclaration = (TypeDeclarationSyntax)current; + var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); + if (firstConstructorIndex >= 0) + { + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(firstConstructorIndex, constructorDeclaration)); + } + + // No constructors. Place after any fields if present, or any properties if there are no fields. + var lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is FieldDeclarationSyntax); + if (lastFieldOrProperty < 0) + lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is PropertyDeclarationSyntax); + + if (lastFieldOrProperty >= 0) + { + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(lastFieldOrProperty + 1, constructorDeclaration)); + } + + // Nothing at all. Just place the construct at the top of the type. + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(0, constructorDeclaration)); + }); return solutionEditor.GetChangedSolution(); @@ -115,24 +168,30 @@ private static async Task ConvertAsync( // hitting each location only once. // // Note Use DistinctBy (.Net6) once available. - foreach (var referenceLocation in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance)) + foreach (var grouping in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance).GroupBy(loc => loc.Location.SourceTree)) { - if (referenceLocation.IsImplicit) - continue; + var syntaxTree = grouping.Key; + var editor = await solutionEditor.GetDocumentEditorAsync(solution.GetDocumentId(syntaxTree), cancellationToken).ConfigureAwait(false); + + foreach (var referenceLocation in grouping) + { + if (referenceLocation.IsImplicit) + continue; - if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) - continue; + if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) + continue; - // Explicitly ignore references to the base-constructor. We don't need to generate fields for these. - if (identifierName.GetAncestor() != null) - continue; + // Explicitly ignore references to the base-constructor. We don't need to generate fields for these. + if (identifierName.GetAncestor() != null) + continue; - if (!includeDocCommentLocations && identifierName.GetAncestor() != null) - continue; + if (!includeDocCommentLocations && identifierName.GetAncestor() != null) + continue; - var expr = identifierName.WalkUpParentheses(); - var assignedFieldOrProperty = GetAssignedFieldOrProperty(identifierName); - result.Add(parameter, (identifierName, assignedFieldOrProperty)); + var expr = identifierName.WalkUpParentheses(); + var assignedFieldOrProperty = GetAssignedFieldOrProperty(identifierName); + result.Add(parameter, (identifierName, assignedFieldOrProperty)); + } } } } @@ -180,21 +239,6 @@ async Task> GetSynthesizedFi name: await MakeFieldNameAsync(parameter.Name).ConfigureAwait(false)); result.Add(parameter, synthesizedField); - //var referencedLocations = parameterToNonDocCommentLocations[parameter]; - //if (referencedLocations.Count == 0) - // continue; - - //// if the parameter is only referenced in a single location, and that location is initializing a - //// field/property already, we don't need to create a field for it. - //if (referencedLocations.Count == 1 && referencedLocations.Single().assignedFieldOrProperty != null) - // continue; - - //// it was referenced outside of a field/prop initializer. Need to synthesize a field for it. - //result.Add( - // parameter, - // CodeGenerationSymbolFactory.CreateFieldSymbol( - // ) - } return result.ToImmutableDictionary(); @@ -206,7 +250,7 @@ async Task MakeFieldNameAsync(string parameterName) new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field), DeclarationModifiers.None, Accessibility.Private, - options, + optionsProvider, cancellationToken).ConfigureAwait(false); var fieldName = rule.NamingStyle.MakeCompliant(parameterName).First(); @@ -215,7 +259,7 @@ async Task MakeFieldNameAsync(string parameterName) ConstructorDeclarationSyntax CreateConstructorDeclaration() { - var attributes = List(methodTargetingAttributes); + var attributes = List(methodTargetingAttributes.Select(a => a.WithTarget(null))); var modifiers = typeDeclaration.Modifiers .Where(m => SyntaxFacts.IsAccessibilityModifier(m.Kind())) .Select(m => m.WithoutTrivia().WithAppendedTrailingTrivia(Space)); From 1f59609db1524fe2108b63e764984d138343d36a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 12:54:09 -0700 Subject: [PATCH 04/39] First pass done --- ...gularConstructorCodeRefactoringProvider.cs | 71 ++++++------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 83a93b8a9d710..b3e73658a148f 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -3,32 +3,26 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; -using System.Diagnostics.CodeAnalysis; -using System.Text; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp.LanguageService; +using Microsoft.CodeAnalysis.CSharp.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using System.Collections.Generic; -using Microsoft.CodeAnalysis.FindSymbols; -using System.Linq; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CodeGeneration; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.CSharp.CodeGeneration; -using Microsoft.CodeAnalysis.Utilities; namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; @@ -87,7 +81,6 @@ private static async Task ConvertAsync( var syntaxGenerator = CSharpSyntaxGenerator.Instance; var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); - var parameterToNonDocCommentLocations = await GetParameterLocationsAsync(includeDocCommentLocations: false).ConfigureAwait(false); var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; @@ -99,11 +92,12 @@ private static async Task ConvertAsync( var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); // Now, update all locations that reference the parameters to reference the new fields. + await RewriteReferencesToParametersAsync().ConfigureAwait(false); // Remove the parameter list, and any base argument passing from the type declaration header itself. mainDocumentEditor.RemoveNode(parameterList); if (baseType != null) - mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseType)current).Type).WithTriviaFrom(baseType)); + mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseTypeSyntax)current).Type).WithTriviaFrom(baseType)); // Remove all the attributes from the type decl that were moved to the constructor. foreach (var attributeList in methodTargetingAttributes) @@ -154,12 +148,17 @@ private static async Task ConvertAsync( return solutionEditor.GetChangedSolution(); - async Task> GetParameterLocationsAsync(bool includeDocCommentLocations) + async Task RewriteReferencesToParametersAsync() { var result = new MultiDictionary(); foreach (var parameter in parameters) { + if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + continue; + + var fieldName = field.Name.ToIdentifierName(); + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, cancellationToken).ConfigureAwait(false); foreach (var reference in references) { @@ -181,46 +180,22 @@ private static async Task ConvertAsync( if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) continue; - // Explicitly ignore references to the base-constructor. We don't need to generate fields for these. + // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as + // they will still reference the parameter in the new constructor when we make the `: + // base(...)` initializer. if (identifierName.GetAncestor() != null) continue; - if (!includeDocCommentLocations && identifierName.GetAncestor() != null) + // Don't need to update doc comment reference (e.g. `paramref=...`). These will move to the + // new constructor and will still reference the parameters there. + if (identifierName.GetAncestor() != null) continue; - var expr = identifierName.WalkUpParentheses(); - var assignedFieldOrProperty = GetAssignedFieldOrProperty(identifierName); - result.Add(parameter, (identifierName, assignedFieldOrProperty)); + editor.ReplaceNode(identifierName, fieldName.WithTriviaFrom(identifierName)); } } } } - - return result; - } - - // See if this is a reference to the parameter that is just initializing an existing field or property. - ISymbol? GetAssignedFieldOrProperty(IdentifierNameSyntax identifierName) - { - var expr = identifierName.WalkUpParentheses(); - if (expr.Parent is EqualsValueClauseSyntax equalsValue) - { - return equalsValue.Parent is PropertyDeclarationSyntax or VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } } - ? semanticModel.GetRequiredDeclaredSymbol(equalsValue.Parent, cancellationToken) - : null; - } - else if (expr.Parent is ArrowExpressionClauseSyntax arrowExpression) - { - return arrowExpression.Parent is PropertyDeclarationSyntax - ? semanticModel.GetRequiredDeclaredSymbol(arrowExpression.Parent, cancellationToken) - : arrowExpression.Parent is AccessorDeclarationSyntax { Parent: PropertyDeclarationSyntax } - ? semanticModel.GetRequiredDeclaredSymbol(arrowExpression.Parent.Parent, cancellationToken) - : null; - } - else - { - return null; - } } async Task> GetSynthesizedFieldsAsync() From ea45ce52dc77afc15f8442c6776fe106c5c13710 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 13:02:38 -0700 Subject: [PATCH 05/39] Working test --- ...gularConstructorCodeRefactoringProvider.cs | 15 +- .../xlf/CSharpFeaturesResources.cs.xlf | 5 + .../xlf/CSharpFeaturesResources.de.xlf | 5 + .../xlf/CSharpFeaturesResources.es.xlf | 5 + .../xlf/CSharpFeaturesResources.fr.xlf | 5 + .../xlf/CSharpFeaturesResources.it.xlf | 5 + .../xlf/CSharpFeaturesResources.ja.xlf | 5 + .../xlf/CSharpFeaturesResources.ko.xlf | 5 + .../xlf/CSharpFeaturesResources.pl.xlf | 5 + .../xlf/CSharpFeaturesResources.pt-BR.xlf | 5 + .../xlf/CSharpFeaturesResources.ru.xlf | 5 + .../xlf/CSharpFeaturesResources.tr.xlf | 5 + .../xlf/CSharpFeaturesResources.zh-Hans.xlf | 5 + .../xlf/CSharpFeaturesResources.zh-Hant.xlf | 5 + ...ConvertPrimaryToRegularConstructorTests.cs | 3080 +++++++++++++++++ 15 files changed, 3151 insertions(+), 9 deletions(-) create mode 100644 src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index b3e73658a148f..5319bfa7c33fd 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -52,9 +52,10 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; context.RegisterRefactoring(CodeAction.Create( - CSharpFeaturesResources.Convert_to_regular_constructor, - cancellationToken => ConvertAsync(document, typeDeclaration, typeDeclaration.ParameterList, context.Options, cancellationToken), - nameof(CSharpFeaturesResources.Convert_to_regular_constructor))); + CSharpFeaturesResources.Convert_to_regular_constructor, + cancellationToken => ConvertAsync(document, typeDeclaration, typeDeclaration.ParameterList, context.Options, cancellationToken), + nameof(CSharpFeaturesResources.Convert_to_regular_constructor)), + triggerSpan); } private static async Task ConvertAsync( @@ -235,10 +236,6 @@ async Task MakeFieldNameAsync(string parameterName) ConstructorDeclarationSyntax CreateConstructorDeclaration() { var attributes = List(methodTargetingAttributes.Select(a => a.WithTarget(null))); - var modifiers = typeDeclaration.Modifiers - .Where(m => SyntaxFacts.IsAccessibilityModifier(m.Kind())) - .Select(m => m.WithoutTrivia().WithAppendedTrailingTrivia(Space)); - using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); foreach (var parameter in parameters) { @@ -255,8 +252,8 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() return ConstructorDeclaration( attributes, - TokenList(modifiers), - typeDeclaration.Identifier.WithoutTrivia().WithAppendedTrailingTrivia(Space), + TokenList(Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)), + typeDeclaration.Identifier.WithoutTrivia(), parameterList.WithoutTrivia(), baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 0eb1a6bfe4cee..a90356a22d7fd 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -152,6 +152,11 @@ Převést na nezpracovaný řetězec + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Převést na běžný řetězec diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 6a83338ce01a8..55f8cc4c4743a 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -152,6 +152,11 @@ In Rohzeichenfolge konvertieren + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string In reguläre Zeichenfolge konvertieren diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index ce73e99a2eb0d..0db82eef8647b 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -152,6 +152,11 @@ Convertir en cadena sin formato + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Convertir en cadena regular diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 73a786d63024d..94252e640afa7 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -152,6 +152,11 @@ Convertir en chaîne brute + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Convertir en chaîne classique diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 390997dd6f517..b8e53de094805 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -152,6 +152,11 @@ Converti in stringa non elaborata + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Converti in stringa normale diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index a3e72ad49a702..16a79eded5f9c 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -152,6 +152,11 @@ 生文字列に変換する + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string 正規文字列に変換する diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 2e27807f62295..bb85387cfcf29 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -152,6 +152,11 @@ 원시 문자열로 변환 + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string 일반 문자열로 변환 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index 8306fa134bfc0..f7b741c89ca98 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -152,6 +152,11 @@ Konwertuj na nieprzetworzony ciąg + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Konwertuj na zwykły ciąg diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index e8c17a99aba5f..c8b6ccc130940 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -152,6 +152,11 @@ Converter em cadeia de caracteres bruta + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Converter para cadeia de caracteres regular diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index ede0667331555..8a188356bd977 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -152,6 +152,11 @@ Преобразовать в необработанную строку + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Преобразовать в обычную строку diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 9aad645ac4851..cf7d27dbf8a14 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -152,6 +152,11 @@ Ham dizeye dönüştür + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string Normal dizeye dönüştür diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 18669d7d60c2c..3ee0cde87a451 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -152,6 +152,11 @@ 转换为原始字符串 + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string 转换为正则字符串 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index 0799bb294c6e3..2d257a24caefc 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -152,6 +152,11 @@ 轉換成原始字串 + + Convert to regular constructor + Convert to regular constructor + + Convert to regular string 轉換為一般字串 diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs new file mode 100644 index 0000000000000..f172e3663baed --- /dev/null +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -0,0 +1,3080 @@ +// 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.CSharp; +using Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertPrimaryToRegularConstructor; + +using VerifyCS = CSharpCodeRefactoringVerifier; + +public class ConvertPrimaryToRegularConstructorTests +{ + [Fact] + public async Task TestInCSharp12() + { + await new VerifyCS.Test + { + TestCode = """ + class [|C(int i)|] + { + } + """, + FixedCode = """ + class C + { + public C(int i) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithNonPublicConstructor() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private C(int i) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestStruct() + { + await new VerifyCS.Test + { + TestCode = """ + struct C + { + public [|C|](int i) + { + } + } + """, + FixedCode = """ + struct C(int i) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithUnchainedConstructor() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public C(int i) + { + } + + public C() + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithThisChainedConstructor() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public [|C|](int i) + { + } + + public C() : this(0) + { + } + } + """, + FixedCode = """ + class C(int i) + { + public C() : this(0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithBaseChainedConstructor1() + { + await new VerifyCS.Test + { + TestCode = """ + class B(int i) + { + } + + class C : B + { + public [|C|](int i) : base(i) + { + } + + public C() : this(0) + { + } + } + """, + FixedCode = """ + class B(int i) + { + } + + class C(int i) : B(i) + { + public C() : this(0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithBaseChainedConstructor2() + { + await new VerifyCS.Test + { + TestCode = """ + class B(int i) + { + } + + class C : B + { + public [|C|](int i) : base(i * i) + { + } + + public C() : this(0) + { + } + } + """, + FixedCode = """ + class B(int i) + { + } + + class C(int i) : B(i * i) + { + public C() : this(0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithBaseChainedConstructor3() + { + await new VerifyCS.Test + { + TestCode = """ + class B(int i, int j) + { + } + + class C : B + { + public [|C|](int i, int j) : base(i, + j) + { + } + + public C() : this(0, 0) + { + } + } + """, + FixedCode = """ + class B(int i, int j) + { + } + + class C(int i, int j) : B(i, + j) + { + public C() : this(0, 0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithBaseChainedConstructor4() + { + await new VerifyCS.Test + { + TestCode = """ + class B(int i, int j) + { + } + + class C : B + { + public [|C|](int i, int j) : base( + i, j) + { + } + + public C() : this(0, 0) + { + } + } + """, + FixedCode = """ + class B(int i, int j) + { + } + + class C(int i, int j) : B( + i, j) + { + public C() : this(0, 0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithBaseChainedConstructor5() + { + await new VerifyCS.Test + { + TestCode = """ + class B(int i, int j) + { + } + + class C : B + { + public [|C|](int i, int j) : base( + i, + j) + { + } + + public C() : this(0, 0) + { + } + } + """, + FixedCode = """ + class B(int i, int j) + { + } + + class C(int i, int j) : B( + i, + j) + { + public C() : this(0, 0) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithBadExpressionBody() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public C(int i) + => System.Console.WriteLine(i); + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithBadBlockBody() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public C(int i) + { + System.Console.WriteLine(i); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithExpressionBodyAssignmentToField1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](int i) + => this.i = i; + } + """, + FixedCode = """ + class C(int i) + { + private int i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithExpressionBodyAssignmentToProperty1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int I { get; } + + public [|C|](int i) + => this.I = i; + } + """, + FixedCode = """ + class C(int i) + { + private int I { get; } = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithExpressionBodyAssignmentToField2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](int j) + => this.i = j; + } + """, + FixedCode = """ + class C(int j) + { + private int i = j; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithExpressionBodyAssignmentToField3() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](int j) + => i = j; + } + """, + FixedCode = """ + class C(int j) + { + private int i = j; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithAssignmentToBaseField1() + { + await new VerifyCS.Test + { + TestCode = """ + class B + { + public int i; + } + + class C : B + { + public C(int i) + => this.i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithAssignmentToBaseField2() + { + await new VerifyCS.Test + { + TestCode = """ + class B + { + public int i; + } + + class C : B + { + public C(int i) + => base.i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithCompoundAssignmentToField() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int i; + + public C(int i) + => this.i += i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithTwoWritesToTheSameMember() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int i; + + public C(int i, int j) + { + this.i = i; + this.i = j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithComplexRightSide1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](int i) + => this.i = i * 2; + } + """, + FixedCode = """ + class C(int i) + { + private int i = i * 2; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestBlockWithMultipleAssignments1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int i; + public int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + """, + FixedCode = """ + class C(int i, int j) + { + public int i = i; + public int j = j; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestBlockWithMultipleAssignments2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int i, j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + """, + FixedCode = """ + class C(int i, int j) + { + public int i = i, j = j; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembers1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + private int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + """, + FixedCode = """ + class C(int i, int j) + { + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembers2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int I { get; } + private int J { get; } + + public [|C|](int i, int j) + { + this.I = i; + this.J = j; + } + } + """, + FixedCode = """ + class C(int i, int j) + { + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersOnlyWithMatchingType() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int I { get; } + private long J { get; } + + public [|C|](int i, int j) + { + this.I = i; + this.J = j; + } + } + """, + FixedCode = """ + class C(int i, int j) + { + private long J { get; } = j; + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestDoNotRemovePublicMembers1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public int i; + public int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task DoNotRemoveMembersUsedInNestedTypes() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class OuterType + { + private int _i; + private int _j; + + public [|OuterType|](int i, int j) + { + _i = i; + _j = j; + } + + public struct Enumerator + { + private int _i; + + public Enumerator(OuterType c) + { + _i = c._i; + Console.WriteLine(c); + } + } + } + """, + FixedCode = """ + using System; + + class OuterType(int i, int j) + { + private int _i = i; + + public struct Enumerator + { + private int _i; + + public Enumerator(OuterType c) + { + _i = c._i; + Console.WriteLine(c); + } + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersUpdateReferences1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int i; + private int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + + void M() + { + Console.WriteLine(this.i + this.j); + } + } + """, + FixedCode = """ + using System; + class C(int i, int j) + { + void M() + { + Console.WriteLine(i + j); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersUpdateReferences2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int i; + private int j; + + public [|C|](int @this, int @delegate) + { + this.i = @this; + this.j = @delegate; + } + + void M() + { + Console.WriteLine(this.i + this.j); + } + } + """, + FixedCode = """ + using System; + class C(int @this, int @delegate) + { + void M() + { + Console.WriteLine(@this + @delegate); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersUpdateReferencesWithRename1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int _i; + private int _j; + + public [|C|](int i, int j) + { + _i = i; + _j = j; + } + + void M() + { + Console.WriteLine(_i + _j); + } + } + """, + FixedCode = """ + using System; + class C(int i, int j) + { + void M() + { + Console.WriteLine(i + j); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersOnlyPrivateMembers() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int _i; + public int _j; + + public [|C|](int i, int j) + { + _i = i; + _j = j; + } + + void M() + { + Console.WriteLine(_i + _j); + } + } + """, + FixedCode = """ + using System; + class C(int i, int j) + { + public int _j = j; + + void M() + { + Console.WriteLine(i + _j); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersOnlyMembersWithoutAttributes() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int _i; + [CLSCompliant(true)] + private int _j; + + public [|C|](int i, int j) + { + _i = i; + _j = j; + } + + void M() + { + Console.WriteLine(_i + _j); + } + } + """, + FixedCode = """ + using System; + class C(int i, int j) + { + [CLSCompliant(true)] + private int _j = j; + + void M() + { + Console.WriteLine(i + _j); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersAccessedOffThis() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + private int _i; + private int _j; + + public [|C|](int i, int j) + { + _i = i; + _j = j; + } + + void M(C c) + { + Console.WriteLine(_i); + Console.WriteLine(_j == c._j); + } + } + """, + FixedCode = """ + using System; + class C(int i, int j) + { + private int _j = j; + + void M(C c) + { + Console.WriteLine(i); + Console.WriteLine(_j == c._j); + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWhenRightSideReferencesThis1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int x; + + public C(int i) + { + x = M(i); + } + + int M(int y) => y; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWhenRightSideReferencesThis2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int x; + + public C(int i) + { + x = this.M(i); + } + + int M(int y) => y; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWhenRightSideDoesNotReferenceThis() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int x; + + public [|C|](int i) + { + x = M(i); + } + + static int M(int y) => y; + } + """, + FixedCode = """ + class C(int i) + { + private int x = M(i); + + static int M(int y) => y; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + private int i; + + /// Doc comment on single line + /// Doc about i single line + public [|C|](int i) + { + this.i = i; + } + } + } + """, + FixedCode = """ + namespace N + { + /// Doc comment on single line + /// Doc about i single line + class C(int i) + { + private int i = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_IfDef1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + #if true + class C + { + private int i; + + /// Doc comment on single line + /// Doc about i single line + public [|C|](int i) + { + this.i = i; + } + } + #endif + } + """, + FixedCode = """ + namespace N + { + #if true + /// Doc comment on single line + /// Doc about i single line + class C(int i) + { + private int i = i; + } + #endif + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + /// Doc comment on single line + /// Doc about i single line + public [|C|](int i) + { + this.i = i; + } + } + """, + FixedCode = """ + /// Doc comment on single line + /// Doc about i single line + class C(int i) + { + private int i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + private int i; + + /// + /// Doc comment + /// On multiple lines + /// + /// + /// Doc about i + /// on multiple lines + /// + public [|C|](int i) + { + this.i = i; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// Doc comment + /// On multiple lines + /// + /// + /// Doc about i + /// on multiple lines + /// + class C(int i) + { + private int i = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_2() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + private int i; + + /// + /// Doc comment + /// On multiple lines + /// + /// Doc about i + /// on multiple lines + public [|C|](int i) + { + this.i = i; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// Doc comment + /// On multiple lines + /// + /// Doc about i + /// on multiple lines + class C(int i) + { + private int i = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_3() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + private int i; + + /// Doc comment + /// On multiple lines + /// Doc about i + /// on multiple lines + public [|C|](int i) + { + this.i = i; + } + } + } + """, + FixedCode = """ + namespace N + { + /// Doc comment + /// On multiple lines + /// Doc about i + /// on multiple lines + class C(int i) + { + private int i = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + /// Existing doc comment + /// + class C + { + private int i; + private int j; + + /// Constructor comment + /// On multiple lines + /// Doc about i + /// Doc about j + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// Existing doc comment + /// + /// Constructor comment + /// On multiple lines + /// Doc about i + /// Doc about j + class C(int i, int j) + { + private int i = i; + private int j = j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments2() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// Existing doc comment + class C + { + private int i; + private int j; + + /// Constructor comment + /// Doc about + /// i + /// Doc about + /// j + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// Existing doc comment + /// Constructor comment + /// Doc about + /// i + /// Doc about + /// j + class C(int i, int j) + { + private int i = i; + private int j = j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + /// Docs for i. + private int i; + /// + /// Docs for j. + /// + private int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// Docs for i. + /// + /// Docs for j. + /// + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments_MembersWithDifferentNames1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + class C + { + /// Docs for x. + private int x; + /// + /// Docs for y. + /// + private int y; + + public [|C|](int i, int j) + { + this.x = i; + this.y = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// Docs for x. + /// + /// Docs for y. + /// + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenTypeDocComments1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + /// C docs + /// + class C + { + /// Docs for i. + private int i; + /// + /// Docs for j. + /// + private int j; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// C docs + /// + /// Docs for i. + /// + /// Docs for j. + /// + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersKeepConstructorDocs1() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + /// C docs + /// + class C + { + /// Field docs for i. + private int i; + /// + /// Field docs for j. + /// + private int j; + + /// Param docs for i + /// Param docs for j + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// C docs + /// + /// Param docs for i + /// Param docs for j + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersKeepConstructorDocs2() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + /// C docs + /// + class C + { + /// Field docs for i. + private int i; + /// + /// Field docs for j. + /// + private int j; + + /// Param docs for j + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// C docs + /// + /// Param docs for j + /// Field docs for i. + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersKeepConstructorDocs3() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + /// C docs + /// + class C + { + /// Field docs for i. + private int i; + /// + /// Field docs for j. + /// + private int j; + + /// Param docs for i + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + } + } + """, + FixedCode = """ + namespace N + { + /// + /// C docs + /// + /// Param docs for i + /// + /// Field docs for j. + /// + class C(int i, int j) + { + } + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestFixAll1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public [|C|](int i) + { + } + } + + class D + { + public [|D|](int j) + { + } + } + """, + FixedCode = """ + class C(int i) + { + } + + class D(int j) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestFixAll2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public [|C|](int i) + { + } + + class D + { + public [|D|](int j) + { + } + } + } + """, + FixedCode = """ + class C(int i) + { + class D(int j) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestFixAll3() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](int i) + { + this.i = i; + } + + class D + { + private int J { get; } + + public [|D|](int j) + { + this.J = j; + } + } + } + """, + FixedCode = """ + class C(int i) + { + private int i = i; + + class D(int j) + { + private int J { get; } = j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + //[Fact] + //public async Task TestFixAll4() + //{ + // await new VerifyCS.Test + // { + // TestCode = """ + // using System; + // class C + // { + // private int i; + + // public [|C|](int i) + // { + // this.i = i; + // } + + // void M() + // { + // Console.WriteLine(i); + // } + + // class D + // { + // private int J { get; } + + // public [|D|](int j) + // { + // this.J = j; + // } + + // void N() + // { + // Console.WriteLine(J); + // } + // } + // } + // """, + // FixedCode = """ + // using System; + // class C(int i) + // { + // void M() + // { + // Console.WriteLine(i); + // } + + // class D(int j) + // { + // void N() + // { + // Console.WriteLine(j); + // } + // } + // } + // """, + // LanguageVersion = LanguageVersion.CSharp12, + // CodeActionIndex = 1, + // NumberOfFixAllIterations = 1, + // }.RunAsync(); + //} + + [Fact] + public async Task TestMoveConstructorAttributes1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class C + { + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + """, + FixedCode = """ + using System; + [method: Obsolete("", error: true)] + class C(int i) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes1A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + [Obsolete("", error: true)] + class C + { + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + """, + FixedCode = """ + using System; + [Obsolete("", error: true)] + [method: Obsolete("", error: true)] + class C(int i) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + [method: Obsolete("", error: true)] + class C(int i) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes2A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + [Obsolete("", error: true)] + class C + { + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + [Obsolete("", error: true)] + [method: Obsolete("", error: true)] + class C(int i) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes3() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + int x; + + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + [method: Obsolete("", error: true)] + class C(int i) + { + int x; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes3A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + [Obsolete("", error: true)] + class C + { + int x; + + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + [Obsolete("", error: true)] + [method: Obsolete("", error: true)] + class C(int i) + { + int x; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes4() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + [Serializable] + class C + { + [CLSCompliant(false)] + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + """, + FixedCode = """ + using System; + + [Serializable] + [method: CLSCompliant(false)] + [method: Obsolete("", error: true)] + class C(int i) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorAttributes4A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + [Serializable] + class C + { + int x; + + [CLSCompliant(false)] + [Obsolete("", error: true)] + public [|C|](int i) + { + } + } + """, + FixedCode = """ + using System; + + [Serializable] + [method: CLSCompliant(false)] + [method: Obsolete("", error: true)] + class C(int i) + { + int x; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public [|C|](int i, + int j) + { + } + } + """, + FixedCode = """ + using System; + + class C(int i, + int j) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove1A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + public [|C|](int i, + int j) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + class C(int i, + int j) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public [|C|]( + int i, + int j) + { + } + } + """, + FixedCode = """ + using System; + + class C( + int i, + int j) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove2A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + public [|C|]( + int i, + int j) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + class C( + int i, + int j) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove3() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public [|C|]( + int i, int j) + { + } + } + """, + FixedCode = """ + using System; + + class C( + int i, int j) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove3A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + public [|C|]( + int i, int j) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + class C( + int i, int j) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove4() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public [|C|]( + int i, + int j) + { + } + } + """, + FixedCode = """ + using System; + + class C( + int i, + int j) + { + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMultipleParametersMove4A() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace N + { + class C + { + public [|C|]( + int i, + int j) + { + } + } + } + """, + FixedCode = """ + using System; + + namespace N + { + class C( + int i, + int j) + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToNestedType1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public class D + { + } + + public [|C|](D d) + { + } + } + """, + FixedCode = """ + class C(C.D d) + { + public class D + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToNestedType2() + { + await new VerifyCS.Test + { + TestCode = """ + using System.Collections.Generic; + + class C + { + public class D + { + } + + public [|C|](List d) + { + } + } + """, + FixedCode = """ + using System.Collections.Generic; + + class C(List.D> d) + { + public class D + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToNestedType3() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + public class D + { + } + + public [|C|](C.D d) + { + } + } + """, + FixedCode = """ + class C(C.D d) + { + public class D + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToNestedType4() + { + await new VerifyCS.Test + { + TestCode = """ + using System.Collections.Generic; + + class C + { + public class D + { + } + + public [|C|](List.D> d) + { + } + } + """, + FixedCode = """ + using System.Collections.Generic; + + class C(List.D> d) + { + public class D + { + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithNonAutoProperty() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + // Can't assign a primary constructor parameter to a non-auto property. + private int I { get { return 0; } set { } } + + public C(int i) + { + this.I = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestInParameter1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](in int i) + { + this.i = i; + } + } + """, + FixedCode = """ + class C(in int i) + { + private int i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestInParameter2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private int i; + + public [|C|](in int i) + { + this.i = i; + } + } + """, + FixedCode = """ + class C(int i) + { + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion1() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public C(int i) + { + #if NET6 + Console.WriteLine(); + #endif + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + public C(int i) + { + #if false + Console.WriteLine(); + #endif + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion3() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + #if NET6 + => _i = i; + #else + => this._i = i; + #endif + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion4() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + #if true + => _i = i; + #else + => this._i = i; + #endif + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion5() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + #if false + => _i = i; + #else + => this._i = i; + #endif + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion6() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + { + #if true + _i = i; + #endif + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion7() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + { + #if true + _i = i; + #else + this._i = i; + #endif + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestNotWithPreprocessorRegion8() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + class C + { + private int _i; + + public C(int i) + { + #if false + _i = i; + #else + this._i = i; + #endif + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithRegionDirective1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + + #region constructors + + public [|C|](int i) + { + } + + #endregion + + } + """, + FixedCode = """ + class C(int i) + { + + #region constructors + + #endregion + + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestWithRegionDirective2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + + #region constructors + + public [|C|](int i) + { + } + + public C(string s) : this(s.Length) + { + } + + #endregion + + } + """, + FixedCode = """ + class C(int i) + { + + #region constructors + + public C(string s) : this(s.Length) + { + } + + #endregion + + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestSeeTag1() + { + await new VerifyCS.Test + { + TestCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class C + { + private int _i; + + public [|C|](int i) + { + _i = i; + } + } + """, + FixedCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class C(int i) + { + private int _i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestSeeTag2() + { + await new VerifyCS.Test + { + TestCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class C + { + private int _i; + + public [|C|](int i) + { + _i = i; + } + } + """, + FixedCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class C(int i) + { + } + """, + CodeActionIndex = 1, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToConstantInParameterInitializer1() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private const int Default = 0; + private int _i; + + public [|C|](int i = Default) + { + _i = i; + } + } + """, + FixedCode = """ + class C(int i = C.Default) + { + private const int Default = 0; + private int _i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToConstantInParameterInitializer2() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private const int Default = 0; + private int _i; + + public [|C|](int i = C.Default) + { + _i = i; + } + } + """, + FixedCode = """ + class C(int i = C.Default) + { + private const int Default = 0; + private int _i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestReferenceToConstantInParameterInitializer3() + { + await new VerifyCS.Test + { + TestCode = """ + class C + { + private const int Default = 0; + private int _i; + + public [|C|](int i = Default) + { + _i = i; + } + } + """, + FixedCode = """ + class C(int i = C.Default) + { + private const int Default = 0; + private int _i = i; + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMergeConstructorSummaryIntoTypeDocComment() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + + namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue + { + /// + /// Active instruction identifier. + /// It has the information necessary to track an active instruction within the debug session. + /// + [CLSCompliant(false)] + internal readonly struct ManagedInstructionId + { + /// + /// Method which the instruction is scoped to. + /// + public string Method { get; } + + /// + /// The IL offset for the instruction. + /// + public int ILOffset { get; } + + /// + /// Creates an ActiveInstructionId. + /// + /// Method which the instruction is scoped to. + /// IL offset for the instruction. + public [|ManagedInstructionId|]( + string method, + int ilOffset) + { + Method = method; + ILOffset = ilOffset; + } + } + } + """, + FixedCode = """ + using System; + + namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue + { + /// + /// Active instruction identifier. + /// It has the information necessary to track an active instruction within the debug session. + /// + /// + /// Creates an ActiveInstructionId. + /// + /// Method which the instruction is scoped to. + /// IL offset for the instruction. + [CLSCompliant(false)] + internal readonly struct ManagedInstructionId( + string method, + int ilOffset) + { + /// + /// Method which the instruction is scoped to. + /// + public string Method { get; } = method; + + /// + /// The IL offset for the instruction. + /// + public int ILOffset { get; } = ilOffset; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } +} From 009568533f5461da83771658f677de9737db1d13 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 13:14:02 -0700 Subject: [PATCH 06/39] porting tests --- ...ConvertPrimaryToRegularConstructorTests.cs | 712 ++++++------------ 1 file changed, 245 insertions(+), 467 deletions(-) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index f172e3663baed..f32dab96ad7f7 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -37,16 +37,13 @@ public C(int i) } [Fact] - public async Task TestNotWithNonPublicConstructor() + public async Task TestNotWithRecord() { await new VerifyCS.Test { TestCode = """ - class C + record class [|C(int i)|] { - private C(int i) - { - } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -59,37 +56,16 @@ public async Task TestStruct() await new VerifyCS.Test { TestCode = """ - struct C + struct [|C(int i)|] { - public [|C|](int i) - { - } } """, FixedCode = """ - struct C(int i) - { - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestNotWithUnchainedConstructor() - { - await new VerifyCS.Test - { - TestCode = """ - class C + struct C { public C(int i) { } - - public C() - { - } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -102,20 +78,20 @@ public async Task TestWithThisChainedConstructor() await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { - public [|C|](int i) - { - } - public C() : this(0) { } } """, FixedCode = """ - class C(int i) + class C { + public C(int i) + { + } + public C() : this(0) { } @@ -135,12 +111,8 @@ class B(int i) { } - class C : B + class [|C(int i)|] : B(i) { - public [|C|](int i) : base(i) - { - } - public C() : this(0) { } @@ -151,8 +123,12 @@ class B(int i) { } - class C(int i) : B(i) + class C : B { + public [|C|](int i) : base(i) + { + } + public C() : this(0) { } @@ -172,12 +148,8 @@ class B(int i) { } - class C : B + class [|C(int i)|] : B(i * i) { - public [|C|](int i) : base(i * i) - { - } - public C() : this(0) { } @@ -188,8 +160,12 @@ class B(int i) { } - class C(int i) : B(i * i) + class C : B { + public [|C|](int i) : base(i * i) + { + } + public C() : this(0) { } @@ -209,13 +185,9 @@ class B(int i, int j) { } - class C : B + class [|C(int i, int j)|] : B(i, + j) { - public [|C|](int i, int j) : base(i, - j) - { - } - public C() : this(0, 0) { } @@ -226,9 +198,13 @@ class B(int i, int j) { } - class C(int i, int j) : B(i, - j) + class C : B { + public C(int i, int j) : base(i, + j) + { + } + public C() : this(0, 0) { } @@ -248,13 +224,9 @@ class B(int i, int j) { } - class C : B + class [|C(int i, int j)|] : B( + i, j) { - public [|C|](int i, int j) : base( - i, j) - { - } - public C() : this(0, 0) { } @@ -265,9 +237,13 @@ class B(int i, int j) { } - class C(int i, int j) : B( - i, j) + class C : B { + public [|C|](int i, int j) : base( + i, j) + { + } + public C() : this(0, 0) { } @@ -287,14 +263,10 @@ class B(int i, int j) { } - class C : B + class [|C(int i, int j)|] : B( + i, + j) { - public [|C|](int i, int j) : base( - i, - j) - { - } - public C() : this(0, 0) { } @@ -305,10 +277,14 @@ class B(int i, int j) { } - class C(int i, int j) : B( - i, - j) + class C : B { + public C(int i, int j) : base( + i, + j) + { + } + public C() : this(0, 0) { } @@ -319,32 +295,24 @@ public C() : this(0, 0) } [Fact] - public async Task TestNotWithBadExpressionBody() + public async Task TestWithBlockBodyAssignmentToField1() { await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { - public C(int i) - => System.Console.WriteLine(i); + private int i = i; } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestNotWithBadBlockBody() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ class C { + private int i; + public C(int i) { - System.Console.WriteLine(i); + this.i = i; } } """, @@ -353,47 +321,25 @@ public C(int i) } [Fact] - public async Task TestWithExpressionBodyAssignmentToField1() + public async Task TestWithBlockBodyAssignmentToProperty1() { await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { - private int i; - - public [|C|](int i) - => this.i = i; + private int I { get; } = i; } """, FixedCode = """ - class C(int i) - { - private int i = i; - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestWithExpressionBodyAssignmentToProperty1() - { - await new VerifyCS.Test - { - TestCode = """ class C { private int I { get; } - public [|C|](int i) - => this.I = i; - } - """, - FixedCode = """ - class C(int i) - { - private int I { get; } = i; + public C(int i) + { + this.I = i; + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -401,47 +347,25 @@ class C(int i) } [Fact] - public async Task TestWithExpressionBodyAssignmentToField2() + public async Task TestWithBlockBodyAssignmentToField2() { await new VerifyCS.Test { TestCode = """ - class C - { - private int i; - - public [|C|](int j) - => this.i = j; - } - """, - FixedCode = """ - class C(int j) + class [|C(int j)|] { private int i = j; } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestWithExpressionBodyAssignmentToField3() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ class C { private int i; - public [|C|](int j) - => i = j; - } - """, - FixedCode = """ - class C(int j) - { - private int i = j; + public C(int j) + { + i = j; + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -449,20 +373,25 @@ class C(int j) } [Fact] - public async Task TestNotWithAssignmentToBaseField1() + public async Task TestWithComplexRightSide1() { await new VerifyCS.Test { TestCode = """ - class B + class [|C(int i)|] { - public int i; + private int i = i * 2; } - - class C : B + """, + FixedCode = """ + class C { + private int i; + public C(int i) - => this.i = i; + { + this.i = i * 2; + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -470,38 +399,28 @@ public C(int i) } [Fact] - public async Task TestNotWithAssignmentToBaseField2() + public async Task TestBlockWithMultipleAssignments1() { await new VerifyCS.Test { TestCode = """ - class B - { - public int i; - } - - class C : B + class [|C(int i, int j)|] { - public C(int i) - => base.i = i; + public int i = i; + public int j = j; } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestNotWithCompoundAssignmentToField() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ class C { public int i; + public int j; - public C(int i) - => this.i += i; + public C(int i, int j) + { + this.i = i; + this.j = j; + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -509,19 +428,25 @@ public C(int i) } [Fact] - public async Task TestNotWithTwoWritesToTheSameMember() + public async Task TestBlockWithMultipleAssignments2() { await new VerifyCS.Test { TestCode = """ + class [|C(int i, int j)|] + { + public int i = i, j = j; + } + """, + FixedCode = """ class C { - public int i; + public int i, j; public C(int i, int j) { this.i = i; - this.i = j; + this.j = j; } } """, @@ -530,7 +455,7 @@ public C(int i, int j) } [Fact] - public async Task TestWithComplexRightSide1() + public async Task TestRemoveMembers1() { await new VerifyCS.Test { @@ -538,98 +463,75 @@ public async Task TestWithComplexRightSide1() class C { private int i; + private int j; - public [|C|](int i) - => this.i = i * 2; + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } } """, FixedCode = """ - class C(int i) + class C(int i, int j) { - private int i = i * 2; } """, + CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } [Fact] - public async Task TestBlockWithMultipleAssignments1() + public async Task TestRemoveMembers2() { await new VerifyCS.Test { TestCode = """ class C { - public int i; - public int j; + private int I { get; } + private int J { get; } public [|C|](int i, int j) { - this.i = i; - this.j = j; + this.I = i; + this.J = j; } } """, FixedCode = """ class C(int i, int j) { - public int i = i; - public int j = j; } """, + CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } [Fact] - public async Task TestBlockWithMultipleAssignments2() + public async Task TestRemoveMembersOnlyWithMatchingType() { await new VerifyCS.Test { TestCode = """ class C { - public int i, j; + private int I { get; } + private long J { get; } public [|C|](int i, int j) { - this.i = i; - this.j = j; - } - } - """, - FixedCode = """ - class C(int i, int j) - { - public int i = i, j = j; - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestRemoveMembers1() - { - await new VerifyCS.Test - { - TestCode = """ - class C - { - private int i; - private int j; - - public [|C|](int i, int j) - { - this.i = i; - this.j = j; + this.I = i; + this.J = j; } } """, FixedCode = """ class C(int i, int j) { + private long J { get; } = j; } """, CodeActionIndex = 1, @@ -638,91 +540,30 @@ class C(int i, int j) } [Fact] - public async Task TestRemoveMembers2() + public async Task TestNestedTypes() { await new VerifyCS.Test { TestCode = """ - class C - { - private int I { get; } - private int J { get; } - - public [|C|](int i, int j) - { - this.I = i; - this.J = j; - } - } - """, - FixedCode = """ - class C(int i, int j) - { - } - """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } + using System; - [Fact] - public async Task TestRemoveMembersOnlyWithMatchingType() - { - await new VerifyCS.Test - { - TestCode = """ - class C + class [|OuterType(int i, int j)|] { - private int I { get; } - private long J { get; } + private int _i = i; - public [|C|](int i, int j) + public struct Enumerator { - this.I = i; - this.J = j; + private int _i; + + public Enumerator(OuterType c) + { + _i = c._i; + Console.WriteLine(c); + } } } """, FixedCode = """ - class C(int i, int j) - { - private long J { get; } = j; - } - """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestDoNotRemovePublicMembers1() - { - await new VerifyCS.Test - { - TestCode = """ - class C - { - public int i; - public int j; - - public [|C|](int i, int j) - { - this.i = i; - this.j = j; - } - } - """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task DoNotRemoveMembersUsedInNestedTypes() - { - await new VerifyCS.Test - { - TestCode = """ using System; class OuterType @@ -748,25 +589,6 @@ public Enumerator(OuterType c) } } """, - FixedCode = """ - using System; - - class OuterType(int i, int j) - { - private int _i = i; - - public struct Enumerator - { - private int _i; - - public Enumerator(OuterType c) - { - _i = c._i; - Console.WriteLine(c); - } - } - } - """, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -778,13 +600,23 @@ public async Task TestRemoveMembersUpdateReferences1() await new VerifyCS.Test { TestCode = """ + using System; + class [|C(int i, int j)|] + { + void M() + { + Console.WriteLine(i + j); + } + } + """, + FixedCode = """ using System; class C { private int i; private int j; - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -796,16 +628,6 @@ void M() } } """, - FixedCode = """ - using System; - class C(int i, int j) - { - void M() - { - Console.WriteLine(i + j); - } - } - """, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -817,13 +639,23 @@ public async Task TestRemoveMembersUpdateReferences2() await new VerifyCS.Test { TestCode = """ + using System; + class [|C(int @this, int @delegate)|] + { + void M() + { + Console.WriteLine(@this + @delegate); + } + } + """, + FixedCode = """ using System; class C { private int i; private int j; - public [|C|](int @this, int @delegate) + public C(int @this, int @delegate) { this.i = @this; this.j = @delegate; @@ -835,16 +667,6 @@ void M() } } """, - FixedCode = """ - using System; - class C(int @this, int @delegate) - { - void M() - { - Console.WriteLine(@this + @delegate); - } - } - """, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -856,13 +678,23 @@ public async Task TestRemoveMembersUpdateReferencesWithRename1() await new VerifyCS.Test { TestCode = """ + using System; + class [|C(int i, int j)|] + { + void M() + { + Console.WriteLine(i + j); + } + } + """, + FixedCode = """ using System; class C { private int _i; private int _j; - public [|C|](int i, int j) + public C(int i, int j) { _i = i; _j = j; @@ -874,16 +706,6 @@ void M() } } """, - FixedCode = """ - using System; - class C(int i, int j) - { - void M() - { - Console.WriteLine(i + j); - } - } - """, CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); @@ -896,32 +718,32 @@ public async Task TestRemoveMembersOnlyPrivateMembers() { TestCode = """ using System; - class C + class [|C(int i, int j)|] { - private int _i; - public int _j; - - public [|C|](int i, int j) - { - _i = i; - _j = j; - } + public int _j = j; void M() { - Console.WriteLine(_i + _j); + Console.WriteLine(i + _j); } } """, FixedCode = """ using System; - class C(int i, int j) + class C { - public int _j = j; + private int _i; + public int _j; + + public C(int i, int j) + { + _i = i; + _j = j; + } void M() { - Console.WriteLine(i + _j); + Console.WriteLine(_i + _j); } } """, @@ -936,53 +758,24 @@ public async Task TestRemoveMembersOnlyMembersWithoutAttributes() await new VerifyCS.Test { TestCode = """ - using System; - class C - { - private int _i; - [CLSCompliant(true)] - private int _j; - - public [|C|](int i, int j) + using System; + class [|C(int i, int j)|] { - _i = i; - _j = j; - } + [CLSCompliant(true)] + private int _j = j; - void M() - { - Console.WriteLine(_i + _j); + void M() + { + Console.WriteLine(i + _j); + } } - } - """, + """, FixedCode = """ - using System; - class C(int i, int j) - { - [CLSCompliant(true)] - private int _j = j; - - void M() - { - Console.WriteLine(i + _j); - } - } - """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestRemoveMembersAccessedOffThis() - { - await new VerifyCS.Test - { - TestCode = """ using System; class C { private int _i; + [CLSCompliant(true)] private int _j; public [|C|](int i, int j) @@ -991,23 +784,9 @@ class C _j = j; } - void M(C c) - { - Console.WriteLine(_i); - Console.WriteLine(_j == c._j); - } - } - """, - FixedCode = """ - using System; - class C(int i, int j) - { - private int _j = j; - - void M(C c) + void M() { - Console.WriteLine(i); - Console.WriteLine(_j == c._j); + Console.WriteLine(_i + _j); } } """, @@ -1017,45 +796,44 @@ void M(C c) } [Fact] - public async Task TestNotWhenRightSideReferencesThis1() + public async Task TestRemoveMembersAccessedOffThis() { await new VerifyCS.Test { TestCode = """ - class C + using System; + class [|C(int i, int j)|] { - private int x; + private int _j = j; - public C(int i) + void M(C c) { - x = M(i); + Console.WriteLine(i); + Console.WriteLine(_j == c._j); } - - int M(int y) => y; } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestNotWhenRightSideReferencesThis2() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ + using System; class C { - private int x; + private int _i; + private int _j; - public C(int i) + public C(int i, int j) { - x = this.M(i); + _i = i; + _j = j; } - int M(int y) => y; + void M(C c) + { + Console.WriteLine(_i); + Console.WriteLine(_j == c._j); + } } """, + CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1066,22 +844,22 @@ public async Task TestWhenRightSideDoesNotReferenceThis() await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { - private int x; - - public [|C|](int i) - { - x = M(i); - } + private int x = M(i); static int M(int y) => y; } """, FixedCode = """ - class C(int i) + class C { - private int x = M(i); + private int x; + + public C(int i) + { + x = M(i); + } static int M(int y) => y; } @@ -1096,6 +874,17 @@ public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// Doc comment on single line + /// Doc about i single line + class [|C(int i)|] + { + private int i = i; + } + } + """, + FixedCode = """ namespace N { class C @@ -1104,24 +893,13 @@ class C /// Doc comment on single line /// Doc about i single line - public [|C|](int i) + public C(int i) { this.i = i; } } } """, - FixedCode = """ - namespace N - { - /// Doc comment on single line - /// Doc about i single line - class C(int i) - { - private int i = i; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1135,16 +913,11 @@ public async Task TestMoveConstructorDocCommentWhenNothingOnType_IfDef1() namespace N { #if true - class C + /// Doc comment on single line + /// Doc about i single line + class [|C(int i)|] { - private int i; - - /// Doc comment on single line - /// Doc about i single line - public [|C|](int i) - { - this.i = i; - } + private int i = i; } #endif } @@ -1153,11 +926,16 @@ class C namespace N { #if true - /// Doc comment on single line - /// Doc about i single line - class C(int i) + class C { - private int i = i; + private int i; + + /// Doc comment on single line + /// Doc about i single line + public C(int i) + { + this.i = i; + } } #endif } From 19cd15be0a0e24548ccc6af748b2c724590db412 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 13:35:21 -0700 Subject: [PATCH 07/39] All tests --- ...ConvertPrimaryToRegularConstructorTests.cs | 1464 ++++++----------- 1 file changed, 533 insertions(+), 931 deletions(-) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index f32dab96ad7f7..b222327a0deb0 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -477,7 +477,6 @@ class C(int i, int j) { } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -505,7 +504,6 @@ class C(int i, int j) { } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -534,7 +532,6 @@ class C(int i, int j) private long J { get; } = j; } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -589,7 +586,6 @@ public Enumerator(OuterType c) } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -628,7 +624,6 @@ void M() } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -667,7 +662,6 @@ void M() } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -706,7 +700,6 @@ void M() } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -747,7 +740,6 @@ void M() } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -790,7 +782,6 @@ void M() } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -833,7 +824,6 @@ void M(C c) } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -950,26 +940,26 @@ public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_2() await new VerifyCS.Test { TestCode = """ + /// Doc comment on single line + /// Doc about i single line + class [|C(int i)|] + { + private int i = i; + } + """, + FixedCode = """ class C { private int i; /// Doc comment on single line /// Doc about i single line - public [|C|](int i) + public C(int i) { this.i = i; } } """, - FixedCode = """ - /// Doc comment on single line - /// Doc about i single line - class C(int i) - { - private int i = i; - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -980,6 +970,23 @@ public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// + /// Doc comment + /// On multiple lines + /// + /// + /// Doc about i + /// on multiple lines + /// + class [|C(int i)|] + { + private int i = i; + } + } + """, + FixedCode = """ namespace N { class C @@ -994,40 +1001,38 @@ class C /// Doc about i /// on multiple lines /// - public [|C|](int i) + public C(int i) { this.i = i; } } } """, - FixedCode = """ + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_2() + { + await new VerifyCS.Test + { + TestCode = """ namespace N { /// /// Doc comment - /// On multiple lines - /// + /// On multiple lines /// /// Doc about i - /// on multiple lines - /// - class C(int i) + /// on multiple lines + class [|C(int i)|] { private int i = i; } } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_2() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ namespace N { class C @@ -1040,28 +1045,13 @@ class C /// /// Doc about i /// on multiple lines - public [|C|](int i) + public C(int i) { this.i = i; } } } """, - FixedCode = """ - namespace N - { - /// - /// Doc comment - /// On multiple lines - /// - /// Doc about i - /// on multiple lines - class C(int i) - { - private int i = i; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1072,6 +1062,19 @@ public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_3() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// Doc comment + /// On multiple lines + /// Doc about i + /// on multiple lines + class [|C(int i)|] + { + private int i = i; + } + } + """, + FixedCode = """ namespace N { class C @@ -1082,26 +1085,13 @@ class C /// On multiple lines /// Doc about i /// on multiple lines - public [|C|](int i) + public C(int i) { this.i = i; } } } """, - FixedCode = """ - namespace N - { - /// Doc comment - /// On multiple lines - /// Doc about i - /// on multiple lines - class C(int i) - { - private int i = i; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1112,6 +1102,23 @@ public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// + /// Existing doc comment + /// + /// Constructor comment + /// On multiple lines + /// Doc about i + /// Doc about j + class [|C(int i, int j)|] + { + private int i = i; + private int j = j; + } + } + """, + FixedCode = """ namespace N { /// @@ -1126,7 +1133,7 @@ class C /// On multiple lines /// Doc about i /// Doc about j - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -1134,23 +1141,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// - /// Existing doc comment - /// - /// Constructor comment - /// On multiple lines - /// Doc about i - /// Doc about j - class C(int i, int j) - { - private int i = i; - private int j = j; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1161,6 +1151,22 @@ public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments2() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// Existing doc comment + /// Constructor comment + /// Doc about + /// i + /// Doc about + /// j + class [|C(int i, int j)|] + { + private int i = i; + private int j = j; + } + } + """, + FixedCode = """ namespace N { /// Existing doc comment @@ -1174,7 +1180,7 @@ class C /// i /// Doc about /// j - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -1182,22 +1188,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// Existing doc comment - /// Constructor comment - /// Doc about - /// i - /// Doc about - /// j - class C(int i, int j) - { - private int i = i; - private int j = j; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1208,6 +1198,18 @@ public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// Docs for i. + /// + /// Docs for j. + /// + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ namespace N { class C @@ -1219,7 +1221,7 @@ class C /// private int j; - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -1227,19 +1229,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// Docs for i. - /// - /// Docs for j. - /// - class C(int i, int j) - { - } - } - """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1250,6 +1239,18 @@ public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments_Members await new VerifyCS.Test { TestCode = """ + namespace N + { + /// Docs for x. + /// + /// Docs for y. + /// + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ namespace N { class C @@ -1261,7 +1262,7 @@ class C /// private int y; - public [|C|](int i, int j) + public C(int i, int j) { this.x = i; this.y = j; @@ -1269,19 +1270,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// Docs for x. - /// - /// Docs for y. - /// - class C(int i, int j) - { - } - } - """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1292,6 +1280,21 @@ public async Task TestRemoveMembersMoveDocComments_WhenTypeDocComments1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// + /// C docs + /// + /// Docs for i. + /// + /// Docs for j. + /// + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ namespace N { /// @@ -1306,7 +1309,7 @@ class C /// private int j; - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -1314,22 +1317,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// - /// C docs - /// - /// Docs for i. - /// - /// Docs for j. - /// - class C(int i, int j) - { - } - } - """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1340,6 +1327,19 @@ public async Task TestRemoveMembersKeepConstructorDocs1() await new VerifyCS.Test { TestCode = """ + namespace N + { + /// + /// C docs + /// + /// Param docs for i + /// Param docs for j + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ namespace N { /// @@ -1356,7 +1356,7 @@ class C /// Param docs for i /// Param docs for j - public [|C|](int i, int j) + public C(int i, int j) { this.i = i; this.j = j; @@ -1364,20 +1364,6 @@ class C } } """, - FixedCode = """ - namespace N - { - /// - /// C docs - /// - /// Param docs for i - /// Param docs for j - class C(int i, int j) - { - } - } - """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -1388,30 +1374,6 @@ public async Task TestRemoveMembersKeepConstructorDocs2() await new VerifyCS.Test { TestCode = """ - namespace N - { - /// - /// C docs - /// - class C - { - /// Field docs for i. - private int i; - /// - /// Field docs for j. - /// - private int j; - - /// Param docs for j - public [|C|](int i, int j) - { - this.i = i; - this.j = j; - } - } - } - """, - FixedCode = """ namespace N { /// @@ -1419,22 +1381,12 @@ namespace N /// /// Param docs for j /// Field docs for i. - class C(int i, int j) + class [|C(int i, int j)|] { } } """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestRemoveMembersKeepConstructorDocs3() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ namespace N { /// @@ -1449,8 +1401,8 @@ class C /// private int j; - /// Param docs for i - public [|C|](int i, int j) + /// Param docs for j + public C(int i, int j) { this.i = i; this.j = j; @@ -1458,193 +1410,58 @@ class C } } """, - FixedCode = """ - namespace N - { - /// - /// C docs - /// - /// Param docs for i - /// - /// Field docs for j. - /// - class C(int i, int j) - { - } - } - """, - CodeActionIndex = 1, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestFixAll1() - { - await new VerifyCS.Test - { - TestCode = """ - class C - { - public [|C|](int i) - { - } - } - - class D - { - public [|D|](int j) - { - } - } - """, - FixedCode = """ - class C(int i) - { - } - - class D(int j) - { - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } [Fact] - public async Task TestFixAll2() + public async Task TestRemoveMembersKeepConstructorDocs3() { await new VerifyCS.Test { TestCode = """ - class C + namespace N { - public [|C|](int i) - { - } - - class D + /// + /// C docs + /// + /// Param docs for i + /// + /// Field docs for j. + /// + class [|C(int i, int j)|] { - public [|D|](int j) - { - } } } """, FixedCode = """ - class C(int i) - { - class D(int j) - { - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestFixAll3() - { - await new VerifyCS.Test - { - TestCode = """ - class C + namespace N { - private int i; - - public [|C|](int i) - { - this.i = i; - } - - class D + /// + /// C docs + /// + class C { - private int J { get; } + /// Field docs for i. + private int i; + /// + /// Field docs for j. + /// + private int j; - public [|D|](int j) + /// Param docs for i + public C(int i, int j) { - this.J = j; + this.i = i; + this.j = j; } } } """, - FixedCode = """ - class C(int i) - { - private int i = i; - - class D(int j) - { - private int J { get; } = j; - } - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } - //[Fact] - //public async Task TestFixAll4() - //{ - // await new VerifyCS.Test - // { - // TestCode = """ - // using System; - // class C - // { - // private int i; - - // public [|C|](int i) - // { - // this.i = i; - // } - - // void M() - // { - // Console.WriteLine(i); - // } - - // class D - // { - // private int J { get; } - - // public [|D|](int j) - // { - // this.J = j; - // } - - // void N() - // { - // Console.WriteLine(J); - // } - // } - // } - // """, - // FixedCode = """ - // using System; - // class C(int i) - // { - // void M() - // { - // Console.WriteLine(i); - // } - - // class D(int j) - // { - // void N() - // { - // Console.WriteLine(j); - // } - // } - // } - // """, - // LanguageVersion = LanguageVersion.CSharp12, - // CodeActionIndex = 1, - // NumberOfFixAllIterations = 1, - // }.RunAsync(); - //} - [Fact] public async Task TestMoveConstructorAttributes1() { @@ -1652,19 +1469,19 @@ public async Task TestMoveConstructorAttributes1() { TestCode = """ using System; - class C + [method: Obsolete("", error: true)] + class [|C(int i)|] { - [Obsolete("", error: true)] - public [|C|](int i) - { - } } """, FixedCode = """ using System; - [method: Obsolete("", error: true)] - class C(int i) + class C { + [Obsolete("", error: true)] + public [|C|](int i) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -1679,20 +1496,20 @@ public async Task TestMoveConstructorAttributes1A() TestCode = """ using System; [Obsolete("", error: true)] - class C + [method: Obsolete("", error: true)] + class [|C(int i)|] { - [Obsolete("", error: true)] - public [|C|](int i) - { - } } """, FixedCode = """ using System; [Obsolete("", error: true)] - [method: Obsolete("", error: true)] - class C(int i) + class C { + [Obsolete("", error: true)] + public C(int i) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -1706,298 +1523,27 @@ public async Task TestMoveConstructorAttributes2() { TestCode = """ using System; - - namespace N - { - class C - { - [Obsolete("", error: true)] - public [|C|](int i) - { - } - } - } - """, - FixedCode = """ - using System; namespace N { [method: Obsolete("", error: true)] - class C(int i) - { - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorAttributes2A() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - namespace N - { - [Obsolete("", error: true)] - class C - { - [Obsolete("", error: true)] - public [|C|](int i) - { - } - } - } - """, - FixedCode = """ - using System; - - namespace N - { - [Obsolete("", error: true)] - [method: Obsolete("", error: true)] - class C(int i) - { - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorAttributes3() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - namespace N - { - class C + class [|C(int i)|] { - int x; - - [Obsolete("", error: true)] - public [|C|](int i) - { - } } } """, FixedCode = """ using System; - - namespace N - { - [method: Obsolete("", error: true)] - class C(int i) - { - int x; - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorAttributes3A() - { - await new VerifyCS.Test - { - TestCode = """ - using System; namespace N { - [Obsolete("", error: true)] class C { - int x; - [Obsolete("", error: true)] - public [|C|](int i) - { - } - } - } - """, - FixedCode = """ - using System; - - namespace N - { - [Obsolete("", error: true)] - [method: Obsolete("", error: true)] - class C(int i) - { - int x; - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorAttributes4() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - [Serializable] - class C - { - [CLSCompliant(false)] - [Obsolete("", error: true)] - public [|C|](int i) - { - } - } - """, - FixedCode = """ - using System; - - [Serializable] - [method: CLSCompliant(false)] - [method: Obsolete("", error: true)] - class C(int i) - { - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorAttributes4A() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - [Serializable] - class C - { - int x; - - [CLSCompliant(false)] - [Obsolete("", error: true)] - public [|C|](int i) - { - } - } - """, - FixedCode = """ - using System; - - [Serializable] - [method: CLSCompliant(false)] - [method: Obsolete("", error: true)] - class C(int i) - { - int x; - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMultipleParametersMove1() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - class C - { - public [|C|](int i, - int j) - { - } - } - """, - FixedCode = """ - using System; - - class C(int i, - int j) - { - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMultipleParametersMove1A() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - namespace N - { - class C - { - public [|C|](int i, - int j) - { - } - } - } - """, - FixedCode = """ - using System; - - namespace N - { - class C(int i, - int j) - { - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMultipleParametersMove2() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - class C - { - public [|C|]( - int i, - int j) - { - } - } - """, - FixedCode = """ - using System; - - class C( - int i, - int j) - { + public C(int i) + { + } + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2005,7 +1551,7 @@ class C( } [Fact] - public async Task TestMultipleParametersMove2A() + public async Task TestMoveConstructorAttributes2A() { await new VerifyCS.Test { @@ -2014,25 +1560,25 @@ public async Task TestMultipleParametersMove2A() namespace N { - class C + [Obsolete("", error: true)] + [method: Obsolete("", error: true)] + class [|C(int i)|] { - public [|C|]( - int i, - int j) - { - } } } """, FixedCode = """ using System; - + namespace N { - class C( - int i, - int j) + [Obsolete("", error: true)] + class C { + [Obsolete("", error: true)] + public C(int i) + { + } } } """, @@ -2041,27 +1587,36 @@ class C( } [Fact] - public async Task TestMultipleParametersMove3() + public async Task TestMoveConstructorAttributes3() { await new VerifyCS.Test { TestCode = """ using System; - class C + namespace N { - public [|C|]( - int i, int j) + [method: Obsolete("", error: true)] + class [|C(int i)|] { + int x; } } """, FixedCode = """ using System; - - class C( - int i, int j) + + namespace N { + class C + { + int x; + + [Obsolete("", error: true)] + public C(int i) + { + } + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2069,7 +1624,7 @@ class C( } [Fact] - public async Task TestMultipleParametersMove3A() + public async Task TestMoveConstructorAttributes3A() { await new VerifyCS.Test { @@ -2078,23 +1633,28 @@ public async Task TestMultipleParametersMove3A() namespace N { - class C + [Obsolete("", error: true)] + [method: Obsolete("", error: true)] + class [|C(int i)|] { - public [|C|]( - int i, int j) - { - } + int x; } } """, FixedCode = """ using System; - + namespace N { - class C( - int i, int j) + [Obsolete("", error: true)] + class C { + int x; + + [Obsolete("", error: true)] + public C(int i) + { + } } } """, @@ -2103,29 +1663,31 @@ class C( } [Fact] - public async Task TestMultipleParametersMove4() + public async Task TestMoveConstructorAttributes4() { await new VerifyCS.Test { TestCode = """ using System; - class C + [Serializable] + [method: CLSCompliant(false)] + [method: Obsolete("", error: true)] + class [|C(int i)|] { - public [|C|]( - int i, - int j) - { - } } """, FixedCode = """ using System; - class C( - int i, - int j) + [Serializable] + class C { + [CLSCompliant(false)] + [Obsolete("", error: true)] + public C(int i) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2133,33 +1695,32 @@ class C( } [Fact] - public async Task TestMultipleParametersMove4A() + public async Task TestMoveConstructorAttributes4A() { await new VerifyCS.Test { TestCode = """ using System; - namespace N + [Serializable] + [method: CLSCompliant(false)] + [method: Obsolete("", error: true)] + class [|C(int i)|] { - class C - { - public [|C|]( - int i, - int j) - { - } - } + int x; } """, FixedCode = """ using System; - namespace N + [Serializable] + class C { - class C( - int i, - int j) + int x; + + [CLSCompliant(false)] + [Obsolete("", error: true)] + public [|C|](int i) { } } @@ -2169,26 +1730,25 @@ class C( } [Fact] - public async Task TestReferenceToNestedType1() + public async Task TestMultipleParametersMove1() { await new VerifyCS.Test { TestCode = """ - class C - { - public class D - { - } + using System; - public [|C|](D d) - { - } + class [|C(int i, + int j)|] + { } """, FixedCode = """ - class C(C.D d) + using System; + + class C { - public class D + public [|C|](int i, + int j) { } } @@ -2198,31 +1758,32 @@ public class D } [Fact] - public async Task TestReferenceToNestedType2() + public async Task TestMultipleParametersMove1A() { await new VerifyCS.Test { TestCode = """ - using System.Collections.Generic; + using System; - class C + namespace N { - public class D - { - } - - public [|C|](List d) + class [|C(int i, + int j)|] { } } """, FixedCode = """ - using System.Collections.Generic; + using System; - class C(List.D> d) + namespace N { - public class D + class C { + public [|C|](int i, + int j) + { + } } } """, @@ -2231,26 +1792,27 @@ public class D } [Fact] - public async Task TestReferenceToNestedType3() + public async Task TestMultipleParametersMove2() { await new VerifyCS.Test { TestCode = """ - class C - { - public class D - { - } + using System; - public [|C|](C.D d) - { - } + class [|C( + int i, + int j)|] + { } """, FixedCode = """ - class C(C.D d) + using System; + + class C { - public class D + public C( + int i, + int j) { } } @@ -2260,31 +1822,34 @@ public class D } [Fact] - public async Task TestReferenceToNestedType4() + public async Task TestMultipleParametersMove2A() { await new VerifyCS.Test { TestCode = """ - using System.Collections.Generic; + using System; - class C + namespace N { - public class D - { - } - - public [|C|](List.D> d) + class [|C( + int i, + int j)|] { } } """, FixedCode = """ - using System.Collections.Generic; + using System; - class C(List.D> d) + namespace N { - public class D + class C { + public C( + int i, + int j) + { + } } } """, @@ -2293,19 +1858,26 @@ public class D } [Fact] - public async Task TestNotWithNonAutoProperty() + public async Task TestMultipleParametersMove3() { await new VerifyCS.Test { TestCode = """ - class C + using System; + + class [|C( + int i, int j)|] { - // Can't assign a primary constructor parameter to a non-auto property. - private int I { get { return 0; } set { } } + } + """, + FixedCode = """ + using System; - public C(int i) + class C + { + public C( + int i, int j) { - this.I = i; } } """, @@ -2314,25 +1886,33 @@ public C(int i) } [Fact] - public async Task TestInParameter1() + public async Task TestMultipleParametersMove3A() { await new VerifyCS.Test { TestCode = """ - class C - { - private int i; + using System; - public [|C|](in int i) + namespace N + { + class [|C( + int i, int j)|] { - this.i = i; } } """, FixedCode = """ - class C(in int i) + using System; + + namespace N { - private int i = i; + class C + { + public C( + int i, int j) + { + } + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2340,68 +1920,64 @@ class C(in int i) } [Fact] - public async Task TestInParameter2() + public async Task TestMultipleParametersMove4() { await new VerifyCS.Test { TestCode = """ - class C - { - private int i; + using System; - public [|C|](in int i) - { - this.i = i; - } + class [|C( + int i, + int j)|] + { } """, FixedCode = """ - class C(int i) + using System; + + class C { + public C( + int i, + int j) + { + } } """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } [Fact] - public async Task TestNotWithPreprocessorRegion1() + public async Task TestMultipleParametersMove4A() { await new VerifyCS.Test { TestCode = """ using System; - class C + namespace N { - public C(int i) + class [|C( + int i, + int j)|] { - #if NET6 - Console.WriteLine(); - #endif } } """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestNotWithPreprocessorRegion2() - { - await new VerifyCS.Test - { - TestCode = """ + FixedCode = """ using System; - class C + namespace N { - public C(int i) + class C { - #if false - Console.WriteLine(); - #endif + public C( + int i, + int j) + { + } } } """, @@ -2410,23 +1986,28 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion3() + public async Task TestReferenceToNestedType1() { await new VerifyCS.Test { TestCode = """ - using System; - + class [|C(C.D d)|] + { + public class D + { + } + } + """, + FixedCode = """ class C { - private int _i; + public class D + { + } - public C(int i) - #if NET6 - => _i = i; - #else - => this._i = i; - #endif + public C(D d) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2434,23 +2015,32 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion4() + public async Task TestReferenceToNestedType2() { await new VerifyCS.Test { TestCode = """ - using System; + using System.Collections.Generic; - class C + class [|C(List.D> d)|] { - private int _i; + public class D + { + } + } + """, + FixedCode = """ + using System.Collections.Generic; - public C(int i) - #if true - => _i = i; - #else - => this._i = i; - #endif + class C + { + public class D + { + } + + public C(List d) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2458,23 +2048,28 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion5() + public async Task TestReferenceToNestedType3() { await new VerifyCS.Test { TestCode = """ - using System; - + class [|C(C.D d)|] + { + public class D + { + } + } + """, + FixedCode = """ class C { - private int _i; + public class D + { + } - public C(int i) - #if false - => _i = i; - #else - => this._i = i; - #endif + public C(C.D d) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2482,22 +2077,31 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion6() + public async Task TestReferenceToNestedType4() { await new VerifyCS.Test { TestCode = """ - using System; + using System.Collections.Generic; - class C + class [|C(List.D> d)|] { - private int _i; + public class D + { + } + } + """, + FixedCode = """ + using System.Collections.Generic; - public C(int i) + class C + { + public class D + { + } + + public C(List.D> d) { - #if true - _i = i; - #endif } } """, @@ -2506,24 +2110,24 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion7() + public async Task TestInParameter1() { await new VerifyCS.Test { TestCode = """ - using System; - + class [|C(in int i)|] + { + private int i = i; + } + """, + FixedCode = """ class C { - private int _i; + private int i; - public C(int i) + public C(in int i) { - #if true - _i = i; - #else - this._i = i; - #endif + this.i = i; } } """, @@ -2532,24 +2136,23 @@ public C(int i) } [Fact] - public async Task TestNotWithPreprocessorRegion8() + public async Task TestInParameter2() { await new VerifyCS.Test { TestCode = """ - using System; - + class [|C(int i)|] + { + } + """, + FixedCode = """ class C { - private int _i; + private int i; - public C(int i) + public C(in int i) { - #if false - _i = i; - #else - this._i = i; - #endif + this.i = i; } } """, @@ -2563,25 +2166,25 @@ public async Task TestWithRegionDirective1() await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { - + #region constructors - public [|C|](int i) - { - } - #endregion } """, FixedCode = """ - class C(int i) + class C { - + #region constructors + public [|C|](int i) + { + } + #endregion } @@ -2596,15 +2199,11 @@ public async Task TestWithRegionDirective2() await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i)|] { #region constructors - public [|C|](int i) - { - } - public C(string s) : this(s.Length) { } @@ -2614,11 +2213,15 @@ public C(string s) : this(s.Length) } """, FixedCode = """ - class C(int i) + class C { #region constructors + public C(int i) + { + } + public C(string s) : this(s.Length) { } @@ -2640,23 +2243,23 @@ public async Task TestSeeTag1() /// /// Provides strongly typed wrapper around . /// - class C + class [|C(int i)|] { - private int _i; - - public [|C|](int i) - { - _i = i; - } + private int _i = i; } """, FixedCode = """ /// /// Provides strongly typed wrapper around . /// - class C(int i) + class C { - private int _i = i; + private int _i; + + public C(int i) + { + _i = i; + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2669,6 +2272,14 @@ public async Task TestSeeTag2() await new VerifyCS.Test { TestCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class [|C(int i)|] + { + } + """, + FixedCode = """ /// /// Provides strongly typed wrapper around . /// @@ -2682,15 +2293,6 @@ class C } } """, - FixedCode = """ - /// - /// Provides strongly typed wrapper around . - /// - class C(int i) - { - } - """, - CodeActionIndex = 1, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -2701,24 +2303,24 @@ public async Task TestReferenceToConstantInParameterInitializer1() await new VerifyCS.Test { TestCode = """ + class [|C(int i = C.Default)|] + { + private const int Default = 0; + private int _i = i; + } + """, + FixedCode = """ class C { private const int Default = 0; private int _i; - public [|C|](int i = Default) + public C(int i = Default) { _i = i; } } """, - FixedCode = """ - class C(int i = C.Default) - { - private const int Default = 0; - private int _i = i; - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -2729,24 +2331,24 @@ public async Task TestReferenceToConstantInParameterInitializer2() await new VerifyCS.Test { TestCode = """ + class [|C(int i = C.Default)|] + { + private const int Default = 0; + private int _i = i; + } + """, + FixedCode = """ class C { private const int Default = 0; private int _i; - public [|C|](int i = C.Default) + public C(int i = C.Default) { _i = i; } } """, - FixedCode = """ - class C(int i = C.Default) - { - private const int Default = 0; - private int _i = i; - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -2757,24 +2359,24 @@ public async Task TestReferenceToConstantInParameterInitializer3() await new VerifyCS.Test { TestCode = """ + class [|C(int i = C.Default)|] + { + private const int Default = 0; + private int _i = i; + } + """, + FixedCode = """ class C { private const int Default = 0; private int _i; - public [|C|](int i = Default) + public C(int i = Default) { _i = i; } } """, - FixedCode = """ - class C(int i = C.Default) - { - private const int Default = 0; - private int _i = i; - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -2793,31 +2395,25 @@ namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue /// Active instruction identifier. /// It has the information necessary to track an active instruction within the debug session. /// + /// + /// Creates an ActiveInstructionId. + /// + /// Method which the instruction is scoped to. + /// IL offset for the instruction. [CLSCompliant(false)] - internal readonly struct ManagedInstructionId + internal readonly struct [|ManagedInstructionId( + string method, + int ilOffset)|] { /// /// Method which the instruction is scoped to. /// - public string Method { get; } - - /// - /// The IL offset for the instruction. - /// - public int ILOffset { get; } + public string Method { get; } = method; /// - /// Creates an ActiveInstructionId. + /// The IL offset for the instruction. /// - /// Method which the instruction is scoped to. - /// IL offset for the instruction. - public [|ManagedInstructionId|]( - string method, - int ilOffset) - { - Method = method; - ILOffset = ilOffset; - } + public int ILOffset { get; } = ilOffset; } } """, @@ -2830,25 +2426,31 @@ namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue /// Active instruction identifier. /// It has the information necessary to track an active instruction within the debug session. /// - /// - /// Creates an ActiveInstructionId. - /// - /// Method which the instruction is scoped to. - /// IL offset for the instruction. [CLSCompliant(false)] - internal readonly struct ManagedInstructionId( - string method, - int ilOffset) + internal readonly struct ManagedInstructionId { /// /// Method which the instruction is scoped to. /// - public string Method { get; } = method; - + public string Method { get; } + /// /// The IL offset for the instruction. /// - public int ILOffset { get; } = ilOffset; + public int ILOffset { get; } + + /// + /// Creates an ActiveInstructionId. + /// + /// Method which the instruction is scoped to. + /// IL offset for the instruction. + public ManagedInstructionId( + string method, + int ilOffset) + { + Method = method; + ILOffset = ilOffset; + } } } """, From 1c49061f69efec6518c999b7247e59d64fee179f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 13 Oct 2023 14:21:35 -0700 Subject: [PATCH 08/39] In progress --- ...UsePrimaryConstructorDiagnosticAnalyzer.cs | 4 +- ...gularConstructorCodeRefactoringProvider.cs | 165 +++++++++++++----- 2 files changed, 126 insertions(+), 43 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs index 64ad93cb23a7e..0aeda52406e30 100644 --- a/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs @@ -204,8 +204,8 @@ public static void AnalyzeNamedTypeStart( for (var containingType = startSymbol.ContainingType; containingType != null; containingType = containingType.ContainingType) { - var containgTypeAnalyzer = TryGetOrCreateAnalyzer(containingType); - RegisterFieldOrPropertyAnalysisIfNecessary(containgTypeAnalyzer); + var containingTypeAnalyzer = TryGetOrCreateAnalyzer(containingType); + RegisterFieldOrPropertyAnalysisIfNecessary(containingTypeAnalyzer); } // Now try to make the analyzer for this type. diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 5319bfa7c33fd..f53c1450bdb11 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -72,6 +72,7 @@ private static async Task ConvertAsync( // 5. Format as appropriate var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var compilation = semanticModel.Compilation; var namedType = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken); // We may have to update multiple files (in the case of a partial type). Use a solution-editor to make that simple. @@ -83,6 +84,9 @@ private static async Task ConvertAsync( var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); + var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); + + var parameterToExistingFieldOrProperty = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); @@ -104,6 +108,10 @@ private static async Task ConvertAsync( foreach (var attributeList in methodTargetingAttributes) mainDocumentEditor.RemoveNode(attributeList); + // Remove all the initializers from existing fields/props the params are assigned to. + foreach (var (parameter, (fieldOrProperty, initializer)) in parameterToExistingFieldOrProperty) + mainDocumentEditor.RemoveNode(initializer); + // Now add all the fields. mainDocumentEditor.ReplaceNode( typeDeclaration, @@ -149,10 +157,9 @@ private static async Task ConvertAsync( return solutionEditor.GetChangedSolution(); - async Task RewriteReferencesToParametersAsync() + async Task> GetParameterReferencesAsync() { - var result = new MultiDictionary(); - + var result = new MultiDictionary(); foreach (var parameter in parameters) { if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) @@ -168,53 +175,117 @@ async Task RewriteReferencesToParametersAsync() // hitting each location only once. // // Note Use DistinctBy (.Net6) once available. - foreach (var grouping in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance).GroupBy(loc => loc.Location.SourceTree)) + foreach (var referenceLocation in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance)) { - var syntaxTree = grouping.Key; - var editor = await solutionEditor.GetDocumentEditorAsync(solution.GetDocumentId(syntaxTree), cancellationToken).ConfigureAwait(false); - - foreach (var referenceLocation in grouping) - { - if (referenceLocation.IsImplicit) - continue; - - if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) - continue; - - // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as - // they will still reference the parameter in the new constructor when we make the `: - // base(...)` initializer. - if (identifierName.GetAncestor() != null) - continue; - - // Don't need to update doc comment reference (e.g. `paramref=...`). These will move to the - // new constructor and will still reference the parameters there. - if (identifierName.GetAncestor() != null) - continue; - - editor.ReplaceNode(identifierName, fieldName.WithTriviaFrom(identifierName)); - } + + if (referenceLocation.IsImplicit) + continue; + + if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) + continue; + + // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as + // they will still reference the parameter in the new constructor when we make the `: + // base(...)` initializer. + if (identifierName.GetAncestor() != null) + continue; + + // Don't need to update doc comment reference (e.g. `paramref=...`). These will move to the + // new constructor and will still reference the parameters there. + if (identifierName.GetAncestor() != null) + continue; + + result.Add(parameter, identifierName); } } } + + return result; } - async Task> GetSynthesizedFieldsAsync() + async Task RewriteReferencesToParametersAsync() + { + foreach (var (parameter, references) in parameterReferences) + { + if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + continue; + + var fieldName = field.Name.ToIdentifierName(); + + foreach (var grouping in references.GroupBy(r => r.SyntaxTree)) + { + var syntaxTree = grouping.Key; + var editor = await solutionEditor.GetDocumentEditorAsync(solution.GetDocumentId(syntaxTree), cancellationToken).ConfigureAwait(false); + + foreach (var identifierName in grouping) + editor.ReplaceNode(identifierName, fieldName.WithTriviaFrom(identifierName)); + } + } + } + + async Task> GetExistingAssignedFieldsOrProperties() { - using var _ = PooledDictionary.GetInstance(out var result); + using var _1 = PooledDictionary.GetInstance(out var parameterToMemberInitializer); - foreach (var parameter in parameters) + foreach (var (parameter, references) in parameterReferences) { - var existingField = namedType.GetMembers().OfType().FirstOrDefault( - f => f.IsImplicitlyDeclared && parameter.Locations.Contains(f.Locations.FirstOrDefault()!)); - if (existingField == null) + // If this already has a synthesized parameter it is assigned to, there's definitely no field/prop it's assigned to. + if (parameterToSynthesizedFields.TryGetValue(parameter, out _)) + continue; + + // only care if there is a single reference to the parameter and it's an initializer. + if (references.Count <= 1) + continue; + + var identifierName = references.First(); + var expr = identifierName.WalkUpParentheses(); + if (expr.Parent is not EqualsValueClauseSyntax initializer) continue; - var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( - existingField, - name: await MakeFieldNameAsync(parameter.Name).ConfigureAwait(false)); + if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) + continue; + + parameterToMemberInitializer.Add(parameter, initializer); + } + + using var _2 = PooledDictionary.GetInstance(out var result); + foreach (var grouping in parameterToMemberInitializer.GroupBy(kvp => kvp.Value.SyntaxTree)) + { + var syntaxTree = grouping.Key; + var semanticModel = await solution.GetRequiredDocument(syntaxTree).GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + foreach (var (parameter, initializer) in grouping) + { + var fieldOrProperty = semanticModel.GetRequiredDeclaredSymbol(initializer.GetRequiredParent(), cancellationToken); + result.Add(parameter, (fieldOrProperty, initializer)); + } + } + + return result.ToImmutableDictionary(); + } + + async Task> GetSynthesizedFieldsAsync() + { + using var _1 = PooledDictionary.GetInstance(out var locationToField); + using var _2 = PooledDictionary.GetInstance(out var result); - result.Add(parameter, synthesizedField); + foreach (var member in namedType.GetMembers()) + { + if (member is IFieldSymbol { IsImplicitlyDeclared: false, Locations: [var location, ..] } field) + locationToField[location] = field; + } + + foreach (var parameter in parameters) + { + if (parameter.Locations is [var location, ..] && + locationToField.TryGetValue(location, out var existingField)) + { + var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( + existingField, + name: await MakeFieldNameAsync(parameter.Name).ConfigureAwait(false)); + + result.Add(parameter, synthesizedField); + } } return result.ToImmutableDictionary(); @@ -239,11 +310,12 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); foreach (var parameter in parameters) { - if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + var member = GetMemberToAssignTo(parameter); + if (member is null) continue; - var fieldName = field.Name.ToIdentifierName(); - var left = parameter.Name == field.Name + var fieldName = member.Name.ToIdentifierName(); + var left = parameter.Name == member.Name ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), fieldName) : (ExpressionSyntax)fieldName; var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, parameter.Name.ToIdentifierName()); @@ -258,5 +330,16 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); } + + ISymbol? GetMemberToAssignTo(IParameterSymbol parameter) + { + if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + return field; + + if (parameterToExistingFieldOrProperty.TryGetValue(parameter, out var member)) + return member.fieldOrProperty; + + return null; + } } } From e46646dd516313da857fa91c0b6db94894741be1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 09:57:02 -0700 Subject: [PATCH 09/39] Narrow the search --- ...PrimaryToRegularConstructorCodeRefactoringProvider.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index f53c1450bdb11..e72a1b76d1a93 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -160,6 +160,12 @@ private static async Task ConvertAsync( async Task> GetParameterReferencesAsync() { var result = new MultiDictionary(); + var documentsToSearch = namedType.DeclaringSyntaxReferences + .Select(r => r.SyntaxTree) + .Distinct() + .Select(solution.GetRequiredDocument) + .ToImmutableHashSet(); + foreach (var parameter in parameters) { if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) @@ -167,7 +173,8 @@ async Task> GetParameter var fieldName = field.Name.ToIdentifierName(); - var references = await SymbolFinder.FindReferencesAsync(parameter, solution, cancellationToken).ConfigureAwait(false); + var references = await SymbolFinder.FindReferencesAsync( + parameter, solution, documentsToSearch, cancellationToken).ConfigureAwait(false); foreach (var reference in references) { // We may hit a location multiple times due to how we do FAR for linked symbols, but each linked symbol From 9d3201fa504d6d41e8062b3995c09d84aded3e03 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:12:35 -0700 Subject: [PATCH 10/39] Fixes --- ...RegularConstructorCodeRefactoringProvider.cs | 17 +++++++++-------- .../ConvertPrimaryToRegularConstructorTests.cs | 12 +++++------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index e72a1b76d1a93..f131a2094b37c 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -146,6 +147,9 @@ private static async Task ConvertAsync( if (lastFieldOrProperty >= 0) { + constructorDeclaration = constructorDeclaration + .WithPrependedLeadingTrivia(ElasticCarriageReturnLineFeed, ElasticCarriageReturnLineFeed); + return currentTypeDeclaration.WithMembers( currentTypeDeclaration.Members.Insert(lastFieldOrProperty + 1, constructorDeclaration)); } @@ -168,10 +172,8 @@ async Task> GetParameter foreach (var parameter in parameters) { - if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) - continue; - - var fieldName = field.Name.ToIdentifierName(); + //if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) + // continue; var references = await SymbolFinder.FindReferencesAsync( parameter, solution, documentsToSearch, cancellationToken).ConfigureAwait(false); @@ -184,7 +186,6 @@ async Task> GetParameter // Note Use DistinctBy (.Net6) once available. foreach (var referenceLocation in reference.Locations.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance)) { - if (referenceLocation.IsImplicit) continue; @@ -241,10 +242,10 @@ async Task RewriteReferencesToParametersAsync() continue; // only care if there is a single reference to the parameter and it's an initializer. - if (references.Count <= 1) + if (references.Count != 1) continue; - var identifierName = references.First(); + var identifierName = references.Single(); var expr = identifierName.WalkUpParentheses(); if (expr.Parent is not EqualsValueClauseSyntax initializer) continue; @@ -313,7 +314,7 @@ async Task MakeFieldNameAsync(string parameterName) ConstructorDeclarationSyntax CreateConstructorDeclaration() { - var attributes = List(methodTargetingAttributes.Select(a => a.WithTarget(null))); + var attributes = List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithAdditionalAnnotations(Formatter.Annotation))); using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); foreach (var parameter in parameters) { diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index b222327a0deb0..ec7ac5a2479c5 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertPrimaryToRegularConstructor; @@ -47,6 +48,7 @@ record class [|C(int i)|] } """, LanguageVersion = LanguageVersion.CSharp12, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } @@ -566,12 +568,10 @@ public Enumerator(OuterType c) class OuterType { private int _i; - private int _j; public [|OuterType|](int i, int j) { _i = i; - _j = j; } public struct Enumerator @@ -1476,6 +1476,7 @@ class [|C(int i)|] """, FixedCode = """ using System; + class C { [Obsolete("", error: true)] @@ -2136,23 +2137,20 @@ public C(in int i) } [Fact] - public async Task TestInParameter2() + public async Task TestInParameter2_Unused() { await new VerifyCS.Test { TestCode = """ - class [|C(int i)|] + class [|C(in int i)|] { } """, FixedCode = """ class C { - private int i; - public C(in int i) { - this.i = i; } } """, From db3545f5f366b0528b1d2d089345be0890a82865 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:34:09 -0700 Subject: [PATCH 11/39] Update nested type references --- ...gularConstructorCodeRefactoringProvider.cs | 31 ++++++++++++++++++- ...ConvertPrimaryToRegularConstructorTests.cs | 23 ++++++++------ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index f131a2094b37c..d212d12758162 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -334,11 +335,39 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() attributes, TokenList(Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)), typeDeclaration.Identifier.WithoutTrivia(), - parameterList.WithoutTrivia(), + RewriteParameterDefaults(parameterList).WithoutTrivia(), baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); } + ParameterListSyntax RewriteParameterDefaults(ParameterListSyntax parameterList) + { + return parameterList.ReplaceNodes( + parameterList.Parameters, + (parameter, _) => RewriteNestedReferences(parameter)); + } + + TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode + { + return parent.ReplaceNodes( + parent.DescendantNodes().Where(n => n is MemberAccessExpressionSyntax or QualifiedNameSyntax), + (node, _) => + { + if (node is MemberAccessExpressionSyntax memberAccessExpression && + namedType.Equals(semanticModel.GetSymbolInfo(memberAccessExpression.Expression).Symbol)) + { + return memberAccessExpression.Name.WithTriviaFrom(node); + } + else if (node is QualifiedNameSyntax qualifiedName && + namedType.Equals(semanticModel.GetSymbolInfo(qualifiedName.Left).Symbol)) + { + return qualifiedName.Right.WithTriviaFrom(node); + } + + return node; + }); + } + ISymbol? GetMemberToAssignTo(IParameterSymbol parameter) { if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index ec7ac5a2479c5..518035ba59d54 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -47,6 +47,11 @@ record class [|C(int i)|] { } """, + FixedCode = """ + record class C(int i) + { + } + """, LanguageVersion = LanguageVersion.CSharp12, ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); @@ -2002,11 +2007,11 @@ public class D FixedCode = """ class C { - public class D + public C(D d) { } - public C(D d) + public class D { } } @@ -2035,11 +2040,11 @@ public class D class C { - public class D + public C(List d) { } - public C(List d) + public class D { } } @@ -2064,11 +2069,11 @@ public class D FixedCode = """ class C { - public class D + public C(D d) { } - public C(C.D d) + public class D { } } @@ -2097,11 +2102,11 @@ public class D class C { - public class D + public C(List d) { } - public C(List.D> d) + public class D { } } @@ -2341,7 +2346,7 @@ class C private const int Default = 0; private int _i; - public C(int i = C.Default) + public C(int i = Default) { _i = i; } From f736afaeadf98107e44476f8aa0dd9a4fa90237b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:41:29 -0700 Subject: [PATCH 12/39] in progress --- ...gularConstructorCodeRefactoringProvider.cs | 16 +++++- ...ConvertPrimaryToRegularConstructorTests.cs | 49 +++++++++++++++---- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index d212d12758162..76b76bf7bff59 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -112,7 +112,21 @@ private static async Task ConvertAsync( // Remove all the initializers from existing fields/props the params are assigned to. foreach (var (parameter, (fieldOrProperty, initializer)) in parameterToExistingFieldOrProperty) - mainDocumentEditor.RemoveNode(initializer); + { + if (initializer.Parent is PropertyDeclarationSyntax propertyDeclaration) + { + mainDocumentEditor.ReplaceNode( + propertyDeclaration, + propertyDeclaration + .WithInitializer(null) + .WithSemicolonToken(default) + .WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); + } + else + { + mainDocumentEditor.RemoveNode(initializer); + } + } // Now add all the fields. mainDocumentEditor.ReplaceNode( diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 518035ba59d54..01a3aae94ec51 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -345,7 +345,7 @@ class C public C(int i) { - this.I = i; + I = i; } } """, @@ -462,11 +462,20 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembers1() + public async Task TestRemoveMembers1_Used() { await new VerifyCS.Test { TestCode = """ + class [|C(int i, int j)|] + { + int M() + { + return i + j; + } + } + """, + FixedCode = """ class C { private int i; @@ -477,11 +486,33 @@ class C this.i = i; this.j = j; } + + int M() + { + return i + j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembers1_Unused() + { + await new VerifyCS.Test + { + TestCode = """ + class [|C(int i, int j)|] + { } """, FixedCode = """ - class C(int i, int j) + class C { + public C(int i, int j) + { + } } """, LanguageVersion = LanguageVersion.CSharp12, @@ -2181,13 +2212,12 @@ class [|C(int i)|] FixedCode = """ class C { - - #region constructors - - public [|C|](int i) + public C(int i) { } + #region constructors + #endregion } @@ -2218,13 +2248,12 @@ public C(string s) : this(s.Length) FixedCode = """ class C { - - #region constructors - public C(int i) { } + #region constructors + public C(string s) : this(s.Length) { } From 48c02589098ac3aeee9e32ca11e4d070a62f9d7e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:46:55 -0700 Subject: [PATCH 13/39] Fixes --- ...ToRegularConstructorCodeRefactoringProvider.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 76b76bf7bff59..efb278b0a189e 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -261,8 +261,8 @@ async Task RewriteReferencesToParametersAsync() continue; var identifierName = references.Single(); - var expr = identifierName.WalkUpParentheses(); - if (expr.Parent is not EqualsValueClauseSyntax initializer) + var initializer = identifierName.AncestorsAndSelf().OfType().LastOrDefault(); + if (initializer is null) continue; if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) @@ -333,15 +333,14 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); foreach (var parameter in parameters) { - var member = GetMemberToAssignTo(parameter); - if (member is null) + if (GetMemberToAssignTo(parameter) is not (var member, var value)) continue; var fieldName = member.Name.ToIdentifierName(); var left = parameter.Name == member.Name ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), fieldName) : (ExpressionSyntax)fieldName; - var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, parameter.Name.ToIdentifierName()); + var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, value); assignmentStatements.Add(ExpressionStatement(assignment)); } @@ -382,13 +381,13 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode }); } - ISymbol? GetMemberToAssignTo(IParameterSymbol parameter) + (ISymbol member, ExpressionSyntax value)? GetMemberToAssignTo(IParameterSymbol parameter) { if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) - return field; + return (field, parameter.Name.ToIdentifierName()); if (parameterToExistingFieldOrProperty.TryGetValue(parameter, out var member)) - return member.fieldOrProperty; + return (member.fieldOrProperty, member.initializer.Value); return null; } From 5aeeb49ce165920e49452fe90488f21f5af0d7fb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:49:34 -0700 Subject: [PATCH 14/39] inner node --- ...ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index efb278b0a189e..d6c505f9657c5 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -204,7 +204,7 @@ async Task> GetParameter if (referenceLocation.IsImplicit) continue; - if (referenceLocation.Location.FindNode(cancellationToken) is not IdentifierNameSyntax identifierName) + if (referenceLocation.Location.FindNode(getInnermostNodeForTie: true, cancellationToken) is not IdentifierNameSyntax identifierName) continue; // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as From 33d07b8f609e5c2ea71f260e1dc8987a6f1e4b0b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 10:56:50 -0700 Subject: [PATCH 15/39] in progrss --- ...gularConstructorCodeRefactoringProvider.cs | 17 +++++++++--- ...ConvertPrimaryToRegularConstructorTests.cs | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index d6c505f9657c5..ac90ca866aca0 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -148,6 +148,8 @@ private static async Task ConvertAsync( { // If there is an existing non-static constructor, place it before that var currentTypeDeclaration = (TypeDeclarationSyntax)current; + currentTypeDeclaration = RewriteParamRefNodes(currentTypeDeclaration); + var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); if (firstConstructorIndex >= 0) { @@ -204,7 +206,7 @@ async Task> GetParameter if (referenceLocation.IsImplicit) continue; - if (referenceLocation.Location.FindNode(getInnermostNodeForTie: true, cancellationToken) is not IdentifierNameSyntax identifierName) + if (referenceLocation.Location.FindNode(findInsideTrivia: true, getInnermostNodeForTie: true, cancellationToken) is not IdentifierNameSyntax identifierName) continue; // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as @@ -257,11 +259,13 @@ async Task RewriteReferencesToParametersAsync() continue; // only care if there is a single reference to the parameter and it's an initializer. - if (references.Count != 1) + var initializers = references + .Select(r => r.AncestorsAndSelf().OfType().LastOrDefault()) + .ToSet(); + if (initializers.Count != 1) continue; - var identifierName = references.Single(); - var initializer = identifierName.AncestorsAndSelf().OfType().LastOrDefault(); + var initializer = initializers.Single(); if (initializer is null) continue; @@ -391,5 +395,10 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode return null; } + + TypeDeclarationSyntax RewriteParamRefNodes(TypeDeclarationSyntax typeDeclaration) + { + + } } } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 01a3aae94ec51..15bd1114a368c 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -405,6 +405,32 @@ public C(int i) }.RunAsync(); } + [Fact] + public async Task TestWithComplexRightSide2() + { + await new VerifyCS.Test + { + TestCode = """ + class [|C(int i)|] + { + private int i = i * i; + } + """, + FixedCode = """ + class C + { + private int i; + + public C(int i) + { + this.i = i * i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestBlockWithMultipleAssignments1() { From 7be704fec1a27289d1356bb2ce17b42589c9dc84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 11:16:00 -0700 Subject: [PATCH 16/39] Update xml --- ...gularConstructorCodeRefactoringProvider.cs | 37 ++++++----- ...ConvertPrimaryToRegularConstructorTests.cs | 63 ++++++++++++++++++- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index ac90ca866aca0..6f074f6859952 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -148,7 +149,6 @@ private static async Task ConvertAsync( { // If there is an existing non-static constructor, place it before that var currentTypeDeclaration = (TypeDeclarationSyntax)current; - currentTypeDeclaration = RewriteParamRefNodes(currentTypeDeclaration); var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); if (firstConstructorIndex >= 0) @@ -215,11 +215,6 @@ async Task> GetParameter if (identifierName.GetAncestor() != null) continue; - // Don't need to update doc comment reference (e.g. `paramref=...`). These will move to the - // new constructor and will still reference the parameters there. - if (identifierName.GetAncestor() != null) - continue; - result.Add(parameter, identifierName); } } @@ -243,7 +238,22 @@ async Task RewriteReferencesToParametersAsync() var editor = await solutionEditor.GetDocumentEditorAsync(solution.GetDocumentId(syntaxTree), cancellationToken).ConfigureAwait(false); foreach (var identifierName in grouping) - editor.ReplaceNode(identifierName, fieldName.WithTriviaFrom(identifierName)); + { + var xmlElement = identifierName.AncestorsAndSelf().OfType().FirstOrDefault(); + if (xmlElement is { Name.LocalName.ValueText: "paramref" }) + { + var seeTag = xmlElement + .ReplaceToken(xmlElement.Name.LocalName, Identifier("see").WithTriviaFrom(xmlElement.Name.LocalName)) + .WithAttributes(SingletonList(XmlCrefAttribute( + TypeCref(fieldName)))); + + editor.ReplaceNode(xmlElement, seeTag); + } + else + { + editor.ReplaceNode(identifierName, fieldName.WithTriviaFrom(identifierName)); + } + } } } } @@ -258,17 +268,15 @@ async Task RewriteReferencesToParametersAsync() if (parameterToSynthesizedFields.TryGetValue(parameter, out _)) continue; - // only care if there is a single reference to the parameter and it's an initializer. + // only care if all the references to the parameter are in a single initializer. var initializers = references .Select(r => r.AncestorsAndSelf().OfType().LastOrDefault()) + .WhereNotNull() .ToSet(); if (initializers.Count != 1) continue; var initializer = initializers.Single(); - if (initializer is null) - continue; - if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) continue; @@ -298,7 +306,7 @@ async Task> GetSynthesizedFi foreach (var member in namedType.GetMembers()) { - if (member is IFieldSymbol { IsImplicitlyDeclared: false, Locations: [var location, ..] } field) + if (member is IFieldSymbol { IsImplicitlyDeclared: true, Locations: [var location, ..] } field) locationToField[location] = field; } @@ -395,10 +403,5 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode return null; } - - TypeDeclarationSyntax RewriteParamRefNodes(TypeDeclarationSyntax typeDeclaration) - { - - } } } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 15bd1114a368c..cdc97b3945329 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -15,6 +15,17 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertPrimaryToRegular public class ConvertPrimaryToRegularConstructorTests { + private const string FieldNamesCamelCaseWithFieldUnderscorePrefixEditorConfig = """ + [*.cs] + dotnet_naming_style.field_camel_case.capitalization = camel_case + dotnet_naming_style.field_camel_case.required_prefix = _ + dotnet_naming_symbols.fields.applicable_kinds = field + dotnet_naming_symbols.fields.applicable_accessibilities = * + dotnet_naming_rule.fields_should_be_camel_case.severity = error + dotnet_naming_rule.fields_should_be_camel_case.symbols = fields + dotnet_naming_rule.fields_should_be_camel_case.style = field_camel_case + """; + [Fact] public async Task TestInCSharp12() { @@ -2335,6 +2346,50 @@ public async Task TestSeeTag2() /// class [|C(int i)|] { + int M() + { + return i; + } + } + """, + FixedCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class C + { + private int i; + + public C(int i) + { + this.i = i; + } + + int M() + { + return i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestSeeTag3() + { + await new VerifyCS.Test + { + TestCode = """ + /// + /// Provides strongly typed wrapper around . + /// + class [|C(int i)|] + { + int M() + { + return i; + } } """, FixedCode = """ @@ -2345,13 +2400,19 @@ class C { private int _i; - public [|C|](int i) + public C(int i) { _i = i; } + + int M() + { + return _i; + } } """, LanguageVersion = LanguageVersion.CSharp12, + EditorConfig = FieldNamesCamelCaseWithFieldUnderscorePrefixEditorConfig, }.RunAsync(); } From 2201c499275eaaabab059dcc2179b23764db729d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 14 Oct 2023 11:29:03 -0700 Subject: [PATCH 17/39] in proress --- ...gularConstructorCodeRefactoringProvider.cs | 25 ++++++++++ ...ConvertPrimaryToRegularConstructorTests.cs | 47 +++---------------- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 6f074f6859952..7ef16e0a1e31b 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -149,6 +149,7 @@ private static async Task ConvertAsync( { // If there is an existing non-static constructor, place it before that var currentTypeDeclaration = (TypeDeclarationSyntax)current; + currentTypeDeclaration = RemoveParamXmlElements(currentTypeDeclaration); var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); if (firstConstructorIndex >= 0) @@ -178,6 +179,30 @@ private static async Task ConvertAsync( return solutionEditor.GetChangedSolution(); + TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) + { + using var _ = ArrayBuilder.GetInstance(out var leadingTrivia); + + foreach (var trivia in typeDeclaration.GetLeadingTrivia()) + { + if (trivia.GetStructure() is not DocumentationCommentTriviaSyntax docComment) + { + leadingTrivia.Add(trivia); + } + else + { + var updatedComment = docComment.RemoveNodes(docComment + .DescendantNodes() + .OfType() + .Where(x => x.StartTag.Name.LocalName.ValueText == "param"), + SyntaxRemoveOptions.AddElasticMarker); + leadingTrivia.Add(Trivia(updatedComment!)); + } + } + + return typeDeclaration.WithLeadingTrivia(leadingTrivia); + } + async Task> GetParameterReferencesAsync() { var result = new MultiDictionary(); diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index cdc97b3945329..3f9e93ae67146 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -557,56 +557,29 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembers2() + public async Task TestRemoveMembersOnlyWithMatchingType() { await new VerifyCS.Test { TestCode = """ - class C + class [|C(int i, int j)|] { - private int I { get; } - private int J { get; } - - public [|C|](int i, int j) - { - this.I = i; - this.J = j; - } + private long J { get; } = j; } """, FixedCode = """ - class C(int i, int j) - { - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestRemoveMembersOnlyWithMatchingType() - { - await new VerifyCS.Test - { - TestCode = """ class C { private int I { get; } private long J { get; } - public [|C|](int i, int j) + public C(int i, int j) { this.I = i; this.J = j; } } """, - FixedCode = """ - class C(int i, int j) - { - private long J { get; } = j; - } - """, LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } @@ -881,8 +854,8 @@ void M(C c) using System; class C { - private int _i; private int _j; + private int _i; public C(int i, int j) { @@ -898,6 +871,7 @@ void M(C c) } """, LanguageVersion = LanguageVersion.CSharp12, + EditorConfig = FieldNamesCamelCaseWithFieldUnderscorePrefixEditorConfig, }.RunAsync(); } @@ -1420,19 +1394,10 @@ namespace N /// class C { - /// Field docs for i. - private int i; - /// - /// Field docs for j. - /// - private int j; - /// Param docs for i /// Param docs for j public C(int i, int j) { - this.i = i; - this.j = j; } } } From 07e0b32b8b76487f2e866eefb618e407ef965e92 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 09:45:01 -0700 Subject: [PATCH 18/39] move all initializers --- ...gularConstructorCodeRefactoringProvider.cs | 67 +++++++++++-------- ...ConvertPrimaryToRegularConstructorTests.cs | 29 ++++++++ 2 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 7ef16e0a1e31b..8e0e04b9004af 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Data.Common; @@ -74,8 +75,10 @@ private static async Task ConvertAsync( // 4. Update references to parameters to be references to fields // 5. Format as appropriate - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var compilation = semanticModel.Compilation; + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var semanticModels = new ConcurrentSet(); + + var semanticModel = await GetSemanticModelAsync(document).ConfigureAwait(false); var namedType = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken); // We may have to update multiple files (in the case of a partial type). Use a solution-editor to make that simple. @@ -89,7 +92,7 @@ private static async Task ConvertAsync( var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); - var parameterToExistingFieldOrProperty = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); + var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); @@ -112,7 +115,7 @@ private static async Task ConvertAsync( mainDocumentEditor.RemoveNode(attributeList); // Remove all the initializers from existing fields/props the params are assigned to. - foreach (var (parameter, (fieldOrProperty, initializer)) in parameterToExistingFieldOrProperty) + foreach (var (_, initializer) in initializedFieldsAndProperties.OrderBy(i => i.initializer.SpanStart)) { if (initializer.Parent is PropertyDeclarationSyntax propertyDeclaration) { @@ -123,10 +126,14 @@ private static async Task ConvertAsync( .WithSemicolonToken(default) .WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); } - else + else if (initializer.Parent is VariableDeclaratorSyntax) { mainDocumentEditor.RemoveNode(initializer); } + else + { + throw ExceptionUtilities.Unreachable(); + } } // Now add all the fields. @@ -179,6 +186,15 @@ private static async Task ConvertAsync( return solutionEditor.GetChangedSolution(); + async ValueTask GetSemanticModelAsync(Document document) + { + // Ensure that if we get a semantic model for another document this named type is contained in, that we only + // produce that semantic model once. + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + semanticModels.Add(semanticModel); + return semanticModel; + } + TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) { using var _ = ArrayBuilder.GetInstance(out var leadingTrivia); @@ -283,45 +299,38 @@ async Task RewriteReferencesToParametersAsync() } } - async Task> GetExistingAssignedFieldsOrProperties() + async Task> GetExistingAssignedFieldsOrProperties() { - using var _1 = PooledDictionary.GetInstance(out var parameterToMemberInitializer); - + using var _1 = PooledHashSet.GetInstance(out var initializers); foreach (var (parameter, references) in parameterReferences) { - // If this already has a synthesized parameter it is assigned to, there's definitely no field/prop it's assigned to. - if (parameterToSynthesizedFields.TryGetValue(parameter, out _)) - continue; - - // only care if all the references to the parameter are in a single initializer. - var initializers = references - .Select(r => r.AncestorsAndSelf().OfType().LastOrDefault()) - .WhereNotNull() - .ToSet(); - if (initializers.Count != 1) - continue; + foreach (var reference in references) + { + var initializer = reference.AncestorsAndSelf().OfType().LastOrDefault(); + if (initializer is null) + continue; - var initializer = initializers.Single(); - if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) - continue; + if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) + continue; - parameterToMemberInitializer.Add(parameter, initializer); + initializers.Add(initializer); + } } - using var _2 = PooledDictionary.GetInstance(out var result); - foreach (var grouping in parameterToMemberInitializer.GroupBy(kvp => kvp.Value.SyntaxTree)) + using var _2 = PooledHashSet<(ISymbol fieldOrProperty, EqualsValueClauseSyntax initializer)>.GetInstance(out var result); + foreach (var grouping in initializers.GroupBy(kvp => kvp.Value.SyntaxTree)) { var syntaxTree = grouping.Key; - var semanticModel = await solution.GetRequiredDocument(syntaxTree).GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await GetSemanticModelAsync(solution.GetRequiredDocument(syntaxTree)).ConfigureAwait(false); - foreach (var (parameter, initializer) in grouping) + foreach (var initializer in grouping) { var fieldOrProperty = semanticModel.GetRequiredDeclaredSymbol(initializer.GetRequiredParent(), cancellationToken); - result.Add(parameter, (fieldOrProperty, initializer)); + result.Add((fieldOrProperty, initializer)); } } - return result.ToImmutableDictionary(); + return result.ToImmutableHashSet(); } async Task> GetSynthesizedFieldsAsync() diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 3f9e93ae67146..3afeae6599eec 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -442,6 +442,35 @@ public C(int i) }.RunAsync(); } + [Fact] + public async Task TestWithComplexRightSide3() + { + await new VerifyCS.Test + { + TestCode = """ + class [|C(int i)|] + { + private int i = i * i; + private int j = i + i; + } + """, + FixedCode = """ + class C + { + private int i; + private int j; + + public C(int i) + { + this.i = i * i; + this.j = i + i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestBlockWithMultipleAssignments1() { From 8f18337840080b11be39dc8e7c044b22d9675a2e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 09:51:37 -0700 Subject: [PATCH 19/39] Handle multiple initializers case --- ...gularConstructorCodeRefactoringProvider.cs | 26 ++++++++++++++----- ...ConvertPrimaryToRegularConstructorTests.cs | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 8e0e04b9004af..81fa1216d6bfd 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -25,6 +25,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -89,9 +90,14 @@ private static async Task ConvertAsync( var syntaxGenerator = CSharpSyntaxGenerator.Instance; var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); + // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll + // create real fields for all these cases. var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); + var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); + // Find any field/properties whose initializer references a primary constructor parameter. These initializers + // will have to move inside the constructor we generate. var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; @@ -115,7 +121,7 @@ private static async Task ConvertAsync( mainDocumentEditor.RemoveNode(attributeList); // Remove all the initializers from existing fields/props the params are assigned to. - foreach (var (_, initializer) in initializedFieldsAndProperties.OrderBy(i => i.initializer.SpanStart)) + foreach (var (_, initializer) in initializedFieldsAndProperties) { if (initializer.Parent is PropertyDeclarationSyntax propertyDeclaration) { @@ -375,8 +381,9 @@ async Task MakeFieldNameAsync(string parameterName) ConstructorDeclarationSyntax CreateConstructorDeclaration() { - var attributes = List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithAdditionalAnnotations(Formatter.Annotation))); using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); + + // First, if we're making a real field for a primary constructor parameter, assign the parameter to it. foreach (var parameter in parameters) { if (GetMemberToAssignTo(parameter) is not (var member, var value)) @@ -390,8 +397,18 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() assignmentStatements.Add(ExpressionStatement(assignment)); } + // Next, actually assign to all the fields/properties that were previously referencing any primary + // constructor parameters. + foreach (var (fieldOrProperty, initializer) in initializedFieldsAndProperties.OrderBy(i => i.initializer.SpanStart)) + { + var left = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), fieldOrProperty.Name.ToIdentifierName()) + .WithAdditionalAnnotations(Simplifier.Annotation); + var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, initializer.EqualsToken, initializer.Value); + assignmentStatements.Add(ExpressionStatement(assignment)); + } + return ConstructorDeclaration( - attributes, + List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithAdditionalAnnotations(Formatter.Annotation))), TokenList(Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)), typeDeclaration.Identifier.WithoutTrivia(), RewriteParameterDefaults(parameterList).WithoutTrivia(), @@ -432,9 +449,6 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) return (field, parameter.Name.ToIdentifierName()); - if (parameterToExistingFieldOrProperty.TryGetValue(parameter, out var member)) - return (member.fieldOrProperty, member.initializer.Value); - return null; } } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 3afeae6599eec..fbff9527eee88 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -463,7 +463,7 @@ class C public C(int i) { this.i = i * i; - this.j = i + i; + j = i + i; } } """, From 3f583e06bf62af72047c156b00bae29985e3ccd3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 10:11:00 -0700 Subject: [PATCH 20/39] Cleaner doc code --- ...gularConstructorCodeRefactoringProvider.cs | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 81fa1216d6bfd..5a2dd62ed5a5b 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -203,26 +203,39 @@ async ValueTask GetSemanticModelAsync(Document document) TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) { - using var _ = ArrayBuilder.GetInstance(out var leadingTrivia); + var triviaList = typeDeclaration.GetLeadingTrivia(); + var trivia = GetDocComment(triviaList); + var docComment = GetDocCommentStructure(trivia); + if (docComment == null) + return typeDeclaration; - foreach (var trivia in typeDeclaration.GetLeadingTrivia()) + using var _ = ArrayBuilder.GetInstance(out var content); + + foreach (var node in docComment.Content) { - if (trivia.GetStructure() is not DocumentationCommentTriviaSyntax docComment) - { - leadingTrivia.Add(trivia); - } - else - { - var updatedComment = docComment.RemoveNodes(docComment - .DescendantNodes() - .OfType() - .Where(x => x.StartTag.Name.LocalName.ValueText == "param"), - SyntaxRemoveOptions.AddElasticMarker); - leadingTrivia.Add(Trivia(updatedComment!)); - } + if (!IsXmlElement(node, "param", out var paramElement)) + content.Add(node); } - return typeDeclaration.WithLeadingTrivia(leadingTrivia); + if (content.Count == 0) + { + // Nothing but param nodes. Just remove all the doc comments entirely. + var triviaIndex = triviaList.IndexOf(trivia); + + // remove the doc comment itself + var updatedTriviaList = triviaList.RemoveAt(triviaIndex); + + // If the comment was on a line that started with whitespace, remove that whitespce too. + if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) + updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); + + return typeDeclaration.WithLeadingTrivia(updatedTriviaList); + } + else + { + var updatedTrivia = Trivia(docComment.WithContent(List(content))); + return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); + } } async Task> GetParameterReferencesAsync() @@ -452,4 +465,27 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode return null; } } + + private static SyntaxTrivia GetDocComment(SyntaxNode node) + => GetDocComment(node.GetLeadingTrivia()); + + private static SyntaxTrivia GetDocComment(SyntaxTriviaList trivia) + => trivia.LastOrDefault(t => t.IsSingleLineDocComment()); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxNode node) + => GetDocCommentStructure(node.GetLeadingTrivia()); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTriviaList trivia) + => GetDocCommentStructure(GetDocComment(trivia)); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTrivia trivia) + => (DocumentationCommentTriviaSyntax?)trivia.GetStructure(); + + private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(true)] out XmlElementSyntax? element) + { + element = node is XmlElementSyntax { StartTag.Name.LocalName.ValueText: var elementName } xmlElement && elementName == name + ? xmlElement + : null; + return element != null; + } } From eb60b9af840e9ccc0684cf0a6156dd04dde32604 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 12:11:06 -0700 Subject: [PATCH 21/39] Doc comments --- ...gularConstructorCodeRefactoringProvider.cs | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 5a2dd62ed5a5b..345ecd91d9a51 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -213,8 +213,16 @@ TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclarati foreach (var node in docComment.Content) { - if (!IsXmlElement(node, "param", out var paramElement)) + if (IsXmlElement(node, "param", out var paramElement)) + { + // We're skipping a param node. Remove any blank xml lines preceding this. + if (IsDocCommentNewLine(content.LastOrDefault())) + content.RemoveLast(); + } + else + { content.Add(node); + } } if (content.Count == 0) @@ -394,7 +402,7 @@ async Task MakeFieldNameAsync(string parameterName) ConstructorDeclarationSyntax CreateConstructorDeclaration() { - using var _ = ArrayBuilder.GetInstance(out var assignmentStatements); + using var _1 = ArrayBuilder.GetInstance(out var assignmentStatements); // First, if we're making a real field for a primary constructor parameter, assign the parameter to it. foreach (var parameter in parameters) @@ -420,13 +428,43 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() assignmentStatements.Add(ExpressionStatement(assignment)); } - return ConstructorDeclaration( - List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithAdditionalAnnotations(Formatter.Annotation))), + var constructorDeclaration = ConstructorDeclaration( + List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation))), TokenList(Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)), typeDeclaration.Identifier.WithoutTrivia(), RewriteParameterDefaults(parameterList).WithoutTrivia(), baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); + + // Now move the param tags on the type decl over to the constructor. + var triviaList = typeDeclaration.GetLeadingTrivia(); + var trivia = GetDocComment(triviaList); + var docComment = GetDocCommentStructure(trivia); + if (docComment != null) + { + using var _2 = ArrayBuilder.GetInstance(out var content); + + for (int i = 0, n = docComment.Content.Count; i < n; i++) + { + var node = docComment.Content[i]; + if (IsXmlElement(node, "param", out var paramElement)) + { + // if the param tag was on a newline, the preserve that when transferring over. + if (content.Count > 0 && IsDocCommentNewLine(docComment.Content[i - 1])) + content.Add(docComment.Content[i - 1]); + + content.Add(node); + } + } + + if (content.Count > 0) + content.Add(XmlText(XmlTextNewLine("\r\n", continueXmlDocumentationComment: false))); + + if (content.Count > 0) + return constructorDeclaration.WithLeadingTrivia(Trivia(DocumentationComment(content.ToArray())).WithAdditionalAnnotations(Formatter.Annotation)); + } + + return constructorDeclaration; } ParameterListSyntax RewriteParameterDefaults(ParameterListSyntax parameterList) @@ -488,4 +526,8 @@ private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(t : null; return element != null; } + + private static bool IsDocCommentNewLine([NotNullWhen(true)] XmlNodeSyntax? node) + => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralNewLineToken), (kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && + string.IsNullOrWhiteSpace(precedingText.Text); } From 4823bc2e0c160d6a53efed940197fb5110b9734a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 12:17:19 -0700 Subject: [PATCH 22/39] Fies --- ...ConvertPrimaryToRegularConstructorTests.cs | 93 +++++++------------ 1 file changed, 31 insertions(+), 62 deletions(-) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index fbff9527eee88..5beeec1951b40 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -599,13 +599,11 @@ class [|C(int i, int j)|] FixedCode = """ class C { - private int I { get; } private long J { get; } public C(int i, int j) { - this.I = i; - this.J = j; + J = j; } } """, @@ -695,7 +693,7 @@ public C(int i, int j) void M() { - Console.WriteLine(this.i + this.j); + Console.WriteLine(i + j); } } """, @@ -722,18 +720,18 @@ void M() using System; class C { - private int i; - private int j; + private int @this; + private int @delegate; public C(int @this, int @delegate) { - this.i = @this; - this.j = @delegate; + this.@this = @this; + this.@delegate = @delegate; } void M() { - Console.WriteLine(this.i + this.j); + Console.WriteLine(@this + @delegate); } } """, @@ -760,18 +758,18 @@ void M() using System; class C { - private int _i; - private int _j; + private int i; + private int j; public C(int i, int j) { - _i = i; - _j = j; + this.i = i; + this.j = j; } void M() { - Console.WriteLine(_i + _j); + Console.WriteLine(i + j); } } """, @@ -800,18 +798,18 @@ void M() using System; class C { - private int _i; public int _j; + private int i; public C(int i, int j) { - _i = i; + this.i = i; _j = j; } void M() { - Console.WriteLine(_i + _j); + Console.WriteLine(i + _j); } } """, @@ -841,19 +839,19 @@ void M() using System; class C { - private int _i; [CLSCompliant(true)] private int _j; + private int i; public [|C|](int i, int j) { - _i = i; + this. i = i; _j = j; } void M() { - Console.WriteLine(_i + _j); + Console.WriteLine(i + _j); } } """, @@ -1290,17 +1288,12 @@ namespace N { class C { - /// Docs for i. - private int i; - /// + /// Docs for i. + /// /// Docs for j. - /// - private int j; - + /// public C(int i, int j) { - this.i = i; - this.j = j; } } } @@ -1331,17 +1324,12 @@ namespace N { class C { - /// Docs for x. - private int x; - /// + /// Docs for x. + /// /// Docs for y. - /// - private int y; - + /// public C(int i, int j) { - this.x = i; - this.y = j; } } } @@ -1378,17 +1366,12 @@ namespace N /// class C { - /// Docs for i. - private int i; - /// + /// Docs for i. + /// /// Docs for j. - /// - private int j; - + /// public C(int i, int j) { - this.i = i; - this.j = j; } } } @@ -1461,18 +1444,10 @@ namespace N /// class C { - /// Field docs for i. - private int i; - /// - /// Field docs for j. - /// - private int j; - /// Param docs for j + /// Field docs for i. public C(int i, int j) { - this.i = i; - this.j = j; } } } @@ -1509,18 +1484,12 @@ namespace N /// class C { - /// Field docs for i. - private int i; - /// - /// Field docs for j. - /// - private int j; - /// Param docs for i + /// + /// Field docs for j. + /// public C(int i, int j) { - this.i = i; - this.j = j; } } } From acca36731ba45076234c40fbefc10a0893a3268e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 12:30:20 -0700 Subject: [PATCH 23/39] further along --- ...gularConstructorCodeRefactoringProvider.cs | 6 + ...ConvertPrimaryToRegularConstructorTests.cs | 156 +++++++++++++++++- 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 345ecd91d9a51..cc18b703ef628 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -218,6 +218,8 @@ TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclarati // We're skipping a param node. Remove any blank xml lines preceding this. if (IsDocCommentNewLine(content.LastOrDefault())) content.RemoveLast(); + else if (content.Count == 1 && IsEmptyDocCommentStartText(content.LastOrDefault())) + content.RemoveLast(); } else { @@ -530,4 +532,8 @@ private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(t private static bool IsDocCommentNewLine([NotNullWhen(true)] XmlNodeSyntax? node) => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralNewLineToken), (kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && string.IsNullOrWhiteSpace(precedingText.Text); + + private static bool IsEmptyDocCommentStartText([NotNullWhen(true)] XmlNodeSyntax? node) + => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && + string.IsNullOrWhiteSpace(precedingText.Text); } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 5beeec1951b40..8b087cf4366ce 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -845,7 +845,7 @@ class C public [|C|](int i, int j) { - this. i = i; + this. i = i; _j = j; } @@ -1038,6 +1038,36 @@ public C(int i) }.RunAsync(); } + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_3() + { + await new VerifyCS.Test + { + TestCode = """ + ///Doc comment on single line + ///Doc about i single line + class [|C(int i)|] + { + private int i = i; + } + """, + FixedCode = """ + /// Doc comment on single line + class C + { + private int i; + + ///Doc about i single line + public C(int i) + { + this.i = i; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_1() { @@ -1063,14 +1093,14 @@ class [|C(int i)|] FixedCode = """ namespace N { + /// + /// Doc comment + /// On multiple lines + /// class C { private int i; - /// - /// Doc comment - /// On multiple lines - /// /// /// Doc about i /// on multiple lines @@ -1170,6 +1200,54 @@ public C(int i) }.RunAsync(); } + [Fact] + public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_4() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// + ///Doc comment + ///On multiple lines + /// + /// + ///Doc about i + ///on multiple lines + /// + class [|C(int i)|] + { + private int i = i; + } + } + """, + FixedCode = """ + namespace N + { + /// + ///Doc comment + ///On multiple lines + /// + class C + { + private int i; + + /// + ///Doc about i + ///on multiple lines + /// + public C(int i) + { + this.i = i; + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments1() { @@ -1302,6 +1380,74 @@ public C(int i, int j) }.RunAsync(); } + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments2() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + ///Docs for i. + /// + ///Docs for j. + /// + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ + namespace N + { + class C + { + ///Docs for i. + /// + ///Docs for j. + /// + public C(int i, int j) + { + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments3() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + ///Docs for i. + ///Docs for j. + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ + namespace N + { + class C + { + ///Docs for i. + ///Docs for j. + public C(int i, int j) + { + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments_MembersWithDifferentNames1() { From 9834647dd7d07847e8655277fe428e4bd29e5a8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 12:33:36 -0700 Subject: [PATCH 24/39] Fixes --- ...PrimaryToRegularConstructorCodeRefactoringProvider.cs | 9 +++++---- .../ConvertPrimaryToRegularConstructorTests.cs | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index cc18b703ef628..2340dc743b530 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -218,8 +218,8 @@ TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclarati // We're skipping a param node. Remove any blank xml lines preceding this. if (IsDocCommentNewLine(content.LastOrDefault())) content.RemoveLast(); - else if (content.Count == 1 && IsEmptyDocCommentStartText(content.LastOrDefault())) - content.RemoveLast(); + //else if (content.Count == 1 && IsEmptyDocCommentStartText(content.LastOrDefault())) + // content.RemoveLast(); } else { @@ -227,7 +227,8 @@ TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclarati } } - if (content.Count == 0) + if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( + t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) { // Nothing but param nodes. Just remove all the doc comments entirely. var triviaIndex = triviaList.IndexOf(trivia); @@ -279,7 +280,7 @@ async Task> GetParameter if (referenceLocation.Location.FindNode(findInsideTrivia: true, getInnermostNodeForTie: true, cancellationToken) is not IdentifierNameSyntax identifierName) continue; - // Explicitly ignore references in the base-type-list. Tehse don't need to be rewritten as + // Explicitly ignore references in the base-type-list. These don't need to be rewritten as // they will still reference the parameter in the new constructor when we make the `: // base(...)` initializer. if (identifierName.GetAncestor() != null) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 8b087cf4366ce..f656a3a94a8f1 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -845,7 +845,7 @@ class C public [|C|](int i, int j) { - this. i = i; + this.i = i; _j = j; } @@ -1402,7 +1402,7 @@ namespace N { class C { - ///Docs for i. + /// Docs for i. /// ///Docs for j. /// @@ -1436,7 +1436,7 @@ namespace N { class C { - ///Docs for i. + /// Docs for i. ///Docs for j. public C(int i, int j) { From f3883a8f9efea0649917cd01355a4ae8f3f75a17 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 13:17:58 -0700 Subject: [PATCH 25/39] Workign docs --- ...gularConstructorCodeRefactoringProvider.cs | 180 ++++++++++++------ ...ConvertPrimaryToRegularConstructorTests.cs | 106 ++++++++++- 2 files changed, 220 insertions(+), 66 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 2340dc743b530..4c93c990d449e 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -201,54 +201,6 @@ async ValueTask GetSemanticModelAsync(Document document) return semanticModel; } - TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) - { - var triviaList = typeDeclaration.GetLeadingTrivia(); - var trivia = GetDocComment(triviaList); - var docComment = GetDocCommentStructure(trivia); - if (docComment == null) - return typeDeclaration; - - using var _ = ArrayBuilder.GetInstance(out var content); - - foreach (var node in docComment.Content) - { - if (IsXmlElement(node, "param", out var paramElement)) - { - // We're skipping a param node. Remove any blank xml lines preceding this. - if (IsDocCommentNewLine(content.LastOrDefault())) - content.RemoveLast(); - //else if (content.Count == 1 && IsEmptyDocCommentStartText(content.LastOrDefault())) - // content.RemoveLast(); - } - else - { - content.Add(node); - } - } - - if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( - t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) - { - // Nothing but param nodes. Just remove all the doc comments entirely. - var triviaIndex = triviaList.IndexOf(trivia); - - // remove the doc comment itself - var updatedTriviaList = triviaList.RemoveAt(triviaIndex); - - // If the comment was on a line that started with whitespace, remove that whitespce too. - if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) - updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); - - return typeDeclaration.WithLeadingTrivia(updatedTriviaList); - } - else - { - var updatedTrivia = Trivia(docComment.WithContent(List(content))); - return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); - } - } - async Task> GetParameterReferencesAsync() { var result = new MultiDictionary(); @@ -452,19 +404,24 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() var node = docComment.Content[i]; if (IsXmlElement(node, "param", out var paramElement)) { - // if the param tag was on a newline, the preserve that when transferring over. - if (content.Count > 0 && IsDocCommentNewLine(docComment.Content[i - 1])) - content.Add(docComment.Content[i - 1]); - content.Add(node); + + // if the param tag is followed with a newline, then preserve that when transferring over. + if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) + content.Add(docComment.Content[i + 1]); } } if (content.Count > 0) - content.Add(XmlText(XmlTextNewLine("\r\n", continueXmlDocumentationComment: false))); + { + if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) + content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); - if (content.Count > 0) - return constructorDeclaration.WithLeadingTrivia(Trivia(DocumentationComment(content.ToArray())).WithAdditionalAnnotations(Formatter.Annotation)); + content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); + + var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, List(content)); + return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia).WithAdditionalAnnotations(Formatter.Annotation)); + } } return constructorDeclaration; @@ -530,11 +487,112 @@ private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(t return element != null; } - private static bool IsDocCommentNewLine([NotNullWhen(true)] XmlNodeSyntax? node) - => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralNewLineToken), (kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && - string.IsNullOrWhiteSpace(precedingText.Text); + //private static bool IsDocCommentNewLine([NotNullWhen(true)] XmlNodeSyntax? node) + //{ + // if (node is not XmlTextSyntax xmlText) + // return false; + + // var textTokens = xmlText.TextTokens; + // if (textTokens ) + // : [(kind: SyntaxKind.XmlTextLiteralNewLineToken), (kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && + // string.IsNullOrWhiteSpace(precedingText.Text); + + //private static bool IsEmptyDocCommentStartText([NotNullWhen(true)] XmlNodeSyntax? node) + // => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && + // string.IsNullOrWhiteSpace(precedingText.Text); + + private static TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) + { + var triviaList = typeDeclaration.GetLeadingTrivia(); + var trivia = GetDocComment(triviaList); + var docComment = GetDocCommentStructure(trivia); + if (docComment == null) + return typeDeclaration; + + using var _ = ArrayBuilder.GetInstance(out var content); + + foreach (var node in docComment.Content) + { + if (IsXmlElement(node, "param", out var paramElement)) + { + // We're skipping a param node. Fixup any preceding text node we may have before it. + FixupLastTextNode(); + } + else + { + content.Add(node); + } + } + + if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( + t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) + { + // Nothing but param nodes. Just remove all the doc comments entirely. + var triviaIndex = triviaList.IndexOf(trivia); + + // remove the doc comment itself + var updatedTriviaList = triviaList.RemoveAt(triviaIndex); + + // If the comment was on a line that started with whitespace, remove that whitespce too. + if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) + updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); + + return typeDeclaration.WithLeadingTrivia(updatedTriviaList); + } + else + { + var updatedTrivia = Trivia(docComment.WithContent(List(content))); + return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); + } + + void FixupLastTextNode() + { + var node = content.LastOrDefault(); + if (node is not XmlTextSyntax xmlText) + return; + + var tokens = xmlText.TextTokens; + var lastIndex = tokens.Count; + if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(tokens[lastIndex - 1].Text)) + lastIndex--; + + if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralNewLineToken) + lastIndex--; + + if (lastIndex == tokens.Count) + { + // no change necessary. + return; + } + else if (lastIndex == 0) + { + // Removed all tokens from the text node. So remove the text node entirely. + content.RemoveLast(); + } + else + { + // Otherwise, replace with newlines stripped. + content[^1] = xmlText.WithTextTokens(TokenList(tokens.Take(lastIndex))); + } + } + } + + private static bool IsDocCommentNewLine(XmlNodeSyntax node) + { + if (node is not XmlTextSyntax xmlText) + return false; + + foreach (var textToken in xmlText.TextTokens) + { + if (textToken.Kind() == SyntaxKind.XmlTextLiteralNewLineToken) + continue; - private static bool IsEmptyDocCommentStartText([NotNullWhen(true)] XmlNodeSyntax? node) - => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && - string.IsNullOrWhiteSpace(precedingText.Text); + if (textToken.Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(textToken.Text)) + continue; + + return false; + } + + return true; + } } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index f656a3a94a8f1..d78c324210f7e 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -1380,6 +1380,102 @@ public C(int i, int j) }.RunAsync(); } + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments4() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// Docs for i. + /// Docs for j. + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ + namespace N + { + class C + { + /// Docs for i. + /// Docs for j. + public C(int i, int j) + { + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments5() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// Docs for i. + /// Docs for j. + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ + namespace N + { + class C + { + /// Docs for i. + /// Docs for j. + public C(int i, int j) + { + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + + [Fact] + public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments6() + { + await new VerifyCS.Test + { + TestCode = """ + namespace N + { + /// Docs for i. + ///Docs for j. + class [|C(int i, int j)|] + { + } + } + """, + FixedCode = """ + namespace N + { + class C + { + /// Docs for i. + ///Docs for j. + public C(int i, int j) + { + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments2() { @@ -1402,7 +1498,7 @@ namespace N { class C { - /// Docs for i. + ///Docs for i. /// ///Docs for j. /// @@ -1436,7 +1532,7 @@ namespace N { class C { - /// Docs for i. + ///Docs for i. ///Docs for j. public C(int i, int j) { @@ -2654,6 +2750,9 @@ namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue /// Active instruction identifier. /// It has the information necessary to track an active instruction within the debug session. /// + /// + /// Creates an ActiveInstructionId. + /// [CLSCompliant(false)] internal readonly struct ManagedInstructionId { @@ -2667,9 +2766,6 @@ internal readonly struct ManagedInstructionId /// public int ILOffset { get; } - /// - /// Creates an ActiveInstructionId. - /// /// Method which the instruction is scoped to. /// IL offset for the instruction. public ManagedInstructionId( From ff3b72d28629e787774c6e67b2fcc003a2f50cb3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 13:23:26 -0700 Subject: [PATCH 26/39] More fixups --- ...gularConstructorCodeRefactoringProvider.cs | 2 +- ...ConvertPrimaryToRegularConstructorTests.cs | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 4c93c990d449e..16777304ef05b 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -179,7 +179,7 @@ private static async Task ConvertAsync( if (lastFieldOrProperty >= 0) { constructorDeclaration = constructorDeclaration - .WithPrependedLeadingTrivia(ElasticCarriageReturnLineFeed, ElasticCarriageReturnLineFeed); + .WithPrependedLeadingTrivia(ElasticCarriageReturnLineFeed); return currentTypeDeclaration.WithMembers( currentTypeDeclaration.Members.Insert(lastFieldOrProperty + 1, constructorDeclaration)); diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index d78c324210f7e..f5f59d10e2143 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -951,11 +951,11 @@ class [|C(int i)|] FixedCode = """ namespace N { + /// Doc comment on single line class C { private int i; - /// Doc comment on single line /// Doc about i single line public C(int i) { @@ -990,11 +990,11 @@ class [|C(int i)|] namespace N { #if true + /// Doc comment on single line class C { private int i; - /// Doc comment on single line /// Doc about i single line public C(int i) { @@ -1022,11 +1022,11 @@ class [|C(int i)|] } """, FixedCode = """ + /// Doc comment on single line class C { private int i; - /// Doc comment on single line /// Doc about i single line public C(int i) { @@ -1052,7 +1052,7 @@ class [|C(int i)|] } """, FixedCode = """ - /// Doc comment on single line + ///Doc comment on single line class C { private int i; @@ -1139,13 +1139,13 @@ class [|C(int i)|] FixedCode = """ namespace N { + /// + /// Doc comment + /// On multiple lines class C { private int i; - /// - /// Doc comment - /// On multiple lines /// /// Doc about i /// on multiple lines @@ -1181,12 +1181,12 @@ class [|C(int i)|] FixedCode = """ namespace N { + /// Doc comment + /// On multiple lines class C { private int i; - /// Doc comment - /// On multiple lines /// Doc about i /// on multiple lines public C(int i) @@ -1276,13 +1276,13 @@ namespace N /// /// Existing doc comment /// + /// Constructor comment + /// On multiple lines class C { private int i; private int j; - /// Constructor comment - /// On multiple lines /// Doc about i /// Doc about j public C(int i, int j) @@ -1322,12 +1322,12 @@ class [|C(int i, int j)|] namespace N { /// Existing doc comment + /// Constructor comment class C { private int i; private int j; - /// Constructor comment /// Doc about /// i /// Doc about From fca2ced4895d99660c347e8b5a58ae910e75d2eb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:01:49 -0700 Subject: [PATCH 27/39] Do not know what's going on here --- ...yToRegularConstructorCodeRefactoringProvider.cs | 14 ++++++++++---- .../ConvertPrimaryToRegularConstructorTests.cs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 16777304ef05b..cd6839ccf73b6 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -404,23 +404,29 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() var node = docComment.Content[i]; if (IsXmlElement(node, "param", out var paramElement)) { - content.Add(node); + content.Add(node);//.WithAdditionalAnnotations(Formatter.Annotation)); // if the param tag is followed with a newline, then preserve that when transferring over. if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) - content.Add(docComment.Content[i + 1]); + content.Add(docComment.Content[i + 1]);//.WithAdditionalAnnotations(Formatter.Annotation)); } } if (content.Count > 0) { - if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) + if (content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) + { + content[0] = content[0].WithPrependedLeadingTrivia(ElasticMarker); + } + else + { content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); + } content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, List(content)); - return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia).WithAdditionalAnnotations(Formatter.Annotation)); + return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia)); //.WithAdditionalAnnotations(Formatter.Annotation)); } } diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index f5f59d10e2143..3dbf80216513b 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -1233,7 +1233,7 @@ class C { private int i; - /// + /// ///Doc about i ///on multiple lines /// From bab846b86e9e3a0d988377cd33a7a192814b328d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:04:47 -0700 Subject: [PATCH 28/39] Cleanup --- ...oRegularConstructorCodeRefactoringProvider.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index cd6839ccf73b6..51f213171629a 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -404,29 +402,23 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() var node = docComment.Content[i]; if (IsXmlElement(node, "param", out var paramElement)) { - content.Add(node);//.WithAdditionalAnnotations(Formatter.Annotation)); + content.Add(node); // if the param tag is followed with a newline, then preserve that when transferring over. if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) - content.Add(docComment.Content[i + 1]);//.WithAdditionalAnnotations(Formatter.Annotation)); + content.Add(docComment.Content[i + 1]); } } if (content.Count > 0) { - if (content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) - { - content[0] = content[0].WithPrependedLeadingTrivia(ElasticMarker); - } - else - { + if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); - } content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, List(content)); - return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia)); //.WithAdditionalAnnotations(Formatter.Annotation)); + return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia)); } } From 2a51300905c83da1e063b70bd5e1a556c58c45c5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:07:31 -0700 Subject: [PATCH 29/39] Move to new file --- ...gularConstructorCodeRefactoringProvider.cs | 135 +---------------- ...factoringProvider_DocumentationComments.cs | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 51f213171629a..d40facd3e1ccd 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -34,7 +33,7 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertPrimaryToRegularConstructor), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class ConvertPrimaryToRegularConstructorCodeRefactoringProvider() +internal sealed partial class ConvertPrimaryToRegularConstructorCodeRefactoringProvider() : CodeRefactoringProvider { public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) @@ -461,136 +460,4 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode return null; } } - - private static SyntaxTrivia GetDocComment(SyntaxNode node) - => GetDocComment(node.GetLeadingTrivia()); - - private static SyntaxTrivia GetDocComment(SyntaxTriviaList trivia) - => trivia.LastOrDefault(t => t.IsSingleLineDocComment()); - - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxNode node) - => GetDocCommentStructure(node.GetLeadingTrivia()); - - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTriviaList trivia) - => GetDocCommentStructure(GetDocComment(trivia)); - - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTrivia trivia) - => (DocumentationCommentTriviaSyntax?)trivia.GetStructure(); - - private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(true)] out XmlElementSyntax? element) - { - element = node is XmlElementSyntax { StartTag.Name.LocalName.ValueText: var elementName } xmlElement && elementName == name - ? xmlElement - : null; - return element != null; - } - - //private static bool IsDocCommentNewLine([NotNullWhen(true)] XmlNodeSyntax? node) - //{ - // if (node is not XmlTextSyntax xmlText) - // return false; - - // var textTokens = xmlText.TextTokens; - // if (textTokens ) - // : [(kind: SyntaxKind.XmlTextLiteralNewLineToken), (kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && - // string.IsNullOrWhiteSpace(precedingText.Text); - - //private static bool IsEmptyDocCommentStartText([NotNullWhen(true)] XmlNodeSyntax? node) - // => node is XmlTextSyntax { TextTokens: [(kind: SyntaxKind.XmlTextLiteralToken) precedingText] } && - // string.IsNullOrWhiteSpace(precedingText.Text); - - private static TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) - { - var triviaList = typeDeclaration.GetLeadingTrivia(); - var trivia = GetDocComment(triviaList); - var docComment = GetDocCommentStructure(trivia); - if (docComment == null) - return typeDeclaration; - - using var _ = ArrayBuilder.GetInstance(out var content); - - foreach (var node in docComment.Content) - { - if (IsXmlElement(node, "param", out var paramElement)) - { - // We're skipping a param node. Fixup any preceding text node we may have before it. - FixupLastTextNode(); - } - else - { - content.Add(node); - } - } - - if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( - t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) - { - // Nothing but param nodes. Just remove all the doc comments entirely. - var triviaIndex = triviaList.IndexOf(trivia); - - // remove the doc comment itself - var updatedTriviaList = triviaList.RemoveAt(triviaIndex); - - // If the comment was on a line that started with whitespace, remove that whitespce too. - if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) - updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); - - return typeDeclaration.WithLeadingTrivia(updatedTriviaList); - } - else - { - var updatedTrivia = Trivia(docComment.WithContent(List(content))); - return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); - } - - void FixupLastTextNode() - { - var node = content.LastOrDefault(); - if (node is not XmlTextSyntax xmlText) - return; - - var tokens = xmlText.TextTokens; - var lastIndex = tokens.Count; - if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(tokens[lastIndex - 1].Text)) - lastIndex--; - - if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralNewLineToken) - lastIndex--; - - if (lastIndex == tokens.Count) - { - // no change necessary. - return; - } - else if (lastIndex == 0) - { - // Removed all tokens from the text node. So remove the text node entirely. - content.RemoveLast(); - } - else - { - // Otherwise, replace with newlines stripped. - content[^1] = xmlText.WithTextTokens(TokenList(tokens.Take(lastIndex))); - } - } - } - - private static bool IsDocCommentNewLine(XmlNodeSyntax node) - { - if (node is not XmlTextSyntax xmlText) - return false; - - foreach (var textToken in xmlText.TextTokens) - { - if (textToken.Kind() == SyntaxKind.XmlTextLiteralNewLineToken) - continue; - - if (textToken.Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(textToken.Text)) - continue; - - return false; - } - - return true; - } } diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs new file mode 100644 index 0000000000000..d6b0a1301e552 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs @@ -0,0 +1,137 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; + +using static SyntaxFactory; + +internal sealed partial class ConvertPrimaryToRegularConstructorCodeRefactoringProvider +{ + private static SyntaxTrivia GetDocComment(SyntaxNode node) + => GetDocComment(node.GetLeadingTrivia()); + + private static SyntaxTrivia GetDocComment(SyntaxTriviaList trivia) + => trivia.LastOrDefault(t => t.IsSingleLineDocComment()); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxNode node) + => GetDocCommentStructure(node.GetLeadingTrivia()); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTriviaList trivia) + => GetDocCommentStructure(GetDocComment(trivia)); + + private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTrivia trivia) + => (DocumentationCommentTriviaSyntax?)trivia.GetStructure(); + + private static bool IsXmlElement(XmlNodeSyntax node, string name, [NotNullWhen(true)] out XmlElementSyntax? element) + { + element = node is XmlElementSyntax { StartTag.Name.LocalName.ValueText: var elementName } xmlElement && elementName == name + ? xmlElement + : null; + return element != null; + } + + private static TypeDeclarationSyntax RemoveParamXmlElements(TypeDeclarationSyntax typeDeclaration) + { + var triviaList = typeDeclaration.GetLeadingTrivia(); + var trivia = GetDocComment(triviaList); + var docComment = GetDocCommentStructure(trivia); + if (docComment == null) + return typeDeclaration; + + using var _ = ArrayBuilder.GetInstance(out var content); + + foreach (var node in docComment.Content) + { + if (IsXmlElement(node, "param", out var paramElement)) + { + // We're skipping a param node. Fixup any preceding text node we may have before it. + FixupLastTextNode(); + } + else + { + content.Add(node); + } + } + + if (content.All(c => c is XmlTextSyntax xmlText && xmlText.TextTokens.All( + t => t.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || string.IsNullOrWhiteSpace(t.Text)))) + { + // Nothing but param nodes. Just remove all the doc comments entirely. + var triviaIndex = triviaList.IndexOf(trivia); + + // remove the doc comment itself + var updatedTriviaList = triviaList.RemoveAt(triviaIndex); + + // If the comment was on a line that started with whitespace, remove that whitespce too. + if (triviaIndex > 0 && triviaList[triviaIndex - 1].IsWhitespace()) + updatedTriviaList = updatedTriviaList.RemoveAt(triviaIndex - 1); + + return typeDeclaration.WithLeadingTrivia(updatedTriviaList); + } + else + { + var updatedTrivia = Trivia(docComment.WithContent(List(content))); + return typeDeclaration.WithLeadingTrivia(triviaList.Replace(trivia, updatedTrivia)); + } + + void FixupLastTextNode() + { + var node = content.LastOrDefault(); + if (node is not XmlTextSyntax xmlText) + return; + + var tokens = xmlText.TextTokens; + var lastIndex = tokens.Count; + if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(tokens[lastIndex - 1].Text)) + lastIndex--; + + if (lastIndex - 1 >= 0 && tokens[lastIndex - 1].Kind() == SyntaxKind.XmlTextLiteralNewLineToken) + lastIndex--; + + if (lastIndex == tokens.Count) + { + // no change necessary. + return; + } + else if (lastIndex == 0) + { + // Removed all tokens from the text node. So remove the text node entirely. + content.RemoveLast(); + } + else + { + // Otherwise, replace with newlines stripped. + content[^1] = xmlText.WithTextTokens(TokenList(tokens.Take(lastIndex))); + } + } + } + + private static bool IsDocCommentNewLine(XmlNodeSyntax node) + { + if (node is not XmlTextSyntax xmlText) + return false; + + foreach (var textToken in xmlText.TextTokens) + { + if (textToken.Kind() == SyntaxKind.XmlTextLiteralNewLineToken) + continue; + + if (textToken.Kind() == SyntaxKind.XmlTextLiteralToken && string.IsNullOrWhiteSpace(textToken.Text)) + continue; + + return false; + } + + return true; + } +} From 11fe5272179251a48f7ed5a1f29c5d676034ecb0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:19:21 -0700 Subject: [PATCH 30/39] Inlined --- ...gularConstructorCodeRefactoringProvider.cs | 72 ++++--------------- ...factoringProvider_DocumentationComments.cs | 38 ++++++++++ 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index d40facd3e1ccd..349e6f5c48738 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -83,24 +83,20 @@ private static async Task ConvertAsync( var solution = document.Project.Solution; var solutionEditor = new SolutionEditor(solution); - var codeGenService = document.GetRequiredLanguageService(); - var syntaxGenerator = CSharpSyntaxGenerator.Instance; var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll // create real fields for all these cases. var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); + // Find the references to all the parameters. This will help us determine how they're used and what change we + // may need to make. var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); // Find any field/properties whose initializer references a primary constructor parameter. These initializers // will have to move inside the constructor we generate. var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); - var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; - var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); - var constructorDeclaration = CreateConstructorDeclaration(); - // Now start editing the document var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); @@ -110,10 +106,12 @@ private static async Task ConvertAsync( // Remove the parameter list, and any base argument passing from the type declaration header itself. mainDocumentEditor.RemoveNode(parameterList); + var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; if (baseType != null) mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseTypeSyntax)current).Type).WithTriviaFrom(baseType)); - // Remove all the attributes from the type decl that were moved to the constructor. + // Remove all the attributes from the type decl that we're moving to the constructor. + var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); foreach (var attributeList in methodTargetingAttributes) mainDocumentEditor.RemoveNode(attributeList); @@ -148,6 +146,7 @@ private static async Task ConvertAsync( var fieldsInOrder = parameters .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) .WhereNotNull(); + var codeGenService = document.GetRequiredLanguageService(); return codeGenService.AddMembers( currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); }); @@ -157,10 +156,13 @@ private static async Task ConvertAsync( typeDeclaration, (current, _) => { - // If there is an existing non-static constructor, place it before that + // Move any tags from the type decl to the constructor decl. var currentTypeDeclaration = (TypeDeclarationSyntax)current; currentTypeDeclaration = RemoveParamXmlElements(currentTypeDeclaration); + var constructorDeclaration = CreateConstructorDeclaration(); + + // If there is an existing non-static constructor, place it before that var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); if (firstConstructorIndex >= 0) { @@ -209,9 +211,6 @@ async Task> GetParameter foreach (var parameter in parameters) { - //if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) - // continue; - var references = await SymbolFinder.FindReferencesAsync( parameter, solution, documentsToSearch, cancellationToken).ConfigureAwait(false); foreach (var reference in references) @@ -359,14 +358,14 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() // First, if we're making a real field for a primary constructor parameter, assign the parameter to it. foreach (var parameter in parameters) { - if (GetMemberToAssignTo(parameter) is not (var member, var value)) + if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) continue; - var fieldName = member.Name.ToIdentifierName(); - var left = parameter.Name == member.Name + var fieldName = field.Name.ToIdentifierName(); + var left = parameter.Name == field.Name ? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), fieldName) : (ExpressionSyntax)fieldName; - var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, value); + var assignment = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, parameter.Name.ToIdentifierName()); assignmentStatements.Add(ExpressionStatement(assignment)); } @@ -388,40 +387,7 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); - // Now move the param tags on the type decl over to the constructor. - var triviaList = typeDeclaration.GetLeadingTrivia(); - var trivia = GetDocComment(triviaList); - var docComment = GetDocCommentStructure(trivia); - if (docComment != null) - { - using var _2 = ArrayBuilder.GetInstance(out var content); - - for (int i = 0, n = docComment.Content.Count; i < n; i++) - { - var node = docComment.Content[i]; - if (IsXmlElement(node, "param", out var paramElement)) - { - content.Add(node); - - // if the param tag is followed with a newline, then preserve that when transferring over. - if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) - content.Add(docComment.Content[i + 1]); - } - } - - if (content.Count > 0) - { - if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) - content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); - - content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); - - var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, List(content)); - return constructorDeclaration.WithLeadingTrivia(Trivia(finalTrivia)); - } - } - - return constructorDeclaration; + return WithTypeDeclarationParamDocComments(typeDeclaration, constructorDeclaration); } ParameterListSyntax RewriteParameterDefaults(ParameterListSyntax parameterList) @@ -451,13 +417,5 @@ TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode return node; }); } - - (ISymbol member, ExpressionSyntax value)? GetMemberToAssignTo(IParameterSymbol parameter) - { - if (parameterToSynthesizedFields.TryGetValue(parameter, out var field)) - return (field, parameter.Name.ToIdentifierName()); - - return null; - } } } diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs index d6b0a1301e552..25d2bbd4c1c4e 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs @@ -116,6 +116,44 @@ void FixupLastTextNode() } } + private static ConstructorDeclarationSyntax WithTypeDeclarationParamDocComments(TypeDeclarationSyntax typeDeclaration, ConstructorDeclarationSyntax constructor) + { + // Now move the param tags on the type decl over to the constructor. + var triviaList = typeDeclaration.GetLeadingTrivia(); + var trivia = GetDocComment(triviaList); + var docComment = GetDocCommentStructure(trivia); + if (docComment is not null) + { + using var _2 = ArrayBuilder.GetInstance(out var content); + + for (int i = 0, n = docComment.Content.Count; i < n; i++) + { + var node = docComment.Content[i]; + if (IsXmlElement(node, "param", out _)) + { + content.Add(node); + + // if the param tag is followed with a newline, then preserve that when transferring over. + if (i + 1 < docComment.Content.Count && IsDocCommentNewLine(docComment.Content[i + 1])) + content.Add(docComment.Content[i + 1]); + } + } + + if (content.Count > 0) + { + if (!content[0].GetLeadingTrivia().Any(SyntaxKind.DocumentationCommentExteriorTrivia)) + content[0] = content[0].WithLeadingTrivia(DocumentationCommentExterior("/// ")); + + content[^1] = content[^1].WithTrailingTrivia(EndOfLine("")); + + var finalTrivia = DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, List(content)); + return constructor.WithLeadingTrivia(Trivia(finalTrivia)); + } + } + + return constructor; + } + private static bool IsDocCommentNewLine(XmlNodeSyntax node) { if (node is not XmlTextSyntax xmlText) From 1cd956b82aa99dea81e0b13acf76380493bb7191 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:27:35 -0700 Subject: [PATCH 31/39] In progress --- ...gularConstructorCodeRefactoringProvider.cs | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 349e6f5c48738..ee5fa56e06bed 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; @@ -67,44 +66,33 @@ private static async Task ConvertAsync( CodeActionOptionsProvider optionsProvider, CancellationToken cancellationToken) { - // 1. Create constructor - // 2. Remove base arguments - // 3. Add fields if necessary - // 4. Update references to parameters to be references to fields - // 5. Format as appropriate - var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var semanticModels = new ConcurrentSet(); var semanticModel = await GetSemanticModelAsync(document).ConfigureAwait(false); + var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); + + // Get the named type and all its parameters for use during the rewrite. var namedType = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken); + var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); - // We may have to update multiple files (in the case of a partial type). Use a solution-editor to make that simple. + // We may have to update multiple files (in the case of a partial type). Use a solution-editor to make that + // simple. We will insert the regular constructor into the partial part containing the primary constructor. var solution = document.Project.Solution; var solutionEditor = new SolutionEditor(solution); - - var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); - - // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll - // create real fields for all these cases. - var parameterToSynthesizedFields = await GetSynthesizedFieldsAsync().ConfigureAwait(false); - - // Find the references to all the parameters. This will help us determine how they're used and what change we - // may need to make. - var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); - - // Find any field/properties whose initializer references a primary constructor parameter. These initializers - // will have to move inside the constructor we generate. - var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); - - // Now start editing the document var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); - var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); - // Now, update all locations that reference the parameters to reference the new fields. - await RewriteReferencesToParametersAsync().ConfigureAwait(false); + RemovePrimaryConstructorParameterList(); + RemovePrimaryConstructorBaseTypeArgumentList(); + RemovePrimaryConstructorTargettingAttributes(); + RemoveDirectFieldAndPropertyAssignments(); + AddNewFields(); + AddConstructorDeclaration(); + RewritePrimaryConstructorParameterReferences(); // Remove the parameter list, and any base argument passing from the type declaration header itself. + + mainDocumentEditor.RemoveNode(parameterList); var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; if (baseType != null) @@ -115,6 +103,14 @@ private static async Task ConvertAsync( foreach (var attributeList in methodTargetingAttributes) mainDocumentEditor.RemoveNode(attributeList); + // Find the references to all the parameters. This will help us determine how they're used and what change we + // may need to make. + var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); + + // Find any field/properties whose initializer references a primary constructor parameter. These initializers + // will have to move inside the constructor we generate. + var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); + // Remove all the initializers from existing fields/props the params are assigned to. foreach (var (_, initializer) in initializedFieldsAndProperties) { @@ -137,7 +133,11 @@ private static async Task ConvertAsync( } } - // Now add all the fields. + // Now add all the fields, and rewrite any references to the parameters to now reference the fields. + + var parameterToSynthesizedFields = await CreateSynthesizedFieldsAsync().ConfigureAwait(false); + await RewriteReferencesToParametersAsync().ConfigureAwait(false); + mainDocumentEditor.ReplaceNode( typeDeclaration, (current, _) => @@ -152,6 +152,7 @@ private static async Task ConvertAsync( }); // Now add the constructor + var constructorAnnotation = new SyntaxAnnotation(); mainDocumentEditor.ReplaceNode( typeDeclaration, (current, _) => @@ -160,7 +161,7 @@ private static async Task ConvertAsync( var currentTypeDeclaration = (TypeDeclarationSyntax)current; currentTypeDeclaration = RemoveParamXmlElements(currentTypeDeclaration); - var constructorDeclaration = CreateConstructorDeclaration(); + var constructorDeclaration = CreateConstructorDeclaration().WithAdditionalAnnotations(constructorAnnotation); // If there is an existing non-static constructor, place it before that var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); @@ -189,6 +190,8 @@ private static async Task ConvertAsync( currentTypeDeclaration.Members.Insert(0, constructorDeclaration)); }); + // + return solutionEditor.GetChangedSolution(); async ValueTask GetSemanticModelAsync(Document document) @@ -246,6 +249,7 @@ async Task RewriteReferencesToParametersAsync() { foreach (var (parameter, references) in parameterReferences) { + // Only have to update references if we're synthesizing a field for this parameter. if (!parameterToSynthesizedFields.TryGetValue(parameter, out var field)) continue; @@ -311,11 +315,14 @@ async Task RewriteReferencesToParametersAsync() return result.ToImmutableHashSet(); } - async Task> GetSynthesizedFieldsAsync() + async Task> CreateSynthesizedFieldsAsync() { using var _1 = PooledDictionary.GetInstance(out var locationToField); using var _2 = PooledDictionary.GetInstance(out var result); + // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll + // create real fields for all these cases. + foreach (var member in namedType.GetMembers()) { if (member is IFieldSymbol { IsImplicitlyDeclared: true, Locations: [var location, ..] } field) From 634b4c94c7ed18fcfd4bc44108fe6d742fc12fe6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:36:11 -0700 Subject: [PATCH 32/39] inline --- ...gularConstructorCodeRefactoringProvider.cs | 297 +++++++++--------- 1 file changed, 154 insertions(+), 143 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index ee5fa56e06bed..5a70da8e72610 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -70,8 +70,16 @@ private static async Task ConvertAsync( var semanticModels = new ConcurrentSet(); var semanticModel = await GetSemanticModelAsync(document).ConfigureAwait(false); + var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); + var fieldNameRule = await document.GetApplicableNamingRuleAsync( + new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field), + DeclarationModifiers.None, + Accessibility.Private, + optionsProvider, + cancellationToken).ConfigureAwait(false); + // Get the named type and all its parameters for use during the rewrite. var namedType = semanticModel.GetRequiredDeclaredSymbol(typeDeclaration, cancellationToken); var parameters = parameterList.Parameters.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); @@ -82,115 +90,27 @@ private static async Task ConvertAsync( var solutionEditor = new SolutionEditor(solution); var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); - RemovePrimaryConstructorParameterList(); - RemovePrimaryConstructorBaseTypeArgumentList(); - RemovePrimaryConstructorTargettingAttributes(); - RemoveDirectFieldAndPropertyAssignments(); - AddNewFields(); - AddConstructorDeclaration(); - RewritePrimaryConstructorParameterReferences(); - - // Remove the parameter list, and any base argument passing from the type declaration header itself. - - - mainDocumentEditor.RemoveNode(parameterList); - var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; - if (baseType != null) - mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseTypeSyntax)current).Type).WithTriviaFrom(baseType)); - - // Remove all the attributes from the type decl that we're moving to the constructor. - var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); - foreach (var attributeList in methodTargetingAttributes) - mainDocumentEditor.RemoveNode(attributeList); - // Find the references to all the parameters. This will help us determine how they're used and what change we // may need to make. var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); + // Determine the fields we'll need to synthesize for each parameter. + var parameterToSynthesizedFields = await CreateSynthesizedFieldsAsync().ConfigureAwait(false); + // Find any field/properties whose initializer references a primary constructor parameter. These initializers // will have to move inside the constructor we generate. var initializedFieldsAndProperties = await GetExistingAssignedFieldsOrProperties().ConfigureAwait(false); - // Remove all the initializers from existing fields/props the params are assigned to. - foreach (var (_, initializer) in initializedFieldsAndProperties) - { - if (initializer.Parent is PropertyDeclarationSyntax propertyDeclaration) - { - mainDocumentEditor.ReplaceNode( - propertyDeclaration, - propertyDeclaration - .WithInitializer(null) - .WithSemicolonToken(default) - .WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); - } - else if (initializer.Parent is VariableDeclaratorSyntax) - { - mainDocumentEditor.RemoveNode(initializer); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - } - - // Now add all the fields, and rewrite any references to the parameters to now reference the fields. - - var parameterToSynthesizedFields = await CreateSynthesizedFieldsAsync().ConfigureAwait(false); - await RewriteReferencesToParametersAsync().ConfigureAwait(false); - - mainDocumentEditor.ReplaceNode( - typeDeclaration, - (current, _) => - { - var currentTypeDeclaration = (TypeDeclarationSyntax)current; - var fieldsInOrder = parameters - .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) - .WhereNotNull(); - var codeGenService = document.GetRequiredLanguageService(); - return codeGenService.AddMembers( - currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); - }); - - // Now add the constructor var constructorAnnotation = new SyntaxAnnotation(); - mainDocumentEditor.ReplaceNode( - typeDeclaration, - (current, _) => - { - // Move any tags from the type decl to the constructor decl. - var currentTypeDeclaration = (TypeDeclarationSyntax)current; - currentTypeDeclaration = RemoveParamXmlElements(currentTypeDeclaration); - - var constructorDeclaration = CreateConstructorDeclaration().WithAdditionalAnnotations(constructorAnnotation); - // If there is an existing non-static constructor, place it before that - var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); - if (firstConstructorIndex >= 0) - { - return currentTypeDeclaration.WithMembers( - currentTypeDeclaration.Members.Insert(firstConstructorIndex, constructorDeclaration)); - } - - // No constructors. Place after any fields if present, or any properties if there are no fields. - var lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is FieldDeclarationSyntax); - if (lastFieldOrProperty < 0) - lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is PropertyDeclarationSyntax); - - if (lastFieldOrProperty >= 0) - { - constructorDeclaration = constructorDeclaration - .WithPrependedLeadingTrivia(ElasticCarriageReturnLineFeed); - - return currentTypeDeclaration.WithMembers( - currentTypeDeclaration.Members.Insert(lastFieldOrProperty + 1, constructorDeclaration)); - } - - // Nothing at all. Just place the construct at the top of the type. - return currentTypeDeclaration.WithMembers( - currentTypeDeclaration.Members.Insert(0, constructorDeclaration)); - }); - - // + RemovePrimaryConstructorParameterList(); + RemovePrimaryConstructorBaseTypeArgumentList(); + RemovePrimaryConstructorTargetingAttributes(); + RemoveDirectFieldAndPropertyAssignments(); + AddNewFields(); + AddConstructorDeclaration(); + await RewritePrimaryConstructorParameterReferencesAsync().ConfigureAwait(false); + FixConstructoDeclarationFormatting(); return solutionEditor.GetChangedSolution(); @@ -245,7 +165,141 @@ async Task> GetParameter return result; } - async Task RewriteReferencesToParametersAsync() + async Task> CreateSynthesizedFieldsAsync() + { + using var _1 = PooledDictionary.GetInstance(out var locationToField); + using var _2 = PooledDictionary.GetInstance(out var result); + + // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll + // create real fields for all these cases. + + foreach (var member in namedType.GetMembers()) + { + if (member is IFieldSymbol { IsImplicitlyDeclared: true, Locations: [var location, ..] } field) + locationToField[location] = field; + } + + foreach (var parameter in parameters) + { + if (parameter.Locations is [var location, ..] && + locationToField.TryGetValue(location, out var existingField)) + { + var baseFieldName = fieldNameRule.NamingStyle.MakeCompliant(parameter.Name).First(); + var fieldName = NameGenerator.GenerateUniqueName(baseFieldName, n => namedType.Name != n && !namedType.GetMembers(n).Any()); + + var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( + existingField, + name: fieldName); + + result.Add(parameter, synthesizedField); + } + } + + return result.ToImmutableDictionary(); + } + + void RemovePrimaryConstructorParameterList() + { + mainDocumentEditor.RemoveNode(parameterList); + } + + void RemovePrimaryConstructorBaseTypeArgumentList() + { + var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; + if (baseType != null) + mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseTypeSyntax)current).Type).WithTriviaFrom(baseType)); + } + + void RemovePrimaryConstructorTargetingAttributes() + { + // Remove all the attributes from the type decl that we're moving to the constructor. + var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); + foreach (var attributeList in methodTargetingAttributes) + mainDocumentEditor.RemoveNode(attributeList); + } + + void RemoveDirectFieldAndPropertyAssignments() + { + // Remove all the initializers from existing fields/props the params are assigned to. + foreach (var (_, initializer) in initializedFieldsAndProperties) + { + if (initializer.Parent is PropertyDeclarationSyntax propertyDeclaration) + { + mainDocumentEditor.ReplaceNode( + propertyDeclaration, + propertyDeclaration + .WithInitializer(null) + .WithSemicolonToken(default) + .WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())); + } + else if (initializer.Parent is VariableDeclaratorSyntax) + { + mainDocumentEditor.RemoveNode(initializer); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + } + } + + void AddNewFields() + { + mainDocumentEditor.ReplaceNode( + typeDeclaration, + (current, _) => + { + var currentTypeDeclaration = (TypeDeclarationSyntax)current; + var fieldsInOrder = parameters + .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) + .WhereNotNull(); + var codeGenService = document.GetRequiredLanguageService(); + return codeGenService.AddMembers( + currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); + }); + } + + void AddConstructorDeclaration() + { + mainDocumentEditor.ReplaceNode( + typeDeclaration, + (current, _) => + { + // Move any tags from the type decl to the constructor decl. + var currentTypeDeclaration = (TypeDeclarationSyntax)current; + currentTypeDeclaration = RemoveParamXmlElements(currentTypeDeclaration); + + var constructorDeclaration = CreateConstructorDeclaration().WithAdditionalAnnotations(constructorAnnotation); + + // If there is an existing non-static constructor, place it before that + var firstConstructorIndex = currentTypeDeclaration.Members.IndexOf(m => m is ConstructorDeclarationSyntax c && !c.Modifiers.Any(SyntaxKind.StaticKeyword)); + if (firstConstructorIndex >= 0) + { + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(firstConstructorIndex, constructorDeclaration)); + } + + // No constructors. Place after any fields if present, or any properties if there are no fields. + var lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is FieldDeclarationSyntax); + if (lastFieldOrProperty < 0) + lastFieldOrProperty = currentTypeDeclaration.Members.LastIndexOf(m => m is PropertyDeclarationSyntax); + + if (lastFieldOrProperty >= 0) + { + constructorDeclaration = constructorDeclaration + .WithPrependedLeadingTrivia(ElasticCarriageReturnLineFeed); + + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(lastFieldOrProperty + 1, constructorDeclaration)); + } + + // Nothing at all. Just place the construct at the top of the type. + return currentTypeDeclaration.WithMembers( + currentTypeDeclaration.Members.Insert(0, constructorDeclaration)); + }); + } + + async Task RewritePrimaryConstructorParameterReferencesAsync() { foreach (var (parameter, references) in parameterReferences) { @@ -315,49 +369,6 @@ async Task RewriteReferencesToParametersAsync() return result.ToImmutableHashSet(); } - async Task> CreateSynthesizedFieldsAsync() - { - using var _1 = PooledDictionary.GetInstance(out var locationToField); - using var _2 = PooledDictionary.GetInstance(out var result); - - // Compiler already knows which primary constructor parameters ended up becoming fields. So just defer to it. We'll - // create real fields for all these cases. - - foreach (var member in namedType.GetMembers()) - { - if (member is IFieldSymbol { IsImplicitlyDeclared: true, Locations: [var location, ..] } field) - locationToField[location] = field; - } - - foreach (var parameter in parameters) - { - if (parameter.Locations is [var location, ..] && - locationToField.TryGetValue(location, out var existingField)) - { - var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( - existingField, - name: await MakeFieldNameAsync(parameter.Name).ConfigureAwait(false)); - - result.Add(parameter, synthesizedField); - } - } - - return result.ToImmutableDictionary(); - } - - async Task MakeFieldNameAsync(string parameterName) - { - var rule = await document.GetApplicableNamingRuleAsync( - new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field), - DeclarationModifiers.None, - Accessibility.Private, - optionsProvider, - cancellationToken).ConfigureAwait(false); - - var fieldName = rule.NamingStyle.MakeCompliant(parameterName).First(); - return NameGenerator.GenerateUniqueName(fieldName, n => namedType.Name != n && !namedType.GetMembers(n).Any()); - } - ConstructorDeclarationSyntax CreateConstructorDeclaration() { using var _1 = ArrayBuilder.GetInstance(out var assignmentStatements); From 23e22411e2d5cf3d454195620e39b8a77da9aa34 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:37:49 -0700 Subject: [PATCH 33/39] cleanup --- ...gularConstructorCodeRefactoringProvider.cs | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 5a70da8e72610..4a0e2c8936842 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -95,7 +95,7 @@ private static async Task ConvertAsync( var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); // Determine the fields we'll need to synthesize for each parameter. - var parameterToSynthesizedFields = await CreateSynthesizedFieldsAsync().ConfigureAwait(false); + var parameterToSynthesizedFields = CreateSynthesizedFields(); // Find any field/properties whose initializer references a primary constructor parameter. These initializers // will have to move inside the constructor we generate. @@ -165,7 +165,7 @@ async Task> GetParameter return result; } - async Task> CreateSynthesizedFieldsAsync() + ImmutableDictionary CreateSynthesizedFields() { using var _1 = PooledDictionary.GetInstance(out var locationToField); using var _2 = PooledDictionary.GetInstance(out var result); @@ -198,6 +198,40 @@ async Task> CreateSynthesize return result.ToImmutableDictionary(); } + async Task> GetExistingAssignedFieldsOrProperties() + { + using var _1 = PooledHashSet.GetInstance(out var initializers); + foreach (var (parameter, references) in parameterReferences) + { + foreach (var reference in references) + { + var initializer = reference.AncestorsAndSelf().OfType().LastOrDefault(); + if (initializer is null) + continue; + + if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) + continue; + + initializers.Add(initializer); + } + } + + using var _2 = PooledHashSet<(ISymbol fieldOrProperty, EqualsValueClauseSyntax initializer)>.GetInstance(out var result); + foreach (var grouping in initializers.GroupBy(kvp => kvp.Value.SyntaxTree)) + { + var syntaxTree = grouping.Key; + var semanticModel = await GetSemanticModelAsync(solution.GetRequiredDocument(syntaxTree)).ConfigureAwait(false); + + foreach (var initializer in grouping) + { + var fieldOrProperty = semanticModel.GetRequiredDeclaredSymbol(initializer.GetRequiredParent(), cancellationToken); + result.Add((fieldOrProperty, initializer)); + } + } + + return result.ToImmutableHashSet(); + } + void RemovePrimaryConstructorParameterList() { mainDocumentEditor.RemoveNode(parameterList); @@ -335,40 +369,6 @@ async Task RewritePrimaryConstructorParameterReferencesAsync() } } - async Task> GetExistingAssignedFieldsOrProperties() - { - using var _1 = PooledHashSet.GetInstance(out var initializers); - foreach (var (parameter, references) in parameterReferences) - { - foreach (var reference in references) - { - var initializer = reference.AncestorsAndSelf().OfType().LastOrDefault(); - if (initializer is null) - continue; - - if (initializer.Parent is not PropertyDeclarationSyntax and not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) - continue; - - initializers.Add(initializer); - } - } - - using var _2 = PooledHashSet<(ISymbol fieldOrProperty, EqualsValueClauseSyntax initializer)>.GetInstance(out var result); - foreach (var grouping in initializers.GroupBy(kvp => kvp.Value.SyntaxTree)) - { - var syntaxTree = grouping.Key; - var semanticModel = await GetSemanticModelAsync(solution.GetRequiredDocument(syntaxTree)).ConfigureAwait(false); - - foreach (var initializer in grouping) - { - var fieldOrProperty = semanticModel.GetRequiredDeclaredSymbol(initializer.GetRequiredParent(), cancellationToken); - result.Add((fieldOrProperty, initializer)); - } - } - - return result.ToImmutableHashSet(); - } - ConstructorDeclarationSyntax CreateConstructorDeclaration() { using var _1 = ArrayBuilder.GetInstance(out var assignmentStatements); @@ -397,24 +397,21 @@ ConstructorDeclarationSyntax CreateConstructorDeclaration() assignmentStatements.Add(ExpressionStatement(assignment)); } + var rewrittenParameters = parameterList.ReplaceNodes( + parameterList.Parameters, + (parameter, _) => RewriteNestedReferences(parameter)); + var constructorDeclaration = ConstructorDeclaration( List(methodTargetingAttributes.Select(a => a.WithTarget(null).WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation))), TokenList(Token(SyntaxKind.PublicKeyword).WithAppendedTrailingTrivia(Space)), typeDeclaration.Identifier.WithoutTrivia(), - RewriteParameterDefaults(parameterList).WithoutTrivia(), + rewrittenParameters.WithoutTrivia(), baseType?.ArgumentList is null ? null : ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseType.ArgumentList), Block(assignmentStatements)); return WithTypeDeclarationParamDocComments(typeDeclaration, constructorDeclaration); } - ParameterListSyntax RewriteParameterDefaults(ParameterListSyntax parameterList) - { - return parameterList.ReplaceNodes( - parameterList.Parameters, - (parameter, _) => RewriteNestedReferences(parameter)); - } - TNode RewriteNestedReferences(TNode parent) where TNode : SyntaxNode { return parent.ReplaceNodes( From 700680ba8111b45fce576c2ad36c8eeedc601335 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:38:50 -0700 Subject: [PATCH 34/39] simplify --- ...ertPrimaryToRegularConstructorCodeRefactoringProvider.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 4a0e2c8936842..b2d5e44282e58 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -90,6 +90,9 @@ private static async Task ConvertAsync( var solutionEditor = new SolutionEditor(solution); var mainDocumentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; + var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); + // Find the references to all the parameters. This will help us determine how they're used and what change we // may need to make. var parameterReferences = await GetParameterReferencesAsync().ConfigureAwait(false); @@ -110,7 +113,7 @@ private static async Task ConvertAsync( AddNewFields(); AddConstructorDeclaration(); await RewritePrimaryConstructorParameterReferencesAsync().ConfigureAwait(false); - FixConstructoDeclarationFormatting(); + FixConstructorDeclarationFormatting(); return solutionEditor.GetChangedSolution(); @@ -247,7 +250,6 @@ void RemovePrimaryConstructorBaseTypeArgumentList() void RemovePrimaryConstructorTargetingAttributes() { // Remove all the attributes from the type decl that we're moving to the constructor. - var methodTargetingAttributes = typeDeclaration.AttributeLists.Where(list => list.Target?.Identifier.ValueText == "method"); foreach (var attributeList in methodTargetingAttributes) mainDocumentEditor.RemoveNode(attributeList); } From 806db14386826d35a03bdf6a71ecfa8b9ad1ccbe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 14:48:51 -0700 Subject: [PATCH 35/39] Make readonly --- ...gularConstructorCodeRefactoringProvider.cs | 5 +- ...ConvertPrimaryToRegularConstructorTests.cs | 107 +++++++++++++++--- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index b2d5e44282e58..f7495ec4a3988 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -73,6 +73,7 @@ private static async Task ConvertAsync( var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, optionsProvider, cancellationToken).ConfigureAwait(false); + // The naming rule we need to follow if we synthesize new private fields. var fieldNameRule = await document.GetApplicableNamingRuleAsync( new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Field), DeclarationModifiers.None, @@ -113,7 +114,7 @@ private static async Task ConvertAsync( AddNewFields(); AddConstructorDeclaration(); await RewritePrimaryConstructorParameterReferencesAsync().ConfigureAwait(false); - FixConstructorDeclarationFormatting(); + // FixConstructorDeclarationFormatting(); return solutionEditor.GetChangedSolution(); @@ -190,8 +191,10 @@ ImmutableDictionary CreateSynthesizedFields() var baseFieldName = fieldNameRule.NamingStyle.MakeCompliant(parameter.Name).First(); var fieldName = NameGenerator.GenerateUniqueName(baseFieldName, n => namedType.Name != n && !namedType.GetMembers(n).Any()); + var isWrittenTo = parameterReferences[parameter].Any(r => r.IsWrittenTo(semanticModel, cancellationToken)); var synthesizedField = CodeGenerationSymbolFactory.CreateFieldSymbol( existingField, + modifiers: isWrittenTo ? existingField.GetSymbolModifiers() : existingField.GetSymbolModifiers().WithIsReadOnly(true), name: fieldName); result.Add(parameter, synthesizedField); diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index 3dbf80216513b..fc4d6081beb15 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -544,8 +544,8 @@ int M() FixedCode = """ class C { - private int i; - private int j; + private readonly int i; + private readonly int j; public [|C|](int i, int j) { @@ -563,6 +563,42 @@ int M() }.RunAsync(); } + [Fact] + public async Task TestRemoveMembers1_Used_Written1() + { + await new VerifyCS.Test + { + TestCode = """ + class [|C(int i, int j)|] + { + int M() + { + return i++ + j; + } + } + """, + FixedCode = """ + class C + { + private readonly int j; + private int i; + + public [|C|](int i, int j) + { + this.i = i; + this.j = j; + } + + int M() + { + return i++ + j; + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + }.RunAsync(); + } + [Fact] public async Task TestRemoveMembers1_Unused() { @@ -682,8 +718,8 @@ void M() using System; class C { - private int i; - private int j; + private readonly int i; + private readonly int j; public C(int i, int j) { @@ -720,8 +756,8 @@ void M() using System; class C { - private int @this; - private int @delegate; + private readonly int @this; + private readonly int @delegate; public C(int @this, int @delegate) { @@ -758,8 +794,8 @@ void M() using System; class C { - private int i; - private int j; + private readonly int i; + private readonly int j; public C(int i, int j) { @@ -798,8 +834,8 @@ void M() using System; class C { + private readonly int i; public int _j; - private int i; public C(int i, int j) { @@ -839,9 +875,9 @@ void M() using System; class C { + private readonly int i; [CLSCompliant(true)] private int _j; - private int i; public [|C|](int i, int j) { @@ -860,7 +896,7 @@ void M() } [Fact] - public async Task TestRemoveMembersAccessedOffThis() + public async Task TestRemoveMembersAccessedOffThis1() { await new VerifyCS.Test { @@ -881,8 +917,8 @@ void M(C c) using System; class C { + private readonly int _i; private int _j; - private int _i; public C(int i, int j) { @@ -902,6 +938,49 @@ void M(C c) }.RunAsync(); } + [Fact] + public async Task TestRemoveMembersAccessedOffThis2() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + class [|C(int i, int j)|] + { + private int _j = j; + + void M(C c) + { + Console.WriteLine(i++); + Console.WriteLine(_j == c._j); + } + } + """, + FixedCode = """ + using System; + class C + { + private int _j; + private int _i; + + public C(int i, int j) + { + _i = i; + _j = j; + } + + void M(C c) + { + Console.WriteLine(_i++); + Console.WriteLine(_j == c._j); + } + } + """, + LanguageVersion = LanguageVersion.CSharp12, + EditorConfig = FieldNamesCamelCaseWithFieldUnderscorePrefixEditorConfig, + }.RunAsync(); + } + [Fact] public async Task TestWhenRightSideDoesNotReferenceThis() { @@ -2563,7 +2642,7 @@ int M() /// class C { - private int i; + private readonly int i; public C(int i) { @@ -2603,7 +2682,7 @@ int M() /// class C { - private int _i; + private readonly int _i; public C(int i) { From 74600a9552f38a5bd4e8e43b43f04dea787c5186 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 15:16:12 -0700 Subject: [PATCH 36/39] Fix indentation --- ...arpUsePrimaryConstructorCodeFixProvider.cs | 6 +- ...gularConstructorCodeRefactoringProvider.cs | 66 ++++++++++++++++++- ...factoringProvider_DocumentationComments.cs | 10 --- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs index 81ee69dd3455a..9d43f4848a4c2 100644 --- a/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UsePrimaryConstructor/CSharpUsePrimaryConstructorCodeFixProvider.cs @@ -268,12 +268,12 @@ static TListSyntax RemoveElementIndentation( getElements(list), (p, _) => { - var parameterLeadingWhitespace = GetLeadingWhitespace(p); - if (parameterLeadingWhitespace.EndsWith(indentation)) + var elementLeadingWhitespace = GetLeadingWhitespace(p); + if (elementLeadingWhitespace.EndsWith(indentation)) { var leadingTrivia = p.GetLeadingTrivia(); return p.WithLeadingTrivia( - leadingTrivia.Take(leadingTrivia.Count - 1).Concat(Whitespace(parameterLeadingWhitespace[..^indentation.Length]))); + leadingTrivia.Take(leadingTrivia.Count - 1).Concat(Whitespace(elementLeadingWhitespace[..^indentation.Length]))); } return p; diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index f7495ec4a3988..9179632fc52ab 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -18,6 +19,7 @@ using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -107,6 +109,8 @@ private static async Task ConvertAsync( var constructorAnnotation = new SyntaxAnnotation(); + // Now go do the entire transformation. + RemovePrimaryConstructorParameterList(); RemovePrimaryConstructorBaseTypeArgumentList(); RemovePrimaryConstructorTargetingAttributes(); @@ -114,7 +118,7 @@ private static async Task ConvertAsync( AddNewFields(); AddConstructorDeclaration(); await RewritePrimaryConstructorParameterReferencesAsync().ConfigureAwait(false); - // FixConstructorDeclarationFormatting(); + FixParameterAndBaseArgumentListIndentation(); return solutionEditor.GetChangedSolution(); @@ -374,6 +378,66 @@ async Task RewritePrimaryConstructorParameterReferencesAsync() } } + void FixParameterAndBaseArgumentListIndentation() + { + var currentRoot = mainDocumentEditor.GetChangedRoot(); + var formattingOptions = optionsProvider.GetOptions(document.Project.Services).CleanupOptions.FormattingOptions; + var indentationOptions = new IndentationOptions(formattingOptions); + + var formattedRoot = Formatter.Format(currentRoot, SyntaxAnnotation.ElasticAnnotation, solution.Services, formattingOptions, cancellationToken); + + var constructor = (ConstructorDeclarationSyntax)formattedRoot.GetAnnotatedNodes(constructorAnnotation).Single(); + + var rewrittenParameterList = AddElementIndentation(typeDeclaration, constructor, constructor.ParameterList, static list => list.Parameters); + var initializer = constructor.Initializer; + var rewrittenInitializer = initializer?.WithArgumentList(AddElementIndentation(typeDeclaration, constructor, initializer.ArgumentList, static list => list.Arguments)); + + var rewrittenConstructor = constructor + .WithParameterList(rewrittenParameterList) + .WithInitializer(rewrittenInitializer); + + var rewrittenRoot = formattedRoot.ReplaceNode(constructor, rewrittenConstructor); + mainDocumentEditor.ReplaceNode(mainDocumentEditor.OriginalRoot, rewrittenRoot); + } + + static TListSyntax AddElementIndentation( + TypeDeclarationSyntax typeDeclaration, + ConstructorDeclarationSyntax constructorDeclaration, + TListSyntax list, + Func> getElements) + where TListSyntax : SyntaxNode + { + // Since we're moving parameters from the constructor to the type, attempt to dedent them if appropriate. + + var typeLeadingWhitespace = GetLeadingWhitespace(typeDeclaration); + var constructorLeadingWhitespace = GetLeadingWhitespace(constructorDeclaration); + + if (constructorLeadingWhitespace.Length > typeLeadingWhitespace.Length && + constructorLeadingWhitespace.StartsWith(typeLeadingWhitespace)) + { + var indentation = constructorLeadingWhitespace[typeLeadingWhitespace.Length..]; + return list.ReplaceNodes( + getElements(list), + (p, _) => + { + var elementLeadingWhitespace = GetLeadingWhitespace(p); + if (elementLeadingWhitespace.Length > 0 && elementLeadingWhitespace.StartsWith(typeLeadingWhitespace)) + { + var leadingTrivia = p.GetLeadingTrivia(); + return p.WithLeadingTrivia( + leadingTrivia.Concat(Whitespace(indentation))); + } + + return p; + }); + } + + return list; + } + + static string GetLeadingWhitespace(SyntaxNode node) + => node.GetLeadingTrivia() is [.., (kind: SyntaxKind.WhitespaceTrivia) whitespace] ? whitespace.ToString() : ""; + ConstructorDeclarationSyntax CreateConstructorDeclaration() { using var _1 = ArrayBuilder.GetInstance(out var assignmentStatements); diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs index 25d2bbd4c1c4e..a99133018049f 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider_DocumentationComments.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -17,18 +16,9 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertPrimaryToRegularConstructor; internal sealed partial class ConvertPrimaryToRegularConstructorCodeRefactoringProvider { - private static SyntaxTrivia GetDocComment(SyntaxNode node) - => GetDocComment(node.GetLeadingTrivia()); - private static SyntaxTrivia GetDocComment(SyntaxTriviaList trivia) => trivia.LastOrDefault(t => t.IsSingleLineDocComment()); - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxNode node) - => GetDocCommentStructure(node.GetLeadingTrivia()); - - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTriviaList trivia) - => GetDocCommentStructure(GetDocComment(trivia)); - private static DocumentationCommentTriviaSyntax? GetDocCommentStructure(SyntaxTrivia trivia) => (DocumentationCommentTriviaSyntax?)trivia.GetStructure(); From eccf5360706d82bb84678f6b2c5650fdc8ceb178 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 15:23:16 -0700 Subject: [PATCH 37/39] Rename tests --- ...ConvertPrimaryToRegularConstructorTests.cs | 225 ++++-------------- 1 file changed, 40 insertions(+), 185 deletions(-) diff --git a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs index fc4d6081beb15..5afc6c7b2a6d6 100644 --- a/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs +++ b/src/Features/CSharpTest/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorTests.cs @@ -313,7 +313,7 @@ public C() : this(0, 0) } [Fact] - public async Task TestWithBlockBodyAssignmentToField1() + public async Task TestWithReferenceOnlyInExistingSameNamedField() { await new VerifyCS.Test { @@ -339,7 +339,7 @@ public C(int i) } [Fact] - public async Task TestWithBlockBodyAssignmentToProperty1() + public async Task TestWithReferenceOnlyInPropertyInitializer1() { await new VerifyCS.Test { @@ -365,7 +365,7 @@ public C(int i) } [Fact] - public async Task TestWithBlockBodyAssignmentToField2() + public async Task TestWithReferenceInDifferentNamedField() { await new VerifyCS.Test { @@ -391,7 +391,7 @@ public C(int j) } [Fact] - public async Task TestWithComplexRightSide1() + public async Task TestWithComplexFieldInitializer1() { await new VerifyCS.Test { @@ -417,7 +417,7 @@ public C(int i) } [Fact] - public async Task TestWithComplexRightSide2() + public async Task TestWithComplexFieldInitializer2() { await new VerifyCS.Test { @@ -443,7 +443,7 @@ public C(int i) } [Fact] - public async Task TestWithComplexRightSide3() + public async Task TestWithComplexFieldInitializer3() { await new VerifyCS.Test { @@ -472,7 +472,7 @@ public C(int i) } [Fact] - public async Task TestBlockWithMultipleAssignments1() + public async Task TestMultipleParametersWithFieldInitializers1() { await new VerifyCS.Test { @@ -501,7 +501,7 @@ public C(int i, int j) } [Fact] - public async Task TestBlockWithMultipleAssignments2() + public async Task TestMultipleParametersWithFieldInitializers2() { await new VerifyCS.Test { @@ -528,7 +528,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembers1_Used() + public async Task TestWithParametersReferencedOutsideOfConstructor() { await new VerifyCS.Test { @@ -564,7 +564,7 @@ int M() } [Fact] - public async Task TestRemoveMembers1_Used_Written1() + public async Task TestWithParametersReferencedOutsideOfConstructor_Mutation1() { await new VerifyCS.Test { @@ -600,7 +600,7 @@ int M() } [Fact] - public async Task TestRemoveMembers1_Unused() + public async Task TestWithoutParametersReferencedOutsideOfConstructor() { await new VerifyCS.Test { @@ -622,7 +622,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersOnlyWithMatchingType() + public async Task TestAssignmentToPropertyOfDifferentType() { await new VerifyCS.Test { @@ -648,7 +648,7 @@ public C(int i, int j) } [Fact] - public async Task TestNestedTypes() + public async Task TestAssignedToFieldUsedInNestedType() { await new VerifyCS.Test { @@ -700,7 +700,7 @@ public Enumerator(OuterType c) } [Fact] - public async Task TestRemoveMembersUpdateReferences1() + public async Task TestWithNotMutatedReferencesOutsideOfConstructor1() { await new VerifyCS.Test { @@ -738,7 +738,7 @@ void M() } [Fact] - public async Task TestRemoveMembersUpdateReferences2() + public async Task TestEscapedParameterNames() { await new VerifyCS.Test { @@ -776,45 +776,7 @@ void M() } [Fact] - public async Task TestRemoveMembersUpdateReferencesWithRename1() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - class [|C(int i, int j)|] - { - void M() - { - Console.WriteLine(i + j); - } - } - """, - FixedCode = """ - using System; - class C - { - private readonly int i; - private readonly int j; - - public C(int i, int j) - { - this.i = i; - this.j = j; - } - - void M() - { - Console.WriteLine(i + j); - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestRemoveMembersOnlyPrivateMembers() + public async Task TestWithNotMutatedReferencesOutsideOfConstructor2() { await new VerifyCS.Test { @@ -854,7 +816,7 @@ void M() } [Fact] - public async Task TestRemoveMembersOnlyMembersWithoutAttributes() + public async Task TestWithNotMutatedReferencesOutsideOfConstructor3() { await new VerifyCS.Test { @@ -896,7 +858,7 @@ void M() } [Fact] - public async Task TestRemoveMembersAccessedOffThis1() + public async Task TestGenerateUnderscoreName1() { await new VerifyCS.Test { @@ -939,7 +901,7 @@ void M(C c) } [Fact] - public async Task TestRemoveMembersAccessedOffThis2() + public async Task TestGenerateUnderscoreName2() { await new VerifyCS.Test { @@ -982,7 +944,7 @@ void M(C c) } [Fact] - public async Task TestWhenRightSideDoesNotReferenceThis() + public async Task TestComplexFieldInitializer() { await new VerifyCS.Test { @@ -1012,7 +974,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_1() + public async Task TestMoveParamDocs1() { await new VerifyCS.Test { @@ -1048,7 +1010,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_IfDef1() + public async Task TestMoveParamDocs2() { await new VerifyCS.Test { @@ -1088,37 +1050,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_2() - { - await new VerifyCS.Test - { - TestCode = """ - /// Doc comment on single line - /// Doc about i single line - class [|C(int i)|] - { - private int i = i; - } - """, - FixedCode = """ - /// Doc comment on single line - class C - { - private int i; - - /// Doc about i single line - public C(int i) - { - this.i = i; - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } - - [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_SingleLine_3() + public async Task TestMoveParamDocs3() { await new VerifyCS.Test { @@ -1148,7 +1080,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_1() + public async Task TestMoveParamDocs4() { await new VerifyCS.Test { @@ -1196,7 +1128,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_2() + public async Task TestMoveParamDocs5() { await new VerifyCS.Test { @@ -1240,7 +1172,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_3() + public async Task TestMoveParamDocs6() { await new VerifyCS.Test { @@ -1280,7 +1212,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorDocCommentWhenNothingOnType_MultiLine_4() + public async Task TestMoveParamDocs7() { await new VerifyCS.Test { @@ -1328,7 +1260,7 @@ public C(int i) } [Fact] - public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments1() + public async Task TestMoveParamDocs9() { await new VerifyCS.Test { @@ -1377,7 +1309,7 @@ public C(int i, int j) } [Fact] - public async Task TestMoveConstructorParamDocCommentsIntoTypeDocComments2() + public async Task TestMoveParamDocs10() { await new VerifyCS.Test { @@ -1424,7 +1356,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments1() + public async Task TestMoveParamDocs11() { await new VerifyCS.Test { @@ -1460,7 +1392,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments4() + public async Task TestMoveParamDocs13() { await new VerifyCS.Test { @@ -1492,7 +1424,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments5() + public async Task TestMoveParamDocs14() { await new VerifyCS.Test { @@ -1524,7 +1456,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments6() + public async Task TestMoveParamDocs15() { await new VerifyCS.Test { @@ -1556,7 +1488,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments2() + public async Task TestMoveParamDocs16() { await new VerifyCS.Test { @@ -1592,7 +1524,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments3() + public async Task TestMoveParamDocs17() { await new VerifyCS.Test { @@ -1624,7 +1556,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenNoTypeDocComments_MembersWithDifferentNames1() + public async Task TestMoveParamDocs18() { await new VerifyCS.Test { @@ -1660,7 +1592,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersMoveDocComments_WhenTypeDocComments1() + public async Task TestMoveParamDocs19() { await new VerifyCS.Test { @@ -1702,7 +1634,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersKeepConstructorDocs1() + public async Task TestMoveParamDocs20() { await new VerifyCS.Test { @@ -1740,7 +1672,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersKeepConstructorDocs2() + public async Task TestMoveParamDocs21() { await new VerifyCS.Test { @@ -1778,7 +1710,7 @@ public C(int i, int j) } [Fact] - public async Task TestRemoveMembersKeepConstructorDocs3() + public async Task TestMoveParamDocs22() { await new VerifyCS.Test { @@ -2783,81 +2715,4 @@ public C(int i = Default) LanguageVersion = LanguageVersion.CSharp12, }.RunAsync(); } - - [Fact] - public async Task TestMergeConstructorSummaryIntoTypeDocComment() - { - await new VerifyCS.Test - { - TestCode = """ - using System; - - namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue - { - /// - /// Active instruction identifier. - /// It has the information necessary to track an active instruction within the debug session. - /// - /// - /// Creates an ActiveInstructionId. - /// - /// Method which the instruction is scoped to. - /// IL offset for the instruction. - [CLSCompliant(false)] - internal readonly struct [|ManagedInstructionId( - string method, - int ilOffset)|] - { - /// - /// Method which the instruction is scoped to. - /// - public string Method { get; } = method; - - /// - /// The IL offset for the instruction. - /// - public int ILOffset { get; } = ilOffset; - } - } - """, - FixedCode = """ - using System; - - namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue - { - /// - /// Active instruction identifier. - /// It has the information necessary to track an active instruction within the debug session. - /// - /// - /// Creates an ActiveInstructionId. - /// - [CLSCompliant(false)] - internal readonly struct ManagedInstructionId - { - /// - /// Method which the instruction is scoped to. - /// - public string Method { get; } - - /// - /// The IL offset for the instruction. - /// - public int ILOffset { get; } - - /// Method which the instruction is scoped to. - /// IL offset for the instruction. - public ManagedInstructionId( - string method, - int ilOffset) - { - Method = method; - ILOffset = ilOffset; - } - } - } - """, - LanguageVersion = LanguageVersion.CSharp12, - }.RunAsync(); - } } From e533908656e80fee3cac6d7818f7a6ec12c892ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 15:28:28 -0700 Subject: [PATCH 38/39] simplify --- .../ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 9179632fc52ab..1ef4aabf47603 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -249,7 +249,6 @@ void RemovePrimaryConstructorParameterList() void RemovePrimaryConstructorBaseTypeArgumentList() { - var baseType = typeDeclaration.BaseList?.Types is [PrimaryConstructorBaseTypeSyntax type, ..] ? type : null; if (baseType != null) mainDocumentEditor.ReplaceNode(baseType, (current, _) => SimpleBaseType(((PrimaryConstructorBaseTypeSyntax)current).Type).WithTriviaFrom(baseType)); } From 1b58f6cad42f89e8ece79851d16f7ee2f5546e8d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 16 Oct 2023 15:28:50 -0700 Subject: [PATCH 39/39] indent --- ...gularConstructorCodeRefactoringProvider.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 1ef4aabf47603..1fbe78a52b723 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -288,17 +288,17 @@ void RemoveDirectFieldAndPropertyAssignments() void AddNewFields() { mainDocumentEditor.ReplaceNode( - typeDeclaration, - (current, _) => - { - var currentTypeDeclaration = (TypeDeclarationSyntax)current; - var fieldsInOrder = parameters - .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) - .WhereNotNull(); - var codeGenService = document.GetRequiredLanguageService(); - return codeGenService.AddMembers( - currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); - }); + typeDeclaration, + (current, _) => + { + var currentTypeDeclaration = (TypeDeclarationSyntax)current; + var fieldsInOrder = parameters + .Select(p => parameterToSynthesizedFields.TryGetValue(p, out var field) ? field : null) + .WhereNotNull(); + var codeGenService = document.GetRequiredLanguageService(); + return codeGenService.AddMembers( + currentTypeDeclaration, fieldsInOrder, contextInfo, cancellationToken); + }); } void AddConstructorDeclaration()