Wednesday, February 26, 2014

Введение в Roslyn

В этой статье поговорим об использовании компилятора как сервиса. Мы рассмотрим проект, над которым сейчас трудится компания Microsoft, под названием "Roslyn". Наконец-то у меня дошли руки, чтобы испробовать это чудо от первого лица и описать свои ощущения об этом проекте. В современном мире разработки программного обеспечения сложно раскрыть свои возможности и знания. Поэтому попробуем вместе окунуться в мир разработки ПО вместе с Roslyn.  С момента написания строчек выше прошло больше месяца, и только сейчас я поборол свой страх и решил снова вернуться к анализу Roslyn. Возможно, столь значительному перерыву "посодействовал" тот факт, что во время подготовки к написанию материала мой мозг работал не так активно, как обычно, из-за затянувшегося бронхита. 
Для понимания принципов работы Roslyn не нужно быть гуру в программировании на языке C#; для начала использования компилятора как сервиса в своих программах достаточно соответствующих базисных знаний. На момент описания статьи Roslyn доступен для предварительного просмотра, и его уже можно "пощупать". Не стоит спешить сразу включать его в свои проекты, так как окончательная версия, которую планируют, по слухам, выпустить в текущем 2014 году может значительно измениться. Но в случае с проектом Roslyn, это попытка упростить себе будущее, так как на данный момент это основной проект, над которым работает компания Microsoft. В рамках выхода в мир языка C# 6, будем надеяться, что компания Microsoft выпустит уже и Roslyn
Перейдем немного к теории и рассмотрим основные части, из которых состоит Roslyn.
Для начала опишу основную идею проекта Roslyn. Все мы знаем, что компилятор являет собой некий черный ящик, о реализации которого неизвестно и которому мы передаем наши файлы кода с одной стороны, а после некоторой "магии" со стороны компилятора мы получаем на выходе либо исполняемые файлы, либо библиотеки или другие файлы, в зависимости от компилятора и его возможностей. Основная миссия, которую на себя взял проект Roslyn, – это приоткрытые завесы тайны компиляторов и доступность возможностей этих компиляторов для взаимодействия с конечными пользователями и инструментами разработки через Ваш код. То есть сейчас Вы можете сами построить дерево, с которым будет работать Ваш компилятор, добавить разную валидацию для своей среды разработки и т.д. Единственное, что может Вас ограничивать, – это Ваша фантазия, и пока неполная поддержка некоторых ключевых возможностей языка C# (например, Roslyn пока не дружит с dynamic, async/await). Но, как обещает команда разработки, это все будет учтено в следующей версии. Проект Roslyn предоставляет Вам как потребителю анализ кода компилятора, таких языков как C# и Visual Basic предоставляя слой API, который отражает традиционный конвейер компилятора. Вот как это выглядит:
Это схема с документации компании Microsoft к Roslyn. На рисунке показаны четыре фазы конвейера (к сожалению, я не знаю, как корректно в данном контексте перевести слово pipeline, которое используется во всех примерах). Каждая фаза этого конвейера является отдельным компонентом. В первой фазе происходит разбор источника на лексемы со сбором в синтаксические инструкции с помощью языковой грамматики. Вторая фаза,  это фаза объявления, в которой импорт метаданных анализируются для формирования именованных символов. Третья фаза является фазой привязки, где идентификаторы в коде сопоставляются с символами. И последней выделяют фазу, где вся информация, построенная  компилятором, собирается в сборку (assembly).
Компилятор как сервис по использованию API разделяется на 4 части.
  • Compiler APIs
  • Scripting APIs
  • Workspace APIs
  • Services APIs
