Ha pasado un tiempo desde que escribí sobre Application Checker en 2019, y aquí vuelvo a estar. En este post voy a hablar sobre SocrateX y XQuery también, y veremos cómo generar los archivos y bases de datos para analizar el código.

Fake Socrates
Este SocrateX es falsoX

Si quieres aprender más sobre App Checker o SocrateX, puedes leer estos recursos además del post que he enlazado arriba:

¿Qué hay de nuevo?

Qué ha cambiado para que escriba sobre esto de nuevo? Dos cosas:

Primero de todo, Peter Villadsen ha empezado a escribir un blog en Peter’s X++ developer Blog, y ha empezado con dos posts sobre SocrateX.

Estos dos posts son la inspiración y base de lo que estoy escribiendo aquí, podéis leer muchas de las cosas que voy a contar en sus posts, así que os recomiendo que vayáis a su blog y los leáis.

La segunda razón es que desde la versión 10.0.17 se han actualizado las opciones al compilar, y ahora podemos ejecutar las reglas de App checker sin instalar BaseX.:

App checker en las opciones de compilar para Dynamics 365 en Visual Studio
App checker en las opciones de compilar para Dynamics 365 en Visual Studio

Hay tres nuevas opciones en la parte inferior:

  • «Run Socratex Checks«: si queremos usar esta opción, tenemos que instalar BaseX. Esta opción generará los ASTs (luego vemos qué son) y creará una base de datos de BaseX para los paquetes/modelos que hayamos seleccionado. También se ejecutan las reglas de App Checker.
  • «Run Appchecker Rules«: esta opción ejecutará las queries que se encuentran en K:\AosService\PackagesLocalDirectory\bin\AppCheckerRules\X++. Si añades tus propias reglas también se ejecutarán. Esta opción no genera ASTs.
  • «Log Appchecker diagnostics as errors«: la anterior opción sólo muestra warnings, pero si seleccionamos esta también, Appchecker convertirá los warnings en errores y nuestra build fallará. Aunque podemos marcar esta opción en solitario, no ejecutará las reglas de App Checker, es necesario marcar la opción anterior para ello.

¿Qué es SocrateX?

SocrateX es una herramienta desarrollada por Microsoft que nos ayuda a entender y analizar nuestro código X++ que está guardado en una base de datos XML de BaseX.

Socratex: A tool for reasoning over source code.

This tool allows you to reason over source code as if it were data stored in a database. It works by submitting queries in the XQuery language towards a server where a dedicated XML database handles the queries against a repository of XML documents that describe the source code corpus in a specific language.

https://github.com/microsoft/Dynamics365FO-AppChecker/tree/master/tools

Cuando le decimos al compilador que genere los Abstract Syntax Trees (ASTs) de nuestro código… espera, qué son los Abstract Syntax Trees? Es una forma de representar la estructira de nuestro código, en este caso, traducido al XML. Este XML se guardará en una base de datos de BaseX y podremos explorar el código usando XQuery (que ahora veremos).

¿Y qué es XQuery?

XQuery es un lenguaje que usaremos para navegar por los ASTs que se han generado a partir de nuestro código y que están en la base de datos de BaseX. Todas las queries de AppChecker que se usan para comprobar nuestro código están hechas con XQuery, y si abrimos alguna, por ejemplo, la ExtensionsWithoutPrefix.xq, veremos esto:

<Diagnostics Category='Mandatory' href='docs.microsoft.com/Socratex/ExtensionsWithoutPrefix' Version='1.0'>
{
  for $a in /Class
  where $a/AttributeList/Attribute[@Name = "ExtensionOf"]
  let $targetClass := $a/AttributeList/Attribute[@Name = "ExtensionOf"]/AttributeExpression/IntrinsicAttributeLiteral[@FunctionName = "classStr"]/string(@Arg1)
  where $a/@Name = fn:concat($targetClass, "_Extension")
  return
    <Diagnostic>
      <Moniker>ExtensionsWithoutPrefix</Moniker>
      <Severity>Error</Severity>
      <Path>{string($a/@PathPrefix)}</Path>
      <Message>Extension class name should include a prefix</Message>
      <DiagnosticType>AppChecker</DiagnosticType>
      <Line>{string($a/@StartLine)}</Line>
      <Column>{string($a/@StartCol)}</Column>
      <EndLine>{string($a/@EndLine)}</EndLine>
      <EndColumn>{string($a/@EndCol)}</EndColumn>
    </Diagnostic>
}
</Diagnostics>

