Skip to content

Commit 248bd10

Browse files
Add root namespace declaration unless explicitly global
In VB it defaults to root namespace, in C# the default is global
1 parent dee91a9 commit 248bd10

9 files changed

+172
-32
lines changed

ICSharpCode.CodeConverter/CSharp/NodesVisitor.cs

+20-24
Original file line numberDiff line numberDiff line change
@@ -100,45 +100,41 @@ public override CSharpSyntaxNode VisitCompilationUnit(VBSyntax.CompilationUnitSy
100100
var importsClauses = options.GlobalImports.Select(gi => gi.Clause).Concat(node.Imports.SelectMany(imp => imp.ImportsClauses)).ToList();
101101

102102
var attributes = SyntaxFactory.List(node.Attributes.SelectMany(a => a.AttributeLists).SelectMany(ConvertAttribute));
103-
var convertedMembers = node.Members.Select(m => (MemberDeclarationSyntax)m.Accept(TriviaConvertingVisitor)).ToReadOnlyCollection();
104-
if (!string.IsNullOrEmpty(options.RootNamespace))
105-
{
106-
var rootNamespaceIdentifier = SyntaxFactory.IdentifierName(options.RootNamespace);
107-
convertedMembers = PrependRootNamespace(convertedMembers, rootNamespaceIdentifier);
108-
}
103+
var sourceAndConverted = node.Members.Select(m => (Source: m, Converted: (MemberDeclarationSyntax)m.Accept(TriviaConvertingVisitor))).ToReadOnlyCollection();
104+
var convertedMembers = string.IsNullOrEmpty(options.RootNamespace)
105+
? sourceAndConverted.Select(sd => sd.Converted)
106+
: PrependRootNamespace(sourceAndConverted, SyntaxFactory.IdentifierName(options.RootNamespace));
109107

108+
var usingDirectiveSyntax = importsClauses.GroupBy(c => c.ToString()).Select(g => g.First())
109+
.Select(c => (UsingDirectiveSyntax)c.Accept(TriviaConvertingVisitor));
110110
return SyntaxFactory.CompilationUnit(
111111
SyntaxFactory.List<ExternAliasDirectiveSyntax>(),
112-
SyntaxFactory.List(importsClauses.GroupBy(c => c.ToString()).Select(g => g.First()).Select(c => (UsingDirectiveSyntax)c.Accept(TriviaConvertingVisitor))),
112+
SyntaxFactory.List(usingDirectiveSyntax),
113113
attributes,
114114
SyntaxFactory.List(convertedMembers)
115115
);
116116
}
117117

