Translate

lunes, 22 de mayo de 2023

Comprehensive Rust 馃


Quiero recomendarles un sitio para aprender rust, que se llama Comprehensive Rust

El sitio nos dice : 

"Este es un curso de Rust de tres d铆as desarrollado por el equipo de Android. El curso cubre el espectro completo de Rust, desde la sintaxis b谩sica hasta temas avanzados como los gen茅ricos y el manejo de errores. Tambi茅n incluye contenido espec铆fico de Android en el 煤ltimo d铆a.

El objetivo del curso es ense帽arte Rust. Suponemos que no sabe nada sobre Rust y esperamos:

  • Brindarle una comprensi贸n integral de la sintaxis y el lenguaje de Rust.
  • Le permite modificar programas existentes y escribir nuevos programas en Rust.
  • Mostrar expresiones idiom谩ticas comunes de Rust."

Test de arquitectura con Roslyn parte 2

Siguiento la idea del post anterior, de testear cosas con roslyn. Vamos a testear que en todo el proyecto, todos los atributos privados comiencen con _ (gui贸n bajo) 

Bueno para esto primero necesitamos 3 dependencias m谩s : 

    <PackageReference Include="Microsoft.Build.Locator" Version="1.5.5" />

    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.5.0" />

    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.5.0" />


Locator es para localizar el sdk y las librer铆as, pienso no estoy muy seguro que tenemos que hacer esto porque estamos en un test (pero corrijanme si estoy equivocado) 

Workspace porque vamos abrir un workspace y msbuild para compilar. (esto esta explicado en este post)

Entonces nuestro test nos queda de la siguiente manera : 

    [Test]

    public async Task The_Field_Private_Should_Start_With_Underscore()

    {

        // Arrange

        string projectPath = @"C:\projects\hat\ArchTestWithRoslyn\test\test.csproj";

        MSBuildLocator.RegisterDefaults();

        using (var workspace = MSBuildWorkspace.Create())

        {

            var project = await workspace.OpenProjectAsync(projectPath);

            var compilation = await project.GetCompilationAsync();

            foreach (var syntaxTree in compilation.SyntaxTrees)

            {

                var nodeRoot = syntaxTree.GetRoot();

                var fields = nodeRoot.DescendantNodes()

                    .OfType<FieldDeclarationSyntax>()

                    .Where(field => field.Modifiers

                                        .Any(modify => 

                                            modify.Kind().Equals(SyntaxKind.PrivateKeyword))

                    && field.Declaration.Variables

                        .Any(aVar => !aVar.Identifier.ValueText.StartsWith("_"))

                    );

                // Assert

                Assert.IsTrue(!fields.Any());

            }

        }

    }


Este test lo que hace es crear un workspace, importar el proyecto (podemos importar soluciones si quisieramos) y luego obtiene el o los arboles sintacticos y luego busca si existe algun atributo privado que no comience con "_".  


Y listo!! 

Por qu茅 son importantes los test de arquitectura.


En el desarrollo de aplicaciones de software, la arquitectura juega un papel fundamental. Define la estructura y el dise帽o de la aplicaci贸n, proporcionando una base s贸lida sobre la cual se construyen todas las funcionalidades. A medida que las aplicaciones se vuelven m谩s complejas, es esencial garantizar que la arquitectura sea s贸lida y cumpla con los requisitos deseados. Aqu铆 es donde entran en juego los test de arquitectura.  

¿Qu茅 son los test de arquitectura? Los test de arquitectura son una pr谩ctica que busca evaluar la robustez y la calidad de la arquitectura de una aplicaci贸n. Se centran en verificar que los componentes clave de la arquitectura funcionen correctamente y cumplan con los requisitos esperados. A diferencia de los test unitarios, que se centran en probar unidades individuales de c贸digo, los test de arquitectura se enfocan en la estructura global de la aplicaci贸n y en c贸mo interact煤an sus diferentes componentes. 

¿Porque es importante verificar la arquitectura? Principalmente porque a medida que desarrollamos podemos no respetar los principios que definimos desde la concepci贸n de la aplicaci贸n. Los test de arquitectura nos permiten mantener esos principios a lo largo del tiempo verificando si se cumplen commit a commit.  

Adem谩s, permiten:  

  • Identificaci贸n temprana de problemas: Los test de arquitectura permiten identificar problemas potenciales en la fase inicial del desarrollo. Al evaluar la arquitectura antes de implementar todas las funcionalidades, es posible detectar problemas de dise帽o, cuellos de botella de rendimiento, dependencias no deseadas, entre otros. Esto ayuda a reducir el costo y el esfuerzo necesario para corregir problemas en etapas m谩s avanzadas del desarrollo. 
  • Mejora de la calidad y mantenibilidad: Una arquitectura s贸lida es fundamental para garantizar la calidad y mantenibilidad a largo plazo de una aplicaci贸n. Los test de arquitectura ayudan a validar que la estructura de la aplicaci贸n cumpla con principios de dise帽o y buenas pr谩cticas. Esto facilita la comprensi贸n del c贸digo, la reutilizaci贸n de componentes y la realizaci贸n de cambios sin afectar negativamente otras partes del sistema. 
  • Alineaci贸n con los requisitos: Los test de arquitectura permiten asegurar que la arquitectura est茅 alineada con los requisitos del negocio y las necesidades de los usuarios. Al realizar pruebas exhaustivas de la arquitectura, se pueden identificar brechas en los requisitos y asegurar que todas las funcionalidades esperadas est茅n cubiertas. Esto ayuda a evitar costosos cambios posteriores debido a requisitos omitidos o malinterpretados. 
  • Rendimiento y escalabilidad: Las aplicaciones modernas deben ser capaces de manejar grandes vol煤menes de datos y un alto n煤mero de usuarios concurrentes. Los test de arquitectura pueden ayudar a identificar cuellos de botella de rendimiento y evaluar la escalabilidad de la aplicaci贸n. Esto permite tomar decisiones informadas sobre el uso de tecnolog铆as adecuadas, la optimizaci贸n de consultas y la distribuci贸n de la carga de trabajo, asegurando un rendimiento 贸ptimo incluso en condiciones de alta demanda. 
  • Gesti贸n de riesgos: Los test de arquitectura ayudan a mitigar los riesgos asociados con el desarrollo de aplicaciones complejas. Permiten identificar y abordar problemas de seguridad, integridad de datos y fiabilidad del sistema. Al probar y validar la arquitectura en diferentes escenarios y condiciones, se pueden detectar posibles vulnerabilidades o debilidades y tomar medidas para corregirlas antes de que se conviertan en problemas reales. 

¿Y c贸mo podemos probar nuestra arquitectura f谩cilmente? Para ello existen varios frameworks y herramientas disponibles para realizar test de arquitectura en las plataformas Java y .NET algunos de los m谩s utilizados son: 

Java: 

  • Arquillian: Es un framework de pruebas de integraci贸n que se enfoca en la creaci贸n de escenarios de prueba realistas para aplicaciones Java. Proporciona soporte para la ejecuci贸n de pruebas en diferentes entornos y contenedores. 
  • JUnit: Aunque principalmente utilizado para test unitarios, JUnit tambi茅n puede ser utilizado para realizar pruebas de integraci贸n y pruebas de arquitectura. Permite la ejecuci贸n de pruebas en paralelo y la creaci贸n de casos de prueba complejos. 

