From de3bc1f67aaab3c0a097c90470ccad283f7a9c62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 12:46:15 -0700 Subject: [PATCH 01/59] Initial work --- ...berFromParameterCodeRefactoringProvider.cs | 54 +- ...tructorParameterCodeRefactoringProvider.cs | 884 ++++++++++++++++++ .../InitializeParameterHelpers.cs | 85 ++ .../PredefinedCodeRefactoringProviderNames.cs | 1 + ...ddParameterCheckCodeRefactoringProvider.cs | 4 +- ...erCodeRefactoringProviderMemberCreation.cs | 62 +- ...tializeParameterCodeRefactoringProvider.cs | 12 +- .../InitializeParameterHelpersCore.cs | 75 ++ 8 files changed, 1064 insertions(+), 113 deletions(-) create mode 100644 src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs create mode 100644 src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs index b72b48cd9fa4f..5c074211ac31d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs @@ -16,10 +16,13 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter { + using static InitializeParameterHelpers; + using static InitializeParameterHelpersCore; + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromParameter), Shared] [ExtensionOrder(Before = nameof(CSharpAddParameterCheckCodeRefactoringProvider))] [ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.Wrapping)] - internal class CSharpInitializeMemberFromParameterCodeRefactoringProvider : + internal sealed class CSharpInitializeMemberFromParameterCodeRefactoringProvider : AbstractInitializeMemberFromParameterCodeRefactoringProvider< BaseTypeDeclarationSyntax, ParameterSyntax, @@ -56,55 +59,10 @@ protected override SyntaxNode GetBody(SyntaxNode functionDeclaration) => InitializeParameterHelpers.GetBody(functionDeclaration); protected override SyntaxNode? GetAccessorBody(IMethodSymbol accessor, CancellationToken cancellationToken) - { - var node = accessor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); - if (node is AccessorDeclarationSyntax accessorDeclaration) - return accessorDeclaration.ExpressionBody ?? (SyntaxNode?)accessorDeclaration.Body; - - // `int Age => ...;` - if (node is ArrowExpressionClauseSyntax arrowExpression) - return arrowExpression; - - return null; - } + => InitializeParameterHelpers.GetAccessorBody(accessor, cancellationToken); protected override SyntaxNode RemoveThrowNotImplemented(SyntaxNode node) - { - if (node is PropertyDeclarationSyntax propertyDeclaration) - { - if (propertyDeclaration.ExpressionBody != null) - { - var result = propertyDeclaration - .WithExpressionBody(null) - .WithSemicolonToken(default) - .AddAccessorListAccessors(SyntaxFactory - .AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))) - .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia) - .WithAdditionalAnnotations(Formatter.Annotation); - return result; - } - - if (propertyDeclaration.AccessorList != null) - { - var accessors = propertyDeclaration.AccessorList.Accessors.Select(RemoveThrowNotImplemented); - return propertyDeclaration.WithAccessorList( - propertyDeclaration.AccessorList.WithAccessors(SyntaxFactory.List(accessors))); - } - } - - return node; - } - - private static AccessorDeclarationSyntax RemoveThrowNotImplemented(AccessorDeclarationSyntax accessorDeclaration) - { - var result = accessorDeclaration - .WithExpressionBody(null) - .WithBody(null) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - - return result.WithTrailingTrivia(accessorDeclaration.Body?.GetTrailingTrivia() ?? accessorDeclaration.SemicolonToken.TrailingTrivia); - } + => InitializeParameterHelpers.RemoveThrowNotImplemented(node); protected override bool TryUpdateTupleAssignment( IBlockOperation? blockStatement, diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..b6304e981b0c5 --- /dev/null +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -0,0 +1,884 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +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.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.InitializeParameter; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Naming; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter +{ + using static InitializeParameterHelpers; + using static InitializeParameterHelpersCore; + + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] + internal sealed class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider() + { + } + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var (document, _, cancellationToken) = context; + + // TODO: One could try to retrieve TParameterList and then filter out parameters that intersect with + // textSpan and use that as `parameterNodes`, where `selectedParameter` would be the first one. + + var selectedParameter = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + if (selectedParameter == null) + return; + + if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration }) + return; + + var generator = SyntaxGenerator.GetGenerator(document); + // var parameterNodes = generator.GetParameters(functionDeclaration); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + // we can't just call GetDeclaredSymbol on functionDeclaration because it could an anonymous function, + // so first we have to get the parameter symbol and then its containing method symbol + var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); + if (parameter?.Name is null or "") + return; + + if (parameter.ContainingSymbol is not IMethodSymbol methodSymbol || + methodSymbol.IsAbstract || + methodSymbol.IsExtern || + methodSymbol.PartialImplementationPart != null || + methodSymbol.ContainingType.TypeKind == TypeKind.Interface) + { + return; + } + + //// We shouldn't offer a refactoring if the compilation doesn't contain the ArgumentNullException type, + //// as we use it later on in our computations. + //var argumentNullExceptionType = typeof(ArgumentNullException).FullName; + //if (argumentNullExceptionType is null || semanticModel.Compilation.GetTypeByMetadataName(argumentNullExceptionType) is null) + // return; + + //if (CanOfferRefactoring(functionDeclaration, semanticModel, syntaxFacts, cancellationToken, out var blockStatementOpt)) + //{ + // Ok. Looks like the selected parameter could be refactored. Defer to subclass to + // actually determine if there are any viable refactorings here. + var refactorings = await GetRefactoringsForSingleParameterAsync( + document, typeDeclaration, selectedParameter, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); + context.RegisterRefactorings(refactorings, context.Span); + // } + + //// List with parameterNodes that pass all checks + //using var _ = ArrayBuilder.GetInstance(out var listOfPotentiallyValidParametersNodes); + //foreach (var parameterNode in parameterNodes) + //{ + // if (!TryGetParameterSymbol(parameterNode, semanticModel, out parameter, cancellationToken)) + // return; + + // // Update the list of valid parameter nodes + // listOfPotentiallyValidParametersNodes.Add(parameterNode); + //} + + //if (listOfPotentiallyValidParametersNodes.Count > 1) + //{ + // // Looks like we can offer a refactoring for more than one parameter. Defer to subclass to + // // actually determine if there are any viable refactorings here. + // var refactorings = await GetRefactoringsForAllParametersAsync( + // document, functionDeclaration, methodSymbol, blockStatementOpt, + // listOfPotentiallyValidParametersNodes.ToImmutable(), selectedParameter.Span, context.Options, cancellationToken).ConfigureAwait(false); + // context.RegisterRefactorings(refactorings, context.Span); + //} + + return; + + //static bool TryGetParameterSymbol( + // SyntaxNode parameterNode, + // SemanticModel semanticModel, + // [NotNullWhen(true)] out IParameterSymbol? parameter, + // CancellationToken cancellationToken) + //{ + // parameter = (IParameterSymbol?)semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken); + + // return parameter is { Name: not "" }; + //} + } + + private async Task> GetRefactoringsForSingleParameterAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + ParameterSyntax parameterSyntax, + IParameterSymbol parameter, + IMethodSymbol method, + CleanCodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + // Only supported for constructor parameters. + if (method.MethodKind != MethodKind.Constructor) + return ImmutableArray.Empty; + + // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing + // more for us to do. + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var assignmentExpression = TryFindFieldOrPropertyInitializerValue(semanticModel.Compilation, parameter, cancellationToken); + if (assignmentExpression != null) + return ImmutableArray.Empty; + + // Haven't initialized any fields/properties with this parameter. Offer to assign + // to an existing matching field/prop if we can find one, or add a new field/prop + // if we can't. + + var rules = await document.GetNamingRulesAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); + if (parameterNameParts.BaseName == "") + return ImmutableArray.Empty; + + var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync( + document, parameter, rules, parameterNameParts.BaseNameParts, cancellationToken).ConfigureAwait(false); + + if (fieldOrProperty != null) + { + return HandleExistingFieldOrProperty( + document, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions); + } + else + { + return await HandleNoExistingFieldOrPropertyAsync( + document, typeDeclaration, parameter, method, rules, fallbackOptions, cancellationToken).ConfigureAwait(false); + } + } + + private async Task> HandleNoExistingFieldOrPropertyAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + IParameterSymbol parameter, + IMethodSymbol method, + ImmutableArray rules, + CleanCodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + // Didn't find a field/prop that this parameter could be assigned to. + // Offer to create new one and assign to that. + using var _ = ArrayBuilder.GetInstance(out var allActions); + + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + + var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions( + document, parameter, constructorDeclaration, blockStatement, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); + + // Check if the surrounding parameters are assigned to another field in this class. If so, offer to + // make this parameter into a field as well. Otherwise, default to generating a property + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(semanticModel, typeDeclaration, parameter, cancellationToken); + if (siblingFieldOrProperty is IFieldSymbol) + { + allActions.Add(fieldAction); + allActions.Add(propertyAction); + } + else + { + allActions.Add(propertyAction); + allActions.Add(fieldAction); + } + + var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions( + document, semanticModel, typeDeclaration, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); + + if (allFieldsAction != null && allPropertiesAction != null) + { + if (siblingFieldOrProperty is IFieldSymbol) + { + allActions.Add(allFieldsAction); + allActions.Add(allPropertiesAction); + } + else + { + allActions.Add(allPropertiesAction); + allActions.Add(allFieldsAction); + } + } + + return allActions.ToImmutable(); + } + + private (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( + Document document, + SemanticModel semanticModel, + TypeDeclarationSyntax typeDeclaration, + IMethodSymbol method, + ImmutableArray rules, + AccessibilityModifiersRequired accessibilityModifiersRequired, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + var parameters = GetParametersWithoutAssociatedMembers( + semanticModel, typeDeclaration, rules, method, cancellationToken); + + if (parameters.Length < 2) + return default; + + var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, accessibilityModifiersRequired, rules)); + var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, accessibilityModifiersRequired, rules)); + + var allFieldsAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_fields, + c => AddAllSymbolInitializationsAsync( + document, constructorDeclaration, blockStatement, parameters, fields, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); + var allPropertiesAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_properties, + c => AddAllSymbolInitializationsAsync( + document, constructorDeclaration, blockStatement, parameters, properties, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); + + return (allFieldsAction, allPropertiesAction); + } + + private (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( + Document document, + IParameterSymbol parameter, + SyntaxNode constructorDeclaration, + IBlockOperation? blockStatement, + ImmutableArray rules, + AccessibilityModifiersRequired accessibilityModifiersRequired, + CodeGenerationOptionsProvider fallbackOptions) + { + var field = CreateField(parameter, accessibilityModifiersRequired, rules); + var property = CreateProperty(parameter, accessibilityModifiersRequired, rules); + + // we're generating the field or property, so we don't have to handle throwing versions of them. + var isThrowNotImplementedProperty = false; + + var fieldAction = CodeAction.Create( + string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), + c => AddSingleSymbolInitializationAsync(document, constructorDeclaration, blockStatement, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_field_0) + "_" + field.Name); + var propertyAction = CodeAction.Create( + string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), + c => AddSingleSymbolInitializationAsync(document, constructorDeclaration, blockStatement, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_property_0) + "_" + property.Name); + + return (fieldAction, propertyAction); + } + + private static ImmutableArray GetParametersWithoutAssociatedMembers( + Compilation compilation, + ImmutableArray rules, + IMethodSymbol method, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var parameter in method.Parameters) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); + if (parameterNameParts.BaseName == "") + continue; + + var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); + if (assignmentOp != null) + continue; + + result.Add(parameter); + } + + return result.ToImmutable(); + } + + private ImmutableArray HandleExistingFieldOrProperty( + Document document, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + bool isThrowNotImplementedProperty, + CodeGenerationOptionsProvider fallbackOptions) + { + // Found a field/property that this parameter should be assigned to. + // Just offer the simple assignment to it. + + var resource = fieldOrProperty.Kind == SymbolKind.Field + ? FeaturesResources.Initialize_field_0 + : FeaturesResources.Initialize_property_0; + + var title = string.Format(resource, fieldOrProperty.Name); + + return ImmutableArray.Create(CodeAction.Create( + title, + cancellationToken => AddSingleSymbolInitializationAsync( + document, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), + title)); + } + + private static ISymbol? TryFindSiblingFieldOrProperty( + Compilation compilation, + IParameterSymbol parameter, + CancellationToken cancellationToken) + { + foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) + { + TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, out var sibling, cancellationToken); + if (sibling != null) + return sibling; + } + + return null; + } + + private static IFieldSymbol CreateField( + IParameterSymbol parameter, + AccessibilityModifiersRequired accessibilityModifiersRequired, + ImmutableArray rules) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + + foreach (var rule in rules) + { + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) + { + var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); + + var accessibilityLevel = accessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault + ? Accessibility.NotApplicable + : Accessibility.Private; + //if (accessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault) + //{ + // var defaultAccessibility = DetermineDefaultFieldAccessibility(parameter.ContainingType); + // if (defaultAccessibility == Accessibility.Private) + // { + // accessibilityLevel = Accessibility.NotApplicable; + // } + //} + + return CodeGenerationSymbolFactory.CreateFieldSymbol( + default, + accessibilityLevel, + DeclarationModifiers.ReadOnly, + parameter.Type, uniqueName); + } + } + + // We place a special rule in s_builtInRules that matches all fields. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); + } + + private IPropertySymbol CreateProperty( + IParameterSymbol parameter, + ImmutableArray rules) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + + foreach (var rule in rules) + { + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) + { + var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); + + var accessibilityLevel = Accessibility.Public; + + var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( + default, + Accessibility.Public, + default); + + return CodeGenerationSymbolFactory.CreatePropertySymbol( + default, + accessibilityLevel, + new DeclarationModifiers(), + parameter.Type, + RefKind.None, + explicitInterfaceImplementations: default, + name: uniqueName, + parameters: default, + getMethod: getMethod, + setMethod: null); + } + } + + // We place a special rule in s_builtInRules that matches all properties. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); + } + + private async Task AddAllSymbolInitializationsAsync( + Document document, + SyntaxNode constructorDeclaration, + IBlockOperation? blockStatement, + ImmutableArray parameters, + ImmutableArray fieldsOrProperties, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + Debug.Assert(parameters.Length >= 2); + Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length == fieldsOrProperties.Length); + + // Process each param+field/prop in order. Apply the pair to the document getting the updated document. + // Then find all the current data in that updated document and move onto the next pair. + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var nodesToTrack = new List { constructorDeclaration }; + if (blockStatement != null) + nodesToTrack.Add(blockStatement.Syntax); + + var trackedRoot = root.TrackNodes(nodesToTrack); + var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; + + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var fieldOrProperty = fieldsOrProperties[i]; + + var currentDocument = currentSolution.GetRequiredDocument(document.Id); + var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var currentCompilation = currentSemanticModel.Compilation; + var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var currentConstructorDeclaration = currentRoot.GetCurrentNode(constructorDeclaration); + if (currentConstructorDeclaration == null) + continue; + + IBlockOperation? currentBlockStatement = null; + if (blockStatement != null) + { + currentBlockStatement = (IBlockOperation?)currentSemanticModel.GetOperation(currentRoot.GetCurrentNode(blockStatement.Syntax)!, cancellationToken); + if (currentBlockStatement == null) + continue; + } + + var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (currentParameter == null) + continue; + + // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. + + currentSolution = await AddSingleSymbolInitializationAsync( + currentDocument, + currentConstructorDeclaration, + currentBlockStatement, + currentParameter, + fieldOrProperty, + isThrowNotImplementedProperty: false, + fallbackOptions, + cancellationToken).ConfigureAwait(false); + } + + return currentSolution; + } + + private async Task AddSingleSymbolInitializationAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + bool isThrowNotImplementedProperty, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + var services = document.Project.Solution.Services; + + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var editor = new SolutionEditor(document.Project.Solution); + var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var codeGenerator = document.GetRequiredLanguageService(); + + var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + var generator = mainEditor.Generator; + if (fieldOrProperty.ContainingType == null) + { + // We're generating a new field/property. Place into the containing type, + // ideally before/after a relevant existing member. + // + // Now add the field/property to this type. Use the 'ReplaceNode+callback' form + // so that nodes will be appropriate tracked and so we can then update the constructor + // below even after we've replaced the whole type with a new type. + // + // Note: We'll pass the appropriate options so that the new field/property + // is appropriate placed before/after an existing field/property. We'll try + // to preserve the same order for fields/properties that we have for the constructor + // parameters. + mainEditor.ReplaceNode( + typeDeclaration, + (currentTypeDecl, _) => + { + if (fieldOrProperty is IPropertySymbol property) + { + return codeGenerator.AddProperty( + currentTypeDecl, property, + codeGenerator.GetInfo(GetAddContext(compilation, parameter, typeDeclaration, cancellationToken), options, root.SyntaxTree.Options), + cancellationToken); + } + else if (fieldOrProperty is IFieldSymbol field) + { + return codeGenerator.AddField( + currentTypeDecl, field, + codeGenerator.GetInfo(GetAddContext(compilation, parameter, typeDeclaration, cancellationToken), options, root.SyntaxTree.Options), + cancellationToken); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + }); + } + + AddAssignment(constructorDeclaration, blockStatement, parameter, fieldOrProperty, editor); + + // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. + var currentSolution = document.Project.Solution; + if (isThrowNotImplementedProperty) + { + var declarationService = document.GetRequiredLanguageService(); + var propertySyntax = await declarationService.GetDeclarations(fieldOrProperty)[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + var withoutThrowNotImplemented = InitializeParameterHelpers.RemoveThrowNotImplemented(propertySyntax); + + if (propertySyntax.SyntaxTree == root.SyntaxTree) + { + // Edit to the same file, just update this editor. + mainEditor.ReplaceNode(propertySyntax, withoutThrowNotImplemented); + } + else + { + // edit to a different file. Just replace things directly in there. + var otherDocument = currentSolution.GetDocument(propertySyntax.SyntaxTree); + if (otherDocument != null) + { + var otherRoot = await propertySyntax.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + currentSolution = currentSolution.WithDocumentSyntaxRoot( + otherDocument.Id, otherRoot.ReplaceNode(propertySyntax, withoutThrowNotImplemented)); + } + } + } + + return currentSolution.WithDocumentSyntaxRoot(document.Id, editor.GetChangedRoot()); + } + + private void AddAssignment( + IParameterSymbol parameter, + ISymbol fieldOrProperty, + SyntaxEditor editor) + { + //// First see if the user has `(_x, y) = (x, y);` and attempt to update that. + //if (TryUpdateTupleAssignment(blockStatement, parameter, fieldOrProperty, editor)) + // return; + + var generator = editor.Generator; + + // Now that we've added any potential members, create an assignment between it + // and the parameter. + var initializationStatement = (StatementSyntax)generator.ExpressionStatement( + generator.AssignmentStatement( + generator.MemberAccessExpression( + generator.ThisExpression(), + generator.IdentifierName(fieldOrProperty.Name)), + generator.IdentifierName(parameter.Name))); + + // Attempt to place the initialization in a good location in the constructor + // We'll want to keep initialization statements in the same order as we see + // parameters for the constructor. + var statementToAddAfter = TryGetStatementToAddInitializationAfter(parameter, blockStatement); + + InsertStatement(editor, constructorDeclaration, returnsVoid: true, statementToAddAfter, initializationStatement); + } + + private static CodeGenerationContext GetAddContext( + Compilation compilation, + IParameterSymbol parameter, + TypeDeclarationSyntax typeDeclaration, + CancellationToken cancellationToken) + where TSymbol : ISymbol + { + foreach (var (sibling, before) in GetSiblingParameters(parameter)) + { + var initializer = TryFindFieldOrPropertyInitializerValue( + compilation, sibling, out var fieldOrProperty, cancellationToken); + + if (initializer != null && + fieldOrProperty is TSymbol symbol) + { + var symbolSyntax = symbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + if (symbolSyntax.Ancestors().Contains(typeDeclaration)) + { + if (before) + { + // Found an existing field/property that corresponds to a preceding parameter. + // Place ourselves directly after it. + return new CodeGenerationContext(afterThisLocation: symbolSyntax.GetLocation()); + } + else + { + // Found an existing field/property that corresponds to a following parameter. + // Place ourselves directly before it. + return new CodeGenerationContext(beforeThisLocation: symbolSyntax.GetLocation()); + } + } + } + } + + return CodeGenerationContext.Default; + } + + //private SyntaxNode? TryGetStatementToAddInitializationAfter( + // IParameterSymbol parameter, IBlockOperation? blockStatement) + //{ + // // look for an existing assignment for a parameter that comes before/after us. + // // If we find one, we'll add ourselves before/after that parameter check. + // foreach (var (sibling, before) in GetSiblingParameters(parameter)) + // { + // var statement = TryFindFieldOrPropertyAssignmentStatement(sibling, blockStatement); + // if (statement != null) + // { + // if (before) + // { + // return statement.Syntax; + // } + // else + // { + // var statementIndex = blockStatement!.Operations.IndexOf(statement); + // return statementIndex > 0 ? blockStatement.Operations[statementIndex - 1].Syntax : null; + // } + // } + // } + + // // We couldn't find a reasonable location for the new initialization statement. + // // Just place ourselves after the last statement in the constructor. + // return TryGetLastStatement(blockStatement); + //} + + private static IOperation? TryFindFieldOrPropertyInitializerValue( + Compilation compilation, + IParameterSymbol parameter, + CancellationToken cancellationToken) + => TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); + + //protected static bool TryGetPartsOfTupleAssignmentOperation( + // IOperation operation, + // [NotNullWhen(true)] out ITupleOperation? targetTuple, + // [NotNullWhen(true)] out ITupleOperation? valueTuple) + //{ + // if (operation is IExpressionStatementOperation + // { + // Operation: IDeconstructionAssignmentOperation + // { + // Target: ITupleOperation targetTupleTemp, + // Value: IConversionOperation { Operand: ITupleOperation valueTupleTemp }, + // } + // } && + // targetTupleTemp.Elements.Length == valueTupleTemp.Elements.Length) + // { + // targetTuple = targetTupleTemp; + // valueTuple = valueTupleTemp; + // return true; + // } + + // targetTuple = null; + // valueTuple = null; + // return false; + //} + + private static IOperation? TryFindFieldOrPropertyInitializerValue( + Compilation compilation, + IParameterSymbol parameter, + out ISymbol? fieldOrProperty, + CancellationToken cancellationToken) + { + foreach (var syntaxReference in parameter.ContainingType.DeclaringSyntaxReferences) + { + if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) + { + var semanticModel = compilation.GetSemanticModel(syntaxReference.SyntaxTree); + + foreach (var member in typeDeclaration.Members) + { + if (member is PropertyDeclarationSyntax { Initializer.Value: var propertyInitializer } propertyDeclaration) + { + var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + { + fieldOrProperty = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); + return operation; + } + } + else if (member is FieldDeclarationSyntax field) + { + foreach (var varDecl in field.Declaration.Variables) + { + if (varDecl is { Initializer.Value: var fieldInitializer }) + { + var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + { + fieldOrProperty = semanticModel.GetDeclaredSymbol(varDecl, cancellationToken); + return operation; + } + } + } + } + } + } + } + + //if (blockStatement != null) + //{ + // var containingType = parameter.ContainingType; + // foreach (var statement in blockStatement.Operations) + // { + // // look for something of the form: "this.s = s" or "this.s = s ?? ..." + // if (IsFieldOrPropertyAssignment(statement, containingType, out var assignmentExpression, out fieldOrProperty) && + // IsParameterReferenceOrCoalesceOfParameterReference(assignmentExpression, parameter)) + // { + // return statement; + // } + + // //// look inside the form `(this.s, this.t) = (s, t)` + // //if (TryGetPartsOfTupleAssignmentOperation(statement, out var targetTuple, out var valueTuple)) + // //{ + // // for (int i = 0, n = targetTuple.Elements.Length; i < n; i++) + // // { + // // var target = targetTuple.Elements[i]; + // // var value = valueTuple.Elements[i]; + + // // if (IsFieldOrPropertyReference(target, containingType, out fieldOrProperty) && + // // IsParameterReference(value, parameter)) + // // { + // // return statement; + // // } + // // } + // //} + // } + //} + + fieldOrProperty = null; + return null; + } + + private async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync( + Document document, IParameterSymbol parameter, ImmutableArray rules, ImmutableArray parameterWords, CancellationToken cancellationToken) + { + // Look for a field/property that really looks like it corresponds to this parameter. + // Use a variety of heuristics around the name/type to see if this is a match. + + var containingType = parameter.ContainingType; + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Walk through the naming rules against this parameter's name to see what + // name the user would like for it as a member in this type. Note that we + // have some fallback rules that use the standard conventions around + // properties /fields so that can still find things even if the user has no + // naming preferences set. + + foreach (var rule in rules) + { + var memberName = rule.NamingStyle.CreateName(parameterWords); + foreach (var memberWithName in containingType.GetMembers(memberName)) + { + // We found members in our type with that name. If it's a writable + // field that we could assign this parameter to, and it's not already + // been assigned to, then this field is a good candidate for us to + // hook up to. + if (memberWithName is IFieldSymbol field && + !field.IsConst && + InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && + field.DeclaringSyntaxReferences is [var syntaxRef1, ..] && + syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) + { + return (field, isThrowNotImplementedProperty: false); + } + + // If it's a writable property that we could assign this parameter to, and it's + // not already been assigned to, then this property is a good candidate for us to + // hook up to. + if (memberWithName is IPropertySymbol property && + InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && + property.DeclaringSyntaxReferences is [var syntaxRef2, ..] && + syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) + { + // We also allow assigning into a property of the form `=> throw new NotImplementedException()`. + // That way users can easily spit out those methods, but then convert them to be normal + // properties with ease. + if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) + return (property, isThrowNotImplementedProperty: true); + + if (property.IsWritableInConstructor()) + return (property, isThrowNotImplementedProperty: false); + } + } + } + + // Couldn't find any existing member. Just return nothing so we can offer to + // create a member for them. + return default; + + //static bool ContainsMemberAssignment(IBlockOperation? blockStatement, ISymbol member) + //{ + // if (blockStatement != null) + // { + // foreach (var statement in blockStatement.Operations) + // { + // if (IsFieldOrPropertyAssignment(statement, member.ContainingType, out var assignmentExpression) && + // assignmentExpression.Target.UnwrapImplicitConversion() is IMemberReferenceOperation memberReference && + // member.Equals(memberReference.Member)) + // { + // return true; + // } + // } + // } + + // return false; + //} + + //bool IsThrowNotImplementedProperty(IPropertySymbol property) + //{ + // using var _ = ArrayBuilder.GetInstance(out var accessors); + + // if (property.GetMethod != null) + // accessors.AddIfNotNull(InitializeParameterHelpers.GetAccessorBody(property.GetMethod, cancellationToken)); + + // if (property.SetMethod != null) + // accessors.AddIfNotNull(InitializeParameterHelpers.GetAccessorBody(property.SetMethod, cancellationToken)); + + // if (accessors.Count == 0) + // return false; + + // foreach (var group in accessors.GroupBy(node => node.SyntaxTree)) + // { + // var semanticModel = compilation.GetSemanticModel(group.Key); + // foreach (var accessorBody in accessors) + // { + // var operation = semanticModel.GetOperation(accessorBody, cancellationToken); + // if (operation is null) + // return false; + + // if (!operation.IsSingleThrowNotImplementedOperation()) + // return false; + // } + // } + + // return true; + //} + } + } +} diff --git a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs index 88ef3a4545535..f15835e074bb8 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs @@ -5,13 +5,17 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter @@ -138,5 +142,86 @@ public static bool TryConvertExpressionBodyToStatement( _ => throw ExceptionUtilities.UnexpectedValue(body), }; } + + public static SyntaxNode? GetAccessorBody(IMethodSymbol accessor, CancellationToken cancellationToken) + { + var node = accessor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + if (node is AccessorDeclarationSyntax accessorDeclaration) + return accessorDeclaration.ExpressionBody ?? (SyntaxNode?)accessorDeclaration.Body; + + // `int Age => ...;` + if (node is ArrowExpressionClauseSyntax arrowExpression) + return arrowExpression; + + return null; + } + + public static SyntaxNode RemoveThrowNotImplemented(SyntaxNode node) + { + if (node is PropertyDeclarationSyntax propertyDeclaration) + { + if (propertyDeclaration.ExpressionBody != null) + { + var result = propertyDeclaration + .WithExpressionBody(null) + .WithSemicolonToken(default) + .AddAccessorListAccessors(SyntaxFactory + .AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))) + .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia) + .WithAdditionalAnnotations(Formatter.Annotation); + return result; + } + + if (propertyDeclaration.AccessorList != null) + { + var accessors = propertyDeclaration.AccessorList.Accessors.Select(RemoveThrowNotImplemented); + return propertyDeclaration.WithAccessorList( + propertyDeclaration.AccessorList.WithAccessors(SyntaxFactory.List(accessors))); + } + } + + return node; + } + + private static AccessorDeclarationSyntax RemoveThrowNotImplemented(AccessorDeclarationSyntax accessorDeclaration) + { + var result = accessorDeclaration + .WithExpressionBody(null) + .WithBody(null) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + + return result.WithTrailingTrivia(accessorDeclaration.Body?.GetTrailingTrivia() ?? accessorDeclaration.SemicolonToken.TrailingTrivia); + } + + public static bool IsThrowNotImplementedProperty(Compilation compilation, IPropertySymbol property, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var accessors); + + if (property.GetMethod != null) + accessors.AddIfNotNull(GetAccessorBody(property.GetMethod, cancellationToken)); + + if (property.SetMethod != null) + accessors.AddIfNotNull(GetAccessorBody(property.SetMethod, cancellationToken)); + + if (accessors.Count == 0) + return false; + + foreach (var group in accessors.GroupBy(node => node.SyntaxTree)) + { + var semanticModel = compilation.GetSemanticModel(group.Key); + foreach (var accessorBody in accessors) + { + var operation = semanticModel.GetOperation(accessorBody, cancellationToken); + if (operation is null) + return false; + + if (!operation.IsSingleThrowNotImplementedOperation()) + return false; + } + } + + return true; + } } } diff --git a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs index 549a0daf2dc01..bdb55f63e0be7 100644 --- a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs +++ b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs @@ -49,6 +49,7 @@ internal static class PredefinedCodeRefactoringProviderNames public const string ImplementInterfaceExplicitly = nameof(ImplementInterfaceExplicitly); public const string ImplementInterfaceImplicitly = nameof(ImplementInterfaceImplicitly); public const string InitializeMemberFromParameter = nameof(InitializeMemberFromParameter); + public const string InitializeMemberFromPrimaryConstructorParameter = nameof(InitializeMemberFromPrimaryConstructorParameter); public const string InlineMethod = "Inline Method Code Action Provider"; public const string InlineTemporary = "Inline Temporary Code Action Provider"; public const string IntroduceLocalForExpression = nameof(IntroduceLocalForExpression); diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index a18678f60c305..7c0b437cd3baf 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -23,6 +23,8 @@ namespace Microsoft.CodeAnalysis.InitializeParameter { + using static InitializeParameterHelpersCore; + internal abstract class AbstractAddParameterCheckCodeRefactoringProvider< TTypeDeclarationSyntax, TParameterSyntax, @@ -198,7 +200,7 @@ private static bool ContainsNullCoalesceCheck( var operation = semanticModel.GetOperation(coalesceNode, cancellationToken); if (operation is ICoalesceOperation coalesceExpression) { - if (IsParameterReference(coalesceExpression.Value, parameter) && + if (InitializeParameterHelpersCore.IsParameterReference(coalesceExpression.Value, parameter) && syntaxFacts.IsThrowExpression(coalesceExpression.WhenNull.Syntax)) { return true; diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs index b0096d37d46d8..645d89bad1f30 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeMemberFromParameterCodeRefactoringProviderMemberCreation.cs @@ -27,6 +27,8 @@ namespace Microsoft.CodeAnalysis.InitializeParameter { + using static InitializeParameterHelpersCore; + internal abstract partial class AbstractInitializeMemberFromParameterCodeRefactoringProvider< TTypeDeclarationSyntax, TParameterSyntax, @@ -48,7 +50,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto protected abstract SyntaxNode RemoveThrowNotImplemented(SyntaxNode propertySyntax); protected abstract bool TryUpdateTupleAssignment(IBlockOperation? blockStatement, IParameterSymbol parameter, ISymbol fieldOrProperty, SyntaxEditor editor); - protected override Task> GetRefactoringsForAllParametersAsync( + protected sealed override Task> GetRefactoringsForAllParametersAsync( Document document, SyntaxNode functionDeclaration, IMethodSymbol method, IBlockOperation? blockStatementOpt, ImmutableArray listOfParameterNodes, TextSpan parameterSpan, CleanCodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) @@ -56,7 +58,7 @@ protected override Task> GetRefactoringsForAllParamet return SpecializedTasks.EmptyImmutableArray(); } - protected override async Task> GetRefactoringsForSingleParameterAsync( + protected sealed override async Task> GetRefactoringsForSingleParameterAsync( Document document, TParameterSyntax parameterSyntax, IParameterSymbol parameter, @@ -314,19 +316,6 @@ private IFieldSymbol CreateField( throw ExceptionUtilities.Unreachable(); } - private static string GenerateUniqueName(IParameterSymbol parameter, ImmutableArray parameterNameParts, NamingRule rule) - { - // Determine an appropriate name to call the new field. - var containingType = parameter.ContainingType; - var baseName = rule.NamingStyle.CreateName(parameterNameParts); - - // Ensure that the name is unique in the containing type so we - // don't stomp on an existing member. - var uniqueName = NameGenerator.GenerateUniqueName( - baseName, n => containingType.GetMembers(n).IsEmpty); - return uniqueName; - } - private IPropertySymbol CreateProperty( IParameterSymbol parameter, AccessibilityModifiersRequired accessibilityModifiersRequired, @@ -594,28 +583,6 @@ private static CodeGenerationContext GetAddContext( return CodeGenerationContext.Default; } - protected static ImmutableArray<(IParameterSymbol parameter, bool before)> GetSiblingParameters(IParameterSymbol parameter) - { - using var _ = ArrayBuilder<(IParameterSymbol, bool before)>.GetInstance(out var siblings); - - if (parameter.ContainingSymbol is IMethodSymbol method) - { - var parameterIndex = method.Parameters.IndexOf(parameter); - - // look for an existing assignment for a parameter that comes before us. - // If we find one, we'll add ourselves after that parameter check. - for (var i = parameterIndex - 1; i >= 0; i--) - siblings.Add((method.Parameters[i], before: true)); - - // look for an existing check for a parameter that comes before us. - // If we find one, we'll add ourselves after that parameter check. - for (var i = parameterIndex + 1; i < method.Parameters.Length; i++) - siblings.Add((method.Parameters[i], before: false)); - } - - return siblings.ToImmutable(); - } - private SyntaxNode? TryGetStatementToAddInitializationAfter( IParameterSymbol parameter, IBlockOperation? blockStatement) { @@ -709,25 +676,8 @@ protected static bool TryGetPartsOfTupleAssignmentOperation( } private static bool IsParameterReferenceOrCoalesceOfParameterReference( - IAssignmentOperation assignmentExpression, IParameterSymbol parameter) - { - if (IsParameterReference(assignmentExpression.Value, parameter)) - { - // We already have a member initialized with this parameter like: - // this.field = parameter - return true; - } - - if (assignmentExpression.Value.UnwrapImplicitConversion() is ICoalesceOperation coalesceExpression && - IsParameterReference(coalesceExpression.Value, parameter)) - { - // We already have a member initialized with this parameter like: - // this.field = parameter ?? ... - return true; - } - - return false; - } + IAssignmentOperation assignmentExpression, IParameterSymbol parameter) + => InitializeParameterHelpersCore.IsParameterReferenceOrCoalesceOfParameterReference(assignmentExpression.Value, parameter); private async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync( Document document, IParameterSymbol parameter, IBlockOperation? blockStatement, ImmutableArray rules, ImmutableArray parameterWords, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs index 3a958ead603e4..de9cee029c514 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractInitializeParameterCodeRefactoringProvider.cs @@ -85,8 +85,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (!TryGetParameterSymbol(selectedParameter, semanticModel, out var parameter, cancellationToken)) return; - var methodSymbol = (IMethodSymbol)parameter.ContainingSymbol; - if (methodSymbol.IsAbstract || + if (parameter.ContainingSymbol is not IMethodSymbol methodSymbol || + methodSymbol.IsAbstract || methodSymbol.IsExtern || methodSymbol.PartialImplementationPart != null || methodSymbol.ContainingType.TypeKind == TypeKind.Interface) @@ -140,7 +140,7 @@ static bool TryGetParameterSymbol( { parameter = (IParameterSymbol?)semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken); - return parameter != null && parameter.Name != ""; + return parameter is { Name: not "" }; } } @@ -184,10 +184,6 @@ protected bool CanOfferRefactoring( return true; } - protected static bool IsParameterReference(IOperation operation, IParameterSymbol parameter) - => operation.UnwrapImplicitConversion() is IParameterReferenceOperation parameterReference && - parameter.Equals(parameterReference.Parameter); - protected static bool ContainsParameterReference( SemanticModel semanticModel, IOperation condition, @@ -197,7 +193,7 @@ protected static bool ContainsParameterReference( foreach (var child in condition.Syntax.DescendantNodes().OfType()) { var childOperation = semanticModel.GetOperation(child, cancellationToken); - if (childOperation != null && IsParameterReference(childOperation, parameter)) + if (childOperation != null && InitializeParameterHelpersCore.IsParameterReference(childOperation, parameter)) return true; } diff --git a/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs b/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs new file mode 100644 index 0000000000000..96f9f42369135 --- /dev/null +++ b/src/Features/Core/Portable/InitializeParameter/InitializeParameterHelpersCore.cs @@ -0,0 +1,75 @@ +// 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.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.InitializeParameter +{ + internal static class InitializeParameterHelpersCore + { + public static ImmutableArray<(IParameterSymbol parameter, bool before)> GetSiblingParameters(IParameterSymbol parameter) + { + using var _ = ArrayBuilder<(IParameterSymbol, bool before)>.GetInstance(out var siblings); + + if (parameter.ContainingSymbol is IMethodSymbol method) + { + var parameterIndex = method.Parameters.IndexOf(parameter); + + // look for an existing assignment for a parameter that comes before us. + // If we find one, we'll add ourselves after that parameter check. + for (var i = parameterIndex - 1; i >= 0; i--) + siblings.Add((method.Parameters[i], before: true)); + + // look for an existing check for a parameter that comes before us. + // If we find one, we'll add ourselves after that parameter check. + for (var i = parameterIndex + 1; i < method.Parameters.Length; i++) + siblings.Add((method.Parameters[i], before: false)); + } + + return siblings.ToImmutable(); + } + + public static bool IsParameterReference(IOperation? operation, IParameterSymbol parameter) + => operation.UnwrapImplicitConversion() is IParameterReferenceOperation parameterReference && + parameter.Equals(parameterReference.Parameter); + + public static bool IsParameterReferenceOrCoalesceOfParameterReference( + IOperation? value, IParameterSymbol parameter) + { + if (IsParameterReference(value, parameter)) + { + // We already have a member initialized with this parameter like: + // this.field = parameter + return true; + } + + if (value.UnwrapImplicitConversion() is ICoalesceOperation coalesceExpression && + IsParameterReference(coalesceExpression.Value, parameter)) + { + // We already have a member initialized with this parameter like: + // this.field = parameter ?? ... + return true; + } + + return false; + } + + public static string GenerateUniqueName(IParameterSymbol parameter, ImmutableArray parameterNameParts, NamingRule rule) + { + // Determine an appropriate name to call the new field. + var containingType = parameter.ContainingType; + var baseName = rule.NamingStyle.CreateName(parameterNameParts); + + // Ensure that the name is unique in the containing type so we + // don't stomp on an existing member. + var uniqueName = NameGenerator.GenerateUniqueName( + baseName, n => containingType.GetMembers(n).IsEmpty); + return uniqueName; + } + } +} From 174d082f82f5b3ece1fcc422faf7c7363d8bd391 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 12:50:29 -0700 Subject: [PATCH 02/59] In progress --- ...nstructorParameterCodeRefactoringProvider.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index b6304e981b0c5..81ea56582f095 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -191,8 +191,8 @@ private async Task> HandleNoExistingFieldOrPropertyAs // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(semanticModel, typeDeclaration, parameter, cancellationToken); + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(compilation, parameter, cancellationToken); if (siblingFieldOrProperty is IFieldSymbol) { allActions.Add(fieldAction); @@ -205,7 +205,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs } var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions( - document, semanticModel, typeDeclaration, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); + document, compilation, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); if (allFieldsAction != null && allPropertiesAction != null) { @@ -226,8 +226,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs private (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( Document document, - SemanticModel semanticModel, - TypeDeclarationSyntax typeDeclaration, + Compilation compilation, IMethodSymbol method, ImmutableArray rules, AccessibilityModifiersRequired accessibilityModifiersRequired, @@ -235,13 +234,13 @@ private async Task> HandleNoExistingFieldOrPropertyAs CancellationToken cancellationToken) { var parameters = GetParametersWithoutAssociatedMembers( - semanticModel, typeDeclaration, rules, method, cancellationToken); + compilation, rules, method, cancellationToken); if (parameters.Length < 2) return default; var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, accessibilityModifiersRequired, rules)); - var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, accessibilityModifiersRequired, rules)); + var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, rules)); var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, @@ -267,7 +266,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs CodeGenerationOptionsProvider fallbackOptions) { var field = CreateField(parameter, accessibilityModifiersRequired, rules); - var property = CreateProperty(parameter, accessibilityModifiersRequired, rules); + var property = CreateProperty(parameter, rules); // we're generating the field or property, so we don't have to handle throwing versions of them. var isThrowNotImplementedProperty = false; @@ -573,7 +572,7 @@ private async Task AddSingleSymbolInitializationAsync( } } - return currentSolution.WithDocumentSyntaxRoot(document.Id, editor.GetChangedRoot()); + return editor.GetChangedSolution(); } private void AddAssignment( From a6831c28a372e3fb0100348ca9c09bb630a9cf6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:17:43 -0700 Subject: [PATCH 03/59] In progress --- ...tructorParameterCodeRefactoringProvider.cs | 211 +++++++++--------- .../InitializeParameterHelpers.cs | 40 ++-- 2 files changed, 120 insertions(+), 131 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 81ea56582f095..a7669a7f16044 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -7,14 +7,14 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; @@ -26,13 +26,13 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Naming; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter { using static InitializeParameterHelpers; using static InitializeParameterHelpersCore; + using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] internal sealed class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider @@ -421,10 +421,9 @@ private IPropertySymbol CreateProperty( throw ExceptionUtilities.Unreachable(); } - private async Task AddAllSymbolInitializationsAsync( + private static async Task AddAllSymbolInitializationsAsync( Document document, - SyntaxNode constructorDeclaration, - IBlockOperation? blockStatement, + TypeDeclarationSyntax typeDeclaration, ImmutableArray parameters, ImmutableArray fieldsOrProperties, CodeGenerationOptionsProvider fallbackOptions, @@ -438,11 +437,8 @@ private async Task AddAllSymbolInitializationsAsync( // Then find all the current data in that updated document and move onto the next pair. var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var nodesToTrack = new List { constructorDeclaration }; - if (blockStatement != null) - nodesToTrack.Add(blockStatement.Syntax); - var trackedRoot = root.TrackNodes(nodesToTrack); + var trackedRoot = root.TrackNodes(typeDeclaration); var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; for (var i = 0; i < parameters.Length; i++) @@ -455,18 +451,10 @@ private async Task AddAllSymbolInitializationsAsync( var currentCompilation = currentSemanticModel.Compilation; var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var currentConstructorDeclaration = currentRoot.GetCurrentNode(constructorDeclaration); - if (currentConstructorDeclaration == null) + var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); + if (currentTypeDeclaration == null) continue; - IBlockOperation? currentBlockStatement = null; - if (blockStatement != null) - { - currentBlockStatement = (IBlockOperation?)currentSemanticModel.GetOperation(currentRoot.GetCurrentNode(blockStatement.Syntax)!, cancellationToken); - if (currentBlockStatement == null) - continue; - } - var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); if (currentParameter == null) continue; @@ -475,8 +463,7 @@ private async Task AddAllSymbolInitializationsAsync( currentSolution = await AddSingleSymbolInitializationAsync( currentDocument, - currentConstructorDeclaration, - currentBlockStatement, + currentTypeDeclaration, currentParameter, fieldOrProperty, isThrowNotImplementedProperty: false, @@ -487,7 +474,7 @@ private async Task AddAllSymbolInitializationsAsync( return currentSolution; } - private async Task AddSingleSymbolInitializationAsync( + private static async Task AddSingleSymbolInitializationAsync( Document document, TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, @@ -496,30 +483,33 @@ private async Task AddSingleSymbolInitializationAsync( CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - var services = document.Project.Solution.Services; + var project = document.Project; + var solution = project.Solution; + var services = solution.Services; - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var editor = new SolutionEditor(document.Project.Solution); + var solutionEditor = new SolutionEditor(solution); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var codeGenerator = document.GetRequiredLanguageService(); - var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); - var generator = mainEditor.Generator; + // var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); if (fieldOrProperty.ContainingType == null) { - // We're generating a new field/property. Place into the containing type, - // ideally before/after a relevant existing member. - // - // Now add the field/property to this type. Use the 'ReplaceNode+callback' form - // so that nodes will be appropriate tracked and so we can then update the constructor - // below even after we've replaced the whole type with a new type. - // - // Note: We'll pass the appropriate options so that the new field/property - // is appropriate placed before/after an existing field/property. We'll try - // to preserve the same order for fields/properties that we have for the constructor - // parameters. - mainEditor.ReplaceNode( + // We're generating a new field/property. Place into the containing type, ideally before/after a + // relevant existing member. + var (sibling, siblingSyntax, addContext) = fieldOrProperty switch + { + IPropertySymbol => GetAddContext(compilation, parameter, cancellationToken), + IFieldSymbol => GetAddContext(compilation, parameter, cancellationToken), + _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), + }; + + var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; + + var editingDocument = solution.GetRequiredDocument(typeDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( typeDeclaration, (currentTypeDecl, _) => { @@ -527,14 +517,14 @@ private async Task AddSingleSymbolInitializationAsync( { return codeGenerator.AddProperty( currentTypeDecl, property, - codeGenerator.GetInfo(GetAddContext(compilation, parameter, typeDeclaration, cancellationToken), options, root.SyntaxTree.Options), + codeGenerator.GetInfo(addContext, options, root.SyntaxTree.Options), cancellationToken); } else if (fieldOrProperty is IFieldSymbol field) { return codeGenerator.AddField( currentTypeDecl, field, - codeGenerator.GetInfo(GetAddContext(compilation, parameter, typeDeclaration, cancellationToken), options, root.SyntaxTree.Options), + codeGenerator.GetInfo(addContext, options, root.SyntaxTree.Options), cancellationToken); } else @@ -543,72 +533,83 @@ private async Task AddSingleSymbolInitializationAsync( } }); } - - AddAssignment(constructorDeclaration, blockStatement, parameter, fieldOrProperty, editor); - - // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. - var currentSolution = document.Project.Solution; - if (isThrowNotImplementedProperty) + else { - var declarationService = document.GetRequiredLanguageService(); - var propertySyntax = await declarationService.GetDeclarations(fieldOrProperty)[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); - var withoutThrowNotImplemented = InitializeParameterHelpers.RemoveThrowNotImplemented(propertySyntax); - - if (propertySyntax.SyntaxTree == root.SyntaxTree) + // We're updating an exiting field/prop. + if (fieldOrProperty is IPropertySymbol property) { - // Edit to the same file, just update this editor. - mainEditor.ReplaceNode(propertySyntax, withoutThrowNotImplemented); + foreach (var syntaxRef in property.DeclaringSyntaxReferences) + { + if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) + { + var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. + var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; + editor.ReplaceNode( + propertyDeclaration, + newPropertyDeclaration.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + } + } } - else + else if (fieldOrProperty is IFieldSymbol field) { - // edit to a different file. Just replace things directly in there. - var otherDocument = currentSolution.GetDocument(propertySyntax.SyntaxTree); - if (otherDocument != null) + foreach (var syntaxRef in field.DeclaringSyntaxReferences) { - var otherRoot = await propertySyntax.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentSyntaxRoot( - otherDocument.Id, otherRoot.ReplaceNode(propertySyntax, withoutThrowNotImplemented)); + if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) + { + var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + variableDeclarator, + variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + break; + } } } + else + { + throw ExceptionUtilities.Unreachable(); + } } - return editor.GetChangedSolution(); + return solutionEditor.GetChangedSolution(); } - private void AddAssignment( - IParameterSymbol parameter, - ISymbol fieldOrProperty, - SyntaxEditor editor) - { - //// First see if the user has `(_x, y) = (x, y);` and attempt to update that. - //if (TryUpdateTupleAssignment(blockStatement, parameter, fieldOrProperty, editor)) - // return; - - var generator = editor.Generator; - - // Now that we've added any potential members, create an assignment between it - // and the parameter. - var initializationStatement = (StatementSyntax)generator.ExpressionStatement( - generator.AssignmentStatement( - generator.MemberAccessExpression( - generator.ThisExpression(), - generator.IdentifierName(fieldOrProperty.Name)), - generator.IdentifierName(parameter.Name))); - - // Attempt to place the initialization in a good location in the constructor - // We'll want to keep initialization statements in the same order as we see - // parameters for the constructor. - var statementToAddAfter = TryGetStatementToAddInitializationAfter(parameter, blockStatement); - - InsertStatement(editor, constructorDeclaration, returnsVoid: true, statementToAddAfter, initializationStatement); - } + //private void AddAssignment( + // IParameterSymbol parameter, + // ISymbol fieldOrProperty, + // SyntaxEditor editor) + //{ + // //// First see if the user has `(_x, y) = (x, y);` and attempt to update that. + // //if (TryUpdateTupleAssignment(blockStatement, parameter, fieldOrProperty, editor)) + // // return; + + // var generator = editor.Generator; + + // // Now that we've added any potential members, create an assignment between it + // // and the parameter. + // var initializationStatement = (StatementSyntax)generator.ExpressionStatement( + // generator.AssignmentStatement( + // generator.MemberAccessExpression( + // generator.ThisExpression(), + // generator.IdentifierName(fieldOrProperty.Name)), + // generator.IdentifierName(parameter.Name))); + + // // Attempt to place the initialization in a good location in the constructor + // // We'll want to keep initialization statements in the same order as we see + // // parameters for the constructor. + // var statementToAddAfter = TryGetStatementToAddInitializationAfter(parameter, blockStatement); + + // InsertStatement(editor, constructorDeclaration, returnsVoid: true, statementToAddAfter, initializationStatement); + //} - private static CodeGenerationContext GetAddContext( + private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext( Compilation compilation, IParameterSymbol parameter, - TypeDeclarationSyntax typeDeclaration, CancellationToken cancellationToken) - where TSymbol : ISymbol + where TSymbol : class, ISymbol { foreach (var (sibling, before) in GetSiblingParameters(parameter)) { @@ -616,28 +617,16 @@ private static CodeGenerationContext GetAddContext( compilation, sibling, out var fieldOrProperty, cancellationToken); if (initializer != null && - fieldOrProperty is TSymbol symbol) + fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) { - var symbolSyntax = symbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); - if (symbolSyntax.Ancestors().Contains(typeDeclaration)) - { - if (before) - { - // Found an existing field/property that corresponds to a preceding parameter. - // Place ourselves directly after it. - return new CodeGenerationContext(afterThisLocation: symbolSyntax.GetLocation()); - } - else - { - // Found an existing field/property that corresponds to a following parameter. - // Place ourselves directly before it. - return new CodeGenerationContext(beforeThisLocation: symbolSyntax.GetLocation()); - } - } + var syntax = syntaxReference.GetSyntax(cancellationToken); + return (symbol, syntax, before + ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) + : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); } } - return CodeGenerationContext.Default; + return (symbol: null, syntax: null, CodeGenerationContext.Default); } //private SyntaxNode? TryGetStatementToAddInitializationAfter( diff --git a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs index f15835e074bb8..edc50e31109fe 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/InitializeParameterHelpers.cs @@ -157,31 +157,31 @@ public static bool TryConvertExpressionBodyToStatement( } public static SyntaxNode RemoveThrowNotImplemented(SyntaxNode node) + => node is PropertyDeclarationSyntax propertyDeclaration ? RemoveThrowNotImplemented(propertyDeclaration) : node; + + public static PropertyDeclarationSyntax RemoveThrowNotImplemented(PropertyDeclarationSyntax propertyDeclaration) { - if (node is PropertyDeclarationSyntax propertyDeclaration) + if (propertyDeclaration.ExpressionBody != null) { - if (propertyDeclaration.ExpressionBody != null) - { - var result = propertyDeclaration - .WithExpressionBody(null) - .WithSemicolonToken(default) - .AddAccessorListAccessors(SyntaxFactory - .AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))) - .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia) - .WithAdditionalAnnotations(Formatter.Annotation); - return result; - } + var result = propertyDeclaration + .WithExpressionBody(null) + .WithSemicolonToken(default) + .AddAccessorListAccessors(SyntaxFactory + .AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))) + .WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia) + .WithAdditionalAnnotations(Formatter.Annotation); + return result; + } - if (propertyDeclaration.AccessorList != null) - { - var accessors = propertyDeclaration.AccessorList.Accessors.Select(RemoveThrowNotImplemented); - return propertyDeclaration.WithAccessorList( - propertyDeclaration.AccessorList.WithAccessors(SyntaxFactory.List(accessors))); - } + if (propertyDeclaration.AccessorList != null) + { + var accessors = propertyDeclaration.AccessorList.Accessors.Select(RemoveThrowNotImplemented); + return propertyDeclaration.WithAccessorList( + propertyDeclaration.AccessorList.WithAccessors(SyntaxFactory.List(accessors))); } - return node; + return propertyDeclaration; } private static AccessorDeclarationSyntax RemoveThrowNotImplemented(AccessorDeclarationSyntax accessorDeclaration) From ed83caa0bc1225f775d6f58b917d06f8b9398a7b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:19:55 -0700 Subject: [PATCH 04/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index a7669a7f16044..2730d4d9a278d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -3,11 +3,9 @@ // 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.Diagnostics; -using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -21,8 +19,6 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InitializeParameter; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Naming; @@ -57,10 +53,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration }) return; - var generator = SyntaxGenerator.GetGenerator(document); // var parameterNodes = generator.GetParameters(functionDeclaration); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); // we can't just call GetDeclaredSymbol on functionDeclaration because it could an anonymous function, // so first we have to get the parameter symbol and then its containing method symbol @@ -88,7 +82,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // Ok. Looks like the selected parameter could be refactored. Defer to subclass to // actually determine if there are any viable refactorings here. var refactorings = await GetRefactoringsForSingleParameterAsync( - document, typeDeclaration, selectedParameter, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); + document, typeDeclaration, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); context.RegisterRefactorings(refactorings, context.Span); // } @@ -130,7 +124,6 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte private async Task> GetRefactoringsForSingleParameterAsync( Document document, TypeDeclarationSyntax typeDeclaration, - ParameterSyntax parameterSyntax, IParameterSymbol parameter, IMethodSymbol method, CleanCodeGenerationOptionsProvider fallbackOptions, @@ -162,7 +155,7 @@ private async Task> GetRefactoringsForSingleParameter if (fieldOrProperty != null) { return HandleExistingFieldOrProperty( - document, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions); + document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions); } else { @@ -171,7 +164,7 @@ private async Task> GetRefactoringsForSingleParameter } } - private async Task> HandleNoExistingFieldOrPropertyAsync( + private static async Task> HandleNoExistingFieldOrPropertyAsync( Document document, TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, @@ -187,7 +180,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions( - document, parameter, constructorDeclaration, blockStatement, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); + document, typeDeclaration, parameter, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property @@ -205,7 +198,7 @@ private async Task> HandleNoExistingFieldOrPropertyAs } var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions( - document, compilation, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); + document, compilation, typeDeclaration, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); if (allFieldsAction != null && allPropertiesAction != null) { @@ -224,9 +217,10 @@ private async Task> HandleNoExistingFieldOrPropertyAs return allActions.ToImmutable(); } - private (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( + private static (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( Document document, Compilation compilation, + TypeDeclarationSyntax typeDeclaration, IMethodSymbol method, ImmutableArray rules, AccessibilityModifiersRequired accessibilityModifiersRequired, @@ -245,22 +239,21 @@ private async Task> HandleNoExistingFieldOrPropertyAs var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, c => AddAllSymbolInitializationsAsync( - document, constructorDeclaration, blockStatement, parameters, fields, fallbackOptions, c), + document, typeDeclaration, parameters, fields, fallbackOptions, c), nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, c => AddAllSymbolInitializationsAsync( - document, constructorDeclaration, blockStatement, parameters, properties, fallbackOptions, c), + document, typeDeclaration, parameters, properties, fallbackOptions, c), nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); return (allFieldsAction, allPropertiesAction); } - private (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( + private static (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( Document document, + TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, - SyntaxNode constructorDeclaration, - IBlockOperation? blockStatement, ImmutableArray rules, AccessibilityModifiersRequired accessibilityModifiersRequired, CodeGenerationOptionsProvider fallbackOptions) @@ -273,11 +266,11 @@ private async Task> HandleNoExistingFieldOrPropertyAs var fieldAction = CodeAction.Create( string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - c => AddSingleSymbolInitializationAsync(document, constructorDeclaration, blockStatement, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), + c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), nameof(FeaturesResources.Create_and_assign_field_0) + "_" + field.Name); var propertyAction = CodeAction.Create( string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - c => AddSingleSymbolInitializationAsync(document, constructorDeclaration, blockStatement, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), + c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), nameof(FeaturesResources.Create_and_assign_property_0) + "_" + property.Name); return (fieldAction, propertyAction); @@ -307,8 +300,9 @@ private static ImmutableArray GetParametersWithoutAssociatedMe return result.ToImmutable(); } - private ImmutableArray HandleExistingFieldOrProperty( + private static ImmutableArray HandleExistingFieldOrProperty( Document document, + TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, ISymbol fieldOrProperty, bool isThrowNotImplementedProperty, @@ -326,7 +320,7 @@ private ImmutableArray HandleExistingFieldOrProperty( return ImmutableArray.Create(CodeAction.Create( title, cancellationToken => AddSingleSymbolInitializationAsync( - document, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), + document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), title)); } @@ -383,7 +377,7 @@ private static IFieldSymbol CreateField( throw ExceptionUtilities.Unreachable(); } - private IPropertySymbol CreateProperty( + private static IPropertySymbol CreateProperty( IParameterSymbol parameter, ImmutableArray rules) { From f21ab8056fdbb97c19200a22975e47ed862a486b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:20:46 -0700 Subject: [PATCH 05/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 209 ------------------ 1 file changed, 209 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 2730d4d9a278d..17d7ef1d3e292 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -71,54 +71,11 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - //// We shouldn't offer a refactoring if the compilation doesn't contain the ArgumentNullException type, - //// as we use it later on in our computations. - //var argumentNullExceptionType = typeof(ArgumentNullException).FullName; - //if (argumentNullExceptionType is null || semanticModel.Compilation.GetTypeByMetadataName(argumentNullExceptionType) is null) - // return; - - //if (CanOfferRefactoring(functionDeclaration, semanticModel, syntaxFacts, cancellationToken, out var blockStatementOpt)) - //{ - // Ok. Looks like the selected parameter could be refactored. Defer to subclass to - // actually determine if there are any viable refactorings here. var refactorings = await GetRefactoringsForSingleParameterAsync( document, typeDeclaration, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); context.RegisterRefactorings(refactorings, context.Span); - // } - - //// List with parameterNodes that pass all checks - //using var _ = ArrayBuilder.GetInstance(out var listOfPotentiallyValidParametersNodes); - //foreach (var parameterNode in parameterNodes) - //{ - // if (!TryGetParameterSymbol(parameterNode, semanticModel, out parameter, cancellationToken)) - // return; - - // // Update the list of valid parameter nodes - // listOfPotentiallyValidParametersNodes.Add(parameterNode); - //} - - //if (listOfPotentiallyValidParametersNodes.Count > 1) - //{ - // // Looks like we can offer a refactoring for more than one parameter. Defer to subclass to - // // actually determine if there are any viable refactorings here. - // var refactorings = await GetRefactoringsForAllParametersAsync( - // document, functionDeclaration, methodSymbol, blockStatementOpt, - // listOfPotentiallyValidParametersNodes.ToImmutable(), selectedParameter.Span, context.Options, cancellationToken).ConfigureAwait(false); - // context.RegisterRefactorings(refactorings, context.Span); - //} return; - - //static bool TryGetParameterSymbol( - // SyntaxNode parameterNode, - // SemanticModel semanticModel, - // [NotNullWhen(true)] out IParameterSymbol? parameter, - // CancellationToken cancellationToken) - //{ - // parameter = (IParameterSymbol?)semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken); - - // return parameter is { Name: not "" }; - //} } private async Task> GetRefactoringsForSingleParameterAsync( @@ -355,14 +312,6 @@ private static IFieldSymbol CreateField( var accessibilityLevel = accessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault ? Accessibility.NotApplicable : Accessibility.Private; - //if (accessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault) - //{ - // var defaultAccessibility = DetermineDefaultFieldAccessibility(parameter.ContainingType); - // if (defaultAccessibility == Accessibility.Private) - // { - // accessibilityLevel = Accessibility.NotApplicable; - // } - //} return CodeGenerationSymbolFactory.CreateFieldSymbol( default, @@ -571,34 +520,6 @@ private static async Task AddSingleSymbolInitializationAsync( return solutionEditor.GetChangedSolution(); } - //private void AddAssignment( - // IParameterSymbol parameter, - // ISymbol fieldOrProperty, - // SyntaxEditor editor) - //{ - // //// First see if the user has `(_x, y) = (x, y);` and attempt to update that. - // //if (TryUpdateTupleAssignment(blockStatement, parameter, fieldOrProperty, editor)) - // // return; - - // var generator = editor.Generator; - - // // Now that we've added any potential members, create an assignment between it - // // and the parameter. - // var initializationStatement = (StatementSyntax)generator.ExpressionStatement( - // generator.AssignmentStatement( - // generator.MemberAccessExpression( - // generator.ThisExpression(), - // generator.IdentifierName(fieldOrProperty.Name)), - // generator.IdentifierName(parameter.Name))); - - // // Attempt to place the initialization in a good location in the constructor - // // We'll want to keep initialization statements in the same order as we see - // // parameters for the constructor. - // var statementToAddAfter = TryGetStatementToAddInitializationAfter(parameter, blockStatement); - - // InsertStatement(editor, constructorDeclaration, returnsVoid: true, statementToAddAfter, initializationStatement); - //} - private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext( Compilation compilation, IParameterSymbol parameter, @@ -623,64 +544,12 @@ private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext conte return (symbol: null, syntax: null, CodeGenerationContext.Default); } - //private SyntaxNode? TryGetStatementToAddInitializationAfter( - // IParameterSymbol parameter, IBlockOperation? blockStatement) - //{ - // // look for an existing assignment for a parameter that comes before/after us. - // // If we find one, we'll add ourselves before/after that parameter check. - // foreach (var (sibling, before) in GetSiblingParameters(parameter)) - // { - // var statement = TryFindFieldOrPropertyAssignmentStatement(sibling, blockStatement); - // if (statement != null) - // { - // if (before) - // { - // return statement.Syntax; - // } - // else - // { - // var statementIndex = blockStatement!.Operations.IndexOf(statement); - // return statementIndex > 0 ? blockStatement.Operations[statementIndex - 1].Syntax : null; - // } - // } - // } - - // // We couldn't find a reasonable location for the new initialization statement. - // // Just place ourselves after the last statement in the constructor. - // return TryGetLastStatement(blockStatement); - //} - private static IOperation? TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, CancellationToken cancellationToken) => TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); - //protected static bool TryGetPartsOfTupleAssignmentOperation( - // IOperation operation, - // [NotNullWhen(true)] out ITupleOperation? targetTuple, - // [NotNullWhen(true)] out ITupleOperation? valueTuple) - //{ - // if (operation is IExpressionStatementOperation - // { - // Operation: IDeconstructionAssignmentOperation - // { - // Target: ITupleOperation targetTupleTemp, - // Value: IConversionOperation { Operand: ITupleOperation valueTupleTemp }, - // } - // } && - // targetTupleTemp.Elements.Length == valueTupleTemp.Elements.Length) - // { - // targetTuple = targetTupleTemp; - // valueTuple = valueTupleTemp; - // return true; - // } - - // targetTuple = null; - // valueTuple = null; - // return false; - //} - private static IOperation? TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, @@ -723,36 +592,6 @@ private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext conte } } - //if (blockStatement != null) - //{ - // var containingType = parameter.ContainingType; - // foreach (var statement in blockStatement.Operations) - // { - // // look for something of the form: "this.s = s" or "this.s = s ?? ..." - // if (IsFieldOrPropertyAssignment(statement, containingType, out var assignmentExpression, out fieldOrProperty) && - // IsParameterReferenceOrCoalesceOfParameterReference(assignmentExpression, parameter)) - // { - // return statement; - // } - - // //// look inside the form `(this.s, this.t) = (s, t)` - // //if (TryGetPartsOfTupleAssignmentOperation(statement, out var targetTuple, out var valueTuple)) - // //{ - // // for (int i = 0, n = targetTuple.Elements.Length; i < n; i++) - // // { - // // var target = targetTuple.Elements[i]; - // // var value = valueTuple.Elements[i]; - - // // if (IsFieldOrPropertyReference(target, containingType, out fieldOrProperty) && - // // IsParameterReference(value, parameter)) - // // { - // // return statement; - // // } - // // } - // //} - // } - //} - fieldOrProperty = null; return null; } @@ -813,54 +652,6 @@ private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext conte // Couldn't find any existing member. Just return nothing so we can offer to // create a member for them. return default; - - //static bool ContainsMemberAssignment(IBlockOperation? blockStatement, ISymbol member) - //{ - // if (blockStatement != null) - // { - // foreach (var statement in blockStatement.Operations) - // { - // if (IsFieldOrPropertyAssignment(statement, member.ContainingType, out var assignmentExpression) && - // assignmentExpression.Target.UnwrapImplicitConversion() is IMemberReferenceOperation memberReference && - // member.Equals(memberReference.Member)) - // { - // return true; - // } - // } - // } - - // return false; - //} - - //bool IsThrowNotImplementedProperty(IPropertySymbol property) - //{ - // using var _ = ArrayBuilder.GetInstance(out var accessors); - - // if (property.GetMethod != null) - // accessors.AddIfNotNull(InitializeParameterHelpers.GetAccessorBody(property.GetMethod, cancellationToken)); - - // if (property.SetMethod != null) - // accessors.AddIfNotNull(InitializeParameterHelpers.GetAccessorBody(property.SetMethod, cancellationToken)); - - // if (accessors.Count == 0) - // return false; - - // foreach (var group in accessors.GroupBy(node => node.SyntaxTree)) - // { - // var semanticModel = compilation.GetSemanticModel(group.Key); - // foreach (var accessorBody in accessors) - // { - // var operation = semanticModel.GetOperation(accessorBody, cancellationToken); - // if (operation is null) - // return false; - - // if (!operation.IsSingleThrowNotImplementedOperation()) - // return false; - // } - // } - - // return true; - //} } } } From e43c28cebe26e4f2ff341a0c9ef91d549ece239e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:21:25 -0700 Subject: [PATCH 06/59] Make static --- ...rFromPrimaryConstructorParameterCodeRefactoringProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 17d7ef1d3e292..980bfc3f2fb09 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -78,7 +78,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - private async Task> GetRefactoringsForSingleParameterAsync( + private static async Task> GetRefactoringsForSingleParameterAsync( Document document, TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, @@ -596,7 +596,7 @@ private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext conte return null; } - private async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync( + private static async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync( Document document, IParameterSymbol parameter, ImmutableArray rules, ImmutableArray parameterWords, CancellationToken cancellationToken) { // Look for a field/property that really looks like it corresponds to this parameter. From a4eb8d5edbcc300f85e81e8d70ec0b57d6b6387a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:27:51 -0700 Subject: [PATCH 07/59] before tests --- ...berFromParameterCodeRefactoringProvider.cs | 3 --- ...tructorParameterCodeRefactoringProvider.cs | 26 ++++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs index 5c074211ac31d..337d218330f4d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromParameterCodeRefactoringProvider.cs @@ -5,18 +5,15 @@ using System.Collections.Generic; using System.Composition; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter { - using static InitializeParameterHelpers; using static InitializeParameterHelpersCore; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromParameter), Shared] diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 980bfc3f2fb09..e368ac84e8f96 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -62,7 +62,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (parameter?.Name is null or "") return; - if (parameter.ContainingSymbol is not IMethodSymbol methodSymbol || + if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } methodSymbol || methodSymbol.IsAbstract || methodSymbol.IsExtern || methodSymbol.PartialImplementationPart != null || @@ -86,10 +86,6 @@ private static async Task> GetRefactoringsForSinglePa CleanCodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - // Only supported for constructor parameters. - if (method.MethodKind != MethodKind.Constructor) - return ImmutableArray.Empty; - // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -314,10 +310,12 @@ private static IFieldSymbol CreateField( : Accessibility.Private; return CodeGenerationSymbolFactory.CreateFieldSymbol( - default, + attributes: default, accessibilityLevel, DeclarationModifiers.ReadOnly, - parameter.Type, uniqueName); + parameter.Type, + uniqueName, + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); } } @@ -341,12 +339,13 @@ private static IPropertySymbol CreateProperty( var accessibilityLevel = Accessibility.Public; var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( - default, + attributes: default, Accessibility.Public, - default); + statements: default); return CodeGenerationSymbolFactory.CreatePropertySymbol( - default, + containingType: null, + attributes: default, accessibilityLevel, new DeclarationModifiers(), parameter.Type, @@ -355,7 +354,8 @@ private static IPropertySymbol CreateProperty( name: uniqueName, parameters: default, getMethod: getMethod, - setMethod: null); + setMethod: null, + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); } } @@ -492,7 +492,9 @@ private static async Task AddSingleSymbolInitializationAsync( var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; editor.ReplaceNode( propertyDeclaration, - newPropertyDeclaration.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + newPropertyDeclaration.WithoutTrailingTrivia() + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) + .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); } } } From 522846329a4d325cca12107e22339f727983e16c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:55:48 -0700 Subject: [PATCH 08/59] Tests --- ...tructorParameterCodeRefactoringProvider.cs | 5 +- ...berFromPrimaryConstructorParameterTests.cs | 1887 +++++++++++++++++ .../CodeGeneration/PropertyGenerator.cs | 3 +- 3 files changed, 1891 insertions(+), 4 deletions(-) create mode 100644 src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index e368ac84e8f96..4f42300967244 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -2,10 +2,10 @@ // 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.Collections.Immutable; using System.Composition; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -34,7 +33,7 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter internal sealed class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider { [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider() { } diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs new file mode 100644 index 0000000000000..cb385b17ae5f4 --- /dev/null +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -0,0 +1,1887 @@ +// 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.CodeRefactorings; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.CSharp.InitializeParameter; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; +using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InitializeParameter +{ + [Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] + public partial class InitializeMemberFromPrimaryConstructorParameterTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider(); + + private readonly NamingStylesTestOptionSets options = new NamingStylesTestOptionSets(LanguageNames.CSharp); + + [Fact] + public async Task TestInitializeFieldWithSameName() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private string s; + } + """, + """ + class C(string s) + { + private string s = s; + } + """); + } + + [Fact] + public async Task TestEndOfParameter1() + { + await TestInRegularAndScript1Async( + """ + class C(string s[||]) + { + private string s; + } + """, + """ + class C(string s) + { + private string s = s; + } + """); + } + + [Fact] + public async Task TestEndOfParameter2() + { + await TestInRegularAndScript1Async( + """ + class C(string s[||], string t) + { + private string s; + } + """, + """ + class C(string s, string t) + { + private string s = s; + } + """); + } + + [Fact] + public async Task TestInitializeFieldWithUnderscoreName() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private string _s; + } + """, + """ + class C(string s) + { + private string _s = s; + } + """); + } + + [Fact] + public async Task TestInitializeWritableProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private string S { get; } + } + """, + """ + class C(string s) + { + private string S { get; } = s; + } + """); + } + + [Fact] + public async Task TestInitializeFieldWithDifferentName() + { + await TestInRegularAndScriptAsync( + """ + class C([||]string s) + { + private string t; + } + """, + """ + class C(string s) + { + private string t; + + public string S { get; } = s; + } + """); + } + + [Fact] + public async Task TestInitializeNonWritableProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private string S => null; + } + """, + """ + class C(string s) + { + private string S => null; + + public string S1 { get; } = s; + } + """); + } + + [Fact] + public async Task TestInitializeDoesNotUsePropertyWithUnrelatedName() + { + await TestInRegularAndScriptAsync( + """ + class C([||]string s) + { + private string T { get; } + } + """, + """ + class C(string s) + { + private string T { get; } + public string S { get; } = s; + } + """); + } + + [Fact] + public async Task TestInitializeFieldWithWrongType1() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private int s; + } + """, + """ + class C(string s) + { + private int s; + + public string S { get; } = s; + } + """); + } + + [Fact] + public async Task TestInitializeFieldWithWrongType2() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private int s; + } + """, + """ + class C(string s) + { + private readonly string s1 = s; + private int s; + } + """, index: 1); + } + + [Fact] + public async Task TestInitializeFieldWithConvertibleType() + { + await TestInRegularAndScriptAsync( + """ + class C([||]string s) + { + private object s; + } + """, + """ + class C(string s) + { + private object s = s; + } + """); + } + + [Fact] + public async Task TestWhenAlreadyInitialized1() + { + await TestMissingInRegularAndScriptAsync( + """ + class C([||]string s) + { + private int s; + private int x = s; + } + """); + } + + [Fact] + public async Task TestWhenAlreadyInitialized2() + { + await TestMissingInRegularAndScriptAsync( + """ + class C([||]string s) + { + private int s; + private int x = s ?? throw new Exception(); + } + """); + } + + [Fact] + public async Task TestWhenAlreadyInitialized3() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private int s = 0; + } + """, + + """ + class C([||]string s) + { + private int s = 0; + + public string S { get; } = s; + } + """); + } + + [Fact] + public async Task TestInsertionLocation1() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s, string t) + { + private string s; + private string t = t; + + public C + { + this.t = t; + } + } + """, + """ + class C(string s, string t) + { + private string s = s; + private string t = t; + } + """); + } + + [Fact] + public async Task TestInsertionLocation2() + { + await TestInRegularAndScript1Async( + """ + class C(string s, [||]string t) + { + private string s = s; + private string t; + } + """, + """ + class C(string s, string t) + { + private string s = s; + private string t = t; + } + """); + } + + //[Fact] + //public async Task TestInsertionLocation3() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private string s; + + // public C([||]string s) + // { + // if (true) { } + // } + // } + // """, + // """ + // class C + // { + // private string s; + + // public C(string s) + // { + // if (true) { } + + // this.s = s; + // } + // } + // """); + //} + + [Fact] + public async Task TestNotInMethod() + { + await TestMissingInRegularAndScriptAsync( + """ + class C + { + private string s; + + public void M([||]string s) + { + } + } + """); + } + + //[Fact] + //public async Task TestInsertionLocation4() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private string s; + // private string t; + + // public C(string s, [||]string t) + // => this.s = s; + // } + // """, + // """ + // class C + // { + // private string s; + // private string t; + + // public C(string s, string t) + // { + // this.s = s; + // this.t = t; + // } + // } + // """); + //} + + //[Fact] + //public async Task TestInsertionLocation5() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private string s; + // private string t; + + // public C([||]string s, string t) + // => this.t = t; + // } + // """, + // """ + // class C + // { + // private string s; + // private string t; + + // public C(string s, string t) + // { + // this.s = s; + // this.t = t; + // } + // } + // """); + //} + + [Fact] + public async Task TestInsertionLocation6() + { + await TestInRegularAndScript1Async( + """ + class C(string s, [||]string t) + { + public string S { get; } = s; + } + """, + """ + class C(string s, string t) + { + public string S { get; } = s; + public string T { get; } = t; + } + """); + } + + [Fact] + public async Task TestInsertionLocation7() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s, string t) + { + public string T { get; } = t; + } + """, + """ + class C(string s, string t) + { + public string S { get; } = s; + public string T { get; } = t; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/19956")] + public async Task TestNoBlock1() + { + await TestInRegularAndScript1Async( + """ + class C(string s[||]) + """, + """ + class C(string s) + { + public string S { get; } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/19956")] + public async Task TestNoBlock2() + { + await TestInRegularAndScript1Async( + """ + class C(string s[||]) + """, + """ + class C(string s) + { + private readonly string s = s; + } + """, + index: 1); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/29190")] + public async Task TestInitializeFieldWithParameterNameSelected1() + { + await TestInRegularAndScript1Async( + """ + class C(string [|s|]) + { + private string s; + } + """, + """ + class C(string s) + { + private string s = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/29190")] + public async Task TestInitializeField_ParameterNameSelected2() + { + await TestInRegularAndScript1Async( + """ + class C(string [|s|], int i) + { + private string s; + } + """, + """ + class C(string s, int i) + { + private string s = s; + } + """); + } + + [Fact] + public async Task TestInitializeClassProperty_RequiredAccessibilityOmitIfDefault() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + + public int Test2 { get; } = test2; + } + """, index: 0, parameters: OmitIfDefault_Warning); + } + + [Fact] + public async Task TestInitializeClassProperty_RequiredAccessibilityNever() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + + public int Test2 { get; } = test2; + } + """, index: 0, parameters: Never_Warning); + } + + [Fact] + public async Task TestInitializeClassProperty_RequiredAccessibilityAlways() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + + public int Test2 { get; } = test2; + } + """, index: 0, parameters: Always_Warning); + } + + [Fact] + public async Task TestInitializeClassField_RequiredAccessibilityOmitIfDefault() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + readonly int test2 = test2; + } + """, index: 1, parameters: OmitIfDefault_Warning); + } + + [Fact] + public async Task TestInitializeClassField_RequiredAccessibilityNever() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + readonly int test2 = test2; + } + """, index: 1, parameters: Never_Warning); + } + + [Fact] + public async Task TestInitializeClassField_RequiredAccessibilityAlways() + { + await TestInRegularAndScript1Async( + """ + class C(int test, int [|test2|]) + { + readonly int test = 5; + } + """, + """ + class C(int test, int test2) + { + readonly int test = 5; + private readonly int test2 = test2; + } + """, index: 1, parameters: Always_Warning); + } + + [Fact] + public async Task TestInitializeStructProperty_RequiredAccessibilityOmitIfDefault() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + public int Test { get; } = test; + } + """, index: 0, parameters: OmitIfDefault_Warning); + } + + [Fact] + public async Task TestInitializeStructProperty_RequiredAccessibilityNever() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + public int Test { get; } = test; + } + """, index: 0, parameters: Never_Warning); + } + + [Fact] + public async Task TestInitializeStructProperty_RequiredAccessibilityAlways() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + public int Test { get; } = test; + } + """, index: 0, parameters: Always_Warning); + } + + [Fact] + public async Task TestInitializeStructField_RequiredAccessibilityOmitIfDefault() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + readonly int test = test; + } + """, index: 1, parameters: OmitIfDefault_Warning); + } + + [Fact] + public async Task TestInitializeStructField_RequiredAccessibilityNever() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + readonly int test = test; + } + """, index: 1, parameters: Never_Warning); + } + + [Fact] + public async Task TestInitializeStructField_RequiredAccessibilityAlways() + { + await TestInRegularAndScript1Async( + """ + struct S(int [|test|]) + { + } + """, + """ + struct S(int test) + { + private readonly int test = test; + } + """, index: 1, parameters: Always_Warning); + } + + [Fact] + public async Task TestNoParameterNamingStyle_CreateAndInitField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + } + """, + """ + class C(string s) + { + private readonly string _s = s; + } + """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } + + [Fact] + public async Task TestCommonParameterNamingStyle_CreateAndInitField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_s) + { + } + """, + """ + class C(string t_s) + { + private readonly string _s = t_s; + } + """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } + + [Fact] + public async Task TestSpecifiedParameterNamingStyle_CreateAndInitField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_s_End) + { + } + """, + """ + class C(string p_s_End) + { + private readonly string _s = p_s_End + } + """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle_CreateAndInitField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_p_s_End) + { + } + """, + """ + class C(string t_p_s_End) + { + private readonly string _s = t_p_s_End; + } + """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle2_CreateAndInitField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_t_s) + { + } + """, + """ + class C([||]string p_t_s) + { + private readonly string _s = p_t_s; + } + """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefix))); + } + + [Fact] + public async Task TestNoParameterNamingStyle_CreateAndInitProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + } + """, + """ + class C(string s) + { + public string S { get; } = s; + } + """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + } + + [Fact] + public async Task TestCommonParameterNamingStyle_CreateAndInitProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_s) + { + } + """, + """ + class C(string t_s) + { + public string S { get; } = t_s + } + """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + } + + [Fact] + public async Task TestSpecifiedParameterNamingStyle_CreateAndInitProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_s_End) + { + } + """, + """ + class C(string p_s_End) + { + public string S { get; } = p_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle_CreateAndInitProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_p_s_End) + { + } + """, + """ + class C(string t_p_s_End) + { + public string S { get; } = t_p_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle2_CreateAndInitProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_t_s_End) + { + } + """, + """ + class C(string p_t_s_End) + { + public string S { get; } = p_t_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestNoParameterNamingStyle_InitializeField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + private readonly string _s; + } + """, + """ + class C(string s) + { + private readonly string _s = s; + } + """, index: 0, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } + + [Fact] + public async Task TestCommonParameterNamingStyle_InitializeField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_s) + { + private readonly string _s; + } + """, + """ + class C(string t_s) + { + private readonly string _s = t_s; + } + """, index: 0, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } + + [Fact] + public async Task TestSpecifiedParameterNamingStyle_InitializeField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_s_End) + { + private readonly string _s; + } + """, + """ + class C(string p_s_End) + { + private readonly string _s = p_s_End; + } + """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle_InitializeField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_p_s_End) + { + private readonly string _s; + } + """, + """ + class CC(string t_p_s_End) + { + private readonly string _s = t_p_s_End; + } + """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle2_InitializeField() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_t_s_End) + { + private readonly string _s; + } + """, + """ + class C([||]string p_t_s_End) + { + private readonly string _s = p_t_s_End; + } + """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestNoParameterNamingStyle_InitializeProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) + { + public string S { get; } + } + """, + """ + class C(string s) + { + public string S { get; } = s; + } + """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + } + + [Fact] + public async Task TestCommonParameterNamingStyle_InitializeProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_s) + { + public string S { get; } + } + """, + """ + class C(string t_s) + { + public string S { get; } = t_s; + } + """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + } + + [Fact] + public async Task TestSpecifiedParameterNamingStyle_InitializeProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_s_End) + { + public string S { get; } + } + """, + """ + class C(string p_s_End) + { + public string S { get; } = p_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle_InitializeProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string t_p_s_End) + { + public string S { get; } + } + """, + """ + class C(string t_p_s_End) + { + public string S { get; } = t_p_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestCommonAndSpecifiedParameterNamingStyle2_InitializeProperty() + { + await TestInRegularAndScript1Async( + """ + class C([||]string p_t_s_End) + { + public string S { get; } + } + """, + """ + class C([||]string p_t_s_End) + { + public string S { get; } = p_t_s_End; + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestBaseNameEmpty() + { + await TestMissingAsync( + """ + class C([||]string p__End) + { + public string S { get; } + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + [Fact] + public async Task TestSomeBaseNamesEmpty() + { + // Currently, this case does not offer a refactoring because selecting multiple parameters + // is not supported. If multiple parameters are supported in the future, this case should + // be updated to verify that only the parameter name that does not have an empty base is offered. + await TestMissingAsync( + """ + class C([|string p__End, string p_test_t|]) + { + } + """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + } + + private TestParameters OmitIfDefault_Warning => new TestParameters(options: Option(CodeStyleOptions2.AccessibilityModifiersRequired, AccessibilityModifiersRequired.OmitIfDefault, NotificationOption2.Warning)); + private TestParameters Never_Warning => new TestParameters(options: Option(CodeStyleOptions2.AccessibilityModifiersRequired, AccessibilityModifiersRequired.Never, NotificationOption2.Warning)); + private TestParameters Always_Warning => new TestParameters(options: Option(CodeStyleOptions2.AccessibilityModifiersRequired, AccessibilityModifiersRequired.Always, NotificationOption2.Warning)); + + [Fact] + public async Task TestCreateFieldWithTopLevelNullability() + { + await TestInRegularAndScript1Async( + """ + #nullable enable + class C([||]string? s) + { + } + """, + """ + #nullable enable + class C(string? s) + { + private readonly string? _s = s; + } + """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } + + [Fact] + public async Task TestCreatePropertyWithTopLevelNullability() + { + await TestInRegularAndScript1Async( + """ + #nullable enable + class C([||]string? s) + { + } + """, + """ + #nullable enable + class C(string? s) + { + public string? S { get; } = s; + } + """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24526")] + public async Task TestSingleLineBlock_BraceOnNextLine() + { + await TestInRegularAndScript1Async( + """ + class C([||]string s) { } + """, + """ + class C(string s) + { + public string S { get; } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24526")] + public async Task TestSingleLineBlock_BraceOnSameLine() + { + await TestInRegularAndScriptAsync( + """ + class C([||]string s) { } + """, + """ + class C(string s) { + public string S { get; } = s; + } + """, options: this.Option(CSharpFormattingOptions2.NewLineBeforeOpenBrace, NewLineBeforeOpenBracePlacement.All & ~NewLineBeforeOpenBracePlacement.Types)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment() + { + await TestInRegularAndScript1Async( + """ + class C(string s, [||]int i) + { + private readonly string s = s; + } + """, + """ + class C(string s, int i) + { + private readonly string s = s; + private readonly int i = i; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + public async Task TestGenerateFieldIfParameterPrecedesExistingFieldAssignment() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, string s) + { + private readonly string s = s; + } + """, + """ + class C(int i, string s) + { + private readonly int i = i; + private readonly string s = s; + } + """); + } + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment1() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C(string s, string t, [||]int i) + // { + // (this.s, this.t) = (s, t); + // } + // } + // """, + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + // private readonly int i; + + // public C(string s, string t, int i) + // { + // (this.s, this.t, this.i) = (s, t, i); + // } + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment2() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C(string s, string t, [||]int i) => + // (this.s, this.t) = (s, t); + // } + // """, + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + // private readonly int i; + + // public C(string s, string t, int i) => + // (this.s, this.t, this.i) = (s, t, i); + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment3() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C(string s, string t, [||]int i) + // { + // if (s is null) throw new ArgumentNullException(); + // (this.s, this.t) = (s, t); + // } + // } + // """, + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + // private readonly int i; + + // public C(string s, string t, int i) + // { + // if (s is null) throw new ArgumentNullException(); + // (this.s, this.t, this.i) = (s, t, i); + // } + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterPrecedesExistingFieldAssignment_TupleAssignment1() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C([||]int i, string s, string t) + // { + // (this.s, this.t) = (s, t); + // } + // } + // """, + // """ + // class C + // { + // private readonly int i; + // private readonly string s; + // private readonly string t; + + // public C(int i, string s, string t) + // { + // (this.i, this.s, this.t) = (i, s, t); + // } + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterPrecedesExistingFieldAssignment_TupleAssignment2() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C([||]int i, string s, string t) => + // (this.s, this.t) = (s, t); + // } + // """, + // """ + // class C + // { + // private readonly int i; + // private readonly string s; + // private readonly string t; + + // public C(int i, string s, string t) => + // (this.i, this.s, this.t) = (i, s, t); + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterInMiddleOfExistingFieldAssignment_TupleAssignment1() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C(string s, [||]int i, string t) + // { + // (this.s, this.t) = (s, t); + // } + // } + // """, + // """ + // class C + // { + // private readonly string s; + // private readonly int i; + // private readonly string t; + + // public C(string s, int i, string t) + // { + // (this.s, this.i, this.t) = (s, i, t); + // } + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGenerateFieldIfParameterInMiddleOfExistingFieldAssignment_TupleAssignment2() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // private readonly string s; + // private readonly string t; + + // public C(string s, [||]int i, string t) => + // (this.s, this.t) = (s, t); + // } + // """, + // """ + // class C + // { + // private readonly string s; + // private readonly int i; + // private readonly string t; + + // public C(string s, int i, string t) => + // (this.s, this.i, this.t) = (s, i, t); + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] + //public async Task TestGeneratePropertyIfParameterFollowsExistingPropertyAssignment_TupleAssignment1() + //{ + // await TestInRegularAndScript1Async( + // """ + // class C + // { + // public string S { get; } + // public string T { get; } + + // public C(string s, string t, [||]int i) + // { + // (S, T) = (s, t); + // } + // } + // """, + // """ + // class C + // { + // public string S { get; } + // public string T { get; } + // public int I { get; } + + // public C(string s, string t, int i) + // { + // (S, T, I) = (s, t, i); + // } + // } + // """); + //} + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/41824")] + //public async Task TestMissingInArgList() + //{ + // await TestMissingInRegularAndScriptAsync( + // """ + // class C + // { + // private static void M() + // { + // M2(__arglist(1, 2, 3, 5, 6)); + // } + + // public static void M2([||]__arglist) + // { + // } + // } + // """); + //} + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingFields1() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + } + """, + """ + class C(int i, int j, int k) + { + private readonly int i = i; + private readonly int j = j; + private readonly int k = k; + } + """, index: 3); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingFields2() + { + await TestInRegularAndScript1Async( + """ + class C(int i, [||]int j, int k) + { + private readonly int i = i; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int i = i; + private readonly int j = j; + private readonly int k = k; + } + """, index: 2); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingFields3() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + private readonly int j = j; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int i = i; + private readonly int j = j; + private readonly int k = k; + } + """, index: 2); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingFields4() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + private readonly int k = k; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int i = i; + private readonly int j = j; + private readonly int k = k; + } + """, index: 2); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingProperties1() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + } + """, + """ + class C(int i, int j, int k) + { + public int I { get; } = i; + public int J { get; } = j; + public int K { get; } = k; + } + """, index: 2); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingProperties2() + { + await TestInRegularAndScript1Async( + """ + class C(int i, [||]int j, int k) + { + private readonly int i = i; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int i; + + public int J { get; } = j; + public int K { get; } = k; + } + """, index: 3); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingProperties3() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + private readonly int j = j; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int j = j; + + public int I { get; } = i; + public int K { get; } = k; + } + """, index: 3); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] + public async Task TestGenerateRemainingProperties4() + { + await TestInRegularAndScript1Async( + """ + class C([||]int i, int j, int k) + { + private readonly int k = k; + } + """, + """ + class C(int i, int j, int k) + { + private readonly int k = k; + + public int I { get; } = i; + public int J { get; } = j; + } + """, index: 3); + } + + //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/53467")] + //public async Task TestMissingWhenTypeNotInCompilation() + //{ + // await TestMissingInRegularAndScriptAsync( + // """ + // + // + // + // public class Goo + // { + // public Goo(int prop1) + // { + // Prop1 = prop1; + // } + + // public int Prop1 { get; } + // } + + // public class Bar : Goo + // { + // public Bar(int prop1, int [||]prop2) : base(prop1) { } + // } + // + // + // + // """); + //} + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty1() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S => throw new NotImplementedException(); + } + """, + """ + using System; + + class C(string s) + { + private string S { get; } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty2() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S + { + get => throw new NotImplementedException(); + } + } + """, + """ + using System; + + class C(string s) + { + private string S + { + get; + } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty3() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S + { + get { throw new NotImplementedException(); } + } + } + """, + """ + using System; + + class C(string s) + { + private string S + { + get; + } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty4() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + } + """, + """ + using System; + + class C(string s) + { + private string S + { + get; + set; + } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty5() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } + """, + """ + using System; + + class C(string s) + { + private string S + { + get; + set; + } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty6() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string S => throw new InvalidOperationException(); + } + """, + """ + using System; + + class C(string s) + { + private string S => throw new InvalidOperationException(); + + public string S1 { get; } = s; + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeThrowingProperty_DifferentFile1() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string [||]name) + { + } + + + using System; + public partial class Goo + { + public string Name => throw new NotImplementedException(); + } + + + + """, + """ + + + + public partial class Goo(string name) + { + } + + + using System; + public partial class Goo + { + public string Name { get; } = name; + } + + + + """); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs index ee6f740502bc8..c5aef222fc6b2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs @@ -124,7 +124,8 @@ private static MemberDeclarationSyntax GeneratePropertyDeclaration( identifier: property.Name.ToIdentifierToken(), accessorList: accessorList, expressionBody: null, - initializer: initializer); + initializer: initializer, + semicolonToken: initializer is null ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken)); propertyDeclaration = UseExpressionBodyIfDesired(info, propertyDeclaration); From 01d5982ab620a29a45e2c6a2b45897f09f3dc835 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 13:58:11 -0700 Subject: [PATCH 09/59] Fixes --- ...berFromPrimaryConstructorParameterTests.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index cb385b17ae5f4..e36717c2312ba 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -146,9 +146,9 @@ class C([||]string s) """ class C(string s) { - private string S => null; - public string S1 { get; } = s; + + private string S => null; } """); } @@ -166,8 +166,8 @@ class C([||]string s) """ class C(string s) { - private string T { get; } public string S { get; } = s; + private string T { get; } } """); } @@ -285,11 +285,6 @@ class C([||]string s, string t) { private string s; private string t = t; - - public C - { - this.t = t; - } } """, """ @@ -475,6 +470,7 @@ class C(string s) { public string S { get; } = s; } + """); } @@ -490,6 +486,7 @@ class C(string s) { private readonly string s = s; } + """, index: 1); } @@ -795,7 +792,7 @@ class C([||]string p_s_End) """ class C(string p_s_End) { - private readonly string _s = p_s_End + private readonly string _s = p_s_End; } """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } @@ -863,7 +860,7 @@ class C([||]string t_s) """ class C(string t_s) { - public string S { get; } = t_s + public string S { get; } = t_s; } """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); } @@ -984,7 +981,7 @@ class C([||]string t_p_s_End) } """, """ - class CC(string t_p_s_End) + class C(string t_p_s_End) { private readonly string _s = t_p_s_End; } @@ -1602,7 +1599,7 @@ class C(int i, [||]int j, int k) """ class C(int i, int j, int k) { - private readonly int i; + private readonly int i = i; public int J { get; } = j; public int K { get; } = k; @@ -1835,9 +1832,9 @@ class C([||]string s) class C(string s) { - private string S => throw new InvalidOperationException(); - public string S1 { get; } = s; + + private string S => throw new InvalidOperationException(); } """); } From 4cb3a3789bea24e3288c5b11b45ca310f17a2e3b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:08:55 -0700 Subject: [PATCH 10/59] Extract --- ...tructorParameterCodeRefactoringProvider.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 4f42300967244..15cb77d5b63e0 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -431,12 +431,29 @@ private static async Task AddSingleSymbolInitializationAsync( var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var parseOptions = root.SyntaxTree.Options; + var solutionEditor = new SolutionEditor(solution); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var codeGenerator = document.GetRequiredLanguageService(); + // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references + // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + await UpdateParameterReferencesAsync(solutionEditor, parameter, cancellationToken).ConfigureAwait(false); + // var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); if (fieldOrProperty.ContainingType == null) + { + await AddFieldOrPropertyAsync().ConfigureAwait(false); + } + else + { + await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + } + + return solutionEditor.GetChangedSolution(); + + async Task AddFieldOrPropertyAsync() { // We're generating a new field/property. Place into the containing type, ideally before/after a // relevant existing member. @@ -459,14 +476,14 @@ private static async Task AddSingleSymbolInitializationAsync( { return codeGenerator.AddProperty( currentTypeDecl, property, - codeGenerator.GetInfo(addContext, options, root.SyntaxTree.Options), + codeGenerator.GetInfo(addContext, options, parseOptions), cancellationToken); } else if (fieldOrProperty is IFieldSymbol field) { return codeGenerator.AddField( currentTypeDecl, field, - codeGenerator.GetInfo(addContext, options, root.SyntaxTree.Options), + codeGenerator.GetInfo(addContext, options, parseOptions), cancellationToken); } else @@ -475,7 +492,8 @@ private static async Task AddSingleSymbolInitializationAsync( } }); } - else + + async Task UpdateFieldOrPropertyAsync() { // We're updating an exiting field/prop. if (fieldOrProperty is IPropertySymbol property) @@ -517,8 +535,6 @@ private static async Task AddSingleSymbolInitializationAsync( throw ExceptionUtilities.Unreachable(); } } - - return solutionEditor.GetChangedSolution(); } private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext( From 0c2a1b7b849c6ca6485c3cba2a162e53e09acc7d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:30:29 -0700 Subject: [PATCH 11/59] Update references --- ...tructorParameterCodeRefactoringProvider.cs | 36 +++- ...berFromPrimaryConstructorParameterTests.cs | 200 ++++++++++++++++++ 2 files changed, 234 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 15cb77d5b63e0..a51c057d67c1e 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -6,6 +6,7 @@ using System.Composition; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -16,6 +17,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.PooledObjects; @@ -49,7 +51,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (selectedParameter == null) return; - if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration }) + if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax(kind: SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration) typeDeclaration }) return; // var parameterNodes = generator.GetParameters(functionDeclaration); @@ -439,7 +441,7 @@ private static async Task AddSingleSymbolInitializationAsync( // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references // to this primary constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync(solutionEditor, parameter, cancellationToken).ConfigureAwait(false); + await UpdateParameterReferencesAsync().ConfigureAwait(false); // var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); if (fieldOrProperty.ContainingType == null) @@ -453,6 +455,36 @@ private static async Task AddSingleSymbolInitializationAsync( return solutionEditor.GetChangedSolution(); + async Task UpdateParameterReferencesAsync() + { + var namedType = parameter.ContainingType; + var documents = namedType.DeclaringSyntaxReferences + .Select(r => solution.GetDocument(r.SyntaxTree)) + .WhereNotNull() + .ToImmutableHashSet(); + + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); + foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + { + var editingDocument = group.Key; + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + foreach (var location in group) + { + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && + identifierName.Identifier.ValueText == parameter.Name) + { + // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' + // just because we're generating a new property 'X' for the parameter to be assigned to. + editor.ReplaceNode( + identifierName, + IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); + } + } + } + } + async Task AddFieldOrPropertyAsync() { // We're generating a new field/property. Place into the containing type, ideally before/after a diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index e36717c2312ba..b2672dc42d100 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -1880,5 +1880,205 @@ public partial class Goo """); } + + [Fact] + public async Task TestUpdateCodeToReferenceExistingField1() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string _s; + + private void M() + { + Console.WriteLine(s); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + private string _s = s; + + private void M() + { + Console.WriteLine(_s); + var v = new C(s: ""); + } + } + """); + } + + [Fact] + public async Task TestUpdateCodeToReferenceExistingField2() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private string _s; + + private void M() + { + Console.WriteLine(/*t*/ s /*t2*/); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + private string _s = s; + + private void M() + { + Console.WriteLine(/*t*/ _s /*t2*/); + var v = new C(s: ""); + } + } + """); + } + + [Fact] + public async Task TestUpdateCodeToReferenceExistingProperty() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + public string S { get; } + + private void M() + { + Console.WriteLine(s); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + public string S { get; } = s; + + private void M() + { + Console.WriteLine(S); + var v = new C(s: ""); + } + } + """); + } + + [Fact] + public async Task TestUpdateCodeToReferenceExistingProperty2() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + public string S { get; } + + private void M() + { + Console.WriteLine(/*t*/ s /*t2*/); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + public string S { get; } = s; + + private void M() + { + Console.WriteLine(/*t*/ S /*t2*/); + var v = new C(s: ""); + } + } + """); + } + + [Fact] + public async Task TestUpdateCodeToReferenceNewProperty1() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private void M() + { + Console.WriteLine(s); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + public string S { get; } = s; + + private void M() + { + Console.WriteLine(S); + var v = new C(s: ""); + } + } + """); + } + + [Fact] + public async Task TestUpdateCodeToReferenceNewField1() + { + await TestInRegularAndScript1Async( + """ + using System; + + class C([||]string s) + { + private void M() + { + Console.WriteLine(s); + var v = new C(s: ""); + } + } + """, + """ + using System; + + class C(string s) + { + private readonly string _s = s; + + private void M() + { + Console.WriteLine(_s); + var v = new C(s: ""); + } + } + """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + } } } From 15f37471d7fca03141b7a7762abcc0f2b4cd0884 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:34:35 -0700 Subject: [PATCH 12/59] Simplify --- ...yConstructorParameterCodeRefactoringProvider.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index a51c057d67c1e..a7cdd920f2cd2 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -44,9 +44,6 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte { var (document, _, cancellationToken) = context; - // TODO: One could try to retrieve TParameterList and then filter out parameters that intersect with - // textSpan and use that as `parameterNodes`, where `selectedParameter` would be the first one. - var selectedParameter = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); if (selectedParameter == null) return; @@ -54,23 +51,14 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax(kind: SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration) typeDeclaration }) return; - // var parameterNodes = generator.GetParameters(functionDeclaration); var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - // we can't just call GetDeclaredSymbol on functionDeclaration because it could an anonymous function, - // so first we have to get the parameter symbol and then its containing method symbol var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); if (parameter?.Name is null or "") return; - if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } methodSymbol || - methodSymbol.IsAbstract || - methodSymbol.IsExtern || - methodSymbol.PartialImplementationPart != null || - methodSymbol.ContainingType.TypeKind == TypeKind.Interface) - { + if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } methodSymbol) return; - } var refactorings = await GetRefactoringsForSingleParameterAsync( document, typeDeclaration, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); From 76c3370f1c5b8846ea5ffc01cbdb11fb50ec0285 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:37:31 -0700 Subject: [PATCH 13/59] Inline method --- SpellingExclusions.dic | 1 + ...tructorParameterCodeRefactoringProvider.cs | 41 +++++-------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/SpellingExclusions.dic b/SpellingExclusions.dic index 7e3eb2e1770b1..a2cd10e8f97fb 100644 --- a/SpellingExclusions.dic +++ b/SpellingExclusions.dic @@ -1 +1,2 @@ awaitable +Refactorings diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index a7cdd920f2cd2..c79d21abfde4f 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -52,58 +52,37 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); if (parameter?.Name is null or "") return; - if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } methodSymbol) + if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) return; - var refactorings = await GetRefactoringsForSingleParameterAsync( - document, typeDeclaration, parameter, methodSymbol, context.Options, cancellationToken).ConfigureAwait(false); - context.RegisterRefactorings(refactorings, context.Span); - - return; - } - - private static async Task> GetRefactoringsForSingleParameterAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - IMethodSymbol method, - CleanCodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var assignmentExpression = TryFindFieldOrPropertyInitializerValue(semanticModel.Compilation, parameter, cancellationToken); if (assignmentExpression != null) - return ImmutableArray.Empty; + return; // Haven't initialized any fields/properties with this parameter. Offer to assign // to an existing matching field/prop if we can find one, or add a new field/prop // if we can't. - + var fallbackOptions = context.Options; var rules = await document.GetNamingRulesAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); if (parameterNameParts.BaseName == "") - return ImmutableArray.Empty; + return; var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync( document, parameter, rules, parameterNameParts.BaseNameParts, cancellationToken).ConfigureAwait(false); - if (fieldOrProperty != null) - { - return HandleExistingFieldOrProperty( - document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions); - } - else - { - return await HandleNoExistingFieldOrPropertyAsync( - document, typeDeclaration, parameter, method, rules, fallbackOptions, cancellationToken).ConfigureAwait(false); - } + var refactorings = fieldOrProperty != null + ? HandleExistingFieldOrProperty(document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions) + : await HandleNoExistingFieldOrPropertyAsync( + document, typeDeclaration, parameter, constructor, rules, fallbackOptions, cancellationToken).ConfigureAwait(false); + + context.RegisterRefactorings(refactorings, context.Span); } private static async Task> HandleNoExistingFieldOrPropertyAsync( From 9e03946641d906c7a02f30d7138bf9ee6c186d09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:40:03 -0700 Subject: [PATCH 14/59] Inline --- ...tructorParameterCodeRefactoringProvider.cs | 174 ++++++++---------- 1 file changed, 77 insertions(+), 97 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index c79d21abfde4f..81f7891bc97c2 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -52,6 +52,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var compilation = semanticModel.Compilation; var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); if (parameter?.Name is null or "") return; @@ -78,97 +79,100 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte document, parameter, rules, parameterNameParts.BaseNameParts, cancellationToken).ConfigureAwait(false); var refactorings = fieldOrProperty != null - ? HandleExistingFieldOrProperty(document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions) - : await HandleNoExistingFieldOrPropertyAsync( - document, typeDeclaration, parameter, constructor, rules, fallbackOptions, cancellationToken).ConfigureAwait(false); + ? HandleExistingFieldOrProperty() + : await HandleNoExistingFieldOrPropertyAsync().ConfigureAwait(false); context.RegisterRefactorings(refactorings, context.Span); - } + return; - private static async Task> HandleNoExistingFieldOrPropertyAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - IMethodSymbol method, - ImmutableArray rules, - CleanCodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - // Didn't find a field/prop that this parameter could be assigned to. - // Offer to create new one and assign to that. - using var _ = ArrayBuilder.GetInstance(out var allActions); + ImmutableArray HandleExistingFieldOrProperty() + { + // Found a field/property that this parameter should be assigned to. + // Just offer the simple assignment to it. - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var resource = fieldOrProperty.Kind == SymbolKind.Field + ? FeaturesResources.Initialize_field_0 + : FeaturesResources.Initialize_property_0; - var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions( - document, typeDeclaration, parameter, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); + var title = string.Format(resource, fieldOrProperty.Name); - // Check if the surrounding parameters are assigned to another field in this class. If so, offer to - // make this parameter into a field as well. Otherwise, default to generating a property - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(compilation, parameter, cancellationToken); - if (siblingFieldOrProperty is IFieldSymbol) - { - allActions.Add(fieldAction); - allActions.Add(propertyAction); + return ImmutableArray.Create(CodeAction.Create( + title, + cancellationToken => AddSingleSymbolInitializationAsync( + document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), + title)); } - else + + async Task> HandleNoExistingFieldOrPropertyAsync() { - allActions.Add(propertyAction); - allActions.Add(fieldAction); - } + // Didn't find a field/prop that this parameter could be assigned to. + // Offer to create new one and assign to that. + using var _ = ArrayBuilder.GetInstance(out var allActions); - var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions( - document, compilation, typeDeclaration, method, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions, cancellationToken); + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - if (allFieldsAction != null && allPropertiesAction != null) - { + var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions( + document, typeDeclaration, parameter, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); + + // Check if the surrounding parameters are assigned to another field in this class. If so, offer to + // make this parameter into a field as well. Otherwise, default to generating a property + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(compilation, parameter, cancellationToken); if (siblingFieldOrProperty is IFieldSymbol) { - allActions.Add(allFieldsAction); - allActions.Add(allPropertiesAction); + allActions.Add(fieldAction); + allActions.Add(propertyAction); } else { - allActions.Add(allPropertiesAction); - allActions.Add(allFieldsAction); + allActions.Add(propertyAction); + allActions.Add(fieldAction); } - } - return allActions.ToImmutable(); - } + var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(formattingOptions.AccessibilityModifiersRequired); - private static (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( - Document document, - Compilation compilation, - TypeDeclarationSyntax typeDeclaration, - IMethodSymbol method, - ImmutableArray rules, - AccessibilityModifiersRequired accessibilityModifiersRequired, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - var parameters = GetParametersWithoutAssociatedMembers( - compilation, rules, method, cancellationToken); - - if (parameters.Length < 2) - return default; - - var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, accessibilityModifiersRequired, rules)); - var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, rules)); - - var allFieldsAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_fields, - c => AddAllSymbolInitializationsAsync( - document, typeDeclaration, parameters, fields, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); - var allPropertiesAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_properties, - c => AddAllSymbolInitializationsAsync( - document, typeDeclaration, parameters, properties, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); - - return (allFieldsAction, allPropertiesAction); + if (allFieldsAction != null && allPropertiesAction != null) + { + if (siblingFieldOrProperty is IFieldSymbol) + { + allActions.Add(allFieldsAction); + allActions.Add(allPropertiesAction); + } + else + { + allActions.Add(allPropertiesAction); + allActions.Add(allFieldsAction); + } + } + + return allActions.ToImmutable(); + } + + (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( + AccessibilityModifiersRequired accessibilityModifiersRequired) + { + var parameters = GetParametersWithoutAssociatedMembers( + compilation, rules, constructor, cancellationToken); + + if (parameters.Length < 2) + return default; + + var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, accessibilityModifiersRequired, rules)); + var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, rules)); + + var allFieldsAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_fields, + c => AddAllSymbolInitializationsAsync( + document, typeDeclaration, parameters, fields, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); + var allPropertiesAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_properties, + c => AddAllSymbolInitializationsAsync( + document, typeDeclaration, parameters, properties, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); + + return (allFieldsAction, allPropertiesAction); + } } private static (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( @@ -221,30 +225,6 @@ private static ImmutableArray GetParametersWithoutAssociatedMe return result.ToImmutable(); } - private static ImmutableArray HandleExistingFieldOrProperty( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - ISymbol fieldOrProperty, - bool isThrowNotImplementedProperty, - CodeGenerationOptionsProvider fallbackOptions) - { - // Found a field/property that this parameter should be assigned to. - // Just offer the simple assignment to it. - - var resource = fieldOrProperty.Kind == SymbolKind.Field - ? FeaturesResources.Initialize_field_0 - : FeaturesResources.Initialize_property_0; - - var title = string.Format(resource, fieldOrProperty.Name); - - return ImmutableArray.Create(CodeAction.Create( - title, - cancellationToken => AddSingleSymbolInitializationAsync( - document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), - title)); - } - private static ISymbol? TryFindSiblingFieldOrProperty( Compilation compilation, IParameterSymbol parameter, From 5826dd2f7bc6efdd7081c092e0eb96160a6921ff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:42:14 -0700 Subject: [PATCH 15/59] inline --- ...tructorParameterCodeRefactoringProvider.cs | 109 +++++++++--------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 81f7891bc97c2..52b68ced2f737 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -162,17 +162,65 @@ async Task> HandleNoExistingFieldOrPropertyAsync() var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, - c => AddAllSymbolInitializationsAsync( - document, typeDeclaration, parameters, fields, fallbackOptions, c), + cancellationToken => AddAllSymbolInitializationsAsync(parameters, fields, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, - c => AddAllSymbolInitializationsAsync( - document, typeDeclaration, parameters, properties, fallbackOptions, c), + cancellationToken => AddAllSymbolInitializationsAsync(parameters, properties, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); return (allFieldsAction, allPropertiesAction); } + + async Task AddAllSymbolInitializationsAsync( + ImmutableArray parameters, + ImmutableArray fieldsOrProperties, + CancellationToken cancellationToken) + { + Debug.Assert(parameters.Length >= 2); + Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length == fieldsOrProperties.Length); + + // Process each param+field/prop in order. Apply the pair to the document getting the updated document. + // Then find all the current data in that updated document and move onto the next pair. + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var trackedRoot = root.TrackNodes(typeDeclaration); + var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; + + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var fieldOrProperty = fieldsOrProperties[i]; + + var currentDocument = currentSolution.GetRequiredDocument(document.Id); + var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var currentCompilation = currentSemanticModel.Compilation; + var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); + if (currentTypeDeclaration == null) + continue; + + var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (currentParameter == null) + continue; + + // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. + + currentSolution = await AddSingleSymbolInitializationAsync( + currentDocument, + currentTypeDeclaration, + currentParameter, + fieldOrProperty, + isThrowNotImplementedProperty: false, + fallbackOptions, + cancellationToken).ConfigureAwait(false); + } + + return currentSolution; + } } private static (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( @@ -312,59 +360,6 @@ private static IPropertySymbol CreateProperty( throw ExceptionUtilities.Unreachable(); } - private static async Task AddAllSymbolInitializationsAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - ImmutableArray parameters, - ImmutableArray fieldsOrProperties, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - Debug.Assert(parameters.Length >= 2); - Debug.Assert(fieldsOrProperties.Length > 0); - Debug.Assert(parameters.Length == fieldsOrProperties.Length); - - // Process each param+field/prop in order. Apply the pair to the document getting the updated document. - // Then find all the current data in that updated document and move onto the next pair. - - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var trackedRoot = root.TrackNodes(typeDeclaration); - var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; - - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var fieldOrProperty = fieldsOrProperties[i]; - - var currentDocument = currentSolution.GetRequiredDocument(document.Id); - var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var currentCompilation = currentSemanticModel.Compilation; - var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); - if (currentTypeDeclaration == null) - continue; - - var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); - if (currentParameter == null) - continue; - - // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. - - currentSolution = await AddSingleSymbolInitializationAsync( - currentDocument, - currentTypeDeclaration, - currentParameter, - fieldOrProperty, - isThrowNotImplementedProperty: false, - fallbackOptions, - cancellationToken).ConfigureAwait(false); - } - - return currentSolution; - } - private static async Task AddSingleSymbolInitializationAsync( Document document, TypeDeclarationSyntax typeDeclaration, From a18d4506db582287d198d5a142bb88c6091c3a82 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:42:46 -0700 Subject: [PATCH 16/59] Inline --- ...tructorParameterCodeRefactoringProvider.cs | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 52b68ced2f737..e4e964d70e56b 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -151,9 +151,7 @@ async Task> HandleNoExistingFieldOrPropertyAsync() (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( AccessibilityModifiersRequired accessibilityModifiersRequired) { - var parameters = GetParametersWithoutAssociatedMembers( - compilation, rules, constructor, cancellationToken); - + var parameters = GetParametersWithoutAssociatedMembers(); if (parameters.Length < 2) return default; @@ -172,6 +170,26 @@ async Task> HandleNoExistingFieldOrPropertyAsync() return (allFieldsAction, allPropertiesAction); } + ImmutableArray GetParametersWithoutAssociatedMembers() + { + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var parameter in constructor.Parameters) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); + if (parameterNameParts.BaseName == "") + continue; + + var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); + if (assignmentOp != null) + continue; + + result.Add(parameter); + } + + return result.ToImmutable(); + } + async Task AddAllSymbolInitializationsAsync( ImmutableArray parameters, ImmutableArray fieldsOrProperties, @@ -249,30 +267,6 @@ private static (CodeAction fieldAction, CodeAction propertyAction) AddSpecificPa return (fieldAction, propertyAction); } - private static ImmutableArray GetParametersWithoutAssociatedMembers( - Compilation compilation, - ImmutableArray rules, - IMethodSymbol method, - CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var parameter in method.Parameters) - { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); - if (parameterNameParts.BaseName == "") - continue; - - var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); - if (assignmentOp != null) - continue; - - result.Add(parameter); - } - - return result.ToImmutable(); - } - private static ISymbol? TryFindSiblingFieldOrProperty( Compilation compilation, IParameterSymbol parameter, From c19d80637c365c080e18f247d2a63ed1cf66dd3b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:44:11 -0700 Subject: [PATCH 17/59] Inline --- ...tructorParameterCodeRefactoringProvider.cs | 121 +++++++++--------- 1 file changed, 57 insertions(+), 64 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index e4e964d70e56b..b5732eebd79ed 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -66,18 +66,15 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (assignmentExpression != null) return; - // Haven't initialized any fields/properties with this parameter. Offer to assign - // to an existing matching field/prop if we can find one, or add a new field/prop - // if we can't. + // Haven't initialized any fields/properties with this parameter. Offer to assign to an existing matching + // field/prop if we can find one, or add a new field/prop if we can't. var fallbackOptions = context.Options; var rules = await document.GetNamingRulesAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); if (parameterNameParts.BaseName == "") return; - var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync( - document, parameter, rules, parameterNameParts.BaseNameParts, cancellationToken).ConfigureAwait(false); - + var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync().ConfigureAwait(false); var refactorings = fieldOrProperty != null ? HandleExistingFieldOrProperty() : await HandleNoExistingFieldOrPropertyAsync().ConfigureAwait(false); @@ -85,6 +82,60 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactorings(refactorings, context.Span); return; + async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync() + { + // Look for a field/property that really looks like it corresponds to this parameter. Use a variety of + // heuristics around the name/type to see if this is a match. + + var parameterWords = parameterNameParts.BaseNameParts; + var containingType = parameter.ContainingType; + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Walk through the naming rules against this parameter's name to see what name the user would like for + // it as a member in this type. Note that we have some fallback rules that use the standard conventions + // around properties /fields so that can still find things even if the user has no naming preferences + // set. + + foreach (var rule in rules) + { + var memberName = rule.NamingStyle.CreateName(parameterWords); + foreach (var memberWithName in containingType.GetMembers(memberName)) + { + // We found members in our type with that name. If it's a writable field that we could assign + // this parameter to, and it's not already been assigned to, then this field is a good candidate + // for us to hook up to. + if (memberWithName is IFieldSymbol field && + !field.IsConst && + InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && + field.DeclaringSyntaxReferences is [var syntaxRef1, ..] && + syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) + { + return (field, isThrowNotImplementedProperty: false); + } + + // If it's a writable property that we could assign this parameter to, and it's not already been + // assigned to, then this property is a good candidate for us to hook up to. + if (memberWithName is IPropertySymbol property && + InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && + property.DeclaringSyntaxReferences is [var syntaxRef2, ..] && + syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) + { + // We also allow assigning into a property of the form `=> throw new + // NotImplementedException()`. That way users can easily spit out those methods, but then + // convert them to be normal properties with ease. + if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) + return (property, isThrowNotImplementedProperty: true); + + if (property.IsWritableInConstructor()) + return (property, isThrowNotImplementedProperty: false); + } + } + } + + // Couldn't find any existing member. Just return nothing so we can offer to create a member for them. + return default; + } + ImmutableArray HandleExistingFieldOrProperty() { // Found a field/property that this parameter should be assigned to. @@ -580,63 +631,5 @@ private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext conte fieldOrProperty = null; return null; } - - private static async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync( - Document document, IParameterSymbol parameter, ImmutableArray rules, ImmutableArray parameterWords, CancellationToken cancellationToken) - { - // Look for a field/property that really looks like it corresponds to this parameter. - // Use a variety of heuristics around the name/type to see if this is a match. - - var containingType = parameter.ContainingType; - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - - // Walk through the naming rules against this parameter's name to see what - // name the user would like for it as a member in this type. Note that we - // have some fallback rules that use the standard conventions around - // properties /fields so that can still find things even if the user has no - // naming preferences set. - - foreach (var rule in rules) - { - var memberName = rule.NamingStyle.CreateName(parameterWords); - foreach (var memberWithName in containingType.GetMembers(memberName)) - { - // We found members in our type with that name. If it's a writable - // field that we could assign this parameter to, and it's not already - // been assigned to, then this field is a good candidate for us to - // hook up to. - if (memberWithName is IFieldSymbol field && - !field.IsConst && - InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && - field.DeclaringSyntaxReferences is [var syntaxRef1, ..] && - syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) - { - return (field, isThrowNotImplementedProperty: false); - } - - // If it's a writable property that we could assign this parameter to, and it's - // not already been assigned to, then this property is a good candidate for us to - // hook up to. - if (memberWithName is IPropertySymbol property && - InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && - property.DeclaringSyntaxReferences is [var syntaxRef2, ..] && - syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) - { - // We also allow assigning into a property of the form `=> throw new NotImplementedException()`. - // That way users can easily spit out those methods, but then convert them to be normal - // properties with ease. - if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) - return (property, isThrowNotImplementedProperty: true); - - if (property.IsWritableInConstructor()) - return (property, isThrowNotImplementedProperty: false); - } - } - } - - // Couldn't find any existing member. Just return nothing so we can offer to - // create a member for them. - return default; - } } } From b8697f8b3fb9a9c2aefef2d7b6a85a18b4942a6a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:44:32 -0700 Subject: [PATCH 18/59] Rename --- ...rFromPrimaryConstructorParameterCodeRefactoringProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index b5732eebd79ed..99a95bc3766cb 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -62,8 +62,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. - var assignmentExpression = TryFindFieldOrPropertyInitializerValue(semanticModel.Compilation, parameter, cancellationToken); - if (assignmentExpression != null) + var initializerValue = TryFindFieldOrPropertyInitializerValue(semanticModel.Compilation, parameter, cancellationToken); + if (initializerValue != null) return; // Haven't initialized any fields/properties with this parameter. Offer to assign to an existing matching From 6db4c3734e6e6da44dac9c761cae49afef8762bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:46:27 -0700 Subject: [PATCH 19/59] Rename --- ...tructorParameterCodeRefactoringProvider.cs | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 99a95bc3766cb..ff8e590bece88 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -430,7 +430,7 @@ private static async Task AddSingleSymbolInitializationAsync( // to this primary constructor parameter (within this type) to refer to the field/prop now instead. await UpdateParameterReferencesAsync().ConfigureAwait(false); - // var mainEditor = await editor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. if (fieldOrProperty.ContainingType == null) { await AddFieldOrPropertyAsync().ConfigureAwait(false); @@ -478,8 +478,8 @@ async Task AddFieldOrPropertyAsync() // relevant existing member. var (sibling, siblingSyntax, addContext) = fieldOrProperty switch { - IPropertySymbol => GetAddContext(compilation, parameter, cancellationToken), - IFieldSymbol => GetAddContext(compilation, parameter, cancellationToken), + IPropertySymbol => GetAddContext(), + IFieldSymbol => GetAddContext(), _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), }; @@ -512,6 +512,26 @@ async Task AddFieldOrPropertyAsync() }); } + (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol + { + foreach (var (sibling, before) in GetSiblingParameters(parameter)) + { + var initializer = TryFindFieldOrPropertyInitializerValue( + compilation, sibling, out var fieldOrProperty, cancellationToken); + + if (initializer != null && + fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) + { + var syntax = syntaxReference.GetSyntax(cancellationToken); + return (symbol, syntax, before + ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) + : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); + } + } + + return (symbol: null, syntax: null, CodeGenerationContext.Default); + } + async Task UpdateFieldOrPropertyAsync() { // We're updating an exiting field/prop. @@ -556,30 +576,6 @@ async Task UpdateFieldOrPropertyAsync() } } - private static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext( - Compilation compilation, - IParameterSymbol parameter, - CancellationToken cancellationToken) - where TSymbol : class, ISymbol - { - foreach (var (sibling, before) in GetSiblingParameters(parameter)) - { - var initializer = TryFindFieldOrPropertyInitializerValue( - compilation, sibling, out var fieldOrProperty, cancellationToken); - - if (initializer != null && - fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) - { - var syntax = syntaxReference.GetSyntax(cancellationToken); - return (symbol, syntax, before - ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) - : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); - } - } - - return (symbol: null, syntax: null, CodeGenerationContext.Default); - } - private static IOperation? TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, From 43342df25665abd18038550797dca6d06c983638 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:50:18 -0700 Subject: [PATCH 20/59] inline --- ...tructorParameterCodeRefactoringProvider.cs | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index ff8e590bece88..87fb49426193e 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -74,6 +74,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte if (parameterNameParts.BaseName == "") return; + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync().ConfigureAwait(false); var refactorings = fieldOrProperty != null ? HandleExistingFieldOrProperty() @@ -160,10 +162,7 @@ async Task> HandleNoExistingFieldOrPropertyAsync() // Offer to create new one and assign to that. using var _ = ArrayBuilder.GetInstance(out var allActions); - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - - var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions( - document, typeDeclaration, parameter, rules, formattingOptions.AccessibilityModifiersRequired, fallbackOptions); + var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property @@ -180,8 +179,7 @@ async Task> HandleNoExistingFieldOrPropertyAsync() allActions.Add(fieldAction); } - var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(formattingOptions.AccessibilityModifiersRequired); - + var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); if (allFieldsAction != null && allPropertiesAction != null) { if (siblingFieldOrProperty is IFieldSymbol) @@ -199,14 +197,33 @@ async Task> HandleNoExistingFieldOrPropertyAsync() return allActions.ToImmutable(); } - (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions( - AccessibilityModifiersRequired accessibilityModifiersRequired) + (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() + { + var field = CreateField(parameter, formattingOptions.AccessibilityModifiersRequired, rules); + var property = CreateProperty(parameter, rules); + + // we're generating the field or property, so we don't have to handle throwing versions of them. + var isThrowNotImplementedProperty = false; + + var fieldAction = CodeAction.Create( + string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), + c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_field_0) + "_" + field.Name); + var propertyAction = CodeAction.Create( + string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), + c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), + nameof(FeaturesResources.Create_and_assign_property_0) + "_" + property.Name); + + return (fieldAction, propertyAction); + } + + (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions() { var parameters = GetParametersWithoutAssociatedMembers(); if (parameters.Length < 2) return default; - var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, accessibilityModifiersRequired, rules)); + var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, formattingOptions.AccessibilityModifiersRequired, rules)); var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, rules)); var allFieldsAction = CodeAction.Create( @@ -292,32 +309,6 @@ async Task AddAllSymbolInitializationsAsync( } } - private static (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - ImmutableArray rules, - AccessibilityModifiersRequired accessibilityModifiersRequired, - CodeGenerationOptionsProvider fallbackOptions) - { - var field = CreateField(parameter, accessibilityModifiersRequired, rules); - var property = CreateProperty(parameter, rules); - - // we're generating the field or property, so we don't have to handle throwing versions of them. - var isThrowNotImplementedProperty = false; - - var fieldAction = CodeAction.Create( - string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_field_0) + "_" + field.Name); - var propertyAction = CodeAction.Create( - string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_property_0) + "_" + property.Name); - - return (fieldAction, propertyAction); - } - private static ISymbol? TryFindSiblingFieldOrProperty( Compilation compilation, IParameterSymbol parameter, @@ -405,6 +396,13 @@ private static IPropertySymbol CreateProperty( throw ExceptionUtilities.Unreachable(); } + /// + /// This is the workhorse function that actually goes and handles each parameter and either creates a + /// field/property for it, or updates an existing field/property with it. It's extracted out from the rest as + /// we do not want it capturing anything. Specifically, this is called in a loop for each parameter we're + /// processing. For each, we produce new solution snapshots and thus must ensure we're always pointing at the + /// new view of the world, not anything from the original view. + /// private static async Task AddSingleSymbolInitializationAsync( Document document, TypeDeclarationSyntax typeDeclaration, From 8bde839937e5950eecf9e24d8fdbe281bf536ba3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:50:49 -0700 Subject: [PATCH 21/59] Simplify --- ...FromPrimaryConstructorParameterCodeRefactoringProvider.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 87fb49426193e..5e822347f26bf 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -76,7 +76,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var (fieldOrProperty, isThrowNotImplementedProperty) = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync().ConfigureAwait(false); + var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); var refactorings = fieldOrProperty != null ? HandleExistingFieldOrProperty() : await HandleNoExistingFieldOrPropertyAsync().ConfigureAwait(false); @@ -84,14 +84,13 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactorings(refactorings, context.Span); return; - async Task<(ISymbol?, bool isThrowNotImplementedProperty)> TryFindMatchingUninitializedFieldOrPropertySymbolAsync() + (ISymbol?, bool isThrowNotImplementedProperty) TryFindMatchingUninitializedFieldOrPropertySymbol() { // Look for a field/property that really looks like it corresponds to this parameter. Use a variety of // heuristics around the name/type to see if this is a match. var parameterWords = parameterNameParts.BaseNameParts; var containingType = parameter.ContainingType; - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); // Walk through the naming rules against this parameter's name to see what name the user would like for // it as a member in this type. Note that we have some fallback rules that use the standard conventions From d1dc362e8fa909105802a1b02d74bc640e4beefb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:51:43 -0700 Subject: [PATCH 22/59] Simplify --- ...imaryConstructorParameterCodeRefactoringProvider.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 5e822347f26bf..e20195cff86d5 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -139,14 +139,10 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte ImmutableArray HandleExistingFieldOrProperty() { - // Found a field/property that this parameter should be assigned to. - // Just offer the simple assignment to it. - - var resource = fieldOrProperty.Kind == SymbolKind.Field + // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. + var title = string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 - : FeaturesResources.Initialize_property_0; - - var title = string.Format(resource, fieldOrProperty.Name); + : FeaturesResources.Initialize_property_0, fieldOrProperty.Name); return ImmutableArray.Create(CodeAction.Create( title, From 850d29e7e0f15ee7a3ce31308d98f89e8ac2805b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:54:11 -0700 Subject: [PATCH 23/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index e20195cff86d5..853825f2d873a 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -79,7 +79,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); var refactorings = fieldOrProperty != null ? HandleExistingFieldOrProperty() - : await HandleNoExistingFieldOrPropertyAsync().ConfigureAwait(false); + : HandleNoExistingFieldOrProperty(); context.RegisterRefactorings(refactorings, context.Span); return; @@ -151,18 +151,17 @@ ImmutableArray HandleExistingFieldOrProperty() title)); } - async Task> HandleNoExistingFieldOrPropertyAsync() + ImmutableArray HandleNoExistingFieldOrProperty() { // Didn't find a field/prop that this parameter could be assigned to. // Offer to create new one and assign to that. using var _ = ArrayBuilder.GetInstance(out var allActions); - var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); - // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(compilation, parameter, cancellationToken); + var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); + var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); + if (siblingFieldOrProperty is IFieldSymbol) { allActions.Add(fieldAction); @@ -192,6 +191,18 @@ async Task> HandleNoExistingFieldOrPropertyAsync() return allActions.ToImmutable(); } + ISymbol? TryFindSiblingFieldOrProperty() + { + foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) + { + TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, out var sibling, cancellationToken); + if (sibling != null) + return sibling; + } + + return null; + } + (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() { var field = CreateField(parameter, formattingOptions.AccessibilityModifiersRequired, rules); @@ -304,21 +315,6 @@ async Task AddAllSymbolInitializationsAsync( } } - private static ISymbol? TryFindSiblingFieldOrProperty( - Compilation compilation, - IParameterSymbol parameter, - CancellationToken cancellationToken) - { - foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) - { - TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, out var sibling, cancellationToken); - if (sibling != null) - return sibling; - } - - return null; - } - private static IFieldSymbol CreateField( IParameterSymbol parameter, AccessibilityModifiersRequired accessibilityModifiersRequired, From bf8fbdb7917264c9d26588dbf067637f4dfcc06c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:56:54 -0700 Subject: [PATCH 24/59] Extract --- ...tructorParameterCodeRefactoringProvider.cs | 107 +++++++++--------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 853825f2d873a..f12695f37663e 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -234,11 +234,11 @@ ImmutableArray HandleNoExistingFieldOrProperty() var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddAllSymbolInitializationsAsync(parameters, fields, cancellationToken), + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, fields, fallbackOptions, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddAllSymbolInitializationsAsync(parameters, properties, cancellationToken), + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, properties, fallbackOptions, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); return (allFieldsAction, allPropertiesAction); @@ -263,56 +263,6 @@ ImmutableArray GetParametersWithoutAssociatedMembers() return result.ToImmutable(); } - - async Task AddAllSymbolInitializationsAsync( - ImmutableArray parameters, - ImmutableArray fieldsOrProperties, - CancellationToken cancellationToken) - { - Debug.Assert(parameters.Length >= 2); - Debug.Assert(fieldsOrProperties.Length > 0); - Debug.Assert(parameters.Length == fieldsOrProperties.Length); - - // Process each param+field/prop in order. Apply the pair to the document getting the updated document. - // Then find all the current data in that updated document and move onto the next pair. - - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var trackedRoot = root.TrackNodes(typeDeclaration); - var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; - - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var fieldOrProperty = fieldsOrProperties[i]; - - var currentDocument = currentSolution.GetRequiredDocument(document.Id); - var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var currentCompilation = currentSemanticModel.Compilation; - var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); - if (currentTypeDeclaration == null) - continue; - - var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); - if (currentParameter == null) - continue; - - // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. - - currentSolution = await AddSingleSymbolInitializationAsync( - currentDocument, - currentTypeDeclaration, - currentParameter, - fieldOrProperty, - isThrowNotImplementedProperty: false, - fallbackOptions, - cancellationToken).ConfigureAwait(false); - } - - return currentSolution; - } } private static IFieldSymbol CreateField( @@ -387,6 +337,59 @@ private static IPropertySymbol CreateProperty( throw ExceptionUtilities.Unreachable(); } + private static async Task AddAllSymbolInitializationsAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + ImmutableArray parameters, + ImmutableArray fieldsOrProperties, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + Debug.Assert(parameters.Length >= 2); + Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length == fieldsOrProperties.Length); + + // Process each param+field/prop in order. Apply the pair to the document getting the updated document. + // Then find all the current data in that updated document and move onto the next pair. + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var trackedRoot = root.TrackNodes(typeDeclaration); + var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; + + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var fieldOrProperty = fieldsOrProperties[i]; + + var currentDocument = currentSolution.GetRequiredDocument(document.Id); + var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var currentCompilation = currentSemanticModel.Compilation; + var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); + if (currentTypeDeclaration == null) + continue; + + var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (currentParameter == null) + continue; + + // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. + + currentSolution = await AddSingleSymbolInitializationAsync( + currentDocument, + currentTypeDeclaration, + currentParameter, + fieldOrProperty, + isThrowNotImplementedProperty: false, + fallbackOptions, + cancellationToken).ConfigureAwait(false); + } + + return currentSolution; + } + /// /// This is the workhorse function that actually goes and handles each parameter and either creates a /// field/property for it, or updates an existing field/property with it. It's extracted out from the rest as From 88cb9de592c41a964b3291999c3ea6d5ccd1da52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 14:58:49 -0700 Subject: [PATCH 25/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 127 +++++++++--------- 1 file changed, 61 insertions(+), 66 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index f12695f37663e..c93adc1ac9ba8 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -205,8 +205,8 @@ ImmutableArray HandleNoExistingFieldOrProperty() (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() { - var field = CreateField(parameter, formattingOptions.AccessibilityModifiersRequired, rules); - var property = CreateProperty(parameter, rules); + var field = CreateField(parameter); + var property = CreateProperty(parameter); // we're generating the field or property, so we don't have to handle throwing versions of them. var isThrowNotImplementedProperty = false; @@ -229,8 +229,8 @@ ImmutableArray HandleNoExistingFieldOrProperty() if (parameters.Length < 2) return default; - var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p, formattingOptions.AccessibilityModifiersRequired, rules)); - var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p, rules)); + var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p)); + var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p)); var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, @@ -263,78 +263,73 @@ ImmutableArray GetParametersWithoutAssociatedMembers() return result.ToImmutable(); } - } - private static IFieldSymbol CreateField( - IParameterSymbol parameter, - AccessibilityModifiersRequired accessibilityModifiersRequired, - ImmutableArray rules) - { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; - - foreach (var rule in rules) + IFieldSymbol CreateField(IParameterSymbol parameter) { - if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + + foreach (var rule in rules) { - var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); - - var accessibilityLevel = accessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault - ? Accessibility.NotApplicable - : Accessibility.Private; - - return CodeGenerationSymbolFactory.CreateFieldSymbol( - attributes: default, - accessibilityLevel, - DeclarationModifiers.ReadOnly, - parameter.Type, - uniqueName, - initializer: IdentifierName(parameter.Name.EscapeIdentifier())); + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) + { + var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); + + var accessibilityLevel = formattingOptions.AccessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault + ? Accessibility.NotApplicable + : Accessibility.Private; + + return CodeGenerationSymbolFactory.CreateFieldSymbol( + attributes: default, + accessibilityLevel, + DeclarationModifiers.ReadOnly, + parameter.Type, + uniqueName, + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); + } } - } - - // We place a special rule in s_builtInRules that matches all fields. So we should - // always find a matching rule. - throw ExceptionUtilities.Unreachable(); - } - private static IPropertySymbol CreateProperty( - IParameterSymbol parameter, - ImmutableArray rules) - { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + // We place a special rule in s_builtInRules that matches all fields. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); + } - foreach (var rule in rules) + IPropertySymbol CreateProperty(IParameterSymbol parameter) { - if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + + foreach (var rule in rules) { - var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); - - var accessibilityLevel = Accessibility.Public; - - var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( - attributes: default, - Accessibility.Public, - statements: default); - - return CodeGenerationSymbolFactory.CreatePropertySymbol( - containingType: null, - attributes: default, - accessibilityLevel, - new DeclarationModifiers(), - parameter.Type, - RefKind.None, - explicitInterfaceImplementations: default, - name: uniqueName, - parameters: default, - getMethod: getMethod, - setMethod: null, - initializer: IdentifierName(parameter.Name.EscapeIdentifier())); + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) + { + var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); + + var accessibilityLevel = Accessibility.Public; + + var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: default, + Accessibility.Public, + statements: default); + + return CodeGenerationSymbolFactory.CreatePropertySymbol( + containingType: null, + attributes: default, + accessibilityLevel, + new DeclarationModifiers(), + parameter.Type, + RefKind.None, + explicitInterfaceImplementations: default, + name: uniqueName, + parameters: default, + getMethod: getMethod, + setMethod: null, + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); + } } - } - // We place a special rule in s_builtInRules that matches all properties. So we should - // always find a matching rule. - throw ExceptionUtilities.Unreachable(); + // We place a special rule in s_builtInRules that matches all properties. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); + } } private static async Task AddAllSymbolInitializationsAsync( From 646878475d0f2b556b761ad22c5c973b22998130 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 15:00:40 -0700 Subject: [PATCH 26/59] Simplify --- ...maryConstructorParameterCodeRefactoringProvider.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index c93adc1ac9ba8..d92d7a61b92e2 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -229,16 +229,13 @@ ImmutableArray HandleNoExistingFieldOrProperty() if (parameters.Length < 2) return default; - var fields = parameters.SelectAsArray(p => (ISymbol)CreateField(p)); - var properties = parameters.SelectAsArray(p => (ISymbol)CreateProperty(p)); - var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, fields, fallbackOptions, cancellationToken), + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, properties, fallbackOptions, cancellationToken), + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken), nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); return (allFieldsAction, allPropertiesAction); @@ -264,7 +261,7 @@ ImmutableArray GetParametersWithoutAssociatedMembers() return result.ToImmutable(); } - IFieldSymbol CreateField(IParameterSymbol parameter) + ISymbol CreateField(IParameterSymbol parameter) { var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; @@ -293,7 +290,7 @@ IFieldSymbol CreateField(IParameterSymbol parameter) throw ExceptionUtilities.Unreachable(); } - IPropertySymbol CreateProperty(IParameterSymbol parameter) + ISymbol CreateProperty(IParameterSymbol parameter) { var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; From 6c9a9bf56376f23598cce95185c1b498b5db39de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 15:02:11 -0700 Subject: [PATCH 27/59] Remove --- ...aryConstructorParameterCodeRefactoringProvider.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index d92d7a61b92e2..a55e755cf5f7c 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -62,7 +62,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. - var initializerValue = TryFindFieldOrPropertyInitializerValue(semanticModel.Compilation, parameter, cancellationToken); + var initializerValue = TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); if (initializerValue != null) return; @@ -243,7 +243,7 @@ ImmutableArray HandleNoExistingFieldOrProperty() ImmutableArray GetParametersWithoutAssociatedMembers() { - using var _ = ArrayBuilder.GetInstance(out var result); + using var _1 = ArrayBuilder.GetInstance(out var result); foreach (var parameter in constructor.Parameters) { @@ -251,7 +251,7 @@ ImmutableArray GetParametersWithoutAssociatedMembers() if (parameterNameParts.BaseName == "") continue; - var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); + var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); if (assignmentOp != null) continue; @@ -560,12 +560,6 @@ async Task UpdateFieldOrPropertyAsync() } } - private static IOperation? TryFindFieldOrPropertyInitializerValue( - Compilation compilation, - IParameterSymbol parameter, - CancellationToken cancellationToken) - => TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); - private static IOperation? TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, From cc42a3551dc194b1b7a236d814438a8b6163c142 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 15:05:36 -0700 Subject: [PATCH 28/59] Extract --- ...tructorParameterCodeRefactoringProvider.cs | 237 +--------------- ...ParameterCodeRefactoringProvider_Update.cs | 259 ++++++++++++++++++ 2 files changed, 260 insertions(+), 236 deletions(-) create mode 100644 src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index a55e755cf5f7c..e189bb1013548 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -4,9 +4,7 @@ using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -15,9 +13,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.PooledObjects; @@ -32,7 +28,7 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] - internal sealed class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider + internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -329,237 +325,6 @@ ISymbol CreateProperty(IParameterSymbol parameter) } } - private static async Task AddAllSymbolInitializationsAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - ImmutableArray parameters, - ImmutableArray fieldsOrProperties, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - Debug.Assert(parameters.Length >= 2); - Debug.Assert(fieldsOrProperties.Length > 0); - Debug.Assert(parameters.Length == fieldsOrProperties.Length); - - // Process each param+field/prop in order. Apply the pair to the document getting the updated document. - // Then find all the current data in that updated document and move onto the next pair. - - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var trackedRoot = root.TrackNodes(typeDeclaration); - var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; - - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var fieldOrProperty = fieldsOrProperties[i]; - - var currentDocument = currentSolution.GetRequiredDocument(document.Id); - var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var currentCompilation = currentSemanticModel.Compilation; - var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); - if (currentTypeDeclaration == null) - continue; - - var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); - if (currentParameter == null) - continue; - - // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. - - currentSolution = await AddSingleSymbolInitializationAsync( - currentDocument, - currentTypeDeclaration, - currentParameter, - fieldOrProperty, - isThrowNotImplementedProperty: false, - fallbackOptions, - cancellationToken).ConfigureAwait(false); - } - - return currentSolution; - } - - /// - /// This is the workhorse function that actually goes and handles each parameter and either creates a - /// field/property for it, or updates an existing field/property with it. It's extracted out from the rest as - /// we do not want it capturing anything. Specifically, this is called in a loop for each parameter we're - /// processing. For each, we produce new solution snapshots and thus must ensure we're always pointing at the - /// new view of the world, not anything from the original view. - /// - private static async Task AddSingleSymbolInitializationAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - ISymbol fieldOrProperty, - bool isThrowNotImplementedProperty, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - var project = document.Project; - var solution = project.Solution; - var services = solution.Services; - - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = root.SyntaxTree.Options; - - var solutionEditor = new SolutionEditor(solution); - var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var codeGenerator = document.GetRequiredLanguageService(); - - // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references - // to this primary constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync().ConfigureAwait(false); - - // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - if (fieldOrProperty.ContainingType == null) - { - await AddFieldOrPropertyAsync().ConfigureAwait(false); - } - else - { - await UpdateFieldOrPropertyAsync().ConfigureAwait(false); - } - - return solutionEditor.GetChangedSolution(); - - async Task UpdateParameterReferencesAsync() - { - var namedType = parameter.ContainingType; - var documents = namedType.DeclaringSyntaxReferences - .Select(r => solution.GetDocument(r.SyntaxTree)) - .WhereNotNull() - .ToImmutableHashSet(); - - var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); - foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) - { - var editingDocument = group.Key; - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - - foreach (var location in group) - { - var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); - if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && - identifierName.Identifier.ValueText == parameter.Name) - { - // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' - // just because we're generating a new property 'X' for the parameter to be assigned to. - editor.ReplaceNode( - identifierName, - IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); - } - } - } - } - - async Task AddFieldOrPropertyAsync() - { - // We're generating a new field/property. Place into the containing type, ideally before/after a - // relevant existing member. - var (sibling, siblingSyntax, addContext) = fieldOrProperty switch - { - IPropertySymbol => GetAddContext(), - IFieldSymbol => GetAddContext(), - _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), - }; - - var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; - - var editingDocument = solution.GetRequiredDocument(typeDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - typeDeclaration, - (currentTypeDecl, _) => - { - if (fieldOrProperty is IPropertySymbol property) - { - return codeGenerator.AddProperty( - currentTypeDecl, property, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else if (fieldOrProperty is IFieldSymbol field) - { - return codeGenerator.AddField( - currentTypeDecl, field, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - }); - } - - (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol - { - foreach (var (sibling, before) in GetSiblingParameters(parameter)) - { - var initializer = TryFindFieldOrPropertyInitializerValue( - compilation, sibling, out var fieldOrProperty, cancellationToken); - - if (initializer != null && - fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) - { - var syntax = syntaxReference.GetSyntax(cancellationToken); - return (symbol, syntax, before - ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) - : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); - } - } - - return (symbol: null, syntax: null, CodeGenerationContext.Default); - } - - async Task UpdateFieldOrPropertyAsync() - { - // We're updating an exiting field/prop. - if (fieldOrProperty is IPropertySymbol property) - { - foreach (var syntaxRef in property.DeclaringSyntaxReferences) - { - if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) - { - var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - - // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. - var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; - editor.ReplaceNode( - propertyDeclaration, - newPropertyDeclaration.WithoutTrailingTrivia() - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) - .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - } - } - } - else if (fieldOrProperty is IFieldSymbol field) - { - foreach (var syntaxRef in field.DeclaringSyntaxReferences) - { - if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) - { - var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - variableDeclarator, - variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - break; - } - } - } - else - { - throw ExceptionUtilities.Unreachable(); - } - } - } - private static IOperation? TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs new file mode 100644 index 0000000000000..01c8d344d191e --- /dev/null +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -0,0 +1,259 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.InitializeParameter; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter +{ + using static InitializeParameterHelpers; + using static InitializeParameterHelpersCore; + using static SyntaxFactory; + + internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider + { + /// This functions are the workhorses that actually go and handle each parameter (either creating a + /// field/property for it, or updates an existing field/property with it). They are extracted out from the rest + /// as we do not want it capturing anything. Specifically, AddSingleSymbolInitializationAsync is called in a + /// loop from AddAllSymbolInitializationsAsync for each parameter we're processing. For each, we produce new + /// solution snapshots and thus must ensure we're always pointing at the new view of the world, not anything + /// from the original view. + + private static async Task AddAllSymbolInitializationsAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + ImmutableArray parameters, + ImmutableArray fieldsOrProperties, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + Debug.Assert(parameters.Length >= 2); + Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length == fieldsOrProperties.Length); + + // Process each param+field/prop in order. Apply the pair to the document getting the updated document. + // Then find all the current data in that updated document and move onto the next pair. + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var trackedRoot = root.TrackNodes(typeDeclaration); + var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; + + for (var i = 0; i < parameters.Length; i++) + { + var parameter = parameters[i]; + var fieldOrProperty = fieldsOrProperties[i]; + + var currentDocument = currentSolution.GetRequiredDocument(document.Id); + var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var currentCompilation = currentSemanticModel.Compilation; + var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); + if (currentTypeDeclaration == null) + continue; + + var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (currentParameter == null) + continue; + + // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. + + currentSolution = await AddSingleSymbolInitializationAsync( + currentDocument, + currentTypeDeclaration, + currentParameter, + fieldOrProperty, + isThrowNotImplementedProperty: false, + fallbackOptions, + cancellationToken).ConfigureAwait(false); + } + + return currentSolution; + } + + private static async Task AddSingleSymbolInitializationAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + bool isThrowNotImplementedProperty, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + var project = document.Project; + var solution = project.Solution; + var services = solution.Services; + + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var parseOptions = root.SyntaxTree.Options; + + var solutionEditor = new SolutionEditor(solution); + var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var codeGenerator = document.GetRequiredLanguageService(); + + // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references + // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + await UpdateParameterReferencesAsync().ConfigureAwait(false); + + // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. + if (fieldOrProperty.ContainingType == null) + { + await AddFieldOrPropertyAsync().ConfigureAwait(false); + } + else + { + await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + } + + return solutionEditor.GetChangedSolution(); + + async Task UpdateParameterReferencesAsync() + { + var namedType = parameter.ContainingType; + var documents = namedType.DeclaringSyntaxReferences + .Select(r => solution.GetDocument(r.SyntaxTree)) + .WhereNotNull() + .ToImmutableHashSet(); + + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); + foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + { + var editingDocument = group.Key; + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + foreach (var location in group) + { + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && + identifierName.Identifier.ValueText == parameter.Name) + { + // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' + // just because we're generating a new property 'X' for the parameter to be assigned to. + editor.ReplaceNode( + identifierName, + IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); + } + } + } + } + + async Task AddFieldOrPropertyAsync() + { + // We're generating a new field/property. Place into the containing type, ideally before/after a + // relevant existing member. + var (sibling, siblingSyntax, addContext) = fieldOrProperty switch + { + IPropertySymbol => GetAddContext(), + IFieldSymbol => GetAddContext(), + _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), + }; + + var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; + + var editingDocument = solution.GetRequiredDocument(typeDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + typeDeclaration, + (currentTypeDecl, _) => + { + if (fieldOrProperty is IPropertySymbol property) + { + return codeGenerator.AddProperty( + currentTypeDecl, property, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else if (fieldOrProperty is IFieldSymbol field) + { + return codeGenerator.AddField( + currentTypeDecl, field, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + }); + } + + (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol + { + foreach (var (sibling, before) in GetSiblingParameters(parameter)) + { + var initializer = TryFindFieldOrPropertyInitializerValue( + compilation, sibling, out var fieldOrProperty, cancellationToken); + + if (initializer != null && + fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) + { + var syntax = syntaxReference.GetSyntax(cancellationToken); + return (symbol, syntax, before + ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) + : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); + } + } + + return (symbol: null, syntax: null, CodeGenerationContext.Default); + } + + async Task UpdateFieldOrPropertyAsync() + { + // We're updating an exiting field/prop. + if (fieldOrProperty is IPropertySymbol property) + { + foreach (var syntaxRef in property.DeclaringSyntaxReferences) + { + if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) + { + var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. + var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; + editor.ReplaceNode( + propertyDeclaration, + newPropertyDeclaration.WithoutTrailingTrivia() + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) + .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + } + } + } + else if (fieldOrProperty is IFieldSymbol field) + { + foreach (var syntaxRef in field.DeclaringSyntaxReferences) + { + if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) + { + var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + variableDeclarator, + variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + break; + } + } + } + else + { + throw ExceptionUtilities.Unreachable(); + } + } + } + } +} From b018c0b43c9a60587499e09914e44ff42287d606 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 15:08:28 -0700 Subject: [PATCH 29/59] simplify --- ...FromPrimaryConstructorParameterCodeRefactoringProvider.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index e189bb1013548..aacbc44f79c22 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.InitializeParameter; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Naming; using Roslyn.Utilities; @@ -239,7 +240,7 @@ ImmutableArray HandleNoExistingFieldOrProperty() ImmutableArray GetParametersWithoutAssociatedMembers() { - using var _1 = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; foreach (var parameter in constructor.Parameters) { @@ -254,7 +255,7 @@ ImmutableArray GetParametersWithoutAssociatedMembers() result.Add(parameter); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } ISymbol CreateField(IParameterSymbol parameter) From dd20cc3426a06ebdd5db5b1203b2e9d42a11b2ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 15:10:44 -0700 Subject: [PATCH 30/59] group --- ...tructorParameterCodeRefactoringProvider.cs | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index aacbc44f79c22..58a94fcedbfef 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -332,34 +333,37 @@ ISymbol CreateProperty(IParameterSymbol parameter) out ISymbol? fieldOrProperty, CancellationToken cancellationToken) { - foreach (var syntaxReference in parameter.ContainingType.DeclaringSyntaxReferences) + foreach (var group in parameter.ContainingType.DeclaringSyntaxReferences.GroupBy(r => r.SyntaxTree)) { - if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) - { - var semanticModel = compilation.GetSemanticModel(syntaxReference.SyntaxTree); + var semanticModel = compilation.GetSemanticModel(group.Key); - foreach (var member in typeDeclaration.Members) + foreach (var syntaxReference in group) + { + if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) { - if (member is PropertyDeclarationSyntax { Initializer.Value: var propertyInitializer } propertyDeclaration) + foreach (var member in typeDeclaration.Members) { - var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); - if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + if (member is PropertyDeclarationSyntax { Initializer.Value: var propertyInitializer } propertyDeclaration) { - fieldOrProperty = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); - return operation; + var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + { + fieldOrProperty = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); + return operation; + } } - } - else if (member is FieldDeclarationSyntax field) - { - foreach (var varDecl in field.Declaration.Variables) + else if (member is FieldDeclarationSyntax field) { - if (varDecl is { Initializer.Value: var fieldInitializer }) + foreach (var varDecl in field.Declaration.Variables) { - var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); - if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + if (varDecl is { Initializer.Value: var fieldInitializer }) { - fieldOrProperty = semanticModel.GetDeclaredSymbol(varDecl, cancellationToken); - return operation; + var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + { + fieldOrProperty = semanticModel.GetDeclaredSymbol(varDecl, cancellationToken); + return operation; + } } } } From 81666c5b3d72a528667242d7408c36d4582d8a0c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 16:37:00 -0700 Subject: [PATCH 31/59] Simplify --- .../AbstractAddParameterCheckCodeRefactoringProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs index 7c0b437cd3baf..ee294301c0b8e 100644 --- a/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InitializeParameter/AbstractAddParameterCheckCodeRefactoringProvider.cs @@ -200,7 +200,7 @@ private static bool ContainsNullCoalesceCheck( var operation = semanticModel.GetOperation(coalesceNode, cancellationToken); if (operation is ICoalesceOperation coalesceExpression) { - if (InitializeParameterHelpersCore.IsParameterReference(coalesceExpression.Value, parameter) && + if (IsParameterReference(coalesceExpression.Value, parameter) && syntaxFacts.IsThrowExpression(coalesceExpression.WhenNull.Syntax)) { return true; From 7f3322e5f9415a790bc42bc786777884a6160fb2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 19:55:58 -0700 Subject: [PATCH 32/59] cleanup --- ...tructorParameterCodeRefactoringProvider.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 58a94fcedbfef..b01f920d8fc31 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -75,9 +75,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); - var refactorings = fieldOrProperty != null - ? HandleExistingFieldOrProperty() - : HandleNoExistingFieldOrProperty(); + var refactorings = fieldOrProperty == null + ? HandleNoExistingFieldOrProperty() + : HandleExistingFieldOrProperty(); context.RegisterRefactorings(refactorings, context.Span); return; @@ -103,10 +103,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // We found members in our type with that name. If it's a writable field that we could assign // this parameter to, and it's not already been assigned to, then this field is a good candidate // for us to hook up to. - if (memberWithName is IFieldSymbol field && - !field.IsConst && - InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && - field.DeclaringSyntaxReferences is [var syntaxRef1, ..] && + if (memberWithName is IFieldSymbol { IsConst: false, DeclaringSyntaxReferences: [var syntaxRef1, ..] } field && + IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) { return (field, isThrowNotImplementedProperty: false); @@ -114,9 +112,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // If it's a writable property that we could assign this parameter to, and it's not already been // assigned to, then this property is a good candidate for us to hook up to. - if (memberWithName is IPropertySymbol property && - InitializeParameterHelpers.IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && - property.DeclaringSyntaxReferences is [var syntaxRef2, ..] && + if (memberWithName is IPropertySymbol { DeclaringSyntaxReferences: [var syntaxRef2, ..] } property && + IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) { // We also allow assigning into a property of the form `=> throw new @@ -153,7 +150,7 @@ ImmutableArray HandleNoExistingFieldOrProperty() { // Didn't find a field/prop that this parameter could be assigned to. // Offer to create new one and assign to that. - using var _ = ArrayBuilder.GetInstance(out var allActions); + using var allActions = TemporaryArray.Empty; // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property @@ -186,7 +183,7 @@ ImmutableArray HandleNoExistingFieldOrProperty() } } - return allActions.ToImmutable(); + return allActions.ToImmutableAndClear(); } ISymbol? TryFindSiblingFieldOrProperty() From 0b940bff69e55f04ccd830375df32984449c27cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 19:57:57 -0700 Subject: [PATCH 33/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index b01f920d8fc31..287623f3135dc 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -60,7 +60,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. - var initializerValue = TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); + var (initializerValue, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); if (initializerValue != null) return; @@ -190,7 +190,7 @@ ImmutableArray HandleNoExistingFieldOrProperty() { foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) { - TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, out var sibling, cancellationToken); + var (_, sibling) = TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, cancellationToken); if (sibling != null) return sibling; } @@ -246,7 +246,7 @@ ImmutableArray GetParametersWithoutAssociatedMembers() if (parameterNameParts.BaseName == "") continue; - var assignmentOp = TryFindFieldOrPropertyInitializerValue(compilation, parameter, out _, cancellationToken); + var (assignmentOp, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); if (assignmentOp != null) continue; @@ -324,16 +324,14 @@ ISymbol CreateProperty(IParameterSymbol parameter) } } - private static IOperation? TryFindFieldOrPropertyInitializerValue( + private static (IOperation? initializer, ISymbol? fieldOrProperty) TryFindFieldOrPropertyInitializerValue( Compilation compilation, IParameterSymbol parameter, - out ISymbol? fieldOrProperty, CancellationToken cancellationToken) { foreach (var group in parameter.ContainingType.DeclaringSyntaxReferences.GroupBy(r => r.SyntaxTree)) { var semanticModel = compilation.GetSemanticModel(group.Key); - foreach (var syntaxReference in group) { if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) @@ -344,10 +342,7 @@ ISymbol CreateProperty(IParameterSymbol parameter) { var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - { - fieldOrProperty = semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken); - return operation; - } + return (operation, semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken)); } else if (member is FieldDeclarationSyntax field) { @@ -357,10 +352,7 @@ ISymbol CreateProperty(IParameterSymbol parameter) { var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - { - fieldOrProperty = semanticModel.GetDeclaredSymbol(varDecl, cancellationToken); - return operation; - } + return (operation, semanticModel.GetDeclaredSymbol(varDecl, cancellationToken)); } } } @@ -369,8 +361,7 @@ ISymbol CreateProperty(IParameterSymbol parameter) } } - fieldOrProperty = null; - return null; + return default; } } } From aff31de53432b259f22a26ed0846776788af45a6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 19:59:24 -0700 Subject: [PATCH 34/59] Simplify --- .../AbstractAssignOutParametersCodeFixProvider.cs | 8 ++++---- ...yConstructorParameterCodeRefactoringProvider.cs | 4 ++-- .../CSharp/Extensions/SemanticModelExtensions.cs | 14 ++++++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AbstractAssignOutParametersCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AbstractAssignOutParametersCodeFixProvider.cs index 46cd0a2e6b516..278daf79e61d3 100644 --- a/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AbstractAssignOutParametersCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/AssignOutParameters/AbstractAssignOutParametersCodeFixProvider.cs @@ -110,10 +110,10 @@ private static bool IsValidLocation(SyntaxNode location) var parameterList = container.GetParameterList(); Contract.ThrowIfNull(parameterList); - var outParameters = - parameterList.Parameters.Select(p => (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)) - .Where(p => p.RefKind == RefKind.Out) - .ToImmutableArray(); + var outParameters = parameterList.Parameters + .Select(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)) + .Where(p => p.RefKind == RefKind.Out) + .ToImmutableArray(); var distinctExprsOrStatements = group.Select(t => t.exprOrStatement).Distinct(); foreach (var exprOrStatement in distinctExprsOrStatements) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 287623f3135dc..171269af84072 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -50,8 +50,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var compilation = semanticModel.Compilation; - var parameter = (IParameterSymbol)semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); + var parameter = semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); if (parameter?.Name is null or "") return; @@ -60,6 +59,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. + var compilation = semanticModel.Compilation; var (initializerValue, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); if (initializerValue != null) return; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs index 00b5b925ae529..ff674e62a1f0d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs @@ -451,15 +451,21 @@ private static string TryGenerateNameForArgumentExpression( return null; } - public static INamedTypeSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, BaseTypeDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) + public static INamedTypeSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, BaseTypeDeclarationSyntax declaration, CancellationToken cancellationToken) { - return semanticModel.GetDeclaredSymbol(declarationSyntax, cancellationToken) + return semanticModel.GetDeclaredSymbol(declaration, cancellationToken) ?? throw new InvalidOperationException(); } - public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ConstructorDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) + public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ConstructorDeclarationSyntax declaration, CancellationToken cancellationToken) { - return semanticModel.GetDeclaredSymbol(declarationSyntax, cancellationToken) + return semanticModel.GetDeclaredSymbol(declaration, cancellationToken) + ?? throw new InvalidOperationException(); + } + + public static IParameterSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ParameterSyntax parameter, CancellationToken cancellationToken) + { + return semanticModel.GetDeclaredSymbol(parameter, cancellationToken) ?? throw new InvalidOperationException(); } } From 42e899acca01f1b15a2951fa6bda74c833b73a02 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:04:01 -0700 Subject: [PATCH 35/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 24 ++++--------------- ...ParameterCodeRefactoringProvider_Update.cs | 4 ++-- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 171269af84072..760d6a1f3bba0 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -157,30 +157,14 @@ ImmutableArray HandleNoExistingFieldOrProperty() var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); - if (siblingFieldOrProperty is IFieldSymbol) - { - allActions.Add(fieldAction); - allActions.Add(propertyAction); - } - else - { - allActions.Add(propertyAction); - allActions.Add(fieldAction); - } + allActions.Add(siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction); + allActions.Add(siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction); var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); if (allFieldsAction != null && allPropertiesAction != null) { - if (siblingFieldOrProperty is IFieldSymbol) - { - allActions.Add(allFieldsAction); - allActions.Add(allPropertiesAction); - } - else - { - allActions.Add(allPropertiesAction); - allActions.Add(allFieldsAction); - } + allActions.Add(siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction); + allActions.Add(siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction); } return allActions.ToImmutableAndClear(); diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 01c8d344d191e..46e376bcd7fce 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -196,8 +196,8 @@ async Task AddFieldOrPropertyAsync() { foreach (var (sibling, before) in GetSiblingParameters(parameter)) { - var initializer = TryFindFieldOrPropertyInitializerValue( - compilation, sibling, out var fieldOrProperty, cancellationToken); + var (initializer, fieldOrProperty) = TryFindFieldOrPropertyInitializerValue( + compilation, sibling, cancellationToken); if (initializer != null && fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) From 111a8a1f8f68a5a53b0851b72d55e2accf144ddd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:10:06 -0700 Subject: [PATCH 36/59] Simplify --- .../ConvertToRecord/PositionalParameterInfo.cs | 3 +-- ...structorParameterCodeRefactoringProvider.cs | 4 ++-- .../Extensions/SemanticModelExtensions.cs | 18 ++++++++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs index c328f1a9ac0ae..b06556d542db0 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertToRecord/PositionalParameterInfo.cs @@ -41,8 +41,7 @@ public static ImmutableArray GetPropertiesForPositional using var _ = ArrayBuilder.GetInstance(out var resultBuilder); // get all declared property symbols, put inherited property symbols first - var symbols = properties - .SelectAsArray(p => (IPropertySymbol)semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); + var symbols = properties.SelectAsArray(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken)); // add inherited properties from a potential base record first var inheritedProperties = GetInheritedPositionalParams(type, cancellationToken); diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 760d6a1f3bba0..c20d6f26baca1 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -326,7 +326,7 @@ private static (IOperation? initializer, ISymbol? fieldOrProperty) TryFindFieldO { var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - return (operation, semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken)); + return (operation, semanticModel.GetRequiredDeclaredSymbol(propertyDeclaration, cancellationToken)); } else if (member is FieldDeclarationSyntax field) { @@ -336,7 +336,7 @@ private static (IOperation? initializer, ISymbol? fieldOrProperty) TryFindFieldO { var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - return (operation, semanticModel.GetDeclaredSymbol(varDecl, cancellationToken)); + return (operation, semanticModel.GetRequiredDeclaredSymbol(varDecl, cancellationToken)); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs index ff674e62a1f0d..1595ecfacd2d8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SemanticModelExtensions.cs @@ -451,21 +451,27 @@ private static string TryGenerateNameForArgumentExpression( return null; } - public static INamedTypeSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, BaseTypeDeclarationSyntax declaration, CancellationToken cancellationToken) + public static INamedTypeSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, BaseTypeDeclarationSyntax syntax, CancellationToken cancellationToken) { - return semanticModel.GetDeclaredSymbol(declaration, cancellationToken) + return semanticModel.GetDeclaredSymbol(syntax, cancellationToken) ?? throw new InvalidOperationException(); } - public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ConstructorDeclarationSyntax declaration, CancellationToken cancellationToken) + public static IMethodSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ConstructorDeclarationSyntax syntax, CancellationToken cancellationToken) { - return semanticModel.GetDeclaredSymbol(declaration, cancellationToken) + return semanticModel.GetDeclaredSymbol(syntax, cancellationToken) ?? throw new InvalidOperationException(); } - public static IParameterSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ParameterSyntax parameter, CancellationToken cancellationToken) + public static IParameterSymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, ParameterSyntax syntax, CancellationToken cancellationToken) { - return semanticModel.GetDeclaredSymbol(parameter, cancellationToken) + return semanticModel.GetDeclaredSymbol(syntax, cancellationToken) + ?? throw new InvalidOperationException(); + } + + public static IPropertySymbol GetRequiredDeclaredSymbol(this SemanticModel semanticModel, PropertyDeclarationSyntax syntax, CancellationToken cancellationToken) + { + return semanticModel.GetDeclaredSymbol(syntax, cancellationToken) ?? throw new InvalidOperationException(); } } From c1e3adc6eb510d21d96f14eead41a5264803bd19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:10:19 -0700 Subject: [PATCH 37/59] Simplify --- ...mberFromPrimaryConstructorParameterCodeRefactoringProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index c20d6f26baca1..63fda62f86221 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -17,7 +17,6 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.InitializeParameter; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Naming; From ef6e8eb8d4ea3e5fe102534a03754b76ebfd2332 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:20:34 -0700 Subject: [PATCH 38/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 63fda62f86221..768a8015ba51c 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -242,23 +242,20 @@ ImmutableArray GetParametersWithoutAssociatedMembers() ISymbol CreateField(IParameterSymbol parameter) { var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + var accessibilityLevel = formattingOptions.AccessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault + ? Accessibility.NotApplicable + : Accessibility.Private; foreach (var rule in rules) { if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) { - var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); - - var accessibilityLevel = formattingOptions.AccessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault - ? Accessibility.NotApplicable - : Accessibility.Private; - return CodeGenerationSymbolFactory.CreateFieldSymbol( attributes: default, accessibilityLevel, DeclarationModifiers.ReadOnly, parameter.Type, - uniqueName, + name: GenerateUniqueName(parameter, parameterNameParts, rule), initializer: IdentifierName(parameter.Name.EscapeIdentifier())); } } @@ -276,26 +273,20 @@ ISymbol CreateProperty(IParameterSymbol parameter) { if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) { - var uniqueName = GenerateUniqueName(parameter, parameterNameParts, rule); - - var accessibilityLevel = Accessibility.Public; - - var getMethod = CodeGenerationSymbolFactory.CreateAccessorSymbol( - attributes: default, - Accessibility.Public, - statements: default); - return CodeGenerationSymbolFactory.CreatePropertySymbol( containingType: null, attributes: default, - accessibilityLevel, - new DeclarationModifiers(), + Accessibility.Public, + modifiers: default, parameter.Type, RefKind.None, explicitInterfaceImplementations: default, - name: uniqueName, + name: GenerateUniqueName(parameter, parameterNameParts, rule), parameters: default, - getMethod: getMethod, + getMethod: CodeGenerationSymbolFactory.CreateAccessorSymbol( + attributes: default, + Accessibility.Public, + statements: default), setMethod: null, initializer: IdentifierName(parameter.Name.EscapeIdentifier())); } From 1d29426df547fcfabd600bd46118841d84ed7d25 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:22:44 -0700 Subject: [PATCH 39/59] Simplify --- ...yConstructorParameterCodeRefactoringProvider_Update.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 46e376bcd7fce..ae4f2f32edd94 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -52,14 +52,10 @@ private static async Task AddAllSymbolInitializationsAsync( var trackedRoot = root.TrackNodes(typeDeclaration); var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; - for (var i = 0; i < parameters.Length; i++) + foreach (var (parameter, fieldOrProperty) in parameters.Zip(fieldsOrProperties, static (a, b) => (a, b))) { - var parameter = parameters[i]; - var fieldOrProperty = fieldsOrProperties[i]; - var currentDocument = currentSolution.GetRequiredDocument(document.Id); - var currentSemanticModel = await currentDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var currentCompilation = currentSemanticModel.Compilation; + var currentCompilation = await currentDocument.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); From 086a37261fca5c07e0cea565e0d6f791e56ae56c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:24:46 -0700 Subject: [PATCH 40/59] Simplify --- ...rimaryConstructorParameterCodeRefactoringProvider_Update.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index ae4f2f32edd94..2f31738e2b230 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -95,8 +95,7 @@ private static async Task AddSingleSymbolInitializationAsync( var services = solution.Services; var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = root.SyntaxTree.Options; + var parseOptions = document.DocumentState.ParseOptions; var solutionEditor = new SolutionEditor(solution); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); From ad634c31ff1a82d5a1741062ce5c0ae687427c6b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:26:26 -0700 Subject: [PATCH 41/59] Simplify --- ...ParameterCodeRefactoringProvider_Update.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 2f31738e2b230..d8b4eb7c33269 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -106,16 +106,9 @@ private static async Task AddSingleSymbolInitializationAsync( await UpdateParameterReferencesAsync().ConfigureAwait(false); // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - if (fieldOrProperty.ContainingType == null) - { - await AddFieldOrPropertyAsync().ConfigureAwait(false); - } - else - { - await UpdateFieldOrPropertyAsync().ConfigureAwait(false); - } - - return solutionEditor.GetChangedSolution(); + return fieldOrProperty.ContainingType == null + ? await AddFieldOrPropertyAsync().ConfigureAwait(false) + : await UpdateFieldOrPropertyAsync().ConfigureAwait(false); async Task UpdateParameterReferencesAsync() { @@ -147,7 +140,7 @@ async Task UpdateParameterReferencesAsync() } } - async Task AddFieldOrPropertyAsync() + async Task AddFieldOrPropertyAsync() { // We're generating a new field/property. Place into the containing type, ideally before/after a // relevant existing member. @@ -185,6 +178,8 @@ async Task AddFieldOrPropertyAsync() throw ExceptionUtilities.Unreachable(); } }); + + return solutionEditor.GetChangedSolution(); } (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol @@ -207,7 +202,7 @@ async Task AddFieldOrPropertyAsync() return (symbol: null, syntax: null, CodeGenerationContext.Default); } - async Task UpdateFieldOrPropertyAsync() + async Task UpdateFieldOrPropertyAsync() { // We're updating an exiting field/prop. if (fieldOrProperty is IPropertySymbol property) @@ -248,6 +243,8 @@ async Task UpdateFieldOrPropertyAsync() { throw ExceptionUtilities.Unreachable(); } + + return solutionEditor.GetChangedSolution(); } } } From 1112ff7f5dc89fb9498ce46483fbb05422756b73 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:27:59 -0700 Subject: [PATCH 42/59] NRT --- ...aryConstructorParameterCodeRefactoringProvider_Update.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index d8b4eb7c33269..34f4b71328b4f 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -95,7 +95,7 @@ private static async Task AddSingleSymbolInitializationAsync( var services = solution.Services; var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = document.DocumentState.ParseOptions; + var parseOptions = document.DocumentState.ParseOptions!; var solutionEditor = new SolutionEditor(solution); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); @@ -140,7 +140,7 @@ async Task UpdateParameterReferencesAsync() } } - async Task AddFieldOrPropertyAsync() + async ValueTask AddFieldOrPropertyAsync() { // We're generating a new field/property. Place into the containing type, ideally before/after a // relevant existing member. @@ -202,7 +202,7 @@ async Task AddFieldOrPropertyAsync() return (symbol: null, syntax: null, CodeGenerationContext.Default); } - async Task UpdateFieldOrPropertyAsync() + async ValueTask UpdateFieldOrPropertyAsync() { // We're updating an exiting field/prop. if (fieldOrProperty is IPropertySymbol property) From 7cae32530b99932541d61c2e49e26f9abf232e3c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:33:47 -0700 Subject: [PATCH 43/59] Add tests --- ...ParameterCodeRefactoringProvider_Update.cs | 7 +-- ...berFromPrimaryConstructorParameterTests.cs | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 34f4b71328b4f..90c457dd628af 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -114,16 +114,13 @@ async Task UpdateParameterReferencesAsync() { var namedType = parameter.ContainingType; var documents = namedType.DeclaringSyntaxReferences - .Select(r => solution.GetDocument(r.SyntaxTree)) - .WhereNotNull() + .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) .ToImmutableHashSet(); var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) { - var editingDocument = group.Key; - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - + var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); foreach (var location in group) { var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index b2672dc42d100..5cb99f90cfb10 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -2080,5 +2080,57 @@ private void M() } """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } + + [Fact] + public async Task TestInitializeIntoFieldInDifferentPart() + { + await TestInRegularAndScript1Async( + """ + partial class C([||]string s) + { + } + + partial class C + { + private string s; + } + """, + """ + partial class C(string s) + { + } + + partial class C + { + private string s = s; + } + """); + } + + [Fact] + public async Task TestInitializeIntoPropertyInDifferentPart() + { + await TestInRegularAndScript1Async( + """ + partial class C([||]string s) + { + } + + partial class C + { + private string S { get; } + } + """, + """ + partial class C(string s) + { + } + + partial class C + { + private string S { get; } = s; + } + """); + } } } From 8d7587bf13df927db2eba3d2c797dbe4eeec2f6a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:39:40 -0700 Subject: [PATCH 44/59] Multi file fixes --- ...ParameterCodeRefactoringProvider_Update.cs | 4 +- ...berFromPrimaryConstructorParameterTests.cs | 455 +++++++----------- 2 files changed, 172 insertions(+), 287 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 90c457dd628af..df3d6d8d325f9 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -150,10 +150,10 @@ async ValueTask AddFieldOrPropertyAsync() var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; - var editingDocument = solution.GetRequiredDocument(typeDeclaration.SyntaxTree); + var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); editor.ReplaceNode( - typeDeclaration, + preferredTypeDeclaration, (currentTypeDecl, _) => { if (fieldOrProperty is IPropertySymbol property) diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index 5cb99f90cfb10..cace80fc0b068 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -1231,263 +1231,6 @@ class C(int i, string s) """); } - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment1() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C(string s, string t, [||]int i) - // { - // (this.s, this.t) = (s, t); - // } - // } - // """, - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - // private readonly int i; - - // public C(string s, string t, int i) - // { - // (this.s, this.t, this.i) = (s, t, i); - // } - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment2() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C(string s, string t, [||]int i) => - // (this.s, this.t) = (s, t); - // } - // """, - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - // private readonly int i; - - // public C(string s, string t, int i) => - // (this.s, this.t, this.i) = (s, t, i); - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterFollowsExistingFieldAssignment_TupleAssignment3() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C(string s, string t, [||]int i) - // { - // if (s is null) throw new ArgumentNullException(); - // (this.s, this.t) = (s, t); - // } - // } - // """, - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - // private readonly int i; - - // public C(string s, string t, int i) - // { - // if (s is null) throw new ArgumentNullException(); - // (this.s, this.t, this.i) = (s, t, i); - // } - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterPrecedesExistingFieldAssignment_TupleAssignment1() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C([||]int i, string s, string t) - // { - // (this.s, this.t) = (s, t); - // } - // } - // """, - // """ - // class C - // { - // private readonly int i; - // private readonly string s; - // private readonly string t; - - // public C(int i, string s, string t) - // { - // (this.i, this.s, this.t) = (i, s, t); - // } - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterPrecedesExistingFieldAssignment_TupleAssignment2() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C([||]int i, string s, string t) => - // (this.s, this.t) = (s, t); - // } - // """, - // """ - // class C - // { - // private readonly int i; - // private readonly string s; - // private readonly string t; - - // public C(int i, string s, string t) => - // (this.i, this.s, this.t) = (i, s, t); - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterInMiddleOfExistingFieldAssignment_TupleAssignment1() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C(string s, [||]int i, string t) - // { - // (this.s, this.t) = (s, t); - // } - // } - // """, - // """ - // class C - // { - // private readonly string s; - // private readonly int i; - // private readonly string t; - - // public C(string s, int i, string t) - // { - // (this.s, this.i, this.t) = (s, i, t); - // } - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGenerateFieldIfParameterInMiddleOfExistingFieldAssignment_TupleAssignment2() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private readonly string s; - // private readonly string t; - - // public C(string s, [||]int i, string t) => - // (this.s, this.t) = (s, t); - // } - // """, - // """ - // class C - // { - // private readonly string s; - // private readonly int i; - // private readonly string t; - - // public C(string s, int i, string t) => - // (this.s, this.i, this.t) = (s, i, t); - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/23308")] - //public async Task TestGeneratePropertyIfParameterFollowsExistingPropertyAssignment_TupleAssignment1() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // public string S { get; } - // public string T { get; } - - // public C(string s, string t, [||]int i) - // { - // (S, T) = (s, t); - // } - // } - // """, - // """ - // class C - // { - // public string S { get; } - // public string T { get; } - // public int I { get; } - - // public C(string s, string t, int i) - // { - // (S, T, I) = (s, t, i); - // } - // } - // """); - //} - - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/41824")] - //public async Task TestMissingInArgList() - //{ - // await TestMissingInRegularAndScriptAsync( - // """ - // class C - // { - // private static void M() - // { - // M2(__arglist(1, 2, 3, 5, 6)); - // } - - // public static void M2([||]__arglist) - // { - // } - // } - // """); - //} - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/35665")] public async Task TestGenerateRemainingFields1() { @@ -1649,34 +1392,6 @@ class C(int i, int j, int k) """, index: 3); } - //[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/53467")] - //public async Task TestMissingWhenTypeNotInCompilation() - //{ - // await TestMissingInRegularAndScriptAsync( - // """ - // - // - // - // public class Goo - // { - // public Goo(int prop1) - // { - // Prop1 = prop1; - // } - - // public int Prop1 { get; } - // } - - // public class Bar : Goo - // { - // public Bar(int prop1, int [||]prop2) : base(prop1) { } - // } - // - // - // - // """); - //} - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] public async Task TestInitializeThrowingProperty1() { @@ -2132,5 +1847,175 @@ partial class C } """); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeProperty_DifferentFile1() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string [||]name) + { + } + + + using System; + public partial class Goo + { + public string Name { get; } + } + + + + """, + """ + + + + public partial class Goo(string name) + { + } + + + using System; + public partial class Goo + { + public string Name { get; } = name; + } + + + + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeProperty_DifferentFile2() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string x, string [||]y) + { + } + + + using System; + public partial class Goo + { + public string X { get; } = x; + } + + + + """, + """ + + + + public partial class Goo(string x, string y) + { + } + + + using System; + public partial class Goo + { + public string X { get; } = x; + public string Y { get; } = y; + } + + + + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeProperty_DifferentFile3() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string [||]x, string y) + { + } + + + using System; + public partial class Goo + { + public string Y { get; } = y; + } + + + + """, + """ + + + + public partial class Goo(string x, string y) + { + } + + + using System; + public partial class Goo + { + public string X { get; } = x; + public string Y { get; } = y; + } + + + + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeField_DifferentFile1() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string [||]name) + { + } + + + using System; + public partial class Goo + { + private readonly string name; + } + + + + """, + """ + + + + public partial class Goo(string name) + { + } + + + using System; + public partial class Goo + { + private readonly string name = name; + } + + + + """); + } } } From f4bf2f0aabda3f0373ac73e2bd032618471a09aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:41:32 -0700 Subject: [PATCH 45/59] Add tests --- ...berFromPrimaryConstructorParameterTests.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index cace80fc0b068..e278b1e447990 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -1976,6 +1976,50 @@ public partial class Goo """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] + public async Task TestInitializeProperty_DifferentFile4() + { + await TestInRegularAndScriptAsync( + """ + + + + public partial class Goo(string [||]x, string y, string z) + { + } + + + using System; + public partial class Goo + { + public string Y { get; } = y; + } + + + + """, + """ + + + + public partial class Goo(string x, string y, string z) + { + } + + + using System; + public partial class Goo + { + public string X { get; } = x; + public string Y { get; } = y; + public string Z { get; } = z; + } + + + + """, index: 2); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36998")] public async Task TestInitializeField_DifferentFile1() { From 081e5cdb6448a3fea61b25c27a07c8e4886ac598 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:45:15 -0700 Subject: [PATCH 46/59] Tests --- ...berFromPrimaryConstructorParameterTests.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index e278b1e447990..cd438757a70ae 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -21,7 +21,7 @@ public partial class InitializeMemberFromPrimaryConstructorParameterTests : Abst protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) => new CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider(); - private readonly NamingStylesTestOptionSets options = new NamingStylesTestOptionSets(LanguageNames.CSharp); + private readonly NamingStylesTestOptionSets _options = new(LanguageNames.CSharp); [Fact] public async Task TestInitializeFieldWithSameName() @@ -760,7 +760,7 @@ class C(string s) { private readonly string _s = s; } - """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 1, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] @@ -777,7 +777,7 @@ class C(string t_s) { private readonly string _s = t_s; } - """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 1, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] @@ -794,7 +794,7 @@ class C(string p_s_End) { private readonly string _s = p_s_End; } - """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, index: 1, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -811,7 +811,7 @@ class C(string t_p_s_End) { private readonly string _s = t_p_s_End; } - """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, index: 1, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -828,7 +828,7 @@ class C([||]string p_t_s) { private readonly string _s = p_t_s; } - """, index: 1, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefix))); + """, index: 1, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefix))); } [Fact] @@ -845,7 +845,7 @@ class C(string s) { public string S { get; } = s; } - """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + """, parameters: new TestParameters(options: _options.PropertyNamesArePascalCase)); } [Fact] @@ -862,7 +862,7 @@ class C(string t_s) { public string S { get; } = t_s; } - """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + """, parameters: new TestParameters(options: _options.PropertyNamesArePascalCase)); } [Fact] @@ -879,7 +879,7 @@ class C(string p_s_End) { public string S { get; } = p_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -896,7 +896,7 @@ class C(string t_p_s_End) { public string S { get; } = t_p_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -913,7 +913,7 @@ class C(string p_t_s_End) { public string S { get; } = p_t_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -931,7 +931,7 @@ class C(string s) { private readonly string _s = s; } - """, index: 0, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 0, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] @@ -949,7 +949,7 @@ class C(string t_s) { private readonly string _s = t_s; } - """, index: 0, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 0, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] @@ -967,7 +967,7 @@ class C(string p_s_End) { private readonly string _s = p_s_End; } - """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, index: 0, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -985,7 +985,7 @@ class C(string t_p_s_End) { private readonly string _s = t_p_s_End; } - """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, index: 0, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1003,7 +1003,7 @@ class C([||]string p_t_s_End) { private readonly string _s = p_t_s_End; } - """, index: 0, parameters: new TestParameters(options: options.MergeStyles(options.FieldNamesAreCamelCaseWithUnderscorePrefix, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, index: 0, parameters: new TestParameters(options: _options.MergeStyles(_options.FieldNamesAreCamelCaseWithUnderscorePrefix, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1021,7 +1021,7 @@ class C(string s) { public string S { get; } = s; } - """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + """, parameters: new TestParameters(options: _options.PropertyNamesArePascalCase)); } [Fact] @@ -1039,7 +1039,7 @@ class C(string t_s) { public string S { get; } = t_s; } - """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + """, parameters: new TestParameters(options: _options.PropertyNamesArePascalCase)); } [Fact] @@ -1057,7 +1057,7 @@ class C(string p_s_End) { public string S { get; } = p_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1075,7 +1075,7 @@ class C(string t_p_s_End) { public string S { get; } = t_p_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1093,7 +1093,7 @@ class C([||]string p_t_s_End) { public string S { get; } = p_t_s_End; } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1105,7 +1105,7 @@ class C([||]string p__End) { public string S { get; } } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } [Fact] @@ -1119,7 +1119,7 @@ await TestMissingAsync( class C([|string p__End, string p_test_t|]) { } - """, parameters: new TestParameters(options: options.MergeStyles(options.PropertyNamesArePascalCase, options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); + """, parameters: new TestParameters(options: _options.MergeStyles(_options.PropertyNamesArePascalCase, _options.ParameterNamesAreCamelCaseWithPUnderscorePrefixAndUnderscoreEndSuffix))); } private TestParameters OmitIfDefault_Warning => new TestParameters(options: Option(CodeStyleOptions2.AccessibilityModifiersRequired, AccessibilityModifiersRequired.OmitIfDefault, NotificationOption2.Warning)); @@ -1142,7 +1142,7 @@ class C(string? s) { private readonly string? _s = s; } - """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 1, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] @@ -1161,7 +1161,7 @@ class C(string? s) { public string? S { get; } = s; } - """, parameters: new TestParameters(options: options.PropertyNamesArePascalCase)); + """, parameters: new TestParameters(options: _options.PropertyNamesArePascalCase)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24526")] @@ -1793,7 +1793,7 @@ private void M() var v = new C(s: ""); } } - """, index: 1, parameters: new TestParameters(options: options.FieldNamesAreCamelCaseWithUnderscorePrefix)); + """, index: 1, parameters: new TestParameters(options: _options.FieldNamesAreCamelCaseWithUnderscorePrefix)); } [Fact] From e8790d3832003eca4e3a386f3be2ff9e430e8f70 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 20:45:59 -0700 Subject: [PATCH 47/59] Tests --- ...berFromPrimaryConstructorParameterTests.cs | 88 ------------------- 1 file changed, 88 deletions(-) diff --git a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs index cd438757a70ae..94dfece4342e2 100644 --- a/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs +++ b/src/Features/CSharpTest/InitializeParameter/InitializeMemberFromPrimaryConstructorParameterTests.cs @@ -316,36 +316,6 @@ class C(string s, string t) """); } - //[Fact] - //public async Task TestInsertionLocation3() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private string s; - - // public C([||]string s) - // { - // if (true) { } - // } - // } - // """, - // """ - // class C - // { - // private string s; - - // public C(string s) - // { - // if (true) { } - - // this.s = s; - // } - // } - // """); - //} - [Fact] public async Task TestNotInMethod() { @@ -362,64 +332,6 @@ public void M([||]string s) """); } - //[Fact] - //public async Task TestInsertionLocation4() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private string s; - // private string t; - - // public C(string s, [||]string t) - // => this.s = s; - // } - // """, - // """ - // class C - // { - // private string s; - // private string t; - - // public C(string s, string t) - // { - // this.s = s; - // this.t = t; - // } - // } - // """); - //} - - //[Fact] - //public async Task TestInsertionLocation5() - //{ - // await TestInRegularAndScript1Async( - // """ - // class C - // { - // private string s; - // private string t; - - // public C([||]string s, string t) - // => this.t = t; - // } - // """, - // """ - // class C - // { - // private string s; - // private string t; - - // public C(string s, string t) - // { - // this.s = s; - // this.t = t; - // } - // } - // """); - //} - [Fact] public async Task TestInsertionLocation6() { From 2f139dd9ef164c2b7da96fba6701be09473b04cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:00:41 -0700 Subject: [PATCH 48/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 768a8015ba51c..a9d608de0ffbe 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -2,6 +2,8 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; @@ -78,7 +80,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte ? HandleNoExistingFieldOrProperty() : HandleExistingFieldOrProperty(); - context.RegisterRefactorings(refactorings, context.Span); + context.RegisterRefactorings(refactorings.ToImmutableArray(), context.Span); return; (ISymbol?, bool isThrowNotImplementedProperty) TryFindMatchingUninitializedFieldOrPropertySymbol() @@ -131,42 +133,36 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return default; } - ImmutableArray HandleExistingFieldOrProperty() + static CodeAction CreateCodeAction(string title, Func> createSolution) + => CodeAction.Create(title, createSolution, title); + + IEnumerable HandleExistingFieldOrProperty() { // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. - var title = string.Format(fieldOrProperty.Kind == SymbolKind.Field - ? FeaturesResources.Initialize_field_0 - : FeaturesResources.Initialize_property_0, fieldOrProperty.Name); - - return ImmutableArray.Create(CodeAction.Create( - title, + yield return CreateCodeAction( + string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 : FeaturesResources.Initialize_property_0, fieldOrProperty.Name), cancellationToken => AddSingleSymbolInitializationAsync( - document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken), - title)); + document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); } - ImmutableArray HandleNoExistingFieldOrProperty() + IEnumerable HandleNoExistingFieldOrProperty() { - // Didn't find a field/prop that this parameter could be assigned to. - // Offer to create new one and assign to that. - using var allActions = TemporaryArray.Empty; + // Didn't find a field/prop that this parameter could be assigned to. Offer to create new one and assign to that. // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); - allActions.Add(siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction); - allActions.Add(siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction); + yield return siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction; + yield return siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction; var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); if (allFieldsAction != null && allPropertiesAction != null) { - allActions.Add(siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction); - allActions.Add(siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction); + yield return siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction; + yield return siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction; } - - return allActions.ToImmutableAndClear(); } ISymbol? TryFindSiblingFieldOrProperty() @@ -189,14 +185,12 @@ ImmutableArray HandleNoExistingFieldOrProperty() // we're generating the field or property, so we don't have to handle throwing versions of them. var isThrowNotImplementedProperty = false; - var fieldAction = CodeAction.Create( + var fieldAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_field_0) + "_" + field.Name); - var propertyAction = CodeAction.Create( + cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + var propertyAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - c => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, c), - nameof(FeaturesResources.Create_and_assign_property_0) + "_" + property.Name); + cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); return (fieldAction, propertyAction); } @@ -209,12 +203,10 @@ ImmutableArray HandleNoExistingFieldOrProperty() var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken), - nameof(FeaturesResources.Create_and_assign_remaining_as_fields)); + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken), - nameof(FeaturesResources.Create_and_assign_remaining_as_properties)); + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); return (allFieldsAction, allPropertiesAction); } From dabf84a48a23d9ffdd55e32e0411a8ec5e61a6ff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:02:51 -0700 Subject: [PATCH 49/59] File namespaces --- ...tructorParameterCodeRefactoringProvider.cs | 481 +++++++++--------- ...ParameterCodeRefactoringProvider_Update.cs | 373 +++++++------- 2 files changed, 426 insertions(+), 428 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index a9d608de0ffbe..2456373424c4d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -24,310 +24,309 @@ using Microsoft.CodeAnalysis.Shared.Naming; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter +namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; + +using static InitializeParameterHelpers; +using static InitializeParameterHelpersCore; +using static SyntaxFactory; + +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] +internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider { - using static InitializeParameterHelpers; - using static InitializeParameterHelpersCore; - using static SyntaxFactory; + [ImportingConstructor] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + public CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider() + { + } - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] - internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider() - { - } + var (document, _, cancellationToken) = context; - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var (document, _, cancellationToken) = context; - - var selectedParameter = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); - if (selectedParameter == null) - return; - - if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax(kind: SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration) typeDeclaration }) - return; - - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var parameter = semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); - if (parameter?.Name is null or "") - return; - - if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) - return; - - // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing - // more for us to do. - var compilation = semanticModel.Compilation; - var (initializerValue, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); - if (initializerValue != null) - return; - - // Haven't initialized any fields/properties with this parameter. Offer to assign to an existing matching - // field/prop if we can find one, or add a new field/prop if we can't. - var fallbackOptions = context.Options; - var rules = await document.GetNamingRulesAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); - if (parameterNameParts.BaseName == "") - return; - - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - - var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); - var refactorings = fieldOrProperty == null - ? HandleNoExistingFieldOrProperty() - : HandleExistingFieldOrProperty(); - - context.RegisterRefactorings(refactorings.ToImmutableArray(), context.Span); + var selectedParameter = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); + if (selectedParameter == null) return; - (ISymbol?, bool isThrowNotImplementedProperty) TryFindMatchingUninitializedFieldOrPropertySymbol() - { - // Look for a field/property that really looks like it corresponds to this parameter. Use a variety of - // heuristics around the name/type to see if this is a match. + if (selectedParameter.Parent is not ParameterListSyntax { Parent: TypeDeclarationSyntax(kind: SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration) typeDeclaration }) + return; - var parameterWords = parameterNameParts.BaseNameParts; - var containingType = parameter.ContainingType; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var parameter = semanticModel.GetRequiredDeclaredSymbol(selectedParameter, cancellationToken); + if (parameter?.Name is null or "") + return; - // Walk through the naming rules against this parameter's name to see what name the user would like for - // it as a member in this type. Note that we have some fallback rules that use the standard conventions - // around properties /fields so that can still find things even if the user has no naming preferences - // set. + if (parameter.ContainingSymbol is not IMethodSymbol { MethodKind: MethodKind.Constructor } constructor) + return; - foreach (var rule in rules) - { - var memberName = rule.NamingStyle.CreateName(parameterWords); - foreach (var memberWithName in containingType.GetMembers(memberName)) - { - // We found members in our type with that name. If it's a writable field that we could assign - // this parameter to, and it's not already been assigned to, then this field is a good candidate - // for us to hook up to. - if (memberWithName is IFieldSymbol { IsConst: false, DeclaringSyntaxReferences: [var syntaxRef1, ..] } field && - IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && - syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) - { - return (field, isThrowNotImplementedProperty: false); - } + // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing + // more for us to do. + var compilation = semanticModel.Compilation; + var (initializerValue, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); + if (initializerValue != null) + return; - // If it's a writable property that we could assign this parameter to, and it's not already been - // assigned to, then this property is a good candidate for us to hook up to. - if (memberWithName is IPropertySymbol { DeclaringSyntaxReferences: [var syntaxRef2, ..] } property && - IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && - syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) - { - // We also allow assigning into a property of the form `=> throw new - // NotImplementedException()`. That way users can easily spit out those methods, but then - // convert them to be normal properties with ease. - if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) - return (property, isThrowNotImplementedProperty: true); - - if (property.IsWritableInConstructor()) - return (property, isThrowNotImplementedProperty: false); - } - } - } + // Haven't initialized any fields/properties with this parameter. Offer to assign to an existing matching + // field/prop if we can find one, or add a new field/prop if we can't. + var fallbackOptions = context.Options; + var rules = await document.GetNamingRulesAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); + if (parameterNameParts.BaseName == "") + return; - // Couldn't find any existing member. Just return nothing so we can offer to create a member for them. - return default; - } + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - static CodeAction CreateCodeAction(string title, Func> createSolution) - => CodeAction.Create(title, createSolution, title); + var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); + var refactorings = fieldOrProperty == null + ? HandleNoExistingFieldOrProperty() + : HandleExistingFieldOrProperty(); - IEnumerable HandleExistingFieldOrProperty() - { - // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. - yield return CreateCodeAction( - string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 : FeaturesResources.Initialize_property_0, fieldOrProperty.Name), - cancellationToken => AddSingleSymbolInitializationAsync( - document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); - } + context.RegisterRefactorings(refactorings.ToImmutableArray(), context.Span); + return; - IEnumerable HandleNoExistingFieldOrProperty() - { - // Didn't find a field/prop that this parameter could be assigned to. Offer to create new one and assign to that. + (ISymbol?, bool isThrowNotImplementedProperty) TryFindMatchingUninitializedFieldOrPropertySymbol() + { + // Look for a field/property that really looks like it corresponds to this parameter. Use a variety of + // heuristics around the name/type to see if this is a match. - // Check if the surrounding parameters are assigned to another field in this class. If so, offer to - // make this parameter into a field as well. Otherwise, default to generating a property - var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); - var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); + var parameterWords = parameterNameParts.BaseNameParts; + var containingType = parameter.ContainingType; - yield return siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction; - yield return siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction; + // Walk through the naming rules against this parameter's name to see what name the user would like for + // it as a member in this type. Note that we have some fallback rules that use the standard conventions + // around properties /fields so that can still find things even if the user has no naming preferences + // set. - var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); - if (allFieldsAction != null && allPropertiesAction != null) + foreach (var rule in rules) + { + var memberName = rule.NamingStyle.CreateName(parameterWords); + foreach (var memberWithName in containingType.GetMembers(memberName)) { - yield return siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction; - yield return siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction; + // We found members in our type with that name. If it's a writable field that we could assign + // this parameter to, and it's not already been assigned to, then this field is a good candidate + // for us to hook up to. + if (memberWithName is IFieldSymbol { IsConst: false, DeclaringSyntaxReferences: [var syntaxRef1, ..] } field && + IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && + syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) + { + return (field, isThrowNotImplementedProperty: false); + } + + // If it's a writable property that we could assign this parameter to, and it's not already been + // assigned to, then this property is a good candidate for us to hook up to. + if (memberWithName is IPropertySymbol { DeclaringSyntaxReferences: [var syntaxRef2, ..] } property && + IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) && + syntaxRef2.GetSyntax(cancellationToken) is PropertyDeclarationSyntax { Initializer: null }) + { + // We also allow assigning into a property of the form `=> throw new + // NotImplementedException()`. That way users can easily spit out those methods, but then + // convert them to be normal properties with ease. + if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) + return (property, isThrowNotImplementedProperty: true); + + if (property.IsWritableInConstructor()) + return (property, isThrowNotImplementedProperty: false); + } } } - ISymbol? TryFindSiblingFieldOrProperty() - { - foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) - { - var (_, sibling) = TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, cancellationToken); - if (sibling != null) - return sibling; - } + // Couldn't find any existing member. Just return nothing so we can offer to create a member for them. + return default; + } - return null; - } + static CodeAction CreateCodeAction(string title, Func> createSolution) + => CodeAction.Create(title, createSolution, title); - (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() - { - var field = CreateField(parameter); - var property = CreateProperty(parameter); + IEnumerable HandleExistingFieldOrProperty() + { + // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. + yield return CreateCodeAction( + string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 : FeaturesResources.Initialize_property_0, fieldOrProperty.Name), + cancellationToken => AddSingleSymbolInitializationAsync( + document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + } - // we're generating the field or property, so we don't have to handle throwing versions of them. - var isThrowNotImplementedProperty = false; + IEnumerable HandleNoExistingFieldOrProperty() + { + // Didn't find a field/prop that this parameter could be assigned to. Offer to create new one and assign to that. - var fieldAction = CreateCodeAction( - string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); - var propertyAction = CreateCodeAction( - string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + // Check if the surrounding parameters are assigned to another field in this class. If so, offer to + // make this parameter into a field as well. Otherwise, default to generating a property + var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); + var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); - return (fieldAction, propertyAction); - } + yield return siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction; + yield return siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction; - (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions() + var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); + if (allFieldsAction != null && allPropertiesAction != null) { - var parameters = GetParametersWithoutAssociatedMembers(); - if (parameters.Length < 2) - return default; - - var allFieldsAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); - var allPropertiesAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); - - return (allFieldsAction, allPropertiesAction); + yield return siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction; + yield return siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction; } + } - ImmutableArray GetParametersWithoutAssociatedMembers() + ISymbol? TryFindSiblingFieldOrProperty() + { + foreach (var (siblingParam, _) in InitializeParameterHelpersCore.GetSiblingParameters(parameter)) { - using var result = TemporaryArray.Empty; + var (_, sibling) = TryFindFieldOrPropertyInitializerValue(compilation, siblingParam, cancellationToken); + if (sibling != null) + return sibling; + } - foreach (var parameter in constructor.Parameters) - { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); - if (parameterNameParts.BaseName == "") - continue; + return null; + } - var (assignmentOp, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); - if (assignmentOp != null) - continue; + (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() + { + var field = CreateField(parameter); + var property = CreateProperty(parameter); - result.Add(parameter); - } + // we're generating the field or property, so we don't have to handle throwing versions of them. + var isThrowNotImplementedProperty = false; - return result.ToImmutableAndClear(); - } + var fieldAction = CreateCodeAction( + string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), + cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + var propertyAction = CreateCodeAction( + string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), + cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + + return (fieldAction, propertyAction); + } + + (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions() + { + var parameters = GetParametersWithoutAssociatedMembers(); + if (parameters.Length < 2) + return default; + + var allFieldsAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_fields, + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); + var allPropertiesAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_properties, + cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); - ISymbol CreateField(IParameterSymbol parameter) + return (allFieldsAction, allPropertiesAction); + } + + ImmutableArray GetParametersWithoutAssociatedMembers() + { + using var result = TemporaryArray.Empty; + + foreach (var parameter in constructor.Parameters) { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; - var accessibilityLevel = formattingOptions.AccessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault - ? Accessibility.NotApplicable - : Accessibility.Private; + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); + if (parameterNameParts.BaseName == "") + continue; - foreach (var rule in rules) - { - if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) - { - return CodeGenerationSymbolFactory.CreateFieldSymbol( - attributes: default, - accessibilityLevel, - DeclarationModifiers.ReadOnly, - parameter.Type, - name: GenerateUniqueName(parameter, parameterNameParts, rule), - initializer: IdentifierName(parameter.Name.EscapeIdentifier())); - } - } + var (assignmentOp, _) = TryFindFieldOrPropertyInitializerValue(compilation, parameter, cancellationToken); + if (assignmentOp != null) + continue; - // We place a special rule in s_builtInRules that matches all fields. So we should - // always find a matching rule. - throw ExceptionUtilities.Unreachable(); + result.Add(parameter); } - ISymbol CreateProperty(IParameterSymbol parameter) + return result.ToImmutableAndClear(); + } + + ISymbol CreateField(IParameterSymbol parameter) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + var accessibilityLevel = formattingOptions.AccessibilityModifiersRequired is AccessibilityModifiersRequired.Never or AccessibilityModifiersRequired.OmitIfDefault + ? Accessibility.NotApplicable + : Accessibility.Private; + + foreach (var rule in rules) { - var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Field, Accessibility.Private)) + { + return CodeGenerationSymbolFactory.CreateFieldSymbol( + attributes: default, + accessibilityLevel, + DeclarationModifiers.ReadOnly, + parameter.Type, + name: GenerateUniqueName(parameter, parameterNameParts, rule), + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); + } + } + + // We place a special rule in s_builtInRules that matches all fields. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); + } + + ISymbol CreateProperty(IParameterSymbol parameter) + { + var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules).BaseNameParts; - foreach (var rule in rules) + foreach (var rule in rules) + { + if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) { - if (rule.SymbolSpecification.AppliesTo(SymbolKind.Property, Accessibility.Public)) - { - return CodeGenerationSymbolFactory.CreatePropertySymbol( - containingType: null, + return CodeGenerationSymbolFactory.CreatePropertySymbol( + containingType: null, + attributes: default, + Accessibility.Public, + modifiers: default, + parameter.Type, + RefKind.None, + explicitInterfaceImplementations: default, + name: GenerateUniqueName(parameter, parameterNameParts, rule), + parameters: default, + getMethod: CodeGenerationSymbolFactory.CreateAccessorSymbol( attributes: default, Accessibility.Public, - modifiers: default, - parameter.Type, - RefKind.None, - explicitInterfaceImplementations: default, - name: GenerateUniqueName(parameter, parameterNameParts, rule), - parameters: default, - getMethod: CodeGenerationSymbolFactory.CreateAccessorSymbol( - attributes: default, - Accessibility.Public, - statements: default), - setMethod: null, - initializer: IdentifierName(parameter.Name.EscapeIdentifier())); - } + statements: default), + setMethod: null, + initializer: IdentifierName(parameter.Name.EscapeIdentifier())); } - - // We place a special rule in s_builtInRules that matches all properties. So we should - // always find a matching rule. - throw ExceptionUtilities.Unreachable(); } + + // We place a special rule in s_builtInRules that matches all properties. So we should + // always find a matching rule. + throw ExceptionUtilities.Unreachable(); } + } - private static (IOperation? initializer, ISymbol? fieldOrProperty) TryFindFieldOrPropertyInitializerValue( - Compilation compilation, - IParameterSymbol parameter, - CancellationToken cancellationToken) + private static (IOperation? initializer, ISymbol? fieldOrProperty) TryFindFieldOrPropertyInitializerValue( + Compilation compilation, + IParameterSymbol parameter, + CancellationToken cancellationToken) + { + foreach (var group in parameter.ContainingType.DeclaringSyntaxReferences.GroupBy(r => r.SyntaxTree)) { - foreach (var group in parameter.ContainingType.DeclaringSyntaxReferences.GroupBy(r => r.SyntaxTree)) + var semanticModel = compilation.GetSemanticModel(group.Key); + foreach (var syntaxReference in group) { - var semanticModel = compilation.GetSemanticModel(group.Key); - foreach (var syntaxReference in group) + if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) { - if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) + foreach (var member in typeDeclaration.Members) { - foreach (var member in typeDeclaration.Members) + if (member is PropertyDeclarationSyntax { Initializer.Value: var propertyInitializer } propertyDeclaration) { - if (member is PropertyDeclarationSyntax { Initializer.Value: var propertyInitializer } propertyDeclaration) - { - var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); - if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - return (operation, semanticModel.GetRequiredDeclaredSymbol(propertyDeclaration, cancellationToken)); - } - else if (member is FieldDeclarationSyntax field) + var operation = semanticModel.GetOperation(propertyInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + return (operation, semanticModel.GetRequiredDeclaredSymbol(propertyDeclaration, cancellationToken)); + } + else if (member is FieldDeclarationSyntax field) + { + foreach (var varDecl in field.Declaration.Variables) { - foreach (var varDecl in field.Declaration.Variables) + if (varDecl is { Initializer.Value: var fieldInitializer }) { - if (varDecl is { Initializer.Value: var fieldInitializer }) - { - var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); - if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) - return (operation, semanticModel.GetRequiredDeclaredSymbol(varDecl, cancellationToken)); - } + var operation = semanticModel.GetOperation(fieldInitializer, cancellationToken); + if (IsParameterReferenceOrCoalesceOfParameterReference(operation, parameter)) + return (operation, semanticModel.GetRequiredDeclaredSymbol(varDecl, cancellationToken)); } } } } } } - - return default; } + + return default; } } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index df3d6d8d325f9..af77f418baf9a 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -17,232 +17,231 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter -{ - using static InitializeParameterHelpers; - using static InitializeParameterHelpersCore; - using static SyntaxFactory; +namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; + +using static InitializeParameterHelpers; +using static InitializeParameterHelpersCore; +using static SyntaxFactory; - internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider +internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider +{ + /// This functions are the workhorses that actually go and handle each parameter (either creating a + /// field/property for it, or updates an existing field/property with it). They are extracted out from the rest + /// as we do not want it capturing anything. Specifically, AddSingleSymbolInitializationAsync is called in a + /// loop from AddAllSymbolInitializationsAsync for each parameter we're processing. For each, we produce new + /// solution snapshots and thus must ensure we're always pointing at the new view of the world, not anything + /// from the original view. + + private static async Task AddAllSymbolInitializationsAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + ImmutableArray parameters, + ImmutableArray fieldsOrProperties, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) { - /// This functions are the workhorses that actually go and handle each parameter (either creating a - /// field/property for it, or updates an existing field/property with it). They are extracted out from the rest - /// as we do not want it capturing anything. Specifically, AddSingleSymbolInitializationAsync is called in a - /// loop from AddAllSymbolInitializationsAsync for each parameter we're processing. For each, we produce new - /// solution snapshots and thus must ensure we're always pointing at the new view of the world, not anything - /// from the original view. - - private static async Task AddAllSymbolInitializationsAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - ImmutableArray parameters, - ImmutableArray fieldsOrProperties, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - Debug.Assert(parameters.Length >= 2); - Debug.Assert(fieldsOrProperties.Length > 0); - Debug.Assert(parameters.Length == fieldsOrProperties.Length); + Debug.Assert(parameters.Length >= 2); + Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length == fieldsOrProperties.Length); - // Process each param+field/prop in order. Apply the pair to the document getting the updated document. - // Then find all the current data in that updated document and move onto the next pair. + // Process each param+field/prop in order. Apply the pair to the document getting the updated document. + // Then find all the current data in that updated document and move onto the next pair. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var trackedRoot = root.TrackNodes(typeDeclaration); - var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; + var trackedRoot = root.TrackNodes(typeDeclaration); + var currentSolution = document.WithSyntaxRoot(trackedRoot).Project.Solution; - foreach (var (parameter, fieldOrProperty) in parameters.Zip(fieldsOrProperties, static (a, b) => (a, b))) - { - var currentDocument = currentSolution.GetRequiredDocument(document.Id); - var currentCompilation = await currentDocument.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); - if (currentTypeDeclaration == null) - continue; - - var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); - if (currentParameter == null) - continue; - - // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. - - currentSolution = await AddSingleSymbolInitializationAsync( - currentDocument, - currentTypeDeclaration, - currentParameter, - fieldOrProperty, - isThrowNotImplementedProperty: false, - fallbackOptions, - cancellationToken).ConfigureAwait(false); - } - - return currentSolution; + foreach (var (parameter, fieldOrProperty) in parameters.Zip(fieldsOrProperties, static (a, b) => (a, b))) + { + var currentDocument = currentSolution.GetRequiredDocument(document.Id); + var currentCompilation = await currentDocument.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var currentRoot = await currentDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var currentTypeDeclaration = currentRoot.GetCurrentNode(typeDeclaration); + if (currentTypeDeclaration == null) + continue; + + var currentParameter = (IParameterSymbol?)parameter.GetSymbolKey(cancellationToken).Resolve(currentCompilation, cancellationToken: cancellationToken).GetAnySymbol(); + if (currentParameter == null) + continue; + + // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. + + currentSolution = await AddSingleSymbolInitializationAsync( + currentDocument, + currentTypeDeclaration, + currentParameter, + fieldOrProperty, + isThrowNotImplementedProperty: false, + fallbackOptions, + cancellationToken).ConfigureAwait(false); } - private static async Task AddSingleSymbolInitializationAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - ISymbol fieldOrProperty, - bool isThrowNotImplementedProperty, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - var project = document.Project; - var solution = project.Solution; - var services = solution.Services; + return currentSolution; + } - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = document.DocumentState.ParseOptions!; + private static async Task AddSingleSymbolInitializationAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + bool isThrowNotImplementedProperty, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + var project = document.Project; + var solution = project.Solution; + var services = solution.Services; - var solutionEditor = new SolutionEditor(solution); - var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var codeGenerator = document.GetRequiredLanguageService(); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var parseOptions = document.DocumentState.ParseOptions!; - // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references - // to this primary constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync().ConfigureAwait(false); + var solutionEditor = new SolutionEditor(solution); + var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var codeGenerator = document.GetRequiredLanguageService(); - // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - return fieldOrProperty.ContainingType == null - ? await AddFieldOrPropertyAsync().ConfigureAwait(false) - : await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references + // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + await UpdateParameterReferencesAsync().ConfigureAwait(false); - async Task UpdateParameterReferencesAsync() - { - var namedType = parameter.ContainingType; - var documents = namedType.DeclaringSyntaxReferences - .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) - .ToImmutableHashSet(); + // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. + return fieldOrProperty.ContainingType == null + ? await AddFieldOrPropertyAsync().ConfigureAwait(false) + : await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + + async Task UpdateParameterReferencesAsync() + { + var namedType = parameter.ContainingType; + var documents = namedType.DeclaringSyntaxReferences + .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) + .ToImmutableHashSet(); - var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); - foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); + foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + { + var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); + foreach (var location in group) { - var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); - foreach (var location in group) + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && + identifierName.Identifier.ValueText == parameter.Name) { - var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); - if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && - identifierName.Identifier.ValueText == parameter.Name) - { - // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' - // just because we're generating a new property 'X' for the parameter to be assigned to. - editor.ReplaceNode( - identifierName, - IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); - } + // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' + // just because we're generating a new property 'X' for the parameter to be assigned to. + editor.ReplaceNode( + identifierName, + IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); } } } + } - async ValueTask AddFieldOrPropertyAsync() + async ValueTask AddFieldOrPropertyAsync() + { + // We're generating a new field/property. Place into the containing type, ideally before/after a + // relevant existing member. + var (sibling, siblingSyntax, addContext) = fieldOrProperty switch { - // We're generating a new field/property. Place into the containing type, ideally before/after a - // relevant existing member. - var (sibling, siblingSyntax, addContext) = fieldOrProperty switch + IPropertySymbol => GetAddContext(), + IFieldSymbol => GetAddContext(), + _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), + }; + + var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; + + var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + preferredTypeDeclaration, + (currentTypeDecl, _) => { - IPropertySymbol => GetAddContext(), - IFieldSymbol => GetAddContext(), - _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), - }; - - var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; - - var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - preferredTypeDeclaration, - (currentTypeDecl, _) => + if (fieldOrProperty is IPropertySymbol property) { - if (fieldOrProperty is IPropertySymbol property) - { - return codeGenerator.AddProperty( - currentTypeDecl, property, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else if (fieldOrProperty is IFieldSymbol field) - { - return codeGenerator.AddField( - currentTypeDecl, field, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - }); - - return solutionEditor.GetChangedSolution(); - } + return codeGenerator.AddProperty( + currentTypeDecl, property, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else if (fieldOrProperty is IFieldSymbol field) + { + return codeGenerator.AddField( + currentTypeDecl, field, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + }); + + return solutionEditor.GetChangedSolution(); + } - (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol + (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol + { + foreach (var (sibling, before) in GetSiblingParameters(parameter)) { - foreach (var (sibling, before) in GetSiblingParameters(parameter)) - { - var (initializer, fieldOrProperty) = TryFindFieldOrPropertyInitializerValue( - compilation, sibling, cancellationToken); + var (initializer, fieldOrProperty) = TryFindFieldOrPropertyInitializerValue( + compilation, sibling, cancellationToken); - if (initializer != null && - fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) - { - var syntax = syntaxReference.GetSyntax(cancellationToken); - return (symbol, syntax, before - ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) - : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); - } + if (initializer != null && + fieldOrProperty is TSymbol { DeclaringSyntaxReferences: [var syntaxReference, ..] } symbol) + { + var syntax = syntaxReference.GetSyntax(cancellationToken); + return (symbol, syntax, before + ? new CodeGenerationContext(afterThisLocation: syntax.GetLocation()) + : new CodeGenerationContext(beforeThisLocation: syntax.GetLocation())); } - - return (symbol: null, syntax: null, CodeGenerationContext.Default); } - async ValueTask UpdateFieldOrPropertyAsync() + return (symbol: null, syntax: null, CodeGenerationContext.Default); + } + + async ValueTask UpdateFieldOrPropertyAsync() + { + // We're updating an exiting field/prop. + if (fieldOrProperty is IPropertySymbol property) { - // We're updating an exiting field/prop. - if (fieldOrProperty is IPropertySymbol property) + foreach (var syntaxRef in property.DeclaringSyntaxReferences) { - foreach (var syntaxRef in property.DeclaringSyntaxReferences) + if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) { - if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) - { - var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - - // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. - var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; - editor.ReplaceNode( - propertyDeclaration, - newPropertyDeclaration.WithoutTrailingTrivia() - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) - .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - } + var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. + var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; + editor.ReplaceNode( + propertyDeclaration, + newPropertyDeclaration.WithoutTrailingTrivia() + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) + .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); } } - else if (fieldOrProperty is IFieldSymbol field) + } + else if (fieldOrProperty is IFieldSymbol field) + { + foreach (var syntaxRef in field.DeclaringSyntaxReferences) { - foreach (var syntaxRef in field.DeclaringSyntaxReferences) + if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) { - if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) - { - var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - variableDeclarator, - variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - break; - } + var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + variableDeclarator, + variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + break; } } - else - { - throw ExceptionUtilities.Unreachable(); - } - - return solutionEditor.GetChangedSolution(); } + else + { + throw ExceptionUtilities.Unreachable(); + } + + return solutionEditor.GetChangedSolution(); } } } From 368b3665b5dbedf1104900e9ab7500ede8514316 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:03:36 -0700 Subject: [PATCH 50/59] Simplify --- ...imaryConstructorParameterCodeRefactoringProvider_Update.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index af77f418baf9a..c434050e21194 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -236,10 +236,6 @@ async ValueTask UpdateFieldOrPropertyAsync() } } } - else - { - throw ExceptionUtilities.Unreachable(); - } return solutionEditor.GetChangedSolution(); } From bd795884b302753dbbea5e0d4453548fc4c283b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:40:14 -0700 Subject: [PATCH 51/59] break out --- ...tructorParameterCodeRefactoringProvider.cs | 15 ++-- ...ParameterCodeRefactoringProvider_Update.cs | 78 +++++++++++++++---- 2 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 2456373424c4d..73d3ec5c6c7b2 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -141,8 +141,8 @@ IEnumerable HandleExistingFieldOrProperty() // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. yield return CreateCodeAction( string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 : FeaturesResources.Initialize_property_0, fieldOrProperty.Name), - cancellationToken => AddSingleSymbolInitializationAsync( - document, typeDeclaration, parameter, fieldOrProperty, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + cancellationToken => UpdateExistingMemberAsync( + document, parameter, fieldOrProperty, isThrowNotImplementedProperty, cancellationToken)); } IEnumerable HandleNoExistingFieldOrProperty() @@ -182,15 +182,12 @@ IEnumerable HandleNoExistingFieldOrProperty() var field = CreateField(parameter); var property = CreateProperty(parameter); - // we're generating the field or property, so we don't have to handle throwing versions of them. - var isThrowNotImplementedProperty = false; - var fieldAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, field, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + cancellationToken => AddSingleMemberAsync(document, typeDeclaration, parameter, field, fallbackOptions, cancellationToken)); var propertyAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - cancellationToken => AddSingleSymbolInitializationAsync(document, typeDeclaration, parameter, property, isThrowNotImplementedProperty, fallbackOptions, cancellationToken)); + cancellationToken => AddSingleMemberAsync(document, typeDeclaration, parameter, property, fallbackOptions, cancellationToken)); return (fieldAction, propertyAction); } @@ -203,10 +200,10 @@ IEnumerable HandleNoExistingFieldOrProperty() var allFieldsAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); var allPropertiesAction = CodeAction.Create( FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddAllSymbolInitializationsAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); return (allFieldsAction, allPropertiesAction); } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index c434050e21194..ded3c581db4ac 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -25,14 +25,15 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider { - /// This functions are the workhorses that actually go and handle each parameter (either creating a - /// field/property for it, or updates an existing field/property with it). They are extracted out from the rest - /// as we do not want it capturing anything. Specifically, AddSingleSymbolInitializationAsync is called in a - /// loop from AddAllSymbolInitializationsAsync for each parameter we're processing. For each, we produce new - /// solution snapshots and thus must ensure we're always pointing at the new view of the world, not anything - /// from the original view. - - private static async Task AddAllSymbolInitializationsAsync( + /// + /// These functions are the workhorses that actually go and handle each parameter (either creating a field/property + /// for it, or updates an existing field/property with it). They are extracted out from the rest as we do not want + /// it capturing anything. Specifically, is called in a loop from for each parameter we're processing. For each, we produce new solution + /// snapshots and thus must ensure we're always pointing at the new view of the world, not anything from the + /// original view. + /// + private static async Task AddMultipleMembersAsync( Document document, TypeDeclarationSyntax typeDeclaration, ImmutableArray parameters, @@ -68,12 +69,11 @@ private static async Task AddAllSymbolInitializationsAsync( // fieldOrProperty is a new member. So we don't have to track it to this edit we're making. - currentSolution = await AddSingleSymbolInitializationAsync( + currentSolution = await AddSingleMemberAsync( currentDocument, currentTypeDeclaration, currentParameter, fieldOrProperty, - isThrowNotImplementedProperty: false, fallbackOptions, cancellationToken).ConfigureAwait(false); } @@ -81,12 +81,11 @@ private static async Task AddAllSymbolInitializationsAsync( return currentSolution; } - private static async Task AddSingleSymbolInitializationAsync( + private static async Task AddSingleMemberAsync( Document document, TypeDeclarationSyntax typeDeclaration, IParameterSymbol parameter, ISymbol fieldOrProperty, - bool isThrowNotImplementedProperty, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { @@ -106,9 +105,7 @@ private static async Task AddSingleSymbolInitializationAsync( await UpdateParameterReferencesAsync().ConfigureAwait(false); // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - return fieldOrProperty.ContainingType == null - ? await AddFieldOrPropertyAsync().ConfigureAwait(false) - : await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + return await AddFieldOrPropertyAsync().ConfigureAwait(false); async Task UpdateParameterReferencesAsync() { @@ -198,6 +195,57 @@ async ValueTask AddFieldOrPropertyAsync() return (symbol: null, syntax: null, CodeGenerationContext.Default); } + } + + private static async Task UpdateExistingMemberAsync( + Document document, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + bool isThrowNotImplementedProperty, + CancellationToken cancellationToken) + { + var project = document.Project; + var solution = project.Solution; + var services = solution.Services; + + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var parseOptions = document.DocumentState.ParseOptions!; + + var solutionEditor = new SolutionEditor(solution); + + // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references + // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + await UpdateParameterReferencesAsync().ConfigureAwait(false); + + // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. + return await UpdateFieldOrPropertyAsync().ConfigureAwait(false); + + async Task UpdateParameterReferencesAsync() + { + var namedType = parameter.ContainingType; + var documents = namedType.DeclaringSyntaxReferences + .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) + .ToImmutableHashSet(); + + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); + foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + { + var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); + foreach (var location in group) + { + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && + identifierName.Identifier.ValueText == parameter.Name) + { + // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' + // just because we're generating a new property 'X' for the parameter to be assigned to. + editor.ReplaceNode( + identifierName, + IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); + } + } + } + } async ValueTask UpdateFieldOrPropertyAsync() { From 47c840717814355145bdd0edaf2c524d2eb9f23c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:42:48 -0700 Subject: [PATCH 52/59] break out --- ...ParameterCodeRefactoringProvider_Update.cs | 96 +++++++------------ 1 file changed, 36 insertions(+), 60 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index ded3c581db4ac..af96177f67175 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -102,38 +102,12 @@ private static async Task AddSingleMemberAsync( // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references // to this primary constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync().ConfigureAwait(false); + await UpdateParameterReferencesAsync( + solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. return await AddFieldOrPropertyAsync().ConfigureAwait(false); - async Task UpdateParameterReferencesAsync() - { - var namedType = parameter.ContainingType; - var documents = namedType.DeclaringSyntaxReferences - .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) - .ToImmutableHashSet(); - - var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); - foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) - { - var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); - foreach (var location in group) - { - var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); - if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && - identifierName.Identifier.ValueText == parameter.Name) - { - // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' - // just because we're generating a new property 'X' for the parameter to be assigned to. - editor.ReplaceNode( - identifierName, - IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); - } - } - } - } - async ValueTask AddFieldOrPropertyAsync() { // We're generating a new field/property. Place into the containing type, ideally before/after a @@ -197,6 +171,38 @@ async ValueTask AddFieldOrPropertyAsync() } } + private static async Task UpdateParameterReferencesAsync( + SolutionEditor solutionEditor, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + CancellationToken cancellationToken) + { + var solution = solutionEditor.OriginalSolution; + var namedType = parameter.ContainingType; + var documents = namedType.DeclaringSyntaxReferences + .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) + .ToImmutableHashSet(); + + var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); + foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) + { + var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); + foreach (var location in group) + { + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && + identifierName.Identifier.ValueText == parameter.Name) + { + // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' + // just because we're generating a new property 'X' for the parameter to be assigned to. + editor.ReplaceNode( + identifierName, + IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); + } + } + } + } + private static async Task UpdateExistingMemberAsync( Document document, IParameterSymbol parameter, @@ -206,47 +212,17 @@ private static async Task UpdateExistingMemberAsync( { var project = document.Project; var solution = project.Solution; - var services = solution.Services; - - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = document.DocumentState.ParseOptions!; var solutionEditor = new SolutionEditor(solution); // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references // to this primary constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync().ConfigureAwait(false); + await UpdateParameterReferencesAsync( + solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. return await UpdateFieldOrPropertyAsync().ConfigureAwait(false); - async Task UpdateParameterReferencesAsync() - { - var namedType = parameter.ContainingType; - var documents = namedType.DeclaringSyntaxReferences - .Select(r => solution.GetRequiredDocument(r.SyntaxTree)) - .ToImmutableHashSet(); - - var references = await SymbolFinder.FindReferencesAsync(parameter, solution, documents, cancellationToken).ConfigureAwait(false); - foreach (var group in references.SelectMany(r => r.Locations.Where(loc => !loc.IsImplicit).GroupBy(loc => loc.Document))) - { - var editor = await solutionEditor.GetDocumentEditorAsync(group.Key.Id, cancellationToken).ConfigureAwait(false); - foreach (var location in group) - { - var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); - if (node is IdentifierNameSyntax { Parent: not NameColonSyntax } identifierName && - identifierName.Identifier.ValueText == parameter.Name) - { - // we may have things like `new MyType(x: ...)` we don't want to update `x` there to 'X' - // just because we're generating a new property 'X' for the parameter to be assigned to. - editor.ReplaceNode( - identifierName, - IdentifierName(fieldOrProperty.Name.EscapeIdentifier()).WithTriviaFrom(identifierName)); - } - } - } - } - async ValueTask UpdateFieldOrPropertyAsync() { // We're updating an exiting field/prop. From 0d07ce35f2cde38bba69cfdb94ffaac3588ad894 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:44:44 -0700 Subject: [PATCH 53/59] Simplify --- ...ParameterCodeRefactoringProvider_Update.cs | 144 ++++++++---------- 1 file changed, 66 insertions(+), 78 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index af96177f67175..b9b25970b19a7 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -100,55 +100,49 @@ private static async Task AddSingleMemberAsync( var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var codeGenerator = document.GetRequiredLanguageService(); - // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references - // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + // We're assigning the parameter to a new field/prop . Convert all existing references to this primary + // constructor parameter (within this type) to refer to the field/prop now instead. await UpdateParameterReferencesAsync( solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); - // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - return await AddFieldOrPropertyAsync().ConfigureAwait(false); - - async ValueTask AddFieldOrPropertyAsync() + // We're generating a new field/property. Place into the containing type, ideally before/after a + // relevant existing member. + var (sibling, siblingSyntax, addContext) = fieldOrProperty switch { - // We're generating a new field/property. Place into the containing type, ideally before/after a - // relevant existing member. - var (sibling, siblingSyntax, addContext) = fieldOrProperty switch + IPropertySymbol => GetAddContext(), + IFieldSymbol => GetAddContext(), + _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), + }; + + var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; + + var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + preferredTypeDeclaration, + (currentTypeDecl, _) => { - IPropertySymbol => GetAddContext(), - IFieldSymbol => GetAddContext(), - _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), - }; - - var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; - - var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - preferredTypeDeclaration, - (currentTypeDecl, _) => + if (fieldOrProperty is IPropertySymbol property) { - if (fieldOrProperty is IPropertySymbol property) - { - return codeGenerator.AddProperty( - currentTypeDecl, property, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else if (fieldOrProperty is IFieldSymbol field) - { - return codeGenerator.AddField( - currentTypeDecl, field, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - }); - - return solutionEditor.GetChangedSolution(); - } + return codeGenerator.AddProperty( + currentTypeDecl, property, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else if (fieldOrProperty is IFieldSymbol field) + { + return codeGenerator.AddField( + currentTypeDecl, field, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + }); + + return solutionEditor.GetChangedSolution(); (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol { @@ -215,53 +209,47 @@ private static async Task UpdateExistingMemberAsync( var solutionEditor = new SolutionEditor(solution); - // We're assigning the parameter to a field/prop (either new or existing). Convert all existing references - // to this primary constructor parameter (within this type) to refer to the field/prop now instead. + // We're assigning the parameter to a field/prop. Convert all existing references to this primary constructor + // parameter (within this type) to refer to the field/prop now instead. await UpdateParameterReferencesAsync( solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); - // Now, either add the new field/prop, or update the existing one by assigning the parameter to it. - return await UpdateFieldOrPropertyAsync().ConfigureAwait(false); - - async ValueTask UpdateFieldOrPropertyAsync() + // We're updating an exiting field/prop. + if (fieldOrProperty is IPropertySymbol property) { - // We're updating an exiting field/prop. - if (fieldOrProperty is IPropertySymbol property) + foreach (var syntaxRef in property.DeclaringSyntaxReferences) { - foreach (var syntaxRef in property.DeclaringSyntaxReferences) + if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) { - if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration) - { - var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - - // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. - var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; - editor.ReplaceNode( - propertyDeclaration, - newPropertyDeclaration.WithoutTrailingTrivia() - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) - .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - } + var editingDocument = solution.GetRequiredDocument(propertyDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + + // If the user had a property that has 'throw NotImplementedException' in it, then remove those throws. + var newPropertyDeclaration = isThrowNotImplementedProperty ? RemoveThrowNotImplemented(propertyDeclaration) : propertyDeclaration; + editor.ReplaceNode( + propertyDeclaration, + newPropertyDeclaration.WithoutTrailingTrivia() + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) + .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); } } - else if (fieldOrProperty is IFieldSymbol field) + } + else if (fieldOrProperty is IFieldSymbol field) + { + foreach (var syntaxRef in field.DeclaringSyntaxReferences) { - foreach (var syntaxRef in field.DeclaringSyntaxReferences) + if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) { - if (syntaxRef.GetSyntax(cancellationToken) is VariableDeclaratorSyntax variableDeclarator) - { - var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - variableDeclarator, - variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); - break; - } + var editingDocument = solution.GetRequiredDocument(variableDeclarator.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + variableDeclarator, + variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + break; } } - - return solutionEditor.GetChangedSolution(); } + + return solutionEditor.GetChangedSolution(); } } From 4564f502abfda149a4b8f0ec849299b12c140ab5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:50:31 -0700 Subject: [PATCH 54/59] Simplify --- ...tructorParameterCodeRefactoringProvider.cs | 4 +- ...ParameterCodeRefactoringProvider_Update.cs | 129 +++++++++--------- 2 files changed, 63 insertions(+), 70 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 73d3ec5c6c7b2..dac8e72bacbd9 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -184,10 +184,10 @@ IEnumerable HandleNoExistingFieldOrProperty() var fieldAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - cancellationToken => AddSingleMemberAsync(document, typeDeclaration, parameter, field, fallbackOptions, cancellationToken)); + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(field), fallbackOptions, cancellationToken)); var propertyAction = CreateCodeAction( string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - cancellationToken => AddSingleMemberAsync(document, typeDeclaration, parameter, property, fallbackOptions, cancellationToken)); + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(property), fallbackOptions, cancellationToken)); return (fieldAction, propertyAction); } diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index b9b25970b19a7..4ec73fdace1c7 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -25,14 +25,6 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider { - /// - /// These functions are the workhorses that actually go and handle each parameter (either creating a field/property - /// for it, or updates an existing field/property with it). They are extracted out from the rest as we do not want - /// it capturing anything. Specifically, is called in a loop from for each parameter we're processing. For each, we produce new solution - /// snapshots and thus must ensure we're always pointing at the new view of the world, not anything from the - /// original view. - /// private static async Task AddMultipleMembersAsync( Document document, TypeDeclarationSyntax typeDeclaration, @@ -41,8 +33,8 @@ private static async Task AddMultipleMembersAsync( CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - Debug.Assert(parameters.Length >= 2); - Debug.Assert(fieldsOrProperties.Length > 0); + Debug.Assert(parameters.Length >= 1); + Debug.Assert(fieldsOrProperties.Length >= 1); Debug.Assert(parameters.Length == fieldsOrProperties.Length); // Process each param+field/prop in order. Apply the pair to the document getting the updated document. @@ -79,72 +71,73 @@ private static async Task AddMultipleMembersAsync( } return currentSolution; - } - private static async Task AddSingleMemberAsync( - Document document, - TypeDeclarationSyntax typeDeclaration, - IParameterSymbol parameter, - ISymbol fieldOrProperty, - CodeGenerationOptionsProvider fallbackOptions, - CancellationToken cancellationToken) - { - var project = document.Project; - var solution = project.Solution; - var services = solution.Services; + static async Task AddSingleMemberAsync( + Document document, + TypeDeclarationSyntax typeDeclaration, + IParameterSymbol parameter, + ISymbol fieldOrProperty, + CodeGenerationOptionsProvider fallbackOptions, + CancellationToken cancellationToken) + { + var project = document.Project; + var solution = project.Solution; + var services = solution.Services; - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var parseOptions = document.DocumentState.ParseOptions!; + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var parseOptions = document.DocumentState.ParseOptions!; - var solutionEditor = new SolutionEditor(solution); - var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var codeGenerator = document.GetRequiredLanguageService(); + var solutionEditor = new SolutionEditor(solution); + var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); + var codeGenerator = document.GetRequiredLanguageService(); - // We're assigning the parameter to a new field/prop . Convert all existing references to this primary - // constructor parameter (within this type) to refer to the field/prop now instead. - await UpdateParameterReferencesAsync( - solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); + // We're assigning the parameter to a new field/prop . Convert all existing references to this primary + // constructor parameter (within this type) to refer to the field/prop now instead. + await UpdateParameterReferencesAsync( + solutionEditor, parameter, fieldOrProperty, cancellationToken).ConfigureAwait(false); - // We're generating a new field/property. Place into the containing type, ideally before/after a - // relevant existing member. - var (sibling, siblingSyntax, addContext) = fieldOrProperty switch - { - IPropertySymbol => GetAddContext(), - IFieldSymbol => GetAddContext(), - _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), - }; - - var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; - - var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); - var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode( - preferredTypeDeclaration, - (currentTypeDecl, _) => + // We're generating a new field/property. Place into the containing type, ideally before/after a + // relevant existing member. + var (sibling, siblingSyntax, addContext) = fieldOrProperty switch { - if (fieldOrProperty is IPropertySymbol property) + IPropertySymbol => GetAddContext(compilation, parameter, cancellationToken), + IFieldSymbol => GetAddContext(compilation, parameter, cancellationToken), + _ => throw ExceptionUtilities.UnexpectedValue(fieldOrProperty), + }; + + var preferredTypeDeclaration = siblingSyntax?.GetAncestorOrThis() ?? typeDeclaration; + + var editingDocument = solution.GetRequiredDocument(preferredTypeDeclaration.SyntaxTree); + var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode( + preferredTypeDeclaration, + (currentTypeDecl, _) => { - return codeGenerator.AddProperty( - currentTypeDecl, property, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else if (fieldOrProperty is IFieldSymbol field) - { - return codeGenerator.AddField( - currentTypeDecl, field, - codeGenerator.GetInfo(addContext, options, parseOptions), - cancellationToken); - } - else - { - throw ExceptionUtilities.Unreachable(); - } - }); - - return solutionEditor.GetChangedSolution(); + if (fieldOrProperty is IPropertySymbol property) + { + return codeGenerator.AddProperty( + currentTypeDecl, property, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else if (fieldOrProperty is IFieldSymbol field) + { + return codeGenerator.AddField( + currentTypeDecl, field, + codeGenerator.GetInfo(addContext, options, parseOptions), + cancellationToken); + } + else + { + throw ExceptionUtilities.Unreachable(); + } + }); + + return solutionEditor.GetChangedSolution(); + } - (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext() where TSymbol : class, ISymbol + static (ISymbol? symbol, SyntaxNode? syntax, CodeGenerationContext context) GetAddContext( + Compilation compilation, IParameterSymbol parameter, CancellationToken cancellationToken) where TSymbol : class, ISymbol { foreach (var (sibling, before) in GetSiblingParameters(parameter)) { From a3fad6b4fc6c20174a29307d3349f0193f3144e0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:52:29 -0700 Subject: [PATCH 55/59] Extract --- ...aryConstructorParameterCodeRefactoringProvider_Update.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 4ec73fdace1c7..337556c5d209d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -201,6 +201,7 @@ private static async Task UpdateExistingMemberAsync( var solution = project.Solution; var solutionEditor = new SolutionEditor(solution); + var initializer = EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())); // We're assigning the parameter to a field/prop. Convert all existing references to this primary constructor // parameter (within this type) to refer to the field/prop now instead. @@ -223,7 +224,8 @@ await UpdateParameterReferencesAsync( propertyDeclaration, newPropertyDeclaration.WithoutTrailingTrivia() .WithSemicolonToken(Token(SyntaxKind.SemicolonToken).WithTrailingTrivia(newPropertyDeclaration.GetTrailingTrivia())) - .WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + .WithInitializer(initializer)); + break; } } } @@ -237,7 +239,7 @@ await UpdateParameterReferencesAsync( var editor = await solutionEditor.GetDocumentEditorAsync(editingDocument.Id, cancellationToken).ConfigureAwait(false); editor.ReplaceNode( variableDeclarator, - variableDeclarator.WithInitializer(EqualsValueClause(IdentifierName(parameter.Name.EscapeIdentifier())))); + variableDeclarator.WithInitializer(initializer)); break; } } From 1083bbd5258df0fa092dbf3dff330750b47740a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 21:57:06 -0700 Subject: [PATCH 56/59] Wrap --- ...romPrimaryConstructorParameterCodeRefactoringProvider.cs | 3 ++- ...aryConstructorParameterCodeRefactoringProvider_Update.cs | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index dac8e72bacbd9..3dd4001d78e07 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -31,7 +31,8 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; using static SyntaxFactory; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromPrimaryConstructorParameter), Shared] -internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider +internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider + : CodeRefactoringProvider { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 337556c5d209d..1e3b4fca41ff6 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter; using static InitializeParameterHelpersCore; using static SyntaxFactory; -internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider : CodeRefactoringProvider +internal sealed partial class CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider { private static async Task AddMultipleMembersAsync( Document document, @@ -72,6 +72,10 @@ private static async Task AddMultipleMembersAsync( return currentSolution; + // Intentionally static so that we do not capture outer state. This function is called in a loop with a fresh + // fork of the solution after each change that has been made. This ensures we don't accidentally refer to the + // original state in any way. + static async Task AddSingleMemberAsync( Document document, TypeDeclarationSyntax typeDeclaration, From 96a22ce385be49dc1c6ba78767a9348d017d04ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 22:01:12 -0700 Subject: [PATCH 57/59] inline --- ...tructorParameterCodeRefactoringProvider.cs | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index 3dd4001d78e07..b56eeddc65a70 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -150,17 +150,33 @@ IEnumerable HandleNoExistingFieldOrProperty() { // Didn't find a field/prop that this parameter could be assigned to. Offer to create new one and assign to that. - // Check if the surrounding parameters are assigned to another field in this class. If so, offer to - // make this parameter into a field as well. Otherwise, default to generating a property + // Check if the surrounding parameters are assigned to another field in this class. If so, offer to make + // this parameter into a field as well. Otherwise, default to generating a property var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(); - var (fieldAction, propertyAction) = AddSpecificParameterInitializationActions(); + + var field = CreateField(parameter); + var property = CreateProperty(parameter); + + var fieldAction = CreateCodeAction( + string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(field), fallbackOptions, cancellationToken)); + var propertyAction = CreateCodeAction( + string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(property), fallbackOptions, cancellationToken)); yield return siblingFieldOrProperty is IFieldSymbol ? fieldAction : propertyAction; yield return siblingFieldOrProperty is IFieldSymbol ? propertyAction : fieldAction; - var (allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions(); - if (allFieldsAction != null && allPropertiesAction != null) + var parameters = GetParametersWithoutAssociatedMembers(); + if (parameters.Length >= 2) { + var allFieldsAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_fields, + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); + var allPropertiesAction = CodeAction.Create( + FeaturesResources.Create_and_assign_remaining_as_properties, + cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); + yield return siblingFieldOrProperty is IFieldSymbol ? allFieldsAction : allPropertiesAction; yield return siblingFieldOrProperty is IFieldSymbol ? allPropertiesAction : allFieldsAction; } @@ -178,37 +194,6 @@ IEnumerable HandleNoExistingFieldOrProperty() return null; } - (CodeAction fieldAction, CodeAction propertyAction) AddSpecificParameterInitializationActions() - { - var field = CreateField(parameter); - var property = CreateProperty(parameter); - - var fieldAction = CreateCodeAction( - string.Format(FeaturesResources.Create_and_assign_field_0, field.Name), - cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(field), fallbackOptions, cancellationToken)); - var propertyAction = CreateCodeAction( - string.Format(FeaturesResources.Create_and_assign_property_0, property.Name), - cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, ImmutableArray.Create(parameter), ImmutableArray.Create(property), fallbackOptions, cancellationToken)); - - return (fieldAction, propertyAction); - } - - (CodeAction? fieldAction, CodeAction? propertyAction) AddAllParameterInitializationActions() - { - var parameters = GetParametersWithoutAssociatedMembers(); - if (parameters.Length < 2) - return default; - - var allFieldsAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_fields, - cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateField), fallbackOptions, cancellationToken)); - var allPropertiesAction = CodeAction.Create( - FeaturesResources.Create_and_assign_remaining_as_properties, - cancellationToken => AddMultipleMembersAsync(document, typeDeclaration, parameters, parameters.SelectAsArray(CreateProperty), fallbackOptions, cancellationToken)); - - return (allFieldsAction, allPropertiesAction); - } - ImmutableArray GetParametersWithoutAssociatedMembers() { using var result = TemporaryArray.Empty; From 05bd07774a730b43813e828cd117310988af8f66 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 22:02:44 -0700 Subject: [PATCH 58/59] Whitespace --- ...mberFromPrimaryConstructorParameterCodeRefactoringProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index b56eeddc65a70..fc6bb27ee898d 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -96,7 +96,6 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // it as a member in this type. Note that we have some fallback rules that use the standard conventions // around properties /fields so that can still find things even if the user has no naming preferences // set. - foreach (var rule in rules) { var memberName = rule.NamingStyle.CreateName(parameterWords); From 445db56e06330c0f12199cd930edd67b412bf075 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 17 Jul 2023 22:09:05 -0700 Subject: [PATCH 59/59] Remove --- ...ConstructorParameterCodeRefactoringProvider.cs | 15 +++++++-------- ...ctorParameterCodeRefactoringProvider_Update.cs | 4 +++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs index fc6bb27ee898d..431bbc9ccd0d4 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider.cs @@ -76,7 +76,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var (fieldOrProperty, isThrowNotImplementedProperty) = TryFindMatchingUninitializedFieldOrPropertySymbol(); + var fieldOrProperty = TryFindMatchingUninitializedFieldOrPropertySymbol(); var refactorings = fieldOrProperty == null ? HandleNoExistingFieldOrProperty() : HandleExistingFieldOrProperty(); @@ -84,7 +84,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactorings(refactorings.ToImmutableArray(), context.Span); return; - (ISymbol?, bool isThrowNotImplementedProperty) TryFindMatchingUninitializedFieldOrPropertySymbol() + ISymbol? TryFindMatchingUninitializedFieldOrPropertySymbol() { // Look for a field/property that really looks like it corresponds to this parameter. Use a variety of // heuristics around the name/type to see if this is a match. @@ -108,7 +108,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) && syntaxRef1.GetSyntax(cancellationToken) is VariableDeclaratorSyntax { Initializer: null }) { - return (field, isThrowNotImplementedProperty: false); + return field; } // If it's a writable property that we could assign this parameter to, and it's not already been @@ -121,16 +121,16 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte // NotImplementedException()`. That way users can easily spit out those methods, but then // convert them to be normal properties with ease. if (IsThrowNotImplementedProperty(compilation, property, cancellationToken)) - return (property, isThrowNotImplementedProperty: true); + return property; if (property.IsWritableInConstructor()) - return (property, isThrowNotImplementedProperty: false); + return property; } } } // Couldn't find any existing member. Just return nothing so we can offer to create a member for them. - return default; + return null; } static CodeAction CreateCodeAction(string title, Func> createSolution) @@ -141,8 +141,7 @@ IEnumerable HandleExistingFieldOrProperty() // Found a field/property that this parameter should be assigned to. Just offer the simple assignment to it. yield return CreateCodeAction( string.Format(fieldOrProperty.Kind == SymbolKind.Field ? FeaturesResources.Initialize_field_0 : FeaturesResources.Initialize_property_0, fieldOrProperty.Name), - cancellationToken => UpdateExistingMemberAsync( - document, parameter, fieldOrProperty, isThrowNotImplementedProperty, cancellationToken)); + cancellationToken => UpdateExistingMemberAsync(document, parameter, fieldOrProperty, cancellationToken)); } IEnumerable HandleNoExistingFieldOrProperty() diff --git a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs index 1e3b4fca41ff6..342c162edcf4b 100644 --- a/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs +++ b/src/Features/CSharp/Portable/InitializeParameter/CSharpInitializeMemberFromPrimaryConstructorParameterCodeRefactoringProvider_Update.cs @@ -198,7 +198,6 @@ private static async Task UpdateExistingMemberAsync( Document document, IParameterSymbol parameter, ISymbol fieldOrProperty, - bool isThrowNotImplementedProperty, CancellationToken cancellationToken) { var project = document.Project; @@ -215,6 +214,9 @@ await UpdateParameterReferencesAsync( // We're updating an exiting field/prop. if (fieldOrProperty is IPropertySymbol property) { + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var isThrowNotImplementedProperty = IsThrowNotImplementedProperty(compilation, property, cancellationToken); + foreach (var syntaxRef in property.DeclaringSyntaxReferences) { if (syntaxRef.GetSyntax(cancellationToken) is PropertyDeclarationSyntax propertyDeclaration)