2016-06-08 18 views
4

Ich untersuche die Verwendung des Roslyn-Compilers innerhalb einer Visual Studio-Erweiterung (VSIX), die den VisualStudioWorkspace verwendet, um vorhandenen Code zu aktualisieren. Nachdem ich die letzten Tage damit verbracht habe, dies zu lesen, scheint es verschiedene Wege zu geben, dies zu erreichen. Ich bin mir nicht sicher, welcher der beste Ansatz für mich ist.Roslyn fügen neue Methode zu einer vorhandenen Klasse hinzu

Okay, so nehmen wir an, dass die Benutzer ihre Lösung hat offen in Visual Studio 2015. Sie klicken auf meine Erweiterung und (über ein Formular) sie mir sagen, dass sie die folgende Methode Definition einer Schnittstelle hinzufügen möchten:

GetSomeDataResponse GetSomeData(GetSomeDataRequest request); 

Sie sagen mir auch den Namen der Schnittstelle, es ist ITheInterface.

Die Schnittstelle hat bereits einige Code in es:

namespace TheProjectName.Interfaces 
{ 
    using System; 
    public interface ITheInterface 
    { 
     /// <summary> 
     /// A lonely method. 
     /// </summary> 
     LonelyMethodResponse LonelyMethod(LonelyMethodRequest request); 
    } 
} 

Okay, also ich kann die Schnittstelle Dokument laden folgendes mit:

Document myInterface = this.Workspace.CurrentSolution?.Projects? 
    .FirstOrDefault(p 
     => p.Name.Equals("TheProjectName")) 
    ?.Documents? 
     .FirstOrDefault(d 
      => d.Name.Equals("ITheInterface.cs")); 

Also, was ist der beste Weg, jetzt hinzufügen meine neue Methode zu dieser bestehenden Schnittstelle, idealerweise auch in den XML-Kommentar (Triple-Slash-Kommentar) schreiben? Beachten Sie, dass die Anfrage- und Antworttypen (GetSomeDataRequest und GetSomeDataResponse) möglicherweise noch nicht vorhanden sind. Ich bin sehr neu, also, wenn Sie Code-Beispiele zur Verfügung stellen können, dann wäre das grandios.

UPDATE

ich, dass (wahrscheinlich) der beste Ansatz einfach entschieden werden würde in einem Text zu injizieren, anstatt zu versuchen, die Methode Erklärung programmatisch aufzubauen.

habe ich versucht, die folgend, aber mit einer Ausnahme endete, dass ich nicht begreifen: Die

SourceText sourceText = await myInterface.GetTextAsync(); 
string text = sourceText.ToString(); 
var sb = new StringBuilder(); 

// I want to all the text up to and including the last 
// method, but without the closing "}" for the interface and the namespace 
sb.Append(text.Substring(0, text.LastIndexOf("}", text.LastIndexOf("}") - 1))); 

// Now add my method and close the interface and namespace. 
sb.AppendLine("GetSomeDataResponse GetSomeData(GetSomeDataRequest request);"); 
sb.AppendLine("}"); 
sb.AppendLine("}"); 

Inspizieren, es ist alles gut (mein wirklicher Code fügt Kommentare Formatierung und XML, aber entfernt, dass aus Gründen der Klarheit).

So wohl wissend, dass diese sind unveränderlich, habe ich versucht, es zu speichern, wie folgt:

var updatedSourceText = SourceText.From(sb.ToString()); 
var newInterfaceDocument = myInterface.WithText(updatedSourceText); 
var newProject = newInterfaceDocument.Project; 
var newSolution = newProject.Solution; 
this.Workspace.TryApplyChanges(newSolution); 

Aber diese erstellt die folgende Ausnahme:

bufferAdapter is not a VsTextDocData 

bei Microsoft.VisualStudio.Editor. Implementation.VsEditorAdaptersFactoryService.GetAdapter (IVsTextBuffer bufferAdapter) bei Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer (IVsTextBuffer bufferAdapter) bei Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor (IServiceProvider serviceprovider, String filePath, Boolean needsSave, Boolean needsUndoDisabled) bei Microsoft.VisualStudio.LanguageServices.RoslynVisualStudioWorkspace.OpenInvisibleEditor (IVisualStudioHostDocument hostDocument) bei Microsoft.VisualStudio. LanguageServices.Implementation.ProjectSystem.DocumentProvider.StandardTextDocument.UpdateText (SourceText newText) bei Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.ApplyDocumentTextChanged (DocumentId DocumentID, SourceText newText) bei Microsoft.CodeAnalysis.Workspace.ApplyProjectChanges (ProjectChanges projectChanges) bei Microsoft.CodeAnalysis.Workspace.TryApplyChanges (Lösung newSolution) bei Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges (Lösung newSolution)

