2016-12-07 6 views
1

фон:Roslyn - Как я могу заменить несколько узлов на несколько узлов?

Использования Рослин с C#, я пытаюсь расширить авто Реализуемого свойства, так что аксессоры тело может иметь код впрыскивается путем последующей обработки. Я использую StackExchange.Precompilation как компилятор, поэтому эти синтаксические преобразования происходят в конвейере сборки, а не как часть анализатора или рефакторинга.

Я хочу, чтобы превратить это:

[SpecialAttribute] 
int AutoImplemented { get; set; } 

в этом:

[SpecialAttribute] 
int AutoImplemented { 
    get { return _autoImplemented; } 
    set { _autoImplemented = value; } 
} 

private int _autoImplemented; 

Проблема:

Я был в состоянии получить простые преобразования рабочих, но Я застрял на авто-свойствах, а некоторые другие, похожие на некоторые w AYS. Проблема, с которой я столкнулась, заключается в правильном использовании методов расширения SyntaxNodeExtensions.ReplaceNode и SyntaxNodeExtensions.ReplaceNodes при замене нескольких узлов на дерево.

Я использую класс, расширяющий CSharpSyntaxRewriter для преобразований. Я просто расскажу о соответствующих членах этого класса. Этот класс посещает каждое объявление class и struct, а затем заменяет любые объявления свойств, которые помечены как SpecialAttribute.

private readonly SemanticModel model; 

public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { 
    if (node == null) throw new ArgumentNullException(nameof(node)); 
    node = VisitMembers(node); 
    return base.VisitClassDeclaration(node); 
} 

public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { 
    if (node == null) throw new ArgumentNullException(nameof(node)); 
    node = VisitMembers(node); 
    return base.VisitStructDeclaration(node); 
} 

private TNode VisitMembers<TNode>(TNode node) 
    where TNode : SyntaxNode { 

    IEnumerable<PropertyDeclarationSyntax> markedProperties = 
     node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Where(prop => prop.HasAttribute<SpecialAttribute>(model)); 

    foreach (var prop in markedProperties) { 
     SyntaxList<SyntaxNode> expanded = ExpandProperty(prop); 
     //If I set a breakpoint here, I can see that 'expanded' will hold the correct value. 
     //ReplaceNode appears to not be replacing anything 
     node = node.ReplaceNode(prop, expanded); 
    } 

    return node; 
} 

private SyntaxList<SyntaxNode> ExpandProperty(PropertyDeclarationSyntax node) { 
    //Generates list of new syntax elements from original. 
    //This method will produce correct output. 
} 

HasAttribute<TAttribute> является метод расширения я определил для PropertyDeclarationSyntax, который проверяет, если это свойство имеет атрибут данного типа. Этот метод работает правильно.


Я считаю, что я просто не использую ReplaceNode правильно. Есть три родственные методы:

TRoot ReplaceNode<TRoot>(
    TRoot root, 
    SyntaxNode oldNode, 
    SyntaxNode newNode); 

TRoot ReplaceNode<TRoot>(
    TRoot root, 
    SyntaxNode oldNode, 
    IEnumerable<SyntaxNode> newNodes); 

TRoot ReplaceNodes<TRoot, TNode>(
    TRoot root, 
    IEnumerable<TNode> nodes, 
    Func<TNode, TNode, SyntaxNode> computeReplacementNode); 

Я использую второй, потому что мне нужно заменить каждый узел свойств с обеих полей и свойств узлов. Мне нужно сделать это со многими узлами, но нет перегрузки ReplaceNodes, что позволяет заменять узел «один ко многим». Единственный способ, с которым я столкнулся в этой перегрузке, - использовать цикл foreach, который кажется очень «императивным» и против функционального ощущения API Roslyn.

Есть ли лучший способ выполнять пакетные преобразования, подобные этому?


Update: Я нашел большой серии блога на Рослин и дело с его неизменности. Я еще не нашел точного ответа, но это похоже на хорошее место для начала. https://joshvarty.wordpress.com/learn-roslyn-now/


Update: Так вот где я действительно путают. Я знаю, что API Roslyn основан на неизменяемых структурах данных, и проблема здесь заключается в тонкости того, как копирование структур используется для имитации изменчивости.Я думаю, проблема в том, что каждый раз, когда я заменяю узел в моем дереве, у меня тогда есть новое дерево, и поэтому, когда я вызываю ReplaceNode, это дерево якобы не содержит мой первоначальный узел, который я хочу заменить.

Я понимаю, что способ копирования деревьев в Roslyn заключается в том, что при замене узла в дереве вы фактически создаете новое дерево, которое ссылается на все те же узлы исходного дерева, за исключением узла, который вы заменили, и все узлы непосредственно над этим. Узлы под замененным узлом могут быть удалены, если замещающий узел больше не ссылается на них, или новые ссылки могут быть добавлены, но все старые ссылки по-прежнему указывают на те же экземпляры узлов, что и раньше. Я вполне уверен, что это именно то, что Андерс Хейлсберг описывает в this interview на Roslyn (от 20 до 23 минут).