.NET: 

  • NUnit: Similar a JUnit, NUnit es un framework de pruebas para la plataforma .NET. Ofrece una amplia gama de funcionalidades para realizar pruebas de arquitectura, incluyendo aserciones, configuraciones y la ejecuci贸n de pruebas en paralelo. 
  • SpecFlow: Esta herramienta se basa en el concepto de BDD (Behavior-Driven Development) y permite escribir pruebas de aceptaci贸n en un lenguaje natural f谩cilmente comprensible. Es especialmente 煤til para probar la arquitectura desde la perspectiva del comportamiento esperado del sistema. 

ArchUnit es otro framework relevante para realizar test de arquitectura en aplicaciones Java y .NET. ArchUnit(en java) y NArchiUnit(en .NET) es un framework de c贸digo abierto que permite definir y verificar reglas arquitect贸nicas en el c贸digo fuente de una aplicaci贸n. Proporciona una forma declarativa de definir restricciones sobre la estructura y el dise帽o de la arquitectura, como reglas de dependencia entre paquetes, convenciones de nomenclatura, restricciones de visibilidad, entre otros aspectos.  

Al utilizar ArchUnit, los desarrolladores pueden escribir pruebas que verifiquen autom谩ticamente si la arquitectura de la aplicaci贸n cumple con las reglas definidas. Esto permite garantizar la coherencia y la integridad de la arquitectura, evitando que se introduzcan violaciones arquitect贸nicas en el c贸digo. Adem谩s, ArchUnit ofrece una sintaxis expresiva y flexible que facilita la definici贸n de reglas espec铆ficas para cada proyecto.  

Para que los test unitarios sean 煤tiles es importante, correrlos regularmente y para esto herramientas como un servidor de integraci贸n continua son fundamentales.  

Los servidores de integraci贸n continua desempe帽an un papel importante en la ejecuci贸n automatizada de los test de arquitectura. Estos servidores, como Jenkins, Bamboo o Azure DevOps, permiten la configuraci贸n y ejecuci贸n de pipelines de integraci贸n continua, que incluyen la compilaci贸n, pruebas y despliegue automatizado de la aplicaci贸n. Al utilizar un servidor de integraci贸n continua, los test de arquitectura se pueden ejecutar de manera regular y sistem谩tica, lo que garantiza que cualquier cambio o actualizaci贸n en la aplicaci贸n se someta a pruebas de arquitectura antes de ser desplegado en producci贸n. 

Conclusi贸n: Los test de arquitectura son fundamentales para garantizar una base s贸lida y robusta en el desarrollo de aplicaciones de software. Ayudan a identificar problemas tempranamente, mejorar la calidad y mantenibilidad, alinear la arquitectura con los requisitos, garantizar el rendimiento y escalabilidad, y mitigar riesgos. Utilizar frameworks espec铆ficos y aprovechar los servidores de integraci贸n continua facilita su implementaci贸n y automatizaci贸n. No programar estos test puede tener consecuencias negativas, como problemas de escalabilidad, falta de mantenibilidad, vulnerabilidades de seguridad y dificultad para cumplir requisitos. En resumen, los test de arquitectura son una pr谩ctica esencial para asegurar el 茅xito y la calidad de las aplicaciones. 


 

viernes, 19 de mayo de 2023

Emit API de Roslyn

Hasta ahora, hemos analizado principalmente c贸mo podemos usar Roslyn para analizar y manipular el c贸digo fuente. Ahora veremos c贸mo finalizar el proceso de compilaci贸n emiti茅ndolo en el disco o en la memoria. Para comenzar, solo intentaremos emitir una compilaci贸n simple al disco y verificar si tuvo 茅xito o no.

var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class C

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


//Emitting to file is available through an extension method in the Microsoft.CodeAnalysis namespace

var emitResult = compilation.Emit("output.exe", "output.pdb");


//If our compilation failed, we can discover exactly why.

if(!emitResult.Success)

{

    foreach(var diagnostic in emitResult.Diagnostics)

    {

        Console.WriteLine(diagnostic.ToString());

    }

}


Despu茅s de ejecutar este c贸digo, podemos ver que nuestro ejecutable y .pdb se han emitido a Debug/bin/. Podemos hacer doble clic en output.exe y ver que nuestro programa se ejecuta como se esperaba. El archivo .pdb es opcional. Escribir el archivo .pdb en el disco puede llevar bastante tiempo y, a menudo, vale la pena omitir este argumento a menos que realmente se necesite.

A veces es posible que no queramos emitir a disco. Es posible que solo queramos compilar el c贸digo, enviarlo a la memoria y luego ejecutarlo desde la memoria. Para esto, la API de script probablemente tenga m谩s sentido de usar. A煤n as铆, vale la pena conocer nuestras opciones.

var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class MyClass

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


//Emit to stream

var ms = new MemoryStream();

var emitResult = compilation.Emit(ms);


//Load into currently running assembly. Normally we'd probably

//want to do this in an AppDomain

var ourAssembly = Assembly.Load(ms.ToArray());

var type = ourAssembly.GetType("MyClass");


//Invokes our main method and writes "Hello World" 馃檪

type.InvokeMember("Main", BindingFlags.Default | BindingFlags.InvokeMethod, null, null, null);


Finalmente, ¿qu茅 pasa si queremos influir en c贸mo se compila nuestro c贸digo? Es posible que queramos permitir c贸digo no seguro, marcar advertencias como errores o retrasar la firma del ensamblado. Todas estas opciones se pueden personalizar pasando un objeto CSharpCompilationOptions a CSharpCompilation.Create(). Echaremos un vistazo a c贸mo podemos interactuar con algunas de estas propiedades a continuaci贸n.


var tree = CSharpSyntaxTree.ParseText(@"

using System;

public class MyClass

{

    public static void Main()

    {

        Console.WriteLine(""Hello World!"");

        Console.ReadLine();

    }   

}");


//We first have to choose what kind of output we're creating: DLL, .exe etc.

var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication);

options = options.WithAllowUnsafe(true);                                //Allow unsafe code;

options = options.WithOptimizationLevel(OptimizationLevel.Release);     //Set optimization level

options = options.WithPlatform(Platform.X64);                           //Set platform


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree },

    references: new[] { mscorlib },

    options: options);   


En total hay unas veinticinco opciones diferentes disponibles para la personalizaci贸n. B谩sicamente, cualquier opci贸n que tiene la p谩gina de propiedades del proyecto de Visual Studio deber铆a estar disponible aqu铆.

Hay algunos par谩metros opcionales disponibles en Compilation.Emit() que vale la pena analizar. 

xmlDocPath: genera autom谩ticamente documentaci贸n XML basada en los comentarios de documentaci贸n presentes en sus clases, m茅todos, propiedades, etc.

manifestResources: le permite incrustar manualmente recursos como cadenas e im谩genes dentro del ensamblaje emitido. 

win32ResourcesPath: ruta del archivo desde el que se leer谩n los recursos Win32 de la compilaci贸n (en formato RES). 


mi茅rcoles, 17 de mayo de 2023

SymbolVisitor

