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); }
private static async Task <Document> GenerateWithMethods( Document document, ClassDeclarationSyntax classDeclarationSyntax, IList <PropertyDeclarationSyntax> properties, int[] propertyToParameterIdx, CancellationToken cancellationToken) { int MappedIdx(int i) { int mappedIdx = propertyToParameterIdx[i]; if (mappedIdx == -1) { throw new ArgumentException($"{nameof(propertyToParameterIdx)} contains invalid mapping"); } return(mappedIdx); } ArgumentSyntax[] ReorderArgumentsToMapping(IReadOnlyList <ArgumentSyntax> arguments) { var result = new ArgumentSyntax[arguments.Count]; for (int i = 0; i < arguments.Count; ++i) { result[MappedIdx(i)] = arguments[i]; } return(result); } T ExecuteWithTempArg <T>(ArgumentSyntax[] args, int argIdx, ArgumentSyntax tempArg, Func <ArgumentSyntax[], T> operation) { var oldArg = args[argIdx]; args[argIdx] = tempArg; var result = operation(args); args[argIdx] = oldArg; return(result); } if (properties.Count != propertyToParameterIdx.Length) { throw new ArgumentException("properties.Count != propertyToParameterIdx.Length"); } var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (properties.Count == 0) { return(document); } var classType = SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia()); var newClassDeclaration = classDeclarationSyntax; var argumentsArray = properties.Select(p => p.ToArgument()).ToArray(); argumentsArray = ReorderArgumentsToMapping(argumentsArray); for (int i = 0; i < properties.Count; ++i) { var p = properties[i]; var id = p.Identifier.WithoutTrivia(); var lcId = SyntaxHelpers.LowercaseIdentifierFirstLetter(id); var methodName = WithRefactoringUtils.MethodName(p.Identifier); var arg = SyntaxHelpers.ArgumentFromIdentifier(lcId); var objectCreation = ExecuteWithTempArg(argumentsArray, MappedIdx(i), arg, args => ExpressionGenerationHelper.CreateObject(classType, args)); var withMethodExpression = MethodGenerationHelper.Builder(methodName) .Modifiers(Modifiers.Public) .Parameters(SyntaxHelpers.Parameter(p.Type, lcId)) .ReturnType(SyntaxFactory.IdentifierName(classDeclarationSyntax.Identifier.WithoutTrivia())) .ArrowBody(ExpressionGenerationHelper.Arrow(objectCreation)) .Build(); var previousWith = ClassDeclarationSyntaxAnalysis.GetMembers <MethodDeclarationSyntax>( newClassDeclaration) .FirstOrDefault(m => m.Identifier.ValueText.Equals(methodName)); if (previousWith == null) { // TODO: Group with members next to each other rather than adding at the end withMethodExpression = withMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithLeadingTrivia( Settings.EndOfLine, SyntaxFactory.ElasticSpace) .WithTrailingTrivia(Settings.EndOfLine); newClassDeclaration = newClassDeclaration.AddMembers(withMethodExpression); } else { withMethodExpression = withMethodExpression .NormalizeWhitespace(elasticTrivia: false) .WithTriviaFrom(previousWith); newClassDeclaration = newClassDeclaration.ReplaceNode(previousWith, withMethodExpression); } } var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newRoot = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration); var newDocument = document.WithSyntaxRoot(newRoot); return(newDocument); }