Skip to content

Commit 184f603

Browse files
CopilotmarcpopMSFTbaronfel
authored
Fix RootNamespace handling for dashes and starting digits in project names (#49328)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com> Co-authored-by: Marc Paine <marcpop@microsoft.com> Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com>
1 parent 273969a commit 184f603

File tree

2 files changed

+138
-16
lines changed

2 files changed

+138
-16
lines changed

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.props

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ Copyright (c) .NET Foundation. All rights reserved.
3939
<FileAlignment Condition=" '$(FileAlignment)' == '' ">512</FileAlignment>
4040
<ErrorReport Condition=" '$(ErrorReport)' == '' ">prompt</ErrorReport>
4141
<AssemblyName Condition=" '$(AssemblyName)' == '' ">$(MSBuildProjectName)</AssemblyName>
42-
<RootNamespace Condition=" '$(RootNamespace)' == '' ">$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
4342
<Deterministic Condition=" '$(Deterministic)' == '' ">true</Deterministic>
4443
</PropertyGroup>
4544

45+
<PropertyGroup Condition=" '$(RootNamespace)' == '' ">
46+
<!-- Transform project name for RootNamespace: replace spaces and dashes with underscores, prefix with underscore if starts with digit -->
47+
<RootNamespace>$(MSBuildProjectName.Replace(" ", "_").Replace("-", "_"))</RootNamespace>
48+
<RootNamespace Condition=" $([System.Char]::IsDigit($(RootNamespace), 0)) ">_$(RootNamespace)</RootNamespace>
49+
</PropertyGroup>
50+
4651
<!-- User-facing configuration-specific defaults -->
4752
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
4853
<DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>

test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildALibrary.cs

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,24 @@ internal static List<string> GetValuesFromTestLibrary(
105105
return itemValues;
106106
}
107107

108+
private string GetPropertyValue(string propertyName, string projectFolder, string targetFramework)
109+
{
110+
var getValuesCommand = new GetValuesCommand(Log, projectFolder,
111+
targetFramework, propertyName, GetValuesCommand.ValueType.Property)
112+
{
113+
Configuration = "Debug"
114+
};
115+
116+
getValuesCommand
117+
.Execute()
118+
.Should()
119+
.Pass();
120+
121+
var values = getValuesCommand.GetValues();
122+
values.Count.Should().Be(1);
123+
return values[0];
124+
}
125+
108126
private TestAsset CreateDocumentationFileLibraryAsset(bool? generateDocumentationFile, string documentationFile, string language, [CallerMemberName] string callingMethod = "")
109127
{
110128
string genDocFileIdentifier = generateDocumentationFile == null ? "null" : generateDocumentationFile.Value.ToString();
@@ -1045,25 +1063,124 @@ public class ProjectNameWithSpacesClass
10451063
.Should()
10461064
.Pass();
10471065

1048-
string GetPropertyValue(string propertyName)
1066+
GetPropertyValue("RootNamespace", projectFolder, testProject.TargetFrameworks).Should().Be("Project_Name_With_Spaces");
1067+
}
1068+
1069+
[Theory]
1070+
[InlineData("netcoreapp3.1")]
1071+
[InlineData("netcoreapp5.0")]
1072+
public void It_makes_RootNamespace_safe_when_project_name_has_dashes(string targetFramework)
1073+
{
1074+
var testProject = new TestProject()
10491075
{
1050-
var getValuesCommand = new GetValuesCommand(Log, projectFolder,
1051-
testProject.TargetFrameworks, propertyName, GetValuesCommand.ValueType.Property)
1052-
{
1053-
Configuration = "Debug"
1054-
};
1076+
Name = "my-project-with-dashes",
1077+
TargetFrameworks = targetFramework,
1078+
};
10551079

1056-
getValuesCommand
1057-
.Execute()
1058-
.Should()
1059-
.Pass();
1080+
var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework);
10601081

1061-
var values = getValuesCommand.GetValues();
1062-
values.Count.Should().Be(1);
1063-
return values[0];
1064-
}
1082+
// Overwrite the default file. CreateTestProject uses the defined project name for the namespace.
1083+
// We need a buildable project to extract the property to verify it
1084+
// since this issue only surfaces in VS when adding a new class through an item template.
1085+
File.WriteAllText(Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.cs"), @"
1086+
using System;
1087+
using System.Collections.Generic;
1088+
1089+
namespace MyProjectWithDashes
1090+
{
1091+
public class MyProjectWithDashesClass
1092+
{
1093+
public static string Name { get { return ""my-project-with-dashes""; } }
1094+
public static List<string> List { get { return null; } }
1095+
}
1096+
}");
1097+
string projectFolder = Path.Combine(testAsset.Path, testProject.Name);
1098+
1099+
var buildCommand = new BuildCommand(testAsset, $"{testProject.Name}");
1100+
buildCommand
1101+
.Execute()
1102+
.Should()
1103+
.Pass();
1104+
1105+
GetPropertyValue("RootNamespace", projectFolder, testProject.TargetFrameworks).Should().Be("my_project_with_dashes");
1106+
}
1107+
1108+
[Theory]
1109+
[InlineData("netcoreapp3.1")]
1110+
[InlineData("netcoreapp5.0")]
1111+
public void It_makes_RootNamespace_safe_when_project_name_starts_with_digit(string targetFramework)
1112+
{
1113+
var testProject = new TestProject()
1114+
{
1115+
Name = "13monkeys",
1116+
TargetFrameworks = targetFramework,
1117+
};
1118+
1119+
var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework);
1120+
1121+
// Overwrite the default file. CreateTestProject uses the defined project name for the namespace.
1122+
// We need a buildable project to extract the property to verify it
1123+
// since this issue only surfaces in VS when adding a new class through an item template.
1124+
File.WriteAllText(Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.cs"), @"
1125+
using System;
1126+
using System.Collections.Generic;
1127+
1128+
namespace _13monkeys
1129+
{
1130+
public class _13monkeysClass
1131+
{
1132+
public static string Name { get { return ""13monkeys""; } }
1133+
public static List<string> List { get { return null; } }
1134+
}
1135+
}");
1136+
string projectFolder = Path.Combine(testAsset.Path, testProject.Name);
1137+
1138+
var buildCommand = new BuildCommand(testAsset, $"{testProject.Name}");
1139+
buildCommand
1140+
.Execute()
1141+
.Should()
1142+
.Pass();
1143+
1144+
GetPropertyValue("RootNamespace", projectFolder, testProject.TargetFrameworks).Should().Be("_13monkeys");
1145+
}
1146+
1147+
[Theory]
1148+
[InlineData("netcoreapp3.1")]
1149+
[InlineData("netcoreapp5.0")]
1150+
public void It_makes_RootNamespace_safe_when_project_name_has_dashes_and_starts_with_digit(string targetFramework)
1151+
{
1152+
var testProject = new TestProject()
1153+
{
1154+
Name = "13-monkeys-project",
1155+
TargetFrameworks = targetFramework,
1156+
};
1157+
1158+
var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: targetFramework);
1159+
1160+
// Overwrite the default file. CreateTestProject uses the defined project name for the namespace.
1161+
// We need a buildable project to extract the property to verify it
1162+
// since this issue only surfaces in VS when adding a new class through an item template.
1163+
File.WriteAllText(Path.Combine(testAsset.Path, testProject.Name, $"{testProject.Name}.cs"), @"
1164+
using System;
1165+
using System.Collections.Generic;
1166+
1167+
namespace _13_monkeys_project
1168+
{
1169+
public class _13_monkeys_projectClass
1170+
{
1171+
public static string Name { get { return ""13-monkeys-project""; } }
1172+
public static List<string> List { get { return null; } }
1173+
}
1174+
}");
1175+
string projectFolder = Path.Combine(testAsset.Path, testProject.Name);
1176+
1177+
var buildCommand = new BuildCommand(testAsset, $"{testProject.Name}");
1178+
buildCommand
1179+
.Execute()
1180+
.Should()
1181+
.Pass();
10651182

1066-
GetPropertyValue("RootNamespace").Should().Be("Project_Name_With_Spaces");
1183+
GetPropertyValue("RootNamespace", projectFolder, testProject.TargetFrameworks).Should().Be("_13_monkeys_project");
10671184
}
10681185

10691186
[WindowsOnlyFact(Skip = "We need new SDK packages with different assembly versions to build this (.38 and .39 have the same assembly version)")]

0 commit comments

Comments
 (0)