Octubre de 2015

Volumen 30, número 10

Análisis de código: compilación e implementación de bibliotecas con análisis de código Roslyn integrado en NuGet

De Alessandro Del Del | Octubre de 2015 | Obtener el código: C#VB

La plataforma de compilación de Microsoft .NET (también conocida como base de código “Roslyn”) ofrece compiladores de código abierto de C# y Visual Basic que muestran, entre otros elementos, API de análisis de código enriquecido que se pueden usar para compilar reglas de análisis dinámico que se integran en el editor de código de Visual Studio 2015. Con la plataforma de compilación de .NET, podrá escribir refactorizaciones y analizadores de código personalizados para dominios específicos para que Visual Studio pueda detectar posibles problemas de código conforme se vayan escribiendo y mostrar advertencias y mensajes de error. Una importante ventaja de la plataforma de compilación de .NET es que permite agrupar analizadores de código con las API. Por ejemplo, si compila bibliotecas o controles de usuario reutilizables, podrá entregar los analizadores junto con las bibliotecas y proporcionar a los desarrolladores una experiencia de codificación mejorada.

En este artículo, explicaré cómo agrupar bibliotecas y analizadores en paquetes de NuGet para la implementación en línea. Asimismo, mostraré cómo es posible ofrecer análisis de código Roslyn integrado para las API. Para ello, se necesitan conocimientos básicos sobre conceptos de la plataforma de compilación de .NET y sobre cómo escribir un analizador de código. Estos temas se han abordado en anteriores artículos de MSDN Magazine de Alex Turner: “C# y Visual Basic: Uso de Roslyn para escribir un analizador de código en directo para su API” (msdn.microsoft.com/magazine/dn879356) y “C#—Adding a Code Fix to Your Roslyn Analyzer” (C#: Incorporación de correcciones de código al analizador de Roslyn) (msdn.microsoft.com/magazine/dn904670). Se recomienda leer estos artículos antes de continuar con el contenido de este artículo.

Preparación de una biblioteca de muestra para simular API personalizadas

Lo primero que necesitará es una biblioteca de clase que simule una API personalizada. La biblioteca de muestra de este artículo muestra un método público simple que recupera información común de una fuente RSS y devuelve una colección de elementos de fuente. En Visual Studio 2015, cree una nueva biblioteca de clases portátil denominada FeedLibrary con C# o Visual Basic y asegúrese de que el destino mínimo sea Windows 8.1, Windows Phone 8.1 y Microsoft .NET Framework 4.5.1. Con este destino, la biblioteca también podrá sacar partido del patrón Async/Await sin requisitos adicionales.

Cambie el nombre del archivo generado Class1.vb o Class1.cs a FeedItem.vb/.cs. El código C# para esta clase se muestra en la Ilustración 1 y el código Visual Basic en la Ilustración 2.

Ilustración 1 Implementación de una clase para recuperar elementos comunes desde una fuente RSS en C#

using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace FeedLibrary
{
  // Represent a single content in the RSS feed
  public class FeedItem
  {
    // Properties representing information,
    // which is common to any RSS feed
    public string Title { get; set; }
    public string Author { get; set; }
    public string Description { get; set; }
    public DateTimeOffset PubDate { get; set; }
    public Uri Link { get; set; }
    // Return a collection of FeedItem objects from a RSS feed
    public static async Task<IEnumerable<FeedItem>> ParseFeedAsync(
      string feedUrl)
    {
      var client = new HttpClient();
      // Download the feed content as a string
      var result = await client.GetStringAsync(new Uri(feedUrl));
      // If no result, throw an exception
      if (result == null)
      {
        throw new InvalidOperationException(
          "The specified URL returned a null result");
      }
      else
      {
        // LINQ to XML: Convert the returned string into an XDocument object
        var doc = XDocument.Parse(result);
        var dc = XNamespace.Get("http://purl.org/dc/elements/1.1/");
        // Execute a LINQ query over the XML document
        // and return a collection of FeedItem objects
        var query = (from entry in doc.Descendants("item")
                     select new FeedItem
                     {
                         Title = entry.Element("title").Value,
                         Link = new Uri(entry.Element("link").Value),
                         Author = entry.Element(dc + "creator").Value,
                         Description = entry.Element("description").Value,
                         PubDate = DateTimeOffset.Parse(
                           entry.Element("pubDate").Value,
                     System.Globalization.CultureInfo.InvariantCulture)
                     });
        return query;
      }
    }
  }
}

Ilustración 2 Implementación de una clase para recuperar elementos comunes desde una fuente RSS en Visual Basic

