Номенклатура JavaScript (в контексті Node.js і Web API)

I. Передісторія
Я багато років використовую UltraEdit як редактор на різні випадки життя. Одна з основних причин — швидка робота з гігабайтними файлами без завантаження їх у пам'ять. Для програмування на JavaScript він теж досить зручний, ось тільки з одним істотним недоліком: автодоповнення в ньому грунтується на досить бідному, жорстко заданому списку ключових слів і глобальних змінних, до того ж відстає від розвитку мови. Як-то я задався питанням, чи можна поповнити цей список повним переліком всіх готових властивостей і методів, які тільки можна ввести в контексті Node.js і Web API (браузера). Де б такий список можна роздобути? Мені приходили в голову такі варіанти:
  1. Готовий перелік, ким-то складається та оновлюється для загального користування, на зразок бібліотеки globals, але повніше.
  2. Парсинг документації (специфікація ECMAScript, сайти MDN і Node.js тощо), вручну або програмно.
  3. Отримання списку метапрограммированием.
Основним відповіддю на мої питання була пропозиція змінити редактор і не мучитися. Але так як зручних редакторів для великих файлів не так вже й багато, а мій мінімалізм утруднював використання декількох під різні потреби, так і програмування не було моїм основним заняттям, я не здавався. Зрештою, це стало для мене не тільки практичної задачкою, але і принциповим інтересом: як же так, начебто така проста потреба, а легких рішень немає.
Оскільки готових списків я не знайшов, а парсити документацію — шлях довгий і ненадійний, я вирішив спробувати третій спосіб.
II. Код
Скориставшись кількома порадами, я написав невеликий скрипт, який виводить основну частину мовної номенклатури в різних контекстах кількома способами.
Ось що у мене вийшло.

