public void RunModifier(ref List <Diff> diffs)
        {
            List <Diff> memberedClassDiffs = diffs
                                             .Where(diff => diff.Target is ClassDescriptor)
                                             .Where(diff => (diff.Target as ClassDescriptor).Members.Count > 0)
                                             .ToList();

            List <Diff> newClassDiffs = memberedClassDiffs
                                        .Where(diff => diff.Type == DiffType.Add)
                                        .ToList();

            List <Diff> oldClassDiffs = memberedClassDiffs
                                        .Where(diff => diff.Type == DiffType.Remove)
                                        .ToList();

            if (oldClassDiffs.Count > 0 && newClassDiffs.Count > 0)
            {
                foreach (Diff newClassDiff in newClassDiffs)
                {
                    // Ignore disposed diffs.
                    if (newClassDiff.Disposed)
                    {
                        continue;
                    }

                    // Grab the summary version of the new diff.
                    var    newClass = newClassDiff.Target as ClassDescriptor;
                    string newDiff  = newClassDiff.WriteDiffTxt(false);

                    foreach (Diff oldClassDiff in oldClassDiffs)
                    {
                        // Ignore disposed diffs.
                        if (oldClassDiff.Disposed)
                        {
                            continue;
                        }

                        // Grab the summary version of the old diff.
                        var    oldClass = oldClassDiff.Target as ClassDescriptor;
                        string oldDiff  = oldClassDiff.WriteDiffTxt(false);

                        // Try to convert the old diff into the new diff generated above.
                        string nameChange = oldDiff
                                            .Replace(oldClass.Name, newClass.Name)
                                            .Replace("Removed", "Added");

                        // Intersect some of the old and new signatures to see if they are similar enough.
                        List <string> oldLines = newDiff.Split('\r', '\n')
                                                 .Select(oldLine => oldLine.Trim())
                                                 .Where(oldLine => oldLine.Length > 0)
                                                 .ToList();

                        List <string> newLines = nameChange.Split('\r', '\n')
                                                 .Select(newLine => newLine.Trim())
                                                 .Where(newLine => newLine.Length > 0)
                                                 .ToList();

                        List <string> intersects = oldLines
                                                   .Intersect(newLines)
                                                   .ToList();

                        // If the signatures match, then this is probably a renamed class?
                        if (intersects.Count >= ((oldLines.Count + newLines.Count) / 2))
                        {
                            // HACK: To allow the members to be compared nicely, I need to change the name
                            // of the old class to the name of the new class. However, I still want to
                            // describe the target of the ClassName change with the old ClassName, so I
                            // also have to create a dummy ClassDescriptor to serve as the target.

                            ClassDescriptor dummy = new ClassDescriptor();
                            dummy.Name = oldClass.Name;

                            // Create a diff describing the ClassName change.
                            Diff nameChangeDiff = new Diff()
                            {
                                Type = DiffType.Rename,

                                Field  = "Class",
                                Target = dummy,

                                To = { newClass.Name }
                            };

                            // Add this change to the diffs.
                            diffs.Add(nameChangeDiff);

                            // Remap the old class with the new class name.
                            var oldClasses = oldClass.Database.Classes;
                            var newName    = newClass.Name;

                            oldClasses.Remove(oldClass.Name);
                            oldClass.Name = newName;
                            oldClasses.Add(newName, oldClass);

                            // Dispose the original class diffs.
                            oldClassDiff.Disposed = true;
                            newClassDiff.Disposed = true;
                        }
                    }
                }
            }
        }
