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..8c4ed4b 100644
--- a/src/Testura.Code/Saver/CodeSaver.cs
+++ b/src/Testura.Code/Saver/CodeSaver.cs
@@ -33,52 +33,100 @@ 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));
}
- var workspace = CreateWorkspace();
- var formattedCode = Formatter.Format(cu, workspace);
- using var sourceWriter = new StreamWriter(path);
+ EnsurePathExists(destinationFileAbsolutePath);
+ using var createdWorkspaceForCodeGen = CreateWorkspace();
+ var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen);
+ createdWorkspaceForCodeGen.Dispose();
+ using var sourceWriter = new StreamWriter(destinationFileAbsolutePath);
formattedCode.WriteTo(sourceWriter);
}
+ ///
+ /// Save generated code to a file asynchronously
+ ///
+ /// Generated code.
+ /// Full output destinationFileAbsolutePath.
+ /// The cancellation token.
+ /// Writes the generated code to the supplied by the user.
+ public async Task SaveCodeToFileAsync(CompilationUnitSyntax compiledSourceCode, string destinationFileAbsolutePath, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (compiledSourceCode == null)
+ {
+ throw new ArgumentNullException(nameof(compiledSourceCode));
+ }
+
+ if (string.IsNullOrEmpty(destinationFileAbsolutePath))
+ {
+ throw new ArgumentException("Value cannot be null or empty.", nameof(destinationFileAbsolutePath));
+ }
+
+ EnsurePathExists(destinationFileAbsolutePath);
+ await using var fileStream = File.Open(destinationFileAbsolutePath, FileMode.Create, FileAccess.Write);
+ await using var sourceWriter = new StreamWriter(fileStream);
+ using var createdWorkspaceForCodeGen = CreateWorkspace();
+ await sourceWriter.WriteAsync(Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen, cancellationToken: cancellationToken).ToFullString());
+ createdWorkspaceForCodeGen.Dispose();
+ }
+
///
/// 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);
+ using var createdWorkspaceForCodeGen = CreateWorkspace();
+ var formattedCode = Formatter.Format(compiledSourceCode, createdWorkspaceForCodeGen);
+ createdWorkspaceForCodeGen.Dispose();
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 createdWorkspaceForCodeGen;
+ }
+
+ 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}");
}
- return cw;
+ if (!Directory.Exists(fileInfo.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..500e117 100644
--- a/src/Testura.Code/Saver/ICodeSaver.cs
+++ b/src/Testura.Code/Saver/ICodeSaver.cs
@@ -8,14 +8,26 @@ 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);
+
+ ///
+ /// 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);
+}
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
-
-
-
-
-
-
+
+
+
+
+
+