SymbolVisitor es el an谩logo de SyntaxVisitor, pero se aplica a nivel de s铆mbolo. Desafortunadamente, a diferencia de SyntaxWalker y CSharpSyntaxRewriter, cuando usamos SymbolVisitor debemos construir el c贸digo de andamiaje para visitar todos los nodos.

Para enumerar simplemente todos los tipos disponibles para una compilaci贸n, podemos usar lo siguiente.


public class NamedTypeVisitor : SymbolVisitor

{

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach(var childSymbol in symbol.GetMembers())

        {

            //We must implement the visitor pattern ourselves and 

            //accept the child symbols in order to visit their children

            childSymbol.Accept(this);

        }

    }


    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach (var childSymbol in symbol.GetTypeMembers())

        {

            //Once againt we must accept the children to visit 

            //all of their children

            childSymbol.Accept(this);

        }

    }

}


//Now we need to use our visitor

var tree = CSharpSyntaxTree.ParseText(@"

class MyClass

{

    class Nested

    {

    }

    void M()

    {

    }

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


var visitor = new NamedTypeVisitor();

visitor.Visit(compilation.GlobalNamespace);


Para visitar todos los m茅todos disponibles para una compilaci贸n dada podemos utilizar los siguientes:


public class MethodSymbolVisitor : SymbolVisitor

{

    //NOTE: We have to visit the namespace's children even though

    //we don't care about them. 馃槮

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }

    

    //NOTE: We have to visit the named type's children even though

    //we don't care about them. 馃槮

    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }


    public override void VisitMethod(IMethodSymbol symbol)

    {

        Console.WriteLine(symbol);

    }

}

Es importante tener en cuenta c贸mo  se debe estructurar el c贸digo para poder visitar todos los s铆mbolos que interesan. Si estoy interesado en visitar s铆mbolos de m茅todos, no quiero tener que escribir c贸digo que visite espacios de nombres y tipos.

Con suerte, en alg煤n momento obtendremos una clase SymbolWalker que podamos usar para separar nuestra implementaci贸n del c贸digo transversal. 

¿C贸mo obtenemos una lista de todos los tipos disponibles para una compilaci贸n? as铆 :


public class CustomSymbolFinder

{

    public List<INamedTypeSymbol> GetAllSymbols(Compilation compilation)

    {

        var visitor = new FindAllSymbolsVisitor();

        visitor.Visit(compilation.GlobalNamespace);

        return visitor.AllTypeSymbols;

    }


    private class FindAllSymbolsVisitor : SymbolVisitor

    {

        public List<INamedTypeSymbol> AllTypeSymbols { get; } = new List<INamedTypeSymbol>();


        public override void VisitNamespace(INamespaceSymbol symbol)

        {

            Parallel.ForEach(symbol.GetMembers(), s => s.Accept(this));

        }


        public override void VisitNamedType(INamedTypeSymbol symbol)

        {

            AllTypeSymbols.Add(symbol);

            foreach (var childSymbol in symbol.GetTypeMembers())

            {

                base.Visit(childSymbol);

            }

        }

    }

}

La clase SymbolVisitor probablemente sea apropiada para usos 煤nicos durante la compilaci贸n o para visitar un subconjunto de s铆mbolos disponibles. Por lo menos, vale la pena ser consciente de ello.


viernes, 12 de mayo de 2023

La Gu铆a de Google Cloud que te ayudar谩 a innovar y crecer.

 

jueves, 11 de mayo de 2023

Scripting API de Roslyn

Para instalar la API de Scripting en el proyecto simplemente hay que ejecutar:

Install-Package Microsoft.CodeAnalysis.Scripting -Pre

CSharpScript.EvaluateAsync es probablemente la forma m谩s sencilla de empezar a evaluar expresiones. 


var result = await CSharpScript.EvaluateAsync("5 + 5");

Console.WriteLine(result); // 10


result = await CSharpScript.EvaluateAsync(@"""sample""");

Console.WriteLine(result); // sample


result = await CSharpScript.EvaluateAsync(@"""sample"" + "" string""");

Console.WriteLine(result); // sample string


result = await CSharpScript.EvaluateAsync("int x = 5; int y = 5; x"); //Note the last x is not contained in a proper statement

Console.WriteLine(result); // 5

No todos los scripts devuelven un 煤nico valor. Para secuencias de comandos m谩s complejos, es posible que deseemos realizar un seguimiento del estado o inspeccionar diferentes variables. CSharpScript.RunAsync crea y devuelve un objeto ScriptState que nos permite hacer exactamente esto. 


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""");

ScriptVariable x = state.Variables["x"];

ScriptVariable y = state.Variables["y"];


Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 5

Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 3


Tambi茅n podemos mantener el estado de nuestro script y continuar aplic谩ndole cambios con ScriptState.ContinueWith():


var state = CSharpScript.RunAsync(@"int x = 5; int y = 3; int z = x + y;""").Result;

state = state.ContinueWithAsync("x++; y = 1;").Result;

state = state.ContinueWithAsync("x = x + y;").Result;


ScriptVariable x = state.Variables["x"];

ScriptVariable y = state.Variables["y"];


Console.Write($"{x.Name} : {x.Value} : {x.Type} "); // x : 7

Console.Write($"{y.Name} : {y.Value} : {y.Type} "); // y : 1



Podemos agregar referencias a los archivos DLL que nos gustar铆a usar. Usamos ScriptOptions para proporcionar nuestro script con las MetadataReferences adecuadas.

ScriptOptions scriptOptions = ScriptOptions.Default;

//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Linq.Enumerable).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore);
//Add namespaces
scriptOptions = scriptOptions.AddNamespaces("System");
scriptOptions = scriptOptions.AddNamespaces("System.Linq");
scriptOptions = scriptOptions.AddNamespaces("System.Collections.Generic");

var state = await CSharpScript.RunAsync(@"var x = new List(){1,2,3,4,5};", scriptOptions);
state = await state.ContinueWithAsync("var y = x.Take(3).ToList();");

var y = state.Variables["y"];
var yList = (List)y.Value;
foreach(var val in yList)
{
  Console.Write(val + " "); // Prints 1 2 3
}

Este material es sorprendentemente amplio. El espacio de nombres Microsoft.CodeAnalysis.Scripting est谩 lleno de tipos.


String Interpolation en Java


La interpolaci贸n de cadenas es una forma sencilla y precisa de inyectar valores variables en una cadena. Permite a los usuarios incrustar referencias de variables directamente en literales de cadena procesados. Java carece de soporte nativo para la interpolaci贸n de cadenas en comparaci贸n con lenguajes como Scala.

Sin embargo, existen algunos enfoques para lograr este comportamiento en Java.

Primero, tenemos el operador "+". Podemos usar el operador "+" para concatenar nuestras variables y valores de cadena. La variable se reemplaza por su valor, por lo que logramos la interpolaci贸n o concatenaci贸n de cadenas:


@Test

public void givenTwoString_thenInterpolateWithPlusSign() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = "String " + first + " in " + second + " with some " + second + " examples.";

    assertEquals(EXPECTED_STRING, result);

}


Como podemos ver en el ejemplo anterior, con este operador el String resultante contiene los valores de las variables “interpolados” con otros valores de string. Dado que puede ajustarse para satisfacer necesidades espec铆ficas, este m茅todo de concatenaci贸n de cadenas se encuentra entre los m谩s sencillos y valiosos. Cuando usamos el operador, no necesitamos poner el texto entre comillas.

