Beispiel #1
0
        /// <summary>
        /// Inspects the given code document to see if there's any need for immutable / mutable generation
        /// </summary>
        /// <param name="codeDocument"></param>
        /// <param name="text"></param>
        private void InspectAndUpdate(IDocument codeDocument, Roslyn.Compilers.Common.CommonCompilation commonCompilation)
        {
            // mild premature optimization
            var text = codeDocument.GetText().GetText();
            if (text.Contains("immutable_generated") && text.Contains("immutable_declarations"))
            {
                var updateRegions = new List<UpdateRegion>();

                // Compile
                var syntax = codeDocument.GetSyntaxTree();
                var root = syntax.GetRoot();

                // Filter down to classes
                foreach (var classDeclaration in root.DescendantNodes().OfType<ClassDeclarationSyntax>())
                {
                    var immutableGeneratedTrivia = this.GetRegion(classDeclaration, "immutable_generated");

                    if (null != immutableGeneratedTrivia)
                    {
                        var className = classDeclaration.Identifier.ValueText;
                        var classFullName = className;

                        var parent = classDeclaration.Parent;
                        while (null != parent)
                        {
                            if (parent is NamespaceDeclarationSyntax)
                                classFullName = ((NamespaceDeclarationSyntax)parent).Name.ToString().Trim() + "." + classFullName;

                            parent = parent.Parent;
                        }

                        var compiledClass = commonCompilation.GetTypeByMetadataName(classFullName);

                        var updateRegion = new UpdateRegion()
                        {
                            begin = immutableGeneratedTrivia.beginRegionTrivia.FullSpan.End,
                            end = immutableGeneratedTrivia.endRegionTrivia.FullSpan.Start
                        };

                        try
                        {
                            if (null != compiledClass)
                            {
                                Console.WriteLine("\t\t\t{0} is an immutable", classDeclaration.Identifier.GetText());

                                var immutableDeclarationsTrivia = this.GetRegion(classDeclaration, "immutable_declarations");

                                if (null != immutableDeclarationsTrivia)
                                {
                                    var fields = new List<IFieldSymbol>();

                                    // Iterate through all the tokens in the immutable_declarations to figure out the fields
                                    var token = immutableDeclarationsTrivia.beginRegionTrivia.Token;
                                    while (token != immutableDeclarationsTrivia.endRegionTrivia.Token)
                                    {
                                        if (token.Kind == SyntaxKind.IdentifierToken)
                                        {
                                            // Get the compiled version of the identifies
                                            var identifier = token.ValueText;
                                            var field = compiledClass.GetMembers(identifier)
                                                .FirstOrDefault() as IFieldSymbol;

                                            // Verify it
                                            if (null != field)
                                            {
                                                if (field.IsStatic)
                                                    throw new Exception(string.Format(
                                                        "Can not wrap static fields: {0}",
                                                        identifier));

                                                if (!field.IsReadOnly)
                                                    throw new Exception(string.Format(
                                                        "Exposed fields must be readonly: {0}",
                                                        identifier));

                                                if (field.HasConstantValue)
                                                    throw new Exception(string.Format(
                                                        "Exposed fields not be constant: {0}",
                                                        identifier));

                                                // TODO: Need to verify that the field is private

                                                fields.Add(field);
                                            }
                                        }

                                        token = token.GetNextToken();
                                    }

                                    var builder = new StringBuilder();
                                    builder.AppendLine();
                                    builder.Append(immutableGeneratedTrivia.whiteSpace);
                                    builder.AppendLine("// Generated code, do not edit unless you know what you're doing!");

                                    var propertyNames = new Dictionary<IFieldSymbol, string>();

                                    // Generate properties and set mutators
                                    foreach (var field in fields)
                                    {
                                        var type = field.Type.ToDisplayString();

                                        // Determine property name
                                        string propertyName;
                                        if (field.Name.StartsWith("_"))
                                            propertyName = field.Name.Substring(1);
                                        else
                                        {
                                            var firstChar = field.Name[0].ToString();
                                            var firstCharU = firstChar.ToUpperInvariant();
                                            var firstCharL = firstChar.ToLowerInvariant();

                                            if (firstCharL == firstCharU)
                                            {
                                                propertyName = field.Name.ToUpperInvariant();

                                                if (propertyName == field.Name)
                                                    propertyName = field.Name.ToLowerInvariant();

                                                if (propertyName == field.Name)
                                                    propertyName = field.Name + "_Property";
                                            }
                                            else
                                            {
                                                propertyName = field.Name.Substring(1);
                                                if (firstCharL == firstChar)
                                                    propertyName = firstCharU + propertyName;
                                                else
                                                    propertyName = firstCharL + propertyName;
                                            }
                                        }

                                        propertyNames[field] = propertyName;

                                        // Property
                                        builder.AppendFormat(
                                            "{0}public {1} {2} {{ get {{ return this.{3}; }}}}",
                                            immutableGeneratedTrivia.whiteSpace,
                                            type,
                                            propertyName,
                                            field.Name);

                                        builder.AppendLine();

                                        // Set Mutator
                                        builder.AppendFormat(
                                            "{0}public {1} Set{2}({3} {4}) {{ return new {1}(",
                                            immutableGeneratedTrivia.whiteSpace,
                                            className,
                                            propertyName,
                                            type,
                                            field.Name);

                                        var passes = new List<string>();
                                        foreach (var subField in fields)
                                        {
                                            if (subField == field)
                                                passes.Add(field.Name);
                                            else
                                                passes.Add("this." + subField.Name);
                                        }

                                        builder.Append(string.Join(", ", passes.ToArray()));
                                        builder.AppendLine("); }");

                                        builder.AppendLine();
                                    }

                                    builder.AppendLine();

                                    // Generate constructor
                                    builder.Append(immutableDeclarationsTrivia.whiteSpace);
                                    builder.AppendFormat("public {0}(", className);

                                    var argumentDeclarations = new List<string>();
                                    foreach (var field in fields)
                                    {
                                        argumentDeclarations.Add(string.Format(
                                            "{0} {1} = default({0})",
                                            field.Type.ToDisplayString(),
                                            field.Name));
                                    }

                                    builder.Append(string.Join(", ", argumentDeclarations.ToArray()));
                                    builder.Append(") { ");

                                    foreach (var field in fields)
                                    {
                                        var typeDisplayString = field.Type.ToDisplayString();
                                        if (this.IsIEnumerable(typeDisplayString))
                                        {
                                            builder.AppendFormat("if (default({1}) != {0}) this.{0} = {0}.ToArray().AsEnumerable(); ", field.Name, typeDisplayString);
                                        }
                                        else
                                        {
                                            builder.AppendFormat("this.{0} = {0}; ", field.Name);
                                        }
                                    }

                                    builder.AppendLine("}");
                                    builder.AppendLine();

                                    // Mutable class
                                    builder.Append(immutableGeneratedTrivia.whiteSpace);
                                    builder.Append("public class Mutable {");

                                    foreach (var field in fields)
                                    {
                                        var typeDisplayString = field.Type.ToDisplayString();
                                        if (this.IsIEnumerable(typeDisplayString))
                                        {
                                            typeDisplayString = typeDisplayString.Replace("IEnumerable", "IList");
                                        }

                                        builder.AppendFormat(
                                            " public {0} {1} {{ get; set; }}",
                                            typeDisplayString,
                                            propertyNames[field]);
                                    }

                                    // Generate immutable from mutable
                                    builder.AppendFormat(
                                        " public {0} ToImmutable() {{ return new {0}(",
                                        className);

                                    var assignments = new List<string>(fields.Count);
                                    foreach (var field in fields)
                                        assignments.Add(string.Format(
                                            "this.{0}",
                                            propertyNames[field]));

                                    builder.Append(string.Join(", ", assignments.ToArray()));
                                    builder.AppendLine(");} }");

                                    // GenerateMutable method
                                    builder.AppendFormat(
                                        "{0}public Mutable ToMutable() {{ return new Mutable() {{ ",
                                        immutableGeneratedTrivia.whiteSpace);

                                    assignments = new List<string>(fields.Count);
                                    foreach (var field in fields)
                                    {
                                        var copyMethod = String.Empty;
                                        var typeDisplayString = field.Type.ToDisplayString();
                                        if (this.IsIEnumerable(typeDisplayString))
                                        {
                                            copyMethod = ".ToList()";
                                        }

                                        assignments.Add(string.Format(
                                            "{0} = this.{1}{2}",
                                            propertyNames[field],
                                            field.Name,
                                            copyMethod));
                                    }

                                    builder.Append(string.Join(", ", assignments.ToArray()));
                                    builder.AppendLine("}; }");

                                    builder.AppendLine();

                                    // ==
                                    builder.AppendFormat(
                                        "{0}public static bool operator ==({1} lhs, {1} rhs) {{ return",
                                        immutableGeneratedTrivia.whiteSpace,
                                        className);

                                    var conditionBuilder = new List<string>(fields.Count);
                                    foreach (var field in fields)
                                    {
                                        var typeDisplayString = field.Type.ToDisplayString();
                                        if (this.IsIEnumerable(typeDisplayString))
                                        {
                                            conditionBuilder.Add(string.Format(
                                                " lhs.{0}.SequenceEqual(rhs.{0})",
                                                field.Name));
                                        }
                                        else
                                        {
                                            conditionBuilder.Add(string.Format(
                                                " lhs.{0} == rhs.{0} ",
                                                field.Name));
                                        }
                                    }

                                    builder.Append(string.Join("&&", conditionBuilder.ToArray()));
                                    builder.Append("; }");

                                    builder.AppendLine();

                                    // !=
                                    builder.AppendFormat(
                                        "{0}public static bool operator !=({1} lhs, {1} rhs) {{ return",
                                        immutableGeneratedTrivia.whiteSpace,
                                        className);

                                    conditionBuilder = new List<string>(fields.Count);
                                    foreach (var field in fields)
                                    {
                                        var typeDisplayString = field.Type.ToDisplayString();
                                        if (this.IsIEnumerable(typeDisplayString))
                                        {
                                            conditionBuilder.Add(string.Format(
                                                " (!lhs.{0}.SequenceEqual(rhs.{0}))",
                                                field.Name));
                                        }
                                        else
                                        {
                                            conditionBuilder.Add(string.Format(
                                                " lhs.{0} != rhs.{0} ",
                                                field.Name));
                                        }
                                    }

                                    builder.Append(string.Join("||", conditionBuilder.ToArray()));
                                    builder.Append("; }");

                                    builder.AppendLine();

                                    updateRegion.newText = builder.ToString();
                                }
                                else
                                {
                                    updateRegion.newText = immutableGeneratedTrivia.whiteSpace + "// Missing #region immutable_declarations\n\r";
                                }
                            }
                            else
                            {
                                updateRegion.newText = immutableGeneratedTrivia.whiteSpace + "// " + classFullName + " does not compile\n\r";
                            }
                        }
                        catch (Exception e)
                        {
                            updateRegion.newText = string.Format("/*{0}*/\r\n", e);
                        }

                        updateRegions.Add(updateRegion);
                    }
                }

                if (updateRegions.Count > 0)
                    this.Update(codeDocument, text, updateRegions);
            }
        }