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
38 changes: 30 additions & 8 deletions TUnit.Analyzers.CodeFixers/TwoPhase/NUnitTwoPhaseAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,8 @@ private AttributeConversion ConvertTestAttributeFull(AttributeSyntax node)
{
// [Platform(Include = "Win")] -> [RunOn(OS.Windows)]
// [Platform(Exclude = "Linux")] -> [ExcludeOn(OS.Linux)]
// Note: Unsupported platforms like "Mono", "Net", "NetCore" have no direct TUnit equivalent
// and will be left unconverted (returning null)
if (node.ArgumentList?.Arguments.Count > 0)
{
foreach (var arg in node.ArgumentList.Arguments)
Expand All @@ -1662,40 +1664,60 @@ private AttributeConversion ConvertTestAttributeFull(AttributeSyntax node)
if (nameText == "Include")
{
var os = MapPlatformToOS(valueText);
if (os == null)
{
// Unsupported platform - don't convert
return (null, null);
}
return ("RunOn", $"({os})");
}
if (nameText == "Exclude")
{
var os = MapPlatformToOS(valueText);
if (os == null)
{
// Unsupported platform - don't convert
return (null, null);
}
return ("ExcludeOn", $"({os})");
}
}
}
return (null, null);
}

private static string MapPlatformToOS(string platform)
private static string? MapPlatformToOS(string platform)
{
// Handle multiple platforms separated by comma: "Win,Linux" -> "OS.Windows | OS.Linux"
if (platform.Contains(","))
{
var platforms = platform.Split(',')
.Select(p => MapSinglePlatformToOS(p.Trim()))
.ToArray();
return string.Join(" | ", platforms);
var mappedPlatforms = new List<string>();
foreach (var p in platform.Split(','))
{
var mapped = MapSinglePlatformToOS(p.Trim());
if (mapped == null)
{
// If any platform is unsupported, return null
return null;
}
mappedPlatforms.Add(mapped);
}
return string.Join(" | ", mappedPlatforms);
}

return MapSinglePlatformToOS(platform);
}

private static string MapSinglePlatformToOS(string platform)
private static string? MapSinglePlatformToOS(string platform)
{
return platform.ToLowerInvariant() switch
{
"win" or "windows" or "win32" or "win64" => "OS.Windows",
"linux" or "unix" => "OS.Linux",
"macos" or "osx" or "macosx" => "OS.MacOS",
_ => $"OS.{platform}"
"macos" or "osx" or "macosx" => "OS.MacOs",
"browser" or "wasm" or "webassembly" => "OS.Browser",
// Unsupported platforms - return null to indicate no direct equivalent
_ => null
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,60 @@ public void TestName(bool flag) { }
.WithArguments("Tests")
);
}

[Test]
public async Task Warning_When_Multiple_Concrete_Classes_Exist_But_None_Have_InheritsTests()
{
// When there are multiple subclasses but none have [InheritsTests], warn
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class Tests1 : Tests { }
public class Tests2 : Tests { }
public class Tests3 : Tests { }

public abstract class {|#0:Tests|}
{
[Test]
[Arguments(true)]
[Arguments(false)]
public void TestName(bool flag) { }
}
""",

Verifier.Diagnostic(Rules.AbstractTestClassWithDataSources)
.WithLocation(0)
.WithArguments("Tests")
);
}

[Test]
public async Task No_Warning_When_At_Least_One_Concrete_Class_Has_InheritsTests()
{
// When at least one subclass has [InheritsTests], no warning
// (even if other subclasses don't have it)
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class Tests1 : Tests { }

[InheritsTests]
public class Tests2 : Tests { }

public class Tests3 : Tests { }

public abstract class Tests
{
[Test]
[Arguments(true)]
[Arguments(false)]
public void TestName(bool flag) { }
}
"""
);
}
}
166 changes: 166 additions & 0 deletions TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5294,6 +5294,172 @@ public async Task BooleanTest(bool value)
);
}

[Test]
public async Task NUnit_Platform_MacOS_Converted_To_RunOn_With_Correct_Casing()
{
// Issue #4489: MacOS should be converted to OS.MacOs (not OS.MacOS)
await CodeFixer.VerifyCodeFixAsync(
"""
using NUnit.Framework;

public class MyClass
{
{|#0:[Test]|}
[Platform(Include = "MacOS")]
public void TestMethod()
{
}
}
""",
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
"""

public class MyClass
{
[Test]
[RunOn(OS.MacOs)]
public void TestMethod()
{
}
}
""",
ConfigureNUnitTest
);
}

[Test]
public async Task NUnit_Platform_OSX_Converted_To_RunOn_MacOs()
{
await CodeFixer.VerifyCodeFixAsync(
"""
using NUnit.Framework;

public class MyClass
{
{|#0:[Test]|}
[Platform(Include = "OSX")]
public void TestMethod()
{
}
}
""",
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
"""

public class MyClass
{
[Test]
[RunOn(OS.MacOs)]
public void TestMethod()
{
}
}
""",
ConfigureNUnitTest
);
}

