コード例 #1
0
        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));
        }