العمل باستخدام أشجار بناء جملة JavaScript مجردة

لماذا تحليل رمزك؟ على سبيل المثال ، للعثور على وحدة التحكم المنسية المسجلة قبل الالتزام. ولكن ماذا لو كنت بحاجة إلى تغيير توقيع الوظيفة في مئات الإدخالات في الرمز؟ هل ستتعامل التعبيرات العادية هنا؟ ستوضح لك هذه المقالة الإمكانيات التي توفرها أشجار بناء مجردة لمطور.



تحت القطع - مقطع فيديو ونص نص لتقرير بقلم كيريل تشيركاشين ( z6Dabrata ) من مؤتمر HolyJS 2018 Piter .


عن المؤلف
ولد سيريل في موسكو ، ويعيش الآن في نيويورك ويعمل في Firebase. يعلم Angular ليس فقط في Google ، ولكن في جميع أنحاء العالم. الجهة المنظمة لأكبر أداة تخطيط زاوية في العالم هي AngularNYC (وكذلك VueNYC و ReactNYC). في وقت فراغه من البرمجة ، فهو مغرم بالتانغو والكتب والمحادثات الممتعة.

Hacksaw أو الخشب؟


لنبدأ بمثال: لنفترض أنك قمت بتصحيح برنامج وأرسلت التغييرات التي تم إجراؤها إلى git ، وبعد ذلك ذهبت إلى الفراش بهدوء. في الصباح اتضح أن زملائك قاموا بتنزيل التغييرات الخاصة بك ، وبما أنك نسيت إزالة ناتج معلومات تصحيح الأخطاء إلى وحدة التحكم في اليوم السابق ، فإنه يعرضها ويسد الإخراج. واجه الكثير هذه المشكلة.

هناك أدوات ، مثل EsLint ، لإصلاح الوضع ، ولكن لأغراض تعليمية ، دعنا نحاول إيجاد حل بمفردنا.
ما الأداة التي يجب استخدامها لإزالة كافة console.log() من التعليمات البرمجية؟
نختار بين التعبيرات العادية واستخدام أشجار Sitax المجردة (ASD). دعونا نحاول حل هذا بالتعبيرات العادية بكتابة بعض وظيفة findConsoleLog . عند الإدخال ، سيتلقى رمز البرنامج كوسيطة ويظهر صحيحًا إذا تم العثور على console.log () في مكان ما في نص البرنامج.

 function findConsoleLog(code) { return !!code.match(/console.log/); } 

لقد كتبت 17 اختبارًا ، أحاول التوصل إلى طرق مختلفة لكسر وظيفتنا. هذه القائمة بعيدة عن الاكتمال.



مرت أبسط اختبار.
وماذا إن وجدت أي وظيفة تحتوي على السلسلة "console.log" في اسمها؟

 function findConsoleLog(code) { return !!code.match(/\bconsole.log/); } 

تمت إضافة حرف يشير إلى أنه يجب أن تحدث وحدة console.log في بداية الكلمة.



تم اجتياز اختبارين فقط ، ولكن ماذا لو كان هناك ملف console.log في التعليق ولا يلزم حذفه؟

نعيد كتابتها حتى لا يلمس المحلل التعليقات.

 function findConsoleLog(code) { return !!code   .replace(/\/\/.*/)   .match(/\bconsole.log/); } 



نستبعد إزالة "console.log" من السطور:

 function findConsoleLog(code) { return !!code   .replace(/\/\/.*|'.*'/, '')   .match(/\bconsole.log/); } 



لا تنس أنه لا يزال لدينا مساحات وشخصيات أخرى قد تمنع بعض الاختبارات من النجاح:



