Skip to content

Commit 881b14e

Browse files
authored
feat(namespace): update namespace declarations in source files (#19)
1 parent 4809047 commit 881b14e

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
</Compile>
7272
<Compile Include="ProjectRenamifierPackage.cs" />
7373
<Compile Include="Services\ProjectFileService.cs" />
74+
<Compile Include="Services\SourceFileService.cs" />
7475
<Compile Include="source.extension.cs">
7576
<AutoGen>True</AutoGen>
7677
<DesignTime>True</DesignTime>

src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ private void RenameProject(Project project, DTE2 dte)
8383
// Update RootNamespace and AssemblyName in .csproj
8484
ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName);
8585

86+
// Update namespace declarations in source files
87+
SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName);
88+
8689
// TODO: Implement remaining rename operations
8790
// See open issues for requirements:
88-
// - #8: Update namespace declarations in source files
8991
// - #9: Update using statements across solution
9092
// - #11: Solution folder support
9193
// - #12: Progress indication
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.IO;
2+
using System.Text;
3+
using System.Text.RegularExpressions;
4+
5+
namespace CodingWithCalvin.ProjectRenamifier.Services
6+
{
7+
/// <summary>
8+
/// Service for updating namespace declarations in source files.
9+
/// </summary>
10+
internal static class SourceFileService
11+
{
12+
/// <summary>
13+
/// Updates namespace declarations in all .cs files within the project directory.
14+
/// </summary>
15+
/// <param name="projectFilePath">Full path to the .csproj file.</param>
16+
/// <param name="oldNamespace">The old namespace to find.</param>
17+
/// <param name="newNamespace">The new namespace to replace with.</param>
18+
/// <returns>The number of files modified.</returns>
19+
public static int UpdateNamespacesInProject(string projectFilePath, string oldNamespace, string newNamespace)
20+
{
21+
var projectDirectory = Path.GetDirectoryName(projectFilePath);
22+
if (string.IsNullOrEmpty(projectDirectory))
23+
{
24+
return 0;
25+
}
26+
27+
var csFiles = Directory.GetFiles(projectDirectory, "*.cs", SearchOption.AllDirectories);
28+
var modifiedCount = 0;
29+
30+
foreach (var filePath in csFiles)
31+
{
32+
if (UpdateNamespacesInFile(filePath, oldNamespace, newNamespace))
33+
{
34+
modifiedCount++;
35+
}
36+
}
37+
38+
return modifiedCount;
39+
}
40+
41+
/// <summary>
42+
/// Updates namespace declarations in a single source file.
43+
/// </summary>
44+
/// <param name="filePath">Full path to the .cs file.</param>
45+
/// <param name="oldNamespace">The old namespace to find.</param>
46+
/// <param name="newNamespace">The new namespace to replace with.</param>
47+
/// <returns>True if the file was modified, false otherwise.</returns>
48+
public static bool UpdateNamespacesInFile(string filePath, string oldNamespace, string newNamespace)
49+
{
50+
// Detect file encoding
51+
var encoding = DetectEncoding(filePath);
52+
var content = File.ReadAllText(filePath, encoding);
53+
var originalContent = content;
54+
55+
// Pattern for block-scoped namespace: namespace OldName { or namespace OldName\n{
56+
// Also handles nested: namespace OldName.Something
57+
var blockPattern = $@"(\bnamespace\s+){Regex.Escape(oldNamespace)}(\s*[\{{\r\n]|\.|\s*$)";
58+
content = Regex.Replace(content, blockPattern, $"$1{newNamespace}$2");
59+
60+
// Pattern for file-scoped namespace: namespace OldName;
61+
// Also handles nested: namespace OldName.Something;
62+
var fileScopedPattern = $@"(\bnamespace\s+){Regex.Escape(oldNamespace)}(\.[\w.]*)?(\s*;)";
63+
content = Regex.Replace(content, fileScopedPattern, $"$1{newNamespace}$2$3");
64+
65+
if (content != originalContent)
66+
{
67+
File.WriteAllText(filePath, content, encoding);
68+
return true;
69+
}
70+
71+
return false;
72+
}
73+
74+
/// <summary>
75+
/// Detects the encoding of a file, defaulting to UTF-8 if not determinable.
76+
/// </summary>
77+
private static Encoding DetectEncoding(string filePath)
78+
{
79+
// Read the BOM to detect encoding
80+
var bom = new byte[4];
81+
using (var file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
82+
{
83+
file.Read(bom, 0, 4);
84+
}
85+
86+
// Detect encoding from BOM
87+
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf)
88+
{
89+
return Encoding.UTF8;
90+
}
91+
if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0)
92+
{
93+
return Encoding.UTF32;
94+
}
95+
if (bom[0] == 0xff && bom[1] == 0xfe)
96+
{
97+
return Encoding.Unicode; // UTF-16 LE
98+
}
99+
if (bom[0] == 0xfe && bom[1] == 0xff)
100+
{
101+
return Encoding.BigEndianUnicode; // UTF-16 BE
102+
}
103+
104+
// Default to UTF-8 without BOM
105+
return new UTF8Encoding(false);
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)