private async Task <Document> PopulateConstructor(Document document, ConstructorDeclarationSyntax constructorDeclaration, CancellationToken cancellationToken) { // If there's a Validate method that should be called at the end of the constructor then ensure that it's invoked at the end of the auto-populated // constructor (the Validate method - if there is one that meets the requirements of being a method with zero arguments) is automatically called after // any With call but needs to be explicitly called from the constructor. Note: It would only make sense for the method to be an instance method (since // it can't validate the state of an instance if it's a static method) but the JavaScript doesn't (can't) check this and so, for consistency, we should // not restrict ourselves to only instance methods here. var classDeclaration = constructorDeclaration.AncestorsAndSelf().OfType <ClassDeclarationSyntax>().First(); var validateMethodIfDefined = IAmImmutableAnalyzer.TryToGetValidateMethodThatThisClassMustCall(classDeclaration); // Add the CtorSet calls to the constructor var constructorArgumentNamesThatAppearToBeUsed = constructorDeclaration.Body.DescendantNodes() .OfType <IdentifierNameSyntax>() .Where(i => !(i.Parent is MemberAccessExpressionSyntax)) // Ignore member access (eg. the "_ => _.Id" in "this.CtorSet(_ => _.Id, id)") .Select(i => i.Identifier.Text); var newConstructorBody = constructorDeclaration.Body.AddStatements( IAmImmutableAutoPopulatorAnalyzer.GetConstructorArgumentNamesThatAreNotAccountedFor(constructorDeclaration) .Select(argument => GeneratorCtorSetCall(GetPropertyName(argument.Identifier.Text), argument.Identifier.Text)) .ToArray() ); if (validateMethodIfDefined != null) { var existingValidateCalls = newConstructorBody.Statements .OfType <ExpressionStatementSyntax>() .Where(expression => { var invocationExpression = expression.Expression as InvocationExpressionSyntax; if (invocationExpression == null) { return(false); } var identifier = invocationExpression.Expression as IdentifierNameSyntax; if (identifier == null) { return(false); } return(identifier.Identifier.Text == validateMethodIfDefined.Identifier.Text); }); if (existingValidateCalls.Any()) { var updatedStatements = newConstructorBody.Statements; foreach (var existingValidateCall in existingValidateCalls) { updatedStatements = updatedStatements.Remove(existingValidateCall); } updatedStatements = updatedStatements.AddRange(existingValidateCalls); newConstructorBody = newConstructorBody.WithStatements(updatedStatements); } else { newConstructorBody = newConstructorBody.AddStatements(GetValidateCall(validateMethodIfDefined.Identifier.Text)); } } var populatedConstructor = constructorDeclaration.WithBody(newConstructorBody); // Add properties to the class that correspond to the constructor arguments (ignoring any properties that are already declared - there aren't // expected to be any because the quickest way now to generate an IAmImmutable implementation is to use this code fix on a constructor, to // populate the class with minimum manual labour, but if some of the properties have already been added for whatever reason then that's // fine, they will be ignored. var namesOfPropertiesDefinedOnClass = classDeclaration.ChildNodes() .OfType <PropertyDeclarationSyntax>() .Where(property => property.ExplicitInterfaceSpecifier == null) // Don't consider explicitly-implemented interface properties .Select(property => property.Identifier.Text) .ToArray(); var propertiesToAdd = IAmImmutableAutoPopulatorAnalyzer.GetConstructorArgumentsThatAreNotPassedToBaseConstructor(constructorDeclaration) .Select(constructorArgument => new { Argument = constructorArgument, PropertyName = GetPropertyName(constructorArgument.Identifier.Text) }) .Where(argumentDetails => !namesOfPropertiesDefinedOnClass.Contains(argumentDetails.PropertyName)) .Select(argumentDetails => SyntaxFactory.PropertyDeclaration( argumentDetails.Argument.Type, argumentDetails.PropertyName ) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( // 2016-09-21 DWR: Used to emit property getters of the form "{ get; private set; }" here since Bridge didn't support C# 6 syntax.. // but now it does (since 15.0) and so we can go for the stricter and more succinct "{ get; }" readonly auto-property format SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) ) ); // Return a new document that replaces the current class definition with the auto-populated one // - If there is no "using ProductiveRage.Immutable" statement then insert one of them, otherwise the this.CtorSet calls will fail as those // are calls to extension methods in the "ProductiveRage.Immutable" namespace var populatedClass = classDeclaration .ReplaceNode(constructorDeclaration, populatedConstructor) .AddMembers(propertiesToAdd.ToArray()); var root = await document .GetSyntaxRootAsync(cancellationToken) .ConfigureAwait(false); root = root.ReplaceNode(classDeclaration, populatedClass); var usingDirectives = ((CompilationUnitSyntax)root).Usings; if (!usingDirectives.Any(usingDirective => (usingDirective.Alias == null) && (usingDirective.Name.ToFullString() == CommonAnalyser.AnalyserAssemblyName))) { root = ((CompilationUnitSyntax)root).WithUsings( // Courtesy of http://stackoverflow.com/a/17677024 usingDirectives.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(CommonAnalyser.AnalyserAssemblyName))) ); } return(document.WithSyntaxRoot(root)); }