Ejemplo n.º 2
0
        private static void flagEntireClass(ClassDescriptor classDesc, DiffRecorder record, bool detailed)
        {
            Diff classDiff = record(classDesc, detailed);

            classDesc.Members.ForEach(memberDesc => record(memberDesc, detailed, classDiff));
        }
        public void RunModifier(ref List <Diff> diffs)
        {
            var merging = new Dictionary <MemberDescriptor, List <MemberDescriptor> >();

            var added   = collectMemberDiffs(diffs, DiffType.Add);
            var removed = collectMemberDiffs(diffs, DiffType.Remove);

            foreach (Diff targetDiff in removed)
            {
                var    targetMember = targetDiff.Target as MemberDescriptor;
                string targetName   = targetMember.Name;

                foreach (Diff otherDiff in added)
                {
                    var    otherMember = otherDiff.Target as MemberDescriptor;
                    string otherName   = otherMember.Name;

                    if (targetName == otherName)
                    {
                        // Grab the classes of the two members we have selected.
                        ClassDescriptor targetClass = targetMember.Class;
                        ClassDescriptor otherClass  = otherMember.Class;

                        // Because the databases of these two members are different, this uses
                        // the database of the other member, since it should be the newer one.
                        var database    = otherClass.Database;
                        var classLookup = database.Classes;

                        // Override targetClass with its corresponding entry in the classLookup.
                        // This is necessary to have a snapshot of the newer class hierarchy.
                        if (!classLookup.ContainsKey(targetClass.Name))
                        {
                            continue;
                        }

                        targetClass = classLookup[targetClass.Name];

                        // Now test the ancestry of the two classes.
                        if (otherClass.IsAncestorOf(targetClass))
                        {
                            if (!merging.ContainsKey(otherMember))
                            {
                                var mergers = new List <MemberDescriptor>();
                                merging.Add(otherMember, mergers);
                            }

                            merging[otherMember].Add(targetMember);
                            targetDiff.Disposed = true;
                        }
                        else if (otherClass.Name != targetClass.Name)
                        {
                            if (targetDiff.Field != targetMember.DescriptorType)
                            {
                                continue;
                            }

                            if (otherDiff.Field != otherMember.DescriptorType)
                            {
                                continue;
                            }

                            // Maybe they're just moving?
                            moveMember(ref diffs, targetMember, otherMember);
                        }
                    }
                }
            }

            // Process the diffs and try grouping any
            // that use the same member descriptor.

            foreach (MemberDescriptor member in merging.Keys)
            {
                List <MemberDescriptor> members = merging[member];

                if (members.Count > 1)
                {
                    Diff mergeDiff = new Diff();
                    mergeDiff.Type = DiffType.Merge;

                    var mergeInto = new DiffChangeList();
                    mergeInto.Add(member);
                    mergeDiff.To = mergeInto;

                    var mergeFrom = new DiffChangeList();
                    mergeFrom.AddRange(members);

                    mergeDiff.From   = mergeFrom;
                    mergeDiff.Target = member;

                    diffs.Add(mergeDiff);
                }
                else
                {
                    var target = members.First();
                    moveMember(ref diffs, target, member);

                    var newClassDiff = diffs
                                       .Where(diff => diff.Type == DiffType.Add)
                                       .Where(diff => diff.Target == member.Class)
                                       .FirstOrDefault();

                    if (newClassDiff == null)
                    {
                        continue;
                    }

                    var newMemberDiff = diffs
                                        .SelectMany(diff => diff.Children)
                                        .Where(diff => diff.Target == member)
                                        .FirstOrDefault();

                    if (newMemberDiff == null)
                    {
                        continue;
                    }

                    newClassDiff.RemoveChild(newMemberDiff);
                }
            }
        }