118118
private IReadOnlyCollection<MemberDeclarationSyntax> PrependRootNamespace(
119-
IReadOnlyCollection<MemberDeclarationSyntax> memberDeclarations,
119+
IReadOnlyCollection<(VBSyntax.StatementSyntax VbNode, MemberDeclarationSyntax CsNode)> memberConversion,
120120
IdentifierNameSyntax rootNamespaceIdentifier)
121121
{
122-
if (memberDeclarations.Count == 1 && memberDeclarations.First() is NamespaceDeclarationSyntax nsDecl) {
123-
return memberDeclarations;
122+
var inGlobalNamespace = memberConversion
123+
.ToLookup(m => IsNamespaceDeclarationInGlobalScope(m.VbNode), m => m.CsNode);
124+
var members = inGlobalNamespace[true].ToList();
125+
if (inGlobalNamespace[false].Any()) {
126+
var newNamespaceDecl = (MemberDeclarationSyntax)SyntaxFactory.NamespaceDeclaration(rootNamespaceIdentifier)
127+
.WithMembers(SyntaxFactory.List(inGlobalNamespace[false]));
128+
members.Add(newNamespaceDecl);
124129
}
125-
126-
var newNamespaceDecl = (MemberDeclarationSyntax)SyntaxFactory.NamespaceDeclaration(rootNamespaceIdentifier)
127-
.WithMembers(SyntaxFactory.List(memberDeclarations));
128-
return new [] { newNamespaceDecl };
130+
return members;
129131
}
130132

131-
private NameSyntax PrependName(NameSyntax name, IdentifierNameSyntax toPrepend)
133+
private bool IsNamespaceDeclarationInGlobalScope(VBSyntax.StatementSyntax m)
132134
{
133-
if (name is IdentifierNameSyntax identName) {
134-
return SyntaxFactory.QualifiedName(toPrepend, identName);
135-
}
136-
else if (name is QualifiedNameSyntax qName) {
137-
return SyntaxFactory.QualifiedName(PrependName(qName.Left, toPrepend), qName.Right);
138-
}
139-
else {
140-
throw new ArgumentOutOfRangeException(nameof(name), name, $"{name.GetType().Name} of kind {name.Kind()} not expected within namespace declaration");
141-
}
135+
if (!(m is VBSyntax.NamespaceBlockSyntax nss)) return false;
136+
if (!(_semanticModel.GetSymbolInfo(nss.NamespaceStatement.Name).Symbol is INamespaceSymbol nsSymbol)) return false;
137+
return nsSymbol.ContainingNamespace.IsGlobalNamespace;
142138
}
143139

144140
public override CSharpSyntaxNode VisitSimpleImportsClause(VBSyntax.SimpleImportsClauseSyntax node)

ICSharpCode.CodeConverter/CSharp/VBToCSConversion.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class VBToCSConversion : ILanguageConversion
2020
private Compilation _sourceCompilation;
2121
private readonly List<SyntaxTree> _secondPassResults = new List<SyntaxTree>();
2222
private CSharpCompilation _convertedCompilation;
23+
public string RootNamespace { get; set; }
2324

2425

2526
public void Initialize(Compilation convertedCompilation)
@@ -148,13 +149,14 @@ public SyntaxTree CreateTree(string text)
148149

149150
public Compilation CreateCompilationFromTree(SyntaxTree tree, IEnumerable<MetadataReference> references)
150151
{
151-
var compilation = CreateVisualBasicCompilation(references);
152+
var compilation = CreateVisualBasicCompilation(references, RootNamespace);
152153
return compilation.AddSyntaxTrees(tree);
153154
}
154155

155-
public static VisualBasicCompilation CreateVisualBasicCompilation(IEnumerable<MetadataReference> references)
156+
public static VisualBasicCompilation CreateVisualBasicCompilation(IEnumerable<MetadataReference> references, string rootNamespace = null)
156157
{
157158
var compilationOptions = new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
159+
.WithRootNamespace(rootNamespace)
158160
.WithGlobalImports(GlobalImport.Parse(
159161
"System",
160162
"System.Collections.Generic",

ICSharpCode.CodeConverter/ILanguageConversion.cs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ SyntaxNode GetSurroundedNode(IEnumerable<SyntaxNode> descendantNodes,
2222
IReadOnlyCollection<(string, string)> GetProjectTypeGuidMappings();
2323
IEnumerable<(string, string)> GetProjectFileReplacementRegexes();
2424
string TargetLanguage { get; }
25+
string RootNamespace { get; set; }
2526
void Initialize(Compilation convertedCompilation);
2627
string PostTransformProjectFile(string s);
2728
}

ICSharpCode.CodeConverter/Shared/ProjectConversion.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ private ProjectConversion(Compilation sourceCompilation, IEnumerable<SyntaxTree>
3535
languageConversion.Initialize(convertedCompilation.RemoveAllSyntaxTrees());
3636
}
3737

38-
public static ConversionResult ConvertText<TLanguageConversion>(string text, IReadOnlyCollection<PortableExecutableReference> references) where TLanguageConversion : ILanguageConversion, new()
38+
public static ConversionResult ConvertText<TLanguageConversion>(string text, IReadOnlyCollection<PortableExecutableReference> references, string rootNamespace = null) where TLanguageConversion : ILanguageConversion, new()
3939
{
40-
var languageConversion = new TLanguageConversion();
40+
var languageConversion = new TLanguageConversion {
41+
RootNamespace = rootNamespace
42+
};
4143
var syntaxTree = languageConversion.CreateTree(text);
4244
var compilation = languageConversion.CreateCompilationFromTree(syntaxTree, references);
4345
return ConvertSingle(compilation, syntaxTree, new TextSpan(0, 0), new TLanguageConversion()).GetAwaiter().GetResult();

ICSharpCode.CodeConverter/VB/CSToVBConversion.cs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class CSToVBConversion : ILanguageConversion
2121
{
2222
private Compilation _sourceCompilation;
2323
private VisualBasicCompilation _convertedCompilation;
24+
public string RootNamespace { get; set; }
2425

2526
public void Initialize(Compilation convertedCompilation)
2627
{

Tests/CSharp/NamespaceLevelTests.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
using Xunit;
1+
using System;
2+
using Xunit;
23

34
namespace CodeConverter.Tests.CSharp
45
{
56
public class NamespaceLevelTests : ConverterTestBase
67
{
8+
79
[Fact]
810
public void TestNamespace()
911
{

Tests/CSharp/RootNamespaceTests.cs

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using Xunit;
2+
3+
namespace CodeConverter.Tests.CSharp
4+
{
5+
public class RootNamespaceTests : ConverterTestBase
6+
{
7+
public RootNamespaceTests() : base("TheRootNamespace")
8+
{
9+
}
10+
11+
[Fact]
12+
public void RootNamespaceIsExplicit()
13+
{
14+
// Auto comment testing not used since it can't handle the added namespace
15+
TestConversionVisualBasicToCSharpWithoutComments(@"Class AClassInRootNamespace
16+
End Class
17+
18+
Namespace NestedWithinRoot
19+
Class AClassInANamespace
20+
End Class
21+
End Namespace",
22+
@"namespace TheRootNamespace
23+
{
24+
class AClassInRootNamespace
25+
{
26+
}
27+
28+
namespace NestedWithinRoot
29+
{
30+
class AClassInANamespace
31+
{
32+
}
33+
}
34+
}");
35+
}
36+
37+
[Fact]
38+
public void RootNamespaceIsExplicitWithSingleClass()
39+
{
40+
// Auto comment testing not used since it can't handle the added namespace
41+
TestConversionVisualBasicToCSharpWithoutComments(@"Class AClassInRootNamespace
42+
End Class",
43+
@"namespace TheRootNamespace
44+
{
45+
class AClassInRootNamespace
46+
{
47+
}
48+
}");
49+
}
50+
51+
[Fact]
52+
public void RootNamespaceIsExplicitForSingleNamespace()
53+
{
54+
// Auto comment testing not used since it can't handle the added namespace
55+
TestConversionVisualBasicToCSharpWithoutComments(@"
56+
Namespace NestedWithinRoot
57+
Class AClassInANamespace
58+
End Class
59+
End Namespace",
60+
@"namespace TheRootNamespace
61+
{
62+
namespace NestedWithinRoot
63+
{
64+
class AClassInANamespace
65+
{
66+
}
67+
}
68+
}");
69+
}
70+
71+
[Fact]
72+
public void RootNamespaceNotAppliedToFullyQualifiedNamespace()
73+
{
74+
// Auto comment testing not used since it can't handle the added namespace
75+
TestConversionVisualBasicToCSharpWithoutComments(@"
76+
Namespace Global.NotNestedWithinRoot
77+
Class AClassInANamespace
78+
End Class
79+
End Namespace",
80+
@"namespace NotNestedWithinRoot
81+
{
82+
class AClassInANamespace
83+
{
84+
}
85+
}");
86+
}
87+
88+
[Fact]
89+
public void RootNamespaceOnlyAppliedToUnqualifiedMembers()
90+
{
91+
// Auto comment testing not used since it can't handle the added namespace
92+
TestConversionVisualBasicToCSharpWithoutComments(@"
93+
Class AClassInRootNamespace
94+
End Class
95+
96+
Namespace Global.NotNestedWithinRoot
97+
Class AClassInANamespace
98+
End Class
99+
End Namespace
100+
101+
Namespace NestedWithinRoot
102+
Class AClassInANamespace
103+
End Class
104+
End Namespace",
105+
@"
106+
namespace NotNestedWithinRoot
107+
{
108+
class AClassInANamespace
109+
{
110+
}
111+
}
112+
113+
namespace TheRootNamespace
114+
{
115+
class AClassInRootNamespace
116+
{
117+
}
118+
119+
namespace NestedWithinRoot
120+
{
121+
class AClassInANamespace
122+
{
123+
}
124+
}
125+
}");
126+
}
127+
}
128+
}

Tests/ConverterTestBase.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ namespace CodeConverter.Tests
1212
public class ConverterTestBase
1313
{
1414
private bool _testCstoVBCommentsByDefault = false;
15+
private readonly string _rootNamespace;
16+
17+
public ConverterTestBase(string rootNamespace = null)
18+
{
19+
_rootNamespace = rootNamespace;
20+
}
21+
1522
public void TestConversionCSharpToVisualBasic(string csharpCode, string expectedVisualBasicCode, bool expectSurroundingMethodBlock = false)
1623
{
1724
expectedVisualBasicCode = AddSurroundingMethodBlock(expectedVisualBasicCode, expectSurroundingMethodBlock);
@@ -33,7 +40,7 @@ private static string AddSurroundingMethodBlock(string expectedVisualBasicCode,
3340
return expectedVisualBasicCode;
3441
}
3542

36-
private static void TestConversionCSharpToVisualBasicWithoutComments(string csharpCode, string expectedVisualBasicCode)
43+
private void TestConversionCSharpToVisualBasicWithoutComments(string csharpCode, string expectedVisualBasicCode)
3744
{
3845
AssertConvertedCodeResultEquals<CSToVBConversion>(csharpCode, expectedVisualBasicCode);
3946
}
@@ -56,10 +63,10 @@ public void TestConversionVisualBasicToCSharpWithoutComments(string visualBasicC
5663
AssertConvertedCodeResultEquals<VBToCSConversion>(visualBasicCode, expectedCsharpCode);
5764
}
5865

59-
private static void AssertConvertedCodeResultEquals<TLanguageConversion>(string inputCode, string expectedConvertedCode) where TLanguageConversion : ILanguageConversion, new()
66+
private void AssertConvertedCodeResultEquals<TLanguageConversion>(string inputCode, string expectedConvertedCode) where TLanguageConversion : ILanguageConversion, new()
6067
{
6168
var outputNode =
62-
ProjectConversion.ConvertText<TLanguageConversion>(inputCode, DefaultReferences.NetStandard2);
69+
ProjectConversion.ConvertText<TLanguageConversion>(inputCode, DefaultReferences.NetStandard2, _rootNamespace);
6370
AssertConvertedCodeResultEquals(outputNode, expectedConvertedCode, inputCode);
6471
}
6572

Tests/Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
</Compile>
153153
<Compile Include="ConverterTestBase.cs" />
154154
<Compile Include="CSharp\MissingSemanticModelInfo\ExpressionTests.cs" />
155+
<Compile Include="CSharp\RootNamespaceTests.cs" />
155156
<Compile Include="CSharp\SolutionAndProjectTests.cs" />
156157
<Compile Include="CSharp\StandaloneMultiStatementTests.cs" />
157158
<Compile Include="CSharp\ExpressionTests.cs" />

0 commit comments

Comments
 (0)