Compiler APIs отвечает за процесс сборки, делает семантический и синтаксический анализ кода.
Scripting APIs позволяет использовать возможности языка C# для написания скриптов.
Workspace APIs – набор APIs, позволяющий работать с такими сущностями, как проект и решение (project and solution).
Services APIs – это набор APIs, позволяющий расширять возможности Visual Studio (refactoring, code issue и другие).
В данной статье рассмотрим пример, который предоставляет компания Microsoft под названием How to Write a Quick Fix, который построен на использовании Service APIs. Мы создадим проект Code Issue, в котором с помощью extension для Visual Studio мы будем предлагать заметить обычное объявление переменной на константу , если это возможно.
int x = 0;
Console.WriteLine(x);
Заменим на
const int x = 0;
Console.WriteLine(x);
Создадим новое приложение Code Issue и назовем его FirstQuickFix.
Интересный момент с выбором 4-го фремворка как в примере, у меня в Visual Studio 2012. Для того чтобы ошибки не выпадали, пришлось выставить .NET Framework 4.5.  После выполненных действий нам необходимо перейти в файл source.extension.vsixmanifest.
Затем в этом файле находим строчку и изменяем параметр MinVersion на 4.0.
В документации Вы этого не найдете, так как все примеры были написаны под 2010 студию, возможно, там не нужно было делать таких "телодвижений". Пример, который создан по умолчанию, создает расширения для студии, где будет делать подсказку, если в методе есть буква "a". Если Вы запустите свой проект в дебаг-режиме, то у Вас запустится еще один экземпляр Visual Studio 2012, в котором Вы сможете создать простое консольное приложение и посмотреть результат.
Я назвал тестовый проект "TestRoslyn", а Вы по желанию можете дать ему другое название.
Подведите курсор к любому ключевому слову, где есть буква "a", и посмотрите на результат. Как по мне, то выглядит довольно эффектно. Если у Вас стоит решарпер, то придумать что-то новое будет сложно, но зато Вы сейчас можете написать основные экстеншены сами. Давайте разберем, как это работает.
С использованием Code Issue Provider связано несколько особенностей.
Каждая реализация ICodeIssueProvider должна предоставлять, по крайней мере, один атрибут Export, который описывает важные детали о поставщике.
Каждый Code Issue Provider должен реализовывать интерфейс ICodeIssueProvider. Этот интерфейс содержит три перегруженных метода, называемые GetIssues, каждый из которых работает с syntax nodes, tokens or trivia. Атрибут Export определяет, какие из этих методов будут вызываться.
Еще один интересный факт – это то, что те примеры, которые Вы можете загрузить с MSDN, не будут работать. На момент написания статьи команда, которая работает на Roslyn, уже изменила логику работы некоторых составляющих, но не затронула при этом примеры. Лучше смотрите те примеры, которые Вам установятся вместе с Roslyn под Вашу студию. Они актуальные и содержат последние изменения. 
Для начала удалим код, который использовался для поиска буквы "a", и немного его изменим. Начальный код должен иметь такой вид:
[ExportCodeIssueProvider("FirstQuickFix", LanguageNames.CSharp)]
class CodeIssueProvider : ICodeIssueProvider
{
    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
    {
           
    }

    public IEnumerable<Type> SyntaxNodeTypes
    {
        get
        {
            yield return typeof(LocalDeclarationStatementSyntax);
        }
    }

    #region Unimplemented ICodeIssueProvider members

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<int> SyntaxTokenKinds
    {
        get
        {
            return null;
        }
    }

    #endregion
}
Как видим, кроме того что мы удалили код для поиска буквы, мы также изменили тип, который будет возвращаться методом SyntaxNodeTypes, так как будем работать с объявлением локальных переменных и, если это возможно, предлагать конвертировать в константу.
Для начала добавим игнорирование тех переменных, которые уже установлены как константы.
var localDeclaration = (LocalDeclarationStatementSyntax)node;
// Only consider local variable declarations that aren't already const.
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
    return null;
}
Далее нам необходимо выполнить семантический анализ кода, чтобы определить , можем ли сделать локальную переменную константой. Для этого нам понадобится модель ISemanticModel.
Примечание: IDocument концептуально эквивалентно файлу в Visual Studio, и ISemanticModel является представлением всей семантической информации в одном исходном файле.
Для этого вызовем строчку кода
var semanticModel = document.GetSemanticModel(cancellationToken);
Далее необходимо убедиться, что каждая переменная в декларации имеет инициализатор. Это необходимо, чтобы соответствовать спецификации языка C#, в которой говорится, что все константные переменные должны быть инициализированы. Например, int х = 0, у = 1; можно сделать const, но int х, у = 1; – нельзя.
// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (var variable in localDeclaration.Declaration.Variables)
{
    var initializer = variable.Initializer;
    if (initializer == null)
    {
        return null;
    }

    var constantValue = semanticModel.GetConstantValue(initializer.Value);
    if (!constantValue.HasValue)
    {
        return null;
    }
}
Строка кода
var constantValue = semanticModel.GetConstantValue(initializer.Value);
позволяет проверить, является ли переменная константой времени компиляции (compile-time constant).
Используем ISemanticModel для выполнения потока данных на локальном операторе объявления. Затем используем результаты этого анализа потока данных, чтобы гарантировать, что ни одна из локальных переменных не записывается с новым значением в другом месте. Вы сможете сделать это, вызвав метод ISemanticModel.GetDeclaredSymbol для получения ILocalSymbol для каждой переменной и убедившись, что она не содержится в WrittenOutside коллекции анализа потока данных.
// Perform data flow analysis on the local declaration.
var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
foreach (var variable in localDeclaration.Declaration.Variables)
{
    var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
    if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
    {
        return null;
    }
}
Этот код не так прост для понимания, как предыдущие строки кода, но если его вдумчиво прочитать, то станет понятно, что имеется в виду. После проделанной работы мы можем вернуть новый CodeIssue, в котором выдадим пользователю предупреждение о том, что переменную можно сделать константой.
return new[]
{
    new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
        "Can be made constant")
};
Полный метод выглядит следующим образом:
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
{
    var localDeclaration = (LocalDeclarationStatementSyntax)node;
    // Only consider local variable declarations that aren't already const.
    if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
    {
        return null;
    }

    var semanticModel = document.GetSemanticModel(cancellationToken);

    // Ensure that all variables in the local declaration have initializers that
    // are assigned with constant values.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var initializer = variable.Initializer;
        if (initializer == null)
        {
            return null;
        }

        var constantValue = semanticModel.GetConstantValue(initializer.Value);
        if (!constantValue.HasValue)
        {
            return null;
        }
    }

    // Perform data flow analysis on the local declaration.
    var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
        if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
        {
            return null;
        }
    }

    return new[]
    {
        new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
            "Can be made constant")
    };
}
После этого мы можем нажать F5, чтобы запустить наш проект в дебаг-режиме.

