Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
59 changes: 59 additions & 0 deletions Editor/DoCreateScriptFoldersWithTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2021-2025 Koji Hasegawa.
// This software is released under the MIT License.

using System;
using System.IO;
using System.Text;
using CreateScriptFoldersWithTests.Editor.Internals;
Expand Down Expand Up @@ -47,6 +48,7 @@ private static void CreateSecondLayer(string pathName, string firstLayerName, st
{
AssetDatabase.CreateFolder(PathCombineAllowNull(pathName, firstLayerName), secondLayerName);
CreateAssemblyDefinitionFile(pathName, firstLayerName, secondLayerName);
CreateAssemblyInfoFile(pathName, firstLayerName, secondLayerName);
CreateDotSettingsFile(pathName, firstLayerName, secondLayerName);
}

Expand Down Expand Up @@ -158,5 +160,62 @@ private static string PathCombineAllowNull(string pathName, string firstLayerNam
{
return firstLayerName != null ? Path.Combine(pathName, firstLayerName) : pathName;
}

private static void CreateAssemblyInfoFile(string pathName, string firstLayerName, string secondLayerName)
{
var moduleName = Path.GetFileName(pathName);
var internalsVisibleToAssemblies =
GetInternalsVisibleToAssemblies(moduleName, firstLayerName, secondLayerName);

var content = GenerateAssemblyInfoContent(internalsVisibleToAssemblies);
var assemblyInfoPath = Path.Combine(PathCombineAllowNull(pathName, firstLayerName), secondLayerName,
"AssemblyInfo.cs");
CreateScriptAssetWithContent(assemblyInfoPath, content);
}

private static string[] GetInternalsVisibleToAssemblies(string moduleName, string firstLayerName,
string secondLayerName)
{
if ((firstLayerName == Scripts || firstLayerName == null) && secondLayerName == Runtime)
{
return new[]
{
$"{moduleName}.Editor",
$"{moduleName}.Editor.Tests",
$"{moduleName}.Tests"
};
}

if ((firstLayerName == Scripts || firstLayerName == null) && secondLayerName == Editor)
{
return new[] { $"{moduleName}.Editor.Tests" };
}

if (firstLayerName == Tests && secondLayerName == Runtime)
{
return new[] { $"{moduleName}.Editor.Tests" };
}

return Array.Empty<string>();
}

private static string GenerateAssemblyInfoContent(string[] internalsVisibleToAssemblies)
{
if (internalsVisibleToAssemblies.Length == 0)
{
return string.Empty;
}

var content = new StringBuilder();
content.AppendLine("using System.Runtime.CompilerServices;");
content.AppendLine();

foreach (var assembly in internalsVisibleToAssemblies)
{
content.AppendLine($"[assembly: InternalsVisibleTo(\"{assembly}\")]");
}

return content.ToString();
}
}
}
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,16 @@ graph RL
```


### Creating DotSettings files
### Creating AssemblyInfo.cs files for friend assemblies

And creating .csproj.DotSettings file for each assembly.
This file is set up to make the [Namespace does not correspond to file location](https://www.jetbrains.com/help/rider/CheckNamespace.html) inspection work as expected in JetBrains Rider.
Do not forget to commit .DotSettings files for that project.
Creates an AssemblyInfo.cs file for each assembly to mark **Assembly Definition References** as [Friend assemblies](https://learn.microsoft.com/en-us/dotnet/standard/assembly/friend).
This allows access to `internal` types and members.

Specifically, disabled the [Namespace provider](https://www.jetbrains.com/help/rider/Refactorings__Adjust_Namespaces.html) for the following folders:

- Scripts
- Scripts/Runtime
- Tests
- Tests/Runtime
### Creating DotSettings files

Creates .csproj.DotSettings file for each assembly.
This file is set up to make the [Namespace does not correspond to file location](https://www.jetbrains.com/help/rider/CheckNamespace.html) inspection work as expected in JetBrains Rider.

This will result in the expected namespace per folder as follows:

Expand All @@ -112,6 +110,17 @@ This will result in the expected namespace per folder as follows:
- Tests/Editor: `YourFeature.Editor`
- Tests/Runtime: `YourFeature`

> [!TIP]
> `Tests` is not included in the namespace.
> When the same namespaces as the production code and test code are used, `using` directives are not required.

Specifically, disabled the [Namespace provider](https://www.jetbrains.com/help/rider/Refactorings__Adjust_Namespaces.html) for the following folders:

- Scripts
- Scripts/Runtime
- Tests
- Tests/Runtime

> [!WARNING]
> Under Packages namespace resolution works with Unity 2020.2 or later.
> Because to use the Root Namespace property of asmdef.
Expand Down
48 changes: 48 additions & 0 deletions Tests/Editor/DoCreateScriptFoldersWithTestsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,53 @@ public void Action_CreatedEditorTestsFolderContainingAsmdef()

File.Delete(DotSettingsPath);
}

[Test]
public void Action_CreatedRuntimeAssemblyInfoWithInternalsVisibleTo()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Scripts", "Runtime", "AssemblyInfo.cs");
Assume.That(File.Exists(assemblyInfoPath), Is.True);

var actual = File.ReadAllText(assemblyInfoPath).Replace("\r\n", "\n");
var expected = "using System.Runtime.CompilerServices;\n\n"
+ $"[assembly: InternalsVisibleTo(\"{ModuleName}.Editor\")]\n"
+ $"[assembly: InternalsVisibleTo(\"{ModuleName}.Editor.Tests\")]\n"
+ $"[assembly: InternalsVisibleTo(\"{ModuleName}.Tests\")]\n";
Assert.That(actual, Is.EqualTo(expected));
}

[Test]
public void Action_CreatedEditorAssemblyInfoWithInternalsVisibleTo()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Scripts", "Editor", "AssemblyInfo.cs");
Assume.That(File.Exists(assemblyInfoPath), Is.True);

var actual = File.ReadAllText(assemblyInfoPath).Replace("\r\n", "\n");
var expected = "using System.Runtime.CompilerServices;\n\n"
+ $"[assembly: InternalsVisibleTo(\"{ModuleName}.Editor.Tests\")]\n";
Assert.That(actual, Is.EqualTo(expected));
}

[Test]
public void Action_CreatedTestsRuntimeAssemblyInfoWithInternalsVisibleTo()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Tests", "Runtime", "AssemblyInfo.cs");
Assume.That(File.Exists(assemblyInfoPath), Is.True);

var actual = File.ReadAllText(assemblyInfoPath).Replace("\r\n", "\n");
var expected = "using System.Runtime.CompilerServices;\n\n"
+ $"[assembly: InternalsVisibleTo(\"{ModuleName}.Editor.Tests\")]\n";
Assert.That(actual, Is.EqualTo(expected));
}

[Test]
public void Action_CreatedEmptyTestsEditorAssemblyInfo()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Tests", "Editor", "AssemblyInfo.cs");
Assume.That(File.Exists(assemblyInfoPath), Is.True);

var actual = File.ReadAllText(assemblyInfoPath);
Assert.That(actual, Is.EqualTo(string.Empty));
}
}
}
28 changes: 28 additions & 0 deletions Tests/Editor/DoCreateScriptFoldersWithTestsTestUnderPackages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,33 @@ public void Action_CreatedEditorTestsFolderContainingAsmdef()

File.Delete(DotSettingsPath);
}

[Test]
public void Action_PackagesRuntimeAssemblyInfoPath()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Runtime", "AssemblyInfo.cs");
Assert.That(File.Exists(assemblyInfoPath), Is.True);
}

[Test]
public void Action_PackagesEditorAssemblyInfoPath()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Editor", "AssemblyInfo.cs");
Assert.That(File.Exists(assemblyInfoPath), Is.True);
}

[Test]
public void Action_PackagesTestsRuntimeAssemblyInfoPath()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Tests", "Runtime", "AssemblyInfo.cs");
Assert.That(File.Exists(assemblyInfoPath), Is.True);
}

[Test]
public void Action_PackagesTestsEditorAssemblyInfo()
{
var assemblyInfoPath = Path.Combine(_rootFolderPath, "Tests", "Editor", "AssemblyInfo.cs");
Assert.That(File.Exists(assemblyInfoPath), Is.True);
}
}
}