Подтвердить что ты не робот

Удалите посторонние точки с запятой в С# с помощью Roslyn - (замените пустые пустяки)

Я выяснил, как открыть решение, а затем выполнить итерацию через проекты, а затем документы. Я зациклился на том, как искать классы С#, Enums, Structs и Interfaces, которые могут иметь постороннюю точку с запятой в конце объявления (стиль С++). Я хотел бы удалить их и сохранить файлы .cs обратно на диск. В моей нынешней компании написано около 25 решений, с которыми я бы справился. Примечание. Причина, по которой мы это делаем, - продвижение вперед с лучшим набором стандартов кодирования. (И я хотел бы узнать, как использовать Roslyn для выполнения этих "простых" настроек)

Пример (ОБНОВЛЕНО):

class Program
{
    static void Main(string[] args)
    {
        string solutionFile = @"S:\source\dotnet\SimpleApp\SimpleApp.sln";
        IWorkspace workspace = Workspace.LoadSolution(solutionFile);
        var proj = workspace.CurrentSolution.Projects.First();
        var doc = proj.Documents.First();
        var root = (CompilationUnitSyntax)doc.GetSyntaxRoot();
        var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
        foreach (var decl in classes)
        {
            ProcessClass(decl);
        }
        Console.ReadKey();

    }

    private static SyntaxNode ProcessClass(ClassDeclarationSyntax node)
    {
        ClassDeclarationSyntax newNode;
        if (node.HasTrailingTrivia)
        {
            foreach (var t in node.GetTrailingTrivia())
            {
                var es = new SyntaxTrivia();
                es.Kind = SyntaxKind.EmptyStatement;
                // kind is readonly - what is the right way to create
                // the right SyntaxTrivia?
                if (t.Kind == SyntaxKind.EndOfLineTrivia)
                {
                    node.ReplaceTrivia(t, es);
                }
            }
            return // unsure how to do transform and return it
        }
    }

Пример кода, который я хочу преобразовать

using System;

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
};
// note: the semicolon at the end of the Person class
4b9b3361

Ответ 1

Вот небольшая программа, которая удаляет необязательную точку с запятой после всех описаний класса, структуры, интерфейса и enum-объявлений в рамках решения. Программа перебирает документы внутри решения и использует SyntaxWriter для перезаписи syntaxtree. Если какие-либо изменения были внесены, исходные файлы кода будут перезаписаны новым синтаксисом.

using System;
using System.IO;
using System.Linq;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace TrailingSemicolon
{
  class Program
  {
    static void Main(string[] args)
    {
      string solutionfile = @"c:\temp\mysolution.sln";
      var workspace = Workspace.LoadSolution(solutionfile);
      var solution = workspace.CurrentSolution;

      var rewriter = new TrailingSemicolonRewriter();

      foreach (var project in solution.Projects)
      {
        foreach (var document in project.Documents)
        {
          SyntaxTree tree = (SyntaxTree)document.GetSyntaxTree();

          var newSource = rewriter.Visit(tree.GetRoot());

          if (newSource != tree.GetRoot())
          {
            File.WriteAllText(tree.FilePath, newSource.GetText().ToString());
          }
        }
      }
    }

    class TrailingSemicolonRewriter : SyntaxRewriter
    {
      public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      private SyntaxNode RemoveSemicolon(SyntaxNode node,
                                         SyntaxToken semicolonToken,
                                         Func<SyntaxToken, SyntaxNode> withSemicolonToken)
      {
        if (semicolonToken.Kind != SyntaxKind.None)
        {
          var leadingTrivia = semicolonToken.LeadingTrivia;
          var trailingTrivia = semicolonToken.TrailingTrivia;

          SyntaxToken newToken = Syntax.Token(
            leadingTrivia,
            SyntaxKind.None,
            trailingTrivia);

          bool addNewline = semicolonToken.HasTrailingTrivia
            && trailingTrivia.Count() == 1
            && trailingTrivia.First().Kind == SyntaxKind.EndOfLineTrivia;

          var newNode = withSemicolonToken(newToken);

          if (addNewline)
            return newNode.WithTrailingTrivia(Syntax.Whitespace(Environment.NewLine));
          else
            return newNode;
        }
        return node;
      }
    }
  }
}

Надеюсь, это то, что вам нужно.

Ответ 2

Эта информация должна быть сохранена в ClassDeclaration node - так как, согласно спецификации С#, полуколонка является необязательным токеном в конце своих производств:

Класс декларирование: атрибуты opt class-modifiers opt partial opt идентификатор класса type-parameter-list opt        class-base opt type-parameter-constraints-clauses opt class-body ; opt

UPDATE

Согласно документации Roslyn, вы не можете изменить Синтаксические деревья - поскольку они являются неизменяемыми структурами. Вероятно, причина, по которой kind является только для чтения. Однако вы можете создать новое дерево, используя методы With*, определенные для каждого изменяемого свойства дерева, и используя ReplaceNode. В документации Roslyn есть хороший пример:

var root = (CompilationUnitSyntax)tree.GetRoot();
var oldUsing = root.Usings[1];
var newUsing = oldUsing.WithName(name); //changes the name property of a Using statement
root = root.ReplaceNode(oldUsing, newUsing);

Чтобы снова преобразовать новое дерево в код (ака довольно печатать), вы можете использовать метод GetText() из единицы компиляции node (в нашем примере переменная root).

Вы также можете расширить класс SyntaxRewriter для выполнения преобразований кода. Существует обширный пример для этого на официальном сайте Roslyn; взгляните на это конкретное пошаговое руководство. Следующие команды записывают преобразованное дерево обратно в исходный файл:

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
    File.WriteAllText(sourceTree.FilePath, newSource.GetFullText());
}

где rewriter является экземпляром SyntaxRewriter.