¿Por qué analizar su código? Por ejemplo, para encontrar el console.log olvidado antes de comprometerse. Pero, ¿qué sucede si necesita cambiar la firma de la función en cientos de entradas en el código? ¿Las expresiones regulares harán frente aquí? Este artículo le mostrará qué posibilidades ofrecen los árboles de sintaxis abstracta a un desarrollador.

Under the cut: una transcripción de video y texto de un informe de Kirill Cherkashin (
z6Dabrata ) de la conferencia
HolyJS 2018 Piter .
Sobre el autor
Cyril nació en Moscú, ahora vive en Nueva York y trabaja en Firebase. Enseña Angular no solo en Google, sino en todo el mundo. El organizador del mitap angular más grande del mundo es AngularNYC (así como VueNYC y ReactNYC). En su tiempo libre de programación, le gusta el tango, los libros y las conversaciones agradables.Sierra para metales o madera?
Comencemos con un ejemplo: digamos que depuró un programa y envió los cambios realizados a git, después de lo cual se fue a dormir silenciosamente. Por la mañana resultó que sus colegas descargaron sus cambios y, dado que olvidó eliminar el resultado de la información de depuración a la consola el día anterior, lo muestra y obstruye el resultado. Muchos enfrentaron este problema.
Existen herramientas, como
EsLint , para solucionar la situación, pero con fines educativos, intentemos encontrar una solución por nuestra cuenta.
¿Qué herramienta debo usar para eliminar todo
console.log()
del código?
Elegimos entre expresiones regulares y el uso de árboles Abstract Sitax (ASD). Intentemos resolver esto con expresiones regulares escribiendo alguna función
findConsoleLog
. En la entrada, recibirá el código del programa como argumento y se mostrará verdadero si console.log () se encuentra en algún lugar del texto del programa.
function findConsoleLog(code) { return !!code.match(/console.log/); }
Escribí 17 pruebas, tratando de encontrar diferentes formas de romper nuestra función. Esta lista está lejos de ser completa.

La prueba más simple aprobada.
¿Y qué pasa si alguna función contiene la cadena "console.log" en su nombre?
function findConsoleLog(code) { return !!code.match(/\bconsole.log/); }
Se agregó un carácter que indica que
console.log
debe aparecer al comienzo de la palabra.

Solo se aprobaron dos pruebas, pero ¿qué
console.log
si
console.log
está en el comentario y no necesita eliminarse?
Lo reescribimos para que el analizador no toque los comentarios.
function findConsoleLog(code) { return !!code .replace(/\/\/.*/) .match(/\bconsole.log/); }

Excluimos la eliminación de "console.log" de las líneas:
function findConsoleLog(code) { return !!code .replace(/\/\/.*|'.*'/, '') .match(/\bconsole.log/); }

No olvide que todavía tenemos espacios y otros caracteres que pueden impedir que se pasen algunas pruebas:

A pesar de que la idea no era del todo simple, se pueden pasar las 17 pruebas que utilizan expresiones regulares. Aquí, entonces, en este caso, se verá el código de la solución:
function findConsoleLog(code) { return code .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//) .match(/\bconsole\s*.log\(/); }
El problema es que este código no cubre todos los casos posibles y es bastante difícil mantenerlo.
Considere cómo resolver este problema usando ASD.
¿Cómo se cultivan los árboles?
El árbol de sintaxis abstracta se obtiene como resultado del analizador que trabaja con el código de su aplicación. El analizador
@ babel / analizador se utilizó para la demostración
.Como ejemplo, tome la cadena
console.log('holy')
, páselo por el analizador.
import { parse } from 'babylon'; parse("console.log('holy')");
Como resultado de su trabajo, se obtiene un archivo JSON de aproximadamente 300 líneas. Excluimos de sus líneas numéricas la información del servicio. Estamos interesados en la sección del cuerpo. La metainformación tampoco nos interesa. El resultado es de aproximadamente 100 líneas. En comparación con la estructura que genera el navegador para una variable de cuerpo (aproximadamente 300 líneas), esto no es mucho.
Veamos algunos ejemplos de cómo se representan varios literales en el código en un árbol de sintaxis:

Esta es una expresión en la que hay Numeral Literal, un literal numérico.

La ya conocida expresión console.log. Tiene un objeto que tiene una propiedad.

Si log es una llamada de función, la descripción es la siguiente: hay una expresión de llamada, tiene argumentos: literales numéricos. Al mismo tiempo, la expresión de llamada tiene un nombre: log.
Los literales pueden ser diferentes: números, cadenas, expresiones regulares, booleanos, nulos.
Volver a la llamada de console.log

Esta es una expresión de llamada que tiene una expresión de miembro dentro. De esto queda claro que el objeto de la consola dentro tiene una propiedad llamada log.
ASD bypass
Ahora intentemos trabajar con esta estructura en el código. La biblioteca
babel-traverse se usará para atravesar el árbol
.Se dan las mismas 17 pruebas. Dicho código se obtiene analizando el árbol de sintaxis del programa y buscando entradas de "console.log":
function traverseConsoleLog(code, {babylon, babelTraverse, types, log}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path){ if ( path.node.property.type === 'Identifier' && path.node.property.name === 'log' && path.node.object.type === 'Identifier' && path.node.object.name === 'console' && path.parent.type === 'CallExpression' && path.Parentkey === 'callee' ) { hasConsoleLog = true; } } }) return hasConsoleLog; }
Analicemos lo que está escrito aquí.
const ast = babylon.parse(code);
en la variable ast analizamos el árbol de sintaxis del código. A continuación le damos a la biblioteca babel-parse este árbol para su procesamiento. Estamos buscando nodos y propiedades con nombres coincidentes dentro de las expresiones de llamada. Establezca la variable hasConsoleLog en true si se encuentra la combinación requerida de nodos y sus nombres.
Podemos movernos alrededor del árbol, tomar los padres de nodos, descendientes, buscar qué argumentos y propiedades tienen, mirar los nombres de estas propiedades, tipos, esto es muy conveniente.
Hay un matiz desagradable que se puede solucionar fácilmente con la biblioteca de tipos babel. Para evitar errores al buscar en el árbol debido a un nombre incorrecto, por ejemplo, en lugar de
path.parent.type === 'CallExpression'
accidentalmente escribió
path.parent.type === 'callExpression'
, con tipos de babel puede escribir así :
Reescribimos el código anterior usando babel-types:
function traverseConsoleLogSolved2(code, {babylon, babelTraverse, types}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path) { if ( types.isIdentifier(path.node.object, { name: 'console'}) && types.isIdentifier(path.node.property, { name: 'log'}) && types.isCallExpression(path.parent) && path.parentKey === 'callee' ) { hasConsoleLog = true; } } }); return hasConsoleLog; }
Transformar ASD usando babel-traverse
Para reducir los costos de mano de obra, necesitamos que
console.log
elimine inmediatamente del código, en lugar de una señal de que está en el código.
Dado que necesitamos eliminar no la MemberExpression en sí, sino su padre, en su lugar
hasConsoleLog = true;
escribimos
path.parentPath.remove();
.
Desde la función
removeConsoleLog
, aún devolvemos un valor booleano. Reemplazamos su salida con el código que generará el generador de babel, así:
hasConsoleLog
=>
babelGenerator(ast).code
Babel-generator recibe el árbol de sintaxis abstracta modificada como parámetro, devuelve un objeto con la propiedad de código, dentro de este objeto se regenera el código sin
console.log
. Por cierto, si queremos obtener un mapa de código, podemos llamar a la propiedad sourceMaps para este objeto.
¿Y si necesita encontrar un depurador?
Esta vez usaremos
ASTexplorer para completar la tarea. El depurador es un tipo de nodo de declaración de depurador. No necesitamos mirar toda la estructura, ya que este es un tipo especial de nodo, solo busque la declaración del depurador. Vamos a escribir un complemento para ESLint (en ASTexplorer).
ASTexplorer está diseñado de tal manera que escribes el código a la izquierda y a la derecha obtienes el ASD terminado. Puede elegir en qué formato desea recibirlo: JSON o en formato de árbol.

