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, 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 rules
klasörü içerir.
Her rule
modülünde, kuralı açıklayan bir meta
özelliği ve kuralın davranışını tanımlayan bir create
özelliği bulunur.
create
işlevi, kontrol edilen kodla etkileşimde bulunmak için kullanılan bir context
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.
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 package.json
dosyasını açın ve name
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:
"name": "eslint-plugin-reatom"
Alternatif olarak kapsamlı paket adlandırma kuralını kullanabilirsiniz:
"name": "@reatom/eslint-plugin"
- 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 };
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.
Bir TDD (test odaklı geliştirme) meraklısı olarak, tests/atom-rule.ts
dosyasında bazı basit testler oluşturarak başlayacağız:
// 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 atomRule
henüz uygulamadığımız için başarısız olacaklardır.
atomRule
, kuralın davranışını tanımladığımız yerdir. İşte basit bir uygulama:
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 https://astexplorer.net/ veya basitçe console.log ayrıştırılmış düğümlerini kullanabilirsiniz.
Küçük bir örnekte her tanımlayıcının küçük bir açıklamasını burada bulabilirsiniz:
const kek = atom('kek')
Identifier
: AST'deki bir tanımlayıcı düğümü temsil eden bir TypeScript arayüzü.
const kek = atom ('kek'), kek ve atom Tanımlayıcı düğümleridir.
Literal
: AST'deki değişmez değer (dize, sayı, boolean vb.) düğümünü temsil eden bir TypeScript arabirimi. const kek = atom(' kek '), 'kek' bir Değişmezdir.
CallExpression
: soyut sözdizimi ağacındaki (AST) bir işlev çağrısı ifadesi düğümünü temsil eden bir TypeScript arabirimi.
Örneğimizde atom('kek') , atom - Tanımlayıcı ve kek - Değişmez ifadelerden oluşan bir CallExpression'dır.
VariableDeclarator
: AST'deki değişken bildirici düğümünü temsil eden bir TypeScript arayüzü
Örneğimizde const dışındaki ifadenin tamamı VariableDeclarator kek = atom('kek')
Node
: genel bir AST düğümünü temsil eden bir TypeScript arayüzü.
Veya sadece astexplorer'ı kullanarak
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.
Bağlam raporuna basit bir satır ekleyin.
fix: fixer => fixer.replaceText(node, replaceString)
düğüm - değiştirmek istediğiniz gerçek bir düğüm veya sembol aralığı olabilir.
replacementString - görmeyi beklediğiniz kod.
Kural meta etiketleriniz için fixable : 'code' veya fixable : 'boşluk' eklemeyi unutmayın.
Eslint ile sorunu nasıl çözeceğinizi bilmiyorsanız mevcut projenizi deneyin.
eslint --fix ./src
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.
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.
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.
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 reatom-eslint-plugin
için her yere “@reatom"
yerine “reatom”
yazmalısınız).
Bu eğitimde, Reatom durum yönetimi kitaplığı için bir ESLint eklentisi oluşturma sürecini inceledik. Biz karşılarız:
Daha fazla öğrenme ve keşif için kaynaklar
İyi eğlenceler :)