Thứ bảy, 09/02/2019 | 00:00 GMT+7

Đọc mã nguồn JavaScript, sử dụng AST


Giả sử bạn có một file JavaScript lớn, vẫn còn từ những ngày cũ. Nó dài 70K dòng và bạn thực sự cần phải chia nó ra bằng cách sử dụng webpack hoặc consorts. Sau đó, bạn cần biết hàm hoặc hằng số nào mà nó hiển thị cho phạm vi global .

Để máy tính đọc qua mã của bạn và extract những gì bạn muốn từ nó.

Đó là một công việc dành cho Cây cú pháp trừu tượng (AST).

Anh hùng AST

Ví dụ nhỏ sau đây. Nhiệm vụ của ta , nếu bạn chọn chấp nhận nó, sẽ là extract tên của tất cả các chức năng được hiển thị trong phạm vi global .

// test the code function decrementAndAdd(a, b){    function add(c, d){       return c + d;    }    a--;    b = b - 1;    return add(a,b) }  // test the code function incrementAndMultiply(a, b){     function multiply(c, d){       return c * d;     }     a++;     b = b + 1;     return multiply(a, b) } 

Kết quả phải là ["decrementAndAdd", "incrementAndMultiply"] .

Phân tích mã

AST là kết quả phân tích cú pháp mã. Đối với JavaScript, AST là một đối tượng JavaScript chứa đại diện dạng cây cho nguồn của bạn. Trước khi sử dụng, ta phải tạo ra nó. Tùy thuộc vào mã ta đang phân tích cú pháp mà ta chọn bộ phân tích cú pháp thích hợp.

Ở đây vì mã tương thích với ES5, ta có thể chọn trình phân tích cú pháp acorn .

Dưới đây là một số trình phân tích cú pháp ECMAScript nguồn mở nổi tiếng nhất:

Phân tích cú pháp Các ngôn ngữ được hỗ trợ Github
acorn esnext & JSX (sử dụng acorn-jsx) https://github.com/acornjs/acorn
esprima esnext & cũ hơn https://github.com/jquery/esprima
chim bạc má esnext & cũ hơn https://github.com/cherow/cherow
tán thành esnext & cũ hơn https://github.com/eslint/espree
sự thay đổi esnext & cũ hơn https://github.com/shapesecurity/shift-parser-js
babel esnext, JSX & typecript https://github.com/babel/babel
TypeScript esnext & typecript https://github.com/Microsoft/TypeScript

Tất cả các trình phân tích cú pháp hoạt động như nhau. Cung cấp cho nó một số mã, lấy AST .

const { Parser } = require("acorn")  const ast = Parser.parse(readFileSync(fileName).toString()) 

Cú pháp phân tích cú pháp TypeScript hơi khác một chút. Nhưng nó được ghi lại ở đây .

Đây là cây thu được bằng cách phân tích mã màu đen với @babel/parser .

Biểu đồ cây từ @ babel / parser

Đi ngang qua

Để tìm ra thứ mà ta sẽ chiết xuất, tốt hơn là không nên xử lý toàn bộ AST cùng một lúc. Nó sẽ là một đối tượng lớn với hàng nghìn nút ngay cả đối với các đoạn mã nhỏ. Vì vậy, trước khi extract thông tin ta cần, ta sẽ tinh chỉnh tìm kiếm của bạn .

Cách tốt nhất để làm điều đó là chỉ lọc các mã thông báo mà người ta quan tâm.

, nhiều công cụ có sẵn để thực hiện phần đi ngang này. Đối với ví dụ của ta , ta sẽ sử dụng recast . Nó rất nhanh và có lợi thế là giữ nguyên một version mã của bạn. Bằng cách này, nó có thể trả lại phần mã của bạn mà bạn muốn với định dạng ban đầu của nó.

Trong khi duyệt qua, ta sẽ tìm thấy tất cả các mã thông báo function . Đây là lý do tại sao ta sử dụng phương thức visitFunctionDeclaration .

Nếu ta muốn xem xét các phép gán biến, ta sẽ sử dụng visitAssignmentExpression .

