مقدمة
كل يوم عند العمل على الكود ، في الطريق إلى تطبيق وظيفي مفيد للمستخدم ، تصبح التغييرات القسرية (المحتومة ، أو المرغوبة ببساطة) على الكود. يمكن أن يكون هذا إعادة بناء أو تحديث مكتبة أو إطار عمل إلى إصدار رئيسي جديد ، وتحديث بناء جملة JavaScript (وهو أمر غير شائع مؤخرًا). حتى إذا كانت المكتبة جزءًا من مشروع عمل ، فإن التغيير أمر لا مفر منه. معظم هذه التغييرات روتينية. لا يوجد شيء مثير للاهتمام للمطور فيها ، من ناحية ، من ناحية أخرى ، فهو لا يجلب أي شيء إلى العمل ، ومن ناحية ثالثة ، أثناء عملية التحديث ، عليك أن تكون حريصًا جدًا على عدم كسر الحطب وعدم كسر وظيفة. وهكذا ، توصلنا إلى استنتاج مفاده أنه من الأفضل تحويل مثل هذا الروتين إلى أكتاف البرامج بحيث يفعلون كل شيء بأنفسهم ، وسيحكم الشخص بدوره فيما إذا كان كل شيء قد تم بشكل صحيح. هذا هو ما سيتم مناقشته في مقال اليوم.
AST
من أجل معالجة رمز البرنامج ، من الضروري ترجمته إلى تمثيل خاص ، يكون من المناسب أن تعمل البرامج. يوجد مثل هذا التمثيل ، ويسمى Abstract Syntax Tree (AST).
من أجل الحصول عليها ، استخدم المحللون. يمكن تحويل AST الناتج كما تشاء ، ثم لحفظ النتيجة تحتاج إلى مولد رمز. دعونا نفكر بمزيد من التفصيل في كل خطوة من الخطوات. لنبدأ مع المحلل اللغوي.
محلل
وبالتالي لدينا الرمز:
a + b
ينقسم المحللون عادة إلى قسمين:
يقسم الرمز إلى رموز ، كل منها يصف جزءًا من الكود:
[{ "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "+", }, { "type": "Identifier", "value": "b" }]
ينشئ شجرة بناء جملة من الرموز المميزة:
{ "type": "BinaryExpression", "left": { "type": "Identifier", "name": "a" }, "operator": "+", "right": { "type": "Identifier", "name": "b" } }
والآن لدينا بالفعل هذه الفكرة ذاتها ، والتي يمكنك العمل بها برمجياً. تجدر الإشارة إلى أن هناك عددًا كبيرًا من موزعي JavaScript
، فيما يلي بعض منها:
- babel-parser - محلل يستخدم
babel
؛ - espree - محلل يستخدم
eslint
؛ - البلوط - المحلل اللغوي الذي يستند إليه الاثنان السابقان ؛
- esprima - محلل شائع يدعم JavaScript حتى EcmaScript 2017 ؛
- cherow هو لاعب جديد بين موزعي جافا سكريبت الذي يدعي أنه الأسرع ؛
يوجد محلل JavaScript قياسي ، يسمى ESTree ويحدد العقد التي يجب تحليلها.
للحصول على تحليل أكثر تفصيلاً لعملية تنفيذ المحلل اللغوي (وكذلك المحول والمولد) ، يمكنك قراءة المترجم الصغير جدًا .
لتحويل شجرة AST ، يمكنك استخدام نموذج الزائر ، على سبيل المثال ، باستخدام مكتبة @ babel / traverse . سينتج عن الكود التالي أسماء جميع معرفات كود JavaScript من المتغير code
.
import * as parser from "@babel/parser"; import traverse from "@babel/traverse"; const code = `function square(n) { return n * n; }`; const ast = parser.parse(code); traverse(ast, { Identifier(path) { console.log(path.node.name); } });
مولد
يمكنك إنشاء رمز ، على سبيل المثال ، باستخدام @ babel / generator ، بهذه الطريقة:
import {parse} from '@babel/parser'; import generate from '@babel/generator'; const code = 'class Example {}'; const ast = parse(code); const output = generate(ast, code);
وهكذا ، في هذه المرحلة ، كان على القارئ أن يكون لديه فكرة أساسية حول ما هو مطلوب لتحويل شفرة JavaScript ، وبأي الأدوات التي يتم تنفيذها.
يجدر أيضًا إضافة أداة عبر الإنترنت مثل astexplorer ، فهي تجمع بين عدد كبير من المحللون والمحولات والمولدات.
Putout
Putout عبارة عن محول رمز يدعم المكون الإضافي. في الواقع ، هو تقاطع بين إسلينت وبابل ، ويجمع بين مزايا كلتا الأداتين.
كيف يُظهر eslint
putout
مناطق المشاكل في الكود ، ولكن على عكس eslint
putout
يغير سلوك الكود ، أي أنه قادر على إصلاح جميع الأخطاء التي يمكنه العثور عليها.
مثل وضع babel
putout
يحول الكود ، لكنه يحاول تغييره إلى الحد الأدنى ، بحيث يمكن استخدامه للعمل مع الكود المخزن في المستودع.
أجمل ما يستحق الذكر هو أنه أداة تنسيق ويختلف اختلافًا جذريًا.
Jscodeshift ليس بعيدًا تمامًا عن putout
، لكنه لا يدعم المكونات الإضافية ، ولا يُظهر رسائل خطأ ، ويستخدم أيضًا أنواع ast بدلاً من @ babel / types .
قصة المظهر
في هذه العملية ، eslint
. لكن في بعض الأحيان أريد أكثر منه. على سبيل المثال ، لإزالة مصحح الأخطاء ، حدد test.only ، وحذف أيضًا المتغيرات غير المستخدمة. شكلت النقطة الأخيرة أساس putout
، أثناء عملية التطوير ، أصبح من الواضح أنه ليس بالأمر السهل وأن العديد من التحولات الأخرى أسهل في التنفيذ. وبالتالي ، نمت putout
بسلاسة من وظيفة واحدة إلى نظام المساعد. إن إزالة المتغيرات غير المستخدمة هي الآن العملية الأكثر صعوبة ، لكن هذا لا يمنعنا من تطوير ودعم العديد من التحولات المفيدة الأخرى.
كيف يعمل Putout في الداخل
يمكن تقسيم عمل putout
إلى قسمين: المحرك والإضافات. تسمح لك هذه البنية بعدم تشتيت التحولات عند العمل مع المحرك ، وعند العمل على المكونات الإضافية ، ستركز قدر الإمكان على الغرض منها.
الإضافات المدمجة
بنيت putout
على نظام المساعد. كل ملحق يمثل قاعدة واحدة. باستخدام القواعد المدمجة ، يمكنك القيام بما يلي:
- الجمع بين خصائص التدمير:
تم تصميم كل مكون إضافي وفقًا لفلسفة Unix ، أي أنها بسيطة بقدر الإمكان ، وكل منها يؤدي إجراءً واحدًا ، مما يجعلها سهلة الجمع ، لأنها في جوهرها مرشحات.
على سبيل المثال ، وجود الكود التالي:
const name = user.name; const password = user.password;
يتم تحويله أولاً باستخدام تطبيق destructuring إلى:
const {name} = user; const {password} = user;
ثم ، باستخدام خصائص دمج التدمير ، يتم تحويله إلى:
const { name, password } = user;
وبالتالي ، يمكن أن تعمل الإضافات بشكل منفصل ومعا. عند إنشاء المكونات الإضافية الخاصة بك ، يوصى بالالتزام بهذه القاعدة ، وتنفيذ مكون إضافي مع الحد الأدنى من الوظائف ، وفعل ما تحتاجه فقط ، وستتولى المكونات الإضافية المضمّنة والعناية بالبقية.
مثال للاستخدام
بعد أن نتعرف على القواعد المضمنة ، يمكننا النظر في مثال باستخدام putout
.
قم بإنشاء ملف example.js
بالمحتويات التالية:
const x = 1, y = 2; const name = user.name; const password = user.password; console.log(name, password);
الآن قم بتشغيل putout
بتمرير example.js
كوسيطة:
coderaiser@cloudcmd:~/example$ putout example.js /home/coderaiser/example/example.js 1:6 error "x" is defined but never used remove-unused-variables 1:13 error "y" is defined but never used remove-unused-variables 6:0 error Unexpected "console" call remove-console 1:0 error variables should be declared separately split-variable-declarations 3:6 error Object destructuring should be used apply-destructuring 4:6 error Object destructuring should be used apply-destructuring 6 errors in 1 files fixable with the `--fix` option
سوف نتلقى معلومات تحتوي على 6 أخطاء ، تم النظر فيها بمزيد من التفاصيل أعلاه ، والآن سنقوم بتصحيحها ، ونرى ما حدث:
coderaiser@cloudcmd:~/example$ putout example.js --fix coderaiser@cloudcmd:~/example$ cat example.js const { name, password } = user;
كنتيجة للتصحيح ، تم حذف المتغيرات غير المستخدمة ومكالمات console.log
، كما تم تطبيق التدمير.
الإعدادات
الإعدادات الافتراضية قد لا تكون دائمًا وليست للجميع ، لذا فإن putout
يدعم ملف التكوين .putout.json
، ويتكون من الأقسام التالية:
- القواعد
- تجاهل
- المباراة
- الإضافات
القواعد
يحتوي قسم rules
على نظام للقواعد. يتم تعيين القواعد افتراضيًا على النحو التالي:
{ "rules": { "remove-unused-variables": true, "remove-debugger": true, "remove-only": true, "remove-skip": true, "remove-process-exit": false, "remove-console": true, "split-variable-declarations": true, "remove-empty": true, "remove-empty-pattern": true, "convert-esm-to-commonjs": false, "apply-destructuring": true, "merge-destructuring-properties": true } }
لتمكين remove-process-exit
فقط بتعيينها على " true
في ملف .putout.json
:
{ "rules": { "remove-process-exit": true } }
سيكون هذا كافيًا للإبلاغ عن جميع process.exit
المكالمات الموجودة في التعليمات البرمجية وحذفها إذا تم --fix
الخيار --fix
.
تجاهل
إذا كنت بحاجة إلى إضافة بعض المجلدات إلى قائمة الاستثناءات ، فما عليك سوى إضافة قسم ignore
:
{ "ignore": [ "test/fixture" ] }
المباراة
إذا كنت بحاجة إلى نظام فرعي من القواعد ، على سبيل المثال ، قم بتمكين process.exit
لدليل bin
، ما عليك سوى استخدام قسم match
:
{ "match": { "bin": { "remove-process-exit": true, } } }
الإضافات
إذا كنت تستخدم المكونات الإضافية غير المدمجة ولديك putout-plugin-
، فيجب عليك تضمينها في قسم plugins
قبل تفعيلها في قسم rules
. على سبيل المثال ، لتوصيل putout-plugin-add-hello-world
وتمكين قاعدة add-hello-world
، حدد فقط:
{ "rules": { "add-hello-world": true }, "plugins": [ "add-hello-world" ] }
محرك Putout
محرك putout
هو أداة لسطر الأوامر تقوم بقراءة الإعدادات ، وتوزيع الملفات ، وتحميل المكونات الإضافية وتشغيلها ، ثم يكتب نتيجة المكونات الإضافية.
إنه يستخدم مكتبة إعادة الصياغة ، التي تساعد على تنفيذ مهمة مهمة للغاية: بعد التحليل والتحول ، قم بجمع الكود في حالة تشبه قدر الإمكان السابقة.
للتحليل اللغوي ، يتم استخدام محلل متوافق مع ESTree
( babel
حاليًا estree
، لكن التغييرات ممكنة في المستقبل) ، وتستخدم أدوات babel
للتحويل. لماذا بالضبط babel
؟ كل شيء بسيط. والحقيقة هي أن هذا المنتج ذو شعبية كبيرة ، وأكثر شعبية من الأدوات الأخرى المماثلة ، وأنه يتطور بشكل أسرع بكثير. كل اقتراح جديد في معيار EcmaScript لا يكتمل بدون مكون بابل . يحتوي Babel أيضًا على كتاب بعنوان Babel Handbook ، والذي يصف جيدًا جميع الميزات والأدوات اللازمة لاجتياز شجرة AST وتحويلها.
البرنامج المساعد مخصص ل Putout
نظام المكوّن الإضافي البسيط بسيط جدًا ، ويشبه إلى حد كبير الإضافات الإسبليتية ، وكذلك الإضافات بابل . صحيح ، بدلاً من وظيفة واحدة ، يجب تصدير المكون الإضافي 3. يتم ذلك من أجل زيادة إعادة استخدام التعليمات البرمجية ، لأن تكرار الوظيفة في 3 وظائف ليس مناسبًا للغاية ، فمن الأسهل بكثير وضعه في وظائف منفصلة ودعوته في الأماكن الصحيحة.
هيكل البرنامج المساعد
يتكون البرنامج المساعد Putout
من 3 وظائف:
report
- إرجاع رسالة ؛find
- يبحث عن الأماكن التي بها أخطاء وإرجاعها ؛fix
- إصلاح هذه الأماكن ؛
النقطة الرئيسية التي يجب تذكرها عند إنشاء مكون إضافي لـ putout
هي اسمه ، يجب أن يبدأ بـ putout-plugin-
. قد يكون التالي هو اسم العملية التي ينفذها المكوّن الإضافي ، على سبيل المثال ، يجب استدعاء المكوّن الإضافي remove-wrong
مثل هذا: putout-plugin-remove-wrong
.
يجب عليك أيضًا إضافة الكلمات: putout
و putout-plugin
إلى قسم package.json
في قسم keywords
، وتحديد "putout": ">=3.10"
في peerDependencies
"putout": ">=3.10"
، أو الإصدار الذي سيكون الأخير في وقت كتابة المكوّن الإضافي.
المساعد عينة ل Putout
دعنا نكتب مثالا إضافيا من شأنه أن يزيل كلمة debugger
من الكود. مثل هذا البرنامج المساعد موجود بالفعل ، إنه @ putout / plugin-remove-debugger وهو بسيط بما فيه الكفاية للنظر فيه الآن.
يبدو مثل هذا:
إذا تم تضمين قاعدة remove-debugger
في .putout.json
، @putout/plugin-remove-debugger
. أولاً ، تسمى الدالة find
والتي ، باستخدام الدالة traverse
، ستتجاوز عقد شجرة AST وتحفظ جميع الأماكن اللازمة.
سيتم putout
الخطوة التالية في putout
report
للحصول على الرسالة المطلوبة.
إذا تم استخدام علامة fix
فسيتم استدعاء وظيفة fix
المكون الإضافي وسيتم تنفيذ التحويل ، وفي هذه الحالة ، سيتم حذف العقدة.
مثال اختبار البرنامج المساعد
من أجل تبسيط اختبار المكونات الإضافية ، تمت كتابة أداة @ putout / test . في جوهرها ، ليس أكثر من مجرد غلاف على الشريط ، مع العديد من الطرق لراحة وتبسيط الاختبار.
قد يبدو اختبار المكون الإضافي remove-debugger
كما يلي:
const removeDebugger = require('..'); const test = require('@putout/test')(__dirname, { 'remove-debugger': removeDebugger, });
الترميز
لا يلزم استخدام كل تحويل كل يوم ، لأن التحويلات لمرة واحدة كافية للقيام بنفس الشيء ، ولكن بدلاً من النشر إلى npm
ضعه في ~/.putout
. عند بدء التشغيل ، putout
في هذا المجلد ، ويستلم و يبدأ التحول.
فيما يلي مثال للتحول الذي يحل محل الاتصال بالشريط والاتصال بالشريط باستدعاء supertape : convert-tape-to-supertape .
eslint-plugout-putout
في النهاية ، يجدر إضافة نقطة واحدة: يحاول putout
تغيير الرمز إلى الحد الأدنى ، ولكن إذا حدث لصديق أن بعض قواعد التنسيق تخرق ، eslint --fix جاهز دائمًا eslint --fix
، ولهذا الغرض ، هناك مكون إضافي eslint-plugout-putout . يمكن أن يضيء العديد من أخطاء التنسيق ، وبالطبع يمكن تخصيصها وفقًا لتفضيلات المطورين في مشروع معين. توصيله سهل:
{ "extends": [ "plugin:putout/recommended", ], "plugins": [ "putout" ] }
حتى الآن ، هناك قاعدة واحدة فقط: one-line-destructuring
، تقوم بما يلي:
هناك العديد من قواعد eslint
المضمنة التي يمكنك التعرف عليها بمزيد من التفاصيل .
الخاتمة
أود أن أشكر القارئ على الاهتمام الذي أولاه لهذا النص. آمل مخلصًا أن يصبح موضوع تحويلات AST أكثر شيوعًا ، وستظهر مقالات حول هذه العملية الرائعة في كثير من الأحيان. سأكون ممتنًا جدًا لأي تعليقات أو اقتراحات متعلقة بمزيد من putout
. قم بإنشاء مشكلة ، وإرسال مجموعة من الطلبات ، واختبار ، وكتابة القواعد التي ترغب في رؤيتها ، وكيفية برمجة التعليمات البرمجية الخاصة بك برمجيًا ، وسنعمل معًا لتحسين أداة تحويل AST.