public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>(); if (document == null || classDeclarationSyntax == null) { return; } if (!ClassDeclarationSyntaxAnalysis.IsRecordLike(classDeclarationSyntax)) { return; } var(atMostOneConstructor, nonTrivialConstructorCandidate) = ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax); if (!atMostOneConstructor || nonTrivialConstructorCandidate == null) { return; } var properties = ClassDeclarationSyntaxAnalysis.GetPropertyDeclarations(classDeclarationSyntax).ToList(); if (properties.Count == 0 || properties.Any(PropertyDeclarationSyntaxExtensions.IsStatic)) { return; } var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var constructorSymbol = semanticModel.GetDeclaredSymbol(nonTrivialConstructorCandidate); var propertySymbols = properties.Select(p => semanticModel.GetDeclaredSymbol(p, cancellationToken)).ToArray(); var analyser = new ConstructorPropertyRelationshipAnalyser( Array.Empty <IFieldSymbol>(), propertySymbols); var result = analyser.Analyze(semanticModel, nonTrivialConstructorCandidate); var(idxMappings, isExhaustive) = result.GetIndexMapping(propertySymbols, constructorSymbol); if (!isExhaustive) { return; } context.RegisterRefactoring( new DelegateCodeAction( "Generate with methods for parameters", (c) => GenerateWithMethods(document, classDeclarationSyntax, properties, idxMappings, c))); return; }
public static async Task <Document> RemoveParameter( Document document, ClassDeclarationSyntax classDeclaration, AnalysedDeclaration analysedDeclaration, ConstructorDeclarationSyntax constructorDeclaration, CancellationToken cancellationToken) { var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var analyser = new ConstructorPropertyRelationshipAnalyser( analysedDeclaration.AsFieldArray(), analysedDeclaration.AsPropertyArray()); var result = analyser.Analyze(semanticModel, constructorDeclaration); var updatedConstructorDeclaration = constructorDeclaration; var analysisResult = result.GetResult(analysedDeclaration.Symbol); ParameterSyntax parameterToRemove = null; ExpressionStatementSyntax assignmentToRemove = null; if (analysisResult is AssignmentExpressionAnalyserResult assignment) { // Remove constructor parameter var parameterSyntaxReference = assignment.AssignedParameter.DeclaringSyntaxReferences.FirstOrDefault(); if (parameterSyntaxReference != null) { parameterToRemove = await parameterSyntaxReference.GetSyntaxAsync() as ParameterSyntax; } // Remove assignment in constructor assignmentToRemove = assignment.Assignment.Parent as ExpressionStatementSyntax; } // Remove field / property from class. var rewriter = new RemoveDeclarationClassRewriter(constructorDeclaration, parameterToRemove, assignmentToRemove, analysedDeclaration); var updatedClassDeclaration = rewriter.Visit(classDeclaration); // Apply changes var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(classDeclaration, updatedClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }
public static async Task <Document> AddParameterToConstructor( Document document, ClassDeclarationSyntax classDeclaration, AnalysedDeclaration analysedDeclaration, // must be either field variable or property IMethodSymbol constructorSymbol, ConstructorDeclarationSyntax constructorDeclaration, CancellationToken cancellationToken) { const int AppendPosition = int.MaxValue; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var classMemberAnalysis = new ClassMembersAnalysis(classDeclaration, semanticModel); var analyser = new ConstructorPropertyRelationshipAnalyser( classMemberAnalysis.Fields.Select(f => f.Symbol).ToArray(), classMemberAnalysis.Properties.Select(f => f.Symbol).ToArray()); var result = analyser.Analyze(semanticModel, constructorDeclaration); // Filter to consider only fields and properties that are assigned in constructor. var filteredClassMembers = classMemberAnalysis.WithFilteredProperties( (in FieldInfo fi) => result.GetResult(fi.Symbol) is AssignmentExpressionAnalyserResult, (in PropertyInfo pi) => result.GetResult(pi.Symbol) is AssignmentExpressionAnalyserResult); // Find closest declaration among the ones that are assigned in the constructor var(closestSymbol, isBeforeFoundSymbol) = filteredClassMembers.GetClosestFieldOrProperty(analysedDeclaration.Symbol); int constructorInsertPosition = AppendPosition; if (closestSymbol != null) { // There is another member that is assigned in constructor constructorInsertPosition = result.GetMatchingParameterIdx(constructorSymbol, closestSymbol); if (!isBeforeFoundSymbol) { ++constructorInsertPosition; } } // TODO: resolve name clashes if parameter with a given name already exists? var addedParameter = SyntaxHelpers.LowercaseIdentifierFirstLetter(analysedDeclaration.Identifier); var newConstructorDeclaration = constructorDeclaration.InsertParameter( SyntaxHelpers.Parameter( analysedDeclaration.Type, addedParameter), constructorInsertPosition); AssignmentExpressionSyntax closestSymbolAssignment = null; if (closestSymbol != null && result.GetResult(closestSymbol) is AssignmentExpressionAnalyserResult res) { closestSymbolAssignment = res.Assignment; } var fieldIdentifier = SyntaxFactory.IdentifierName(analysedDeclaration.Identifier); var parameterIdentifier = SyntaxFactory.IdentifierName(addedParameter); var leftSide = addedParameter.ValueText.Equals(analysedDeclaration.Identifier.ValueText, StringComparison.Ordinal) ? (ExpressionSyntax)ExpressionGenerationHelper.ThisMemberAccess(fieldIdentifier) : fieldIdentifier; var assignment = ExpressionGenerationHelper.SimpleAssignment(leftSide, parameterIdentifier); var statementToAdd = SyntaxFactory.ExpressionStatement(assignment); var body = constructorDeclaration.Body; var closestStatement = closestSymbolAssignment?.Parent as ExpressionStatementSyntax; if (closestStatement == null) { newConstructorDeclaration = newConstructorDeclaration .AddBodyStatements(statementToAdd) .WithAdditionalAnnotations(Formatter.Annotation); } else if (isBeforeFoundSymbol) { var newBody = body.InsertBefore(closestStatement, statementToAdd); newConstructorDeclaration = newConstructorDeclaration.WithBody( newBody).WithAdditionalAnnotations(Formatter.Annotation); } else { var newBody = body.InsertAfter(closestStatement, statementToAdd); newConstructorDeclaration = newConstructorDeclaration.WithBody( newBody).WithAdditionalAnnotations(Formatter.Annotation); } var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(constructorDeclaration, newConstructorDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, classDeclarationSyntax) = await context.FindSyntaxForCurrentSpan <ClassDeclarationSyntax>(); if (document == null || classDeclarationSyntax == null) { return; } if (ClassDeclarationSyntaxAnalysis.IsPartial(classDeclarationSyntax) || ClassDeclarationSyntaxAnalysis.IsStatic(classDeclarationSyntax)) { return; } var(atMostOneNonTrivialConstructor, nonTrivialConstructorCandidate) = ClassDeclarationSyntaxAnalysis.HasAtMostOneNoneTrivialConstructor(classDeclarationSyntax); if (!atMostOneNonTrivialConstructor) { return; } var(_, propertyDeclaration) = await context.FindSyntaxForCurrentSpan <PropertyDeclarationSyntax>(); var(_, fieldDeclaration) = await context.FindSyntaxForCurrentSpan <FieldDeclarationSyntax>(); // TODO: Skip properties like string Prop => "something"; if ((fieldDeclaration == null && propertyDeclaration == null) || (fieldDeclaration != null && fieldDeclaration.IsStatic()) || (propertyDeclaration != null && propertyDeclaration.IsStatic())) { return; } var(_, fieldVariableDeclaration) = await context.FindVariableDeclaratorForCurrentSpan(); if (fieldDeclaration != null && fieldVariableDeclaration == null) { return; } switch (nonTrivialConstructorCandidate) { case ConstructorDeclarationSyntax candidate: { var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); HandleCandidate(semanticModel, candidate, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration); } break; case null: { var trivialConstructor = ClassDeclarationSyntaxAnalysis.GetConstructors(classDeclarationSyntax)?.FirstOrDefault(); if (trivialConstructor != null) { var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); HandleCandidate(semanticModel, trivialConstructor, propertyDeclaration, fieldDeclaration, fieldVariableDeclaration); } else { // TODO: register add constructor refactoring } } break; } void HandleCandidate( SemanticModel model, ConstructorDeclarationSyntax candidate, PropertyDeclarationSyntax pDeclaration, FieldDeclarationSyntax fDeclaration, VariableDeclaratorSyntax variableDeclarator) { bool isProp = pDeclaration != null; var constructorSymbol = model.GetDeclaredSymbol(candidate) as IMethodSymbol; ISymbol memberSymbol = isProp ? model.GetDeclaredSymbol(pDeclaration) : model.GetDeclaredSymbol(variableDeclarator); if (constructorSymbol == null || memberSymbol == null) { return; } var analyser = new ConstructorPropertyRelationshipAnalyser( isProp ? Array.Empty <IFieldSymbol>() : new[] { memberSymbol as IFieldSymbol }, isProp ? new[] { memberSymbol as IPropertySymbol } : Array.Empty <IPropertySymbol>()); var result = analyser.Analyze(model, candidate); var assignmentResult = result.GetResult(memberSymbol); AnalysedDeclaration ad = isProp ? (AnalysedDeclaration) new PropertyDeclaration(memberSymbol as IPropertySymbol, pDeclaration) : new FieldDeclaration(memberSymbol as IFieldSymbol, fDeclaration, variableDeclarator); switch (assignmentResult) { case AssignmentExpressionAnalyserResult r: // register remove as assignment exists context.RegisterRefactoring( new DelegateCodeAction( $"Remove {ad.Identifier.ValueText}", (c) => RefactoringActions.RemoveParameter( document, classDeclarationSyntax, ad, candidate, c))); break; case EmptyAssignmentAnalyserResult _: // register add to constructor context.RegisterRefactoring( new DelegateCodeAction( $"Add {ad.Identifier.ValueText} to constructor", (c) => RefactoringActions.AddParameterToConstructor( document, classDeclarationSyntax, ad, constructorSymbol, candidate, c))); break; case MultipleAssignments _: case ParsingError _: default: // Something went wrong so it might be better to do nothing. break; } } }