Не должен ли мой новый экземпляр node содержать те же prop экземпляры, которые были найдены в моей первоначальной последовательности?


Hacky решение для особых случаев:

я, наконец, смог получить эту конкретную проблему трансформации декларации собственности на работу, опираясь на идентификаторы собственности, которые не будут меняться в любом дереве преобразований. Тем не менее, мне все равно понравится общее решение для замены нескольких узлов на несколько узлов. Это решение действительно работает над API, а не через него.

Вот частный случай решения:

private TNode VisitMembers<TNode>(TNode node) 
    where TNode : SyntaxNode { 

    IEnumerable<PropertyDeclarationSyntax> markedPropertyNames = 
     node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Where(prop => prop.HasAttribute<SpecialAttribute>(model)) 
      .Select(prop => prop.Identifier.ValueText); 

    foreach (var prop in markedPropertyNames) { 
     var oldProp = node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Single(p => p.Identifier.ValueText == prop.Name); 

     SyntaxList<SyntaxNode> newProp = ExpandProperty(oldProp); 

     node = node.ReplaceNode(oldProp, newProp); 
    } 

    return node; 
} 

Другая подобная проблема я работаю с модифицирует все return заявления в способе вставки Постусловие проверки. Этот случай не может, очевидно, полагаться на любой уникальный идентификатор, такой как объявление свойства.

+0

Кто является 'm', который вы отправляете на' ReplaceNode'? Вы уверены, что существует в 'node'? И почему вы не посещаете jusrt 'PropertyDeclarationSyntax'? –

+0

К сожалению, это должно было быть 'prop', оно было названо' m' в моем исходном коде, но я изменил его на 'prop', чтобы быть более читаемым здесь. Причина, по которой я не посещаю объявления свойств, заключается в том, что мне нужно заменить объявления свойств как новым объявлением свойства, так и объявлением поля. Я не думаю, что вы можете заменить несколько узлов на один узел при посещении этого одного узла, вам нужно сделать это при посещении родительского узла (объявление типа). – JamesFaix

+0

Я считаю, что ошибка в использовании методов «Заменить». Результат, полученный из 'Replace', не имеет никаких изменений. Я полагаю, что из-за копирования неизменяемых структур данных у меня есть ссылка на другое дерево, чем когда я создал перечисление «помеченные объекты». Я не могу понять, как сделать повторные замены таким образом, когда моя ссылка на дерево продолжает меняться. – JamesFaix

ответ

2

Когда вы сделаете это:

foreach (var prop in markedProperties) { 
    SyntaxList<SyntaxNode> expanded = ExpandProperty(prop); 
    //If I set a breakpoint here, I can see that 'expanded' will hold the correct value. 
    //ReplaceNode appears to not be replacing anything 
    node = node.ReplaceNode(prop, expanded); 
} 

После первой замены, node (ваш class, например) не содержит оригинальное свойства больше.

В Roslyn все неизменно, поэтому первая замена должна работать для вас, и у вас есть новое дерево \ node.

Чтобы сделать его работу вы можете рассмотреть один из следующих вариантов:

  • Построить результат в классе ReWriter, без изменения исходного дерева, и когда вы отделка, заменить все сразу. В вашем случае его среднее значение заменяет ноту class сразу. Я думаю, что это хороший вариант, когда вы хотите заменить оператор (я использовал его, когда я написал код для преобразования запроса linq (понимание) в свободный синтаксис), но для всех классов, возможно, это не оптимально.
  • Используйте SyntaxAnnotaion \ TrackNodes, чтобы найти узел после изменения дерева. С помощью этих параметров вы можете изменить дерево по своему усмотрению, и вы все равно можете отслеживать старые узлы в новом дереве.
  • Используйте DocumentEditor, чтобы вы могли выполнять несколько изменений в документе, а затем возвращать новый документ.

Если вам нужен пример для одного из них, дайте мне знать.

+0

Я думал о чем-то вроде строителя в вашем первом варианте. Будем ли я начинать с пустого дерева, а затем добавлять узлы (или обработанные узлы) из оригинала до тех пор, пока копия не станет полным деревом? Раньше я не слышал об «SyntaxAnnotation»; это кажется многообещающим. Что касается 'Document', то метод' StackExchange.Precompilation' перехватывает компилятор с помощью 'CSharpSyntaxRewriter'. Использует ли «Документ» более широкий охват? – JamesFaix

+0

Я только что нашел ваш блог; очень полезный материал. Недавно я начал нырять в Рослин и Сесил. – JamesFaix

+0

@JamesFaix Спасибо :) Здесь вы можете увидеть пример использования первого метода, который я использовал для преобразования LINQ. SyntaxAnnotions и TrackNodes очень просты в использовании. Я считаю, что вы можете найти полезную информацию в блоге [Josh Varty] (https://joshvarty.wordpress.com/learn-roslyn-now/). И если у вас есть вопрос, спросите здесь или в новом вопросе. –