Inhaltsverzeichnis Repo mithilfe einer Vorlage initialisieren Ausgangsstruktur einer Vorlage Regeln mithilfe von Skripts aus einer Vorlage hinzufügen Schreiben Sie Tests für das Eslint-Plugin Schreiben Sie die Eslint-Regel Kleine AST-Erklärung Letzte Variante Aktualisieren von Dokumenten mithilfe von Skripten Plugin-Veröffentlichung Verbinden Sie es mit Ihrer Anwendung Hintergrund Ich werde versuchen, ein Tutorial basierend auf meiner PR im Reatom-Repository mit einer Schritt-für-Schritt-Erklärung zu schreiben: https://github.com/artalar/Reatom/pull/488 Wenn Sie mehr wissen möchten, können Sie die Ausgabe https://github.com/artalar/reatom/issues/487 lesen. Um etwas Kontext hinzuzufügen: Reatom ist eine Zustandsverwaltungsbibliothek. Atome sind ein Konzept in Reatom, einer Zustandsverwaltungsbibliothek für React. Was sind die ESLint-Plugins und -Regeln? ESLint-Plugins sind Erweiterungen, die mit dem ESLint-Kernpaket zusammenarbeiten, um bestimmte Codierungsstandards durchzusetzen. Plugins enthalten einen , der individuelle Regeln zur Durchsetzung dieser Standards definiert. rules Jedes verfügt über eine , die die beschreibt, und eine , die das Verhalten der definiert. rule meta Regel create Regel Die benötigt ein , das für die Interaktion mit dem überprüften Code verwendet wird. Sie können es verwenden, um die Logik Ihrer Regel zu definieren, z. B. um strenge Namenskonventionen für Ihre Bibliothek zu fordern. create context Lassen Sie uns in den Code eintauchen Repo initialisieren Erstellen eines neuen TypeScript-Eslint-Projekts npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin Navigieren Sie dann zum neuen Projektverzeichnis und installieren Sie die Abhängigkeiten mit: cd reatom-eslint-plugin && npm i Ich möchte ein guter Junge sein, also initiiere ich Git. git init && git add . && git commit -m "init" Öffnen Sie als Nächstes die Datei und suchen Sie das . Dieses Feld ist wichtig, da es bei der Verwendung der Haupteinstiegspunkt für Ihr Plugin ist. Sie können es wie folgt ändern: package.json name "name": "eslint-plugin-reatom" Alternativ können Sie die Namenskonvention für bereichsbezogene Pakete verwenden: "name": "@reatom/eslint-plugin" Ausgangsstruktur - 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 😀 Im allgemeinen Index werden Dateien von Skripten generiert, sodass Sie sich darüber keine Sorgen machen müssen /* 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 }; Regeln hinzufügen und Dokumente aktualisieren In diesem Repository finden Sie einige praktische Skripte zum Hinzufügen von Regeln und zum Aktualisieren von Dokumenten. Um eine neue Regel hinzuzufügen, können Sie den folgenden Befehl verwenden: npm run add-rule atom-rule suggestion Dadurch werden drei Abschnitte für die neue Regel generiert: Dokumentation, Tests und tatsächlicher Code. Wir können den Dokumentationsabschnitt vorerst überspringen und uns auf die letzten beiden konzentrieren. Schreiben Sie Tests Als TDD-Enthusiasten (testgetriebene Entwicklung) erstellen wir zunächst einige einfache Tests in der Datei : 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'}] }, ] }); Wenn Sie die Tests jetzt ausführen, schlagen sie fehl, da wir die noch nicht implementiert haben. atomRule Die Regel schreiben In der definieren wir das Verhalten der Regel. Hier ist eine einfache Implementierung: 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; Es ist eine einfache Variante, aber hier können wir leicht verstehen, was vor sich geht. Für ein besseres Verständnis der AST-Struktur Ihres Codes können Sie oder einfach von console.log analysierte Knoten verwenden. https://astexplorer.net/ Eine kleine Erklärung zum besseren Verständnis von AST-Typisierungen Hier ist eine kleine Beschreibung jedes Bezeichners in einem kleinen Beispiel: const kek = atom('kek') : eine TypeScript-Schnittstelle, die einen Bezeichnerknoten in einem AST darstellt. Identifier const = ('kek'), und sind Bezeichnerknoten. kek atom kek atom : eine TypeScript-Schnittstelle, die einen Literalwertknoten (Zeichenfolge, Zahl, Boolescher Wert usw.) in einem AST darstellt. const kek = atom(' '), 'kek' ist ein Literal kek Literal. : eine TypeScript-Schnittstelle, die einen Funktionsaufruf-Ausdrucksknoten in einem abstrakten Syntaxbaum (AST) darstellt. CallExpression In unserem Beispiel ist ein CallExpression, der aus atom('kek') atom – Identifier und kek – Literal besteht. : eine TypeScript-Schnittstelle, die einen Variablendeklaratorknoten in einem AST darstellt VariableDeclarator In unserem Beispiel lautet der gesamte Ausdruck außer const VariableDeclarator kek = atom('kek') : eine TypeScript-Schnittstelle, die einen generischen AST-Knoten darstellt. Node Oder einfach astexplorer verwenden https://astexplorer.net/?embedable=true#/gist/7fe145026f1b15adefeb307427210d38/35f114eb5b9c4d3cb626e76aa6af7782927315ed Letzte Variante Die letzten Tests 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"); `, }, ] }); Aus Tests wissen wir, dass wir den Quellcode mithilfe unserer Regel irgendwie ändern müssen. Wie können Sie Ihre Regel reparierbar machen? Fügen Sie dem Kontextbericht eine einfache Zeile hinzu. fix: fixer => fixer.replaceText(node, replaceString) – kann ein tatsächlicher Knoten oder ein Bereich von Symbolen sein, den Sie ersetzen möchten. Knoten – welcher Code Sie erwarten. replaceString Vergessen Sie nicht, : 'code' oder : 'whitespace' für Ihre Regel-Meta-Tags hinzuzufügen. fixable fixable Wenn Sie nicht wissen, wie Sie das Problem mit eslint beheben können, probieren Sie es einfach mit Ihrem vorhandenen Projekt aus. eslint --fix ./src Code selbst 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}"`) }); } Wie Sie sehen, verfügt es lediglich über bessere Fehler, Typschutz und eine Importprüfung. Und natürlich mache ich die Regel reparierbar. Aktualisierung der Dokumente Um die Dokumente zu aktualisieren, können Sie den folgenden Befehl verwenden: npm run update Dieser Befehl aktualisiert README.md und aktualisiert die Dokumente für jede Regel (Sie müssen jedoch etwas über jede Regel in die Datei docs/{rule} schreiben). Außerdem müssen Sie sich, wie gesagt, keine Sorgen um die Indexdatei machen. Schritt veröffentlichen Stellen Sie sicher, dass die Version in Ihrer package.json enthalten ist. "version": "1.0.0" Geben Sie term ein, wenn es nicht 1.0.0 ist. npm version 1.0.0 Dann schreiben Sie einfach in die Wurzel. npm publish Alles wird mit Ihrem definierten Paketnamen erstellt und veröffentlicht. Verbinden Sie es mit Ihrer Anwendung Ich benenne mein Paket. @reatom/eslint-plugin Also muss ich es installieren. npm i @reatom/eslint-plugin Und zu meiner .eslintrc-Konfiguration hinzufügen. 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' } } Und alles funktioniert einfach (nur für sollten Sie überall statt schreiben). reatom-eslint-plugin “reatom” “@reatom" Abschluss In diesem Tutorial haben wir den Prozess der Erstellung eines ESLint-Plugins für die Reatom-Statusverwaltungsbibliothek durchlaufen. Wir schützen: So schreiben Sie ein Eslint-Plugin in Typescript. Wie man es mit Tests abdeckt. So bringen Sie es mit der Option --fix zum Laufen. So verwenden Sie meine Vorlage. So veröffentlichen Sie das Eslint-Plugin. So fügen Sie es mit eslint zu Ihrem vorhandenen Repository hinzu Ressourcen für weiteres Lernen und Erkunden 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 Viel Spaß :)