Otro enfoque es usar el m茅todo format() de la clase String. Al contrario del operador "+", en este caso necesitamos usar marcadores de posici贸n para obtener el resultado esperado en la interpolaci贸n de cadenas:

@Test

public void givenTwoString_thenInterpolateWithFormat() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = String.format("String %s in %s with some %s examples.", first, second, second);

    assertEquals(EXPECTED_STRING, result);

}

Adem谩s, podemos hacer referencia a un argumento espec铆fico si queremos evitar repeticiones de variables en nuestra llamada de formato:

@Test

public void givenTwoString_thenInterpolateWithFormatStringReference() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = String.format("String %1$s in %2$s with some %2$s examples.", first, second);

    assertEquals(EXPECTED_STRING, result);

}

Nuestro siguiente enfoque es la clase StringBuilder. Instanciamos un objeto StringBuilder y luego llamamos a la funci贸n append() para construir el String. En el proceso, nuestras variables se agregan a la cadena resultante:


@Test

public void givenTwoString_thenInterpolateWithStringBuilder() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    StringBuilder builder = new StringBuilder();

    builder.append("String ")

      .append(first)

      .append(" in ")

      .append(second)

      .append(" with some ")

      .append(second)

      .append(" examples.");

    String result = builder.toString();

    assertEquals(EXPECTED_STRING, result);

}


Como podemos ver en el ejemplo de c贸digo anterior, podemos interpolar las cadenas con el texto necesario encadenando la funci贸n de agregar, que acepta el par谩metro como una variable (en este caso, dos cadenas).

El uso de la clase MessageFormat es un m茅todo menos conocido para obtener la interpolaci贸n de cadenas en Java. Con MessageFormat, podemos crear mensajes concatenados sin preocuparnos por el idioma subyacente. Es un m茅todo est谩ndar para crear mensajes orientados al usuario. Toma una colecci贸n de objetos, formatea las cadenas contenidas dentro y las inserta en el patr贸n en las ubicaciones adecuadas.

El m茅todo de formato de MessageFormat es casi id茅ntico al m茅todo de formato de String, excepto por c贸mo se escriben los marcadores de posici贸n. 脥ndices como {0}, {1}, {2}, etc., representan el marcador de posici贸n en esta funci贸n:


@Test

public void givenTwoString_thenInterpolateWithMessageFormat() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String first = "Interpolation";

    String second = "Java";

    String result = MessageFormat.format("String {0} in {1} with some {1} examples.", first, second);

    assertEquals(EXPECTED_STRING, result);

}

En cuanto al rendimiento, StringBuilder solo agrega texto a un b煤fer din谩mico; sin embargo, MessageFormat analiza el formato dado antes de agregar los datos. Como resultado, StringBuilder supera a MessageFormat en t茅rminos de eficiencia.

Finalmente, tenemos StringSubstitutor de Apache Commons. En el contexto de esta clase, los valores se sustituyen por variables incluidas dentro de una Cadena. Esta clase toma un fragmento de texto y reemplaza todas las variables. La definici贸n predeterminada de una variable es ${variableName}. Se pueden usar constructores y m茅todos de conjuntos para modificar el prefijo y los sufijos. La resoluci贸n de los valores de las variables normalmente implica el uso de un mapa. Sin embargo, podemos resolverlos utilizando los atributos del sistema o proporcionando un solucionador de variables especializado:

@Test

public void givenTwoString_thenInterpolateWithStringSubstitutor() {

    String EXPECTED_STRING = "String Interpolation in Java with some Java examples.";

    String baseString = "String ${first} in ${second} with some ${second} examples.";

    String first = "Interpolation";

    String second = "Java";

    Map<String, String> parameters = new HashMap<>();

    parameters.put("first", first);

    parameters.put("second", second);

    StringSubstitutor substitutor = new StringSubstitutor(parameters);

    String result = substitutor.replace(baseString);

    assertEquals(EXPECTED_STRING, result);

}

De nuestro ejemplo de c贸digo, podemos ver que creamos un Map. Los nombres de las claves son los mismos que los nombres de las variables que reemplazaremos en el String. Luego pondremos el valor correspondiente para cada clave en el Map. A continuaci贸n, lo pasaremos como argumento constructor a la clase StringSubstitutor. Finalmente, el objeto instanciado llama a la funci贸n replace(). Esta funci贸n recibe como argumento el texto con los marcadores de posici贸n. Como resultado, obtenemos un texto interpolado.

Como vemos no es tarea f谩cil interpolar strings en java actualmente, pero tenemos la esperanza que en java 21 vendr谩 string template. Esta funcionalidad nos permitir谩 hacer lo siguiente : 

String name = "Joan";

String info = STR."My name is \{name}";

assert info.equals("My name is Joan");   // true

Otro ejemplo puede ser : 

// Embedded expressions can be strings

String firstName = "Bill";

String lastName  = "Duck";

String fullName  = STR."\{firstName} \{lastName}"; // "Bill Duck"

String sortName  = STR."\{lastName}, \{firstName}"; // "Duck, Bill"

// Embedded expressions can perform arithmetic

int x = 10, y = 20;

String s = STR."\{x} + \{y} = \{x + y}"; // "10 + 20 = 30"

Que opinan?


Dejo link: https://openjdk.org/jeps/430



lunes, 8 de mayo de 2023

SyntaxAnnotation

Puede ser complicado realizar un seguimiento de los nodos al aplicar cambios a los 谩rboles de sintaxis. Cada vez que "cambiamos" un 谩rbol, en realidad estamos creando una copia del mismo con nuestros cambios aplicados a ese nuevo 谩rbol. En el momento en que hacemos eso, cualquier pieza de sintaxis a la que tuvi茅ramos referencias anteriormente se vuelve inv谩lida en el contexto del nuevo 谩rbol.

¿Qu茅 significa esto en la pr谩ctica? Es dif铆cil hacer un seguimiento de los nodos de sintaxis cuando cambiamos los 谩rboles de sintaxis.

Una pregunta reciente de Stack Overflow se refiri贸 a esto. ¿C贸mo podemos obtener el s铆mbolo de una clase que acabamos de agregar a un documento? Podemos crear una nueva declaraci贸n de clase, pero en el momento en que la agregamos al documento, perdemos el rastro del nodo. Entonces, ¿c贸mo podemos realizar un seguimiento de la clase para que podamos obtener el s铆mbolo una vez que la hayamos agregado al documento?

La respuesta: usar una anotaci贸n de sintaxis

Una SyntaxAnnotation es b谩sicamente una pieza de metadatos que podemos adjuntar a una pieza de sintaxis. A medida que manipulamos el 谩rbol, la anotaci贸n se adhiere a esa parte de la sintaxis, lo que facilita su b煤squeda.


AdhocWorkspace workspace = new AdhocWorkspace();

Project project = workspace.AddProject("SampleProject", LanguageNames.CSharp);


//Attach a syntax annotation to the class declaration

var syntaxAnnotation = new SyntaxAnnotation();

var classDeclaration = SyntaxFactory.ClassDeclaration("MyClass")

.WithAdditionalAnnotations(syntaxAnnotation);


var compilationUnit = SyntaxFactory.CompilationUnit().AddMembers(classDeclaration);


