4

Я изучаю использование компилятора Roslyn в Visual Studio Extension (VSIX), который использует VisualStudioWorkspace для обновления существующего кода. Проведя последние несколько дней, прочитав об этом, похоже, есть несколько способов добиться этого ... Я просто не уверен, что это лучший подход для меня.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. Реализация. VsEditorAdaptersFactoryService.GetAdapter (буферный адаптер IVsTextBuffer) в Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer (IVsTextBuffer bufferAdapter)на Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor (IServiceProvider ServiceProvider, струнного Filepath, Boolean, Boolean 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» (к которому добавлена ​​дополнительная информация о исходном файле), вызывая 'SourceText.WithChanges (новый TextChange (...))', [см. Этот ответ] (http: //stackoverflow.com/a/37553697/155005). – m0sa

ответ

2

Если бы я тебя, я бы преимущество всех преимуществ Roslyn, т. е. я бы работал с SyntaxTreeDocument, а не обрабатывал текст файлов (вы можете сделать последнее без использования 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

Обнаружили это, пришлось добавить declation = declaration.WithSemicolonToken (SyntaxFactory.Token (SyntaxKind.SemicolonToken)); почему-то. Я думаю, у шаблона-строителя меньше ошибок? ;) – Arwin

Смежные вопросы