Скрипт можна запустити в Node.js або в браузері (через консоль або вставляти в сторінку). У першому випадку результат буде виведено у файли, у другому — в додані до поточного документа текстові поля (можна відкрити
about:blank
разом з консоллю).
Спробую прокоментувати код.
  1. Спочатку ми створюємо основні змінні-контейнери. В перших двох ми будемо накопичувати нашу номенклатуру:
    nomenclatureTerms
    буде зберігатися простий список усіх лексем,
    nomenclatureChains
    — ті ж лексеми, але з повними ланцюжками, починаючи від кореневих об'єктів. В
    globs
    ми будемо зберігати наші відправні точки для розмотування клубка і побудови дерева — глобальні (кореневі) об'єкти. Щоб уникнути нескінченної рекурсії з-за циклічних посилань, всі оброблені об'єкти ми будемо складати в
    processedObjects
    для подальшої перевірки.
  2. На другому етапі ми заповнюємо
    globs
    .
    Спочатку скрипт намагається визначити, в якому контексті він виконується. Якщо це браузер, нам досить об'єкта
    window
    .
    Якщо це Node.js все трохи складніше. Спочатку ми додаємо два основних глобальних об'єкта, а також
    require
    , оскільки інакше на цю функцію ми не вийдемо. Потім ми додаємо об'єкти всіх стандартних бібліотек: основну частину — відштовхуючись від недокументированного списку
    require('repl')._builtinLibs
    , посоветованного одним з розробників Node.js а потім кілька відсутніх модулів. На завершення, оскільки кілька внутримодульных змінних (
    __dirname
    та
    __filename
    ) не прив'язані ні якогось глобального об'єкту, ми відразу ж додамо їх в наші номенклатурні контейнери.
  3. Далі йде основна робота: за допомогою рекурсивної функції
    processKeys
    ми обходимо всі глобальні об'єкти і всі об'єкти, які зберігаються в їх властивостях, до останньої можливої глибини. Потім виводимо результати в залежності від контексту і завершуємо їх підсумковим висновком в консоль розмірів наших номенклатур (скрипт працює відчутний час, так що цей висновок може служити сигналом завершення роботи — хоча в Chrome може знадобитися додатковий час на оновлення сторінки навіть після цього сигналу).
  4. Функція
    processKeys
    є основним двигуном процесу.
    Спочатку ми перевіряємо, з кореневим об'єктом ми маємо справу. Якщо так, ми відразу заносимо його ім'я в номенклатуру. Якщо об'єкт розташований в дочірньому властивості об'єкта, це занесення вже відбулося на попередньому етапі рекурсії, тому ми його пропускаємо.
    Потім ми заносимо об'єкт в список оброблених об'єктів, щоб не потрапити в погану нескінченність.
    Після цього починаємо оминати все властивості об'єкта. Кожне з них ми заносимо в
    nomenclatureTerms
    (тип
    Set
    автоматично відкидає повторення), потім формуємо ланцюжок з імені батьківського об'єкта і поточного властивості і заносимо її в
    nomenclatureChains
    ; ця ж ланцюжок стане іменем об'єкта для наступного рекурсивного виклику, тому вона буде постійно зростати з просуванням вглиб (я вибрав нотацію з квадратними дужками на всі випадки для уніфікації сортування: якщо використовувати точку для звичайних ідентифікаторів і дужки для складних строкових, це ламає порядок при виводі списку;
    JSON.stringify
    вживається для перестраховки — для екранування можливих лапок у складі імен властивостей).
    На наступному етапі ми перевіряємо, що зберігається у властивості: якщо це об'єкт, ми робимо новий рекурсивний виклик, якщо цього об'єкта ще немає в списку оброблених. Перевірка на об'єктність подвійна, оскільки
    instanceof Object
    повертає
    false
    ,
    Object.prototype
    та для об'єктів, створених за допомогою
    Object.create(null)
    .
    Таке повсюдне проходження за властивостями часто викликає помилки, тому нам доведеться додати обробник, щоб процес не переривався (виведення повідомлень про помилки залишений задля цікавості). Також в консоль, крім нашого бажання, буде виведено кілька попереджень про спроби запросити властивості, які отримали статус
    deprecated
    .
  5. Функція
    output
    відповідає за виведення результатів в залежності від контексту виконання. Спочатку вона формує список, відсортований у більш звичному словниковому порядку (правда, параметр
    caseFirst
    в Firefox не працює). Потім перевіряє контекст виконання: в браузері списки виводяться в два текстових поля, вбудовані в поточну сторінку (вгору списку додається для зручності ім'я файлу, з яким список можна зберегти за допомогою редактора); Node.js створюються два файли в поточному каталозі.
Слід враховувати, що до браузерному списку додаються імена функцій нашого скрипта, а до списку Node.js — різні змінні оточення; також в перелік включаються різні недокументовані властивості внутрішнього вживання, індекси масивів і т. п. З іншого боку, в наш список не потрапляють багато рядкові елементи номенклатури (наприклад, назви подій або стандартні рядкові параметри функцій).
III. Результати
Після прогону скрипта на останньої стабільної версії Node.js і на нічних збірках двох браузерів я отримав такі списки:
`
Node 6.6.0
Terms: 1 813
Chains: 7 282
Google Chrome Canary 55.0.2867.0
Terms: 3 339
Chains: 14 435
Firefox Nightly 52.0a1 (2016-09-21)
Terms: 5 040
Chains: 14 417
`
Можливо, у результатів програми можуть бути різні застосування. Наприклад, порівняння номенклатури різних браузерів або різних версій браузера (під час тестування я помічав, що нічні збірки сусідніх днів можуть давати результати, що розрізняються десятками позицій — що-то вводиться, щось відходить в історію). Якщо автоматизувати процес, можна, наприклад, створити історію API Node.js протягом багатьох версій. А можна зібрати різноманітну мовну статистику: глибина вкладення властивостей, довжина ідентифікаторів, принципи їх створення і т.д.
Напевно код можна оптимізувати по швидкості, по зручності використання, за повнотою результатів або їх читабельності. Також я міг допустити якісь дурні помилки через незнання тонкощів мови або контекстів використання. Буду вдячний за виправлення і додавання. Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.