¿Qué es roslyn?
Roslyn es un conjunto de compiladores de código abierto y API de análisis de código para los lenguajes de Microsoft C # y VisualBasic .NET.
El analizador Roslyn es una herramienta poderosa para analizar código, encontrar errores y corregirlos.
Árbol de sintaxis y modelo semántico
Para analizar el código, debe comprender el árbol de sintaxis y el modelo semántico, ya que estos son los dos componentes principales para el análisis estático.
Un árbol de sintaxis es un elemento que se construye sobre la base del código fuente del programa y es necesario para el análisis del código. Durante el análisis del código, se mueve a lo largo de él.
Cada código tiene un árbol de sintaxis. Para el siguiente objeto de clase
class A { void Method() { } }
el árbol de sintaxis se verá así:

Un objeto de tipo SyntaxTree es un árbol de sintaxis. Se pueden distinguir tres elementos principales en el árbol: SyntaxNodes, SyntaxTokens, SyntaxTrivia.
Los nodos de sintaxis describen construcciones de sintaxis, a saber, declaraciones, operadores, expresiones, etc. En C #, las construcciones de sintaxis representan una clase de tipo SyntaxNode.
Syntaxtokens describe elementos como: identificadores, palabras clave, caracteres especiales. En C #, es un tipo de clase SyntaxToken.
Syntaxtrivia describe elementos que no se compilarán, a saber, espacios, avances de línea, comentarios, directivas de preprocesador. En C #, se define mediante una clase de tipo SyntaxTrivia.
El modelo semántico representa información sobre objetos y sus tipos. Gracias a esta herramienta, puede realizar un análisis profundo y complejo. En C #, se define por una clase de tipo SemanticModel.
Crear un analizador
Para crear un analizador estático, debe instalar el siguiente componente .NETCompilerPlatformSDK.
Las funciones principales que componen cualquier analizador incluyen:
- Registro de acciones.
Las acciones son cambios de código que el analizador debe iniciar para verificar si hay violaciones en el código. Cuando VisualStudio detecta cambios en el código que corresponden a la acción registrada, llama al método analizador registrado. - Crea diagnósticos.
Cuando se detecta una violación, el analizador crea un objeto de diagnóstico utilizado por VisualStudio para notificar al usuario de la violación.
Hay varios pasos para crear y probar un analizador:
- Crea una solución.
- Registre el nombre y la descripción del analizador.
- Advertencias y recomendaciones del analizador de informes.
- Realice una corrección de código para aceptar las recomendaciones.
- Mejora del análisis con pruebas unitarias.
Las acciones se registran en una anulación del método DiagnosticAnalyzer.Initialize (AnalysisContext), donde AnalysisContext es el método en el que se fija la búsqueda del objeto analizado.
El analizador puede proporcionar una o más correcciones de código. Un parche de código identifica los cambios que abordan el problema informado. El usuario elige los cambios desde la interfaz de usuario (bombillas en el editor), y VisualStudio cambia el código. El método RegisterCodeFixesAsync describe cómo cambiar el código.
Ejemplo
Por ejemplo, escribiremos el analizador de campos públicos. Esta aplicación debe advertir al usuario sobre los campos públicos y proporcionar la capacidad de encapsular el campo con una propiedad.
Esto es lo que debes obtener:

Veamos qué se debe hacer para esto.
Primero necesitas crear una solución.

Después de crear la solución, vemos que ya hay tres proyectos.

Necesitamos dos clases:
1) Class AnalyzerPublicFieldsAnalyzer, en el que especificamos los criterios para analizar el código para encontrar campos públicos y una descripción de la advertencia para el usuario.
Indicamos las siguientes propiedades:
public const string DiagnosticId = "PublicField"; private const string Title = "Filed is public"; private const string MessageFormat = "Field '{0}' is public"; private const string Category = "Syntax"; private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
Después de eso, indicamos con qué criterios se realizará el análisis de los campos públicos.
private static void AnalyzeSymbol(SymbolAnalysisContext context) { var fieldSymbol = context.Symbol as IFieldSymbol; if (fieldSymbol != null && fieldSymbol.DeclaredAccessibility == Accessibility.Public && !fieldSymbol.IsConst && !fieldSymbol.IsAbstract && !fieldSymbol.IsStatic && !fieldSymbol.IsVirtual && !fieldSymbol.IsOverride && !fieldSymbol.IsReadOnly && !fieldSymbol.IsSealed && !fieldSymbol.IsExtern) { var diagnostic = Diagnostic.Create(Rule, fieldSymbol.Locations[0], fieldSymbol.Name); context.ReportDiagnostic(diagnostic); } }
Obtenemos un campo de un objeto de tipo IFieldSymbol, que tiene propiedades para definir modificadores de campo, su nombre y ubicación. Lo que necesitamos para el diagnóstico.
Queda por inicializar el analizador especificando en el método anulado
public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); }
2) Ahora procedemos a cambiar el código propuesto por el usuario en función del análisis del código. Esto sucede en la clase AnalyzerPublicFieldsCodeFixProvider.
Para hacer esto, indique lo siguiente:
private const string title = "Encapsulate field"; public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(AnalyzerPublicFieldsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var initialToken = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( CodeAction.Create(title, c => EncapsulateFieldAsync(context.Document, initialToken, c), AnalyzerPublicFieldsAnalyzer.DiagnosticId), diagnostic); }
Y determinamos la capacidad de encapsular el campo con una propiedad en el método EncapsulateFieldAsync.
private async Task<Document> EncapsulateFieldAsync(Document document, SyntaxToken declaration, CancellationToken cancellationToken) { var field = FindAncestorOfType<FieldDeclarationSyntax>(declaration.Parent); var fieldType = field.Declaration.Type; ChangeNameFieldAndNameProperty(declaration.ValueText, out string fieldName, out string propertyName); var fieldDeclaration = CreateFieldDecaration(fieldName, fieldType); var propertyDeclaration = CreatePropertyDecaration(fieldName, propertyName, fieldType); var root = await document.GetSyntaxRootAsync(); var newRoot = root.ReplaceNode(field, new List<SyntaxNode> { fieldDeclaration, propertyDeclaration }); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; }
Para hacer esto, cree un campo privado.
private FieldDeclarationSyntax CreateFieldDecaration(string fieldName, TypeSyntax fieldType) { var variableDeclarationField = SyntaxFactory.VariableDeclaration(fieldType) .AddVariables(SyntaxFactory.VariableDeclarator(fieldName)); return SyntaxFactory.FieldDeclaration(variableDeclarationField) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); }
Luego cree una propiedad pública que regrese y reciba este campo privado.
private PropertyDeclarationSyntax CreatePropertyDecaration(string fieldName, string propertyName, TypeSyntax propertyType) { var syntaxGet = SyntaxFactory.ParseStatement($"return {fieldName};"); var syntaxSet = SyntaxFactory.ParseStatement($"{fieldName} = value;"); return SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxGet)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxSet))); }
Al mismo tiempo, guardamos el tipo y el nombre del campo de origen. El nombre del campo se construye de la siguiente manera "_name", y el nombre de la propiedad "Nombre".
Referencias
- Fuentes de GitHub
- El SDK de la plataforma del compilador .NET