Imports System.Net.Http
Imports <xmlns:dc="http://purl.org/dc/elements/1.1/">
'Represent a single content in the RSS feed
Public Class FeedItem
  'Properties representing information
  'which is common to any RSS feed
  Public Property Title As String = String.Empty
  Public Property Author As String = String.Empty
  Public Property Description As String = String.Empty
  Public Property PubDate As DateTimeOffset
  Public Property Link As Uri
  'Return a collection of FeedItem objects from a RSS feed
  Public Shared Async Function ParseFeedAsync(feedUrl As String) As _
    Task(Of IEnumerable(Of FeedItem))
    Dim client As New HttpClient
    'Download the feed content as a string
    Dim result = Await client.GetStringAsync(New Uri(feedUrl, UriKind.Absolute))
    'If no result, throw an exception
    If result Is Nothing Then
       Throw New InvalidOperationException(
         "The specified URL returned a null result")
    Else
      'LINQ to XML: Convert the returned string
      'into an XDocument object
      Dim document = XDocument.Parse(result)
      'Execute a LINQ query over the XML document
      'and return a collection of FeedItem objects
      Dim query = From item In document...<item>
                  Select New FeedItem With {
                  .Title = item.<title>.Value,
                  .Author = item.<dc:creator>.Value,
                  .Description = item.<description>.Value,
                  .PubDate = DateTimeOffset.Parse(item.<pubDate>.Value),
                  .Link = New Uri(item.<link>.Value)}
      Return query
    End If
  End Function
End Class

El código es muy sencillo: El código descarga el contenido sindicado de la dirección URL de la fuente RSS especificada, crea una instancia de la clase Feed­Item por cada elemento de fuente y, por último, devuelve una nueva colección de elementos. Para usar la biblioteca, basta con invocar el método estático ParseFeedAsyncAsync para C#, tal como se muestra a continuación:

// Replace the argument with a valid URL
var items = await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss");

El código para Visual Basic sería el siguiente:

'Replace the argument with a valid URL
Dim items = Await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss")

Esta invocación devuelve IEnumerable<FeedItem>, que podrá usar según sus necesidades. Seleccione la configuración de lanzamiento y compile el proyecto. En este punto, Visual Studio 2015 genera una biblioteca denominada FeedLibrary.dll que se usará más adelante.

Escritura de un analizador Roslyn

El siguiente paso consiste en crear un analizador Roslyn que proporcione reglas de análisis dinámico específicas de dominio para las API personalizadas. El analizador detectará si la dirección URL proporcionada como argumento del método ParseFeedAsyncAsync está correctamente formada. Para ello usará el método Uri.IsWellFormedUriString. De lo contrario, el analizador mostrará una advertencia conforme escriba el código. Existen muchas maneras de detectar si una dirección URL no es válida; sin embargo, usaré este modo, ya que es el más sencillo. Además, por las mismas razones, el analizador mostrará advertencias de informe de análisis, pero no ofrecerá ninguna corrección de código, cosa que tendrá que abordar usted a modo de ejercicio. En definitiva, deberá seguir los pasos siguientes:

  1. En el Explorador de soluciones, haga clic con el botón secundario en el nombre de la solución y seleccione Agregar | Nuevo proyecto.
  2. En el nodo Extensibilidad de la lista de plantillas del proyecto, seleccione el analizador con corrección de código (NuGet + VSIX). Tenga en cuenta que el nodo Extensibilidad no se muestra de forma predeterminada. Primero es necesario descargar el SDK de la plataforma de compilación de .NET para recuperar los proyectos de plantilla del analizador con correcciones de código. Busque analizadores en el cuadro de diálogo Nuevo proyecto y verá el proyecto de plantilla necesario para descargar este SDK.
  3. Llame a FeedLibraryAnalyzer del nuevo analizador y haga clic en Aceptar.
  4. Cuando el nuevo proyecto esté listo, quite el archivo CodeFix­Provider.cs (o .vb).

En el archivo DiagnosticAnalyzer.cs (o .vb), lo primero que se debe hacer es proporcionar cadenas que identifiquen el analizador en la experiencia de codificación. Para que la implementación del analizador sea sencilla, en el ejemplo actual usaré cadenas normales en lugar del objeto LocalizableString y los archivos de recursos, ya que doy por sentado que no es necesario localizar el analizador. Vuelva a escribir DiagnosticId, Title, Message, Description y Category para C#, tal como se muestra a continuación:

public const string DiagnosticId = "RSS001";
internal static readonly string Title = "RSS URL analysis";
internal static readonly string MessageFormat = "URL is invalid";
internal static readonly string Description =
  "Provides live analysis for the FeedLibrary APIs";
internal const string Category = "Syntax";

Estos serían los valores para Visual Basic:

Public Const DiagnosticId = "RSS001"
Friend Shared ReadOnly Title As String = "RSS URL analysis"
Friend Shared ReadOnly MessageFormat As String = "URL is invalid"
Friend Shared ReadOnly Description As String =
  "Provides live analysis for the FeedLibrary APIs"
Friend Const Category = "Syntax"

