From 1be04f5ca518f56f882b049200436ce6b93504b1 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Mon, 28 Nov 2022 02:46:40 -0500 Subject: [PATCH 1/5] added SaveCodeToFileAsync method --- .editorconfig | 12 +- Directory.Build.props | 50 ++--- .../Saver/CodeSaverTests.cs | 87 +++++++-- .../Testura.Code.Tests.csproj | 14 +- src/Testura.Code/Builders/Base/BuilderBase.cs | 31 +-- src/Testura.Code/Saver/CodeSaver.cs | 26 +++ src/Testura.Code/Settings/Testura.ruleset | 176 +++++++++--------- src/Testura.Code/Testura.Code.csproj | 17 +- 8 files changed, 254 insertions(+), 159 deletions(-) diff --git a/.editorconfig b/.editorconfig index 02c38f2..1e10b78 100644 --- a/.editorconfig +++ b/.editorconfig @@ -120,15 +120,15 @@ dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.static_readonly_fields.required_modifiers = static,readonly +dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = * # internal and private fields should be camelCase -dotnet_naming_rule.private_members_with_underscore.symbols = private_fields -dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore dotnet_naming_rule.private_members_with_underscore.severity = suggestion -dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private dotnet_naming_style.prefix_underscore.capitalization = camel_case @@ -159,7 +159,7 @@ dotnet_naming_rule.interfaces_must_start_with_i.style = begin_with_i_style dotnet_naming_symbols.interface_symbols.applicable_kinds = interface dotnet_naming_symbols.interface_symbols.applicable_accessibilities = * -dotnet_naming_style.begin_with_i_style.capitalization = pascal_case +dotnet_naming_style.begin_with_i_style.capitalization = pascal_case dotnet_naming_style.begin_with_i_style.required_prefix = I # name everything else using PascalCase @@ -168,7 +168,7 @@ dotnet_naming_rule.methods_and_properties_must_be_pascal_case.symbols = everythi dotnet_naming_rule.methods_and_properties_must_be_pascal_case.style = pascal_case_style #dotnet_naming_symbols.everything_else_symbols.applicable_kinds = class,struct,enum,delegate,event,method,property,namespace,type_parameter -dotnet_naming_symbols.everything_else_symbols.applicable_kinds = class,struct,enum,delegate,event,method,property +dotnet_naming_symbols.everything_else_symbols.applicable_kinds = class, struct, enum, delegate, event, method, property dotnet_naming_symbols.everything_else_symbols.applicable_accessibilities = * # StyleCop diagnostics severity diff --git a/Directory.Build.props b/Directory.Build.props index 0ed8c53..1fafc8d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,32 +1,32 @@ - - true - true - 1591 - + + true + true + 1591 + - - $(MSBuildThisFileDirectory) - en-US - false - - - git - false - + + $(MSBuildThisFileDirectory) + en-US + false + + + git + false + - - latest - enable - + + latest + enable + - - CODE_ANALYSIS - true - + + CODE_ANALYSIS + true + - - - + + + \ No newline at end of file diff --git a/src/Testura.Code.Tests/Saver/CodeSaverTests.cs b/src/Testura.Code.Tests/Saver/CodeSaverTests.cs index 8707a61..999f731 100644 --- a/src/Testura.Code.Tests/Saver/CodeSaverTests.cs +++ b/src/Testura.Code.Tests/Saver/CodeSaverTests.cs @@ -1,11 +1,15 @@ -using System.Collections.Generic; +namespace Testura.Code.Tests.Saver; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Code.Builders; +using Code.Models.Options; +using Code.Saver; using Microsoft.CodeAnalysis.CSharp.Formatting; using NUnit.Framework; -using Testura.Code.Builders; -using Testura.Code.Models.Options; -using Testura.Code.Saver; - -namespace Testura.Code.Tests.Saver; [TestFixture] public class CodeSaverTests @@ -18,25 +22,80 @@ public void SetUp() _coderSaver = new CodeSaver(); } + [Test] + public async Task SaveCodeToFileAsync_WhenSavingCodeAsFile_ShouldSaveCorrectly() + { + var cts = new CancellationTokenSource(); + var destFile = PrepareDestinationFile(); + var compiledCode = new ClassBuilder("TestClass", "test").Build(); + await _coderSaver.SaveCodeToFileAsync(compiledCode, destFile.FullName, cts.Token); + Assert.IsTrue(File.Exists(destFile.FullName)); + var code = await File.ReadAllTextAsync(destFile.FullName, cts.Token); + Assert.IsNotNull(code); + Assert.AreEqual( + "namespace test\r\n{\r\n public class TestClass\r\n {\r\n }\r\n}", + code); + + FileInfo PrepareDestinationFile() + { + var fi = GetDestinationFile(); + if (fi.Exists) + { + fi.Delete(); + } + + return fi; + } + + // Returns a temporary predictable file name which will be saved to the filesystem for testing. + // TODO: Use a filesystem abstraction library to avoid saving to file system. + FileInfo GetDestinationFile() + { + var exampleFileName = + nameof(SaveCodeToFileAsync_WhenSavingCodeAsFile_ShouldSaveCorrectly); + var destinationFile = Path.Combine( + Environment.CurrentDirectory, + "UnitTests", + "Saver", + $"{exampleFileName}.cs"); + + var fi = new FileInfo(destinationFile); + Directory.CreateDirectory(fi.Directory.FullName); + if (fi.Exists) + { + fi.Delete(); + } + + return fi; + } + } + [Test] public void SaveCodeAsString_WhenSavingCodeAsString_ShouldGetString() { var code = _coderSaver.SaveCodeAsString(new ClassBuilder("TestClass", "test").Build()); Assert.IsNotNull(code); - Assert.AreEqual("namespace test\r\n{\r\n public class TestClass\r\n {\r\n }\r\n}", code); + Assert.AreEqual( + "namespace test\r\n{\r\n public class TestClass\r\n {\r\n }\r\n}", + code); } [Test] public void SaveCodeAsString_WhenSavingCodeAsStringAndOptions_ShouldGetString() { - var codeSaver = new CodeSaver(new List { new OptionKeyValue(CSharpFormattingOptions.NewLinesForBracesInMethods, false) }); + var codeSaver = new CodeSaver( + new List + { + new(CSharpFormattingOptions.NewLinesForBracesInMethods, false) + }); var code = codeSaver.SaveCodeAsString( - new ClassBuilder("TestClass", "test") - .WithMethods( - new MethodBuilder("MyMethod") - .Build()) + new ClassBuilder("TestClass", "test").WithMethods(new MethodBuilder("MyMethod").Build()) .Build()); Assert.IsNotNull(code); - Assert.AreEqual("namespace test\r\n{\r\n public class TestClass\r\n {\r\n void MyMethod() {\r\n }\r\n }\r\n}", code); + Assert.AreEqual( + "namespace test\r\n{\r\n public class TestClass\r\n {\r\n void MyMethod() {\r\n }\r\n }\r\n}", + code); } -} \ No newline at end of file +} + + diff --git a/src/Testura.Code.Tests/Testura.Code.Tests.csproj b/src/Testura.Code.Tests/Testura.Code.Tests.csproj index 033a5de..02f68d8 100644 --- a/src/Testura.Code.Tests/Testura.Code.Tests.csproj +++ b/src/Testura.Code.Tests/Testura.Code.Tests.csproj @@ -8,16 +8,16 @@ - - - - - - + + + + + + - + diff --git a/src/Testura.Code/Builders/Base/BuilderBase.cs b/src/Testura.Code/Builders/Base/BuilderBase.cs index 2c48d26..e9fe04b 100644 --- a/src/Testura.Code/Builders/Base/BuilderBase.cs +++ b/src/Testura.Code/Builders/Base/BuilderBase.cs @@ -1,18 +1,20 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Testura.Code.Builders.BuilderHelpers; -using Testura.Code.Builders.BuildMembers; +namespace Testura.Code.Builders.Base; -namespace Testura.Code.Builders.Base; +using BuilderHelpers; +using BuildMembers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; public abstract class BuilderBase where TBuilder : BuilderBase { + private readonly MemberHelper _memberHelper; private readonly NamespaceHelper _namespaceHelper; private readonly UsingHelper _usingHelper; - private readonly MemberHelper _memberHelper; - protected BuilderBase(string @namespace, NamespaceType namespaceType) + protected BuilderBase( + string @namespace, + NamespaceType namespaceType) { _memberHelper = new MemberHelper(); _usingHelper = new UsingHelper(); @@ -22,24 +24,26 @@ protected BuilderBase(string @namespace, NamespaceType namespaceType) protected bool HaveMembers => _memberHelper.Members.Any(); /// - /// Set the using directives. + /// Set the using directives. /// /// A set of wanted using directive names. /// The current builder public TBuilder WithUsings(params string[] usings) { _usingHelper.AddUsings(usings); + return (TBuilder)this; } /// - /// Add build members that will be generated. + /// Add build members that will be generated. /// /// Build members to add /// The current builder public TBuilder With(params IBuildMember[] buildMembers) { _memberHelper.AddMembers(buildMembers); + return (TBuilder)this; } @@ -48,12 +52,16 @@ protected CompilationUnitSyntax BuildUsings(CompilationUnitSyntax @base) return _usingHelper.BuildUsings(@base); } - protected CompilationUnitSyntax BuildNamespace(CompilationUnitSyntax @base, params MemberDeclarationSyntax[] members) + protected CompilationUnitSyntax BuildNamespace( + CompilationUnitSyntax @base, + params MemberDeclarationSyntax[] members) { return _namespaceHelper.BuildNamespace(@base, members); } - protected CompilationUnitSyntax BuildNamespace(CompilationUnitSyntax @base, SyntaxList members) + protected CompilationUnitSyntax BuildNamespace( + CompilationUnitSyntax @base, + SyntaxList members) { return _namespaceHelper.BuildNamespace(@base, members); } @@ -73,3 +81,4 @@ protected CompilationUnitSyntax BuildMembers(CompilationUnitSyntax compilationUn return _memberHelper.BuildMembers(compilationUnitSyntax); } } + diff --git a/src/Testura.Code/Saver/CodeSaver.cs b/src/Testura.Code/Saver/CodeSaver.cs index 773cb73..91b9e68 100644 --- a/src/Testura.Code/Saver/CodeSaver.cs +++ b/src/Testura.Code/Saver/CodeSaver.cs @@ -53,6 +53,32 @@ public void SaveCodeToFile(CompilationUnitSyntax cu, string path) formattedCode.WriteTo(sourceWriter); } + /// + /// Save generated code to a file asynchronously + /// + /// Generated code. + /// Full output path. + /// The cancellation token. + /// Writes the generated code to the supplied by the user. + public async Task SaveCodeToFileAsync(CompilationUnitSyntax cu, string path, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (cu == null) + { + throw new ArgumentNullException(nameof(cu)); + } + + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + } + + await using var fileStream = File.Open(path, FileMode.Create, FileAccess.Write); + await using var sourceWriter = new StreamWriter(fileStream); + await sourceWriter.WriteAsync(Formatter.Format(cu, CreateWorkspace()).ToFullString()); + } + /// /// Save generated code as a string. /// diff --git a/src/Testura.Code/Settings/Testura.ruleset b/src/Testura.Code/Settings/Testura.ruleset index 26e8d4b..dc1d6a1 100644 --- a/src/Testura.Code/Settings/Testura.ruleset +++ b/src/Testura.Code/Settings/Testura.ruleset @@ -1,91 +1,91 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Testura.Code/Testura.Code.csproj b/src/Testura.Code/Testura.Code.csproj index 79515ac..4b930a6 100644 --- a/src/Testura.Code/Testura.Code.csproj +++ b/src/Testura.Code/Testura.Code.csproj @@ -16,13 +16,14 @@ Testura.Code is a wrapper around the Roslyn API and used for generation, saving and and compiling C# code. It provides methods and helpers to generate classes, methods, statements and expressions. 1.3.0 -- Added support for object initialization (with ObjectCreationGenerator) + - Added support for object initialization (with ObjectCreationGenerator) + Code generation roslyn https://i.ibb.co/nnSPd11/logo128-new.png https://github.com/Testura/Testura.Code - logo.png + logo.png $(RepositoryUrl) true MIT @@ -34,14 +35,14 @@ 3 - - - - - - + + + + + + From 2472d18c054014ef66ce982b786f9f09ba90036d Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Mon, 28 Nov 2022 04:03:18 -0500 Subject: [PATCH 2/5] added EnsurePathExists to check for directory existence to SaveCodeToFilesystem methods --- src/Testura.Code/Saver/CodeSaver.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Testura.Code/Saver/CodeSaver.cs b/src/Testura.Code/Saver/CodeSaver.cs index 91b9e68..143be3d 100644 --- a/src/Testura.Code/Saver/CodeSaver.cs +++ b/src/Testura.Code/Saver/CodeSaver.cs @@ -47,6 +47,7 @@ public void SaveCodeToFile(CompilationUnitSyntax cu, string path) throw new ArgumentException("Value cannot be null or empty.", nameof(path)); } + EnsurePathExists(path); var workspace = CreateWorkspace(); var formattedCode = Formatter.Format(cu, workspace); using var sourceWriter = new StreamWriter(path); @@ -74,6 +75,7 @@ public async Task SaveCodeToFileAsync(CompilationUnitSyntax cu, string path, Can throw new ArgumentException("Value cannot be null or empty.", nameof(path)); } + EnsurePathExists(path); await using var fileStream = File.Open(path, FileMode.Create, FileAccess.Write); await using var sourceWriter = new StreamWriter(fileStream); await sourceWriter.WriteAsync(Formatter.Format(cu, CreateWorkspace()).ToFullString()); @@ -107,4 +109,13 @@ private AdhocWorkspace CreateWorkspace() return cw; } + + private void EnsurePathExists(string filePath) + { + var fi = new FileInfo(filePath); + if (!Directory.Exists(fi.Directory.FullName)) + { + Directory.CreateDirectory(fi.Directory.FullName); + } + } } From a740755070025f093620b8d1a0196729b613f469 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Sun, 4 Dec 2022 17:53:32 -0500 Subject: [PATCH 3/5] added more explicit naming to vars --- src/Testura.Code/Saver/CodeSaver.cs | 72 ++++++++++++++-------------- src/Testura.Code/Saver/ICodeSaver.cs | 12 ++--- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Testura.Code/Saver/CodeSaver.cs b/src/Testura.Code/Saver/CodeSaver.cs index 143be3d..52a364a 100644 --- a/src/Testura.Code/Saver/CodeSaver.cs +++ b/src/Testura.Code/Saver/CodeSaver.cs @@ -33,89 +33,89 @@ public CodeSaver(IEnumerable options) /// /// Save generated code to a file. /// - /// Generated code. - /// Full output path. - public void SaveCodeToFile(CompilationUnitSyntax cu, string path) + /// Generated code. + /// Full output destinationFileAbsolutePath. + public void SaveCodeToFile(CompilationUnitSyntax compiledSourceCode, string destinationFileAbsolutePath) { - if (cu == null) + if (compiledSourceCode == null) { - throw new ArgumentNullException(nameof(cu)); + throw new ArgumentNullException(nameof(compiledSourceCode)); } - if (string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(destinationFileAbsolutePath)) { - throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + throw new ArgumentException("Value cannot be null or empty.", nameof(destinationFileAbsolutePath)); } - EnsurePathExists(path); - var workspace = CreateWorkspace(); - var formattedCode = Formatter.Format(cu, workspace); - using var sourceWriter = new StreamWriter(path); + EnsurePathExists(destinationFileAbsolutePath); + var createdWorkspaceForCodeGen = CreateWorkspace(); + var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen); + using var sourceWriter = new StreamWriter(destinationFileAbsolutePath); formattedCode.WriteTo(sourceWriter); } /// /// Save generated code to a file asynchronously /// - /// Generated code. - /// Full output path. + /// Generated code. + /// Full output destinationFileAbsolutePath. /// The cancellation token. - /// Writes the generated code to the supplied by the user. - public async Task SaveCodeToFileAsync(CompilationUnitSyntax cu, string path, CancellationToken cancellationToken = default) + /// Writes the generated code to the supplied by the user. + public async Task SaveCodeToFileAsync(CompilationUnitSyntax compiledSourceCode, string destinationFileAbsolutePath, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - if (cu == null) + if (compiledSourceCode == null) { - throw new ArgumentNullException(nameof(cu)); + throw new ArgumentNullException(nameof(compiledSourceCode)); } - if (string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(destinationFileAbsolutePath)) { - throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + throw new ArgumentException("Value cannot be null or empty.", nameof(destinationFileAbsolutePath)); } - EnsurePathExists(path); - await using var fileStream = File.Open(path, FileMode.Create, FileAccess.Write); + EnsurePathExists(destinationFileAbsolutePath); + await using var fileStream = File.Open(destinationFileAbsolutePath, FileMode.Create, FileAccess.Write); await using var sourceWriter = new StreamWriter(fileStream); - await sourceWriter.WriteAsync(Formatter.Format(cu, CreateWorkspace()).ToFullString()); + await sourceWriter.WriteAsync(Formatter.Format(compiledSourceCode, CreateWorkspace()).ToFullString()); } /// /// Save generated code as a string. /// - /// Generated code. + /// Generated code. /// Generated code as a string. - public string SaveCodeAsString(CompilationUnitSyntax cu) + public string SaveCodeAsString(CompilationUnitSyntax compiledSourceCode) { - if (cu == null) + if (compiledSourceCode == null) { - throw new ArgumentNullException(nameof(cu)); + throw new ArgumentNullException(nameof(compiledSourceCode)); } - var workspace = CreateWorkspace(); - var formattedCode = Formatter.Format(cu, workspace); + var createdWorkspaceForCodeGen = CreateWorkspace(); + var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen); return formattedCode.ToFullString(); } private AdhocWorkspace CreateWorkspace() { - var cw = new AdhocWorkspace(); - cw.Options.WithChangedOption(CSharpFormattingOptions.IndentBraces, true); + var createdWorkspaceForCodeGen = new AdhocWorkspace(); + createdWorkspaceForCodeGen.Options.WithChangedOption(CSharpFormattingOptions.IndentBraces, true); foreach (var optionKeyValue in _options) { - cw.TryApplyChanges(cw.CurrentSolution.WithOptions(cw.Options.WithChangedOption(optionKeyValue.FormattingOption, optionKeyValue.Value))); + createdWorkspaceForCodeGen.TryApplyChanges(createdWorkspaceForCodeGen.CurrentSolution.WithOptions(createdWorkspaceForCodeGen.Options.WithChangedOption(optionKeyValue.FormattingOption, optionKeyValue.Value))); } - return cw; + return createdWorkspaceForCodeGen; } - private void EnsurePathExists(string filePath) + private void EnsurePathExists(string destinationFileAbsolutePath) { - var fi = new FileInfo(filePath); - if (!Directory.Exists(fi.Directory.FullName)) + var fileInfo = new FileInfo(destinationFileAbsolutePath); + if (!Directory.Exists(fileInfo.Directory.FullName)) { - Directory.CreateDirectory(fi.Directory.FullName); + Directory.CreateDirectory(fileInfo.Directory.FullName); } } } diff --git a/src/Testura.Code/Saver/ICodeSaver.cs b/src/Testura.Code/Saver/ICodeSaver.cs index a12a7e3..155c708 100644 --- a/src/Testura.Code/Saver/ICodeSaver.cs +++ b/src/Testura.Code/Saver/ICodeSaver.cs @@ -8,14 +8,14 @@ public interface ICodeSaver /// /// Save generated code to a file. /// - /// Generated code. - /// Full output path. - void SaveCodeToFile(CompilationUnitSyntax cu, string path); + /// Generated code. + /// Full output path. + void SaveCodeToFile(CompilationUnitSyntax compiledSourceCode, string destinationFileAbsolutePath); /// /// Save generated code as a string. /// - /// Generated code. + /// Generated code. /// Generated code as a string. - string SaveCodeAsString(CompilationUnitSyntax cu); -} \ No newline at end of file + string SaveCodeAsString(CompilationUnitSyntax compiledSourceCode); +} From 8ff57855ed935e2194126e96cfeae0042201d869 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Sun, 4 Dec 2022 18:02:04 -0500 Subject: [PATCH 4/5] added interface method --- src/Testura.Code/Saver/ICodeSaver.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Testura.Code/Saver/ICodeSaver.cs b/src/Testura.Code/Saver/ICodeSaver.cs index 155c708..500e117 100644 --- a/src/Testura.Code/Saver/ICodeSaver.cs +++ b/src/Testura.Code/Saver/ICodeSaver.cs @@ -18,4 +18,16 @@ public interface ICodeSaver /// Generated code. /// Generated code as a string. string SaveCodeAsString(CompilationUnitSyntax compiledSourceCode); + + /// + /// Save generated code to a file. + /// + /// Generated code. + /// Full output path. + /// The cancellation token + /// Saves the generated code to the filesystem. + Task SaveCodeToFileAsync( + CompilationUnitSyntax compiledSourceCode, + string destinationFileAbsolutePath, + CancellationToken cancellationToken = default); } From 593376f99cc6be8840d33b227d60e343831dc129 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Sun, 4 Dec 2022 18:06:09 -0500 Subject: [PATCH 5/5] added using statements to satisfy warnings and manually disposed of singletons --- src/Testura.Code/Saver/CodeSaver.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Testura.Code/Saver/CodeSaver.cs b/src/Testura.Code/Saver/CodeSaver.cs index 52a364a..8c4ed4b 100644 --- a/src/Testura.Code/Saver/CodeSaver.cs +++ b/src/Testura.Code/Saver/CodeSaver.cs @@ -48,8 +48,9 @@ public void SaveCodeToFile(CompilationUnitSyntax compiledSourceCode, string dest } EnsurePathExists(destinationFileAbsolutePath); - var createdWorkspaceForCodeGen = CreateWorkspace(); + using var createdWorkspaceForCodeGen = CreateWorkspace(); var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen); + createdWorkspaceForCodeGen.Dispose(); using var sourceWriter = new StreamWriter(destinationFileAbsolutePath); formattedCode.WriteTo(sourceWriter); } @@ -78,7 +79,9 @@ public async Task SaveCodeToFileAsync(CompilationUnitSyntax compiledSourceCode, EnsurePathExists(destinationFileAbsolutePath); await using var fileStream = File.Open(destinationFileAbsolutePath, FileMode.Create, FileAccess.Write); await using var sourceWriter = new StreamWriter(fileStream); - await sourceWriter.WriteAsync(Formatter.Format(compiledSourceCode, CreateWorkspace()).ToFullString()); + using var createdWorkspaceForCodeGen = CreateWorkspace(); + await sourceWriter.WriteAsync(Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen, cancellationToken: cancellationToken).ToFullString()); + createdWorkspaceForCodeGen.Dispose(); } /// @@ -93,8 +96,9 @@ public string SaveCodeAsString(CompilationUnitSyntax compiledSourceCode) throw new ArgumentNullException(nameof(compiledSourceCode)); } - var createdWorkspaceForCodeGen = CreateWorkspace(); + using var createdWorkspaceForCodeGen = CreateWorkspace(); var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen); + createdWorkspaceForCodeGen.Dispose(); return formattedCode.ToFullString(); } @@ -113,6 +117,13 @@ private AdhocWorkspace CreateWorkspace() private void EnsurePathExists(string destinationFileAbsolutePath) { var fileInfo = new FileInfo(destinationFileAbsolutePath); + if (fileInfo.Directory == null) + { + throw new DirectoryNotFoundException( + $"The parent directory of the target destination file cannot be null. Target destination file full path: { + fileInfo.FullName}"); + } + if (!Directory.Exists(fileInfo.Directory.FullName)) { Directory.CreateDirectory(fileInfo.Directory.FullName);