public void WriteHtml(ReflectionDumper buffer, int numTabs = 0, bool diffMode = false) { string paramsTag = "Parameters"; if (diffMode) { paramsTag += " change"; } int closingTabs = 0; buffer.OpenClassTag(paramsTag, numTabs); if (Count > 0) { buffer.NextLine(); foreach (Parameter parameter in this) { parameter.WriteHtml(buffer, numTabs + 1); } closingTabs = numTabs; } buffer.CloseClassTag(closingTabs); }
public void WriteHtml(ReflectionDumper buffer, int numTabs = 0) { buffer.OpenClassTag("Parameter", numTabs); buffer.NextLine(); // Write Type Type.WriteHtml(buffer, numTabs + 1); // Write Name string nameLbl = "ParamName"; if (Default != null) { nameLbl += " default"; } buffer.WriteElement(nameLbl, Name, numTabs + 1); // Write Default if (Default != null) { string typeLbl = Type.GetSignature(); string typeName; if (typeLbl.Contains("<") && typeLbl.EndsWith(">")) { typeName = $"{Type.Category}"; } else { typeName = Type.Name; } if (Type.Category == TypeCategory.DataType && typeName != "Function") { buffer.WriteElement("ClassName Type", typeName, numTabs + 1); buffer.WriteElement("Name", "new", numTabs + 1); buffer.WriteElement("Parameters", null, numTabs + 1); } else { if (Type.Category == TypeCategory.Enum) { typeName = "String"; } buffer.WriteElement("ParamDefault " + typeName, Default, numTabs + 1); } } buffer.CloseClassTag(numTabs); }
public void WriteHtml(ReflectionDumper buffer, bool multiline = false, int extraTabs = 0, Descriptor.HtmlConfig config = null) { if (config == null) { config = new Descriptor.HtmlConfig(); } int numTabs; if (multiline) { buffer.OpenClassTag(Name, extraTabs + 1, "div"); buffer.NextLine(); buffer.OpenClassTag("ChangeList", extraTabs + 2); numTabs = 3; } else { buffer.OpenClassTag(Name, extraTabs + 1); numTabs = 2; } numTabs += extraTabs; if (config.NumTabs == 0) { config.NumTabs = numTabs; } buffer.NextLine(); PreformatList(); foreach (object change in this) { if (change is Parameters) { var parameters = change as Parameters; parameters.WriteHtml(buffer, numTabs, true); } else if (change is LuaType) { var type = change as LuaType; type.WriteHtml(buffer, numTabs); } else if (change is Descriptor) { var desc = change as Descriptor; desc.WriteHtml(buffer, config); } else { string value; if (change is Security) { var security = change as Security; value = security.Describe(true); } else { value = change.ToString(); } string tagClass; if (value.Contains("🧬")) { tagClass = "ThreadSafety"; } else if (value.StartsWith("[")) { tagClass = "Serialization"; } else if (value.StartsWith("{")) { tagClass = "Security"; } else if (value.StartsWith("\"")) { tagClass = "String"; } else { tagClass = change.GetType().Name; } if (tagClass == "Security" && value.Contains("None")) { tagClass += " darken"; } buffer.WriteElement(tagClass, value, numTabs); } } buffer.CloseClassTag(numTabs - 1); if (multiline) { buffer.CloseClassTag(1, "div"); } }
public void WriteDiffHtml(ReflectionDumper buffer) { string diffType = $"{Type}"; if (Type == DiffType.Add) { diffType += "e"; } diffType += "d"; if (HasParent) { diffType += " child"; } buffer.OpenClassTag(diffType, stack, "div"); buffer.NextLine(); switch (Type) { case DiffType.Change: { // Check if we should keep this on one line, based on the text version. string textSignature = WriteDiffTxt(); bool multiline = textSignature.Contains(NL); // Write what we changed. buffer.WriteElement("WhatChanged", Field, stack + 1); // Write what was changed. Target.WriteHtml(buffer, stack + 1, false); // Changed From, Changed To. From.WriteHtml(buffer, multiline); To.WriteHtml(buffer, multiline); break; } case DiffType.Rename: { // Write what we're renaming. buffer.OpenClassTag(Field, stack + 1); buffer.WriteElement("String", Target.Name, stack + 2); buffer.CloseClassTag(stack + 1); // Write its new name. To.WriteHtml(buffer); break; } case DiffType.Merge: { // Write the elements that are being merged. From.WriteHtml(buffer, false, 0, new Descriptor.HtmlConfig() { TagType = "li", NumTabs = stack + 2, }); // Write what they merged into. buffer.OpenClassTag("MergeListInto", stack + 1); buffer.NextLine(); To.WriteHtml(buffer, false, 1, new Descriptor.HtmlConfig() { TagType = "li", NumTabs = stack + 3, }); buffer.CloseClassTag(stack + 1); break; } case DiffType.Move: { string descType = Target.DescriptorType; string name = $" {Target.Name}"; buffer.WriteElement(descType, name, stack); From.WriteHtml(buffer, true); To.WriteHtml(buffer, true); break; } default: { string descType = Target.DescriptorType; bool detailed = (Type == DiffType.Add); if (Field != descType) { if (Context != null && Context is Tags) { Tags tags = Context as Tags; string tagClass = "TagChange"; if (tags.Count == 1) { tagClass += " singular"; } if (Type == DiffType.Add) { tagClass += " to"; } else { tagClass += " from"; } buffer.OpenClassTag(tagClass, stack + 1); buffer.NextLine(); tags.WriteHtml(buffer, stack + 2); buffer.CloseClassTag(stack + 1); detailed = false; } else { buffer.WriteElement("Field", Field, stack + 1); } } buffer.OpenClassTag("Target", stack + 1); buffer.NextLine(); Target.WriteHtml(buffer, stack + 2, detailed); buffer.CloseClassTag(stack + 1); break; } } if (children.Count > 0) { children.Sort(); children.ForEach(child => child.WriteDiffHtml(buffer)); } buffer.CloseClassTag(stack, "div"); }
public void WriteHtml(ReflectionDumper buffer, HtmlConfig config = null) { if (config == null) { config = new HtmlConfig(); } int numTabs = config.NumTabs; string tagType = config.TagType; bool detailed = config.Detailed; bool diffMode = config.DiffMode; var tokens = GetTokens(detailed); tokens.Remove("DescriptorType"); string schema = GetSchema(detailed); string tagClass = DescriptorType; if (!diffMode && Tags.Contains("Deprecated")) { tagClass += " deprecated"; // The CSS will strike-through this. } if (!diffMode && DescriptorType != "Class" && DescriptorType != "Enum") { tagClass += " child"; } buffer.OpenClassTag(tagClass, numTabs, tagType); buffer.NextLine(); int search = 0; while (true) { int openToken = schema.IndexOf('{', search); if (openToken < 0) { break; } int closeToken = schema.IndexOf('}', openToken); if (closeToken < 0) { break; } string token = schema.Substring(openToken + 1, closeToken - openToken - 1); if (tokens.ContainsKey(token)) { if (token == "Tags") { Tags.WriteHtml(buffer, numTabs + 1); } else if (token == "Parameters" || token.EndsWith("Type")) { Type type = GetType(); foreach (FieldInfo info in type.GetFields()) { if (info.FieldType == typeof(Parameters) && token == "Parameters") { var parameters = info.GetValue(this) as Parameters; parameters.WriteHtml(buffer, numTabs + 1); break; } else if (info.FieldType == typeof(LuaType) && token.EndsWith("Type")) { var luaType = info.GetValue(this) as LuaType; luaType.WriteHtml(buffer, numTabs + 1); break; } } } else { string value = tokens[token] .ToString() .Replace("<", "<") .Replace(">", ">") .Trim(); if (value.Length > 0) { if (token == "ClassName") { token += " " + DescriptorType; } buffer.WriteElement(token, value, numTabs + 1); } } } search = closeToken + 1; } buffer.CloseClassTag(numTabs, tagType); }
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()); }