+0

Wahrscheinlich müssen Sie den vorhandenen 'SourceText' (an den zusätzliche Quelldateiinformationen angehängt sind) ändern, indem Sie' SourceText.WithChanges (new TextChange (...)) 'aufrufen, [siehe diese Antwort] (http: //stackoverflow.com/a/37553697/155005) für ein Beispiel. – m0sa

Antwort

2

Wenn ich Sie wäre, würde ich nehmen Vorteil aller Roslyn-Vorteile, dh ich würde mit der SyntaxTree der Document lieber als die Verarbeitung der Dateien Text (Sie können die letztere tun, ohne Roslyn überhaupt zu verwenden).

Zum Beispiel:

... 
SyntaxNode root = await document.GetSyntaxRootAsync().ConfigureAwait(false); 
var interfaceDeclaration = root.DescendantNodes(node => node.IsKind(SyntaxKind.InterfaceDeclaration)).FirstOrDefault() as InterfaceDeclarationSyntax; 
if (interfaceDeclaration == null) return; 

var methodToInsert= GetMethodDeclarationSyntax(returnTypeName: "GetSomeDataResponse ", 
      methodName: "GetSomeData", 
      parameterTypes: new[] { "GetSomeDataRequest" }, 
      paramterNames: new[] { "request" }); 
var newInterfaceDeclaration = interfaceDeclaration.AddMembers(methodToInsert); 

var newRoot = root.ReplaceNode(interfaceDeclaration, newInterfaceDeclaration); 

// this will format all nodes that have Formatter.Annotation 
newRoot = Formatter.Format(newRoot, Formatter.Annotation, workspace); 
workspace.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution); 
... 

public MethodDeclarationSyntax GetMethodDeclarationSyntax(string returnTypeName, string methodName, string[] parameterTypes, string[] paramterNames) 
{ 
    var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(GetParametersList(parameterTypes, paramterNames))); 
    return SyntaxFactory.MethodDeclaration(attributeLists: SyntaxFactory.List<AttributeListSyntax>(), 
        modifiers: SyntaxFactory.TokenList(), 
        returnType: SyntaxFactory.ParseTypeName(returnTypeName), 
        explicitInterfaceSpecifier: null, 
        identifier: SyntaxFactory.Identifier(methodName), 
        typeParameterList: null, 
        parameterList: parameterList, 
        constraintClauses: SyntaxFactory.List<TypeParameterConstraintClauseSyntax>(), 
        body: null, 
        semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken)) 
      // Annotate that this node should be formatted 
      .WithAdditionalAnnotations(Formatter.Annotation); 
} 

private IEnumerable<ParameterSyntax> GetParametersList(string[] parameterTypes, string[] paramterNames) 
{ 
    for (int i = 0; i < parameterTypes.Length; i++) 
    { 
     yield return SyntaxFactory.Parameter(attributeLists: SyntaxFactory.List<AttributeListSyntax>(), 
               modifiers: SyntaxFactory.TokenList(), 
               type: SyntaxFactory.ParseTypeName(parameterTypes[i]), 
               identifier: SyntaxFactory.Identifier(paramterNames[i]), 
               @default: null); 
    } 
} 

Beachten Sie, dass diese ziemlich Rohcode ist, ist Roslyn API extrem leistungsfähig, wenn es um die Analyse/Verarbeitung der Baum Syntax kommt, bekommen Symbolinformation/Referenzen und so weiter. Ich würde Ihnen empfehlen, dieses page und dieses page als Referenz anzusehen.

+0

Wow. Du hast natürlich Recht. Die Idee, eine Codedatei zu injizieren, die die konkrete Implementierung enthält, die auf diese Weise viele Zeilen des Vorlagencodes enthält, könnte einige Zeit zum Konstruieren benötigen. Ich denke, ich könnte eine String-Vorlage laden und diese in einem Syntaxbaum analysieren und dann diesen "Unterbaum" in einen existierenden Baum einfügen. Geh einfach um herauszufinden, wie das geht ... – DrGriff

+0

Das hat mir wirklich geholfen. Ein Semikolon wird jedoch aus irgendeinem Grund nicht hinzugefügt, immer noch versucht, das herauszufinden. – Arwin

+0

Gefunden, musste hinzufügen deklaration = deklaration.WithSemicolonToken (SyntaxFactory.Token (SyntaxKind.SemicolonToken)); aus irgendeinem Grund. Ich denke, das Builder-Muster hat weniger Bugs? ;) – Arwin