@helenagd/espree-logging-solution

0.3.2 • Public • Published

Open in Codespaces

Práctica Espree logging

Helena García Díaz

Índice

  1. Resumen de lo aprendido
  2. CLI con Commander.js
  3. Indicar los valores de los argumentos
  4. Reto 1: Soportar funciones flecha
  5. Reto 2: Añadir el número de línea
  6. Explicación de las funciones en src/logging-espree.js
  7. Tests and Covering
  8. Github Actions
  9. Documentación del módulo
  10. Publicación del paquete
  11. Documentación del módulo
  12. Comprobación del módulo publicado
  13. Scripts

Resumen de lo aprendido

Volver al índice

En estas prácticas he recordado que antes de comenzar a trabjar con un proyecto, debo hacer npm i para instalar los módulos y paquetes necesarios.

Y si quiero instalar cualquier otro módulo debo usar el comando:

npm install [nombre_del_modulo]

He aprendido a debuggear los archivos empleando inspect-brk y el navegador de chrome.

usuario@ubuntu ~/practicas/espree-logging-helena-garcia-diaz-alu0100829150 (main) $ node --inspect-brk bin/log.js
Debugger listening on ws://127.0.0.1:9229/dcc086f9-fb01-40de-8906-6794be6a452f
For help, see: https://nodejs.org/en/docs/inspector

después de ejecutar el comando con el archivo indicado, y dirigirnos a la dirección correspondiente en nuestro navegador, se pincha en "inspect" y se puede empezar a ejecutar paso a paso el archivo.

img1

img2

La función "addLogging" se encarga de recorrer el AST del código pasado. Construye el árbol sintáctico y saca el código.

La función "addBeforeCode" se encarga de añadir al árbol las nuevas sentencias.

Dentro de "addBeforeCode" se va pasando cada nodo y dentro de él se estudia la información. Como en el if de "addLogging" habíamos indicado el tipo de funciones que queríamos que soportara, ahora las llamará '' si no posee un nombre en el id. Luego se accede y se guardan en un array de parámetros usando "node.params.map". A continuación se construye el nuevo array de AST con las nuevas sentencias añadidas.

CLI con Commander.js

Volver al índice

Como en las prácticas anteriores, se ha añadido un CLI para poder ejecutar el programa desde la línea de comandos.

De esta manera, el código en bin/log.js queda de esta forma.

#!/usr/bin/env node

import { program } from "commander";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { version, description } = require("../package.json");
import { transpile } from "../src/logging-espree.js";

program
  .name("jslogging")
  .version(version)
  .description(description)
  .argument("<filename>", 'file with the original code')
  .option("-o, --output <filename>", "file in which to write the output", "output.js")
  .option("-V, --version", "output the version number")
  .option("-h, --help", "output usage information")
  .action((filename, options) => {
    transpile(filename, options.output);
  });

program.parse(process.argv);

const options = program.opts();
if (options.version) {console.log(version);}
if (options.help) {console.log(`Usage: logging-espree.js [options] <filename> [...]\n\nOptions:\n-V, --version\t\toutput the version number\n-o, --output <filename>  file in which to write the output (default: \"output.js\")\n-h, --help               display help for command`);}
if (options.output) {
  const output_file = options.output;
  console.log("Nuevo nombre de archivo de salida: " + output_file);
}

Lo más complicado fue en las prácticas iniciales, el documentarme para poder aplicar bien todas las opciones pedidas.

Indicar los valores de los argumentos

Volver al índice

Para indicar los valores de los argumentos, se ha añadido la siguiente ñlínea en el src/loggin-espree.js en la función "addBeforeCode":

const parameters = node.params.map(param => `\$\{ ${param.name}\ }`);

Reto 1: Soportar funciones flecha

Volver al índice

Para añadir las funciones de flecha gorda, se debe activar la versión 6 en el parse, ya que por defecto si no indicamos nada, utiliza la versión 5.

Entonces, en src/loggin-espree.js se añade en la llamada a parse {ecmaVersion: 6, loc: true} en las funciones "addLogging" y "addBeforeCode". Y el caso condicional para este tipo de función, de modo que queda de la siguiente forma:

export async function transpile(inputFile, outputFile) {
  let input = await fs.readFile(inputFile, 'utf-8');
  let output = addLogging(input);
  if (outputFile === undefined) {
      console.log(output);
      return;
  }
  await fs.writeFile(outputFile, output);

  console.log(`input:\n${inputFile}\n---`);
  console.log(`output:\n${outputFile}\n---`);
}

export function addLogging(code) {
  const ast = espree.parse(code, {ecmaVersion: 12, loc: true});
  
  estraverse.traverse(ast, {
      enter: function(node, parent) {
          if (node.type === 'FunctionDeclaration' ||
              node.type === 'FunctionExpression' ||
              node.type === 'ArrowFunctionExpression') {
              addBeforeCode(node);
          }
      }
  });
  return escodegen.generate(ast);
}

export function addBeforeCode(node) {
  debugger;
  var name = node.id ? node.id.name : '<anonymous function>';
  const parameters = node.params.map(param => `\$\{ ${param.name}\ }`);
  var beforeCode = "console.log(`Entering " + name + "(" + parameters + ") at line " + node.loc.start.line +  "`);";
  var beforeNodes = espree.parse(beforeCode, {ecmaVersion: 12, loc: true}).body; // Array de ASTs
  node.body.body = beforeNodes.concat(node.body.body);
}

Reto 2: Añadir el número de línea

Volver al índice

Para añadir el número de línea, como se puede ver en el código del apartado anterior, dentro de la función addBeforeCode se añade el número de línea en el código utilizando la información proporcionada por el nodo actual.

Para ello, se debe activar el loc, para no tener problemas.

En este punto, tuve errores porque marcaba al nodo como "undefined" y no accedía a "start". Es por ello, que en la función "addLogging", añadí la misma información en el parse para cuando se genera el árbol ast.

Explicación de las funciones en src/logging-espree.js

Volver al índice

Esta función transpila eel código del archivo de entrada inputFile y escribe el resultado en el archivo de szalida outputFile. Si no se especifica el archivo de salida, se escribe el resultado en la salida estándar (output.js).

export async function transpile(inputFile, outputFile) {
  let input = await fs.readFile(inputFile, 'utf-8');
  let output = addLogging(input);
  if (outputFile === undefined) {
      console.log(output);
      return;
  }
  await fs.writeFile(outputFile, output);

  console.log(`input:\n${inputFile}\n---`);
  console.log(`output:\n${outputFile}\n---`);
}

Esta función recibe una cadena de código fuente code y devuelve el código fuente transpilado, en el que se ha añadido una llamada a console.log() antes de cada función. Utiliza las bibliotecas espree y estraverse para analizar y recorrer el AST y escodegen para generar el código fuente resultante.

export function addLogging(code) {
  const ast = espree.parse(code, {ecmaVersion: 12, loc: true});
  
  estraverse.traverse(ast, {
      enter: function(node, parent) {
          if (node.type === 'FunctionDeclaration' ||
              node.type === 'FunctionExpression' ||
              node.type === 'ArrowFunctionExpression') {
              addBeforeCode(node);
          }
      }
  });
  return escodegen.generate(ast);
}

Esta función se utiliza dentro de addLogging() para añadir la llamada a console.log() antes de una función específica. Recibe un nodo de función node y, primero, se crea una cadena de texto que contiene el nombre de la función y sus parámetros, así como el número de línea donde comienza la función. A continuación, se convierte esta cadena de texto en un array de nodos AST utilizando espree.parse. Finalmente, se agrega este array de nodos AST al comienzo del cuerpo de la función mediante la concatenación con el array node.body.body.

export function addBeforeCode(node) {
  debugger;
  var name = node.id ? node.id.name : '<anonymous function>';
  const parameters = node.params.map(param => `\$\{ ${param.name}\ }`);
  var beforeCode = "console.log(`Entering " + name + "(" + parameters + ") at line " + node.loc.start.line +  "`);";
  var beforeNodes = espree.parse(beforeCode, {ecmaVersion: 12, loc: true}).body; // Array de ASTs
  node.body.body = beforeNodes.concat(node.body.body);
}

Tests and Covering

Volver al índice

Se ha completado el archivo test/test.mjs con el siguiente contenido:

Las pruebas se definen como una lista de objetos Test, donde cada objeto tiene una entrada para el archivo de entrada, el archivo de salida esperado, el archivo de registro esperado y el archivo de salida esperado sin registro.

const Test = Tst.map(t => ({
  input: __dirname + '/data/' + t.input,
  output: __dirname + '/data/' + t.output,
  correctLogged: __dirname + '/data/' + t.correctLogged,
  correctOut: __dirname + '/data/' + t.correctOut,
})
)

El bucle for se utiliza para iterar a través de la lista de pruebas y ejecutar la función it proporcionada por la biblioteca Mocha para cada prueba.

Para cada prueba, se llama a la función transpile con los archivos de entrada y salida adecuados y se comprueba que el resultado coincida con el archivo de registro esperado.

Finalmente, se ejecuta el programa de salida y se comprueba que la salida coincida con el archivo de salida esperado sin registro.

En resumen, este archivo de prueba verifica que la función transpile genere el registro esperado y produzca la salida esperada para un conjunto de casos de prueba de entrada.

for (let i = 0; i < Test.length; i++) {
  it(`Transpile(${Test[i].input}, ${Test[i].output})`, async () => {
    // Compile the input and check that the output program is what expected
    await transpile(Test[i].input, Test[i].output);
    let output = await fs.readFile(Test[i].output, 'utf-8');
    let expected = await fs.readFile(Test[i].correctLogged, 'utf-8');
    assert.equal(removeSpaces(output), removeSpaces(expected));
    await fs.unlink(Test[i].output);
    
    // Run the output program and check the logged output is what expected
    let correctOut = await fs.readFile(Test[i].correctOut, 'utf-8');
    let oldLog = console.log;
    let result = "";
    console.log = function (...s) { result += s.join(' ') }
    eval(output);
    assert.equal(removeSpaces(result), removeSpaces(correctOut));
    console.log = oldLog;
  }); 
}

Además, añadí un test para una función recursiva. No creí necesario añadir más, ya que el coverage es de casi el 100%.

Al hacer el coverage, tenía errores a la hora de usar nyc que por lo visto tiene que ver con la versión de node.js, por lo que empleé c8.

Se trata de un paquete npm que proporciona herramientas para generar informes de cobertura de código en Node.js. Es una alternativa a la herramienta de cobertura de código más popular de Node.js, nyc.

Buscando más información al respecto:

C8 utiliza el módulo V8 del motor JavaScript de Google Chrome para medir la cobertura del código. Es fácil de configurar y usar y tiene características como el soporte de JavaScript moderno y la integración con múltiples marcos y herramientas de prueba.

Además, c8 es compatible con el formato de salida de nyc, por lo que puede utilizar informes generados por c8 en herramientas que admiten el formato nyc, como Coveralls o CodeCov.

Al ejecutar npm run cov se genera la siguiente salida:

> espree-logging-solution@0.3.0 cov
> c8 npm run test


> espree-logging-solution@0.3.0 test
> mocha test/test.mjs



input:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test1.js
---
output:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged1.js
---
  ✔ Transpile(/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test1.js, /home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged1.js)
input:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test2.js
---
output:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged2.js
---
  ✔ Transpile(/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test2.js, /home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged2.js)
input:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test3.js
---
output:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged3.js
---
  ✔ Transpile(/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test3.js, /home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged3.js)
input:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test4.js
---
output:
/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged4.js
---
  ✔ Transpile(/home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/test4.js, /home/usuario/practicas/espree-logging-helena-garcia-diaz-alu0100829150/test/data/logged4.js)

  4 passing (58ms)

-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   95.58 |    91.66 |     100 |   95.58 |                   
 logging-espree.js |   95.58 |    91.66 |     100 |   95.58 | 17-19             
-------------------|---------|----------|---------|---------|-------------------

Como se puede ver, el coverage es del 95.58%. Esto se debe a que considero el caso en el que no se tenga un nombre para el archivo de salida.

La tabla de debajo representa el porcentaje de cobertura de código para cada archivo que se probó. Las columnas informan sobre el porcentaje de declaraciones cubiertas (% Stmts), el porcentaje de bifurcaciones de código (caminos de ejecución) cubiertas (% Branch), el porcentaje de funciones cubiertas (% Funcs) y el porcentaje de líneas de código cubiertas (% Lines).

  • El porcentaje de declaraciones cubiertas (% Stmts) mide la cantidad de declaraciones en el código que han sido ejecutadas durante la prueba. Una declaración se considera cubierta si al menos una de las pruebas lo ha ejecutado.

  • El porcentaje de bifurcaciones de código cubiertas (% Branch) mide la cantidad de bifurcaciones de código que han sido ejecutadas durante la prueba. Una bifurcación de código es una parte del código que puede ejecutarse o no, dependiendo de una condición (por ejemplo, un if/else). Una bifurcación se considera cubierta si ambas opciones (verdadera y falsa) han sido ejecutadas en la prueba.

  • El porcentaje de funciones cubiertas (% Funcs) mide la cantidad de funciones en el código que han sido ejecutadas durante la prueba. Una función se considera cubierta si al menos una de las pruebas lo ha ejecutado.

  • El porcentaje de líneas de código cubiertas (% Lines) mide la cantidad de líneas de código que han sido ejecutadas durante la prueba. Una línea de código se considera cubierta si al menos una de las pruebas lo ha ejecutado.

La última columna, "Uncovered Line #s", muestra las líneas de código que no se han ejecutado durante las pruebas. Los números que se muestran allí corresponden a las líneas no cubiertas en el archivo correspondiente.

Para generar el informe de cobertura de código añado un script con el siguiente código:

c8 --reporter=html --reporter=text --report-dir=docs mocha

Que se encuentra en el siguiente link

Github Actions

Volver al índice

Por último se añaden la integración continua, tal y como se hizo en la práctica anterior

Documentación del módulo

Volver al índice

Publicación del paquete

Volver al índice

Para publicar un paquete en NPM nos debemos crear un usuario en npmjs.com.

Luego, se inicia sesión en la terminal con el comando: npm login:

usuario@ubuntu ~/practicas/espree-logging-helena-garcia-diaz-alu0100829150 (main) $ npm login
npm notice Log in on https://registry.npmjs.org/
Login at:
https://www.npmjs.com/login?next=/login/cli/447435a9-bc6f-4882-b8d7-e5d4b5d44b9e
Press ENTER to open in the browser...

Logged in on https://registry.npmjs.org/.

Luego, actualizamos nuestro package.json con la siguiente información

"name": "@helenagd/espree-logging-solution",

Por último, se ejecutar el comando npm publish --access=public para publicar el paquete en NPM, obteniendo la siguiente salida:

npm notice 
npm notice 📦  @helenagd/espree-logging-solution@0.3.0
npm notice === Tarball Contents === 
npm notice 1.9kB   .github/workflows/nodejs.yml                                     
npm notice 2B      .nyc_output/763e7039-91dc-4b3d-9798-c67d4abd0e08.json            
npm notice 2B      .nyc_output/98373f83-a8cb-445e-b9ec-5d29cf56ca51.json            
npm notice 502B    .nyc_output/processinfo/763e7039-91dc-4b3d-9798-c67d4abd0e08.json
npm notice 585B    .nyc_output/processinfo/98373f83-a8cb-445e-b9ec-5d29cf56ca51.json
npm notice 253B    .nyc_output/processinfo/index.json                               
npm notice 18.2kB  README.md                                                        
npm notice 1.2kB   bin/log.js                                                       
npm notice 1.2MB   coverage/tmp/coverage-87018-1677767603632-0.json                 
npm notice 692.0kB coverage/tmp/coverage-87043-1677767603518-0.json                 
npm notice 5.4kB   docs/base.css                                                    
npm notice 2.7kB   docs/block-navigation.js                                         
npm notice 445B    docs/favicon.png                                                 
npm notice 4.4kB   docs/index.html                                                  
npm notice 11.3kB  docs/logging-espree.js.html                                      
npm notice 676B    docs/prettify.css                                                
npm notice 17.6kB  docs/prettify.js                                                 
npm notice 138B    docs/sort-arrow-sprite.png                                       
npm notice 6.2kB   docs/sorter.js                                                   
npm notice 689.9kB docs/tmp/coverage-87164-1677767843804-0.json                     
npm notice 76.5kB  img/intro3.png                                                   
npm notice 161.2kB img/intro4.png                                                   
npm notice 1.1kB   package.json                                                     
npm notice 238B    salida.js                                                        
npm notice 2.3kB   src/logging-espree.js                                            
npm notice 238B    test/data/correct-logged1.js                                     
npm notice 250B    test/data/correct-logged2.js                                     
npm notice 412B    test/data/correct-logged3.js                                     
npm notice 189B    test/data/correct-logged4.js                                     
npm notice 73B     test/data/logged-out1.txt                                        
npm notice 74B     test/data/logged-out2.txt                                        
npm notice 129B    test/data/logged-out3.txt                                        
npm notice 320B    test/data/logged-out4.txt                                        
npm notice 126B    test/data/test1.js                                               
npm notice 122B    test/data/test2.js                                               
npm notice 174B    test/data/test3.js                                               
npm notice 120B    test/data/test4.js                                               
npm notice 550B    test/test-description.mjs                                        
npm notice 1.4kB   test/test.mjs                                                    
npm notice === Tarball Details === 
npm notice name:          @helenagd/espree-logging-solution         
npm notice version:       0.3.0                                     
npm notice filename:      helenagd-espree-logging-solution-0.3.0.tgz
npm notice package size:  605.2 kB                                  
npm notice unpacked size: 2.9 MB                                    
npm notice shasum:        2255d9de5fd8867c34400d89045ea4daaede321c  
npm notice integrity:     sha512-i7CD6p7uvlSJ+[...]/QPfXZZTpMj3A==  
npm notice total files:   39                                        
npm notice 
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
+ @helenagd/espree-logging-solution@0.3.0

Y observamos que ya nos aparece en la página de NPM:

modulo1.png

Documentación del módulo

Para documentarlo, añadimos los comentarios con la etiquetas correspondientes a JSDoc en el archivo src/logging-espree.js.

Para esto de bede haber instalado la librería jsdon-to-markdown. La cual luego se puede utilizar con el comando añadido al script de package.json:

"jsdoc": "jsdo2md --files src/*.js > jsdoc/README.md"

Comprobación del módulo publicado

Volver al índice

Scripts

Volver al índice

Se han añadido los siguiente scripts al package.json:

"scripts": {
    "exec1": "node bin/log.js ./test/data/test1.js",
    "exec2": "node bin/log.js ./test/data/test2.js",
    "exec3": "node bin/log.js ./test/data/test3.js",
    "exec-out": "node bin/log.js ./test/data/test3.js -o ./bin/output.js",
    "test": "mocha test/test.mjs",
    "cov": "c8 npm test",
    "cov-docs": "c8 npm test -- --reporter=html --reporter=text --reports-dir docs"
},

Los tres primeros son para poder ejecutar los archivos de prueba de manera independiente.

El siguiente, para comprobar que acepta nuevos nombres de archivos de salida.

Los siguientes, son para ejecutar los tests, el cubrimiento y la documentación del cubrimiento.

Readme

Keywords

none

Package Sidebar

Install

npm i @helenagd/espree-logging-solution

Weekly Downloads

2

Version

0.3.2

License

none

Unpacked Size

2.99 MB

Total Files

41

Last publish

Collaborators

  • helenagd