على الرغم من حقيقة أن الفكرة لم تكن بسيطة للغاية ، يمكن اجتياز جميع الاختبارات الـ 17 التي تستخدم التعبيرات العادية. هنا ، في هذه الحالة ، سيبدو رمز الحل:

 function findConsoleLog(code) { return code   .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//)   .match(/\bconsole\s*.log\(/); } 


تكمن المشكلة في أن هذا الرمز لا يغطي جميع الحالات المحتملة ، ومن الصعب الحفاظ عليه.

فكر في كيفية حل هذه المشكلة باستخدام ASD.

كيف تزرع الأشجار؟


يتم الحصول على شجرة بناء الجملة المجردة نتيجة لعمل المحلل اللغوي مع رمز التطبيق الخاص بك. تم استخدام المحلل اللغوي @ babel / محلل للمظاهرة .
كمثال ، خذ سلسلة console.log('holy') ، مررها من خلال المحلل اللغوي.

 import { parse } from 'babylon'; parse("console.log('holy')"); 

نتيجة لعمله ، تم الحصول على ملف JSON من حوالي 300 سطر. نستبعد من خطوط أرقامهم بمعلومات الخدمة. نحن مهتمون بقسم الجسم. المعلومات الفوقية لا تهمنا أيضًا. والنتيجة حوالي 100 سطر. مقارنة بالهيكل الذي يولده المتصفح لمتغير جسم واحد (حوالي 300 سطر) ، هذا ليس كثيرًا.

دعنا نلقي نظرة على بعض الأمثلة عن كيفية تمثيل الحرف المختلفة في التعليمات البرمجية في شجرة بناء الجملة:



هذا هو التعبير الذي يوجد فيه Literal Literal ، وهو حرفي رقمي.



تعبير console.log المألوف بالفعل. يحتوي على كائن له خاصية.



إذا كان log عبارة عن استدعاء دالة ، فسيكون الوصف كما يلي: هناك تعبير استدعاء ، يحتوي على وسيطات - حرفية رقمية. في الوقت نفسه ، يحتوي تعبير الاستدعاء على اسم - سجل.

يمكن أن تكون الكلمات الحرفية مختلفة: أرقام ، سلاسل ، تعبيرات عادية ، منطقية ، خالية.
العودة إلى المكالمة console.log



هذا تعبير نداء يحتوي على تعبير العضو بداخله. من الواضح أن كائن وحدة التحكم في الداخل لديه خاصية تسمى السجل.

تجاوز ASD


الآن دعنا نحاول العمل مع هذه البنية في التعليمات البرمجية. سيتم استخدام مكتبة بابل ترافرس لاجتياز الشجرة .

يتم إعطاء نفس الاختبارات 17. يتم الحصول على هذه التعليمات البرمجية عن طريق تحليل شجرة بناء البرنامج والبحث عن إدخالات "console.log":

 function traverseConsoleLog(code, {babylon, babelTraverse, types, log}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, {   MemberExpression(path){     if (       path.node.property.type === 'Identifier' &&       path.node.property.name === 'log' &&       path.node.object.type === 'Identifier' &&       path.node.object.name === 'console' &&       path.parent.type === 'CallExpression' &&       path.Parentkey === 'callee'     ) {       hasConsoleLog = true;     }   } }) return hasConsoleLog; } 

دعونا نحلل ما هو مكتوب هنا. const ast = babylon.parse(code); في المتغير ast نحلل شجرة بناء الجملة من الكود. بعد ذلك نعطي مكتبة babel-parse هذه الشجرة للمعالجة. نحن نبحث عن العقد والخصائص ذات الأسماء المتطابقة داخل تعبيرات المكالمات. قم بتعيين متغير hasConsoleLog إلى true إذا تم العثور على المجموعة المطلوبة من العقد وأسمائها.

يمكننا التحرك حول الشجرة ، وأخذ آباء العقد ، والأحفاد ، والبحث عن الحجج والخصائص التي لديهم ، وإلقاء نظرة على أسماء هذه الخصائص ، والأنواع - وهذا أمر مريح للغاية.

