Skip to content

Commit

Permalink
Merge pull request #20 from nowsprinting/feature/namespace_provider
Browse files Browse the repository at this point in the history
Support namespace provider
  • Loading branch information
nowsprinting authored Feb 14, 2023
2 parents 43ff7d8 + b3f201d commit 7d68154
Show file tree
Hide file tree
Showing 16 changed files with 366 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ dotnet_naming_symbols.all_members.applicable_kinds = *

dotnet_naming_style.pascal_case_style.capitalization = pascal_case

file_header_template = Copyright (c) $CURRENT_YEAR$ Koji Hasegawa.\nThis software is released under the MIT License.
file_header_template = Copyright (c) 2021-$CURRENT_YEAR$ Koji Hasegawa.\nThis software is released under the MIT License.

# RS0016: Only enable if API files are present
dotnet_public_api_analyzer.require_api_files = true
Expand Down
6 changes: 6 additions & 0 deletions Editor/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2021-2023 Koji Hasegawa.
// This software is released under the MIT License.

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("CreateScriptFoldersWithTests.Editor.Tests")]
3 changes: 3 additions & 0 deletions Editor/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion Editor/DoCreateScriptFoldersWithTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Reflection;
using System.Text;
using CreateScriptFoldersWithTests.Editor.Internals;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
using UnityEngine;
Expand Down Expand Up @@ -44,6 +45,7 @@ private static void CreateSecondLayer(string pathName, string firstLayerName, st
{
AssetDatabase.CreateFolder(PathCombineAllowNull(pathName, firstLayerName), secondLayerName);
CreateAssemblyDefinitionFile(pathName, firstLayerName, secondLayerName);
CreateDotSettingsFile(pathName, firstLayerName, secondLayerName);
}

private static void CreateAssemblyDefinitionFile(string pathName, string firstLayerName, string secondLayerName)
Expand All @@ -65,13 +67,39 @@ private static void CreateAssemblyDefinitionFile(string pathName, string firstLa
asmdef.AddReferences(moduleName);
}

asmdef.rootNamespace = assemblyName.Replace($".{Tests}", "");
if (IsUnderPackages(pathName))
{
asmdef.rootNamespace = moduleName;
}

var path = Path.Combine(
PathCombineAllowNull(pathName, firstLayerName), secondLayerName, $"{assemblyName}.asmdef");
CreateScriptAssetWithContent(path, EditorJsonUtility.ToJson(asmdef));
}

private static void CreateDotSettingsFile(string pathName, string firstLayerName, string secondLayerName)
{
var moduleName = Path.GetFileName(pathName);
var assemblyName = AssemblyName(moduleName, firstLayerName, secondLayerName);
var dotSettingsCreator = new DotSettingsCreator(assemblyName);

if (firstLayerName is Scripts || firstLayerName is Tests)
{
dotSettingsCreator.AddNamespaceFoldersToSkip(Path.Combine(pathName, firstLayerName));
}

if (secondLayerName == Runtime)
{
dotSettingsCreator.AddNamespaceFoldersToSkip(
Path.Combine(PathCombineAllowNull(pathName, firstLayerName), secondLayerName));
}

if (dotSettingsCreator.WasAddNamespaceFoldersToSkip())
{
dotSettingsCreator.Flush();
}
}

private static string AssemblyName(string moduleName, string firstLayerName, string secondLayerName)
{
var assemblyName = new StringBuilder(moduleName);
Expand Down Expand Up @@ -107,6 +135,11 @@ private static bool IsUnderAssets(string pathName)
return pathName.StartsWith("Assets/");
}

private static bool IsUnderPackages(string pathName)
{
return !IsUnderAssets(pathName);
}

