diff --git a/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj b/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj index 7f04dd2..e85f232 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj +++ b/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj @@ -71,6 +71,7 @@ + True True diff --git a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs index fd5ef3f..e0ed85e 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs +++ b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs @@ -83,9 +83,11 @@ private void RenameProject(Project project, DTE2 dte) // Update RootNamespace and AssemblyName in .csproj ProjectFileService.UpdateProjectFile(projectFilePath, currentName, newName); + // Update namespace declarations in source files + SourceFileService.UpdateNamespacesInProject(projectFilePath, currentName, newName); + // TODO: Implement remaining rename operations // See open issues for requirements: - // - #8: Update namespace declarations in source files // - #9: Update using statements across solution // - #11: Solution folder support // - #12: Progress indication diff --git a/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs b/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs new file mode 100644 index 0000000..b03173d --- /dev/null +++ b/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs @@ -0,0 +1,108 @@ +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace CodingWithCalvin.ProjectRenamifier.Services +{ + /// + /// Service for updating namespace declarations in source files. + /// + internal static class SourceFileService + { + /// + /// Updates namespace declarations in all .cs files within the project directory. + /// + /// Full path to the .csproj file. + /// The old namespace to find. + /// The new namespace to replace with. + /// The number of files modified. + public static int UpdateNamespacesInProject(string projectFilePath, string oldNamespace, string newNamespace) + { + var projectDirectory = Path.GetDirectoryName(projectFilePath); + if (string.IsNullOrEmpty(projectDirectory)) + { + return 0; + } + + var csFiles = Directory.GetFiles(projectDirectory, "*.cs", SearchOption.AllDirectories); + var modifiedCount = 0; + + foreach (var filePath in csFiles) + { + if (UpdateNamespacesInFile(filePath, oldNamespace, newNamespace)) + { + modifiedCount++; + } + } + + return modifiedCount; + } + + /// + /// Updates namespace declarations in a single source file. + /// + /// Full path to the .cs file. + /// The old namespace to find. + /// The new namespace to replace with. + /// True if the file was modified, false otherwise. + public static bool UpdateNamespacesInFile(string filePath, string oldNamespace, string newNamespace) + { + // Detect file encoding + var encoding = DetectEncoding(filePath); + var content = File.ReadAllText(filePath, encoding); + var originalContent = content; + + // Pattern for block-scoped namespace: namespace OldName { or namespace OldName\n{ + // Also handles nested: namespace OldName.Something + var blockPattern = $@"(\bnamespace\s+){Regex.Escape(oldNamespace)}(\s*[\{{\r\n]|\.|\s*$)"; + content = Regex.Replace(content, blockPattern, $"$1{newNamespace}$2"); + + // Pattern for file-scoped namespace: namespace OldName; + // Also handles nested: namespace OldName.Something; + var fileScopedPattern = $@"(\bnamespace\s+){Regex.Escape(oldNamespace)}(\.[\w.]*)?(\s*;)"; + content = Regex.Replace(content, fileScopedPattern, $"$1{newNamespace}$2$3"); + + if (content != originalContent) + { + File.WriteAllText(filePath, content, encoding); + return true; + } + + return false; + } + + /// + /// Detects the encoding of a file, defaulting to UTF-8 if not determinable. + /// + private static Encoding DetectEncoding(string filePath) + { + // Read the BOM to detect encoding + var bom = new byte[4]; + using (var file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + { + file.Read(bom, 0, 4); + } + + // Detect encoding from BOM + if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) + { + return Encoding.UTF8; + } + if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) + { + return Encoding.UTF32; + } + if (bom[0] == 0xff && bom[1] == 0xfe) + { + return Encoding.Unicode; // UTF-16 LE + } + if (bom[0] == 0xfe && bom[1] == 0xff) + { + return Encoding.BigEndianUnicode; // UTF-16 BE + } + + // Default to UTF-8 without BOM + return new UTF8Encoding(false); + } + } +}