paint-brush
TypeScript'te ESLint Eklentinizi Şablon, Testler ve Yayınla Nasıl Oluşturabilirsiniz?ile@antonkrylov322
1,946 okumalar
1,946 okumalar

TypeScript'te ESLint Eklentinizi Şablon, Testler ve Yayınla Nasıl Oluşturabilirsiniz?

ile Anton Krylov13m2023/03/20
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Testler ve şablon içeren TypeScript'i kullanarak eslint eklentisini kendiniz yazın. TypeScript'te AST ve bununla nasıl başa çıkacağınız hakkında daha fazla bilgi sahibi olacaksınız (evet, eğer iyi biliyorsanız kolaydır). Ayrıca bu makalenin düşüncelerinin geçmişini anlamak için PR ve taahhüt geçmişimi inceleyebilirsiniz :)
featured image - TypeScript'te ESLint Eklentinizi Şablon, Testler ve Yayınla Nasıl Oluşturabilirsiniz?
Anton Krylov HackerNoon profile picture
0-item

İçindekiler

  1. Bir şablon kullanarak repo'yu başlat
  2. Bir şablonun başlangıç yapısı
  3. Bir şablondaki komut dosyalarını kullanarak kural ekleme
  4. Eslint eklentisi için testler yazın
  5. Eslint kuralını yaz
  6. Küçük AST açıklaması
  7. Son değişken
  8. Komut dosyalarını kullanarak dokümanları güncelleme
  9. Eklenti yayınlama
  10. 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 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.

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 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"

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, 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.

Kuralın Yazılması

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.

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')


  1. Identifier : AST'deki bir tanımlayıcı düğümü temsil eden bir TypeScript arayüzü.

    1. const kek = atom ('kek'), kek ve atom Tanımlayıcı düğümleridir.


  2. 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.


  3. CallExpression : soyut sözdizimi ağacındaki (AST) bir işlev çağrısı ifadesi düğümünü temsil eden bir TypeScript arabirimi.


    1. Örneğimizde atom('kek') , atom - Tanımlayıcı ve kek - Değişmez ifadelerden oluşan bir CallExpression'dır.


  4. VariableDeclarator : AST'deki değişken bildirici düğümünü temsil eden bir TypeScript arayüzü


    1. Örneğimizde const dışındaki ifadenin tamamı VariableDeclarator kek = atom('kek')


  5. Node : genel bir AST düğümünü temsil eden bir TypeScript arayüzü.


Veya sadece astexplorer'ı kullanarak

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)


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

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 reatom-eslint-plugin için her yere “@reatom" yerine “reatom” yazmalısınız).

Çözüm

Bu eğitimde, Reatom durum yönetimi kitaplığı için bir ESLint eklentisi oluşturma sürecini inceledik. Biz karşılarız:


  1. TypeScript'te bir eslint eklentisi nasıl yazılır?
  2. Testlerle nasıl kapatılır?
  3. --fix seçeneğiyle nasıl çalıştırılır.
  4. Şablonumu nasıl kullanırım?
  5. Eslint eklentisi nasıl yayınlanır?
  6. Eslint ile mevcut deponuza nasıl eklenir?


Daha fazla öğrenme ve keşif için kaynaklar

  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


İyi eğlenceler :)