No modifique la gravedad del diagnóstico, cuyo valor predeterminado es Advertencia, ya que es la opción correcta para el ejemplo actual. Ahora nos centraremos en la lógica del análisis. El analizador debe comprobar si el código invoca un método llamado ParseFeedAsync. En caso afirmativo, el analizador comprobará si la dirección URL proporcionada está correctamente formada. Gracias al visualizador de sintaxis, en la Ilustración 3 se puede ver cómo la invocación del método ParseFeedAsync se representa mediante la clase Invocation­Expression asignada a un objeto del tipo InvocationExpressionSyntax.

El visualizador de sintaxis ayuda a encontrar la representación del nodo de sintaxis correcta
Ilustración 3 El visualizador de sintaxis ayuda a encontrar la representación del nodo de sintaxis correcta

Puesto que el analizador solo se centra en objetos del tipo Invocation­ExpressionSyntax; cuando encuentra uno, convierte la expresión asociada en un objeto del tipo MemberAccessExpressionSyntax que, a su vez, contiene información sobre una llamada al método. Si la conversión se realiza correctamente, el analizador comprueba si el método es ParseFeedAsync. A continuación, recupera el primer argumento y realiza un análisis dinámico de su valor. Esto se consigue gracias a un nuevo método denominado AnalyzeMethod, que funciona en el nivel de SyntaxNode y se representa en la Ilustración 4 para C# y en la Ilustración 5 para Visual Basic.

Ilustración 4 Detección de problemas en el argumento ParseFeedAsync Argument en C#

private static void AnalyzeMethodInvocation(SyntaxNodeAnalysisContext context)
{
  // Convert the current syntax node into an InvocationExpressionSyntax,
  // which represents a method call
  var invocationExpr = (InvocationExpressionSyntax)context.Node;
  // Convert the associated expression into a MemberAccessExpressionSyntax,
  // which represents a method's information
  // If the expression is not a MemberAccessExpressionSyntax, return
  if (!(invocationExpr.Expression is MemberAccessExpressionSyntax))
  {
    return;
  }
  var memberAccessExpr = (MemberAccessExpressionSyntax)invocationExpr.Expression;
  // If the method name is not ParseFeedAsync, return
  if (memberAccessExpr?.Name.ToString() != "ParseFeedAsync") { return; }
  // If the method name is ParseFeedAsync, check for the symbol
  // info and see if the return type matches
  var memberSymbol = context.SemanticModel.
                     GetSymbolInfo(memberAccessExpr).
                     Symbol as IMethodSymbol;
  if (memberSymbol == null) { return; }
  var result = memberSymbol.ToString();
  if (memberSymbol?.ReturnType.ToString() !=
  "System.Threading.Tasks.Task<
    System.Collections.Generic.IEnumerable<FeedLibrary.FeedItem>>")
  {
      return;
  }
  // Check if the method call has the required argument number
  var argumentList = invocationExpr.ArgumentList;
  if (argumentList?.Arguments.Count != 1) {
      return; }
  // Convert the expression for the first method argument into
  // a LiteralExpressionSyntax. If null, return
  var urlLiteral = (LiteralExpressionSyntax)invocationExpr.ArgumentList.
      Arguments[0].Expression;
  if (urlLiteral == null) { return; }
  // Convert the actual value for the method argument into string
  // If null, return
  var urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral);
  var urlValue = (string)urlLiteralOpt.Value;
  if (urlValue == null) { return; }
  // If the URL is not well-formed, create a diagnostic
  if (Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) == false)
  {
    var diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation(),
      "The specified parameter Is Not a valid RSS feed");
    context.ReportDiagnostic(diagn);
  }
}

Ilustración 5 Detección de problemas en el argumento ParseFeedAsync Argument en Visual Basic

Private Sub Shared AnalyzeMethodInvocation(context As SyntaxNodeAnalysisContext)
  'Convert the current syntax node into an InvocationExpressionSyntax
  'which represents a method call
  Dim invocationExpr = CType(context.Node, InvocationExpressionSyntax)
  'Convert the associated expression into a MemberAccessExpressionSyntax
  'which represents a method's information
  'If the expression Is Not a MemberAccessExpressionSyntax, return
  If TypeOf invocationExpr.Expression IsNot MemberAccessExpressionSyntax Then Return
  Dim memberAccessExpr = DirectCast(invocationExpr.Expression,
    MemberAccessExpressionSyntax)
  'If the method name Is Not ParseFeedAsync, return
  If memberAccessExpr?.Name.ToString <> "ParseFeedAsync" Then Return
  'If the method name is ParseFeedAsync, check for the symbol info
  'and see if the return type matches
  Dim memberSymbol = TryCast(context.SemanticModel.
      GetSymbolInfo(memberAccessExpr).Symbol, IMethodSymbol)
  If memberSymbol Is Nothing Then Return
  Dim result = memberSymbol.ToString
  If Not memberSymbol?.ReturnType.ToString =
    "System.Threading.Tasks.Task(Of System.Collections.Generic.IEnumerable(
    Of FeedLibrary.FeedItem))"
    Then Return
  'Check if the method call has the required argument number
  Dim argumentList = invocationExpr.ArgumentList
  If argumentList?.Arguments.Count <> 1 Then Return
  'Convert the expression for the first method argument into
  'a LiteralExpressionSyntax. If null, return
  Dim urlLiteral =
    DirectCast(invocationExpr.ArgumentList.Arguments(0).GetExpression,
      LiteralExpressionSyntax)
  If urlLiteral Is Nothing Then Return
  'Convert the actual value for the method argument into string
  'If null, return
  Dim urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral)
  Dim urlValue = DirectCast(urlLiteralOpt.Value, String)
  If urlValue Is Nothing Then Return
  'If the URL Is Not well-formed, create a diagnostic
  If Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) = False Then
     Dim diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation,
       "The specified parameter Is Not a valid RSS feed")
     context.ReportDiagnostic(diagn)
  End If
