Roslyn добавляет новый метод в существующий класс

Я исследую использование компилятора Roslyn в расширении Visual Studio (VSIX), которое использует VisualStudioWorkspace для обновления существующего кода. Потратив последние несколько дней на чтение этого, кажется, есть несколько способов достичь этого... Я просто не уверен, что это лучший подход для меня.

Итак, давайте предположим, что у пользователя есть свое решение, открытое в 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) на самом деле могут еще не существовать. Я очень новичок в этом, так что если вы можете предоставить примеры кода, это было бы потрясающе.

ОБНОВИТЬ

Я решил, что (возможно) лучшим подходом было бы просто вставить какой-то текст, а не пытаться программно создать объявление метода.

Я попробовал следующее, но в результате я не понял:

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) в Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer (IVsTextBuffer bufferAdapter) в Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor (IServiceProvider ServiceProvider, String Filepath, 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.CodeAnalry.W ApplyChanges(Solution newSolution) в Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges(Решение newSolution)

1 ответ

На вашем месте я бы воспользовался всеми преимуществами Roslyn, то есть я бы работал с SyntaxTree из Document вместо того, чтобы обрабатывать текстовые файлы (вы можете сделать последнее, не используя 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);
    }
}

Обратите внимание, что это довольно сырой код, Roslyn API чрезвычайно эффективен, когда дело доходит до анализа / обработки синтаксического дерева, получения информации о символах / ссылок и так далее. Я бы порекомендовал вам взглянуть на эту страницу и эту страницу для справки.

Другие вопросы по тегам