Ejemplo n.º 4
0
        public static async Task <string> CompareDatabases(ReflectionDatabase oldApi, ReflectionDatabase newApi, string format = "TXT", bool postProcess = true)
        {
            currentFormat = format.ToLower();

            // For the purposes of the differ, treat png like html.
            // Its assumed that the result will be processed afterwards.

            if (currentFormat == "png")
            {
                currentFormat = "html";
            }

            // Clean up old results.
            if (results.Count > 0)
            {
                results.Clear();
            }

            // Grab the class lists.
            var oldClasses = oldApi.Classes;
            var newClasses = newApi.Classes;

            // Record classes that were added.
            foreach (string className in newClasses.Keys)
            {
                if (!oldClasses.ContainsKey(className))
                {
                    ClassDescriptor classDesc = newClasses[className];
                    flagEntireClass(classDesc, Added, true);
                }
            }

            // Record classes that were removed.
            foreach (string className in oldClasses.Keys)
            {
                if (!newClasses.ContainsKey(className))
                {
                    ClassDescriptor classDesc = oldClasses[className];
                    flagEntireClass(classDesc, Removed, false);
                }
            }

            // Run pre-member-diff modifier tasks.
            foreach (IDiffModifier preModifier in preModifiers)
            {
                preModifier.RunModifier(ref results);
            }

            // Compare class changes.
            foreach (string className in oldClasses.Keys)
            {
                ClassDescriptor oldClass = oldClasses[className];

                if (newClasses.ContainsKey(className))
                {
                    ClassDescriptor newClass = newClasses[className];

                    // Capture the members of these classes.
                    var oldMembers = createLookupTable(oldClass.Members);
                    var newMembers = createLookupTable(newClass.Members);

                    // Compare the classes directly.
                    var classTagDiffs = CompareTags(oldClass, oldClass.Tags, newClass.Tags);
                    Compare(oldClass, "superclass", oldClass.Superclass, newClass.Superclass, true);
                    Compare(oldClass, "memory category", oldClass.MemoryCategory, newClass.MemoryCategory, true);

                    // Record members that were added.
                    foreach (string memberName in newMembers.Keys)
                    {
                        if (!oldMembers.ContainsKey(memberName))
                        {
                            // Add New Member
                            MemberDescriptor newMember = newMembers[memberName];
                            Added(newMember);
                        }
                    }

                    // Record members that were changed or removed.
                    foreach (string memberName in oldMembers.Keys)
                    {
                        MemberDescriptor oldMember = oldMembers[memberName];

                        if (newMembers.ContainsKey(memberName))
                        {
                            MemberDescriptor newMember = newMembers[memberName];

                            if (oldMember.ThreadSafety.Type != ThreadSafetyType.Unknown)
                            {
                                Compare(newMember, "thread safety", oldMember.ThreadSafety, newMember.ThreadSafety);
                            }

                            // Check if any tags added to this member were also added to its parent class.
                            var memberTagDiffs = CompareTags(newMember, oldMember.Tags, newMember.Tags);
                            MergeTagDiffs(classTagDiffs, memberTagDiffs);

                            // Compare the fields specific to these member types
                            // TODO: I'd like to move these routines into their respective
                            //       members, but I'm not sure how to do so in a clean manner.

                            if (newMember is PropertyDescriptor)
                            {
                                var oldProp = oldMember as PropertyDescriptor;
                                var newProp = newMember as PropertyDescriptor;

                                var oldMerged = oldProp.Security.Merged;
                                var newMerged = newProp.Security.Merged;

                                if (oldMerged && newMerged)
                                {
                                    // Just compare them as a security change alone.
                                    var oldSecurity = oldProp.Security.Value;
                                    var newSecurity = newProp.Security.Value;

                                    Compare(newMember, "security", oldSecurity, newSecurity);
                                }
                                else
                                {
                                    // Compare the read/write permissions individually.
                                    var oldSecurity = oldProp.Security;
                                    var newSecurity = newProp.Security;

                                    string oldRead = oldSecurity.Read.Value,
                                           newRead = newSecurity.Read.Value;

                                    string oldWrite = oldSecurity.Write.Value,
                                           newWrite = newSecurity.Write.Value;

                                    Compare(newMember, "read permissions", oldRead, newRead);
                                    Compare(newMember, "write permissions", oldWrite, newWrite);
                                }

                                var oldSerial = oldProp.Serialization.Describe(true);
                                var newSerial = newProp.Serialization.Describe(true);

                                Compare(newMember, "serialization", oldSerial, newSerial);
                                Compare(newMember, "value-type", oldProp.ValueType, newProp.ValueType);
                                Compare(newMember, "category", oldProp.Category, newProp.Category, true);
                            }
                            else if (newMember is FunctionDescriptor)
                            {
                                var oldFunc = oldMember as FunctionDescriptor;
                                var newFunc = newMember as FunctionDescriptor;

                                Compare(newMember, "security", oldFunc.Security, newFunc.Security);
                                Compare(newMember, "parameters", oldFunc.Parameters, newFunc.Parameters);
                                Compare(newMember, "return-type", oldFunc.ReturnType, newFunc.ReturnType);
                            }
                            else if (newMember is CallbackDescriptor)
                            {
                                var oldCall = oldMember as CallbackDescriptor;
                                var newCall = newMember as CallbackDescriptor;

                                Compare(newMember, "security", oldCall.Security, newCall.Security);
                                Compare(newMember, "parameters", oldCall.Parameters, newCall.Parameters);
                                Compare(newMember, "expected return-type", oldCall.ReturnType, newCall.ReturnType);
                            }
                            else if (newMember is EventDescriptor)
                            {
                                var oldEvent = oldMember as EventDescriptor;
                                var newEvent = newMember as EventDescriptor;

                                Compare(newMember, "security", oldEvent.Security, newEvent.Security);
                                Compare(newMember, "parameters", oldEvent.Parameters, newEvent.Parameters);
                            }
                        }
                        else
                        {
                            // Remove old member.
                            Removed(oldMember, false);
                        }
                    }
                }
            }

            // Grab the enum lists.
            var oldEnums = oldApi.Enums;
            var newEnums = newApi.Enums;

            // Record enums that were added.
            foreach (string enumName in newEnums.Keys)
            {
                if (!oldEnums.ContainsKey(enumName))
                {
                    EnumDescriptor newEnum = newEnums[enumName];
                    flagEntireEnum(newEnum, Added, true);
                }
            }

            // Record enums that were changed or removed.
            foreach (string enumName in oldEnums.Keys)
            {
                EnumDescriptor oldEnum = oldEnums[enumName];

                if (newEnums.ContainsKey(enumName))
                {
                    EnumDescriptor newEnum      = newEnums[enumName];
                    var            enumTagDiffs = CompareTags(newEnum, oldEnum.Tags, newEnum.Tags);

                    // Grab the enum-item lists.
                    var oldItems = createLookupTable(oldEnum.Items);
                    var newItems = createLookupTable(newEnum.Items);

                    // Record enum-items that were added.
                    foreach (var itemName in newItems.Keys)
                    {
                        if (!oldItems.ContainsKey(itemName))
                        {
                            EnumItemDescriptor item = newItems[itemName];
                            Added(item);
                        }
                    }

                    foreach (var itemName in oldItems.Keys)
                    {
                        EnumItemDescriptor oldItem = oldItems[itemName];

                        if (newItems.ContainsKey(itemName))
                        {
                            EnumItemDescriptor newItem = newItems[itemName];
                            Compare(newItem, "value", oldItem.Value, newItem.Value);

                            // Check if any tags that were added to this item were also added to its parent enum.
                            var itemTagDiffs = CompareTags(newItem, oldItem.Tags, newItem.Tags);
                            MergeTagDiffs(enumTagDiffs, itemTagDiffs);
                        }
                        else
                        {
                            // Remove old enum-item.
                            Removed(oldItem, false);
                        }
                    }
                }
                else
                {
                    // Remove old enum.
                    flagEntireEnum(oldEnum, Removed, false);
                }
            }

            // Exit early if no diffs were recorded.
            if (results.Count == 0)
            {
                return("");
            }

            // Select diffs that are not parented to other diffs.
            List <Diff> diffs = results
                                .Where(diff => !diff.HasParent)
                                .ToList();

            // Run post-member-diff modifier tasks.
            foreach (IDiffModifier postModifier in postModifiers)
            {
                postModifier.RunModifier(ref diffs);
            }

            // Remove diffs that were disposed during the modifier tasks,
            diffs = diffs
                    .Where(diff => !diff.Disposed)
                    .OrderBy(diff => diff)
                    .ToList();

            // Setup actions for generating the final result, based on the requested format.
            DiffResultLineAdder addLineToResults;
            DiffResultFinalizer finalizeResults;

            List <string> lines = diffs
                                  .Select(diff => diff.ToString())
                                  .ToList();

            if (currentFormat == "html")
            {
                var htmlDumper = new ReflectionDumper();
                var diffLookup = diffs.ToDictionary(diff => diff.ToString());

                addLineToResults = new DiffResultLineAdder((line, addBreak) =>
                {
                    if (addBreak)
                    {
                        htmlDumper.Write(HTML_BREAK);
                        htmlDumper.NextLine();
                    }

                    if (diffLookup.ContainsKey(line))
                    {
                        Diff diff = diffLookup[line];
                        diff.WriteDiffHtml(htmlDumper);
                    }

                    if (line.EndsWith(NL))
                    {
                        htmlDumper.Write(HTML_BREAK);
                        htmlDumper.NextLine();
                    }
                });

                finalizeResults = new DiffResultFinalizer(() =>
                {
                    if (newApi.Branch == "roblox")
                    {
                        htmlDumper.NextLine();
                    }

                    string result = htmlDumper.ExportResults();

                    if (postProcess)
                    {
                        result = ApiDumpTool.PostProcessHtml(result);
                    }

                    return(result);
                });

                if (newApi.Branch == "roblox")
                {
                    var deployLog = await ApiDumpTool.GetLastDeployLog("roblox");

                    htmlDumper.OpenHtmlTag("h2");
                    htmlDumper.Write("Version " + deployLog.VersionId);

                    htmlDumper.CloseHtmlTag("h2");
                    htmlDumper.NextLine(2);
                }
            }
            else
            {
                var final = new List <string>();

                addLineToResults = new DiffResultLineAdder((line, addBreak) =>
                {
                    if (addBreak)
                    {
                        final.Add("");
                    }

                    final.Add(line);
                });

                finalizeResults = new DiffResultFinalizer(() =>
                {
                    string[] finalLines = final.ToArray();
                    return(string.Join(NL, finalLines).Trim());
                });
            }

            // Generate the final diff results
            string prevLead = "";
            string lastLine = NL;

            foreach (string line in lines)
            {
                string[] words = line.Split(' ');

                if (words.Length >= 2)
                {
                    // Capture the first two words in this line.
                    string first  = words[0];
                    string second = words[1];

                    if (second.ToLower() == "the" && words.Length > 2)
                    {
                        second = words[2];
                    }

                    string lead = (first + ' ' + second).Trim();

                    bool addBreak        = false;
                    bool lastLineNoBreak = !lastLine.EndsWith(NL);

                    // If the first two words of this line aren't the same as the last...
                    if (lead != prevLead)
                    {
                        // Add a break if the last line doesn't have a break.
                        // (and if there actually were two previous words)
                        if (prevLead != "" && lastLineNoBreak)
                        {
                            addBreak = true;
                        }

                        prevLead = lead;
                    }

                    // If we didn't add a break, but this line has a break and the
                    // previous line doesn't, then we will add a break.
                    if (!addBreak && lastLineNoBreak && line.EndsWith(NL))
                    {
                        addBreak = true;
                    }

                    // Handle writing this line depending on the format we're using.
                    addLineToResults(line, addBreak);
                    lastLine = line;
                }
            }

            return(finalizeResults());
        }