Document document = project.AddDocument("SampleDocument.cs", compilationUnit);

SemanticModel semanticModel = document.GetSemanticModelAsync().Result;


//Use the annotation on our original node to find the new class declaration

var changedClass = document.GetSyntaxRootAsync().Result.DescendantNodes().OfType<ClassDeclarationSyntax>()

.Where(n => n.HasAnnotation(syntaxAnnotation)).Single();

var symbol = semanticModel.GetDeclaredSymbol(changedClass);



Hay un par de sobrecargas disponibles al crear una SyntaxAnnotation. Podemos especificar Tipo y Datos para adjuntarlos a piezas de sintaxis. Los datos se utilizan para adjuntar informaci贸n adicional a una parte de la sintaxis que nos gustar铆a recuperar m谩s tarde. Tipo es un campo que podemos usar para buscar anotaciones de sintaxis.

Entonces, en lugar de buscar la instancia exacta de nuestra anotaci贸n en cada nodo, podr铆amos buscar anotaciones seg煤n su tipo:

AdhocWorkspace workspace = new AdhocWorkspace();
Project project = workspace.AddProject("Test", LanguageNames.CSharp);

string annotationKind = "SampleKind";
var syntaxAnnotation = new SyntaxAnnotation(annotationKind);
var classDeclaration = SyntaxFactory.ClassDeclaration("MyClass")
.WithAdditionalAnnotations(syntaxAnnotation);

var compilationUnit = SyntaxFactory.CompilationUnit().AddMembers(classDeclaration);

Document document = project.AddDocument("Test.cs", compilationUnit);
SemanticModel semanticModel = await document.GetSemanticModelAsync();
var newAnnotation = new SyntaxAnnotation("test");

//Just search for the Kind instead
var root = await document.GetSyntaxRootAsync();
var changedClass = root.GetAnnotatedNodes(annotationKind).Single();

var symbol = semanticModel.GetDeclaredSymbol(changedClass);


Esta es solo una de las pocas formas diferentes de lidiar con los 谩rboles inmutables de Roslyn. Probablemente no sea el m谩s f谩cil de usar si est谩 realizando varios cambios y necesita realizar un seguimiento de varios nodos de sintaxis. (Si ese es el caso, recomendar铆a el DocumentEditor). Dicho esto, es bueno tenerlo en cuenta para que pueda usarlo cuando tenga sentido.

domingo, 7 de mayo de 2023

Test de arquitectura con Roslyn

Supongamos que queremos testear diferentes reglas que debe seguir el c贸digo general, por ejemplo "todos los nombres de los parametros de los metodos de los servicios deben finalizar con DTO" 

Para esto podemos utilizar Roslyn, veamos un ejemplo: 


     [Test]

    public void The_Parameters_Of_Service_Methods_Should_End_With_DTO()

    {

        // Arrange

        var program = @" public class ModelClassExample

                         {

                         } 


                         public class ExampleService

                         {

                             public bool Test1Method() => true;

                             public bool Test2Method(ModelClassExample example) => true;

                         }";


        var nodeRoot = GetNodeRoot(program);

        var classes = nodeRoot.DescendantNodes()

            .OfType<ClassDeclarationSyntax>()

            .Where(clazz => clazz.Identifier

                                .ToString().EndsWith("Service")

                            && clazz.DescendantNodes()

                                .OfType<MethodDeclarationSyntax>()

                                .Any(method => method.ParameterList.Parameters

                                    .Any(

                                    parameter => !parameter.Identifier.ToString().EndsWith("DTO")

                                )));

        

        // Assert

        Assert.IsTrue(!classes.Any());

    }


Este test no termina bien. Veamos que hace. 

Primero utilizo una variable de tipo string "program" donde pongo mi c贸digo, en un ejemplo m谩s real podria leer el archivo que tiene el c贸digo o mejor aun traerme todo el projecto con Roslyn. 

Segundo genero el SyntaxTree y retorno el nodo root con esta funci贸n que hice : 

    private SyntaxNode GetNodeRoot(string program) {

        var tree = CSharpSyntaxTree.ParseText(program);

        return tree.GetRoot();

    }

Tercero, busco las clases que su nombre terminan en "Service" y me fijo en esas clases si hay un metodo que tenga un parametro que no termine en "DTO". 

Por ultimo chequeo que no haya ninguna clase que rompa la regla, en este caso si la hay porque en mi c贸digo, si hay una clase que finaliza en service y tiene un metodo que tiene un parametro que no finaliza en DTO. 

 


jueves, 4 de mayo de 2023

Edici贸n de documentos con DocumentEditor

Una desventaja de la inmutabilidad de Roslyn es que a veces puede resultar complicado aplicar varios cambios a un documento o 谩rbol de sintaxis. La inmutabilidad significa que cada vez que aplicamos cambios a un 谩rbol de sintaxis, se nos proporciona un 谩rbol de sintaxis completamente nuevo. De forma predeterminada, no podemos comparar nodos entre 谩rboles, entonces, ¿qu茅 hacemos cuando queremos realizar varios cambios en un 谩rbol de sintaxis?

Roslyn nos da cuatro opciones:

  • Use CSharpSyntaxRewriter
  • Usar anotaciones 
  • Usar TrackNodes()
  • Usar el DocumentEditor

El Editor de documentos nos permite realizar m煤ltiples cambios en un documento y obtener el documento resultante despu茅s de que se hayan aplicado los cambios. El DocumentEditor es una clase que hereda de SyntaxEditor.

Usaremos el DocumentEditor para cambiar:


char key = Console.ReadKey();

if(key == 'A')

{

    Console.WriteLine("You pressed A");

}

else

{

    Console.WriteLine("You didn't press A");

}


a:


char key = Console.ReadKey();

if(key == 'A')

{

    LogConditionWasTrue();

    Console.WriteLine("You pressed A");

}

else

{

    Console.WriteLine("You didn't press A");

    LogConditionWasFalse();

}


Usaremos DocumentEditor para insertar simult谩neamente una invocaci贸n antes de la primera Console.WriteLine() y para insertar otra despu茅s de la segunda.

 Por lo general, obtendr谩 un documento de un espacio de trabajo :


var mscorlib = MetadataReference.CreateFromAssembly(typeof(object).Assembly);

var workspace = new AdhocWorkspace();

var projectId = ProjectId.CreateNewId();

var versionStamp = VersionStamp.Create();

var projectInfo = ProjectInfo.Create(projectId, versionStamp, "NewProject", "projName", LanguageNames.CSharp);

var newProject = workspace.AddProject(projectInfo);

var sourceText = SourceText.From(@"

class C

{

    void M()

    {

        char key = Console.ReadKey();

        if (key == 'A')

        {

            Console.WriteLine(""You pressed A"");

        }

        else

        {

            Console.WriteLine(""You didn't press A"");

        }

    }

}");

var document = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText);

var syntaxRoot = await document.GetSyntaxRootAsync();

var ifStatement = syntaxRoot.DescendantNodes().OfType<IfStatementSyntax>().Single();


var conditionWasTrueInvocation =

SyntaxFactory.ExpressionStatement(

    SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("LogConditionWasTrue"))

    .WithArgumentList(

                    SyntaxFactory.ArgumentList()

                    .WithOpenParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.OpenParenToken))

                    .WithCloseParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.CloseParenToken))))

            .WithSemicolonToken(

                SyntaxFactory.Token(

                    SyntaxKind.SemicolonToken));


