diff --git a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs index 37c07df..5842831 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs +++ b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs @@ -105,9 +105,11 @@ private void RenameProject(Project project, DTE2 dte) // Re-add project to solution with new path dte.Solution.AddFromFile(projectFilePath); + // Update using statements across the entire solution + SourceFileService.UpdateUsingStatementsInSolution(dte.Solution, currentName, newName); + // TODO: Implement remaining rename operations // See open issues for requirements: - // - #9: Update using statements across solution // - #11: Solution folder support // - #12: Progress indication // - #13: Error handling and rollback diff --git a/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs b/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs index b03173d..bfcd099 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs +++ b/src/CodingWithCalvin.ProjectRenamifier/Services/SourceFileService.cs @@ -1,14 +1,134 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; +using EnvDTE; namespace CodingWithCalvin.ProjectRenamifier.Services { /// - /// Service for updating namespace declarations in source files. + /// Service for updating namespace declarations and using statements in source files. /// internal static class SourceFileService { + /// + /// Updates using statements in all .cs files across the entire solution. + /// + /// The solution to scan. + /// The old namespace to find. + /// The new namespace to replace with. + /// The number of files modified. + public static int UpdateUsingStatementsInSolution(Solution solution, string oldNamespace, string newNamespace) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var modifiedCount = 0; + + foreach (Project project in solution.Projects) + { + modifiedCount += UpdateUsingStatementsInProjectTree(project, oldNamespace, newNamespace); + } + + return modifiedCount; + } + + /// + /// Recursively updates using statements in a project (handles solution folders). + /// + private static int UpdateUsingStatementsInProjectTree(Project project, string oldNamespace, string newNamespace) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (project == null) + { + return 0; + } + + // Handle solution folders + if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + { + var count = 0; + foreach (ProjectItem item in project.ProjectItems) + { + if (item.SubProject != null) + { + count += UpdateUsingStatementsInProjectTree(item.SubProject, oldNamespace, newNamespace); + } + } + return count; + } + + // Process actual project + if (!string.IsNullOrEmpty(project.FullName) && File.Exists(project.FullName)) + { + var projectDirectory = Path.GetDirectoryName(project.FullName); + if (!string.IsNullOrEmpty(projectDirectory) && Directory.Exists(projectDirectory)) + { + return UpdateUsingStatementsInDirectory(projectDirectory, oldNamespace, newNamespace); + } + } + + return 0; + } + + /// + /// Updates using statements in all .cs files within a directory. + /// + private static int UpdateUsingStatementsInDirectory(string directory, string oldNamespace, string newNamespace) + { + var csFiles = Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories); + var modifiedCount = 0; + + foreach (var filePath in csFiles) + { + if (UpdateUsingStatementsInFile(filePath, oldNamespace, newNamespace)) + { + modifiedCount++; + } + } + + return modifiedCount; + } + + /// + /// Updates using statements 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 UpdateUsingStatementsInFile(string filePath, string oldNamespace, string newNamespace) + { + var encoding = DetectEncoding(filePath); + var content = File.ReadAllText(filePath, encoding); + var originalContent = content; + + // Pattern for: using OldName; + // Also handles nested: using OldName.SubNamespace; + var usingPattern = $@"(\busing\s+){Regex.Escape(oldNamespace)}(\s*;|\.[\w.]*\s*;)"; + content = Regex.Replace(content, usingPattern, $"$1{newNamespace}$2"); + + // Pattern for using aliases: using Alias = OldName.Type; + // Also handles: using Alias = OldName; + var aliasPattern = $@"(\busing\s+\w+\s*=\s*){Regex.Escape(oldNamespace)}(\.[\w.]*)?(\s*;)"; + content = Regex.Replace(content, aliasPattern, $"$1{newNamespace}$2$3"); + + // Pattern for global using: global using OldName; + var globalUsingPattern = $@"(\bglobal\s+using\s+){Regex.Escape(oldNamespace)}(\s*;|\.[\w.]*\s*;)"; + content = Regex.Replace(content, globalUsingPattern, $"$1{newNamespace}$2"); + + // Pattern for using static: using static OldName.ClassName; + var staticUsingPattern = $@"(\busing\s+static\s+){Regex.Escape(oldNamespace)}(\.[\w.]+\s*;)"; + content = Regex.Replace(content, staticUsingPattern, $"$1{newNamespace}$2"); + + if (content != originalContent) + { + File.WriteAllText(filePath, content, encoding); + return true; + } + + return false; + } + /// /// Updates namespace declarations in all .cs files within the project directory. ///