End Sub

Llegados a este punto, necesitará editar el método Initialize para llamar al método AnalyzeMethodInvocation recientemente agregado, tal como se muestra en la Ilustración 6 para C# y en la Ilustración 7 para Visual Basic.

Ilustración 6 Edición del método Initialize en C#

public override void Initialize(AnalysisContext context)
{
  // Register an action when compilation starts
  context.
    RegisterCompilationStartAction((CompilationStartAnalysisContext ctx) =>
  {
    // Detect if the type metadata
    // exists in the compilation context
    var myLibraryType =
    ctx.Compilation.
    GetTypeByMetadataName("FeedLibrary.FeedItem");
    // If not, return
    if (myLibraryType == null)
        return;
    // Register an action against an InvocationExpression
    ctx.RegisterSyntaxNodeAction(AnalyzeMethodInvocation,
      SyntaxKind.InvocationExpression);
  });
}

Ilustración 7 Edición del método Initialize en Visual Basic

Public Overrides Sub Initialize(context As AnalysisContext)
  ' Register an action when compilation starts
  context.
    RegisterCompilationStartAction
    (Sub(ctx As CompilationStartAnalysisContext)
      'Detect if the type metadata
      'exists in the compilation context
      Dim myLibraryType =
        ctx.Compilation.
          GetTypeByMetadataName("FeedLibrary.FeedItem")
        'If not, return
        '(no reference to the library)
        If myLibraryType Is Nothing
Then Return
        'Register an action against
        'an InvocationExpression
        ctx.RegisterSyntaxNodeAction(
          AddressOf AnalyzeMethodInvocation,
          SyntaxKind.InvocationExpression)
     End Sub)
End Sub

Tenga en cuenta que el código comprueba en primer lugar si existe una referencia a la biblioteca en el proyecto. Para ello invoca el método Compilation.GetTypeMetadataName, cuyo argumento es el nombre del tipo que debe existir en el contexto actual para asegurarse de que se ha agregado una referencia. Si esta invocación devuelve un valor nulo, significa que el tipo no existe y, por lo tanto, que no se ha agregado ninguna referencia a la biblioteca. Por lo tanto, no es necesario registrar ninguna acción de análisis de código, lo que mejora el rendimiento del analizador. Si pulsa F5 para probar el analizador en la instancia experimental de Visual Studio 2015 y crea un nuevo proyecto con referencia a la biblioteca FeedLibrary, podrá ver que muestra correctamente una advertencia cada vez que proporciona una dirección URL no válida, tal como se muestra en la Ilustración 8.

El analizador muestra una advertencia si la dirección URL no está formada correctamente
Ilustración 8 El analizador muestra una advertencia si la dirección URL no está formada correctamente

Hasta el momento, hemos compilado las API y las reglas de análisis de código específicas de cada dominio. Ahora es el momento de ver cómo se pueden agrupar en un único paquete de NuGet.

Compilación de un paquete de NuGet que incluya API y analizadores

Las reglas de MSBuild para la plantilla del proyecto del analizador con corrección de código automatiza la generación del paquete de NuGet que incluye el analizador compilado, que se puede compartir con otros desarrolladores mediante la publicación en un repositorio de NuGet. En la práctica, cada vez que se depura un analizador al presionar F5 o cuando se compila el proyecto del analizador, Visual Studio 2015 vuelve a compilar el archivo .dll del analizador (archivo Feed­LibraryAnalyzer.dll en el ejemplo actual) junto con un paquete de NuGet redistribuible que contiene el analizador.

El proceso de compilación también genera un paquete VSIX que puede publicar en la galería de Visual Studio y que se usa para depurar el analizador en la instancia experimental de Visual Studio 2015; sin embargo, esto queda fuera del ámbito del presente artículo, por lo que es un tema que no abordaremos.