var conditionWasFalseInvocation =

SyntaxFactory.ExpressionStatement(

    SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName("LogConditionWasFalse"))

    .WithArgumentList(

                    SyntaxFactory.ArgumentList()

                    .WithOpenParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.OpenParenToken))

                    .WithCloseParenToken(

                        SyntaxFactory.Token(

                            SyntaxKind.CloseParenToken))))

            .WithSemicolonToken(

                SyntaxFactory.Token(

                    SyntaxKind.SemicolonToken));


//Finally… create the document editor

var documentEditor = await DocumentEditor.CreateAsync(document);

//Insert LogConditionWasTrue() before the Console.WriteLine()

documentEditor.InsertBefore(ifStatement.Statement.ChildNodes().Single(), conditionWasTrueInvocation);

//Insert LogConditionWasFalse() after the Console.WriteLine()

documentEditor.InsertAfter(ifStatement.Else.Statement.ChildNodes().Single(), conditionWasFalseInvocation);


var newDocument = documentEditor.GetChangedDocument();


Todos los m茅todos familiares de SyntaxNode est谩n aqu铆. Podemos Insertar, Reemplazar y Eliminar nodos como mejor nos parezca, todo basado en nodos en nuestro 谩rbol de sintaxis original. Mucha gente encuentra este enfoque m谩s intuitivo que construir un CSharpSyntaxRewriter completo.

¿C贸mo podemos saber qu茅 tipos de nodos son compatibles entre s铆? No creo que haya una buena respuesta aqu铆. Esencialmente, tenemos que aprender qu茅 nodos son compatibles nosotros mismos. Como de costumbre, Syntax Visualizer y Roslyn Quoter son las mejores herramientas para determinar qu茅 tipo de nodos debe crear.

Vale la pena se帽alar que DocumentEditor expone el SemanticModel de su documento original. Es posible que necesite esto cuando edite el documento original y tome decisiones sobre lo que le gustar铆a cambiar.

Tambi茅n vale la pena se帽alar que el SyntaxEditor subyacente expone un SyntaxGenerator que puede usar para crear nodos de sintaxis sin depender de SyntaxFactory, que es m谩s detallado.

Primeros pasos con Phoenix parte 9

 


Seguimos la idea del post anterior: Phoenix adopta el dise帽o de enchufe o plugs para funcionalidad componible. 

Por ejemplo los Endpoints estan organizados por plugs: 

defmodule HelloWeb.Endpoint do

  ...


  plug :introspect

  plug HelloWeb.Router


Los conectores Endpoint predeterminados hacen bastante trabajo. Aqu铆 est谩n en orden:

Plug.Static: sirve activos est谩ticos. Dado que este complemento viene antes que el registrador, las solicitudes de activos est谩ticos no se registran.

Phoenix.LiveDashboard.RequestLogger: configura el registrador de solicitudes para Phoenix LiveDashboard, esto le permitir谩 tener la opci贸n de pasar un par谩metro de consulta para transmitir registros de solicitudes o habilitar/deshabilitar una cookie que transmite registros de solicitudes desde su tablero.

Plug.RequestId: genera un ID de solicitud 煤nico para cada solicitud.

Plug.Telemetry: agrega puntos de instrumentaci贸n para que Phoenix pueda registrar la ruta de la solicitud, el c贸digo de estado y la hora de la solicitud de forma predeterminada.

Plug.Parsers: analiza el cuerpo de la solicitud cuando hay disponible un analizador conocido. De forma predeterminada, este complemento puede manejar contenido codificado en URL, de varias partes y JSON (con Jason). El cuerpo de la solicitud se deja intacto si el tipo de contenido de la solicitud no se puede analizar.

Plug.MethodOverride: convierte el m茅todo de solicitud en PUT, PATCH o DELETE para solicitudes POST con un par谩metro _method v谩lido.

Plug.Head: convierte las solicitudes HEAD en solicitudes GET y elimina el cuerpo de la respuesta

Plug.Session: un complemento que configura la gesti贸n de sesiones. Tenga en cuenta que fetch_session/2 a煤n debe llamarse expl铆citamente antes de usar la sesi贸n, ya que este complemento solo configura c贸mo se obtiene la sesi贸n.

En el medio del endpoint, tambi茅n hay un bloque condicional:


  if code_reloading? do

    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket

    plug Phoenix.LiveReloader

    plug Phoenix.CodeReloader

    plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello

  end


Este bloque solo se ejecuta en desarrollo. Permite:

  • recarga en vivo: si cambia un archivo CSS, se actualizan en el navegador sin actualizar la p谩gina;
  • recarga de c贸digo, para que podamos ver los cambios en nuestra aplicaci贸n sin reiniciar el servidor;
  • verificar el estado del repositorio, lo que garantiza que nuestra base de datos est茅 actualizada y, de lo contrario, genera un error legible y procesable.


En el enrutador, podemos declarar enchufes dentro de tuber铆as:


defmodule HelloWeb.Router do

  use HelloWeb, :router

  pipeline :browser do

    plug :accepts, ["html"]

    plug :fetch_session

    plug :fetch_live_flash

    plug :put_root_layout, {HelloWeb.LayoutView, :root}

    plug :protect_from_forgery

    plug :put_secure_browser_headers

    plug HelloWeb.Plugs.Locale, "en"

  end

  scope "/", HelloWeb do

   pipe_through :browser

    get "/", PageController, :index

  end


Las rutas se definen dentro de los 谩mbitos y los 谩mbitos pueden canalizarse a trav茅s de m煤ltiples canalizaciones. Una vez que una ruta coincide, Phoenix invoca todos los complementos definidos en todas las tuber铆as asociadas a esa ruta. Por ejemplo, acceder a "/" se canalizar谩 a trav茅s de la canalizaci贸n del navegador, invocando en consecuencia todos sus conectores.

Como veremos en la gu铆a de enrutamiento, las tuber铆as en s铆 mismas son tapones. All铆, tambi茅n discutiremos todos los complementos en la tuber铆a :browser.

Finalmente, los controladores tambi茅n son enchufes, por lo que podemos hacer:


defmodule HelloWeb.PageController do

  use HelloWeb, :controller

  plug HelloWeb.Plugs.Locale, "en"


En particular, los complementos del controlador brindan una funci贸n que nos permite ejecutar complementos solo dentro de ciertas acciones. Por ejemplo, puedes hacer:

defmodule HelloWeb.PageController do

  use HelloWeb, :controller

 plug HelloWeb.Plugs.Locale, "en" when action in [:index]


Y el complemento solo se ejecutar谩 para la acci贸n de 铆ndice.

Al cumplir con el contrato de conexi贸n, convertimos una solicitud de aplicaci贸n en una serie de transformaciones expl铆citas. No se detiene ah铆. Para ver realmente qu茅 tan efectivo es el dise帽o de Plug, imaginemos un escenario en el que necesitamos verificar una serie de condiciones y luego redirigir o detenernos si una condici贸n falla. Sin enchufe, terminar铆amos con algo como esto:



