Tôi sẽ cố gắng viết một hướng dẫn dựa trên PR của tôi trong kho lưu trữ Reatom với giải thích từng bước: https://github.com/artalar/Reatom/pull/488
Nếu bạn muốn biết thêm, bạn có thể đọc vấn đề https://github.com/artalar/reatom/issues/487.
Để thêm một chút bối cảnh, Reatom là một thư viện quản lý trạng thái. Atoms là một khái niệm trong Reatom, một thư viện quản lý trạng thái cho React.
Các plugin ESLint là các tiện ích mở rộng hoạt động với gói ESLint cốt lõi để thực thi các tiêu chuẩn mã hóa cụ thể. Các plugin chứa một thư mục rules
xác định các quy tắc riêng để thực thi các tiêu chuẩn này.
Mỗi mô-đun rule
có thuộc tính meta
mô tả quy tắc và thuộc tính create
xác định hành vi của quy tắc .
Hàm create
nhận một đối số context
, được sử dụng để tương tác với mã đang được kiểm tra và bạn có thể sử dụng nó để xác định logic của quy tắc của mình, chẳng hạn như yêu cầu các quy ước đặt tên nghiêm ngặt cho thư viện của bạn.
Tạo một dự án TypeScript eslint mới
npx degit https://github.com/pivaszbs/typescript-template-eslint-plugin reatom-eslint-plugin
Sau đó, điều hướng đến thư mục dự án mới và cài đặt các phụ thuộc với:
cd reatom-eslint-plugin && npm i
Tôi muốn trở thành một cậu bé ngoan, vì vậy tôi bắt đầu git.
git init && git add . && git commit -m "init"
Tiếp theo, mở tệp package.json
và tìm trường name
. Trường này rất cần thiết vì nó sẽ là điểm vào chính cho plugin của bạn khi nó được sử dụng. Bạn có thể thay đổi nó như sau:
"name": "eslint-plugin-reatom"
Ngoài ra, bạn có thể sử dụng quy ước đặt tên gói có phạm vi:
"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
Về mục lục chung, các tệp sẽ được tạo bởi các tập lệnh, vì vậy bạn không cần phải lo lắng về điều đó 😀
/* 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 };
Trong kho lưu trữ này, bạn sẽ tìm thấy một số tập lệnh thuận tiện để thêm quy tắc và cập nhật tài liệu. Để thêm một quy tắc mới, bạn có thể sử dụng lệnh sau:
npm run add-rule atom-rule suggestion
Điều này sẽ tạo ra ba phần cho quy tắc mới: tài liệu, kiểm tra và mã thực tế. Bây giờ chúng ta có thể bỏ qua phần tài liệu và tập trung vào hai phần cuối cùng.
Là một người đam mê TDD (phát triển dựa trên thử nghiệm), chúng ta sẽ bắt đầu bằng cách tạo một số thử nghiệm đơn giản trong tệp 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'}] }, ] });
Nếu bạn chạy thử nghiệm ngay bây giờ, chúng sẽ không thành công vì chúng tôi chưa triển khai atomRule
.
atomRule
là nơi chúng ta xác định hành vi của quy tắc. Đây là một thực hiện đơn giản:
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;
Đó là một biến thể đơn giản, nhưng ở đây, chúng ta có thể dễ dàng hiểu chuyện gì đang xảy ra.
Để hiểu rõ hơn về cấu trúc AST của mã, bạn có thể sử dụng https://astexplorer.net/ hoặc đơn giản là các nút được phân tích cú pháp console.log.
Dưới đây là mô tả nhỏ về từng mã định danh trong một ví dụ nhỏ:
const kek = atom('kek')
Identifier
: một giao diện TypeScript đại diện cho một nút định danh trong AST.
const kek = nguyên tử ('kek'), kek và nguyên tử là các nút Định danh.
Literal
: một giao diện TypeScript đại diện cho một nút giá trị bằng chữ (chuỗi, số, boolean, v.v.) trong một AST. const kek = nguyên tử(' kek '), 'kek' là một Nghĩa đen.
CallExpression
: giao diện TypeScript đại diện cho nút biểu thức gọi hàm trong cây cú pháp trừu tượng (AST).
Trong ví dụ của chúng tôi, nguyên tử('kek') là một CallExpression, bao gồm nguyên tử - Định danh và kek - Nghĩa đen.
VariableDeclarator
: giao diện TypeScript đại diện cho nút khai báo biến trong AST
Trong ví dụ của chúng ta, toàn bộ biểu thức ngoại trừ const là VariableDeclarator kek = Atom('kek')
Node
: giao diện TypeScript đại diện cho nút AST chung.
Hoặc đơn giản là sử dụng astexplorer
Các bài kiểm tra cuối cùng
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"); `, }, ] });
Từ các thử nghiệm, chúng tôi hiểu rằng bằng cách nào đó chúng tôi cần thay đổi mã nguồn bằng cách sử dụng quy tắc của mình.
Thêm một dòng đơn giản vào báo cáo ngữ cảnh.
fix: fixer => fixer.replaceText(node, replaceString)
nút - có thể là một nút thực tế hoặc dãy ký hiệu mà bạn muốn thay thế.
replaceString - mã bạn muốn xem.
Đừng quên thêm mã có thể sửa : 'mã' hoặc có thể sửa : 'khoảng trắng' cho các thẻ meta quy tắc của bạn.
Nếu bạn không quen với cách sửa nó bằng eslint, chỉ cần thử với dự án hiện tại của bạn.
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}"`) }); }
Như bạn có thể thấy, nó chỉ có lỗi tốt hơn, loại bảo vệ và bao gồm kiểm tra nhập. Và, tất nhiên, tôi làm cho quy tắc có thể sửa được.
Để cập nhật tài liệu, bạn có thể sử dụng lệnh sau:
npm run update
Lệnh này sẽ cập nhật README.md và cập nhật tài liệu cho từng quy tắc (nhưng bạn cần viết một chút về từng quy tắc trong tệp docs/{rule}).
Ngoài ra, như tôi đã nói, bạn không cần lo lắng về tệp chỉ mục.
Đảm bảo phiên bản nằm trong gói.json của bạn.
"version": "1.0.0"
Viết theo thuật ngữ nếu nó không phải là 1.0.0.
npm version 1.0.0
Sau đó, chỉ cần viết trong thư mục gốc.
npm publish
Mọi thứ sẽ được xây dựng và xuất bản với tên gói đã xác định của bạn.
Tôi đặt tên cho gói của tôi.
@reatom/eslint-plugin
Vì vậy, tôi cần phải cài đặt nó.
npm i @reatom/eslint-plugin
Và thêm vào cấu hình .eslintrc của tôi.
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' } }
Và mọi thứ chỉ hoạt động (đối với reatom-eslint-plugin
, bạn nên viết “reatom”
thay vì “@reatom"
ở mọi nơi).
Trong hướng dẫn này, chúng ta đã thực hiện quá trình tạo plugin ESLint cho thư viện quản lý trạng thái Reatom. Chúng tôi bao gồm:
Tài nguyên để học hỏi và khám phá thêm
Chúc vui vẻ :)