ProjectBuilder.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. Document document = CreateDocument (_sourceCode);
  55. Compilation? compilation = await document.Project.GetCompilationAsync ();
  56. ImmutableArray<Diagnostic> diagnostics = compilation!.GetDiagnostics ();
  57. IEnumerable<Diagnostic> errors = diagnostics.Where (d => d.Severity == DiagnosticSeverity.Error);
  58. IEnumerable<Diagnostic> enumerable = errors as Diagnostic [] ?? errors.ToArray ();
  59. if (enumerable.Any ())
  60. {
  61. string errorMessages = string.Join (Environment.NewLine, enumerable.Select (e => e.ToString ()));
  62. throw new Exception ("Compilation failed with errors:" + Environment.NewLine + errorMessages);
  63. }
  64. // Run analyzer
  65. ImmutableArray<Diagnostic> analyzerDiagnostics = await GetAnalyzerDiagnosticsAsync (compilation, _analyzer);
  66. Assert.NotEmpty (analyzerDiagnostics);
  67. if (_expectedFixedCode != null)
  68. {
  69. if (_codeFix == null)
  70. {
  71. throw new InvalidOperationException ("Expected code fix but none was set.");
  72. }
  73. Document? fixedDocument = await ApplyCodeFixAsync (document, analyzerDiagnostics.First (), _codeFix);
  74. if (fixedDocument is { })
  75. {
  76. Document formattedDocument = await Formatter.FormatAsync (fixedDocument);
  77. string fixedSource = (await formattedDocument.GetTextAsync ()).ToString ();
  78. Assert.Equal (_expectedFixedCode, fixedSource);
  79. }
  80. }
  81. }
  82. private static Document CreateDocument (string source)
  83. {
  84. string dd = typeof (Enumerable).GetTypeInfo ().Assembly.Location;
  85. DirectoryInfo coreDir = Directory.GetParent (dd) ?? throw new Exception ($"Could not find parent directory of dotnet sdk. Sdk directory was {dd}");
  86. AdhocWorkspace workspace = new AdhocWorkspace ();
  87. ProjectId projectId = ProjectId.CreateNewId ();
  88. DocumentId documentId = DocumentId.CreateNewId (projectId);
  89. List<MetadataReference> references =
  90. [
  91. MetadataReference.CreateFromFile (typeof (Button).Assembly.Location),
  92. MetadataReference.CreateFromFile (typeof (View).Assembly.Location),
  93. MetadataReference.CreateFromFile (typeof (System.IO.FileSystemInfo).Assembly.Location),
  94. MetadataReference.CreateFromFile (typeof (System.Linq.Enumerable).Assembly.Location),
  95. MetadataReference.CreateFromFile (typeof (object).Assembly.Location),
  96. MetadataReference.CreateFromFile (typeof (MarshalByValueComponent).Assembly.Location),
  97. MetadataReference.CreateFromFile (typeof (ObservableCollection<string>).Assembly.Location),
  98. // New assemblies required by Terminal.Gui version 2
  99. MetadataReference.CreateFromFile (typeof (Size).Assembly.Location),
  100. MetadataReference.CreateFromFile (typeof (CanBeNullAttribute).Assembly.Location),
  101. MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "mscorlib.dll")),
  102. MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Runtime.dll")),
  103. MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Collections.dll")),
  104. MetadataReference.CreateFromFile (Path.Combine (coreDir.FullName, "System.Data.Common.dll"))
  105. // Add more as necessary
  106. ];
  107. ProjectInfo projectInfo = ProjectInfo.Create (
  108. projectId,
  109. VersionStamp.Create (),
  110. "TestProject",
  111. "TestAssembly",
  112. LanguageNames.CSharp,
  113. compilationOptions: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary),
  114. metadataReferences: references);
  115. Solution solution = workspace.CurrentSolution
  116. .AddProject (projectInfo)
  117. .AddDocument (documentId, "Test.cs", SourceText.From (source));
  118. return solution.GetDocument (documentId)!;
  119. }
  120. private static async Task<ImmutableArray<Diagnostic>> GetAnalyzerDiagnosticsAsync (Compilation compilation, DiagnosticAnalyzer analyzer)
  121. {
  122. CompilationWithAnalyzers compilationWithAnalyzers = compilation.WithAnalyzers ([analyzer]);
  123. return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync ();
  124. }
  125. private static async Task<Document?> ApplyCodeFixAsync (Document document, Diagnostic diagnostic, CodeFixProvider codeFix)
  126. {
  127. CodeAction? codeAction = null;
  128. var context = new CodeFixContext ((TextDocument)document, diagnostic, (action, _) => codeAction = action, CancellationToken.None);
  129. await codeFix.RegisterCodeFixesAsync (context);
  130. if (codeAction == null)
  131. {
  132. throw new InvalidOperationException ("Code fix did not register a fix.");
  133. }
  134. ImmutableArray<CodeActionOperation> operations = await codeAction.GetOperationsAsync (CancellationToken.None);
  135. Solution solution = operations.OfType<ApplyChangesOperation> ().First ().ChangedSolution;
  136. return solution.GetDocument (document.Id);
  137. }
  138. }