Благодаря команде, которая написала для нас подробную инструкцию, мы создали свой первый проект code issue, используя Roslyn APIs. Но поскольку из подсказок как таковых толку мало и приходится самому изменять код по подсказкам, давайте автоматизируем этот процесс. Любой Code Issue может включать несколько Code Actions, которые определяют изменения, которые будут применены к коду. Для этого нам необходимо в наш Code Issue проект добавить новый класс и наследоваться от интерфейса ICodeAction
Вот какая реализация получилась у меня по умолчанию, после того как я имплементировал интерфейс ICodeAction:
using System.Threading;
using Roslyn.Services;

namespace FirstQuickFix
{
    public class CodeAction : ICodeAction
    {
        public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
        {
            throw new System.NotImplementedException();
        }

        public string Description { get; private set; }
    }
}
Лишние пространства имен для наглядности я удалил с помощью ReSharper. Добавим описание, которое будет подсвечивать изменения.
public string Description
{
    get
    {
        return "Make Constant";
    }
}
Затем необходимо сказать созданному Code Issue использовать только что созданный CodeAction класс. Для этого переходим в класс CodeIssueProvider и изменяем наш созданный CodeIssue.
Затем с помощью горячих клавиш Ctrl+ сгенерируем необходимый конструктор. После этого переходим в класс CodeAction в метод GetEdit. Первым делом необходимо создать новый константный (const) маркер ключевого слова, который будет вставлен в передней части оператора объявления.
// Remove the leading trivia from the local declaration.
var firstToken = localDeclaration.GetFirstToken();
var leadingTrivia = firstToken.LeadingTrivia;
var trimmedLocal = localDeclaration.ReplaceToken(
    firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the original leading trivia.
var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);
Далее нам необходимо создать новый SyntaxTokenList в котором поместить созданный константный маркер в первую позицию.
// Insert the const token into the modifiers list, creating a new modifiers list.
var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
После этого создадим новый оператор объявления, который будет содержать новый список модификаторов.
// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers);
Добавим форматирования для новой конструкции.
Извлечь корень CommonSyntaxNode с IDocument и использовать его, чтобы заменить старый оператор объявления на новый.
// Replace the old local declaration with the new local declaration.
var oldRoot = document.GetSyntaxRoot(cancellationToken);
var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);
И, наконец, создаем новый CodeActionEdit, в который передадим обновленный документ с преобразованием дерева.
// Create and return a new CodeActionEdit for the transformed tree.
return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
Ниже приведен полный исходный код метода GetEdit.
public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
{
    // Remove the leading trivia from the local declaration.
    var firstToken = localDeclaration.GetFirstToken();
    var leadingTrivia = firstToken.LeadingTrivia;
    var trimmedLocal = localDeclaration.ReplaceToken(
        firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

    // Create a const token with the original leading trivia.
    var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);

    // Insert the const token into the modifiers list, creating a new modifiers list.
    var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);

    // Produce the new local declaration.
    var newLocal = trimmedLocal.WithModifiers(newModifiers);

    // Add an annotation to format the new local declaration.
    var formattedLocal = CodeAnnotations.Formatting.AddAnnotationTo(newLocal);

    // Replace the old local declaration with the new local declaration.
    var oldRoot = document.GetSyntaxRoot(cancellationToken);
    var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

    // Create and return a new CodeActionEdit for the transformed tree.
    return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
}
Запустим проект и посмотрим, что из этого получилось.