No estoy acostumbrado a trabajar con XQuery para nada, simplemente he intentado crear algunas reglas, pero intentaré explicar lo que hace esta.

La primera línea, «for $a in /Class», recupera las clases en la variable $a, y la segunda línea, «where $a/AttributeList/Attribute[@Name = «ExtensionOf»]» filtra el nodo de Classes para seleccionar sólo aquellas que tienen el atributo [ExtensionOf()].

La tercera línea, la que empieza con la palabra clave let, guarda en la variable $targetClass el valor del nodo IntrinsicAttributeLiteral que se encuentra en la raíz de la clase y que además tiene como valor de atributo FunctionName, el filtro de tipo classStr, de ahí el filtro [@FunctionName = «classStr»].

La cuarta línea filtra la primera variable, $a, que contiene todas las clases, cuyo nombre es igual que la clase base a la que extienden y que se concatena con _Extension.

Y finalmente, retornamos esas clases que están decoradas con AttributeOf, que siguen el patrón ClaseExtendida_Extension, sin prefijos al principio, ni sufijos al final.

Por ejemplo, supongamos que extiendo la clase CustPostInvoice, y creo una clase de extensión que se llame CustPostInvoice_Extension, y la decoro con el atributo ExtensionOf, aparecerá en la lista de warnings porqué estamos llamando a la clase de extensión como la base sin nada más. Debería haberse llamado CustPostInvoiceAAS_Extension.

Creando una query

Así que gracias a XQuery podemos generar nuestras propias reglas. Por ejemplo, continuando con las extensiones, voy a crear una query que me muestre todas las clases que acaban en _Extension, pero que no tengan el atributo ExtensionOf:

