paint-brush
Cómo crear su complemento ESLint en texto mecanografiado con una plantilla, pruebas y publicaciónpor@antonkrylov322
1,940 lecturas
1,940 lecturas

Cómo crear su complemento ESLint en texto mecanografiado con una plantilla, pruebas y publicación

por Anton Krylov13m2023/03/20
Read on Terminal Reader

Demasiado Largo; Para Leer

Escriba el complemento eslint usted mismo usando mecanografiado con pruebas y plantilla. Sabrá más sobre AST y cómo manejarlo en TypecScript (sí, es fácil si lo conoce bien). También puede consultar mi PR y el historial de compromisos para comprender el historial de pensamientos de este artículo :)
featured image - Cómo crear su complemento ESLint en texto mecanografiado con una plantilla, pruebas y publicación
Anton Krylov HackerNoon profile picture
0-item

Tabla de contenido

  1. Inicializar repositorio usando una plantilla
  2. Estructura inicial de una plantilla
  3. Agregar reglas usando scripts de una plantilla
  4. Escribir pruebas para el complemento Eslint
  5. Escribir la regla de Eslint
  6. Pequeña explicación de AST
  7. última variante
  8. Actualización de documentos mediante scripts
  9. Publicación de complementos
  10. Conéctelo con su aplicación

Fondo

Intentaré escribir un tutorial basado en mi PR en el repositorio de Reatom con una explicación paso a paso: https://github.com/artalar/Reatom/pull/488


Si quieres saber más, puedes leer el problema https://github.com/artalar/reatom/issues/487.


Para agregar un poco de contexto, Reatom es una biblioteca de administración de estado. Los átomos son un concepto en Reatom, una biblioteca de administración de estado para React.

¿Qué son los complementos y las reglas de ESLint?

Los complementos de ESLint son extensiones que funcionan con el paquete principal de ESLint para aplicar estándares de codificación específicos. Los complementos contienen una carpeta rules , que define reglas individuales para hacer cumplir estos estándares.


Cada módulo rule tiene una propiedad meta que describe la regla y una propiedad create que define el comportamiento de la regla .


La función create toma un argumento context , que se usa para interactuar con el código que se está verificando, y puede usarlo para definir la lógica de su regla, como requerir convenciones de nomenclatura estrictas para su biblioteca.

Sumerjámonos en el código

Inicializar repositorio

Creando un nuevo proyecto TypeScript eslint

 npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin


Luego, navegue al nuevo directorio del proyecto e instale las dependencias con:

 cd reatom-eslint-plugin && npm i


Quiero ser un buen chico, así que inicio git.

 git init && git add . && git commit -m "init"


A continuación, abra el archivo package.json y localice el campo name . Este campo es esencial porque será el punto de entrada principal para su complemento cuando se use. Puedes cambiarlo por lo siguiente:

 "name": "eslint-plugin-reatom"


Alternativamente, puede usar la convención de nomenclatura de paquetes con ámbito:

 "name": "@reatom/eslint-plugin"

Estructura Inicial

 - scripts // some automation to concentrate on writing rules - docs - rules // here will be generated by npm run add-rule files - src - configs recommended.ts // generated config - rules // all your rules index.ts // Connection point to your plugin, autogenerated by scripts/lib/update-lib-index.ts


En el índice general, los archivos se generarán mediante secuencias de comandos, por lo que no debe preocuparse por eso 😀

 /* DON'T EDIT THIS FILE. This is generated by 'scripts/lib/update-lib-index.ts' */ import { recommended } from './configs/recommended'; import exampleRule from './rules/example-rule' export const configs = { recommended }; export const rules = { 'example-rule': exampleRule };

Agregar reglas y actualizar documentos

En este repositorio, encontrará algunos scripts convenientes para agregar reglas y actualizar documentos. Para agregar una nueva regla, puede usar el siguiente comando:

 npm run add-rule atom-rule suggestion


Esto generará tres secciones para la nueva regla: documentación, pruebas y código real. Podemos omitir la sección de documentación por ahora y centrarnos en los dos últimos.

escribir pruebas

Como entusiasta de TDD (desarrollo basado en pruebas), comenzaremos creando algunas pruebas simples en el archivo tests/atom-rule.ts :

 // tests/atom-rule.ts tester.run('atom-rule', atomRule, { valid: [ { code: 'const countAtom = atom(0, "countAtom");' }, ], invalid: [ { code: `const countAtom = atom(0);`, errors: [{ message: 'atom name is not defined' }] }, { code: 'const countAtom = atom(0, "count");', errors: [{ message: 'atom name is defined bad'}] }, ] });


