Índice Inicializar repositório usando um modelo Estrutura inicial de um modelo Adicionando regras usando scripts de um modelo Escrever testes para o plug-in Eslint Escreva a regra de Eslint Pequena explicação AST variante final Atualizando documentos usando scripts Publicação de plugins Conecte-o com seu aplicativo Fundo Vou tentar escrever um tutorial baseado no meu PR no repositório Reatom com uma explicação passo a passo: https://github.com/artalar/Reatom/pull/488 Se você quiser saber mais, pode ler a edição https://github.com/artalar/reatom/issues/487. Para adicionar um pouco de contexto, Reatom é uma biblioteca de gerenciamento de estado. Atoms são um conceito no Reatom, uma biblioteca de gerenciamento de estado para React. Quais são os plug-ins e regras do ESLint? Os plug-ins ESLint são extensões que funcionam com o pacote ESLint principal para impor padrões de codificação específicos. Os plug-ins contêm uma pasta , que define regras individuais para aplicar esses padrões. rules Cada módulo possui uma propriedade que descreve a e uma propriedade que define o comportamento . rule meta regra create da regra A função recebe um argumento , que é usado para interagir com o código que está sendo verificado, e você pode usá-lo para definir a lógica de sua regra, como exigir convenções de nomenclatura estritas para sua biblioteca. create context Vamos mergulhar no código Inicializar repositório Criando um novo projeto eslint TypeScript npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin Em seguida, navegue até o novo diretório do projeto e instale as dependências com: cd reatom-eslint-plugin && npm i Eu quero ser um bom menino, então eu init git. git init && git add . && git commit -m "init" Em seguida, abra o arquivo e localize o campo . Este campo é essencial porque será o ponto de entrada principal para o seu plugin quando for usado. Você pode alterá-lo para o seguinte: package.json name "name": "eslint-plugin-reatom" Como alternativa, você pode usar a convenção de nomenclatura de pacote com escopo: "name": "@reatom/eslint-plugin" Estrutura 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 😀 No índice geral, os arquivos serão gerados por scripts, então você não precisa se preocupar com isso /* 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 }; Adicionando regras e atualizando documentos Neste repositório, você encontrará alguns scripts convenientes para adicionar regras e atualizar documentos. Para adicionar uma nova regra, você pode usar o seguinte comando: npm run add-rule atom-rule suggestion Isso gerará três seções para a nova regra: documentação, testes e código real. Podemos pular a seção de documentação por enquanto e focar nas duas últimas. Escrever testes Como entusiasta de TDD (desenvolvimento orientado a testes), começaremos criando alguns testes simples no arquivo : 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'}] }, ] }); Se você executar os testes agora, eles falharão porque ainda não implementamos o . atomRule Escrevendo a regra A é onde definimos o comportamento da regra. Aqui está uma implementação simples: atomRule 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; É uma variante simples, mas aqui podemos entender facilmente o que está acontecendo. Para uma melhor compreensão da estrutura AST do seu código, você pode usar ou simplesmente nós parsed console.log. https://astexplorer.net/ Uma pequena explicação para a digitação AST Melhor compreensão Aqui está uma pequena descrição de cada identificador em um pequeno exemplo: const kek = atom('kek') : uma interface TypeScript que representa um nó identificador em um AST. Identifier const = ('kek'), e são nós identificadores. kek atom kek atom : uma interface TypeScript que representa um nó de valor literal (string, número, booleano etc.) em um AST. const kek = atom(' '), 'kek' é um Literal kek Literal. : uma interface TypeScript que representa um nó de expressão de chamada de função em uma árvore de sintaxe abstrata (AST). CallExpression Em nosso exemplo, é uma CallExpression, que consiste em atom('kek') atom - Identifier e kek - Literal. : uma interface TypeScript que representa um nó declarator de variável em um AST VariableDeclarator Em nosso exemplo, toda a expressão exceto const é VariableDeclarator kek = atom('kek') : uma interface TypeScript que representa um nó AST genérico. Node Ou simplesmente usando o astexplorer https://astexplorer.net/?embedable=true#/gist/7fe145026f1b15adefeb307427210d38/35f114eb5b9c4d3cb626e76aa6af7782927315ed Variante Final os testes finais 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"); `, }, ] }); A partir dos testes, entendemos que precisamos de alguma forma alterar o código-fonte usando nossa regra. Como tornar sua regra corrigível? Adicione uma linha simples ao relatório de contexto. fix: fixer => fixer.replaceText(node, replaceString) - pode ser um nó real ou um intervalo de símbolos que você deseja substituir. nó - qual código você espera ver. replaceString Não se esqueça de adicionar : 'code' ou : 'whitespace' para suas meta tags de regra. fixable fixable Se você não estiver familiarizado com como corrigi-lo com o eslint, tente em seu projeto existente. eslint --fix ./src próprio código 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 você pode ver, ele só tem erros melhores, guardas de tipo e inclui verificação de importação. E, claro, eu torno a regra corrigível. Atualizando os documentos Para atualizar os documentos, você pode usar o seguinte comando: npm run update Este comando irá atualizar o README.md e atualizar os documentos para cada regra (mas você precisa escrever um pouco sobre cada regra no arquivo docs/{rule}). Além disso, como eu disse, você não precisa se preocupar com o arquivo de índice. Etapa de Publicação Certifique-se de que a versão esteja em seu package.json. "version": "1.0.0" Escreva em termos se não for 1.0.0. npm version 1.0.0 Depois é só escrever na raiz. npm publish Tudo será construído e publicado com o nome do pacote definido. Conecte-o com seu aplicativo Eu nomeio meu pacote. @reatom/eslint-plugin Então, eu preciso instalá-lo. npm i @reatom/eslint-plugin E adicione à minha configuração .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' } } E tudo simplesmente funciona (para apenas você deve escrever em vez de em todos os lugares). reatom-eslint-plugin “reatom” “@reatom" Conclusão Neste tutorial, percorremos o processo de criação de um plug-in ESLint para a biblioteca de gerenciamento de estado Reatom. Cobrimos: Como escrever um plugin eslint em Typescript. Como cobri-lo com testes. Como fazê-lo funcionar com a opção --fix. Como usar meu modelo. Como publicar o plugin eslint. Como adicioná-lo ao seu repositório existente com eslint Recursos para aprendizado e exploração adicionais https://github.com/pivaszbs/typescript-template-eslint-plugin https://astexplorer.net/ https://github.com/artalar/reatom/pull/488/files https://eslint.org/docs/latest/extend/plugins https://www.reatom.dev/ https://github.com/artalar/reatom https://docs.npmjs.com/about-semantic-versioning Divirta-se :)