diff --git a/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj b/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj
index e85f232..e409917 100644
--- a/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj
+++ b/src/CodingWithCalvin.ProjectRenamifier/CodingWithCalvin.ProjectRenamifier.csproj
@@ -71,6 +71,7 @@
+
True
diff --git a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs
index dd6fef9..37c07df 100644
--- a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs
+++ b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs
@@ -80,6 +80,10 @@ private void RenameProject(Project project, DTE2 dte)
var newName = dialog.NewProjectName;
var projectFilePath = project.FullName;
+ // Collect projects that reference this project before removal
+ var referencingProjects = ProjectReferenceService.FindProjectsReferencingTarget(dte.Solution, projectFilePath);
+ var oldProjectFilePath = projectFilePath;
+
// Remove project from solution before file operations
dte.Solution.Remove(project);
@@ -95,12 +99,14 @@ private void RenameProject(Project project, DTE2 dte)
// Rename parent directory if it matches the old project name
projectFilePath = ProjectFileService.RenameParentDirectoryIfMatches(projectFilePath, currentName, newName);
+ // Update references in projects that referenced this project
+ ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath);
+
// Re-add project to solution with new path
dte.Solution.AddFromFile(projectFilePath);
// TODO: Implement remaining rename operations
// See open issues for requirements:
- // - #23: Update project references
// - #9: Update using statements across solution
// - #11: Solution folder support
// - #12: Progress indication
diff --git a/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs b/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs
new file mode 100644
index 0000000..5c03ae5
--- /dev/null
+++ b/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using EnvDTE;
+
+namespace CodingWithCalvin.ProjectRenamifier.Services
+{
+ ///
+ /// Service for managing project references during rename operations.
+ ///
+ internal static class ProjectReferenceService
+ {
+ ///
+ /// Finds all projects in the solution that reference the specified project.
+ ///
+ /// The solution to search.
+ /// The full path to the project being renamed.
+ /// A list of project paths that reference the target project.
+ public static List FindProjectsReferencingTarget(Solution solution, string targetProjectPath)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ var referencingProjects = new List();
+ var targetFileName = Path.GetFileName(targetProjectPath);
+
+ foreach (Project project in solution.Projects)
+ {
+ FindReferencesInProject(project, targetProjectPath, targetFileName, referencingProjects);
+ }
+
+ return referencingProjects;
+ }
+
+ ///
+ /// Recursively searches a project (and solution folders) for references to the target.
+ ///
+ private static void FindReferencesInProject(Project project, string targetProjectPath, string targetFileName, List referencingProjects)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ if (project == null)
+ {
+ return;
+ }
+
+ // Handle solution folders
+ if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems)
+ {
+ foreach (ProjectItem item in project.ProjectItems)
+ {
+ if (item.SubProject != null)
+ {
+ FindReferencesInProject(item.SubProject, targetProjectPath, targetFileName, referencingProjects);
+ }
+ }
+ return;
+ }
+
+ // Skip the target project itself
+ if (string.Equals(project.FullName, targetProjectPath, System.StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ // Check if this project references the target
+ if (!string.IsNullOrEmpty(project.FullName) && File.Exists(project.FullName))
+ {
+ if (ProjectReferencesTarget(project.FullName, targetFileName))
+ {
+ referencingProjects.Add(project.FullName);
+ }
+ }
+ }
+
+ ///
+ /// Checks if a project file contains a reference to the target project.
+ ///
+ private static bool ProjectReferencesTarget(string projectFilePath, string targetFileName)
+ {
+ var doc = new XmlDocument();
+ doc.Load(projectFilePath);
+
+ var namespaceManager = new XmlNamespaceManager(doc.NameTable);
+ var msbuildNs = "http://schemas.microsoft.com/developer/msbuild/2003";
+ var hasNamespace = doc.DocumentElement?.NamespaceURI == msbuildNs;
+
+ XmlNodeList nodes;
+ if (hasNamespace)
+ {
+ namespaceManager.AddNamespace("ms", msbuildNs);
+ nodes = doc.SelectNodes("//ms:ProjectReference", namespaceManager);
+ }
+ else
+ {
+ nodes = doc.SelectNodes("//ProjectReference");
+ }
+
+ if (nodes == null)
+ {
+ return false;
+ }
+
+ foreach (XmlNode node in nodes)
+ {
+ var includeAttr = node.Attributes?["Include"]?.Value;
+ if (!string.IsNullOrEmpty(includeAttr))
+ {
+ var referencedFileName = Path.GetFileName(includeAttr);
+ if (string.Equals(referencedFileName, targetFileName, System.StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Updates project references in all projects that referenced the old project path.
+ ///
+ /// Projects that need their references updated.
+ /// The old path to the renamed project.
+ /// The new path to the renamed project.
+ public static void UpdateProjectReferences(List referencingProjectPaths, string oldProjectPath, string newProjectPath)
+ {
+ var oldFileName = Path.GetFileName(oldProjectPath);
+
+ foreach (var projectPath in referencingProjectPaths)
+ {
+ UpdateReferencesInProject(projectPath, oldFileName, oldProjectPath, newProjectPath);
+ }
+ }
+
+ ///
+ /// Updates references in a single project file.
+ ///
+ private static void UpdateReferencesInProject(string projectFilePath, string oldFileName, string oldProjectPath, string newProjectPath)
+ {
+ var doc = new XmlDocument();
+ doc.PreserveWhitespace = true;
+ doc.Load(projectFilePath);
+
+ var namespaceManager = new XmlNamespaceManager(doc.NameTable);
+ var msbuildNs = "http://schemas.microsoft.com/developer/msbuild/2003";
+ var hasNamespace = doc.DocumentElement?.NamespaceURI == msbuildNs;
+
+ XmlNodeList nodes;
+ if (hasNamespace)
+ {
+ namespaceManager.AddNamespace("ms", msbuildNs);
+ nodes = doc.SelectNodes("//ms:ProjectReference", namespaceManager);
+ }
+ else
+ {
+ nodes = doc.SelectNodes("//ProjectReference");
+ }
+
+ if (nodes == null)
+ {
+ return;
+ }
+
+ var modified = false;
+ var projectDirectory = Path.GetDirectoryName(projectFilePath);
+
+ foreach (XmlNode node in nodes)
+ {
+ var includeAttr = node.Attributes?["Include"];
+ if (includeAttr == null || string.IsNullOrEmpty(includeAttr.Value))
+ {
+ continue;
+ }
+
+ var referencedFileName = Path.GetFileName(includeAttr.Value);
+ if (!string.Equals(referencedFileName, oldFileName, System.StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ // Calculate new relative path from referencing project to renamed project
+ var newRelativePath = GetRelativePath(projectDirectory, newProjectPath);
+ includeAttr.Value = newRelativePath;
+ modified = true;
+ }
+
+ if (modified)
+ {
+ doc.Save(projectFilePath);
+ }
+ }
+
+ ///
+ /// Gets a relative path from one directory to a file.
+ ///
+ private static string GetRelativePath(string fromDirectory, string toFile)
+ {
+ var fromUri = new System.Uri(fromDirectory.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar);
+ var toUri = new System.Uri(toFile);
+
+ var relativeUri = fromUri.MakeRelativeUri(toUri);
+ var relativePath = System.Uri.UnescapeDataString(relativeUri.ToString());
+
+ return relativePath.Replace('/', Path.DirectorySeparatorChar);
+ }
+ }
+}