Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
50 changes: 25 additions & 25 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
<Project>

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>

<PropertyGroup>
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
<NeutralLanguage>en-US</NeutralLanguage>
<IsPackable>false</IsPackable>
<Authors></Authors>
<PackageLicenseExpression></PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PublishRepositoryUrl>false</PublishRepositoryUrl>
</PropertyGroup>
<PropertyGroup>
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
<NeutralLanguage>en-US</NeutralLanguage>
<IsPackable>false</IsPackable>
<Authors></Authors>
<PackageLicenseExpression></PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<PublishRepositoryUrl>false</PublishRepositoryUrl>
</PropertyGroup>

<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<DefineConstants>CODE_ANALYSIS</DefineConstants>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>CODE_ANALYSIS</DefineConstants>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.354" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.354" PrivateAssets="all"/>
</ItemGroup>
</Project>
87 changes: 73 additions & 14 deletions src/Testura.Code.Tests/Saver/CodeSaverTests.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<OptionKeyValue> { new OptionKeyValue(CSharpFormattingOptions.NewLinesForBracesInMethods, false) });
var codeSaver = new CodeSaver(
new List<OptionKeyValue>
{
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);
}
}
}


14 changes: 7 additions & 7 deletions src/Testura.Code.Tests/Testura.Code.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0"/>
<PackageReference Include="NUnit" Version="3.13.2"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0"/>
<PackageReference Include="coverlet.collector" Version="3.1.0"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Testura.Code\Testura.Code.csproj" />
<ProjectReference Include="..\Testura.Code\Testura.Code.csproj"/>
</ItemGroup>

</Project>
31 changes: 20 additions & 11 deletions src/Testura.Code/Builders/Base/BuilderBase.cs
Original file line number Diff line number Diff line change
@@ -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<TBuilder>
where TBuilder : BuilderBase<TBuilder>
{
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();
Expand All @@ -22,24 +24,26 @@ protected BuilderBase(string @namespace, NamespaceType namespaceType)
protected bool HaveMembers => _memberHelper.Members.Any();

/// <summary>
/// Set the using directives.
/// Set the using directives.
/// </summary>
/// <param name="usings">A set of wanted using directive names.</param>
/// <returns>The current builder</returns>
public TBuilder WithUsings(params string[] usings)
{
_usingHelper.AddUsings(usings);

return (TBuilder)this;
}

/// <summary>
/// Add build members that will be generated.
/// Add build members that will be generated.
/// </summary>
/// <param name="buildMembers">Build members to add</param>
/// <returns>The current builder</returns>
public TBuilder With(params IBuildMember[] buildMembers)
{
_memberHelper.AddMembers(buildMembers);

return (TBuilder)this;
}

Expand All @@ -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<MemberDeclarationSyntax> members)
protected CompilationUnitSyntax BuildNamespace(
CompilationUnitSyntax @base,
SyntaxList<MemberDeclarationSyntax> members)
{
return _namespaceHelper.BuildNamespace(@base, members);
}
Expand All @@ -73,3 +81,4 @@ protected CompilationUnitSyntax BuildMembers(CompilationUnitSyntax compilationUn
return _memberHelper.BuildMembers(compilationUnitSyntax);
}
}

37 changes: 37 additions & 0 deletions src/Testura.Code/Saver/CodeSaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,40 @@ 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);
formattedCode.WriteTo(sourceWriter);
}

/// <summary>
/// Save generated code to a file asynchronously
/// </summary>
/// <param name="cu">Generated code.</param>
/// <param name="path">Full output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Writes the generated code to the <paramref name="path"/> supplied by the user.</returns>
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));
}

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());
}

/// <summary>
/// Save generated code as a string.
/// </summary>
Expand Down Expand Up @@ -81,4 +109,13 @@ private AdhocWorkspace CreateWorkspace()

return cw;
}

private void EnsurePathExists(string filePath)
{
var fi = new FileInfo(filePath);
Copy link
Contributor

@MilleBo MilleBo Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing but should maybe use the full name for FileInfo variables? So var fileInfo = new FileInfo(path).

I just think it's easier to read and understand when we use clear names for a variable (which may sound a bit like hypocrisy as I named CompilationUnitSyntax cu...). But it's more about taste and if you prefer fi we can stick with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I am always for more 'explicit' style of programming, even if its more verbose - its easier to understand.

Haha, yea we all get lazy at times, thats why I use fi, its just laziness haha. I'll make the change now

Copy link
Collaborator Author

@jeffward01 jeffward01 Dec 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I changed a few just to make them more explicit. I'd love to do this everywhere in the code eventually.

I'd also love to add more constructors and such so we can avoid using null as a parameter Such as this example:

// I'd love to remove the null, so the developer can just skip the 'WithBody' part and get the same result
var builder = new MethodBuilder("MyMethod").WithBody(null).Build()

It will create this:

void MyMethod();

I won't do that in this PR - but I just wanted to mention it as its related to the topic of being more explicit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good idea to remove withbody requirment. Approved the PR as well.

if (!Directory.Exists(fi.Directory.FullName))
{
Directory.CreateDirectory(fi.Directory.FullName);
}
}
}
Loading