const recast = require('recast'); const { Parser } = require("acorn");  const ast = Parser.parse(readFileSync(fileName).toString());  recast.visit(ast, visitFunctionDeclaration(path){   // the navigation code here...    // return false to stop at this depth   return false; }) 

Các loại nút AST

Thông thường tên của các loại mã thông báo không rõ ràng. Người ta có thể sử dụng ast-explorer để tra cứu các loại được nghiên cứu. Chỉ cần dán mã của bạn vào console bên trái, chọn trình phân tích cú pháp bạn đang sử dụng và “voilà!”. Duyệt qua mã đã phân tích cú pháp ở bên phải và tìm Loại nút bạn đang tìm.

Nông hoặc sâu

Không phải lúc nào ta cũng muốn xem xét mọi cấp độ của cây. Đôi khi ta muốn thực hiện tìm kiếm sâu trong khi những lần khác ta chỉ muốn nhìn vào lớp trên cùng. Tùy thuộc vào khuôn khổ, cú pháp khác nhau. May mắn là nó thường được ghi lại đầy đủ.

Với recast , nếu ta muốn dừng tìm kiếm ở độ sâu hiện tại, chỉ cần return false khi bạn hoàn tất. Đây là những gì ta đã làm trước đây. Nếu ta muốn đi ngang qua (đi sâu), ta có thể sử dụng this.traverse(path) như bạn sẽ thấy bên dưới.

Với @babel/traverse không cần phải nói với babel nơi để tiếp tục. Người ta chỉ cần chỉ định nơi dừng bằng một câu lệnh return false .

recast.visit(ast, visitFunctionDeclaration(path){   // deal with the path here...    // run the visit on every child node   this.traverse(path); }) 

Ta đã đi từ một tìm kiếm rất rộng sang một mẫu nhỏ hơn. Bây giờ ta có thể extract dữ liệu ta cần.

Đối tượng path được truyền đến visitFunctionDeclaration là một NodePath . Đối tượng này đại diện cho kết nối giữa các node AST cha và con. Bản thân path này không có ích gì đối với ta vì nó thể hiện mối liên kết giữa phần khai báo hàm và phần thân của hàm.

Sử dụng ast-explorer , ta tìm thấy nội dung của con đường mà ta đang tìm kiếm.

Điều cổ điển cần làm: path.node . Nó lấy Node con trong mối quan hệ cha-con. Nếu bạn chọn tìm kiếm các hàm, nút trong path.node sẽ thuộc loại Function :

const functionNames = []; recast.visit(ast, visitFunctionDeclaration(path){   console.log(path.node.type); // will print "FunctionDeclaration"   functionNames.push(path.node.id.name); // will add the name of the function to the array    // return false to avoid looking inside of the functions body   // we stop our search at this level   return false; }) 

Hãy thử gói các hàm đi ngang qua nhau để xem các cây con. Đoạn mã dưới đây sẽ trả về mọi hàm chính xác ở cấp thứ hai trở xuống. Nó sẽ không nhận ra một hàm trong một hàm trong một hàm:

const functionNames = []; recast.visit(ast, visitFunctionDeclaration(path){   var newPath = path.get('body');    // subtraversing   recast.visit(newPath, visitFunctionDeclaration(path){     functionNames.push(path.node.id.name);     return false;   })    // return false to not look at other functions contained in this function   // leave this role to the sub-traversing   return false; }) 

Nhiệm vụ đã hoàn thành !! 🏅

Ta đã lập trình tìm thấy tất cả các tên hàm. Ta có thể dễ dàng tìm thấy tên của các đối số hoặc các biến được hiển thị.

Bảng chú giải

AST Node một đối tượng trong cây. Ví dụ: khai báo hàm, gán biến, biểu thức đối tượng

NodePath liên kết giữa Node cha và Node con trong một cây

NodeProperty các phần của định nghĩa về nút. Tùy thuộc vào nút, người ta có thể chỉ có một tên hoặc nhiều thông tin hơn

bảng {border -fall: sụp đổ; khoảng cách đường viền: 0; background: var (–bg); border: 1px solid var (–gs0); bố cục bảng: auto; margin: 2rem auto} table thead {background: var (–bg3)} table thead tr th {padding: .5rem .625rem .625rem; kích thước phông chữ: 1.2rem; font-weight: 700; color: var (–text-color)} table tr td, table tr th {padding: .5625rem .625rem; kích thước phông chữ: 1.2rem; color: var (–text-color); text-align: center} table tr: nth-of-type (Even) {background: var (–bg3)} table tbody tr td, table tbody tr th, table thead tr th, table tr td {display: table-cell ; line-height: 2.8125rem}


Tags:

Các tin liên quan

JavaScript Biểu thức chính quy cho Người bình thường
2019-02-07
Mẫu kế thừa nguyên mẫu JavaScript
2019-02-01
Các mẫu hướng đối tượng JavaScript: Mẫu nhà máy
2019-01-23
Đối tượng, Nguyên mẫu và Lớp trong JavaScript
2019-01-10
Thủ thuật với JavaScript Hủy cấu trúc
2018-11-26
Đừng sợ theo dõi JavaScript
2018-10-17
Làm phẳng mảng trong Vanilla JavaScript với flat () và flatMap ()
2018-09-28
Cách sử dụng các phương thức đối tượng trong JavaScript
2018-08-03
Xử lý lỗi trong JavaScript Sử dụng try ... catch
2018-08-03
Hiểu sự kiện trong JavaScript
2018-06-19