public static async Task <SyntaxTree> GenerateAsync(string source) { var document = CreateProject(source).Documents.Single(); var tree = await document.GetSyntaxTreeAsync(); if (tree is null) { throw new InvalidOperationException("Could not get the syntax tree of the sources"); } var compilation = (CSharpCompilation?)await document.Project.GetCompilationAsync(); if (compilation is null) { throw new InvalidOperationException("Could not compile the sources"); } var diagnostics = compilation.GetDiagnostics(); Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning)); var progress = new Progress <Diagnostic>(); var result = await DocumentTransform.TransformAsync(compilation, tree, null, Assembly.Load, progress, CancellationToken.None); return(result); }
protected async Task <GenerationResult> GenerateAsync(SourceText inputSource) { var solution = this.solution.WithDocumentText(this.inputDocumentId, inputSource); var inputDocument = solution.GetDocument(this.inputDocumentId); var generatorDiagnostics = new List <Diagnostic>(); var progress = new SynchronousProgress <Diagnostic>(generatorDiagnostics.Add); var inputCompilation = (CSharpCompilation)await inputDocument.Project.GetCompilationAsync(); var inputSyntaxTree = await inputDocument.GetSyntaxTreeAsync(); var outputSyntaxTree = await DocumentTransform.TransformAsync(inputCompilation, inputSyntaxTree, null, Assembly.Load, progress); var outputDocument = inputDocument.Project .AddDocument("output.cs", outputSyntaxTree.GetRoot()); // Make sure the result compiles without errors or warnings. var compilation = await outputDocument.Project.GetCompilationAsync(); var compilationDiagnostics = compilation.GetDiagnostics(); SourceText outputDocumentText = await outputDocument.GetTextAsync(); this.logger.WriteLine("{0}", outputDocumentText); // Verify all line endings are consistent (otherwise VS can bug the heck out of the user if they have the generated file open). string firstLineEnding = null; foreach (var line in outputDocumentText.Lines) { string actualNewLine = line.Text.GetSubText(TextSpan.FromBounds(line.End, line.EndIncludingLineBreak)).ToString(); if (firstLineEnding == null) { firstLineEnding = actualNewLine; } else if (actualNewLine != firstLineEnding && actualNewLine.Length > 0) { string expected = EscapeLineEndingCharacters(firstLineEnding); string actual = EscapeLineEndingCharacters(actualNewLine); Assert.True(false, $"Expected line ending characters '{expected}' but found '{actual}' on line {line.LineNumber + 1}.\nContent: {line}"); } } var semanticModel = await outputDocument.GetSemanticModelAsync(); var result = new GenerationResult(outputDocument, semanticModel, generatorDiagnostics, compilationDiagnostics); foreach (var diagnostic in generatorDiagnostics) { this.logger.WriteLine(diagnostic.ToString()); } foreach (var diagnostic in result.CompilationDiagnostics) { this.logger.WriteLine(diagnostic.ToString()); } return(result); }
protected async Task <GenerationResult> GenerateAsync(SourceText inputSource) { var solution = this.solution.WithDocumentText(this.inputDocumentId, inputSource); var inputDocument = solution.GetDocument(this.inputDocumentId); var outputDocument = await DocumentTransform.TransformAsync(inputDocument, new MockProgress()); // Make sure there are no compile errors. var compilation = await outputDocument.Project.GetCompilationAsync(); var diagnostics = compilation.GetDiagnostics(); var errors = from diagnostic in diagnostics where diagnostic.Severity >= DiagnosticSeverity.Error select diagnostic; var warnings = from diagnostic in diagnostics where diagnostic.Severity >= DiagnosticSeverity.Warning select diagnostic; SourceText outputDocumentText = await outputDocument.GetTextAsync(); this.logger.WriteLine("{0}", outputDocumentText); foreach (var error in errors) { this.logger.WriteLine("{0}", error); } foreach (var warning in warnings) { this.logger.WriteLine("{0}", warning); } Assert.Empty(errors); Assert.Empty(warnings); // Verify all line endings are consistent (otherwise VS can bug the heck out of the user if they have the generated file open). string firstLineEnding = null; foreach (var line in outputDocumentText.Lines) { string actualNewLine = line.Text.GetSubText(TextSpan.FromBounds(line.End, line.EndIncludingLineBreak)).ToString(); if (firstLineEnding == null) { firstLineEnding = actualNewLine; } else if (actualNewLine != firstLineEnding && actualNewLine.Length > 0) { string expected = EscapeLineEndingCharacters(firstLineEnding); string actual = EscapeLineEndingCharacters(actualNewLine); Assert.True(false, $"Expected line ending characters '{expected}' but found '{actual}' on line {line.LineNumber + 1}.\nContent: {line}"); } } var semanticModel = await outputDocument.GetSemanticModelAsync(); return(new GenerationResult(outputDocument, semanticModel)); }
protected static SyntaxTree Generate(string source) { var document = CreateProject(source).Documents.Single(); var tree = document.GetSyntaxTreeAsync().GetAwaiter().GetResult(); var compilation = (CSharpCompilation)document.Project.GetCompilationAsync().GetAwaiter().GetResult(); var diagnostics = compilation.GetDiagnostics(); Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning)); var progress = new Progress <Diagnostic>(); var result = DocumentTransform.TransformAsync(compilation, tree, null, Assembly.Load, Enumerable.Empty <IFreeCodeGenerator>(), progress).GetAwaiter().GetResult(); return(result); }
protected static async Task <TransformResult> GenerateAsync(string source) { var document = CreateProject(source).Documents.Single(); var tree = await document.GetSyntaxTreeAsync(); var compilation = (CSharpCompilation)(await document.Project.GetCompilationAsync()); var diagnostics = compilation.GetDiagnostics(); Assert.Empty(diagnostics.Where(x => x.Severity >= DiagnosticSeverity.Warning)); var progress = new Progress <Diagnostic>(); var result = await DocumentTransform.TransformAsync(compilation, tree, null, null, Assembly.Load, progress, CancellationToken.None); return(result); }
public int Generate(string inputFilePath, string inputFileContents, string defaultNamespace, IntPtr[] outputFileContents, out uint outputLength, IVsGeneratorProgress generatorProgress) { if (outputFileContents != null && outputFileContents.Length > 0) { // Do this first, before input validation, so that the // catch block doesn't try to free memory with an uninitialized pointer. outputFileContents[0] = IntPtr.Zero; } try { Requires.NotNullOrEmpty(inputFilePath, "inputFilePath"); Requires.NotNull(outputFileContents, "outputFileContents"); Requires.Argument(outputFileContents.Length > 0, "outputFileContents", "Non-empty array expected."); string generated = null; ThreadHelper.JoinableTaskFactory.Run(async delegate { VisualStudioWorkspace workspace = GetRoslynWorkspace(); var inputDocumentId = workspace.CurrentSolution.GetDocumentIdsWithFilePath(inputFilePath).First(); var inputDocument = workspace.CurrentSolution.GetDocument(inputDocumentId); var outputDocument = await DocumentTransform.TransformAsync(inputDocument, new ProgressShim(generatorProgress)); // Now render as a complete string, as necessary by our single file generator. var reducedDocumentText = await outputDocument.GetTextAsync(); generated = reducedDocumentText.ToString(); }); // Translate the string we've built up into the bytes of COM memory required. var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true); byte[] bytes = encoding.GetBytes(generated); outputLength = (uint)bytes.Length; outputFileContents[0] = Marshal.AllocCoTaskMem(bytes.Length); Marshal.Copy(bytes, 0, outputFileContents[0], bytes.Length); return(VSConstants.S_OK); } catch (Exception ex) { if (outputFileContents[0] != IntPtr.Zero) { Marshal.FreeCoTaskMem(outputFileContents[0]); outputFileContents[0] = IntPtr.Zero; } outputLength = 0; return(Marshal.GetHRForException(ex)); } }
protected async Task <GenerationResult> GenerateAsync(SourceText inputSource) { var solution = this.solution.WithDocumentText(this.inputDocumentId, inputSource); var inputDocument = solution.GetDocument(this.inputDocumentId); var outputDocument = await DocumentTransform.TransformAsync(inputDocument, new MockProgress()); // Make sure there are no compile errors. var compilation = await outputDocument.Project.GetCompilationAsync(); var diagnostics = compilation.GetDiagnostics(); var errors = from diagnostic in diagnostics where diagnostic.Severity >= DiagnosticSeverity.Error select diagnostic; var warnings = from diagnostic in diagnostics where diagnostic.Severity >= DiagnosticSeverity.Warning select diagnostic; SourceText outputDocumentText = await outputDocument.GetTextAsync(); Console.WriteLine(outputDocumentText); foreach (var error in errors) { Console.WriteLine(error); } foreach (var warning in warnings) { Console.WriteLine(warning); } Assert.Empty(errors); Assert.Empty(warnings); // Verify all line endings are consistent (otherwise VS can bug the heck out of the user if they have the generated file open). foreach (var line in outputDocumentText.Lines) { string actualNewLine = line.Text.GetSubText(TextSpan.FromBounds(line.End, line.EndIncludingLineBreak)).ToString(); if (actualNewLine != Environment.NewLine && actualNewLine.Length > 0) { Assert.True(false, string.Format("Line {0} has unexpected line ending characters. Content: {1}", line.LineNumber, line)); } } var semanticModel = await outputDocument.GetSemanticModelAsync(); return(new GenerationResult(outputDocument, semanticModel)); }
public void Execute() { Task.Run(async delegate { var project = this.CreateProject(); var outputFiles = new List <ITaskItem>(); // For incremental build, we want to consider the input->output files as well as the assemblies involved in code generation. DateTime assembliesLastModified = GetLastModifiedAssemblyTime(); foreach (var inputDocument in project.Documents) { this.CancellationToken.ThrowIfCancellationRequested(); // Skip over documents that aren't on the prescribed list of files to scan. if (!this.CompileToGenerateFromAttributes.Any(i => i.ItemSpec == inputDocument.Name)) { continue; } string outputFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetFileNameWithoutExtension(inputDocument.Name) + ".generated.cs"); // Code generation is relatively fast, but it's not free. // And when we run the Simplifier.ReduceAsync it's dog slow. // So skip files that haven't changed since we last generated them. bool generated = false; DateTime outputLastModified = File.GetLastWriteTime(outputFilePath); if (File.GetLastWriteTime(inputDocument.Name) > outputLastModified || assembliesLastModified > outputLastModified) { this.Log.LogMessage(MessageImportance.Normal, "{0} -> {1}", inputDocument.Name, outputFilePath); var outputDocument = await DocumentTransform.TransformAsync(inputDocument, new ProgressLogger(this.Log, inputDocument.Name)); // Only produce a new file if the generated document is not empty. var semanticModel = await outputDocument.GetSemanticModelAsync(this.CancellationToken); if (!semanticModel.GetDeclarationsInSpan(TextSpan.FromBounds(0, semanticModel.SyntaxTree.Length), false, this.CancellationToken).IsEmpty) { var outputText = await outputDocument.GetTextAsync(this.CancellationToken); using (var outputFileStream = File.OpenWrite(outputFilePath)) using (var outputWriter = new StreamWriter(outputFileStream)) { outputText.Write(outputWriter); // Truncate any data that may be beyond this point if the file existed previously. outputWriter.Flush(); outputFileStream.SetLength(outputFileStream.Position); } generated = true; } } else { generated = true; } if (generated) { var outputItem = new TaskItem(outputFilePath); outputFiles.Add(outputItem); } } this.GeneratedCompile = outputFiles.ToArray(); }).GetAwaiter().GetResult(); }
public void Execute() { #if NET46 AppDomain.CurrentDomain.AssemblyResolve += (s, e) => { return(this.TryLoadAssembly(new AssemblyName(e.Name))); }; #else this.loadContext.Resolving += (lc, an) => { Assumes.True(ReferenceEquals(lc, this.loadContext)); return(this.TryLoadAssembly(an)); }; #endif Task.Run(async delegate { var compilation = this.CreateCompilation(); var outputFiles = new List <ITaskItem>(); var writtenFiles = new List <ITaskItem>(); string generatorAssemblyInputsFile = Path.Combine(this.IntermediateOutputDirectory, "CodeGeneration.Roslyn.InputAssemblies.txt"); // For incremental build, we want to consider the input->output files as well as the assemblies involved in code generation. DateTime assembliesLastModified = GetLastModifiedAssemblyTime(generatorAssemblyInputsFile); var explicitIncludeList = new HashSet <string>( from item in this.Compile where string.Equals(item.GetMetadata("Generator"), $"MSBuild:{this.TargetName}", StringComparison.OrdinalIgnoreCase) select item.ItemSpec, StringComparer.OrdinalIgnoreCase); foreach (var inputSyntaxTree in compilation.SyntaxTrees) { this.CancellationToken.ThrowIfCancellationRequested(); // Skip over documents that aren't on the prescribed list of files to scan. if (!explicitIncludeList.Contains(inputSyntaxTree.FilePath)) { continue; } string sourceHash = inputSyntaxTree.FilePath.GetHashCode().ToString("x", CultureInfo.InvariantCulture); string outputFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetFileNameWithoutExtension(inputSyntaxTree.FilePath) + $".{sourceHash}.generated.cs"); // Code generation is relatively fast, but it's not free. // So skip files that haven't changed since we last generated them. DateTime outputLastModified = File.Exists(outputFilePath) ? File.GetLastWriteTime(outputFilePath) : DateTime.MinValue; if (File.GetLastWriteTime(inputSyntaxTree.FilePath) > outputLastModified || assembliesLastModified > outputLastModified) { var generatedSyntaxTree = await DocumentTransform.TransformAsync( compilation, inputSyntaxTree, new ProgressLogger(this.Log, inputSyntaxTree.FilePath)); var outputText = generatedSyntaxTree.GetText(this.CancellationToken); using (var outputFileStream = File.OpenWrite(outputFilePath)) using (var outputWriter = new StreamWriter(outputFileStream)) { outputText.Write(outputWriter); // Truncate any data that may be beyond this point if the file existed previously. outputWriter.Flush(); outputFileStream.SetLength(outputFileStream.Position); } bool anyTypesGenerated = generatedSyntaxTree?.GetRoot(this.CancellationToken).DescendantNodes().OfType <TypeDeclarationSyntax>().Any() ?? false; if (anyTypesGenerated) { this.Log.LogMessage(MessageImportance.Normal, "{0} -> {1}", inputSyntaxTree.FilePath, outputFilePath); } else { this.Log.LogMessage(MessageImportance.Low, "{0} used no code generation attributes.", inputSyntaxTree.FilePath); } } var outputItem = new TaskItem(outputFilePath); outputFiles.Add(outputItem); } this.SaveGeneratorAssemblyList(generatorAssemblyInputsFile); writtenFiles.Add(new TaskItem(generatorAssemblyInputsFile)); this.GeneratedCompile = outputFiles.ToArray(); this.AdditionalWrittenFiles = writtenFiles.ToArray(); }).GetAwaiter().GetResult(); }
public void Execute() { Task.Run(async delegate { AppDomain.CurrentDomain.AssemblyResolve += (s, e) => { return(this.TryLoadAssembly(new AssemblyName(e.Name))); }; var project = this.CreateProject(); var outputFiles = new List <ITaskItem>(); var writtenFiles = new List <ITaskItem>(); string generatorAssemblyInputsFile = Path.Combine(this.IntermediateOutputDirectory, "CodeGeneration.Roslyn.InputAssemblies.txt"); // For incremental build, we want to consider the input->output files as well as the assemblies involved in code generation. DateTime assembliesLastModified = GetLastModifiedAssemblyTime(generatorAssemblyInputsFile); var explicitIncludeList = new HashSet <string>( from item in this.Compile where string.Equals(item.GetMetadata("Generator"), $"MSBuild:{this.TargetName}", StringComparison.OrdinalIgnoreCase) select item.ItemSpec, StringComparer.OrdinalIgnoreCase); foreach (var inputDocument in project.Documents) { this.CancellationToken.ThrowIfCancellationRequested(); // Skip over documents that aren't on the prescribed list of files to scan. if (!explicitIncludeList.Contains(inputDocument.Name)) { continue; } string sourceHash = inputDocument.Name.GetHashCode().ToString("x", CultureInfo.InvariantCulture); string outputFilePath = Path.Combine(this.IntermediateOutputDirectory, Path.GetFileNameWithoutExtension(inputDocument.Name) + $".{sourceHash}.generated.cs"); // Code generation is relatively fast, but it's not free. // And when we run the Simplifier.ReduceAsync it's dog slow. // So skip files that haven't changed since we last generated them. bool generated = false; DateTime outputLastModified = File.GetLastWriteTime(outputFilePath); if (File.GetLastWriteTime(inputDocument.Name) > outputLastModified || assembliesLastModified > outputLastModified) { this.Log.LogMessage(MessageImportance.Normal, "{0} -> {1}", inputDocument.Name, outputFilePath); var outputDocument = await DocumentTransform.TransformAsync( inputDocument, new ProgressLogger(this.Log, inputDocument.Name)); // Only produce a new file if the generated document is not empty. var semanticModel = await outputDocument.GetSemanticModelAsync(this.CancellationToken); if (!CSharpDeclarationComputer.GetDeclarationsInSpan(semanticModel, TextSpan.FromBounds(0, semanticModel.SyntaxTree.Length), false, this.CancellationToken).IsEmpty) { var outputText = await outputDocument.GetTextAsync(this.CancellationToken); using (var outputFileStream = File.OpenWrite(outputFilePath)) using (var outputWriter = new StreamWriter(outputFileStream)) { outputText.Write(outputWriter); // Truncate any data that may be beyond this point if the file existed previously. outputWriter.Flush(); outputFileStream.SetLength(outputFileStream.Position); } generated = true; } } else { generated = true; } if (generated) { var outputItem = new TaskItem(outputFilePath); outputFiles.Add(outputItem); } } SaveGeneratorAssemblyList(generatorAssemblyInputsFile); writtenFiles.Add(new TaskItem(generatorAssemblyInputsFile)); this.GeneratedCompile = outputFiles.ToArray(); this.AdditionalWrittenFiles = writtenFiles.ToArray(); }).GetAwaiter().GetResult(); }