Исправление ошибок
В приведенной реализации есть несколько ошибок.
1. Метод GetIssues  класса CodeIssueProvider не проверяет, если значение константы на самом деле нельзя привести к переменной типа. Так, текущая реализация будет удачливо преобразовать неправильное объявление например ‘int i = "abs" к локальной константе.
2. Ссылочные типы не обрабатываются должным образом. Пример объявление const string s = "abc" является валидным, а вызов ‘const object s = "abc нет.
3. Если переменная объявлена с ключевым словом "var", Code Action для такого кода делает не то, что мы ожидали, он создаст объявление "const var", не поддерживаемое в языке С#. Чтобы исправить эту ошибку, Code Action должен заменить ключевое слово "var" на соответствующий тип данных.
К счастью, все перечисленные выше ошибки могут быть решены с использованием тех же методов, что мы использовали в этой статье.
Чтобы исправить первый баг, сначала откроем CodeIssueProvider.cs и найдем цикл по каждому элементу, где проверяется каждая из локальных переменных для того, чтобы сделать их константами.
Перед циклом вызовем ISemanicModel.GetTypeInfo () для получения подробной информации о объявленном типе локальной переменной:
var variableTypeName = localDeclaration.Declaration.Type;
var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;
Затем добавим следующий код перед закрывающей фигурной скобкой цикла. Нам необходимо вызвать ISemanticModel.ClassifyConversion () и определить, является ли инициализатор приводимым к локальной переменной. Если преобразование невозможно или оно определяется пользователем, переменная не может быть локальной константой.
// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
var conversion = semanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
    return null;
}
Также необходимо добавить проверку, является переменная строковая или равно null. Добавить код необходимо перед закрывающейся скобкой цикла. Сразу после кода, приведенного выше.
// Special cases:
//  * If the constant value is a string, the type of the local declaration
//    must be System.String.
//  * If the constant value is null, the type of the local declaration must
//    be a reference type.
if (constantValue.Value is string)
{
    if (variableType.SpecialType != SpecialType.System_String)
    {
        return null;
    }
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
    return null;
}
Полный код метода приведен ниже.
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
{
    var localDeclaration = (LocalDeclarationStatementSyntax)node;
    // Only consider local variable declarations that aren't already const.
    if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
    {
        return null;
    }

    var semanticModel = document.GetSemanticModel(cancellationToken);

    var variableTypeName = localDeclaration.Declaration.Type;
    var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

    // Ensure that all variables in the local declaration have initializers that
    // are assigned with constant values.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var initializer = variable.Initializer;
        if (initializer == null)
        {
            return null;
        }

        var constantValue = semanticModel.GetConstantValue(initializer.Value);
        if (!constantValue.HasValue)
        {
            return null;
        }

        // Ensure that the initializer value can be converted to the type of the
        // local declaration without a user-defined conversion.
        var conversion = semanticModel.ClassifyConversion(initializer.Value, variableType);
        if (!conversion.Exists || conversion.IsUserDefined)
        {
            return null;
        }

        // Special cases:
        //  * If the constant value is a string, the type of the local declaration
        //    must be System.String.
        //  * If the constant value is null, the type of the local declaration must
        //    be a reference type.
        if (constantValue.Value is string)
        {
            if (variableType.SpecialType != SpecialType.System_String)
            {
                return null;
            }
        }
        else if (variableType.IsReferenceType && constantValue.Value != null)
        {
            return null;
        }

    }

    // Perform data flow analysis on the local declaration.
    var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration);

    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    foreach (var variable in localDeclaration.Declaration.Variables)
    {
        var variableSymbol = semanticModel.GetDeclaredSymbol(variable);
        if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
        {
            return null;
        }

               

    }

    return new[]
    {
        new CodeIssue(CodeIssueKind.Warning, localDeclaration.Span,
            "Can be made constant",
            new CodeAction(document, localDeclaration))
    };
}
Осталось только исправить третью ошибку с использованием ключевого слова var. Для этого нам нужно перейти в CodeAction.cs и заметь код под комментарием "Produce the new local declaration" на код, приведенный ниже.
// If the type of the declaration is 'var', create a new type name
// for the inferred type.
var variableDeclaration = localDeclaration.Declaration;
var variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{

}

// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers)
                            .WithDeclaration(variableDeclaration);
Затем необходимо добавить проверку в фигурных скобках блока if, чтобы убедиться, что тип переменной не является псевдонимом. Если это псевдоним другого типа (например, "using var = System.String;"), то такое поведение  является законным, чтобы объявить локальную "const var".
var semanticModel = document.GetSemanticModel();

// Special case: Ensure that 'var' isn't actually an alias to another type
// (e.g. using var = System.String).
var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
if (aliasInfo == null)
{

}
Внутри фигурных скобок необходимо написать проверку на извлечения типа "var".
// Retrieve the type inferred for var.
var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

// Special case: Ensure that 'var' isn't actually a type named 'var'.
if (type.Name != "var")
{

}
Теперь добавьте код, чтобы создать новый TypeSyntax для предполагаемого типа внутри фигурных скобок, блока if, который Вы написали выше.
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
var typeName = Syntax.ParseTypeName(type.ToDisplayString())
    .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
    .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
Добавить Simplify синтаксис аннотации, для того чтобы упростить имя типа, чтобы действие зашитое в CodeAction  уменьшало имя типа к его минимальной форме.
// Add an annotation to simplify the type name.
var simplifiedTypeName = CodeAnnotations.Simplify
    .AddAnnotationTo(typeName);
И, в самом конце, заменим тип объявления переменной тем, который только что создали.
Полный код функции GetEdit:
public CodeActionEdit GetEdit(CancellationToken cancellationToken = new CancellationToken())
{
    // Remove the leading trivia from the local declaration.
    var firstToken = localDeclaration.GetFirstToken();
    var leadingTrivia = firstToken.LeadingTrivia;
    var trimmedLocal = localDeclaration.ReplaceToken(
        firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

    // Create a const token with the original leading trivia.
    var constToken = Syntax.Token(leadingTrivia, SyntaxKind.ConstKeyword);

    // Insert the const token into the modifiers list, creating a new modifiers list.
    var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);

    // If the type of the declaration is 'var', create a new type name
    // for the inferred type.
    var variableDeclaration = localDeclaration.Declaration;
    var variableTypeName = variableDeclaration.Type;
    if (variableTypeName.IsVar)
    {
        var semanticModel = document.GetSemanticModel();

        // Special case: Ensure that 'var' isn't actually an alias to another type
        // (e.g. using var = System.String).
        var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
        if (aliasInfo == null)
        {
            // Retrieve the type inferred for var.
            var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;

            // Special case: Ensure that 'var' isn't actually a type named 'var'.
            if (type.Name != "var")
            {
                // Create a new TypeSyntax for the inferred type. Be careful
                // to keep any leading and trailing trivia from the var keyword.
                var typeName = Syntax.ParseTypeName(type.ToDisplayString())
                    .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
                    .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

                // Add an annotation to simplify the type name.
                var simplifiedTypeName = CodeAnnotations.Simplify
                    .AddAnnotationTo(typeName);

                // Replace the type in the variable declaration.
                variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
            }

        }

    }

    // Produce the new local declaration.
    var newLocal = trimmedLocal.WithModifiers(newModifiers)
                                .WithDeclaration(variableDeclaration);

    // Add an annotation to format the new local declaration.
    var formattedLocal = CodeAnnotations.Formatting.AddAnnotationTo(newLocal);

    // Replace the old local declaration with the new local declaration.
    var oldRoot = document.GetSyntaxRoot(cancellationToken);
    var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

    // Create and return a new CodeActionEdit for the transformed tree.
    return new CodeActionEdit(document.UpdateSyntaxRoot(newRoot));
}
Давайте запустим созданный нами пример и посмотрим на исправленные ошибки.

Итоги

В приведенной статье мы создали свой extension для Visual Studio для работы с константами, а также рассмотрели возможности, которые нам предоставляет Roslyn. Надеюсь, на этом наше взаимодействие с проектом Roslyn не закончится. В следующей статье составим уже свой пример. Например, замена проверки строк на пустоту и null. Замена для IEnumerable коллекций проверки if(seq == null && seq.Count == 0) на seq.Any(), или что-то с более сложным уклоном. Обещаю над этим хорошенько подумать и преподнести новую статью по Roslyn. До новых встреч.  

No comments:

Post a Comment