Si desea compartir una biblioteca con análisis Roslyn integrado, tendrá que agregar la biblioteca al paquete de NuGet que genere Visual Studio 2015 al compilar el proyecto. Antes de hacer esto, es necesario comprender cómo se crea un paquete de NuGet para un analizador. En realidad, un paquete de NuGet es un archivo .zip con la extensión .nupkg. Por esta razón, es posible ver fácilmente el contenido y la estructura de los paquetes de NuGet con herramientas de archivos .zip como, por ejemplo, las herramientas de carpetas comprimidas del Explorador de Windows, WinZip o WinRar. A continuación se proporciona un resumen de los elementos más importantes de los paquetes de NuGet diseñados para la implementación de analizadores:

  • Archivo .nuspec: Este archivo contiene los metadatos del paquete e incluye la información necesaria para la publicación como, por ejemplo, el nombre del paquete, la versión, la descripción, el autor o la dirección URL de la licencia, entre otros datos. El archivo .nuspec está agrupado en el paquete de NuGet según el archivo Diagnostic.nuspec que se muestra en el explorador de soluciones, en el proyecto del analizador. En breve editaremos el archivo Diagnostic.nuspec en Visual Studio 2015.
  • Carpeta tools: Esta carpeta contiene los scripts de Windows PowerShell que usa Visual Studio para instalar (Install.ps1) y desinstalar (Uninstall.ps1) un analizador de un proyecto determinado.
  • Carpeta analyzers: Esta carpeta contiene los archivos .dll del analizador organizados en subcarpetas. Las bibliotecas independientes del analizador (es decir, las que están orientadas a todos los lenguajes) residen en una subcarpeta denominada dotnet. Los analizadores orientados a C# residen en una subcarpeta denominada dotnet\cs, mientras que los analizadores orientados a Visual Basic residen en la carpeta dotnet\vb. dotnet representa el perfil NuGet para .NET Core y admite tipos de proyectos como por ejemplo los proyectos de aplicaciones universales de Windows y los proyectos ASP.NET 5.

Es posible agrupar un número determinado de elementos adicionales en un paquete de NuGet; sin embargo, me centraré en un paquete típico generado para el analizador Roslyn. De este modo, solo se abordarán los elementos necesarios.

Todas las bibliotecas a las que se pueda hacer referencia desde un proyecto de Visual Studio deben estar organizadas en una carpeta denominada lib. Puesto que las bibliotecas pueden estar orientadas a distintas plataformas como, por ejemplo, distintas versiones de .NET Framework, Windows en tiempo de ejecución, distintas versiones de Windows Phone o incluso un subconjunto portátil (incluidas las bibliotecas Xamarin), la carpeta lib debe contener una subcarpeta por cada plataforma. A su vez, cada subcarpeta debe contener una copia de la biblioteca que se va a implementar. El nombre de cada subcarpeta deberá coincidir con el nombre del perfil que represente a la plataforma específica. Por ejemplo, si tiene una biblioteca orientada a .NET Framework 4.5.1 y Windows 8.1, deberá disponer de una estructura en la que net451 es el nombre de perfil de .NET Framework 4.5.1 y netcore451 es el nombre de perfil para Windows en tiempo de ejecución en Windows 8.1:

lib\net451\mylib.dll
lib\netcore451\mylib.dll

Cabe destacar que el perfil uap10.0 orientado a la Plataforma universal de Windows (UWP) compilará aplicaciones para Windows 10. Encontrará una lista completa de perfiles compatibles en la documentación de NuGet. La biblioteca de muestra creada anteriormente es una biblioteca portátil orientada a .NET Framework 4.5.1, Windows 8.1 y Windows Phone 8.1. El nombre de perfil para este tipo de destino es portable-net451+netcore451+wpa81 y debe usarse como nombre de la subcarpeta que contendrá la biblioteca en el paquete de NuGet. No es necesario crear la subcarpeta y copiar la biblioteca manualmente. Basta con editar los metadatos del paquete de NuGet (archivo Diagnostic.nuspec) de Visual Studio. La Ilustración 9 muestra los metadatos con la información correcta para la publicación (identificador, título, autor, descripción, licencia, etc.), así como un nuevo elemento de archivo en el nodo de archivos que especifica el archivo de origen y la subcarpeta de destino.

Ilustración 9 Edición de los metadatos del paquete de NuGet