هناك فارق بسيط غير سار يمكن إصلاحه بسهولة باستخدام مكتبة أنواع بابل. لتجنب الأخطاء عند البحث في الشجرة بسبب الاسم غير الصحيح ، على سبيل المثال ، بدلاً من path.parent.type === 'CallExpression' كتبت بطريق الخطأ path.parent.type === 'callExpression' ، مع أنواع بابل يمكنك الكتابة مثل هذا :

 // Before path.node.property.type === 'Identifier' path.node.property.name === 'log' // with babel types import {isIdentifier} from 'babel-types'; isIdentifier(path.node.property, {name: log}) //         ,  ,    isIdentifier,      

نعيد كتابة الرمز السابق باستخدام أنواع بابل:
 function traverseConsoleLogSolved2(code, {babylon, babelTraverse, types}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, {   MemberExpression(path) {     if (       types.isIdentifier(path.node.object, { name: 'console'}) &&       types.isIdentifier(path.node.property, { name: 'log'}) &&       types.isCallExpression(path.parent) &&       path.parentKey === 'callee'     ) {       hasConsoleLog = true;     }   } }); return hasConsoleLog; } 

تحويل ASD باستخدام بابل اجتياز


لتقليل تكاليف العمالة ، نحتاج console.log إزالة console.log الفور من الكود - بدلاً من الإشارة إلى أنه موجود في الكود.

نظرًا لأننا نحتاج إلى إزالة ليس MemberExpression نفسه ، ولكن الوالد ، في المكان hasConsoleLog = true; نكتب path.parentPath.remove(); .

من دالة removeConsoleLog ، ما زلنا نرجع قيمة منطقية. نستبدل ناتجه بالكود الذي سيولد مولد بابل ، مثل هذا:
hasConsoleLog => babelGenerator(ast).code

يتلقى Babel-generator شجرة البنية المجردة المعدلة كمعلمة ، ويعيد كائنًا بخاصية الرمز ، وداخل هذا الكائن يتم إعادة إنشاء التعليمات البرمجية بدون console.log . بالمناسبة ، إذا أردنا الحصول على مخطط الشفرة ، يمكننا استدعاء خاصية sourceMaps لهذا الكائن.

وإذا كنت بحاجة إلى العثور على مصحح أخطاء؟


هذه المرة سوف نستخدم ASTexplorer لإكمال المهمة. المصحح هو نوع من عقدة عبارة المصحح. لسنا بحاجة إلى النظر إلى الهيكل بأكمله ، نظرًا لأن هذا نوع خاص من العقدة ، فقط ابحث عن عبارة المصحح. سنكتب ملحقًا لـ ESLint (على ASTexplorer).

تم تصميم ASTexplorer بطريقة تكتب الرمز على اليسار ، وعلى اليمين تحصل على ASD النهائي. يمكنك اختيار التنسيق الذي تريد استلامه به: JSON أو بتنسيق الشجرة.