Si ejecuta las pruebas ahora, fallarán porque aún no hemos implementado atomRule .

Escribir la regla

atomRule es donde definimos el comportamiento de la regla. Aquí hay una implementación simple:

 import { Rule } from "eslint"; const rule: Rule.RuleModule = { meta: { docs: { description: "Add name for every atom call", // simply describe your rule recommended: true, // if it's recommended, then npm run update will add it to recommmended config }, type: "suggestion" }, create: function (context: Rule.RuleContext): Rule.RuleListener { return { VariableDeclaration: node => { // listener for declaration, here we can specifiy more specific selector node.declarations.forEach(d => { if (d.init?.type !== 'CallExpression') return; if (d.init.callee.type !== 'Identifier') return; if (d.init.callee.name !== 'atom') return; if (d.id.type !== 'Identifier') return; // just guard everything that we don't need if (d.init.arguments.length <= 1) { // show error in user code context.report({ message: `atom name is not defined`, // here we can pass what will be underlined by red/yellow line node, }) } if (d.init.arguments[1]?.type !== 'Literal') return; // just another guard if (d.init.arguments[1].value !== d.id.name) { context.report({ message: `atom name is defined bad`, node }) } }) } }; }, }; export default rule;


Es una variante simple, pero aquí podemos entender fácilmente lo que está pasando.


Para una mejor comprensión de la estructura AST de su código, puede usar https://astexplorer.net/ o simplemente nodos analizados de console.log.

Una pequeña explicación para escribir AST Mejor comprensión

Aquí hay una pequeña descripción de cada identificador en un pequeño ejemplo:

const kek = atom('kek')


  1. Identifier : una interfaz TypeScript que representa un nodo identificador en un AST.

    1. const kek = atom ('kek'), kek y atom son nodos de identificadores.


  2. Literal : una interfaz de TypeScript que representa un nodo de valor literal (cadena, número, booleano, etc.) en un AST. const kek = atom(' kek '), 'kek' es un literal.


  3. CallExpression : una interfaz de TypeScript que representa un nodo de expresión de llamada de función en un árbol de sintaxis abstracta (AST).


    1. En nuestro ejemplo, atom('kek') es una CallExpression, que consta de atom - Identificador y kek - Literal.


  4. VariableDeclarator : una interfaz TypeScript que representa un nodo declarador de variables en un AST


    1. En nuestro ejemplo, la expresión completa excepto const es VariableDeclarator kek = atom('kek')


  5. Node : una interfaz TypeScript que representa un nodo AST genérico.


O simplemente usando astexplorer

Variante final

Las pruebas finales

 tester.run('atom-rule', rule, { valid: [ { code: ` import { atom } from '@reatom/framework' const countAtom = atom(0, "countAtom"); ` }, { code: `const countAtom = atom(0);`, }, { code: 'const countAtom = atom(0, "count");', }, ], invalid: [ { code: ` import { atom } from '@reatom/framework' const countAtom = atom(0); `, errors: [{ message: 'atom "countAtom" should has a name inside atom() call', }], output: ` import { atom } from '@reatom/framework' const countAtom = atom(0, "countAtom"); `, }, { code: ` import { atom } from '@reatom/framework' const countAtom = atom(0, "count"); `, errors: [{ message: `atom "countAtom" should be named as it's variable name, rename it to "countAtom"` }], output: ` import { atom } from '@reatom/framework' const countAtom = atom(0, "countAtom"); `, }, ] });


De las pruebas, entendemos que necesitamos cambiar de alguna manera el código fuente usando nuestra regla.

¿Cómo hacer que su regla sea reparable?

Agregue una línea simple al informe de contexto.

 fix: fixer => fixer.replaceText(node, replaceString)


nodo : puede ser un nodo real o un rango de símbolos que desea reemplazar.


replaceString : qué código espera ver.


No olvide agregar reparable : 'código' o reparable : 'espacio en blanco' para las etiquetas meta de su regla.


Si no está familiarizado con cómo solucionarlo con eslint, intente con su proyecto existente.

 eslint --fix ./src

Código mismo

 import { Rule } from "eslint"; import { CallExpression, Identifier, Literal, VariableDeclarator, Node } from 'estree'; import { isIdentifier, isLiteral } from "../lib"; type AtomCallExpression = CallExpression & { callee: Identifier, arguments: [Literal] | [Literal, Literal] } type AtomVariableDeclarator = VariableDeclarator & { id: Identifier, init: AtomCallExpression } const noname = (atomName: string) => `atom "${atomName}" should has a name inside atom() call`; const invalidName = (atomName: string) => `atom "${atomName}" should be named as it's variable name, rename it to "${atomName}"`; export const atomRule: Rule.RuleModule = { meta: { type: 'suggestion', docs: { recommended: true, description: "Add name for every atom call" }, fixable: 'code' }, create: function (context: Rule.RuleContext): Rule.RuleListener { let importedFromReatom = false; return { ImportSpecifier(node) { const imported = node.imported.name; // @ts-ignore const from = node.parent.source.value; if (from.startsWith('@reatom') && imported === 'atom') { importedFromReatom = true; } }, VariableDeclarator: d => { if (!isAtomVariableDeclarator(d) || !importedFromReatom) return; if (d.init.arguments.length === 1) { reportUndefinedAtomName(context, d); } else if (isLiteral(d.init.arguments[1]) && d.init.arguments[1].value !== d.id.name) { reportBadAtomName(context, d); } } }; } } function isAtomCallExpression(node?: Node | null): node is AtomCallExpression { return node?.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === 'atom'; } function isAtomVariableDeclarator(node: VariableDeclarator): node is AtomVariableDeclarator { return isAtomCallExpression(node.init) && isIdentifier(node.id); } function reportUndefinedAtomName(context: Rule.RuleContext, d: AtomVariableDeclarator) { context.report({ message: noname(d.id.name), node: d, fix: fixer => fixer.insertTextAfter(d.init.arguments[0], `, "${d.id.name}"`) }); } function reportBadAtomName(context: Rule.RuleContext, d: AtomVariableDeclarator) { context.report({ message: invalidName(d.id.name), node: d, fix: fixer => fixer.replaceText(d.init.arguments[1], `"${d.id.name}"`) }); }


Como puede ver, solo tiene mejores errores, protectores de tipo e incluye verificación de importación. Y, por supuesto, hago que la regla sea corregible.

Actualización de los documentos

Para actualizar los documentos, puede utilizar el siguiente comando:

 npm run update


Este comando actualizará README.md y actualizará los documentos para cada regla (pero debe escribir un poco sobre cada regla en el archivo docs/{rule}).


Además, como dije, no necesita preocuparse por el archivo de índice.

Paso de publicación

Asegúrese de que la versión esté en su paquete.json.

 "version": "1.0.0"


Escriba en término si no es 1.0.0.

 npm version 1.0.0


Entonces solo escribe en la raíz.

 npm publish


Todo se compilará y publicará con su nombre de paquete definido.

Conéctelo con su aplicación

Nombro mi paquete.

 @reatom/eslint-plugin


Entonces, necesito instalarlo.

 npm i @reatom/eslint-plugin


Y agregar a mi configuración .eslintrc.

 module.exports = { plugins: [ "@reatom" ], // use all rules extends: [ "plugin:@reatom/recommended" ], // or pick some rules: { '@reatom/atom-rule': 'error', // aditional rules, you can see it in PR '@reatom/action-rule': 'error', '@reatom/reatom-prefix-rule': 'error' } }


Y todo simplemente funciona (para solo reatom-eslint-plugin debe escribir “reatom” en lugar de “@reatom" en todas partes).

Conclusión

En este tutorial, repasamos el proceso de creación de un complemento ESLint para la biblioteca de administración de estado de Reatom. Nosotros cubrimos:


  1. Cómo escribir un complemento de eslint en Typescript.
  2. Cómo cubrirlo con pruebas.
  3. Cómo hacer que funcione con la opción --fix.
  4. Cómo usar mi plantilla.
  5. Cómo publicar el complemento eslint.
  6. Cómo agregarlo a su repositorio existente con eslint


Recursos para seguir aprendiendo y explorando

  1. https://github.com/pivaszbs/typescript-template-eslint-plugin
  2. https://astexplorer.net/
  3. https://github.com/artalar/reatom/pull/488/files
  4. https://eslint.org/docs/latest/extend/plugins
  5. https://www.reatom.dev/
  6. https://github.com/artalar/reatom
  7. https://docs.npmjs.com/about-semantic-versioning


Divertirse :)