<?xml version="1.0"?>
<package xmlns="https://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>RSSFeedLibrary</id>
    <version>1.0.0.0</version>
    <title>RSS Feed Library with Roslyn analysis</title>
    <authors>Alessandro Del Sole</authors>
    <owners>Alessandro Del Sole</owners>
    <licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
    <!-- Removing these lines as they are not needed
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>-->
    <requireLicenseAcceptance>true</requireLicenseAcceptance>
    <description>Companion sample for the "Build and Deploy Libraries
    with Integrated Roslyn Code Analysis to NuGet" article
    on MSDN Magazine</description>
    <releaseNotes>First release.</releaseNotes>
    <copyright>Copyright 2015, Alessandro Del Sole</copyright>
    <tags>RSS, analyzers, Roslyn</tags>
    <frameworkAssemblies>
      <frameworkAssembly assemblyName="System" targetFramework="" />
    </frameworkAssemblies>
  </metadata>
  <files>
    <file src="*.dll" target="analyzers\dotnet\cs"
          exclude="**\Microsoft.CodeAnalysis.*;
          **\System.Collections.Immutable.*;**\System.Reflection.Metadata.*;
          **\System.Composition.*" />
    <file src="tools\*.ps1" target="tools\" />
    <file src="lib\FeedLibrary.dll" target="lib\portable-net451+netcore451+wpa81\"/>
  </files>
</package>

El atributo src indica la ubicación de origen de la biblioteca, mientras que target especifica la subcarpeta de destino en función del nombre del perfil. En este caso, Visual Studio buscará una biblioteca denominada FeedLibrary.dll en una carpeta denominada lib que tendrá que crear en el directorio actual. Esta última es la carpeta que contiene el analizador compilado, que suele ser la carpeta Release o Debug, dependiendo de la configuración de compilación seleccionada. Según el ejemplo actual, necesitará crear una carpeta denominada lib en la carpeta Release para, a continuación, copiar el archivo FeedLibrary.dll (generado al compilar la biblioteca de muestra al principio) a la carpeta lib. Una vez realizada esta acción, podrá volver a compilar la solución y Visual Studio generará un paquete de NuGet actualizado que contendrá la biblioteca y el analizador Roslyn.

Cabe mencionar que al volver a compilar el proyecto, Visual Studio 2015 actualiza automáticamente los números de versión de revisión y compilación del paquete de NuGet, descartando los números proporcionados en la etiqueta de versión. Para ver el contenido actualizado del paquete de NuGet, basta con abrir la versión actualizada y más reciente del paquete de NuGet con una herramienta de archivos .zip. Recuerde: Cada vez que cambie el valor del elemento identificador, tal como se muestra en la Ilustración 9, Visual Studio 2015 genera un paquete de NuGet con un nombre distinto basado en el nuevo identificador. En este caso, al cambiar el valor de identificador a RSSFeedLibrary dará como resultado un paquete de NuGet denominado RSSFeedLibrary.1.0.xxx.yyy.NuPkg, donde xxx es el número de versión de compilación e yyy es el número de versión de revisión. Ambos números se proporcionan de manera automática en el tiempo de compilación. En este punto, hemos conseguido nuestro primer objetivo: empaquetar API personalizadas con análisis Roslyn integrado en un paquete de NuGet.

Como alternativa, se pueden crear (y publicar) dos paquetes individuales, uno para el analizador y otro para la biblioteca, junto con un tercer paquete de NuGet vacío que los agrupa resolviéndolos como dependencias. Este enfoque permite mantener un tamaño de instalación reducido usando solo las API sin el analizador, aunque necesitará estar familiarizado con las convenciones de NuGet para crear manualmente un paquete desde cero. Antes de publicar el paquete recién generado en el repositorio NuGet en línea, puede ser una buena idea probarlo de manera local.

Prueba de paquetes de NuGet de forma local

Visual Studio 2015 permite seleccionar paquetes de NuGet de los repositorios locales. Esto resulta especialmente útil en situaciones en las que es necesario almacenar en la caché los paquetes que se usan más a menudo o que pueden ser necesario para trabajar sin conexión. Esta es una buena solución cuando el número de paquetes de la carpeta local es bajo; cuando hay cientos de paquetes, la situación se vuelve mucho más compleja. Para probar el paquete de NuGet previamente generado, cree una nueva carpeta en el disco denominada LocalPackages y, a continuación, copie la versión más reciente del archivo RSSFeed­Library.1.0.xxx.yyy.nupkg en dicha carpeta. El siguiente paso es habilitar Visual Studio 2015 para seleccionar paquetes de la carpeta local especificada, tal como muestra la Ilustración 10. Seleccione Herramientas | Opciones | Administrador de paquetes de NuGet | Orígenes del paquete y, en el cuadro Orígenes del paquete disponible, haga clic en el botón Agregar (símbolo más de color verde). En este punto, en los cuadros de texto Nombre y Origen, escriba Local packages y C:\LocalPackages, respectivamente. Por último, haga clic en Actualizar para actualizar la lista de orígenes del paquete.

Adición de un repositorio local como origen del paquete de NuGet
Ilustración 10 Adición de un repositorio local como origen del paquete de NuGet