نظرًا لأننا نستخدم ESLint ، فسوف نقوم بكل البحث عن الملفات لنا وتعطينا الملف اللازم حتى نتمكن من العثور على سطر مصحح الأخطاء فيه. تستخدم هذه الأداة محلل ASD مختلف. ومع ذلك ، هناك عدة أنواع من ASDs في JavaScript. شيء يذكرنا بالماضي ، عندما طبقت متصفحات مختلفة المواصفات بطرق مختلفة. وبالتالي ، نقوم بتنفيذ بحث المصحح:

 export default function(context) { return {   DebuggerStatement(node) { // ,     console.log    path,    -  ,     path         context.report(node, 'LOL Debugger!!!'); //   ESLint ,   debugger, node     ,    ,    debugger   } } } 

التحقق من عمل البرنامج المساعد المكتوب:



وبالمثل ، يمكنك إزالة المصحح من التعليمات البرمجية.

ما هي مفيدة أخرى ASD


أنا شخصياً أستخدم ASD لتبسيط العمل مع الأطر الأمامية وغيرها من الواجهات الأمامية. يمكنك استيراد ، توسيع ، إضافة واجهة ، طريقة ، ديكور وأي شيء آخر بنقرة زر واحدة. على الرغم من أننا نتحدث عن جافا سكريبت في هذه الحالة ، إلا أن TypeScript لديها أيضًا ASDs الخاصة بها ، والفرق الوحيد هو الفرق بين أسماء أنواع العقد والهيكل. في نفس ASTExplorer يمكن تحديدها كلغة TypeScript.

لذا:

  • لدينا المزيد من السيطرة على الكود ، إعادة الهيكلة أسهل ، كودودس. على سبيل المثال ، قبل الالتزام ، يمكنك الضغط على مفتاح واحد لتنسيق الرمز بالكامل وفقًا للإرشادات. تتضمن أكواد الرموز مطابقة تلقائية للكود وفقًا للإصدار المطلوب من إطار العمل.
  • خلافات أقل حول تصميم الكود.
  • يمكنك إنشاء مشاريع ألعاب. على سبيل المثال ، قدم تلقائيًا للمبرمج ملاحظات حول الرمز الذي يكتبه.
  • فهم أفضل لجافا سكريبت.

بعض الروابط المفيدة لبابل


  1. تستخدم جميع تحويلات Babel واجهة برمجة التطبيقات هذه: الإضافات والإعدادات المسبقة .
  2. جزء من عملية إضافة وظائف جديدة إلى ECMAScript هو إنشاء مكون إضافي لـ Babel. يعد ذلك ضروريًا حتى يتمكن الأشخاص من اختبار الوظائف الجديدة. إذا اتبعت الرابط ، يمكنك أن ترى أنه داخل نفس الطريقة التي يتم بها استخدام قدرات ASD. على سبيل المثال ، عامل التعيين المنطقي .
  3. يفقد Babel Generator التنسيق عند إنشاء التعليمات البرمجية. هذا جيد جزئيًا ، لأنه إذا تم استخدام هذه الأداة في فريق التطوير ، فبعد إنشاء الشفرة من ASD ، ستبدو متشابهة للجميع. ولكن إذا كنت ترغب في الاحتفاظ بتنسيقك ، فيمكنك استخدام إحدى هذه الأدوات: Recast أو Babel CodeMod .
  4. من هذا الرابط يمكنك العثور على ثروة من المعلومات حول Babel Awesome Babel .
  5. بابل هو مشروع مفتوح المصدر ويعمل عليه فريق من المتطوعين. يمكنك المساعدة. هناك ثلاث طرق للقيام بذلك: المساعدة المالية ، يمكنك دعم موقع patreon على الويب ، والذي يعمل به Henry Zhu ، أحد المساهمين الرئيسيين في Babel ، في المساعدة في التعليمات البرمجية على opencollective.com/babel .

مكافأة


وإلا كيف يمكننا العثور على مدونة لدينا في التعليمات البرمجية؟ استخدم IDE الخاص بك! باستخدام أداة البحث والاستبدال ، بعد تحديد مكان البحث عن التعليمات البرمجية.
يحتوي Intellij IDEA أيضًا على أداة "البحث الهيكلي" التي يمكن أن تساعدك في العثور على الأماكن الصحيحة في التعليمات البرمجية الخاصة بك ، بالمناسبة ، فهي تستخدم ASD.

في الفترة من 24 إلى 25 تشرين الثاني (نوفمبر) ، سيقدم كيريل عرضًا تقديميًا حول البيانات الثنائية JavaScript * LOVES * في Moscow HolyJS : سننزل إلى مستوى البيانات الثنائية ، ونحفر في الملفات الثنائية باستخدام ملفات * .gif كمثال ، ونتعامل مع أطر عمل تسلسلية مثل Protobuf أو Thrift. بعد التقرير ، سيكون من الممكن التحدث مع سيريل ومناقشة جميع القضايا ذات الاهتمام في مجال المناقشة.

Source: https://habr.com/ru/post/ar428628/


All Articles