Reatom リポジトリでの私の PR に基づいて、段階的な説明を含むチュートリアルを作成してみます: https://github.com/artalar/Reatom/pull/488
詳細を知りたい場合は、 https://github.com/artalar/reatom/issues/487 の問題を読むことができます。
少し補足すると、Reatom は状態管理ライブラリです。アトムは、React の状態管理ライブラリである Reatom の概念です。
ESLint プラグインは、コア ESLint パッケージと連携して特定のコーディング標準を適用する拡張機能です。プラグインには、これらの標準を適用するための個々のルールを定義するrules
フォルダーが含まれています。
各rule
モジュールには、ルールを説明するmeta
プロパティと、ルールの動作を定義するcreate
プロパティがあります。
create
関数は、チェック対象のコードと対話するために使用されるcontext
引数を受け取ります。これを使用して、ライブラリに厳密な命名規則を要求するなど、ルールのロジックを定義できます。
新しい TypeScript eslint プロジェクトの作成
npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin
次に、新しいプロジェクト ディレクトリに移動し、次のように依存関係をインストールします。
cd reatom-eslint-plugin && npm i
私はいい子になりたいので、git を初期化します。
git init && git add . && git commit -m "init"
次に、 package.json
ファイルを開き、 name
フィールドを見つけます。このフィールドは、使用時にプラグインのメイン エントリ ポイントになるため、必須です。次のように変更できます。
"name": "eslint-plugin-reatom"
または、スコープ付きパッケージの命名規則を使用できます。
"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
一般的なインデックスでは、スクリプトによってファイルが生成されるため、心配する必要はありません😀
/* 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 };
このリポジトリには、ルールを追加してドキュメントを更新するための便利なスクリプトがいくつかあります。新しいルールを追加するには、次のコマンドを使用できます。
npm run add-rule atom-rule suggestion
これにより、新しいルールの 3 つのセクション (ドキュメント、テスト、および実際のコード) が生成されます。ここでは、ドキュメント セクションをスキップして、最後の 2 つのセクションに焦点を当てることができます。
TDD (テスト駆動開発) 愛好家として、 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'}] }, ] });
この時点でテストを実行すると、 atomRule
まだ実装していないため、テストは失敗します。
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;
これは単純な変種ですが、ここでは何が起こっているのかを簡単に理解できます。
コードの AST 構造をよりよく理解するには、 https://astexplorer.net/または単純に console.log 解析ノードを使用できます。
小さな例での各識別子の簡単な説明を次に示します。
const kek = atom('kek')
Identifier
: AST の識別子ノードを表す TypeScript インターフェース。
const kek = atom ('kek')、 kek、およびatomは識別子ノードです。
Literal
: AST のリテラル値 (文字列、数値、ブール値など) ノードを表す TypeScript インターフェース。 const kek = atom(' kek '), 'kek' はリテラルです。
CallExpression
: 抽象構文木 (AST) で関数呼び出し式ノードを表す TypeScript インターフェース。
この例では、 atom('kek') はCallExpression であり、 atom - 識別子と kek - リテラルで構成されています。
VariableDeclarator
: AST の変数宣言子ノードを表す TypeScript インターフェース
この例では、const を除く式全体が VariableDeclarator kek = atom('kek')です。
Node
: 一般的な AST ノードを表す TypeScript インターフェース。
または単にastexplorerを使用する
最終テスト
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"); `, }, ] });
テストから、ルールを使用して何らかの形でソース コードを変更する必要があることがわかりました。
コンテキスト レポートに簡単な行を追加します。
fix: fixer => fixer.replaceText(node, replaceString)
node - 置き換えたい実際のノードまたはシンボルの範囲です。
replaceString - 表示されると予想されるコード。
ルールのメタ タグにfixable : 'code' またはfixable : 'whitespace' を追加することを忘れないでください。
eslint で修正する方法に慣れていない場合は、既存のプロジェクトで試してみてください。
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}"`) }); }
ご覧のとおり、より優れたエラー、型ガード、およびインポート チェックが含まれています。そしてもちろん、ルールを修正可能にします。
ドキュメントを更新するには、次のコマンドを使用できます。
npm run update
このコマンドは、README.md を更新し、ルールごとにドキュメントを更新します (ただし、docs/{rule} ファイルに各ルールについて少し書く必要があります)。
また、私が言ったように、インデックス ファイルについて心配する必要はありません。
バージョンが package.json にあることを確認してください。
"version": "1.0.0"
1.0.0 でない場合は term で記述します。
npm version 1.0.0
次に、ルートに書き込むだけです。
npm publish
すべてがビルドされ、定義されたパッケージ名で公開されます。
パッケージに名前を付けます。
@reatom/eslint-plugin
だから、私はそれをインストールする必要があります。
npm i @reatom/eslint-plugin
そして、私の .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' } }
そして、すべてが機能します ( reatom-eslint-plugin
の場合は“@reatom"
の代わりに“reatom”
と書く必要があります)。
このチュートリアルでは、Reatom 状態管理ライブラリ用の ESLint プラグインを作成するプロセスについて説明しました。カバーする内容:
さらなる学習と探求のためのリソース
楽しむ :)