Video Summary and Transcription
La charla de hoy trata sobre cómo mejorar el análisis estático del código utilizando el servidor de lenguaje TypeScript y el árbol de sintaxis abstracta (AST). TypeScript puede ayudar con el análisis estático al proporcionar tipos basados en las firmas de las funciones. Al integrar TSMorph en un complemento de Babel, podemos verificar los tipos para nodos específicos en el árbol de sintaxis abstracta. Las mejoras en el análisis estático incluyen verificar los argumentos de console.log y eliminar expresiones innecesarias. La información de tipo de TypeScript se puede utilizar para compilar CSS y extraerlo en una hoja de estilos separada, lo que permite una mejor compilación y rendimiento en el tiempo de construcción.
1. Enhancing Static Code Analysis with AST
Hoy discutiremos cómo mejorar el análisis estático de código utilizando el servidor de lenguaje TypeScript y el árbol de sintaxis abstracta (AST). Podemos utilizar AST para realizar transformaciones de código, como eliminar llamadas específicas a funciones. Al implementar un complemento de Babel, podemos eliminar automáticamente todas las llamadas a console.log del código. AST y el análisis estático de código permiten optimizaciones en tiempo de compilación, minificación, formateo y transpilación para admitir navegadores antiguos.
Hola a todos, soy Artur, líder técnico del equipo de Apps Platform en una empresa con sede en Londres llamada Zoho. Hoy me gustaría hablar sobre cómo mejorar el análisis estático de código utilizando el servidor de lenguaje TypeScript. Pero primero, hablemos rápidamente sobre qué es el árbol de sintaxis abstracta y cómo podemos utilizarlo para el análisis estático.
Imagina que queremos implementar alguna transformación automática de código durante la compilación para eliminar todas las llamadas a CONSOLE.LOG del resultado. En teoría, podríamos usar una expresión regular para encontrar todas las llamadas a CONSOLE.LOG en el código. Pero recuerda, resuelve el problema con una expresión regular y ahora tienes un problema más. Y en realidad, sería bastante complicado escribir una expresión regular para manejar todos los casos diferentes. En su lugar, podemos realizar transformaciones de código utilizando el árbol de sintaxis abstracta.
Echemos un vistazo al proceso de compilación de código. La compilación generalmente consta de 4 etapas: análisis léxico, análisis de sintaxis, análisis semántico y generación de código. Hoy nos centraremos en el análisis semántico con el árbol de sintaxis abstracta. Y implementaremos nuestra transformación de código utilizando AST. La mayoría de las herramientas de nuestro ecosistema utilizan AST para el análisis y los cambios de código.
Echemos otro vistazo a nuestro código. Vamos a utilizar una herramienta llamada AST Explorer. Y en el lado derecho puedes ver cómo se representa nuestro código mediante el árbol de sintaxis abstracta. La llamada a la función, que en nuestro caso es console.log, se representa mediante un nodo de expresión de llamada en el árbol, que contiene otros nodos como callee o arguments. Esta expresión de llamada es parte del cuerpo de la declaración de bloque, simplemente vamos a eliminarla. Una vez que la eliminemos, el cuerpo de la declaración de bloque no contendrá ninguna otra expresión. Entonces, al final, al generar un código, obtendremos simplemente una función vacía de Hello World, en este caso particular. Volviendo al diagrama, lo que hicimos fue simplemente transformar nuestro árbol de sintaxis abstracta, eliminando el nodo de expresión de llamada, y luego generamos código a partir del nuevo AST. Por supuesto, no queremos realizar estas transformaciones manualmente, así que implementemos un complemento simple de Babel para eliminar todas las llamadas a console.log.
En AST Explorer, puedes seleccionar la API de Babel incorporada para implementar un complemento de Babel y puedes ver las cuatro partes en la pantalla, como el código fuente, AST, implementación del complemento y código de salida. El código fuente original contiene tres expresiones de llamada a console.log, que se representan mediante el árbol de sintaxis abstracta. Puedes ver estas tres declaraciones de expresión como parte del cuerpo de la declaración de bloque. Expandamos cada una de ellas y puedes encontrar una de las expresiones de llamada con los nodos callee y argument. Ahora pasemos a la implementación del complemento, la implementación es bastante sencilla. Todo lo que tenemos que hacer es recorrer las expresiones de llamada, verificar si callee es una expresión de miembro y si es console.log, eliminar toda la expresión. Al final, obtendremos una función vacía, hello world, sin console.log nuevamente porque se eliminaron automáticamente. Puedes utilizar AST y análisis estático de código para implementar optimizaciones en tiempo de compilación, minificación, formateo, transpilación, por ejemplo, desde las nuevas características del lenguaje a la antigua para admitir navegadores antiguos, por ejemplo, y muchas más.
2. El papel de TypeScript en el análisis estático
TypeScript puede ayudar con el análisis estático al proporcionar tipos basados en las firmas de las funciones. Ofrece un servidor de lenguaje que permite a las herramientas conectarse y acceder a la información de tipos. TSMorph, una biblioteca que envuelve la API del compilador de TypeScript, simplifica la interacción. Podemos integrar TSMorph en un complemento de Babel para verificar los tipos de nodos específicos en el árbol de sintaxis abstracta. Al mejorar el complemento original con el soporte de TypeScript, podemos eliminar los argumentos de console.log que no son números.
Pero, ¿cómo puede ayudar TypeScript con el análisis estático? Imaginemos ahora que necesitamos eliminar las llamadas a console.log como antes, pero mantener los registros de los argumentos de tipo número. Por ejemplo, queremos mantener 1, 2, 3 o alguna constante si es un número, pero queremos eliminar las cadenas Hello World o las cadenas Now con valores en su lugar como 1, 2, 3, eso es bastante sencillo porque puedes obtener este valor directamente de la sintaxis. Pero se complica bastante con las llamadas a funciones o variables externas como alguna constante aquí. Tenemos que tener algún tipo de sistema de tipos implementado o implementar la inferencia de tipos nosotros mismos y TypeScript puede proporcionar esos tipos.
En este ejemplo, TypeScript puede inferir automáticamente el tipo correcto basado en las firmas de las funciones. Y para que TypeScript no solo verifique los tipos con la herramienta de línea de comandos, sino que funcione con diferentes editores de código, TypeScript proporciona un servidor de lenguaje que es un proceso separado, como un backend, y las herramientas pueden conectarse a este servidor para obtener información sobre los tipos o utilizar herramientas de refactorización de código proporcionadas por TypeScript. Y la belleza del servidor de lenguaje es que se puede utilizar no solo para la edición de código o la verificación de tipos. Siguiendo el protocolo del servidor de lenguaje, podemos interactuar con él programáticamente y beneficiarnos del conocimiento de TypeScript sobre el proyecto y los tipos en cualquier nivel, incluido el análisis semántico.
TSMorph es una increíble biblioteca que envuelve la API del compilador de TypeScript y proporciona una interfaz sencilla para interactuar con ella. Aquí tienes un ejemplo de cómo puedes configurar TSMorph en tu programa. Ahora podemos usarlo durante el análisis del árbol abstracto. Veamos cómo podemos integrar TSMorph en el complemento real. Hay bastante código aquí, pero centrémonos en las partes más importantes. Queremos tener una función getTypeAtPost que acepte el nombre del archivo, el código, el inicio y el final de la posición en la que estamos interesados, y luego podemos crear un archivo fuente virtual basado en el nombre del archivo y el código, y leer el tipo para esta posición proporcionada utilizando TSMorph. La idea es la misma que, por ejemplo, en un IDE. Si necesitamos ver el tipo en nuestro editor, seleccionamos la expresión que nos interesa y el editor muestra los tipos para esa posición de cursor específica. En este caso, en lugar de un cursor, solo tenemos una interfaz programable, pero la idea es en su mayoría la misma. Y ahora integremos TSMorph con nuestro complemento de Babel. Necesitamos inicializar la clase TypedClick definida previamente para el procesador de TS. Luego podemos definir un ayudante llamado GetTSType con el nombre del archivo, el código fuente y la ruta al nodo en el árbol de sintaxis abstracta como argumentos. Cada ruta en el árbol de sintaxis abstracta contiene nodos con información sobre la posición del nodo desde el inicio hasta el final. De esta manera, podemos verificar el tipo para el nodo específico. Y al final, solo necesitamos devolver la representación del tipo proporcionada por TSMorph. Con estos ayudantes simples definidos, veamos cómo se puede mejorar nuestro complemento original de Babel con el soporte de TypeScript. Para recordar, esta fue la implementación original del complemento que se integra con el servidor de lenguaje de TypeScript. En primer lugar, nuevamente, inicialicemos nuestra instancia de TSProcessor y definamos el ayudante GetTSType. A continuación, podemos obtener el código y el nombre del archivo del estado del complemento de Babel. En este estado, ya hemos realizado todas las comprobaciones, como verificar que esta sea una expresión de console.log, por lo que solo necesitamos recorrer los argumentos de console.log y verificar que cada argumento... Necesitamos obtener el tipo para cada argumento y verificar si ese tipo de argumento es un número o no. Si no es un número, simplemente podemos eliminar este argumento por completo, y si es un número, podemos dejarlo.
3. Mejorando el Análisis Estático y la Compilación
Podemos mejorar el análisis estático del código verificando los argumentos de las llamadas a console.log y eliminando expresiones innecesarias. TypeScript proporciona información de tipos que se puede utilizar para compilar CSS y extraerlo en una hoja de estilos separada. La representación atómica de estilos nos permite generar clases basadas en diferentes valores de propiedades. Tidy.dev ofrece una interfaz funcional para la generación de CSS durante el tiempo de compilación. Utiliza TSMorph para la interacción programable con el compilador de TypeScript. Presta atención a la seguridad de tipos en tu código para una mejor compilación y rendimiento durante el tiempo de compilación.
Y luego, solo necesitamos verificar si quedan argumentos o no. Si los hay, podemos mantener esta expresión porque significa que hay argumentos numéricos para esta llamada a console.log. Si no los hay, podemos eliminar toda la expresión con path.remove.
Así que sí, este ejemplo es bastante irreal. No creo que haya un caso de uso real para console.log y los tipos de los parámetros de console.log. Pero hay un ejemplo un poco más práctico en el que he estado trabajando con Compile Time CSS y una biblioteca de JS.
He estado trabajando en una biblioteca llamada Tidy, y la idea es que pueda proporcionar una interfaz funcional simple con la función CSS, que acepta propiedades como estilos en línea. Pero la idea es que en lugar de generar estos estilos en tiempo de ejecución, podemos compilarlos durante el tiempo de compilación y extraerlos en una hoja de estilos separada.
En este caso, eso es bastante sencillo porque el valor azul y el valor amarillo son estáticos. Por lo tanto, podemos obtenerlos directamente del árbol de sintaxis. Pero ¿qué pasa si nuestro código es un poco más complicado? Y por ejemplo, nuestros estilos dependen del tiempo de ejecución. En este caso, el color y el font-weight dependen de la propiedad de variante y la propiedad de peso, que no se conocen durante el tiempo de compilación.
Pero con TypeScript, podemos obtener el tipo real de cada una de las propiedades. Por ejemplo, para el color, podemos ver que puede ser violeta o morado. Y para font-weight, podemos ver que es una unión de los valores lighter, normal y bold. Lo que nos permite recorrer estos valores y generar clases predefinidas para cada uno de los valores.
Gracias a la representación atómica de estilos, podemos componer estos elementos al final, al igual que la composición de nombres de clases. Y todo lo que necesitamos en tiempo de ejecución es identificar el hash correcto para el nombre de clase para este valor específico. No hay generación de estilos. Y nuevamente, podemos beneficiarnos de la compilación de estilos durante el tiempo de compilación y extraerlo en una hoja de estilos separada.
Si eso te parece interesante, por favor visita el sitio web de tidy.dev y el repositorio de tidy. Aún está en un estado bastante experimental, pero cualquier comentario o idea será muy apreciada.
Y al final, solo algunas notas. Utiliza TSMorph para una interfaz programable para interactuar con el compilador de TypeScript. Y puedes obtener sugerencias de tipos de TypeScript en Babel o cualquier otro recorredor de AST solo desde la posición en el código, lo cual es muy útil. La integración de TypeScript puede ralentizar el análisis estático debido a la sobrecarga, por lo que podría ser una buena idea aplicar algunas optimizaciones de rendimiento, por ejemplo, aislarlo en un trabajador separado.
Existen docenas de protocolos de servidor de lenguaje diferentes. No se trata solo de TypeScript. Por lo tanto, también puedes construir muchas integraciones diferentes.
Y al final, por último pero no menos importante, los tipos no son 100% confiables y no pueden serlo porque existen tipos como any o comandos como TSignore. Por lo tanto, cuanto más atención prestes a la seguridad de tipos en tu código, más podrás beneficiarte de ella, especialmente durante la compilación y el tiempo de compilación. Aquí tienes algunos enlaces útiles. Por favor, échales un vistazo y muchas gracias por tu atención.
Comments