ProjectBuilder.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using Microsoft.CodeAnalysis;
  2. using Microsoft.CodeAnalysis.CSharp;
  3. using Microsoft.CodeAnalysis.Text;
  4. using Microsoft.CodeAnalysis.CodeFixes;
  5. using Microsoft.CodeAnalysis.Diagnostics;
  6. using System.Collections.Immutable;
  7. using System.Collections.ObjectModel;
  8. using System.ComponentModel;
  9. using System.Drawing;
  10. using Microsoft.CodeAnalysis.CodeActions;
  11. using Terminal.Gui.ViewBase;
  12. using Terminal.Gui.Views;
  13. using Document = Microsoft.CodeAnalysis.Document;
  14. using Formatter = Microsoft.CodeAnalysis.Formatting.Formatter;
  15. using System.Reflection;
  16. using JetBrains.Annotations;
  17. public sealed class ProjectBuilder
  18. {
  19. private string _sourceCode;
  20. private string _expectedFixedCode;
  21. private DiagnosticAnalyzer _analyzer;
  22. private CodeFixProvider _codeFix;
  23. public ProjectBuilder WithSourceCode (string source)
  24. {
  25. _sourceCode = source;
  26. return this;
  27. }
  28. public ProjectBuilder ShouldFixCodeWith (string expected)
  29. {
  30. _expectedFixedCode = expected;
  31. return this;
  32. }
  33. public ProjectBuilder WithAnalyzer (DiagnosticAnalyzer analyzer)
  34. {
  35. _analyzer = analyzer;
  36. return this;
  37. }
  38. public ProjectBuilder WithCodeFix (CodeFixProvider codeFix)
  39. {
  40. _codeFix = codeFix;
  41. return this;
  42. }
  43. public async Task ValidateAsync ()
  44. {
  45. if (_sourceCode == null)
  46. {
  47. throw new InvalidOperationException ("Source code not set.");
  48. }
  49. if (_analyzer == null)
  50. {
  51. throw new InvalidOperationException ("Analyzer not set.");
  52. }
  53. // Parse original document
  54. var document = CreateDocument (_sourceCode);
  55. var compilation = await document.Project.GetCompilationAsync ();
  56. var diagnostics = compilation.GetDiagnostics ();
  57. var errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
  58. if (errors.Any ())
  59. {
  60. var errorMessages = string.Join (Environment.NewLine, errors.Select (e => e.ToString ()));
  61. throw new Exception ("Compilation failed with errors:" + Environment.NewLine + errorMessages);
  62. }
  63. // Run analyzer
  64. var analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
  65. Assert.NotEmpty (analyzerDiagnostics);
  66. if (_expectedFixedCode != null)
  67. {
  68. if (_codeFix == null)
  69. {
  70. throw new InvalidOperationException ("Expected code fix but none was set.");
  71. }
  72. var fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
  73. var formattedDocument = await Formatter.FormatAsync (fixedDocument);
  74. var fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
  75. Assert.Equal (_expectedFixedCode, fixedSource);
  76. }
  77. }
  78. private static Document CreateDocument (string source)
  79. {
  80. var dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
  81. var coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk. Sdk directory was {dd}");
  82. var workspace = new AdhocWorkspace ();
  83. var projectId = ProjectId.CreateNewId ();
  84. var documentId = DocumentId.CreateNewId (projectId);
  85. var references = new List<MetadataReference> ()
  86. {
  87. MetadataReference.CreateFromFile(typeof(Button).Assembly.Location),
  88. MetadataReference.CreateFromFile(typeof(View).Assembly.Location),
  89. MetadataReference.CreateFromFile(typeof(System.IO.FileSystemInfo).Assembly.Location),
  90. MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
  91. MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
  92. MetadataReference.CreateFromFile(typeof(MarshalByValueComponent).Assembly.Location),
  93. MetadataReference.CreateFromFile(typeof(ObservableCollection<string>).Assembly.Location),
  94. // New assemblies required by Terminal.Gui version 2
  95. MetadataReference.CreateFromFile(typeof(Size).Assembly.Location),
  96. MetadataReference.CreateFromFile(typeof(CanBeNullAttribute).Assembly.Location),
  97. MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "mscorlib.dll")),
  98. MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Runtime.dll")),
  99. MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Collections.dll")),
  100. MetadataReference.CreateFromFile(Path.Combine(coreDir.FullName, "System.Data.Common.dll")),
  101. // Add more as necessary
  102. };
  103. var projectInfo = ProjectInfo.Create (
  104. projectId,
  105. VersionStamp.Create (),
  106. "TestProject",
  107. "TestAssembly",
  108. LanguageNames.CSharp,
  109. compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
  110. metadataReferences: references);
  111. var solution = workspace.CurrentSolution
  112. .AddProject (projectInfo)
  113. .AddDocument (documentId, "Test.cs", SourceText.From (source));
  114. return solution.GetDocument (documentId)!;
  115. }
  116. private static async Task<ImmutableArray<Diagnostic>> GetAnalyzerDiagnosticsAsync (Compilation compilation, DiagnosticAnalyzer analyzer)
  117. {
  118. var compilationWithAnalyzers = compilation.WithAnalyzers (ImmutableArray.Create (analyzer));
  119. return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync ();
  120. }
  121. private static async Task<Document> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
  122. {
  123. CodeAction _codeAction = null;
  124. var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => _codeAction = action, CancellationToken.None);
  125. await codeFix.RegisterCodeFixesAsync (context);
  126. if (_codeAction == null)
  127. {
  128. throw new InvalidOperationException ("Code fix did not register a fix.");
  129. }
  130. var operations = await _codeAction.GetOperationsAsync (CancellationToken.None);
  131. var solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
  132. return solution.GetDocument (document.Id);
  133. }
  134. }