private static string PathCombineAllowNull(string pathName, string firstLayerName)
{
return firstLayerName != null ? Path.Combine(pathName, firstLayerName) : pathName;
Expand Down
3 changes: 3 additions & 0 deletions Editor/Internals.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions Editor/Internals/DotSettingsCreator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2021-2023 Koji Hasegawa.
// This software is released under the MIT License.

using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CreateScriptFoldersWithTests.Editor.Internals
{
internal class DotSettingsCreator
{
private readonly string _assemblyName;
private readonly List<string> _namespaceFoldersToSkip;

public DotSettingsCreator(string assemblyName)
{
_assemblyName = assemblyName;
_namespaceFoldersToSkip = new List<string>();
}

public void AddNamespaceFoldersToSkip(string path)
{
var key = path
.ToLower()
.Replace("/", "_005C")
.Replace(".", "_002E")
.Replace("-", "_002D")
.Replace(" ", "_0020")
.Replace("(", "_0028")
.Replace(")", "_0029");

_namespaceFoldersToSkip.Add(
$"<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/={key}/@EntryIndexedValue\">True</s:Boolean>");
}

public bool WasAddNamespaceFoldersToSkip()
{
return _namespaceFoldersToSkip.Any();
}

public void Flush()
{
using (var writer = new StreamWriter($"{_assemblyName}.csproj.DotSettings"))
{
writer.WriteLine(
"<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">");
foreach (var s in _namespaceFoldersToSkip)
{
writer.WriteLine(s);
}

writer.WriteLine("</wpf:ResourceDictionary>");
}
}
}
}
3 changes: 3 additions & 0 deletions Editor/Internals/DotSettingsCreator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 60 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
[![Test](https://github.com/nowsprinting/create-script-folders-with-tests/actions/workflows/test.yml/badge.svg)](https://github.com/nowsprinting/create-script-folders-with-tests/actions/workflows/test.yml)
[![openupm](https://img.shields.io/npm/v/com.nowsprinting.create-script-folders-with-tests?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.nowsprinting.create-script-folders-with-tests/)

This Unity Editor Extensions create script folders (Editor, Runtime, and each Tests) containing assembly definition file (.asmdef).
This Unity editor extensions create script folders (Editor, Runtime, and each Tests) containing assembly definition file (.asmdef).


### Using under Assets folder:
## Features

When opening the context menu and select **Create | C# Script Folders and Assemblies with Tests**,
The root folder (e.g., named "YourFeature") and below will be created as follows.

### Creating folders and asmdefs

#### Using under Assets folder

```
Assets
└── YourFeature
Expand All @@ -27,21 +31,7 @@ Assets
   └── YourFeature.Tests.asmdef
```

And the references of each asmdef are set as follows.

- `YourFeature` has not references
- `YourFeature.Editor` has references to `YourFeature`
- `YourFeature.Tests` has references to `YourFeature`
- `YourFeature.Editor.Tests` has references to `YourFeature` and `YourFeature.Editor`


### Using under Packages folder:

First, your package folder (e.g., named "your.package.name") must be created in advance.
Because can not open the context menu directly under the Packages folder.

Next, opening the context menu and select **Create | C# Script Folders and Assemblies with Tests**,
The root folder (e.g., named "YourFeature") and below will be created as follows.
#### Using under Packages folder

```
Packages
Expand All @@ -58,10 +48,62 @@ Packages
      └── YourFeature.Tests.asmdef
```

Package folder (e.g., named "your.package.name") must be created in before.
Because can not open the context menu directly under the Packages folder.

After creating folders, move the Editor, Runtime and Tests folders directly under the "your.package.name" folder.
And remove the "YourFeature" folder.
Then it will be the same as the official [package layout](https://docs.unity3d.com/Manual/cus-layout.html).

```
Packages
└── your.package.name
   ├── Editor
   │   └── YourFeature.Editor.asmdef
   ├── Runtime
   │   └── YourFeature.asmdef
   └── Tests
   ├── Editor
   │   └── YourFeature.Editor.Tests.asmdef
   └── Runtime
   └── YourFeature.Tests.asmdef
```


### Assembly Definition References in asmdefs

"Assembly Definition References" in each asmdef are set as follows.

- `YourFeature.Editor` has references to `YourFeature`
- `YourFeature` has not references
- `YourFeature.Tests` has references to `YourFeature`
- `YourFeature.Editor.Tests` has references to `YourFeature` and `YourFeature.Editor`


### Creating DotSettings files

And creating .csproj.DotSettings file for each assembly.
This file is set up to make the "Namespace does not correspond to file location" inspection work as expected in JetBrains Rider.
Do not forget to commit .DotSettings files for that project.

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

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

- Scripts/Editor: YourFeature.Editor
- Scripts/Runtime: YourFeature
- Tests/Editor: YourFeature.Editor
- Tests/Runtime: YourFeature

See more information:

Then it will be the same as the official [Package layout](https://docs.unity3d.com/Manual/cus-layout.html).
- [Code Inspections in C# | JetBrains Rider Documentation](https://www.jetbrains.com/help/rider/Reference__Code_Inspections_CSHARP.html)
- [Code Inspection: Namespace does not correspond to file location | JetBrains Rider Documentation](https://www.jetbrains.com/help/rider/CheckNamespace.html)


## Installation
Expand Down
48 changes: 42 additions & 6 deletions Tests/Editor/DoCreateScriptFoldersWithTestsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ namespace CreateScriptFoldersWithTests.Editor
public class DoCreateScriptFoldersWithTestsTest
{
private const string ModuleName = "CreateScriptFoldersWithTestsTarget";
private const string DotSettingsSuffix = ".csproj.DotSettings";
private readonly string _rootFolderPath = Path.Combine("Assets", ModuleName);
private static readonly string s_rootEncoded = "assets_005C" + ModuleName.ToLower();
private static readonly string s_foldersToSkipScripts = $"{s_rootEncoded}_005Cscripts/";
private static readonly string s_foldersToSkipScriptsRuntime = $"{s_rootEncoded}_005Cscripts_005Cruntime/";
private static readonly string s_foldersToSkipTests = $"{s_rootEncoded}_005Ctests/";
private static readonly string s_foldersToSkipTestsRuntime = $"{s_rootEncoded}_005Ctests_005Cruntime/";

[OneTimeSetUp]
public void OneTimeSetUp()
{
AssetDatabase.DisallowAutoRefresh();

var exist = AssetDatabase.IsValidFolder(_rootFolderPath);
Assume.That(exist, Is.False, "Generated folder does not exist before test");
// Generated folder and files does not exist before test
Assume.That(AssetDatabase.IsValidFolder(_rootFolderPath), Is.False);
Assume.That(new FileInfo(ModuleName + DotSettingsSuffix), Does.Not.Exist);
Assume.That(new FileInfo(ModuleName + ".Editor" + DotSettingsSuffix), Does.Not.Exist);
Assume.That(new FileInfo(ModuleName + ".Tests" + DotSettingsSuffix), Does.Not.Exist);
Assume.That(new FileInfo(ModuleName + ".Editor.Tests" + DotSettingsSuffix), Does.Not.Exist);

var sut = ScriptableObject.CreateInstance<DoCreateScriptFoldersWithTests>();
sut.Action(0, _rootFolderPath, null);
Expand Down Expand Up @@ -49,7 +59,14 @@ public void Action_CreatedRuntimeFolderContainingAsmdef()
Assert.That(asmdef.defineConstraints, Is.Empty);
Assert.That(asmdef.includePlatforms, Is.Empty);
Assert.That(asmdef.references, Is.Empty);
Assert.That(asmdef.rootNamespace, Is.EqualTo(AssemblyName));
Assert.That(asmdef.rootNamespace, Is.Empty);

const string DotSettingsPath = AssemblyName + DotSettingsSuffix;
Assume.That(new FileInfo(DotSettingsPath), Does.Exist);
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipScripts));
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipScriptsRuntime));

File.Delete(DotSettingsPath);
}

[Test]
Expand All @@ -67,7 +84,13 @@ public void Action_CreatedEditorFolderContainingAsmdef()
Assert.That(asmdef.defineConstraints, Is.Empty);
Assert.That(asmdef.includePlatforms, Does.Contain("Editor"));
Assert.That(asmdef.references, Does.Contain(ModuleName));
Assert.That(asmdef.rootNamespace, Is.EqualTo(AssemblyName));
Assert.That(asmdef.rootNamespace, Is.Empty);

const string DotSettingsPath = AssemblyName + DotSettingsSuffix;
Assume.That(new FileInfo(DotSettingsPath), Does.Exist);
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipScripts));

File.Delete(DotSettingsPath);
}

[Test]
Expand All @@ -94,7 +117,14 @@ public void Action_CreatedRuntimeTestsFolderContainingAsmdef()
#else
Assert.That(asmdef.optionalUnityReferences, Does.Contain("TestAssemblies"));
#endif
Assert.That(asmdef.rootNamespace, Is.EqualTo(ModuleName));
Assert.That(asmdef.rootNamespace, Is.Empty);

const string DotSettingsPath = AssemblyName + DotSettingsSuffix;
Assume.That(new FileInfo(DotSettingsPath), Does.Exist);
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipTests));
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipTestsRuntime));

File.Delete(DotSettingsPath);
}

[Test]
Expand Down Expand Up @@ -123,7 +153,13 @@ public void Action_CreatedEditorTestsFolderContainingAsmdef()
#else
Assert.That(asmdef.optionalUnityReferences, Does.Contain("TestAssemblies"));
#endif
Assert.That(asmdef.rootNamespace, Is.EqualTo(EditorAssemblyName));
Assert.That(asmdef.rootNamespace, Is.Empty);

const string DotSettingsPath = AssemblyName + DotSettingsSuffix;
Assume.That(new FileInfo(DotSettingsPath), Does.Exist);
Assert.That(File.ReadAllText(DotSettingsPath), Does.Contain(s_foldersToSkipTests));

File.Delete(DotSettingsPath);
}
}
}
Loading

0 comments on commit 7d68154

Please sign in to comment.