İçindekiler Bir şablon kullanarak repo'yu başlat Bir şablonun başlangıç yapısı Bir şablondaki komut dosyalarını kullanarak kural ekleme Eslint eklentisi için testler yazın Eslint kuralını yaz Küçük AST açıklaması Son değişken Komut dosyalarını kullanarak dokümanları güncelleme Eklenti yayınlama Uygulamanıza bağlayın Arka plan Adım adım anlatımla Reatom deposundaki PR'lerime dayalı bir eğitim yazmaya çalışacağım: https://github.com/artalar/Reatom/pull/488 Daha fazlasını öğrenmek istiyorsanız https://github.com/artalar/reatom/issues/487 sayısını okuyabilirsiniz. Biraz bağlam eklemek için Reatom bir durum yönetimi kütüphanesidir. Atomlar, React'in durum yönetimi kütüphanesi olan Reatom'daki bir kavramdır. ESLint Eklentileri ve Kuralları Nelerdir? ESLint eklentileri, belirli kodlama standartlarını uygulamak için çekirdek ESLint paketiyle çalışan uzantılardır. Eklentiler, bu standartları uygulamaya yönelik bireysel kuralları tanımlayan bir klasörü içerir. rules Her modülünde, açıklayan bir özelliği ve davranışını tanımlayan bir özelliği bulunur. rule kuralı meta kuralın create işlevi, kontrol edilen kodla etkileşimde bulunmak için kullanılan bir argümanı alır ve bunu, kitaplığınız için katı adlandırma kuralları gerektirmek gibi, kuralınızın mantığını tanımlamak için kullanabilirsiniz. create context Hadi Kodun Detaylarına Girelim Repo'yu başlat Yeni bir TypeScript eslint projesi oluşturma npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin Ardından yeni proje dizinine gidin ve bağımlılıkları şununla yükleyin: cd reatom-eslint-plugin && npm i İyi bir çocuk olmak istiyorum, bu yüzden git'i başlatıyorum. git init && git add . && git commit -m "init" Daha sonra dosyasını açın ve alanını bulun. Bu alan önemlidir çünkü kullanıldığında eklentiniz için ana giriş noktası olacaktır. Bunu aşağıdaki şekilde değiştirebilirsiniz: package.json name "name": "eslint-plugin-reatom" Alternatif olarak kapsamlı paket adlandırma kuralını kullanabilirsiniz: "name": "@reatom/eslint-plugin" Başlangıç Yapısı - 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 😀 Genel indekste dosyalar komut dosyaları tarafından oluşturulacağından endişelenmenize gerek yok /* 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 }; Kural Ekleme ve Dokümanları Güncelleme Bu depoda kural eklemek ve belgeleri güncellemek için bazı kullanışlı komut dosyaları bulacaksınız. Yeni bir kural eklemek için aşağıdaki komutu kullanabilirsiniz: npm run add-rule atom-rule suggestion Bu, yeni kural için üç bölüm oluşturacaktır: belgeler, testler ve gerçek kod. Şimdilik dokümantasyon kısmını atlayıp son ikisine odaklanabiliriz. Test Yaz Bir TDD (test odaklı geliştirme) meraklısı olarak, dosyasında bazı basit testler oluşturarak başlayacağız: 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'}] }, ] }); Testleri şimdi çalıştırırsanız henüz uygulamadığımız için başarısız olacaklardır. atomRule Kuralın Yazılması , kuralın davranışını tanımladığımız yerdir. İşte basit bir uygulama: 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; Bu basit bir değişken ama burada neler olduğunu kolayca anlayabiliyoruz. Kodunuzun AST yapısını daha iyi anlamak için veya basitçe console.log ayrıştırılmış düğümlerini kullanabilirsiniz. https://astexplorer.net/ AST Tiplemelerinin Daha İyi Anlaşılması İçin Küçük Bir Açıklama Küçük bir örnekte her tanımlayıcının küçük bir açıklamasını burada bulabilirsiniz: const kek = atom('kek') : AST'deki bir tanımlayıcı düğümü temsil eden bir TypeScript arayüzü. Identifier const = ('kek'), ve Tanımlayıcı düğümleridir. kek atom kek atom : AST'deki değişmez değer (dize, sayı, boolean vb.) düğümünü temsil eden bir TypeScript arabirimi. const kek = atom(' '), 'kek' bir Literal kek Değişmezdir. : soyut sözdizimi ağacındaki (AST) bir işlev çağrısı ifadesi düğümünü temsil eden bir TypeScript arabirimi. CallExpression Örneğimizde , atom('kek') atom - Tanımlayıcı ve kek - Değişmez ifadelerden oluşan bir CallExpression'dır. : AST'deki değişken bildirici düğümünü temsil eden bir TypeScript arayüzü VariableDeclarator Örneğimizde const dışındaki ifadenin tamamı VariableDeclarator kek = atom('kek') : genel bir AST düğümünü temsil eden bir TypeScript arayüzü. Node Veya sadece astexplorer'ı kullanarak https://astexplorer.net/?embedable=true#/Gist/7fe145026f1b15adefeb307427210d38/35f114eb5b9c4d3cb626e76aa6af7782927315ed Nihai Varyant Son testler 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"); `, }, ] }); Testlerden, kuralımızı kullanarak kaynak kodunu bir şekilde değiştirmemiz gerektiğini anlıyoruz. Kuralınızı Nasıl Düzeltilebilir Hale Getirebilirsiniz? Bağlam raporuna basit bir satır ekleyin. fix: fixer => fixer.replaceText(node, replaceString) - değiştirmek istediğiniz gerçek bir düğüm veya sembol aralığı olabilir. düğüm - görmeyi beklediğiniz kod. replacementString Kural meta etiketleriniz için : 'code' veya : 'boşluk' eklemeyi unutmayın. fixable fixable Eslint ile sorunu nasıl çözeceğinizi bilmiyorsanız mevcut projenizi deneyin. eslint --fix ./src Kodun Kendisi 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}"`) }); } Gördüğünüz gibi, sadece daha iyi hatalara sahip, korumaları yazıyor ve içe aktarma kontrolünü içeriyor. Ve tabii ki kuralı düzeltilebilir hale getiriyorum. Dokümanları Güncelleme Belgeleri güncellemek için aşağıdaki komutu kullanabilirsiniz: npm run update Bu komut README.md dosyasını ve her kural için dokümanları güncelleyecektir (ancak docs/{rule} dosyasına her kural hakkında biraz yazmanız gerekir). Ayrıca dediğim gibi indeks dosyası konusunda endişelenmenize gerek yok. Adımı Yayınla Sürümün package.json dosyanızda olduğundan emin olun. "version": "1.0.0" 1.0.0 değilse terim olarak yazın. npm version 1.0.0 Daha sonra köke yazmanız yeterli. npm publish Her şey tanımladığınız paket adınızla oluşturulacak ve yayınlanacaktır. Uygulamanıza Bağlayın Paketime isim veriyorum. @reatom/eslint-plugin Yani onu yüklemem gerekiyor. npm i @reatom/eslint-plugin Ve .eslintrc yapılandırmama ekleyin. 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' } } Ve her şey çalışıyor (sadece için her yere yerine yazmalısınız). reatom-eslint-plugin “@reatom" “reatom” Çözüm Bu eğitimde, Reatom durum yönetimi kitaplığı için bir ESLint eklentisi oluşturma sürecini inceledik. Biz karşılarız: TypeScript'te bir eslint eklentisi nasıl yazılır? Testlerle nasıl kapatılır? --fix seçeneğiyle nasıl çalıştırılır. Şablonumu nasıl kullanırım? Eslint eklentisi nasıl yayınlanır? Eslint ile mevcut deponuza nasıl eklenir? Daha fazla öğrenme ve keşif için kaynaklar 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 İyi eğlenceler :)