Ahora que ya dispone de su repositorio de NuGet local, cree en Visual Studio 2015 una nueva aplicación de consola para probar el paquete de biblioteca con análisis Roslyn. Guarde el proyecto y, a continuación, seleccione Proyecto | Administrar paquetes de NuGet. Cuando aparezca la ventana Administrar paquetes de NuGet, en el cuadro combinado Origen del paquete, seleccione Origen de paquetes local. En este punto, el administrador de paquetes mostrará una lista de paquetes disponibles en el repositorio especificado: en este caso, solo el paquete de muestra.

Haga clic en Instalar. Al igual que con los paquetes de NuGet en línea, Visual Studio mostrará información de resumen del paquete seleccionado y le pedirá que acepte el acuerdo de licencia. Cuando finalice la instalación, podrá ver tanto la biblioteca como el analizador Roslyn en el Explorador de soluciones, tal como se muestra en la Ilustración 11.

Tanto la biblioteca como el analizador Roslyn se han instalado a través del paquete de NuGet
Ilustración 11 Tanto la biblioteca como el analizador Roslyn se han instalado a través del paquete de NuGet

Si escribe código que use el método FeedItem.ParseFeed­Async para pasar una dirección URL no válida como argumento, el motor de análisis dinámico mostrará un mensaje de advertencia según lo esperado (vea la Ilustración 8 para obtener más referencias).

Cuando se instala un analizador, tanto desarrollado por usted como de otros desarrolladores, podrá ver los detalles de cada regla en el Explorador de soluciones. Para ello, basta con expandir Referencias, Analizadores y, a continuación, el nombre del analizador. En este caso, puede expandir el nombre de FeedLibraryAnalyzer y ver la regla de análisis de dirección URL de RSS, tal como se muestra en la Ilustración 11. Al hacer clic en una regla, la ventana Propiedades muestra información detallada como, por ejemplo, la gravedad predeterminada y efectiva, si está habilitada de forma predeterminada o la descripción de regla completa. Además, puede usar el editor de conjuntos de reglas para ver todas las reglas aplicables a un proyecto, para ver y cambiar la gravedad de una regla, así como para deshabilitar o habilitar las reglas y los analizadores. Para abrir el editor de conjuntos de reglas, en el Explorador de soluciones, haga doble clic en Propiedades. A continuación, en la ventana Propiedades del proyecto, seleccione la pestaña Análisis de código. Por último, haga clic en Abrir y deje sin modificar el conjunto de reglas predeterminado.

Tal como se puede ver en la Ilustración 11, es posible deshabilitar/habilitar una regla activando/desactivando la casilla situada junto al código de la regla. Asimismo, es posible modificar la gravedad predeterminada haciendo clic en la flecha hacia abajo de color negro situada a la derecha del nivel de gravedad actual (esto también puede realizarse haciendo clic con el botón secundario en la regla en el Explorador de soluciones y seleccionando, a continuación, Configurar gravedad de conjunto de reglas en el menú contextual). Cuando esté satisfecho con las pruebas locales, podrá pasar a la publicación del paquete de NuGet en línea.

Prueba de un paquete en línea

En NuGet, los desarrolladores esperan encontrar paquetes profesionales y de calidad. Por esta razón, antes de publicar un paquete en la galería de NuGet en línea, es necesario probar el trabajo con la ayuda de un servicio en línea que permita crear fuentes y repositorios de NuGet privados para pasarlos al repositorio de NuGet oficial una vez que el paquete sea estable. MyGet (myget.org) es un servicio en línea que permite crear fuentes de NuGet personales y empresariales, además de fuentes de VSIX, npm y Bower. MyGet ofrece un plan gratuito con la mayoría de las características necesarias para publicar y consumir paquetes de NuGet. El plan gratuito no permite crear fuentes privadas (para ello necesitará un plan de pago), aunque es una excelente opción para probar si los paquetes funcionan según lo esperado desde un repositorio en línea. Al registrarse, tendrá la opción de crear una fuente de NuGet. Por ejemplo, mi fuente pública en MyGet está disponible en myget.org/F/­aledelsole/api/v2. No voy a explicar cómo funciona MyGet, ya que esto queda fuera del ámbito de este artículo; sin embargo, la documentación describe en detalle cómo se debe configurar la fuente de NuGet. Una vez creada la fuente y publicado el paquete, basta con habilitar Visual Studio 2015 para seleccionar los paquetes de NuGet de la fuente de MyGet. Para realizar este proceso, siga los pasos que se describen en la sección anterior y tome la Ilustración 10 como referencia proporcionando la dirección URL de su fuente de MyGet. Para descargar y probar un paquete en Visual Studio, deberá seguir los pasos descritos en la sección anterior seleccionando la fuente de MyGet como origen en la ventana del Administrador de paquetes de NuGet.

Publicación de un paquete en la galería en línea de NuGet

