چگونه اولین قانون ESLint سفارشی خود را ایجاد کردم

وقتی با React یا به طور کلی جاوا اسکریپت کار می کنم، همیشه از ESLint برای لینتینگ استفاده می کنم. اگرچه من با نحوه استفاده و پیکربندی این ابزار بسیار آشنا هستم، اما تا همین اواخر هرگز یک قانون سفارشی ESLint را از ابتدا ننوشتم. در ابتدا کار سختی به نظر می رسید، اما در نهایت به من آموخت. این مقاله در مورد این است: چگونه این قانون خاص را ساختم و چگونه در مورد “درخت نحو انتزاعی” یاد گرفتم. بیا با هم شیرجه بزنیم
چگونه قانون خود را اعمال کنیم
قانون ESLint از 2 بخش اصلی تشکیل شده است:
-
meta: شیئی که در آن استفاده از قانون خود را مشخص خواهیم کرد. -
create: تابعی که یک شی را با تمام متدهایی که ESLint برای تجزیه دستور ما استفاده می کند، برمی گرداند. هر روش برگردانده شده یک گره AST است.
AST (درخت نحو انتزاعی) چیست؟
ممکن است در گذشته AST را دیده یا شنیده باشید، اما در اینجا یک تعریف برای هر موردی وجود دارد:
AST یک نمایش درختی ساده و خلاصه شده از ساختار کد منبع نوشته شده در یک زبان برنامه نویسی معین است. “انتزاعی” است زیرا هر جزئیاتی را که در نحو واقعی ظاهر می شود، نشان نمی دهد، فقط محتوا یا جزئیات ساختاری را نشان می دهد.
برای ساختن قانون ESLint، باید نمایش عبارت را بدست آوریم CHECK && yup.validateSync(); در AST و اجازه دهید create هر زمان که درخت عبارت داده شده با درخت معتبر مطابقت نداشته باشد، تابع یک خطا برمی گرداند. برای یافتن نمایش AST بیان ما، می توانید از AST Explorer استفاده کنید که برای من بسیار مفید بود.
با این حال، قبل از انجام همه این کارها، بیایید با پرداختن به آن شروع کنیم meta بخشی از قانون ما
متا
بیایید با اضافه کردن ساختار اصلی قانون و متا شروع کنیم check-before-type-validation.js
ساختار اصلی قانون ESLint ما
2
'type-check-before-yup': {5
description: '"yup.validateSync()" needs to be preceded by “CHECK &&”',10
'"yup.validateSync()" is found but is not preceded "CHECK &&"',13
create: function (context) {
در بالا می بینیم که ما 2 قسمت مهم را اضافه کرده ایم: پیام ها و اسناد. رشته زیر messages.unexpected پیامی است که در صورت شکست قانون نمایش داده می شود. این طبقه docs.description توضیح مختصری از قانون ارائه می دهد که می تواند توسط برخی ویرایشگرهای متن مانند VSCode نمایش داده شود.
ایجاد کنید
برای این قسمت ابتدا به AST Explorer برویم و عبارت خود را تایپ کنیم تا ببینیم چگونه به AST ترجمه می شود. با ورود به CHECK && yup.validateSync() باید خروجی زیر را دریافت کنیم:
نمایش AST از بیان ما
7
"type": "ExpressionStatement",11
"type": "LogicalExpression",22
"type": "CallExpression",26
"type": "MemberExpression",39
"name": "validateSync"48
"sourceType": "module"
برای نوشتن قانون خود، می توانیم با علامت گذاری شروع کنیم yup.validateSync(). از درخت AST می بینیم که این عبارت a است CallExpression:
ابتدا به ESLint نیاز داریم تا آن گره نام شی خاص را پیدا کنیم yup و نام ملک validateSync در یک CallExpression. اگر پیدا شد، میتوانیم یکی از والدین آن گره را بررسی کنیم تا ببینیم آیا CHECK && حضور دارد. بنابراین می توانیم با نوشتن کد زیر شروع کنیم:
نوشتن قانون (مرحله 1)
1
create: function(context) {4
CallExpression: function(node) {5
const callee = node.callee;9
callee.object.name === 'yup' &&11
callee.property.name === 'validateSync'
قسمت بعدی درخت AST که ما به دنبال آن هستیم یک است LogicalExpression. از اسکرین شات بالا می بینیم که 2 سطح بالاتر در درخت وجود دارد. ما می توانیم از این نتیجه بگیریم که اگر این والد نمی بود LogicalExpression، قانون ESLint ما باید یک خطا را گزارش کند. سپس می توانیم با افزودن موارد زیر به نوشتن قطعه کد خود در بالا ادامه دهیم:
نوشتن قانون (مرحله 2)
3
callee.object.name === 'yup' &&5
callee.property.name === 'validateSync'9
const calleeLogicalExpression = callee.parent.parent;11
if (calleeLogicalExpression.type !== 'LogicalExpression') {15
context.report({ node, messageId: 'unexpected' });
همانطور که در بالا می بینید، برای دریافت ESLint برای گزارش خطا، باید تماس بگیریم context.report تابع به جای نوشتن کل پیام همانطور که در مستندات ESLint توصیه شده است، messageId را که در متای قانون خود مشخص کرده ایم، ارسال می کنیم.
بعد باید بررسی کنیم که آیا a LogicalExpression عملگر این عبارت در واقع AND است، نه OR:
نوشتن قانون (مرحله 3)
3
callee.object.name === 'yup' &&5
callee.property.name === 'validateSync'9
const calleeLogicalExpression = callee.parent.parent;11
if (calleeLogicalExpression.type !== 'LogicalExpression') {15
context.report({ node, messageId: 'unexpected' });19
if (calleeLogicalExpression.operator !== '&&') {20
context.report({ node, messageId: 'unexpected' });
با این کد، قانون ESLint ما یک خطا را برای موارد زیر گزارش می کند:
2
CHECK || yup.validateSync();
اما اگر چیزی شبیه به زیر داشته باشیم:
1
TEST && yup.validateSync();
قانون ما خطایی نمی گیرد. پس بیایید به درخت AST خود برگردیم تا ببینیم در اینجا چه کاری می توانیم انجام دهیم.
ما می توانیم ببینیم که الف LogicalExpression دارای 3 بخش اصلی:
بنابراین برای آخرین بخش از قانون ما می خواهیم بررسی کنیم که آیا نام قسمت چپ ما وجود دارد یا خیر LogicalExpression است CHECK:
نوشتن قانون (مرحله 4)
3
callee.object.name === 'yup' &&5
callee.property.name === 'validateSync'9
const calleeLogicalExpression = callee.parent.parent;11
if (calleeLogicalExpression.type !== 'LogicalExpression') {15
context.report({ node, messageId: 'unexpected' });16
} else if (calleeLogicalExpression.left.name !== 'TYPE_CHECK') {17
context.report({ node, messageId: 'unexpected' });21
if (calleeLogicalExpression.operator !== '&&') {22
context.report({ node, messageId: 'unexpected' });
اکنون که همه مواردی را که میخواهیم قانون ما رسیدگی کند، نوشتهایم، وقت آن است که آن را آزمایش کنیم. ما خوش شانس هستیم زیرا ESLint با ابزار تست قوانین خود به نام ارائه می شود RuleTester. با استفاده از این ابزار، میتوانیم تمام مواردی را که میخواهیم قانون را اجرا کنیم و اینکه آیا این موارد انتظار میرود تصویب شوند یا بهعنوان خطا گزارش شوند را مشخص کنیم. آزمون ما ادامه خواهد داشت tests/lib و قانونی را که در قسمت قبل نوشتیم وارد می کند:
2
const rules = require('../../lib/check-before-type-validation');3
const RuleTester = require('eslint').RuleTester;5
const ruleTester = new RuleTester();8
const errors = [{ messageId: 'unexpected' }];10
const typeCheckRule = rules['type-check-before-yup'];13
ruleTester.run('type-check', typeCheckRule, {16
code: 'CHECK && yup.validateSync()',20
code: 'yup.someOtherCommand()',26
code: 'yup.validateSync()',30
code: 'OTHER && yup.validateSync()',34
code: 'CHECK || yup.validateSync()',
در قطعه کد قبلی، می بینیم که قانون خود را در 5 حالت مختلف آزمایش می کنیم:
-
اگر عبارات را داشته باشیم هیچ خطایی گزارش نمی شود
CHECK && yup.validateیاyup.someOtherCommand() -
اگر عبارات زیر را داشته باشیم یک خطا گزارش می شود:
yup.validateSync()(مفقود شدهLogicalExpression) یاOTHER && yup.validateSync(قسمت چپ اشتباهLogicalExpression) یاCHECK || yup.validateSync()(اپراتور اشتباه).
سپس می توانیم این تست را با جست یا ابزار تست دیگری اجرا کنیم و باید نتیجه ای مشابه این بگیریم:
5
✓ OTHER && CHECK && yup.validateSync() (45ms)7
✓ CHECK && yup.validateSync() (3ms)9
✓ yup.someOtherCommand() (1ms)13
✓ yup.validateSync() (3ms)15
✓ OTHER && yup.validateSync() (1ms)17
✓ CHECK || yup.validateSync() (2ms)
اکنون که تأیید کردیم این قانون همانطور که انتظار می رود کار می کند، می توانیم آن را به عنوان یک بسته NPM منتشر کنیم و آن را به عنوان یک افزونه به هر پیکربندی ESLint که می خواهیم اضافه کنیم.
کل این فرآیند در ابتدا بسیار زیاد به نظر می رسد، به خصوص که شامل کار با AST است که آسان ترین چیز برای یادگیری نیست. اما اکنون که آناتومی یک قانون ESLint را میدانیم، میتوانیم حتی بیشتر از حجم کارهای دیوانهکنندهای که جامعه برای ارائه همه این قوانین پردهای که ما هر روز برای تمیزتر و سازگارتر کردن پایگاه کدمان استفاده میکنیم، قدردانی کنیم.




