paint-brush
Cách tạo Plugin ESLint của bạn trong TypeScript với Mẫu, Kiểm tra và Xuất bảntừ tác giả@antonkrylov322
1,879 lượt đọc
1,879 lượt đọc

Cách tạo Plugin ESLint của bạn trong TypeScript với Mẫu, Kiểm tra và Xuất bản

từ tác giả Anton Krylov13m2023/03/20
Read on Terminal Reader

dài quá đọc không nổi

Tự viết plugin eslint bằng cách sử dụng bản thảo với các bài kiểm tra và mẫu. Bạn sẽ biết thêm về AST và cách xử lý nó trong typeccrip (vâng, nó rất dễ nếu bạn biết rõ về nó). Ngoài ra, bạn có thể xem qua PR và lịch sử cam kết của tôi để hiểu lịch sử suy nghĩ của bài viết này :)
featured image - Cách tạo Plugin ESLint của bạn trong TypeScript với Mẫu, Kiểm tra và Xuất bản
Anton Krylov HackerNoon profile picture
0-item

Mục lục

  1. Khởi tạo repo bằng mẫu
  2. Cấu trúc ban đầu của một mẫu
  3. Thêm quy tắc bằng tập lệnh từ mẫu
  4. Viết bài kiểm tra cho plugin Eslint
  5. Viết quy tắc Eslint
  6. Giải thích nhỏ về AST
  7. biến thể cuối cùng
  8. Cập nhật tài liệu bằng tập lệnh
  9. xuất bản plugin
  10. Kết nối nó với ứng dụng của bạn

Lý lịch

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.

Plugin và Quy tắc ESLint là gì?

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.

Hãy đi sâu vào mã

Khởi tạo Repo

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"

Cấu trúc ban đầu

 - 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 };

Thêm quy tắc và cập nhật tài liệu

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.

Viết bài kiểm tra

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 .

Viết quy tắc

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.

Giải thích nhỏ về cách gõ AST Hiểu rõ hơn

Dưới đây là mô tả nhỏ về từng mã định danh trong một ví dụ nhỏ:

const kek = atom('kek')


  1. Identifier : một giao diện TypeScript đại diện cho một nút định danh trong AST.

    1. const kek = nguyên tử ('kek'), keknguyên tử là các nút Định danh.


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


  3. 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).


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


  4. VariableDeclarator : giao diện TypeScript đại diện cho nút khai báo biến trong AST


    1. Trong ví dụ của chúng ta, toàn bộ biểu thức ngoại trừ const là VariableDeclarator kek = Atom('kek')


  5. Node : giao diện TypeScript đại diện cho nút AST chung.


Hoặc đơn giản là sử dụng astexplorer

Biến thể cuối cùng

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.

Làm thế nào để làm cho quy tắc của bạn có thể sửa được?

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

Mã chính nó

 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

Để 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.

Xuất bản bướ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.

Kết nối nó với ứng dụng 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).

Phần kết luận

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:


  1. Cách viết plugin eslint trong Typescript.
  2. Làm thế nào để che nó bằng các bài kiểm tra.
  3. Làm cách nào để nó hoạt động với tùy chọn --fix.
  4. Làm thế nào để sử dụng mẫu của tôi.
  5. Cách xuất bản plugin eslint.
  6. Cách thêm nó vào kho lưu trữ hiện có của bạn với eslint


Tài nguyên để học hỏi và khám phá thêm

  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


Chúc vui vẻ :)