Roslyn en el Gusenet


El pasado sábado 26 de abril estuve en Torrevieja en el mayor evento de comunidad que se hace en España: Gusenet, con un nivel y número enorme de ponentes y no ponentes.
Tengo que agradecer a Pedro por convencerme la invitación a participar y a él, a Eladio y a Oscar por el currazo que se han pegado organizando y acompañando a tanta gente de un lado a otro.
Había un montón de charlas y todos los que ya han escrito sobre el Gusenet han comentado las que más les han gustado. El resumen es que todas eran muy buenas así que yo os voy a contar las que me perdí y me gustaría haber visto: llegué tarde a la charla de Luis Ruiz Pavón que al final fue sobre Clean Code y también a la de Isa y Toni sobre optimización de JavaScript, aunque vi algún teaser-tocino antes, espero poder ver esa charla algún día.

Isa, Toni y algunos más 🙂 (Foto de @g_perales)


Es un evento muy intenso, que a pesar del cansancio te deja con ganas de más y te llevas un montón de cosas nuevas que probar en casa. Como había más de 20 charlas para un sólo día había dos tracks:

agendagusenet

Roslyn

Además de asistir a las charlas, hablar con un montón de gente muy interesante y beber cervezas, también estuve para dar una charla de 30 minutos sobre Roslyn, el nuevo compilador de C# y VB.Net.

Otro día escribiré algo más largo, pero os quería dejar en el blog los dos ejemplos de código que puse durante la charla.

Una de las cosas interesantes que nos permite hacer Roslyn es la creación de analizadores de código para crear reglas que nos proporcionen avisos o errores en el Visual Studio. Hice un ejemplo sencillo de analizador de código para encontrar bucles (for y foreach) declarados sin un bloque de llaves:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Threading;

namespace EnforceBrackets
{
    [DiagnosticAnalyzer]
    [ExportDiagnosticAnalyzer(DiagnosticId, LanguageNames.CSharp)]
    public class DiagnosticAnalyzer : ISyntaxNodeAnalyzer<SyntaxKind>
    {
        internal const string DiagnosticId = "EnforceBrackets";
        internal const string Description = "Loop should be enclosed in brackets";
        internal const string MessageFormat = "'{0}' does not use brackets";
        internal const string Category = "Style";

        internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Description, MessageFormat, Category, DiagnosticSeverity.Error);

        public ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public ImmutableArray<SyntaxKind> SyntaxKindsOfInterest
        {
            get
            {
                return ImmutableArray.Create(SyntaxKind.ForStatement, SyntaxKind.ForEachStatement);
            }
        }

        public void AnalyzeNode(SyntaxNode node, SemanticModel semanticModel, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken)
        {
            var forSyntax = node as ForStatementSyntax;
            if (forSyntax!=null && forSyntax.Statement!=null && !forSyntax.Statement.IsKind(SyntaxKind.Block))
            {
                addDiagnostic(Diagnostic.Create(Rule, forSyntax.ForKeyword.GetLocation(), "For"));
            }
            else{
                var foreachSyntax = node as ForEachStatementSyntax;
                if (foreachSyntax!=null && foreachSyntax.Statement!=null && !foreachSyntax.Statement.IsKind(SyntaxKind.Block))
                {
                    addDiagnostic(Diagnostic.Create(Rule, foreachSyntax.ForEachKeyword.GetLocation(), "Foreach"));
                }
            }
        }
    }
}

Tener un analizador que nos avise que algo no está como a nosotros nos gusta está muy bien, pero para hacerlo más interesante deberíamos proporcionar un proveedor de refactor, así, además de marcar el error, proporcionamos una solución automatizada.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EnforceBrackets
{
    [ExportCodeFixProvider(DiagnosticAnalyzer.DiagnosticId, LanguageNames.CSharp)]
    internal class CodeFixProvider : ICodeFixProvider
    {
        public IEnumerable<string> GetFixableDiagnosticIds()
        {
            return new[] { DiagnosticAnalyzer.DiagnosticId };
        }

        public async Task<IEnumerable<CodeAction>> GetFixesAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            SyntaxNode newRoot = null;
            // Find the type declaration identified by the diagnostic.
            var token = root.FindToken(span.Start);
            if (token.IsKind(SyntaxKind.ForKeyword))
            {
                var forStatement = token.Parent as ForStatementSyntax;
                var newForStatement = forStatement.WithStatement(SyntaxFactory.Block(forStatement.Statement))
                    .WithAdditionalAnnotations(Formatter.Annotation);

                newRoot = root.ReplaceNode(forStatement, newForStatement);

            }
            else if (token.IsKind(SyntaxKind.ForEachKeyword))
            {
                var foreachStatement = token.Parent as ForEachStatementSyntax;
                var newForeachStatement = foreachStatement.WithStatement(SyntaxFactory.Block(foreachStatement.Statement))
                    .WithAdditionalAnnotations(Formatter.Annotation);
                newRoot = root.ReplaceNode(foreachStatement, newForeachStatement);
            }

            if (newRoot != null)
            {
                // Return a code action that will invoke the fix.
                return new[] { CodeAction.Create("Add brackets", document.WithSyntaxRoot(newRoot)) };
            }
            else
            {
                return null;
            }
        }
    }
}

Para ejecutar todo esto normalmente lo haremos desde Visual Studio, pero también podemos utilizar el análisis que hemos creado mediante la línea de comando del rcsc.exe, así:

rcsc.exe /analyzer:enforcebrackets.dll Program.cs

Y sí, habéis visto bien, no utilizo el csc.exe sino el rcsc.exe. Es el compilador de Roslyn que tendréis que buscar dentro de las extensiones que habéis instalado en el Visual Studio. El mío estaba en:

%localappdata%\Microsoft\VisualStudio\12.0Roslyn\Extensions\lq01135k.z4r

.

Este ejemplo es muy sencillo y sólo utiliza la parte de análisis de sintaxis, en próximos ejemplos veremos cómo consultar la semántica y hacer análisis y refactors mucho más avanzados.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s