2016-06-08 31 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