From 3e8eb1e55d63615750d64c25997b2712563c4074 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 9 Dec 2011 01:25:12 +0100 Subject: [PATCH] Add NRefactory.ConsistencyCheck with round-tripping test. Added parser unit tests for bugs discovered by the round-tripping test. --- .../CSharpProjectContent.cs | 13 +- .../OutputVisitor/CSharpOutputVisitor.cs | 12 +- .../OutputVisitor/IOutputFormatter.cs | 1 + .../TextWriterOutputFormatter.cs | 121 +++++++------ .../CSharpProject.cs | 170 ++++++++++++++++++ ...arpCode.NRefactory.ConsistencyCheck.csproj | 71 ++++++++ .../Program.cs | 84 +++++++++ .../Properties/AssemblyInfo.cs | 31 ++++ .../Readme.txt | 8 + .../RoundtripTest.cs | 104 +++++++++++ .../Solution.cs | 67 +++++++ .../app.config | 6 + .../Expression/PrimitiveExpressionTests.cs | 9 +- .../GeneralScope/AttributeSectionTests.cs | 34 +++- .../PreprocessorDirectiveTests.cs | 55 ++++++ NRefactory.sln | 12 +- 16 files changed, 730 insertions(+), 68 deletions(-) create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/CSharpProject.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Program.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Properties/AssemblyInfo.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Readme.txt create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/Solution.cs create mode 100644 ICSharpCode.NRefactory.ConsistencyCheck/app.config diff --git a/ICSharpCode.NRefactory.CSharp/CSharpProjectContent.cs b/ICSharpCode.NRefactory.CSharp/CSharpProjectContent.cs index 5f198e627..bdc204049 100644 --- a/ICSharpCode.NRefactory.CSharp/CSharpProjectContent.cs +++ b/ICSharpCode.NRefactory.CSharp/CSharpProjectContent.cs @@ -139,7 +139,18 @@ public IProjectContent UpdateProjectContent(IParsedFile oldFile, IParsedFile new public IProjectContent UpdateProjectContent(IEnumerable oldFiles, IEnumerable newFiles) { - throw new NotImplementedException(); + CSharpProjectContent pc = new CSharpProjectContent(this); + if (oldFiles != null) { + foreach (var oldFile in oldFiles) { + pc.parsedFiles.Remove(oldFile.FileName); + } + } + if (newFiles != null) { + foreach (var newFile in newFiles) { + pc.parsedFiles.Add(newFile.FileName, newFile); + } + } + return pc; } IAssembly IAssemblyReference.Resolve(ITypeResolveContext context) diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs index eabfeb940..0a068aeac 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2329,18 +2329,8 @@ public object VisitComment (Comment comment, object data) public object VisitPreProcessorDirective (PreProcessorDirective preProcessorDirective, object data) { - if (lastWritten == LastWritten.Division) { - // When there's a comment starting after a division operator - // "1.0 / /*comment*/a", then we need to insert a space in front of the comment. - formatter.Space (); - } formatter.StartNode (preProcessorDirective); - formatter.WriteToken ("#" + preProcessorDirective.Type.ToString ().ToLower ()); - if (!string.IsNullOrEmpty(preProcessorDirective.Argument)) { - formatter.Space(); - formatter.WriteToken(preProcessorDirective.Argument); - } - formatter.NewLine(); + formatter.WritePreProcessorDirective(preProcessorDirective.Type, preProcessorDirective.Argument); formatter.EndNode (preProcessorDirective); lastWritten = LastWritten.Whitespace; return null; diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs index e1a87f694..66a3ca810 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/IOutputFormatter.cs @@ -55,5 +55,6 @@ public interface IOutputFormatter void NewLine(); void WriteComment(CommentType commentType, string content); + void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument); } } diff --git a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs index f1ee3350f..ed55fc671 100644 --- a/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs +++ b/ICSharpCode.NRefactory.CSharp/OutputVisitor/TextWriterOutputFormatter.cs @@ -77,39 +77,39 @@ public void OpenBrace(BraceStyle style) { bool isAtStartOfLine = needsIndent; switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - WriteIndentation(); - if (!isAtStartOfLine) - textWriter.Write(' '); - textWriter.Write('{'); - break; - case BraceStyle.EndOfLineWithoutSpace: - WriteIndentation(); - textWriter.Write('{'); - break; - case BraceStyle.NextLine: - if (!isAtStartOfLine) + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + WriteIndentation(); + if (!isAtStartOfLine) + textWriter.Write(' '); + textWriter.Write('{'); + break; + case BraceStyle.EndOfLineWithoutSpace: + WriteIndentation(); + textWriter.Write('{'); + break; + case BraceStyle.NextLine: + if (!isAtStartOfLine) + NewLine(); + WriteIndentation(); + textWriter.Write('{'); + break; + + case BraceStyle.NextLineShifted: + NewLine (); + Indent(); + WriteIndentation(); + textWriter.Write('{'); NewLine(); - WriteIndentation(); - textWriter.Write('{'); - break; - - case BraceStyle.NextLineShifted: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - NewLine(); - return; - case BraceStyle.NextLineShifted2: - NewLine (); - Indent(); - WriteIndentation(); - textWriter.Write('{'); - break; - default: - throw new ArgumentOutOfRangeException (); + return; + case BraceStyle.NextLineShifted2: + NewLine (); + Indent(); + WriteIndentation(); + textWriter.Write('{'); + break; + default: + throw new ArgumentOutOfRangeException (); } Indent(); NewLine(); @@ -118,27 +118,27 @@ public void OpenBrace(BraceStyle style) public void CloseBrace(BraceStyle style) { switch (style) { - case BraceStyle.DoNotChange: - case BraceStyle.EndOfLine: - case BraceStyle.EndOfLineWithoutSpace: - case BraceStyle.NextLine: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - break; - case BraceStyle.NextLineShifted: - WriteIndentation(); - textWriter.Write('}'); - Unindent(); - break; - case BraceStyle.NextLineShifted2: - Unindent(); - WriteIndentation(); - textWriter.Write('}'); - Unindent(); - break; - default: - throw new ArgumentOutOfRangeException (); + case BraceStyle.DoNotChange: + case BraceStyle.EndOfLine: + case BraceStyle.EndOfLineWithoutSpace: + case BraceStyle.NextLine: + Unindent(); + WriteIndentation(); + textWriter.Write('}'); + break; + case BraceStyle.NextLineShifted: + WriteIndentation(); + textWriter.Write('}'); + Unindent(); + break; + case BraceStyle.NextLineShifted2: + Unindent(); + WriteIndentation(); + textWriter.Write('}'); + Unindent(); + break; + default: + throw new ArgumentOutOfRangeException (); } } @@ -193,6 +193,21 @@ public void WriteComment(CommentType commentType, string content) } } + public void WritePreProcessorDirective(PreProcessorDirectiveType type, string argument) + { + // pre-processor directive must start on its own line + if (!needsIndent) + NewLine(); + WriteIndentation(); + textWriter.Write('#'); + textWriter.Write(type.ToString().ToLowerInvariant()); + if (!string.IsNullOrEmpty(argument)) { + textWriter.Write(' '); + textWriter.Write(argument); + } + NewLine(); + } + public virtual void StartNode(AstNode node) { } diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/CSharpProject.cs b/ICSharpCode.NRefactory.ConsistencyCheck/CSharpProject.cs new file mode 100644 index 000000000..282038ace --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/CSharpProject.cs @@ -0,0 +1,170 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.TypeSystem; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.ConsistencyCheck +{ + public class CSharpProject + { + public readonly Solution Solution; + public readonly string Title; + public readonly string AssemblyName; + public readonly string FileName; + + public readonly List Files = new List(); + + public readonly bool AllowUnsafeBlocks; + public readonly bool CheckForOverflowUnderflow; + public readonly string[] PreprocessorDefines; + + public IProjectContent ProjectContent; + + public ICompilation Compilation { + get { + return Solution.SolutionSnapshot.GetCompilation(ProjectContent); + } + } + + public CSharpProject(Solution solution, string title, string fileName) + { + this.Solution = solution; + this.Title = title; + this.FileName = fileName; + + var p = new Microsoft.Build.Evaluation.Project(fileName); + this.AssemblyName = p.GetPropertyValue("AssemblyName"); + this.AllowUnsafeBlocks = GetBoolProperty(p, "AllowUnsafeBlocks") ?? false; + this.CheckForOverflowUnderflow = GetBoolProperty(p, "CheckForOverflowUnderflow") ?? false; + this.PreprocessorDefines = p.GetPropertyValue("DefineConstants").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var item in p.GetItems("Compile")) { + Files.Add(new CSharpFile(this, Path.Combine(p.DirectoryPath, item.EvaluatedInclude))); + } + List references = new List(); + foreach (var item in p.GetItems("Reference")) { + string assemblyFileName = null; + if (item.HasMetadata("HintPath")) { + assemblyFileName = Path.Combine(p.DirectoryPath, item.GetMetadataValue("HintPath")); + if (!File.Exists(assemblyFileName)) + assemblyFileName = null; + } + if (assemblyFileName == null) { + assemblyFileName = FindAssembly(Program.AssemblySearchPaths, item.EvaluatedInclude); + } + if (assemblyFileName != null) { + references.Add(Program.LoadAssembly(assemblyFileName)); + } else { + Console.WriteLine("Could not find referenced assembly " + item.EvaluatedInclude); + } + } + foreach (var item in p.GetItems("ProjectReference")) { + references.Add(new ProjectReference(solution, item.GetMetadataValue("Name"))); + } + this.ProjectContent = new CSharpProjectContent() + .SetAssemblyName(this.AssemblyName) + .AddAssemblyReferences(references) + .UpdateProjectContent(null, Files.Select(f => f.ParsedFile)); + } + + string FindAssembly(IEnumerable assemblySearchPaths, string evaluatedInclude) + { + if (evaluatedInclude.IndexOf(',') >= 0) + evaluatedInclude = evaluatedInclude.Substring(0, evaluatedInclude.IndexOf(',')); + foreach (string searchPath in assemblySearchPaths) { + string assemblyFile = Path.Combine(searchPath, evaluatedInclude + ".dll"); + if (File.Exists(assemblyFile)) + return assemblyFile; + } + return null; + } + + static bool? GetBoolProperty(Microsoft.Build.Evaluation.Project p, string propertyName) + { + string val = p.GetPropertyValue(propertyName); + if (val.Equals("true", StringComparison.OrdinalIgnoreCase)) + return true; + if (val.Equals("false", StringComparison.OrdinalIgnoreCase)) + return false; + return null; + } + + public CSharpParser CreateParser() + { + List args = new List(); + if (AllowUnsafeBlocks) + args.Add("-unsafe"); + foreach (string define in PreprocessorDefines) + args.Add("-d:" + define); + return new CSharpParser(args.ToArray()); + } + } + + public class ProjectReference : IAssemblyReference + { + readonly Solution solution; + readonly string projectTitle; + + public ProjectReference(Solution solution, string projectTitle) + { + this.solution = solution; + this.projectTitle = projectTitle; + } + + public IAssembly Resolve(ITypeResolveContext context) + { + var project = solution.Projects.Single(p => string.Equals(p.Title, projectTitle, StringComparison.OrdinalIgnoreCase)); + return project.ProjectContent.Resolve(context); + } + } + + public class CSharpFile + { + public readonly CSharpProject Project; + public readonly string FileName; + + public readonly ITextSource Content; + public readonly int LinesOfCode; + public CompilationUnit CompilationUnit; + public CSharpParsedFile ParsedFile; + + public CSharpFile(CSharpProject project, string fileName) + { + this.Project = project; + this.FileName = fileName; + this.Content = new StringTextSource(File.ReadAllText(FileName)); + this.LinesOfCode = 1 + this.Content.Text.Count(c => c == '\n'); + + CSharpParser p = project.CreateParser(); + this.CompilationUnit = p.Parse(Content.CreateReader(), fileName); + if (p.HasErrors) { + Console.WriteLine("Error parsing " + fileName + ":"); + foreach (var error in p.ErrorPrinter.Errors) { + Console.WriteLine(" " + error.Region + " " + error.Message); + } + } + this.ParsedFile = this.CompilationUnit.ToTypeSystem(); + } + } +} diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj b/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj new file mode 100644 index 000000000..86a7c8533 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/ICSharpCode.NRefactory.ConsistencyCheck.csproj @@ -0,0 +1,71 @@ + + + + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3} + Debug + x86 + Exe + ICSharpCode.NRefactory.ConsistencyCheck + ICSharpCode.NRefactory.ConsistencyCheck + v4.0 + + + Properties + + + x86 + + + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + + + bin\Release\ + False + None + True + False + TRACE + + + + + + 3.5 + + + + 3.5 + + + + 3.5 + + + + + + + + + + + + + + + + {53DCA265-3C3C-42F9-B647-F72BA678122B} + ICSharpCode.NRefactory.CSharp + + + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371} + ICSharpCode.NRefactory + + + + \ No newline at end of file diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs new file mode 100644 index 000000000..b4b5d0fd0 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Program.cs @@ -0,0 +1,84 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Linq; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Utils; + +namespace ICSharpCode.NRefactory.ConsistencyCheck +{ + class Program + { + public static readonly string[] AssemblySearchPaths = { + @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0", + @"C:\Program Files (x86)\GtkSharp\2.12\lib\gtk-sharp-2.0", + @"C:\Program Files (x86)\GtkSharp\2.12\lib\Mono.Posix", + }; + + public const string TempPath = @"C:\temp"; + + public static void Main(string[] args) + { + Solution sln; + using (new Timer("Loading solution... ")) { + sln = new Solution(Path.GetFullPath("../../../NRefactory.sln")); + } + + Console.WriteLine("Loaded {0} lines of code ({1:f1} MB) in {2} files in {3} projects.", + sln.AllFiles.Sum(f => f.LinesOfCode), + sln.AllFiles.Sum(f => f.Content.TextLength) / 1024.0 / 1024.0, + sln.AllFiles.Count(), + sln.Projects.Count); + + using (new Timer("Roundtripping tests... ")) { + foreach (var file in sln.AllFiles) { + RoundtripTest.RunTest(file); + } + } + + Console.Write("Press any key to continue . . . "); + Console.ReadKey(true); + } + + static ConcurrentDictionary assemblyDict = new ConcurrentDictionary(Platform.FileNameComparer); + + public static IUnresolvedAssembly LoadAssembly(string assemblyFileName) + { + return assemblyDict.GetOrAdd(assemblyFileName, file => new CecilLoader().LoadAssemblyFile(file)); + } + + sealed class Timer : IDisposable + { + Stopwatch w = Stopwatch.StartNew(); + + public Timer(string title) + { + Console.Write(title); + } + + public void Dispose() + { + Console.WriteLine(w.Elapsed); + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Properties/AssemblyInfo.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..feefce17f --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +#region Using directives + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ICSharpCode.NRefactory.ConsistencyCheck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ICSharpCode.NRefactory.ConsistencyCheck")] +[assembly: AssemblyCopyright("Copyright 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// This sets the default COM visibility of types in the assembly to invisible. +// If you need to expose a type to COM, use [ComVisible(true)] on that type. +[assembly: ComVisible(false)] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all the values or you can use the default the Revision and +// Build Numbers by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.*")] diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Readme.txt b/ICSharpCode.NRefactory.ConsistencyCheck/Readme.txt new file mode 100644 index 000000000..6b0e48849 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Readme.txt @@ -0,0 +1,8 @@ +This is an automatic consistency check for NRefactory. +It loads a solution file and parses all the source code and referenced libraries, +and then performs a set of consistency checks. +These checks assume that the code is valid C# without any compilation errors, +so make sure to only pass in compilable source code. + +Checks currently being performed: + - diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs b/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs new file mode 100644 index 000000000..9a52ac314 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/RoundtripTest.cs @@ -0,0 +1,104 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Text; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.Editor; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.ConsistencyCheck +{ + public class RoundtripTest + { + public static void RunTest(CSharpFile file) + { + // TODO: also try Windows-style newlines once the parser bug with integer literals followed by \r is fixed + string code = file.Content.Text.Replace("\r\n", "\n"); + if (code.Contains("#pragma")) + return; // skip code with preprocessor directives + if (code.Contains("enum VarianceModifier") || file.FileName.EndsWith("ecore.cs") || file.FileName.EndsWith("method.cs")) + return; // skip enum with ; at end (see TypeDeclarationTests.EnumWithSemicolonAtEnd) + if (file.FileName.EndsWith("KnownTypeReference.cs") || file.FileName.EndsWith("typemanager.cs") || file.FileName.EndsWith("GetAllBaseTypesTest.cs") || file.FileName.EndsWith("Tokens.cs") || file.FileName.EndsWith("OpCode.cs") || file.FileName.EndsWith("MainWindow.cs")) + return; // skip due to optional , at end of array initializer (see ArrayCreateExpressionTests.ArrayInitializerWithCommaAtEnd) + if (file.FileName.EndsWith("cs-parser.cs")) + return; // skip due to completely messed up comment locations + if (file.FileName.EndsWith("PrimitiveExpressionTests.cs")) + return; // skip due to PrimitiveExpressionTests.*WithLeadingDot + if (file.FileName.Contains("FormattingTests") || file.FileName.Contains("ContextAction") || file.FileName.Contains("CodeCompletion")) + return; // skip due to AttributeSectionTests.AttributeWithEmptyParenthesis + if (file.FileName.EndsWith("TypeSystemTests.TestCase.cs")) + return; // skip due to AttributeSectionTests.AssemblyAttributeBeforeNamespace + if (file.FileName.EndsWith("dynamic.cs") || file.FileName.EndsWith("expression.cs")) + return; // skip due to PreprocessorDirectiveTests.NestedInactiveIf + if (file.FileName.EndsWith("property.cs")) + return; // skip due to PreprocessorDirectiveTests.CommentOnEndOfIfDirective + + Roundtrip(file.Project.CreateParser(), file.FileName, code); + } + + public static void Roundtrip(CSharpParser parser, string fileName, string code) + { + // 1. Parse + CompilationUnit cu = parser.Parse(code, fileName); + if (parser.HasErrors) + throw new InvalidOperationException("There were parse errors."); + + // 2. Output + StringWriter w = new StringWriter(); + cu.AcceptVisitor(new CSharpOutputVisitor(w, new CSharpFormattingOptions())); + string generatedCode = w.ToString().TrimEnd(); + + // 3. Compare output with original (modulo whitespaces) + int pos2 = 0; + for (int pos1 = 0; pos1 < code.Length; pos1++) { + if (!char.IsWhiteSpace(code[pos1])) { + while (pos2 < generatedCode.Length && char.IsWhiteSpace(generatedCode[pos2])) + pos2++; + if (pos2 >= generatedCode.Length || code[pos1] != generatedCode[pos2]) { + ReadOnlyDocument doc = new ReadOnlyDocument(code); + File.WriteAllText(Path.Combine(Program.TempPath, "roundtrip-error.cs"), generatedCode); + throw new InvalidOperationException("Mismatch at " + doc.GetLocation(pos1) + " of file " + fileName); + } + pos2++; + } + } + if (pos2 != generatedCode.Length) + throw new InvalidOperationException("Mismatch at end of file " + fileName); + + // 4. Parse generated output + CompilationUnit generatedCU; + try { + generatedCU = parser.Parse(generatedCode, fileName); + } catch { + File.WriteAllText(Path.Combine(Program.TempPath, "roundtrip-error.cs"), generatedCode, Encoding.Unicode); + throw; + } + + if (parser.HasErrors) { + File.WriteAllText(Path.Combine(Program.TempPath, "roundtrip-error.cs"), generatedCode); + throw new InvalidOperationException("There were parse errors in the roundtripped " + fileName); + } + + // 5. Compare AST1 with AST2 + if (!cu.IsMatch(generatedCU)) + throw new InvalidOperationException("AST match failed for " + fileName + "."); + } + } +} diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/Solution.cs b/ICSharpCode.NRefactory.ConsistencyCheck/Solution.cs new file mode 100644 index 000000000..279dd81c8 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/Solution.cs @@ -0,0 +1,67 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory.ConsistencyCheck +{ + public class Solution + { + public readonly string Directory; + public readonly List Projects = new List(); + public readonly ISolutionSnapshot SolutionSnapshot = new DefaultSolutionSnapshot(); + + public IEnumerable AllFiles { + get { + return Projects.SelectMany(p => p.Files); + } + } + + public Solution(string fileName) + { + this.Directory = Path.GetDirectoryName(fileName); + var projectLinePattern = new Regex("Project\\(\"(?.*)\"\\)\\s+=\\s+\"(?.*)\",\\s*\"(?<Location>.*)\",\\s*\"(?<Guid>.*)\""); + foreach (string line in File.ReadLines(fileName)) { + Match match = projectLinePattern.Match(line); + if (match.Success) { + string typeGuid = match.Groups["TypeGuid"].Value; + string title = match.Groups["Title"].Value; + string location = match.Groups["Location"].Value; + string guid = match.Groups["Guid"].Value; + switch (typeGuid.ToUpperInvariant()) { + case "{2150E333-8FDC-42A3-9474-1A3956D46DE8}": // Solution Folder + // ignore folders + break; + case "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}": // C# project + Projects.Add(new CSharpProject(this, title, Path.Combine(Directory, location))); + break; + default: + Console.WriteLine("Project {0} has unsupported type {1}", location, typeGuid); + break; + } + } + } + } + } +} diff --git a/ICSharpCode.NRefactory.ConsistencyCheck/app.config b/ICSharpCode.NRefactory.ConsistencyCheck/app.config new file mode 100644 index 000000000..970c80b50 --- /dev/null +++ b/ICSharpCode.NRefactory.ConsistencyCheck/app.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /> + </startup> +</configuration> \ No newline at end of file diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/Expression/PrimitiveExpressionTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/Expression/PrimitiveExpressionTests.cs index b4f873688..c9aa337f3 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/Expression/PrimitiveExpressionTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/Expression/PrimitiveExpressionTests.cs @@ -46,14 +46,21 @@ void CheckLiteral(string code, object value) PrimitiveExpression pe = ParseUtilCSharp.ParseExpression<PrimitiveExpression>(code); Assert.AreEqual(value.GetType(), pe.Value.GetType()); Assert.AreEqual(value, pe.Value); + Assert.AreEqual(code, pe.LiteralValue); } [Test] - public void DoubleTest1() + public void DoubleWithLeadingDot() { CheckLiteral(".5e-06", .5e-06); } + [Test] + public void FloatWithLeadingDot() + { + CheckLiteral(".5e-06f", .5e-06f); + } + [Test] public void CharTest1() { diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs index 17def861a..baaf7b4e3 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs @@ -17,9 +17,9 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.IO; using System.Linq; using System.Text.RegularExpressions; - using ICSharpCode.NRefactory.PatternMatching; using ICSharpCode.NRefactory.TypeSystem; using NUnit.Framework; @@ -72,6 +72,26 @@ public void TypeAttribute() Assert.AreEqual("type", decl.AttributeTarget); } + [Test] + public void AttributeWithoutParenthesis() + { + string program = @"[Attr] class Test {}"; + TypeDeclaration type = ParseUtilCSharp.ParseGlobal<TypeDeclaration>(program); + var attr = type.Attributes.Single().Attributes.Single(); + Assert.IsTrue(attr.GetChildByRole(AstNode.Roles.LPar).IsNull); + Assert.IsTrue(attr.GetChildByRole(AstNode.Roles.RPar).IsNull); + } + + [Test, Ignore("Parser bug - parenthesis are missing")] + public void AttributeWithEmptyParenthesis() + { + string program = @"[Attr()] class Test {}"; + TypeDeclaration type = ParseUtilCSharp.ParseGlobal<TypeDeclaration>(program); + var attr = type.Attributes.Single().Attributes.Single(); + Assert.IsFalse(attr.GetChildByRole(AstNode.Roles.LPar).IsNull); + Assert.IsFalse(attr.GetChildByRole(AstNode.Roles.RPar).IsNull); + } + [Test] public void TwoAttributesInSameSection() { @@ -170,5 +190,17 @@ public void AttributeWithNamedArguments() } }}); } + + [Test] + public void AssemblyAttributeBeforeNamespace() + { + var cu = new CSharpParser().Parse(new StringReader("using System; [assembly: Attr] namespace X {}"), "code.cs"); + Assert.AreEqual( + new Type[] { + typeof(UsingDeclaration), + typeof(AttributeSection), + typeof(NamespaceDeclaration) + }, cu.Children.Select(c => c.GetType()).ToArray()); + } } } diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/PreprocessorDirectiveTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/PreprocessorDirectiveTests.cs index 940ba7bac..e88d633bf 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/PreprocessorDirectiveTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/PreprocessorDirectiveTests.cs @@ -66,6 +66,61 @@ class A {} Assert.AreEqual(new TextLocation(4, 8), pp.Last().EndLocation); } + [Test] + public void NestedInactiveIf() + { + string program = @"namespace NS { + #if SOMETHING + class A { + #if B + void M() {} + #endif + } + #endif +}"; + NamespaceDeclaration ns = ParseUtilCSharp.ParseGlobal<NamespaceDeclaration>(program); + Assert.AreEqual(0, ns.Members.Count); + + Assert.AreEqual(new Role[] { + AstNode.Roles.Keyword, + AstNode.Roles.Identifier, + AstNode.Roles.LBrace, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.Comment, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.Comment, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.Comment, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.RBrace + }, ns.Children.Select(c => c.Role).ToArray()); + } + + [Test] + public void CommentOnEndOfIfDirective() + { + string program = @"namespace NS { + #if SOMETHING // comment + class A { } + #endif +}"; + NamespaceDeclaration ns = ParseUtilCSharp.ParseGlobal<NamespaceDeclaration>(program); + Assert.AreEqual(0, ns.Members.Count); + + Assert.AreEqual(new Role[] { + AstNode.Roles.Keyword, + AstNode.Roles.Identifier, + AstNode.Roles.LBrace, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.Comment, + AstNode.Roles.Comment, + AstNode.Roles.PreProcessorDirective, + AstNode.Roles.RBrace + }, ns.Children.Select(c => c.Role).ToArray()); + Assert.AreEqual(CommentType.SingleLine, ns.GetChildrenByRole(AstNode.Roles.Comment).First().CommentType); + Assert.AreEqual(CommentType.InactiveCode, ns.GetChildrenByRole(AstNode.Roles.Comment).Last().CommentType); + } + [Test] public void PragmaWarning() { diff --git a/NRefactory.sln b/NRefactory.sln index 057453a59..1cee533d3 100644 --- a/NRefactory.sln +++ b/NRefactory.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -# SharpDevelop 4.2.0.8212-alpha +# SharpDevelop 4.2.0.8278-alpha Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DC98210E-1646-483B-819A-2BB8272461E4}" ProjectSection(SolutionItems) = preProject README = README @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.CSha EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.GtkDemo", "ICSharpCode.NRefactory.GtkDemo\ICSharpCode.NRefactory.GtkDemo.csproj", "{A7EEF7F8-238F-459D-95A9-96467539641D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.ConsistencyCheck", "ICSharpCode.NRefactory.ConsistencyCheck\ICSharpCode.NRefactory.ConsistencyCheck.csproj", "{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,6 +88,14 @@ Global {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|Any CPU.Build.0 = net_4_0_Release|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.ActiveCfg = net_4_0_Release|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.Build.0 = net_4_0_Release|Any CPU + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|Any CPU.Build.0 = Debug|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|x86.Build.0 = Debug|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|x86.ActiveCfg = Debug|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|Any CPU.Build.0 = Release|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|Any CPU.ActiveCfg = Release|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|x86.Build.0 = Release|x86 + {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|x86.ActiveCfg = Release|x86 EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = ICSharpCode.NRefactory.Demo\ICSharpCode.NRefactory.Demo.csproj