Muchas veces en los proyectos nos encontramos con que queremos refactorizar código y vemos que por la cantidad de cambios que habría que hacer se convierte en una tarea tediosa y repetitiva que incluso podríamos descartar por la cantidad de tiempo que tendríamos que invertir. Muchos de estos refactors pueden ser programados y es lo que vamos a ver en este artículo con 4 ejemplos.
Para facilitar las cosas vamos a usar ts-morph que nos proporciona una API más sencilla para navegar y modificar código Typescript.
Para empezar, creamos una carpeta con su package.json
e instalamos ts-morph
.
1 | npm install --save-dev ts-morph |
También instalamos ts-node que nos permite ejecutar Typescript con node.
1 | npm i --save ts-node |
Ya estamos listos para comenzar a configurar nuestro script de refactor.
Podemos añadir la ruta a nuestro tsconfig.json
con tsConfigFilePath
pero ts-morph
usará los mismo ficheros que nuestro tsconfig.json
para evitarlo podemos usar skipAddingFilesFromTsConfig
.
1 | import { Project } from 'ts-morph'; |
A continuación, indicaremos en qué ficheros queremos ejecutar el script sino estamos usando los del tsconfig.json
.
1 | project.addSourceFilesAtPaths('src/**/*.ts'); |
Ejemplo 1: edición de una interfaz
En este primer ejemplo queremos quitar de nuestra interfaz la propiedad _id
para añadir id
.
Fichero que queremos refactorizar:
1 | interface Test1 { |
El resultado del script tiene transformar el contenido del fichero a esto:
1 | interface Test1 { |
Antes de empezar es recomendable utilizar ts-ast-viewer que nos muestra el AST del código que no sirve de gran ayuda a la hora de usar ts-morph
para comprender la estructura del código a refactorizar.
Este es el AST de la interfaz anterior:
Comencemos con el código que refactoriza nuestra primera interfaz.
Creamos el fichero example1.ts
. Configuramos el proyecto como hemos visto y con getSourceFiles
recorremos todos los ficheros.
1 | import { Project } from 'ts-morph'; |
Por cada fichero buscamos las interfaces que contiene con getInterfaces
y las recorremos.
1 | project.getSourceFiles().forEach((sourceFile) => { |
Comprobamos si la interfaz contiene _id
, si es así lo borraremos y añadiremos id
.
1 | interfaces.forEach((interfaceDeclaration) => { |
Para iniciar el refactor ejecutamos el comando npx ts-node example1.ts
. Si todo ha ido bien veremos que el fichero ha sido modificado con los cambios indicados.
Ejemplo 2: buscar y manipular una variable
El objetivo en este ejemplo es modificar el contenido de la variable name
y añadir un console.log
, pero solo vamos a hacerlo para las variables que están dentro de un constructor en una clase que herede de ParentTest
.
1 | class Test extends ParentTest { |
Después del script:
1 | class Test extends ParentTest { |
Lo primero que vamos hacer es buscar los constructores que cumplan este criterio, es decir, aquellos que estén dentro de una clase que herede de ParentTest
. Para ello, usaremos el siguiente código:
1 | import { Node } from 'ts-morph'; |
Si hemos encontrado el constructor que vamos a buscar y si existe la variable name
, reemplazaremos su contenido y añadiremos el console.log
.
1 | if (classConstructor) { |
Ejemplo 3: sólo permitir una clase por fichero
En el ejemplo, buscaremos en un fichero si tiene más de una clase y si fuese así vamos a coger las sobrantes y las vamos a mover a ficheros diferentes.
1 | project.getSourceFiles().forEach((sourceFile) => { |
Ejemplo 4: renombrar una clase y todas sus referencias
Para este ejemplo tenemos dos ficheros, el de la clase Test
que queremos renombrar:
1 | export class Test { |
Y en el que se usa:
1 | import { Test } from './example4'; |
Lo que queremos hacer es renombrar la clase Test
a Hello
y que nada se rompa. El resultado tendría que ser este:
1 | export class Hello { |
1 | import { Hello } from './example4'; |
Con este código lo resolvemos:
1 | // Buscamos y recorremos las clases del fichero |
Conclusiones
Con ts-morph
podéis ver que ya no importa la cantidad de cambios que tengamos que hacer en un refactor gracias a poder programar esos cambios ahorraremos horas/días de trabajo repetitivo. Dominarlo merece mucho la pena.
También, aunque no lo hemos visto en los ejemplos, podemos analizar el código para crear nuestros propios linters.
Más información en la documentación oficial.