2016-06-08 47 views
4

我正在研究在使用VisualStudioWorkspace更新現有代碼的Visual Studio擴展(VSIX)中使用Roslyn編譯器。花了幾天的時間閱讀這篇文章,似乎有幾種方法可以實現這一目標......我只是不確定哪種方法適合我。Roslyn將新方法添加到現有類

好了,讓我們假設用戶有他們的解決方案在Visual Studio 2015年開放,他們點擊我的推廣和(通過一種形式),他們告訴我,他們希望下面的方法定義添加到一個接口:

GetSomeDataResponse GetSomeData(GetSomeDataRequest request); 

他們還告訴我接口的名稱,它是ITheInterface

界面中已經有一些代碼:

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

好了,這樣我就可以使用加載接口文檔如下:

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

那麼,什麼是現在添加的最佳方式我的新方法到這個現有的接口,最好是在XML評論(三斜槓評論)寫?請記住請求和響應類型(GetSomeDataRequest和GetSomeDataResponse)可能實際上還不存在。我對此很新,所以如果你可以提供代碼示例,那就太棒了。

UPDATE

我決定(可能)最好的辦法是簡單地在一些文字注入,而不是試圖以編程方式建立的方法聲明。

我嘗試以下,但結束了一個例外,我不理解:

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("}"); 

檢查這一點,這一切都很好(我真正的代碼添加格式和XML註釋,但去除,爲了清楚)。

因此,知道這些是不可變的,我試圖挽救它,如下所示:

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

但是,這創造了以下異常:

bufferAdapter is not a VsTextDocData 

在Microsoft.VisualStudio.Editor。 Implementation.VsEditorAdaptersFactoryService.GetAdapter(IVsTextBuffer bufferAdapter) at Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer(IVsTextBuffer bufferAdapter) 在Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor(的IServiceProvider的ServiceProvider,字符串文件路徑,布爾needsSave,布爾needsUndoDisabled) 在Microsoft.VisualStudio.LanguageServices.RoslynVisualStudioWorkspace.OpenInvisibleEditor(IVisualStudioHostDocument hostDocument) 在Microsoft.VisualStudio。 LanguageServices.Implementation.ProjectSystem.DocumentProvider.StandardTextDocument.UpdateText(SourceText newText) 在Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.ApplyDocumentTextChanged(documentId documentId,SourceText newText) 在Microsoft.CodeAnalysis.Workspace。ApplyProjectChanges(ProjectChanges projectChanges) 在Microsoft.CodeAnalysis.Workspace.TryApplyChanges(解決方案newSolution) 在Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges(解決方案newSolution)

+0

您可能需要通過調用'SourceText.WithChanges(new TextChange(...))'來更改現有的'SourceText'(它附加了附加的源文件信息),[請參閱本答案](http: //stackoverflow.com/a/37553697/155005)爲例。 – m0sa

回答

2

如果我是你,我會採取所有Roslyn優勢的優勢,即我將與DocumentSyntaxTree一起工作,而不是處理文件文本(你可以在不使用Roslyn的情況下完成後者)。

例如:

... 
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); 
    } 
} 

請注意,這是相當原始代碼,羅斯林API是非常強大的,當涉及到分析/處理語法樹,獲取符號信息/引用等等。我建議你看看這個page和這個page作爲參考。

+0

哇。你當然是對的。以這種方式注入包含具有許多模板代碼行的具體實現的代碼文件的想法可能需要一些時間來構建。我想我可以加載一個字符串模板並將其解析爲語法樹,然後將該「子」樹插入到現有樹中。只是去解決如何做到這一點..... – DrGriff

+0

這真的幫了我。但是,由於某種原因不添加分號,仍然試圖弄清楚。 – Arwin

+0

發現它,不得不添加 declaration = declaration.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));由於某種原因,我們提供了 。我猜建築師模式有更少的錯誤? ;) – Arwin