using System; using System.Collections.Generic; using System.IO; using System.Linq; using DiffLib; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using NUnit.Framework; namespace ICSharpCode.Decompiler.Tests.Helpers { public static class CodeAssert { public static void FilesAreEqual(string fileName1, string fileName2, string[] definedSymbols = null) { AreEqual(File.ReadAllText(fileName1), File.ReadAllText(fileName2), definedSymbols); } public static void AreEqual(string input1, string input2, string[] definedSymbols = null) { var diff = new StringWriter(); if (!CodeComparer.Compare(input1, input2, diff, CodeComparer.NormalizeLine, definedSymbols)) { Assert.Fail(diff.ToString()); } } } public static class CodeComparer { public static bool Compare(string input1, string input2, StringWriter diff, Func normalizeLine, string[] definedSymbols = null) { var collection1 = NormalizeAndSplitCode(input1, definedSymbols ?? new string[0]); var collection2 = NormalizeAndSplitCode(input2, definedSymbols ?? new string[0]); var diffSections = DiffLib.Diff.CalculateSections( collection1, collection2, new CodeLineEqualityComparer(normalizeLine) ); var alignedDiff = Diff.AlignElements(collection1, collection2, diffSections, new StringSimilarityDiffElementAligner()); bool result = true, ignoreChange; int line1 = 0, line2 = 0; foreach (var change in alignedDiff) { switch (change.Operation) { case DiffOperation.Match: diff.Write("{0,4} {1,4} ", ++line1, ++line2); diff.Write(" "); diff.WriteLine(change.ElementFromCollection1.Value); break; case DiffOperation.Insert: diff.Write(" {1,4} ", line1, ++line2); result &= ignoreChange = ShouldIgnoreChange(change.ElementFromCollection2.Value); diff.Write(ignoreChange ? " " : " + "); diff.WriteLine(change.ElementFromCollection2.Value); break; case DiffOperation.Delete: diff.Write("{0,4} ", ++line1, line2); result &= ignoreChange = ShouldIgnoreChange(change.ElementFromCollection1.Value); diff.Write(ignoreChange ? " " : " - "); diff.WriteLine(change.ElementFromCollection1.Value); break; case DiffOperation.Modify: case DiffOperation.Replace: diff.Write("{0,4} ", ++line1, line2); result = false; diff.Write("(-) "); diff.WriteLine(change.ElementFromCollection1.Value); diff.Write(" {1,4} ", line1, ++line2); diff.Write("(+) "); diff.WriteLine(change.ElementFromCollection2.Value); break; } } return result; } class CodeLineEqualityComparer : IEqualityComparer { private IEqualityComparer baseComparer = EqualityComparer.Default; private Func normalizeLine; public CodeLineEqualityComparer(Func normalizeLine) { this.normalizeLine = normalizeLine; } public bool Equals(string x, string y) { return baseComparer.Equals( normalizeLine(x), normalizeLine(y) ); } public int GetHashCode(string obj) { return baseComparer.GetHashCode(NormalizeLine(obj)); } } public static string NormalizeLine(string line) { line = line.Trim(); var index = line.IndexOf("//", StringComparison.Ordinal); if (index >= 0) { return line.Substring(0, index); } else if (line.StartsWith("#", StringComparison.Ordinal)) { return string.Empty; } else { return line; } } private static bool ShouldIgnoreChange(string line) { // for the result, we should ignore blank lines and added comments return NormalizeLine(line) == string.Empty; } class DeleteDisabledTextRewriter : CSharpSyntaxRewriter { public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia) { if (trivia.IsKind(SyntaxKind.DisabledTextTrivia)) { return default(SyntaxTrivia); // delete } return base.VisitTrivia(trivia); } } private static IList NormalizeAndSplitCode(string input, IEnumerable definedSymbols) { var syntaxTree = CSharpSyntaxTree.ParseText(input, new CSharpParseOptions(preprocessorSymbols: definedSymbols)); var result = new DeleteDisabledTextRewriter().Visit(syntaxTree.GetRoot()); input = result.ToFullString(); return input.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); } } }