Статьи

Разбор JavaScript с помощью JavaScript

В выходные я начал работать над llamaduck — простым инструментом, целью которого является выяснить, будет ли ваш код работать на недавно выпущенном узле 0.6.0. В конце концов он может выполнять и другие задачи по оценке совместимости, но сначала я сосредоточусь на простых вещах.

Или, по крайней мере, я думал, что это было просто.

Список изменений API , так как 0.4.x не кажется , что долго , и это должно быть достаточно легко переваривается. Но, как оказалось, я провел почти все воскресенье, просто разбираясь, как превратить javascript в красивый анализируемый AST .

Если вы не знаете, что такое AST — это так называемое абстрактное синтаксическое дерево, что означает, что оно должно выглядеть одинаково, независимо от того, что является фактическим синтаксисом. Хотя это будет отличаться для разных языков. Таким образом, AST должен выглядеть так же, как JavaScript, но Python будет отличаться.

Мое исследование предложило три варианта:

  1. Возьми генератор парсера и грамматику JavaScript, надейся на лучшее
  2. У JSLint есть парсер… где-то около строки 2000
  3. У Uglify-JS предположительно тоже есть парсер

Единственным жизнеспособным вариантом был uglify-js. Это аккуратно упакованный модуль node.js, который делает немного больше, чем мне нужно, но, по крайней мере, у него есть простой в использовании парсер с открытым интерфейсом API.

Гол!

Вот пример файла, который выводит свой собственный AST, чтобы дать вам представление о том, о чем я говорю:

var parser = require('uglify-js').parser;
var util = require('util');
 
(function get_ast (path, callback) {
    require('fs').readFile(path, 'utf-8', function (err, data) {
        if (err) throw err;
 
        callback(parser.parse(data));
    });
})('./example.js', function (data) {
    console.log(util.inspect(data, true, null));
});

Файл анализирует себя и выводит дерево, закодированное в виде массива javascript (прокрутите мимо безумия, там немного больше текста):

[ 'toplevel',
  [ [ 'var',
      [ [ 'parser',
          [ 'dot',
            [ 'call',
              [ 'name', 'require', [length]: 2 ],
              [ [ 'string', 'uglify-js', [length]: 2 ],
                [length]: 1 ],
              [length]: 3 ],
            'parser',
            [length]: 3 ],
          [length]: 2 ],
        [length]: 1 ],
      [length]: 2 ],
    [ 'var',
      [ [ 'util',
          [ 'call',
            [ 'name', 'require', [length]: 2 ],
            [ [ 'string', 'util', [length]: 2 ], [length]: 1 ],
            [length]: 3 ],
          [length]: 2 ],
        [length]: 1 ],
      [length]: 2 ],
    [ 'stat',
      [ 'call',
        [ 'function',
          'get_ast',
          [ 'path', 'callback', [length]: 2 ],
          [ [ 'stat',
              [ 'call',
                [ 'dot',
                  [ 'call',
                    [ 'name', 'require', [length]: 2 ],
                    [ [ 'string', 'fs', [length]: 2 ], [length]: 1 ],
                    [length]: 3 ],
                  'readFile',
                  [length]: 3 ],
                [ [ 'name', 'path', [length]: 2 ],
                  [ 'string', 'utf-8', [length]: 2 ],
                  [ 'function',
                    null,
                    [ 'err', 'data', [length]: 2 ],
                    [ [ 'if',
                        [ 'name', 'err', [length]: 2 ],
                        [ 'throw',
                          [ 'name', 'err', [length]: 2 ],
                          [length]: 2 ],
                        undefined,
                        [length]: 4 ],
                      [ 'stat',
                        [ 'call',
                          [ 'name', 'callback', [length]: 2 ],
                          [ [ 'call',
                              [ 'dot',
                                [ 'name', 'parser', [length]: 2 ],
                                'parse',
                                [length]: 3 ],
                              [ [ 'name', 'data', [length]: 2 ], [length]: 1 ],
                              [length]: 3 ],
                            [length]: 1 ],
                          [length]: 3 ],
                        [length]: 2 ],
                      [length]: 2 ],
                    [length]: 4 ],
                  [length]: 3 ],
                [length]: 3 ],
              [length]: 2 ],
            [length]: 1 ],
          [length]: 4 ],
        [ [ 'string', './example.js', [length]: 2 ],
          [ 'function',
            null,
            [ 'data', [length]: 1 ],
            [ [ 'stat',
                [ 'call',
                  [ 'dot',
                    [ 'name', 'console', [length]: 2 ],
                    'log',
                    [length]: 3 ],
                  [ [ 'call',
                      [ 'dot',
                        [ 'name', 'util', [length]: 2 ],
                        'inspect',
                        [length]: 3 ],
                      [ [ 'name', 'data', [length]: 2 ],
                        [ 'name', 'true', [length]: 2 ],
                        [ 'name', 'null', [length]: 2 ],
                        [length]: 3 ],
                      [length]: 3 ],
                    [length]: 1 ],
                  [length]: 3 ],
                [length]: 2 ],
              [length]: 1 ],
            [length]: 4 ],
          [length]: 2 ],
        [length]: 3 ],
      [length]: 2 ],
    [length]: 3 ],
  [length]: 2 ]

Вывод

Теперь у нас есть простое дерево, которое мы можем рекурсивно анализировать и искать несовместимости. Но прежде чем что-то действительно практичное можно сделать, мне нужно выяснить, как отслеживать переменную область видимости . Это действительно трудная задача, потому что код должен проверять, когда переменные становятся критическим разделом, а затем подтверждать, что они действительно в конечном итоге используются критически.

Но как только этот орех расколется, llamaduck станет изящным маленьким инструментом, полезным для многих вещей.

Если у вас есть склонность к кодированию, я бы хотел помочь вам в репозитории llamaduck github .

 

С http://swizec.com/blog/parsing-javascript-with-javascript/swizec/2909