[Test]
public async Task NUnit_Platform_Unsupported_Mono_Not_Converted()
{
// Issue #4489: Unsupported platforms like Mono should NOT be converted
// because TUnit doesn't have an equivalent OS value
await CodeFixer.VerifyCodeFixAsync(
"""
using NUnit.Framework;

public class MyClass
{
{|#0:[Test]|}
[Platform(Exclude = "Mono")]
public void TestMethod()
{
}
}
""",
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
"""

public class MyClass
{
[Test]
[Platform(Exclude = "Mono")]
public void TestMethod()
{
}
}
""",
ConfigureNUnitTest
);
}

[Test]
public async Task NUnit_Platform_Unsupported_Net_Not_Converted()
{
// Issue #4489: Unsupported platforms like Net should NOT be converted
await CodeFixer.VerifyCodeFixAsync(
"""
using NUnit.Framework;

public class MyClass
{
{|#0:[Test]|}
[Platform(Exclude = "Net")]
public void TestMethod()
{
}
}
""",
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
"""

public class MyClass
{
[Test]
[Platform(Exclude = "Net")]
public void TestMethod()
{
}
}
""",
ConfigureNUnitTest
);
}

[Test]
public async Task NUnit_Platform_Mixed_Supported_And_Unsupported_Not_Converted()
{
// Issue #4489: If any platform in a comma-separated list is unsupported,
// the whole attribute should not be converted
await CodeFixer.VerifyCodeFixAsync(
"""
using NUnit.Framework;

public class MyClass
{
{|#0:[Test]|}
[Platform(Include = "Win,Mono")]
public void TestMethod()
{
}
}
""",
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
"""

public class MyClass
{
[Test]
[Platform(Include = "Win,Mono")]
public void TestMethod()
{
}
}
""",
ConfigureNUnitTest
);
}

private static void ConfigureNUnitTest(Verifier.Test test)
{
test.TestState.AdditionalReferences.Add(typeof(NUnit.Framework.TestAttribute).Assembly);
Expand Down
14 changes: 11 additions & 3 deletions docs/docs/migration/mstest.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,22 @@ dotnet format analyzers --severity info --diagnostics TUMS0001

This command applies all available fixes for the `TUMS0001` diagnostic. You'll see output indicating which files were modified.

:::tip Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), specify the latest framework to avoid issues:
:::warning Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), you **must** specify a single target framework when running the code fixer. Multi-targeting can cause the code fixer to crash with the error `Changes must be within bounds of SourceText` due to a limitation in Roslyn's linked file handling.

**Option 1:** Specify a single framework via command line:
```bash
dotnet format analyzers --severity info --diagnostics TUMS0001 --framework net10.0
```

Replace `net10.0` with your project's highest supported target framework.
**Option 2:** Temporarily modify your project file to single-target:
```xml
<!-- Before migration -->
<TargetFramework>net10.0</TargetFramework>
<!-- <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> -->
```

Run the code fixer, then restore multi-targeting afterward. Replace `net10.0` with your project's highest supported target framework.
:::

**5. Remove the implicit usings workaround**
Expand Down
14 changes: 11 additions & 3 deletions docs/docs/migration/nunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,22 @@ dotnet format analyzers --severity info --diagnostics TUNU0001

This command applies all available fixes for the `TUNU0001` diagnostic. You'll see output indicating which files were modified.

:::tip Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), specify the latest framework to avoid issues:
:::warning Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), you **must** specify a single target framework when running the code fixer. Multi-targeting can cause the code fixer to crash with the error `Changes must be within bounds of SourceText` due to a limitation in Roslyn's linked file handling.

**Option 1:** Specify a single framework via command line:
```bash
dotnet format analyzers --severity info --diagnostics TUNU0001 --framework net10.0
```

Replace `net10.0` with your project's highest supported target framework.
**Option 2:** Temporarily modify your project file to single-target:
```xml
<!-- Before migration -->
<TargetFramework>net10.0</TargetFramework>
<!-- <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> -->
```

Run the code fixer, then restore multi-targeting afterward. Replace `net10.0` with your project's highest supported target framework.
:::

**5. Remove the implicit usings workaround**
Expand Down
14 changes: 11 additions & 3 deletions docs/docs/migration/xunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,22 @@ dotnet format analyzers --severity info --diagnostics TUXU0001

This command applies all available fixes for the `TUXU0001` diagnostic. You'll see output indicating which files were modified.

:::tip Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), specify the latest framework to avoid issues:
:::warning Multi-targeting Projects
If your project targets multiple .NET versions (e.g., `net8.0;net9.0;net10.0`), you **must** specify a single target framework when running the code fixer. Multi-targeting can cause the code fixer to crash with the error `Changes must be within bounds of SourceText` due to a limitation in Roslyn's linked file handling.

**Option 1:** Specify a single framework via command line:
```bash
dotnet format analyzers --severity info --diagnostics TUXU0001 --framework net10.0
```

Replace `net10.0` with your project's highest supported target framework.
**Option 2:** Temporarily modify your project file to single-target:
```xml
<!-- Before migration -->
<TargetFramework>net10.0</TargetFramework>
<!-- <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks> -->
```

Run the code fixer, then restore multi-targeting afterward. Replace `net10.0` with your project's highest supported target framework.
:::

**5. Remove the implicit usings workaround**
Expand Down
Loading