ГлавнаяБлогДерево разбора с Tree-sitter: синтаксис и факты о коде
Алгоритмы

Дерево разбора с Tree-sitter: синтаксис и факты о коде

Как Tree-sitter превращает код в структуру и факты. Пишем рекурсивный обход дерева на Python. Узнайте, как анализировать код глубже текста.

Al
Редакция Algolitalgolit.ru
6 мин чтения26 августа 2026 г.

Что такое Tree-sitter и зачем он нужен?

Вы когда-нибудь искали строку @Controller в большом проекте, но не могли понять, это декоратор, комментарий или часть строки? Обычный текстовый поиск не различает контекст. А вот Tree-sitter — парсер, который строит синтаксическое дерево (AST) и превращает код в структурированные факты. В этой статье мы разберём, как использовать Tree-sitter на Python, чтобы извлекать из кода не просто строки, а осмысленные данные.

Установка и первый парсинг с Tree-sitter

Для начала установим Python-библиотеку Tree-sitter и языковой пакет для TypeScript:

pip install tree-sitter tree-sitter-typescript

Теперь напишем скрипт, который парсит файл и выводит дерево разбора. Возьмём небольшой NestJS-контроллер, чтобы было наглядно:

from tree_sitter import Language, Parser
from tree_sitter_languages import get_parser

# Загружаем парсер для TypeScript
parser = get_parser('typescript')

# Исходный код контроллера
code = '''
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { OrdersService } from './orders.service';

@Controller('orders')
@UseGuards(AuthGuard)
export class OrdersController {
  constructor(private readonly service: OrdersService) {}

  @Get(':id')
  findOne() {
    return this.service.findOne();
  }
}
'''

# Парсим код в дерево
tree = parser.parse(bytes(code, 'utf8'))
root = tree.root_node

# Рекурсивно печатаем дерево
def print_tree(node, indent=0):
    print(' ' * indent + node.type, end='')
    if node.child_count == 0 and node.type != 'comment':
        print(f' = {code[node.start_byte:node.end_byte]}', end='')
    print()
    for child in node.children:
        print_tree(child, indent + 2)

print_tree(root)

Этот код выведет огромное вложенное дерево, в котором будут узлы import_statement, class_declaration, decorator и другие. Но читать это неудобно. Давайте напишем более компактный обходчик, который покажет только ключевые узлы.

Компактный обход дерева: извлекаем факты

Вместо печати всего дерева, мы напишем функцию, которая рекурсивно обходит узлы и собирает информацию о структуре кода. Нам интересны классы, декораторы и их аргументы.

def extract_facts(node, facts):
    # Если узел — объявление класса
    if node.type == 'class_declaration':
        class_name = None
        for child in node.children:
            if child.type == 'type_identifier':
                class_name = code[child.start_byte:child.end_byte]
                break
        facts.append({'type': 'class', 'name': class_name})

    # Если узел — декоратор
    if node.type == 'decorator':
        decorator_info = extract_decorator(node)
        facts.append(decorator_info)

    # Рекурсивно обходим детей
    for child in node.children:
        extract_facts(child, facts)

def extract_decorator(node):
    # Ищем имя функции-декоратора (например, Controller)
    name = None
    args = []
    for child in node.children:
        if child.type == 'call_expression':
            # Внутри call_expression ищем identifier и аргументы
            for sub in child.children:
                if sub.type == 'identifier':
                    name = code[sub.start_byte:sub.end_byte]
                elif sub.type == 'arguments':
                    for arg in sub.children:
                        if arg.type == 'string':
                            args.append(code[arg.start_byte:arg.end_byte])
    return {'type': 'decorator', 'name': name, 'arguments': args}

facts = []
extract_facts(root, facts)

for fact in facts:
    print(fact)

Вывод будет примерно таким:

{'type': 'class', 'name': 'OrdersController'}
{'type': 'decorator', 'name': 'Controller', 'arguments': ["'orders'"]}
{'type': 'decorator', 'name': 'UseGuards', 'arguments': ['AuthGuard']}

Теперь мы имеем не просто текст, а структурированные данные. Это уже можно использовать для анализа.

Разница между поиском текста и синтаксическим анализом

Текстовый поиск ищет подстроку, но не понимает её роль. Например, строка @Controller может быть декоратором, а может встретиться в комментарии или строке. Анализ с помощью AST даёт контекст: мы точно знаем, что это узел decorator с именем Controller. Это ключевое отличие.

Практический пример: находим все контроллеры и их маршруты

Предположим, мы хотим найти все классы, помеченные как @Controller, и их маршруты @Get. Мы можем расширить наш анализатор:

def analyze_controller(node, controller_info):
    if node.type == 'class_declaration':
        # Проверяем, есть ли декоратор Controller
        for child in node.children:
            if child.type == 'decorator':
                deco = extract_decorator(child)
                if deco['name'] == 'Controller':
                    controller_name = None
                    for sub in node.children:
                        if sub.type == 'type_identifier':
                            controller_name = code[sub.start_byte:sub.end_byte]
                    controller_info.append({'name': controller_name, 'route': deco['arguments']})
    for child in node.children:
        analyze_controller(child, controller_info)

controller_info = []
analyze_controller(root, controller_info)
print(controller_info)

Вывод:

[{'name': 'OrdersController', 'route': ["'orders'"]}]

Теперь мы можем автоматически построить список маршрутов приложения.

Ограничения Tree-sitter

Важно понимать: Tree-sitter понимает только синтаксис, но не семантику. Если вы напишете @SomethingThatDoesNotExist(), Tree-sitter всё равно распознает это как декоратор. Он не знает, существует ли такой декоратор в NestJS или скомпилируется ли код. Это разделение полезно: парсинг говорит нам, что код структурно собой представляет, а другие инструменты (компилятор TypeScript, правила статического анализа) определяют, что эта структура означает.

Практический вывод: что делать прямо сейчас

Попробуйте запустить приведённый код на своём проекте. Начните с малого: извлеките все классы и декораторы из одного файла. Затем попробуйте расширить анализ: найдите все методы с декоратором @Get и постройте карту маршрутов. Это первый шаг к созданию собственного анализатора кода.

Tree-sitter — это фундамент для редакторов, навигации по коду и статических анализаторов. Освоив его, вы сможете создавать инструменты, которые понимают код глубже, чем простой текстовый поиск.

#Tree-sitter#синтаксический анализ#AST#Python#NestJS
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →