{"_id":"@alu0101229775/espree-logging","name":"@alu0101229775/espree-logging","dist-tags":{"latest":"0.3.0"},"versions":{"0.3.0":{"name":"@alu0101229775/espree-logging","author":{"name":"Gerard Antony Caramazza Vilá","email":"alu0101229775@ull.edu.es","url":"https://github.com/alu0101229775"},"description":"Adds logs to javascript code","type":"module","bin":{"funlog":"bin/log.js"},"scripts":{"test":"mocha test/test.mjs","cov":"c8 npm test","cov-doc":"c8 --reporter=html --reporter=text --report-dir docs/coverage mocha","doc":"documentation build ./src/** -f html -o docs","test1":"bin/log.js test/data/test1.js","test2":"bin/log.js test/data/test2.js","test3":"bin/log.js test/data/test3.js"},"dependencies":{"acorn":"^8.8.2","commander":"^10.0.0","escodegen":"^2.0.0","espree":"^9.4.1","estraverse":"^5.2.0","i":"^0.3.7","underscore":"^1.12.0"},"version":"0.3.0","devDependencies":{"mocha":"^10.2.0"},"gitHead":"729d795bf8cc88fc993a24a33e4d804ee1801657","_id":"@alu0101229775/espree-logging@0.3.0","_nodeVersion":"19.7.0","_npmVersion":"9.5.0","dist":{"integrity":"sha512-Yg3zSjpu2yjkfOt/YqqMBdZz/n4cSPtMFNbqBI6l3JBkqRWq6zPhqbxJqh6FN9i3Ezj+7SFH1AY8pR2bHshP4w==","shasum":"2192219fdbbf1403a054480333cfa1ac396dac9f","tarball":"https://registry.npmjs.org/@alu0101229775/espree-logging/-/espree-logging-0.3.0.tgz","fileCount":27,"unpackedSize":31713,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCptTsVtp8NvQH2nRAXtpYU8v033N4RoGeHKZVCLraRzAIhAPyPDu1g1EzIKdjoj4bD0esMq3tnFW1fT5nvZ/YO1TO4"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkAelgACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr34w//bmXfFLFJ/IoS9T+8aHed88+s+ZqzuBUZq43t919FjLv5eGKn\r\nYfqYZTSor493UE7NN3jQnvdaNQedI5x3JrCOqy/7+HK74jeX8W1Kq2uX87yB\r\nJXdMznEEzugxqomKCKsv2lq6w9vqq1lQguW9yOhQmZgXgjMdrA4akKt/CMpz\r\n29u9v7Xfx6PkcA3nmVeLup4IfKxGqOJIvqHscU0wFlNq+wpiHLk23j10OBh8\r\natW00uW+iVJhOBwl0aAmQplraq241Zj+L7kKsRai2No8bMszPbAJuidddONS\r\ndVJXoplgaGDDJ3DJVBnq7YO61sxUBQzl8vNq9mRkU30+NwfpgXvsdUlNIADd\r\nB3IY+I8R8ytWxXldMm5u00OEhOq4c+o0NSTlCIMdPari8hy7X6OCmPS/FjWW\r\nfmVAuj1JXtriz3dTqxPHZOr0SnaxSsW0LhFzLx/OQ1LZkLotjquSXjZl7JBJ\r\nRXR5+TLhhQNUJ3+wgCYNErbMyT1zWnH7Uw3Y6eDAYrGcgwAnGENn6J9pUS3+\r\nREjn12jlhJ7/uJemccjNfl6vsQjmegtRynysLlEGDHoY64pDAMLVNMCtI7qs\r\nRn33HGngfCl/FL1GWc7kGcVAiNv9jzgVDhDWyrSVT2aDaRBUcn8gv94YTRm8\r\nREh8Z7CUl+dUS8Wd+iGbzuvfPvWgBUse7SQ=\r\n=Spnp\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"alu0101229775","email":"alu0101229775@ull.edu.es"},"directories":{},"maintainers":[{"name":"alu0101229775","email":"alu0101229775@ull.edu.es"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/espree-logging_0.3.0_1677846879863_0.2591544758798172"},"_hasShrinkwrap":false}},"time":{"created":"2023-03-03T12:34:39.766Z","0.3.0":"2023-03-03T12:34:40.038Z","modified":"2023-03-03T12:34:40.198Z"},"maintainers":[{"name":"alu0101229775","email":"alu0101229775@ull.edu.es"}],"description":"Adds logs to javascript code","author":{"name":"Gerard Antony Caramazza Vilá","email":"alu0101229775@ull.edu.es","url":"https://github.com/alu0101229775"},"readme":"[![Open in Codespaces](https://classroom.github.com/assets/launch-codespace-f4981d0f882b2a3f0472912d15f9806d57e124e0fc890972558857b51b24a6f9.svg)](https://classroom.github.com/open-in-codespaces?assignment_repo_id=10314182)\n\n# [Práctica Espree logging](https://ull-esit-gradoii-pl.github.io//practicas/espree-logging)\n\n- [Práctica Espree logging](#práctica-espree-logging)\n- [Introducción](#introducción)\n- [Resumen de lo aprendido](#resumen-de-lo-aprendido)\n- [El ejecutable](#el-ejecutable)\n- [El programa](#el-programa)\n- [Indicar los valores de los argumentos](#indicar-los-valores-de-los-argumentos)\n- [CLI con Commander.js](#cli-con-commanderjs)\n- [Reto 1: Soportar funciones flecha](#reto-1-soportar-funciones-flecha)\n- [Reto 2: Añadir el número de línea](#reto-2-añadir-el-número-de-línea)\n- [Publicación como paquete npm](#publicación-como-paquete-npm)\n- [Tests and Covering](#tests-and-covering)\n- [Rúbrica](#rúbrica)\n\n## Introducción\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nEn el repo encontrará el programa `logging-espree.js` el cual implementa una función addLogging que:\n- cuando se llama analiza el código JS que se la da como entrada\n- produciendo como salida un código JS equivalente que inserta mensajes de console.log a la entrada de cada función.\n\n## Resumen de lo aprendido\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nEl programa genera un **arbol AST** a partir de un fichero de entrada con código y, una vez se tiene dicho arbol, se recorre para generar el código de nuevo pero modificado con los console.log() a través el `escodegen` y el `transpile`.\n\nAsimismo, se ha aprendido a utilizar el `debugger` de Chrome para inspeccionar el programa.\n\n## El ejecutable\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nEl ejecutable está en `bin/log.js` y se puede ejecutar con `npm start` o `node bin/log.js`.\n\nContenido de `bin/log.js`:\n```javascript\n#!/usr/bin/env node\nimport { program } from \"commander\";\nimport { createRequire } from \"module\";\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\");\nimport { transpile } from \"../src/logging-espree.js\";\n\nprogram\n  .version(version)\n  .argument(\"<filename>\", 'file with the original code')\n  .option(\"-V, --version\", \"output the version number\")\n  .option(\"-o, --output <filename>\", \"file in which to write the output\", \"output.js\")\n  .option(\"-h, --help\", \"output usage information\")\n  .action((filename, options) => {\n    transpile(filename, options.output);\n  });\n\nprogram.parse(process.argv);\n```\n\n## El programa\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nEl programa está en `src/logging-espree.js`: \n```javascript\nimport * as escodegen from \"escodegen\";\nimport * as espree from \"espree\";\nimport * as estraverse from \"estraverse\";\nimport * as fs from \"fs/promises\";\n\n/**\n * Read the file with the js program, calls addLogin to add the login messages and writes the output\n * @param {string} input_file - The name of the input file\n * @param {string} output_file - The name of the output file (default: output.js)\n */\nexport async function transpile(inputFile, outputFile) {\n  try {\n    if (inputFile) {\n      // console.log(`Transpiling '${inputFile}' to '${outputFile}'`);\n      /* Al leer un fichero se usa una función asíncrona\n      * Todas las funciones asíncronas llevan una callback que es el único\n      * lugar donde estamos seguros que el código se va a ejecutar después\n      * de haber ejecutado la función\n      * Normalmente se pasa como argumento el error y resultado de la \n      * función asíncrona, para el caso de que no hubiera error.\n      */\n      let input = await fs.readFile(inputFile, 'utf8', (err) => {\n        console.log(`Input read from file '${inputFile}'`);\n        /// Si hay error se envía un throw\n        if (err) throw `Error reading '${inputFile}': ${err}`;\n      });\n      /// Se llama al addLoggin y se guarda en output\n      const output = addLogging(input);\n      /// Se muesta la cadena de entrada al programador\n      console.error(`input:\\n${input}\\n---`);\n      /// Manera correcta de realizar el write después del read \n      /// (dentro de la callback). Tendencia hacia la diagonalidad\n      await fs.writeFile(outputFile, output, err => {\n        /// Se comprueba si ha habido o no error y se imprime la salida por pantalla\n        if (err) throw `Can't write to '${outputFile}': ${err}`;\n        console.log(`Output in file '${outputFile}'`);\n      });\n    }\n    else program.help();  //< En caso de no usar la sintaxis correcta se imprime la ayuda\n  }\n  catch (e) {\n    console.error(`Hubo errores: ${e}`);\n  }\n}\n\n/**\n * Builds the AST and\n * Traverses it searching for function nodes and callas addBeforeNode to transform the AST\n * @param {string} code - The source code \n * @returns -- The transformed AST \n */\nexport function addLogging(code) {\n  const ast = espree.parse(code, {ecmaVersion: espree.latestEcmaVersion, loc: true});  //< Builds the AST\n  estraverse.traverse(ast, {                                                           //< Traverses the AST searching for function nodes\n    enter: function(node, parent) {\n      if (node.type === 'FunctionDeclaration' ||\n        node.type === 'FunctionExpression' ||\n        node.type === 'ArrowFunctionExpression') {                                     //< If the node is a function node, calls addBeforeNode\n        addBeforeCode(node);                                                           //< to transform the AST\n      }\n    }\n  });\n  return escodegen.generate(ast);                                                      //< Generates the code from the AST\n}\n\n/**\n * AST transformation\n * @param {AST function type node} node - The function node to be transformed\n */\nfunction addBeforeCode(node) {\n  const name = node.id ? node.id.name : '<anonymous function>';                                                        //< Gets the name of the function\n  const parameters = node.params.map(param => `\\$\\{ ${param.name} \\}`);                                                //< Gets the parameters of the function\n  const beforeCode = \"console.log('Entering \" + name + \"(\" + parameters + \") at line \" + node.loc.start.line + \"');\";  //< Builds the code to be added\n  const beforeNodes = espree.parse(beforeCode, { ecmaVersion: 6 }).body;                                               //< Builds the AST from the code to be added\n  node.body.body = beforeNodes.concat(node.body.body);                                                                 //< Adds the code to the AST\n}\n```\n\n## Indicar los valores de los argumentos\n\nSe ha modificado el código de `logging-espree.js` para que el log también indique los valores de los argumentos que se pasaron a la función. \nEjemplo:\n\n```javascript\nfunction foo(a, b) {\n  var x = 'blah';\n  var y = (function (z) {\n    return z+3;\n  })(2);\n}\nfoo(1, 'wut', 3);\n```\n\n```javascript\nfunction foo(a, b) {\n    console.log(`Entering foo(${ a }, ${ b })`);\n    var x = 'blah';\n    var y = function (z) {\n        console.log(`Entering <anonymous function>(${ z })`);\n        return z + 3;\n    }(2);\n}\nfoo(1, 'wut', 3);\n```\n\nPara ello, se ha modificado la función `addBeforeCode` para que añada el código de log de los argumentos de la función. \n```javascript\n/**\n * AST transformation\n * @param {AST function type node} node - The function node to be transformed\n */\nfunction addBeforeCode(node) {\n  const name = node.id ? node.id.name : '<anonymous function>';                                                        //< Gets the name of the function\n  const parameters = node.params.map(param => `\\$\\{ ${param.name} \\}`);                                                //< Gets the parameters of the function\n  const beforeCode = \"console.log('Entering \" + name + \"(\" + parameters + \") at line \" + node.loc.start.line + \"');\";  //< Builds the code to be added\n  const beforeNodes = espree.parse(beforeCode, { ecmaVersion: 6 }).body;                                               //< Builds the AST from the code to be added\n  node.body.body = beforeNodes.concat(node.body.body);                                                                 //< Adds the code to the AST\n}\n```\n\n## CLI con [Commander.js](https://www.npmjs.com/package/commander)\n\nSe hace un parsing de la línea de comandos mediante el módulo npm `commander.js`. \nContenido de `bin/log.js`:\n```javascript\n#!/usr/bin/env node\nimport { program } from \"commander\";\nimport { createRequire } from \"module\";\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\");\nimport { transpile } from \"../src/logging-espree.js\";\n\nprogram\n  .version(version)\n  .argument(\"<filename>\", 'file with the original code')\n  .option(\"-V, --version\", \"output the version number\")\n  .option(\"-o, --output <filename>\", \"file in which to write the output\", \"output.js\")\n  .option(\"-h, --help\", \"output usage information\")\n  .action((filename, options) => {\n    transpile(filename, options.output);\n  });\n\nprogram.parse(process.argv);\n```\n\n## Reto 1: Soportar funciones flecha\n\nPara soportar funciones flecha basta con añadir `node.type === 'ArrowFunctionExpression'` en el condicional `if` de la función `addLogging` en el fichero `logging-espree.js`:\n```javascript\n/**\n * Builds the AST and\n * Traverses it searching for function nodes and callas addBeforeNode to transform the AST\n * @param {string} code - The source code \n * @returns -- The transformed AST \n */\nexport function addLogging(code) {\n  const ast = espree.parse(code);                                                      //< Builds the AST\n  estraverse.traverse(ast, {                                                           //< Traverses the AST searching for function nodes\n    enter: function(node, parent) {\n      if (node.type === 'FunctionDeclaration' ||\n        node.type === 'FunctionExpression' ||\n        node.type === 'ArrowFunctionExpression') {                                     //< If the node is a function node, calls addBeforeNode\n        addBeforeCode(node);                                                           //< to transform the AST\n      }\n    }\n  });\n  return escodegen.generate(ast);                                                      //< Generates the code from the AST\n}\n```\n\n## Reto 2: Añadir el número de línea\n\nPara añadir el número de línea en el logging hemos añadido `node.loc.start.line` al `console.log()` y `{ecmaVersion: espree.latestEcmaVersion, loc: true}` en el parser de `espree` en el fichero `logging-espree.js`:\n```javascript\n/**\n * AST transformation\n * @param {AST function type node} node - The function node to be transformed\n */\nfunction addBeforeCode(node) {\n  const name = node.id ? node.id.name : '<anonymous function>';                                                        //< Gets the name of the function\n  const parameters = node.params.map(param => `\\$\\{ ${param.name} \\}`);                                                //< Gets the parameters of the function\n  const beforeCode = \"console.log('Entering \" + name + \"(\" + parameters + \") at line \" + node.loc.start.line + \"');\";  //< Builds the code to be added\n  const beforeNodes = espree.parse(beforeCode, { ecmaVersion: 6 }).body;                                               //< Builds the AST from the code to be added\n  node.body.body = beforeNodes.concat(node.body.body);                                                                 //< Adds the code to the AST\n}\n```\n\n```javascript\n/**\n * Builds the AST and\n * Traverses it searching for function nodes and callas addBeforeNode to transform the AST\n * @param {string} code - The source code \n * @returns -- The transformed AST \n */\nexport function addLogging(code) {\n  const ast = espree.parse(code, {ecmaVersion: espree.latestEcmaVersion, loc: true});  //< Builds the AST\n  estraverse.traverse(ast, {                                                           //< Traverses the AST searching for function nodes\n    enter: function(node, parent) {\n      if (node.type === 'FunctionDeclaration' ||\n        node.type === 'FunctionExpression' ||\n        node.type === 'ArrowFunctionExpression') {                                     //< If the node is a function node, calls addBeforeNode\n        addBeforeCode(node);                                                           //< to transform the AST\n      }\n    }\n  });\n  return escodegen.generate(ast);                                                      //< Generates the code from the AST\n}\n```\n\n## Publicación como paquete npm\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nSeguimos los pasos de la [documentación de npm](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages) para publicar nuestro paquete en `npmjs.com` también disponible en los [apuntes de la asignatura](https://ull-esit-gradoii-pl.github.io/temas/introduccion-a-javascript/creating-and-publishing-npm-module.html#create-a-user-in-the-npm-registry-with-npm-adduser).\n\nCreeamos un usuario en `npmjs.com`:\n```bash\nnpm adduser\n```\n\nIniciamos sesión en `npmjs.com`:\n```bash\nnpm login\nnpm whoami\n```\n\nConfiguramos nuestro paquete:\n```bash\nnpm set init.author.name \"Gerard Antony Caramazza Vilá\"\nnpm set init.author.email \"alu0101229775@ull.edu.es\"\nnpm set init.author.url \"https://github.com/alu0101229775\"\n```\n\nCambiamos la visibilidad del repositorio a público:\n![Visibilidad Repositorio](https://i.gyazo.com/0fd74625ba232f44057b8c48918e4f61.png)\n\nA continuación ejecutamos el siguiente comando para publicar el paquete:\n```bash\nnpm publish --access=public\n```\n\n## Tests and Covering\n>[Volver al principio 🔝](#práctica-espree-logging)\n\nPara ejecutar los test añadimos las siguientes lineas en nuestro fichero `package.json`:\n```json\n\"scripts\": {\n    \"test\": \"mocha test/test.mjs\",\n    \"cov\": \"c8 npm test\",\n    \"cov-doc\": \"c8 --reporter=html --reporter=text --report-dir docs/coverage mocha\",\n    \"doc\": \"documentation build ./src/** -f html -o docs\",\n    \"test1\": \"bin/log.js test/data/test1.js\",\n    \"test2\": \"bin/log.js test/data/test2.js\",\n    \"test3\": \"bin/log.js test/data/test3.js\"\n}\n```\n\nModificamos el fichero `test.mjs` para que se ejecuten los tests:\n```javascript\nimport { transpile } from \"../src/logging-espree.js\";\nimport assert from 'assert';\nimport * as fs from \"fs/promises\";\nimport { dirname } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nimport Tst from './test-description.mjs';\n\nconst Test = Tst.map(t => ({\n  input: __dirname + '/data/' + t.input,\n  output: __dirname + '/data/' + t.output,\n  correctLogged: __dirname + '/data/' + t.correctLogged,\n  correctOut: __dirname + '/data/' + t.correctOut,\n})\n)\n\nfunction removeSpaces(s) {\n  return s.replace(/\\s/g, '');\n}\n\nfor (let i = 0; i < Test.length; i++) {\n  it(`transpile(${Tst[i].input}, ${Tst[i].output})`, async () => {\n    /// Compile the input and check the output program is what expected\n    await transpile(Test[i].input, Test[i].output);\n    let output = await fs.readFile(Test[i].output, 'utf-8')\n    let correctLogged = await fs.readFile(Test[i].correctLogged, 'utf-8')\n    assert.equal(removeSpaces(output), removeSpaces(correctLogged));\n    await fs.unlink(Test[i].output);\n\n    /// Run the output program and check the logged output is what expected\n    let correctOut = await fs.readFile(Test[i].correctOut, 'utf-8')\n    let oldLog = console.log; // mocking console.log\n    let result = \"\";\n    console.log = function (...s) { result += s.join('') }\n      eval(output);\n      assert.equal(removeSpaces(result), removeSpaces(correctOut))\n    console.log = oldLog;\n  }); \n}\n```\n\nAñadimos 3 tests más en la carpeta `test/data`:\nTest 1:\n```javascript\nfunction foo(a) {\n  console.log(a);\n  let b = () => {\n    console.log('pl');\n  }\n  b();\n}\nfoo(() => console.log('hi'));\n```\n\n# Rúbrica\n>[Volver al principio 🔝](#práctica-espree-logging)\n\n- [x] Opciones en línea de comandos (-o, -V, -h, etc.)\n- [x] Añade mensajes de logs a la entrada de las function()\n- [x] Añade mensajes de logs a la entrada de las arrow () => { ... }\n- [x] Tutorial README.md y paneles bien presentados\n- [x] Da información correcta de los números de línea\n- [x] El package.json tiene scripts para ejecutar el programa\n- [x] El paquete está publicado en npmjs con ámbito aluXXX\n- [x] Contiene un ejecutable que se ejecuta correctamente (--help, etc.)\n- [x] El módulo exporta las funciones adecuadas\n- [x] Contiene suficientes tests\n- [x] Estudio de covering\n- [x] Se ha hecho CI con GitHub Actions\n- [x] La documentación es completa: API, ejecutable, instalación, etc.\n- [x] Se ha probado que la librería está accesible y funciona\n- [x] Se ha probado que el ejecutable queda correctamente instalado, puede ser ejecutado con el nombre publicado y produce salidas correctas\n- [x] Se ha hecho un buen uso del versionado semántico en la evolución del módulo","readmeFilename":"README.md"}