Search code examples
c#visual-studioroslynresxvsix

Move to Resource refactoring


I'm trying to make my own Move to Resource refactoring (like the one found in ReSharper). In my CodeAction.GetChangedDocumentAsync method I'm doing these 3 steps:

  1. Add my resource to the Resources.resx file with XmlDocument
  2. Use DTE to run the custom tool to update the Resources.Designer.cs file
  3. Replace the literal string by the qualified identifier of the new resource with SyntaxNode.ReplaceNode

Steps 1 & 2 are OK, but 3 does not work. If I remove the step 2, 3 is working.

I don't know if it is because I'm mixing Roslyn & DTE or if it's because step 2 generate new code in the solution and my cached context becomes invalid.

// Add the resource to the Resources.resx file
var xmlDoc = new XmlDocument();
xmlDoc.Load(resxPath);
XmlNode node = xmlDoc.SelectSingleNode($"//data[@name='{resourceIndentifierName}']");
if (node != null) return;
XmlElement dataElement = xmlDoc.CreateElement("data");

XmlAttribute nameAtt = xmlDoc.CreateAttribute("name");
nameAtt.Value = resourceIndentifierName;
dataElement.Attributes.Append(nameAtt);

XmlAttribute spaceAtt = xmlDoc.CreateAttribute("space", "xml");
spaceAtt.Value = "preserve";
dataElement.Attributes.Append(spaceAtt);

XmlElement valueElement = xmlDoc.CreateElement("value");
valueElement.InnerText = value;
dataElement.AppendChild(valueElement);

XmlNode rootNode = xmlDoc.SelectSingleNode("//root");
Debug.Assert(rootNode != null, "rootNode != null");
rootNode.AppendChild(dataElement);
xmlDoc.Save(resxPath);

// Update the Resources.Designer.cs file
var dte = (DTE2)Package.GetGlobalService(typeof(SDTE));
ProjectItem item = dte.Solution.FindProjectItem(resxPath);
((VSProjectItem) item.Object)?.RunCustomTool();

// Replace the node
SyntaxNode oldRoot = await context.Document.GetSyntaxRootAsync(cancellationToken)
  .ConfigureAwait(false);
SyntaxNode newRoot = oldRoot.ReplaceNode(oldNode, newNode);
return context.Document.WithSyntaxRoot(newRoot);

Solution

  • it's because step 2 generate new code in the solution and my cached context becomes invalid

    This is what is happening. When you call RunCustomTool the visual studio file watcher api tells roslyn that the files have been updated and roslyn generates a new set of solution snapshots. When you attempt to apply your codefix, roslyn looks at the solution snapshot that your codefix came from, sees that it does not match the current snapshot, and fails to apply it.