defmodule HelloWeb.MessageController do

  use HelloWeb, :controller

  def show(conn, params) do

    case Authenticator.find_user(conn) do

      {:ok, user} ->

        case find_message(params["id"]) do

          nil ->

            conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/")

          message ->

            if Authorizer.can_access?(user, message) do

              render(conn, :show, page: message)

            else

              conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/")

            end

        end

      :error ->

        conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/")


    end

  end

end


¿Observa c贸mo unos pocos pasos de autenticaci贸n y autorizaci贸n requieren anidamiento y duplicaci贸n complicados? Mejoremos esto con un par de enchufes.


defmodule HelloWeb.MessageController do

  use HelloWeb, :controller

  plug :authenticate

  plug :fetch_message

  plug :authorize_message

  def show(conn, params) do

    render(conn, :show, page: conn.assigns[:message])

  end


  defp authenticate(conn, _) do

   case Authenticator.find_user(conn) do

      {:ok, user} ->

        assign(conn, :user, user)

      :error ->

        conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") |> halt()

    end

  end

  defp fetch_message(conn, _) do

    case find_message(conn.params["id"]) do

      nil ->

        conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") |> halt()

      message ->

        assign(conn, :message, message)

    end

  end


  defp authorize_message(conn, _) do

    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do

      conn

    else

      conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt()

    end

  end

end


Para que todo esto funcione, convertimos los bloques de c贸digo anidados y usamos halt(conn) cada vez que llegamos a una ruta de error. La funcionalidad halt(conn) es esencial aqu铆: le dice a Plug que no se debe invocar el siguiente plug.

Al final del d铆a, al reemplazar los bloques de c贸digo anidados con una serie aplanada de transformaciones de complemento, podemos lograr la misma funcionalidad de una manera mucho m谩s componible, clara y reutilizable.


mi茅rcoles, 3 de mayo de 2023

Introducci贸n a el modelo semantico de Roslyn

Hasta este punto, hemos estado trabajando con c贸digo C# en un nivel puramente sint谩ctico. Podemos encontrar declaraciones de propiedades, pero no podemos rastrear referencias a esta propiedad dentro de nuestro c贸digo fuente. Podemos identificar invocaciones, pero no podemos decir qu茅 se est谩 invocando. Y Dios nos ayude si queremos tratar de resolver los problemas realmente dif铆ciles como la resoluci贸n de sobrecarga.

La capa sem谩ntica es donde realmente brilla el poder de Roslyn. El modelo sem谩ntico de Roslyn puede responder todas las preguntas dif铆ciles de tiempo de compilaci贸n que podamos tener. Sin embargo, este poder tiene un costo. Consultar el modelo sem谩ntico suele ser m谩s costoso que consultar los 谩rboles de sintaxis. Esto se debe a que solicitar un modelo sem谩ntico a menudo desencadena una compilaci贸n.

Hay 3 formas diferentes de solicitar el modelo sem谩ntico:

1. Document.GetSemanticModel()

2. Compilation.GetSemanticModel(SyntaxTree)

3. Diferentes contextos como AnalysisContexts, CodeBlockStartAnalysisContext.SemanticModel y SemanticModelAnalysisContext.SemanticModel

Para evitar el problema de configurar nuestro propio espacio de trabajo, simplemente crearemos compilaciones para 谩rboles de sintaxis individuales de la siguiente manera:


var tree = CSharpSyntaxTree.ParseText(@"

public class MyClass 

{

int MyMethod() { return 0; }

}");


var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


Antes de continuar, vale la pena tomarse un momento para analizar los S铆mbolos. Los programas de C# se componen de elementos 煤nicos, como tipos, m茅todos, propiedades, etc. Los s铆mbolos representan casi todo lo que el compilador sabe sobre cada uno de estos elementos 煤nicos.

En un nivel alto, cada s铆mbolo contiene informaci贸n sobre:

  • Donde estos elementos se declaran en fuente o metadatos (Puede haber venido de un ensamblado externo)
  • Dentro de qu茅 espacio de nombres y tipo existe este s铆mbolo
  • Varias verdades acerca de que el s铆mbolo es abstracto, est谩tico, sellado, etc.

Tambi茅n se puede descubrir otra informaci贸n m谩s dependiente del contexto. Cuando se trata de m茅todos, IMethodSymbol nos permite determinar:

  • Si el m茅todo oculta un m茅todo base.
  • El s铆mbolo que representa el tipo de retorno del m茅todo.
  • El m茅todo de extensi贸n del que se sobreescribio este s铆mbolo.

El modelo sem谩ntico es nuestro puente entre el mundo de la sintaxis y el mundo de los s铆mbolos.

SemanticModel.GetDeclaredSymbol() acepta la sintaxis de declaraci贸n y proporciona el s铆mbolo correspondiente.

SemanticModel.GetSymbolInfo() acepta la sintaxis de expresi贸n (p. ej., InvocationExpressionSyntax) y devuelve un s铆mbolo. Si el modelo no pudo resolver con 茅xito un s铆mbolo, proporciona s铆mbolos candidatos que pueden servir como mejores conjeturas.

A continuaci贸n, recuperamos el s铆mbolo de un m茅todo a trav茅s de su sintaxis de declaraci贸n. Luego recuperamos el mismo s铆mbolo, pero a trav茅s de una invocaci贸n (Invocaci贸nExpresi贸nSyntax) en su lugar.


var tree = CSharpSyntaxTree.ParseText(@"

public class MyClass {

int Method1() { return 0; }

void Method2()

{

int x = Method1();

}

}

}");


var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);