Como usamos ESLint, hará todo el trabajo de buscar archivos para nosotros y nos dará el archivo necesario para que podamos encontrar la línea del depurador en él. Esta herramienta utiliza un analizador ASD diferente. Sin embargo, hay varios tipos de ASD en JavaScript. Algo que recuerda al pasado, cuando diferentes navegadores implementaron la especificación de diferentes maneras. Por lo tanto, implementamos la búsqueda del depurador:
export default function(context) { return { DebuggerStatement(node) { // , console.log path, - , path context.report(node, 'LOL Debugger!!!'); // ESLint , debugger, node , , debugger } } }
Comprobación del trabajo de un complemento escrito:

Del mismo modo, puede eliminar el depurador del código.
¿Qué más son útiles ASD?
Personalmente uso ASD para simplificar el trabajo con Angular y otros frameworks front-end. Puede importar, expandir, agregar una interfaz, método, decorador y cualquier otra cosa con solo hacer clic en un botón. Aunque estamos hablando de Javascript en este caso, sin embargo, TypeScript también tiene sus propios ASD, la única diferencia es la diferencia entre los nombres de los tipos de nodos y la estructura. En el mismo ASTExplorer se puede seleccionar como el lenguaje TypeScript.
Entonces
- Tenemos más control sobre el código, refactorización más fácil, codemods. Por ejemplo, antes de comprometerse, puede presionar una sola tecla para formatear todo el código de acuerdo con las pautas. Codemods implica la coincidencia automática de código de acuerdo con la versión requerida del marco.
- Menos disputas sobre el diseño del código.
- Puedes crear proyectos de juegos. Por ejemplo, automáticamente déle retroalimentación al programador sobre el código que escribe.
- Mejor comprensión de JavaScript.
Algunos enlaces útiles para Babel
- Todas las transformaciones de Babel utilizan esta API: complementos y ajustes preestablecidos .
- Parte del proceso de agregar nuevas funciones a ECMAScript es crear un complemento para Babel. Esto es necesario para que las personas puedan probar la nueva funcionalidad. Si sigue el enlace , puede ver que dentro del mismo se utilizan las capacidades del ASD. Por ejemplo, operador de asignación lógica .
- Babel Generator pierde el formato al generar código. Esto es en parte bueno, porque si esta herramienta se usa en el equipo de desarrollo, luego de generar el código desde el ASD, se verá igual para todos. Pero si desea mantener su formato, puede usar una de estas herramientas: Recast o Babel CodeMod .
- Desde este enlace puede encontrar una gran cantidad de información sobre Babel Awesome Babel .
- Babel es un proyecto de código abierto y un equipo de voluntarios está trabajando en ello. Tu puedes ayudar Hay tres formas de hacer esto: asistencia financiera, puede apoyar el sitio web patreon, con el que trabaja Henry Zhu, uno de los principales contribuyentes de babel, ayuda con el código en opencollective.com/babel .
Bono
¿De qué otra manera podemos encontrar nuestro
console.log
en el código? Usa tu IDE! Usando la herramienta de buscar y reemplazar, después de seleccionar dónde buscar el código.
Intellij IDEA también tiene una herramienta de "búsqueda estructural" que puede ayudarlo a encontrar los lugares correctos en su código, por cierto, utiliza un ASD.
Del 24 al 25 de noviembre, Kirill dará una presentación sobre los datos binarios de JavaScript * LOVES * en Moscow HolyJS : bajaremos al nivel de datos binarios, profundizaremos en los archivos binarios usando archivos * .gif como ejemplo y trataremos con marcos de serialización como Protobuf o Thrift. Después del informe, será posible hablar con Cyril y discutir todos los temas de interés en el área de discusión.