Para publicar paquetes en el repositorio de NuGet en línea, necesitará abrir nuget.org e iniciar sesión con una cuenta. Si todavía no tiene ninguna cuenta, haga clic en el hipervínculo Register/Sign In (Registrarse) situado en la esquina superior derecha de la página. Puede registrarse con una cuenta Microsoft (opción recomendada) o con sus credenciales de nombre de usuario/contraseña. Una vez registrado, haga clic en Upload Package (Cargar paquete). Lo primero que deberá hacer es especificar el paquete de NuGet que desea cargar. Por lo tanto, haga clic en Browse (Examinar), seleccione la versión más reciente de RSSFeed­Library.1.0.xxx.yyy.nupkg del disco y, a continuación, haga clic en Upload (Cargar).

A continuación, se le pedirá la información de metadatos del paquete. Aquí tendrá la opción de revisar los detalles del paquete antes de publicarlo en la galería, tal como se muestra en la Ilustración 12. Cuando esté listo, haga clic en Enviar. En este punto, el paquete que contiene las API y el analizador Roslyn integrado se publicará en la galería de NuGet. Tenga en cuenta que deberá esperar entre 15 y 20 minutos para poder ver el paquete disponible en la ventana del Administrador de paquetes de NuGet en Visual Studio 2015.

Revisión de los detalles del paquete antes de la publicación
Ilustración 12 Revisión de los detalles del paquete antes de la publicación

Cuando se muestre el paquete en la galería, podrá instalarlo en su proyecto desde el repositorio en línea de NuGet, tal como se muestra en la Ilustración 13. Una vez instalado, use la biblioteca tal como se ha explicado en la sección anterior, con la característica de análisis dinámico de código específico del dominio que proporciona la plataforma de compilación de .NET.

Paquete de NuGet disponible para el público desde el repositorio de NuGet en línea
Ilustración 13 Paquete de NuGet disponible para el público desde el repositorio de NuGet en línea

Actualizaciones del paquete

En lo que a paquetes de NuGet se refiere, podrá mejorar las bibliotecas y publicar versiones actualizadas del paquete en NuGet. Para crear un paquete actualizado, basta con volver a compilar la solución. Visual Studio 2015 actualizará automáticamente el número de versión del paquete. A continuación, vuelva a compilar el proyecto y repita los pasos descritos anteriormente para publicar el paquete de NuGet en línea. NuGet mostrará una lista de versiones disponibles para cada paquete y permitirá a los desarrolladores elegir la versión que necesiten.

Resumen

Una de las ventajas más importantes que ofrece la plataforma de compilación de .NET es que se pueden crear reglas de análisis de código específicas para cada dominio para sus API. En este artículo, primero he mostrado cómo crear una biblioteca de muestra. A continuación, hemos visto cómo crear un analizador Roslyn capaz de detectar problemas en el código mientras se escribe, una característica específica para los miembros de la biblioteca. También he mostrado cómo agrupar la biblioteca y el analizador en un paquete de NuGet. Esta es la base del presente artículo. De este modo, los desarrolladores que descarguen el paquete de NuGet, obtendrán las API con el analizador dinámico Roslyn integrado. Posteriormente, hemos visto cómo probar el paquete de NuGet de manera local antes de su publicación en línea. Este paso es muy importante, ya que ofrece la oportunidad de comprobar que el conjunto de biblioteca/analizador funciona correctamente antes de hacerlo público. Lo mejor de todo es que otros desarrolladores pueden usar el resultado de nuestro trabajo. Por ello, en la última sección del artículo, he mostrado cómo se puede publicar el paquete en el repositorio en línea de NuGet y cómo instalarlo más adelante en los proyectos desde Visual Studio. La implementación de analizadores Roslyn junto con las bibliotecas incrementa enormemente el valor de nuestro trabajo. Asimismo, mejora la experiencia de codificación de otros desarrolladores.


Alessandro Del Sole ha sido MVP de Microsoft desde el año 2008. Alessandro ha sido proclamado MVP del año en cinco ocasiones y es el autor de numerosos libros, eBooks, vídeos de formación y artículos de desarrollo .NET con Visual Studio. Puede seguirlo en Twitter @progalex.

Gracias a los siguientes expertos técnicos por revisar este artículo: Srivatsn Narayanan y Manish Vasani
Srivatsn Narayanan ha trabajado en lenguajes (IronPython, C#, VB.NET) en Microsoft durante los últimos ocho años. Srivatsn ha trabajado en los compiladores y en los servicios de lenguaje para estos lenguajes. Recientemente ha liderado los esfuerzos que se han invertido en el marco del solucionador/analizador Roslyn.

Manish Vasani es un ingeniero de software con ocho años de experiencia en el trabajo con distintos equipos del grupo de Visual Studio de Microsoft. Actualmente trabaja en el equipo de Servicios de análisis de lenguajes administrados, donde se ocupa de las API del analizador Roslyn, la infraestructura de ejecución del analizador y su integración en Visual Studio.