var compilation = CSharpCompilation.Create("MyCompilation",

syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


//Looking at the first method symbol

var methodSyntax = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();

var methodSymbol = model.GetDeclaredSymbol(methodSyntax);


Console.WriteLine(methodSymbol.ToString());         //MyClass.Method1()

Console.WriteLine(methodSymbol.ContainingSymbol);   //MyClass

Console.WriteLine(methodSymbol.IsAbstract);         //false


//Looking at the first invocation

var invocationSyntax = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>().First();

var invokedSymbol = model.GetSymbolInfo(invocationSyntax).Symbol; //Same as MyClass.Method1


Console.WriteLine(invokedSymbol.ToString());         //MyClass.Method1()

Console.WriteLine(invokedSymbol.ContainingSymbol);   //MyClass

Console.WriteLine(invokedSymbol.IsAbstract);         //false


Console.WriteLine(invokedSymbol.Equals(methodSymbol)); //true


Una instancia de SemanticModel almacena en cach茅 s铆mbolos locales e informaci贸n sem谩ntica. Por lo tanto, es mucho m谩s eficiente usar una sola instancia de SemanticModel cuando se hacen varias preguntas sobre un 谩rbol de sintaxis, porque la informaci贸n de la primera pregunta se puede reutilizar. Esto tambi茅n significa que mantener una instancia de SemanticModel durante mucho tiempo puede evitar que una cantidad significativa de memoria se recopile como basura.

Esencialmente, Roslyn le permite hacer el equilibrio entre la memoria y la computaci贸n. Al consultar el modelo sem谩ntico de forma repetitiva, puede ser de su inter茅s mantener una instancia del mismo, en lugar de solicitar un nuevo modelo de una compilaci贸n o documento.

jueves, 27 de abril de 2023

Trabajando con Workspaces con Roslyn

Hasta este punto, simplemente hemos estado construyendo 谩rboles de sintaxis a partir de cadenas. Este enfoque funciona bien cuando se crean muestras cortas, pero a menudo nos gustar铆a trabajar con soluciones completas. 

Los Workspaces son el nodo ra铆z de una jerarqu铆a de C# que consta de una soluci贸n, proyectos secundarios y documentos secundarios. Un principio fundamental dentro de Roslyn es que la mayor铆a de los objetos son inmutables. Esto significa que no podemos aferrarnos a una referencia a una soluci贸n y esperar que est茅 actualizada para siempre. En el momento en que se realice un cambio, esta soluci贸n quedar谩 obsoleta y se habr谩 creado una nueva soluci贸n actualizada. Los espacios de trabajo son nuestro nodo ra铆z. A diferencia de las soluciones, los proyectos y los documentos, no dejar谩n de ser v谩lidos y siempre contendr谩n una referencia a la soluci贸n actual m谩s actualizada. Hay cuatro variantes de Workspace a considerar:

Workspaces:  La clase base abstracta para todos los dem谩s espacios de trabajo. Es un poco falso afirmar que es una variante del espacio de trabajo, ya que nunca tendr谩s una instancia de ella. En cambio, esta clase sirve como una especie de API en torno a la cual se pueden crear implementaciones de espacios de trabajo reales. Puede ser tentador pensar en 谩reas de trabajo 煤nicamente dentro del contexto de Visual Studio. Despu茅s de todo, para la mayor铆a de los desarrolladores de C#, esta es la 煤nica forma en que hemos tratado las soluciones y los proyectos. Sin embargo, Workspace est谩 destinado a ser agn贸stico en cuanto a la fuente f铆sica de los archivos que representa. Las implementaciones individuales pueden almacenar los archivos en el sistema de archivos local, dentro de una base de datos o incluso en una m谩quina remota. Uno simplemente hereda de esta clase y anula las implementaciones vac铆as de Workspace como mejor le parezca.

MSBuildWorkspace:  Un espacio de trabajo creado para manejar archivos de soluci贸n (.sln) y proyecto (.csproj, .vbproj) de MSBuild. Desafortunadamente, actualmente no puede escribir en archivos .sln, lo que significa que no podemos usarlo para agregar proyectos o crear nuevas soluciones.

El siguiente ejemplo muestra c贸mo podemos iterar sobre todos los documentos en una soluci贸n:


string solutionPath = @"C:\Users\…\PathToSolution\MySolution.sln";

var msWorkspace = MSBuildWorkspace.Create();


var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

foreach (var project in solution.Projects)

{

foreach (var document in project.Documents)

{

Console.WriteLine(project.Name + "\t\t\t" + document.Name);

}

}


AdhocWorkspace : Un espacio de trabajo que permite agregar archivos de soluci贸n y proyecto manualmente. Se debe tener en cuenta que la API para agregar y eliminar elementos de la soluci贸n es diferente dentro de AdhocWorkspace en comparaci贸n con los otros espacios de trabajo. En lugar de llamar a TryApplyChanges(), se proporcionan m茅todos para agregar proyectos y documentos en el nivel del espacio de trabajo. Este espacio de trabajo est谩 destinado a aquellos que solo necesitan una forma r谩pida y sencilla de crear un espacio de trabajo y agregarle proyectos y documentos.


var workspace = new AdhocWorkspace();

string projName = "NewProject";

var projectId = ProjectId.CreateNewId();

var versionStamp = VersionStamp.Create();

var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp);

var newProject = workspace.AddProject(projectInfo);

var sourceText = SourceText.From("class A {}");

var newDocument = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText);


foreach (var project in workspace.CurrentSolution.Projects)

{

foreach (var document in project.Documents)

{

Console.WriteLine(project.Name + "\t\t\t" + document.Name);

}

}

VisualStudioWorkspace : El espacio de trabajo activo consumido dentro de los paquetes de Visual Studio. Como este espacio de trabajo est谩 estrechamente integrado con Visual Studio, es dif铆cil proporcionar un peque帽o ejemplo sobre c贸mo usar este espacio de trabajo. 



Clean

 


Clean es un lenguaje de programaci贸n funcional de prop贸sito general, dise帽ado para hacer que la programaci贸n sea m谩s segura, eficiente y f谩cil de entender. Clean es un lenguaje de programaci贸n de alto nivel que ofrece muchas caracter铆sticas avanzadas de programaci贸n funcional, como la inferencia de tipos, el orden superior y la recursividad estructural.

Caracter铆sticas principales de Clean:

  • Sintaxis clara: Clean ofrece una sintaxis clara y legible que permite una f谩cil comprensi贸n del c贸digo y facilita el mantenimiento y la depuraci贸n.
  • Inferencia de tipos: Clean utiliza la inferencia de tipos, lo que significa que el compilador puede determinar el tipo de datos de una variable sin que el programador tenga que especificarlo expl铆citamente.
  • Recursividad estructural: Clean ofrece una t茅cnica de recursividad estructural que garantiza la terminaci贸n de una funci贸n recursiva y previene la ocurrencia de bucles infinitos.
  • Orden superior: Clean admite el uso de funciones de orden superior, lo que significa que las funciones pueden tomar otras funciones como argumentos y devolver funciones como resultados.
  • Gesti贸n autom谩tica de memoria: Clean maneja autom谩ticamente la gesti贸n de memoria, lo que significa que el programador no tiene que preocuparse por la asignaci贸n y desasignaci贸n de memoria.


Ventajas de Clean:

  • Facilita el mantenimiento y la depuraci贸n: La sintaxis clara y legible de Clean facilita el mantenimiento y la depuraci贸n del c贸digo.
  • Seguridad: Clean garantiza que los programas sean seguros mediante la eliminaci贸n de errores comunes de programaci贸n, como la asignaci贸n de memoria no v谩lida y la divisi贸n por cero.
  • Eficiencia: Clean es un lenguaje de programaci贸n muy eficiente que utiliza t茅cnicas avanzadas de optimizaci贸n de c贸digo para maximizar el rendimiento.
  • Escalabilidad: Clean es un lenguaje de programaci贸n escalable que se adapta bien a proyectos grandes y complejos.


En resumen, Clean es un lenguaje de programaci贸n funcional que ofrece muchas caracter铆sticas avanzadas de programaci贸n funcional, como la inferencia de tipos, el orden superior y la recursividad estructural. Clean es un lenguaje de programaci贸n seguro, eficiente, escalable y f谩cil de entender que es ideal para proyectos de desarrollo de software de alta calidad.


Veamos un ejemplo : 


module Main

import StdEnv


factorial :: Int -> Int

factorial n = if n == 0 then 1 else n * factorial (n - 1)


Start w = writeln (show (factorial 5))


En este ejemplo, la funci贸n factorial toma un argumento entero n y calcula el factorial de n utilizando una definici贸n recursiva. Si n es cero, la funci贸n devuelve 1. De lo contrario, la funci贸n multiplica n por el factorial de n-1. La funci贸n show convierte el resultado en una cadena y la funci贸n writeln imprime la cadena en la salida est谩ndar.

El programa principal es la funci贸n Start, que utiliza writeln para imprimir el resultado del c谩lculo del factorial de 5. El tipo w en la declaraci贸n Start w indica que w no se utiliza en el programa y se puede ignorar.

Dejo link : https://clean-lang.org/