for $c in /Class
let $className := $c/@FullName
where matches($className, '_Extension$')
    and not($c//AttributeList//Attribute/@Name = "ExtensionOf")    
return $className

Es un ejemplo estúpido, pero es un ejemplo. Obtenemos nuestras clases y las metemos en $c, después guardamos en $className el nombre de la clase y filtramos por las clases que acaban en _Extension pero no tienen el atributo ExtensionOf.

Si queréis ver más ejemplos podéis consultar el repositorio de GitHub en el que está la carpeta Samples con ejemplos mucho mejores que el que acabo de hacer.

Si queréis aprender más sobre XQuery podéis leer el tutorial del W3C o, como recomienda Peter Villadsen, el libro XQuery de Priscilla Walmsley (ISBN-13: 978-0596006341, ISBN-10: 0596006349).

Sintaxis XQuery

Como hemos visto antes, la sintaxis de XQuery recuerda un poco la de T-SQL. He usado las palabras clave for, let, where y return, que junto order by conforman las 5 básicas.

  • for: como en for $c in /Class (o /Table o /Form), recorrerá todos los nodos de tipo /Class (o /Table o /Form). Podremos consultar los elementos de las clases usando $c en vez de /Class.
  • let: esta palabra clave se usa para guardar el valor de un elemento, atributo, etc. en una variable. En el ejemplo de antes obtiene el atributo FullName por cada elemento en $c, y para cada valor de la variable en cada loop obtendremos lo que hay en ese elemento dentro de la variable $className.
  • order by: igual que en SQL, ordena los datos según la condición que le digamos.
  • where: como en SQL se usa para filtrar datos.
  • return: devuelve los datos. En el ejemplo de antes devuelve la variable $className, que contiene el atributo FullName.

Estas palabras clave se conocen como FLOWR (flower).

Como ya os habréis dado cuenta las variables se declaran usando el símbolo del dólar ($).

Podemos acceder a los atributos de un elemento XML usando el patrón NombreNodo/@Atributo. Y podemos acceder a nodos hijo usando Nodo/NodoHijo las veces que queramos.

Hay muchísimas cosas más que se pueden hacer, si miráis los ejemplos aprenderéis más sobre la sintaxis, sólo he contado lo más básico de lo que podemos hacer con XQuery.

Generar los ASTs

Ahora tenemos que generar los ASTs para ejecutar las queries sobre nuestro código.

Cómo generamos los ASTs? Hay varias formas de hacer esto, y todas ellas necesitan que BaseX esté instalado porque todas las opciones generarán los ASTs y una base de datos de BaseX.

Primera opción: podemos hacer una full build y seleccionar la opción «Run Socratex Checks»:

Full build con checks de SocrateX
Full build con checks de SocrateX

Como hemos visto antes, esta opción generará los ASTs y creará la base de datos de BaseX.

Segunda opción: en Visual Studio vamos al menú Dynamics 365, Deploy y Create Deployment Package. En la nueva ventana, seleccionamos para qué paquetes queremos generar los ASTs y seleccionamos la opción «Run Appchecker».

Generar los ASTs desde la opción Create Deployment Package
Generar los ASTs desde la opción Create Deployment Package

Esta opción también generará los ASTs y creará la base de datos de BaseX.

Tercera opción: como explica Peter en sus posts (por favor, si todavía estáis leyendo esto y no habéis leído sus posts, hacedlo!), la última opción es usar xppc.exe (el compilador) desde la línea de comandos. xppc.exe está en K:\AosService\PackagesLocalDirectory\bin folder, o C si estáis corriendo una máquina desde el VHD. Lo llamaremos así:

xppc.exe  -metadata="K:\AosService\PackagesLocalDirectory" -compilermetadata="K:\AosService\PackagesLocalDirectory" -output="K:\AosService\PackagesLocalDirectory\YOUR_PACKAGE\bin" -modelmodule="YOUR_PACKAGE" -xmllog="K:\AosService\PackagesLocalDirectory\YOUR_PACKAGE\BuildModelResult.xml" -log="K:\AosService\PackagesLocalDirectory\YOUR_PACKAGE\BuildModelResult.log" -appBase="K:\AosService\PackagesLocalDirectory\bin" -refPath="K:\AosService\PackagesLocalDirectory\YOUR_PACKAGE\bin" -referenceFolder="K:\AosService\PackagesLocalDirectory" -writeAsts -astOutputPath="K:\YOUR_PACKAGE"

Tienes que cambiar YOUR_PACKAGE por el nombre de tu paquete.

Este comando sólo genera los ASTs. Para crear la base de datos tenemos que ejecutar otro comando:

java.exe -cp 'C:\Program Files (x86)\BaseX\BaseX.jar' org.basex.BaseX -c "CREATE DB YOUR_DB K:\YOUR_PACKAGE"

Si has instalado BaseX en otra carpeta, cambia la ruta al .jar.

¿Y por qué usar la línea de comandos? Porque podemos, y porque le pregunté a Mötz Jensen si alguien había intentado añadir esto a las d365fo.tools, y luego me ofrecí a hacerlo 😅 ¡Y lo haré!

Si vas a la carpeta en la que has generado tus ASTs verás carpetas para Classes, Entities, Forms, o Tables, pero no para las extensiones de los objetos. Supongo que es así porque las extensiones de Tablas/Forms sólo tienen parte del código o campos (lo que extendemos), y no debe ser posible analizarlo.

Usando SocrateX explorer

Cuando tenemos los ASTs generados y en la base de datos de BaseX, podemos empezar a usar SocrateX (leed el post de Peter para descargarlo). Ejecutamos SocrateX y nos logueamos con la misma contraseña que el usuario:

SocrateX login
SocrateX login

Si nos da el error «Unable to connect, or bad credentials provided.» es porque el servidor de BaseX no está ejecutándose, vamos al menú inicio y lo arrancamos:

Iniciar el servidor de BaseX
Iniciar el servidor de BaseX

Cuando SocrateX se inicia podemos ver tres ventanas. La superior muestra el código base, la inferior izquierda es donde escribimos las queries, y la inferior derecha es la que se muestran los resultados de las queries.

Socratex Explorer
Socratex Explorer

Y listo. Tenemos una forma de analizar nuestro código, que nos puede ayudar a escribir mejor código, y no sólo seguir las mejores prácticas de desarrollo de X++ sinó buenas prácticas de desarrollo. Ahora a probarlo y ejecutar las queries contra vuestro código a ver qué se puede mejorar!

¡Suscríbete!

Recibe un correo cuando se publique un nuevo post
Author

Microsoft Dynamics 365 Finance & Operations technical architect and developer. Business Applications MVP since 2020.

Write A Comment

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

ariste.info