---
docgen:
  source: npm/rules/rust/lib/has-cargo-toml.mjs
  crc: f55fc57e
---

# has-cargo-toml.mjs

## Огляд

Модуль `has-cargo-toml.mjs` — rule-local утиліта для правил Rust, що визначає наявність маніфесту Cargo (`Cargo.toml`) у дереві файлової системи, починаючи з заданого кореневого каталогу. Використовується як applies-walker на рівні правила: дозволяє правилу Rust динамічно вирішувати, чи варто застосовуватися до конкретного workspace/cwd, шукаючи `Cargo.toml` у поточному каталозі або в будь-якому з підкаталогів.

Реалізація — синхронний рекурсивний обхід каталогів з раннім поверненням (`early return`) при першому ж знайденому `Cargo.toml`, без читання вмісту файлів. Імена директорій, у які заходити не треба (наприклад, `node_modules`, `.git`, `.next`, `.turbo`), передаються ззовні через `Set<string>` — модуль свідомо не містить власного списку, щоб список ігнорувань був єдиним з тим, що в `npm/scripts/auto-rules.mjs` (на рівні виклику).

Утиліта навмисно тримається rule-local (у каталозі правила `rust/lib/`), оскільки на момент написання її єдиний споживач — правило `rust`. У документуючому коментарі модуля зазначено: якщо з'явиться другий споживач — утиліту слід підняти у спільний `npm/scripts/lib/`.

## Експорти / API

Модуль експортує одну іменовану функцію:

- `hasCargoTomlInTree(root, ignoredDirNames)` — `function`, named export.

Інших експортів (default, типів, констант) у модулі немає.

## Функції

### `hasCargoTomlInTree(root, ignoredDirNames)`

**Сигнатура:**

```js
export function hasCargoTomlInTree(root: string, ignoredDirNames: Set<string>): boolean
```

**Параметри:**

- `root` — `string`. Абсолютний шлях до кореневого каталогу, з якого починається пошук. Передається у внутрішню функцію `walk` як стартова точка обходу.
- `ignoredDirNames` — `Set<string>`. Множина імен директорій (саме базових імен — не шляхів), у які НЕ слід заходити під час обходу. Перевірка виконується через `ignoredDirNames.has(entry.name)`. Типові значення, які формує викликова сторона: `'node_modules'`, `'.git'`, `'.next'`, `'.turbo'`.

**Повертає:**

- `boolean` — `true`, якщо в піддереві з коренем `root` (включно з самим `root`) знайдено хоча б один файл з ім'ям `Cargo.toml`. `false` — якщо такого файла немає, або якщо корінь недоступний для читання.

**Алгоритм:**

1. Викликається внутрішня функція `walk(root)`.
2. Усередині `walk(dir)` робиться спроба отримати вміст каталогу через `readdirSync(dir, { withFileTypes: true })`, що повертає масив об'єктів `Dirent`.
3. Якщо `readdirSync` кидає виключення (наприклад, `EACCES`, `ENOENT`, `ENOTDIR`) — воно мовчки ловиться `catch {}` і функція повертає `false` для цього каталогу (обхід продовжується для інших гілок, якщо вони є).
4. Виконується ітерація по `entries`:
   - Якщо запис — це файл і його ім'я дорівнює рівно `'Cargo.toml'`, негайно повертається `true` (early return).
   - Якщо запис — це каталог і його ім'я не входить у `ignoredDirNames`, рекурсивно викликається `walk(join(dir, entry.name))`; при `true` від рекурсії — негайно повертається `true`.
5. Якщо жоден запис не дав `true`, повертається `false`.

Порядок обходу — детерміністичний у межах одного запуску і визначається порядком, який повертає `readdirSync` (як правило, залежить від ФС/ОС). Це означає, що логічний результат (boolean) детерміністичний, але швидкість виявлення `Cargo.toml` залежить від порядку записів.

Алгоритм має властивість «depth-first з раннім return»: знайшовши `Cargo.toml` у першій же підгілці, функція не обходить решту дерева.

**Side effects:**

- Виключно синхронні I/O-операції читання вмісту директорій через `readdirSync` із Node.js `fs`. Не читає вміст файлів, не пише на диск, не звертається до мережі, не змінює глобальний стан.
- При помилках доступу до окремого каталогу не пробрасує виключення назовні — глушить їх через `try/catch` і повертає `false` лише для проблемної гілки.
- Блокує event loop на час обходу (синхронне API). Для дуже великих дерев це може бути помітним; ризик частково мітигує ранній return і список `ignoredDirNames`, що відсікає типові важкі директорії на кшталт `node_modules`.

**Внутрішня функція `walk(dir)`:**

- `dir` — `string`, абсолютний шлях каталогу для обходу.
- Повертає `boolean` — `true`, якщо в піддереві `dir` є `Cargo.toml`.
- Замикається на параметр `ignoredDirNames` зовнішньої функції (тобто множина передається один раз і використовується на всіх рівнях рекурсії без повторної параметризації).

## Залежності

Зовнішні npm-залежності — відсутні. Модуль працює виключно на стандартній бібліотеці Node.js:

- `node:fs` — імпортується `readdirSync` для синхронного читання вмісту каталогу з опцією `withFileTypes: true`, що повертає `Dirent[]` (для уникнення додаткових `statSync` на кожен запис).
- `node:path` — імпортується `join` для безпечного склеювання шляхів `dir + entry.name` із врахуванням розділювача ОС.

Імпорти використовують префікс `node:` (`node:fs`, `node:path`) — це канонічна форма посилання на вбудовані модулі Node.js, що відрізняє їх від npm-пакунків.

Модуль написаний як ES Module (`.mjs`): використовуються `import` та `export`.

Внутрішніх залежностей від інших файлів проєкту немає — модуль повністю самодостатній.

## Потік виконання / Використання

### Контекст застосування

Функція проектована як applies-walker для правил Rust: правило викликає її, щоб дізнатися, чи має воно сенс у поточному репозиторії/workspace. Якщо `Cargo.toml` ніде не знайдено — правило застосовувати не треба.

### Типовий виклик

```js
import { hasCargoTomlInTree } from './lib/has-cargo-toml.mjs'

const IGNORED = new Set(['node_modules', '.git', '.next', '.turbo'])

if (hasCargoTomlInTree(process.cwd(), IGNORED)) {
  // правило Rust застосовується: десь у дереві є Cargo.toml
} else {
  // Rust-проєкту не виявлено — правило пропускається
}
```

Список ігнорованих директорій формується викликовою стороною й має співпадати зі списком, який використовує `npm/scripts/auto-rules.mjs` (це вимога з заголовного коментаря модуля — щоб поведінка обходу була узгоджена між скриптом авто-правил і rule-local утилітою).

### Розгортання потоку виконання

1. Споживач (правило Rust) формує `Set<string>` ігнорованих імен директорій.
2. Споживач викликає `hasCargoTomlInTree(root, ignoredDirNames)` із кореневим шляхом (зазвичай — поточний workspace або репозиторій).
3. Усередині функції стартує `walk(root)`:
   - Якщо `root` сам містить `Cargo.toml` як файл першого рівня — повертається `true` без рекурсії далі по цьому ж рівню (інші записи не перевіряються після першого ж `Cargo.toml`).
   - Якщо `Cargo.toml` на першому рівні немає — обхід заглиблюється у кожен підкаталог, якого немає в `ignoredDirNames`.
   - Перший же `true` із рекурсії припиняє обхід і пробрасує `true` нагору.
4. Якщо все дерево пройдене без знахідки — повертається `false`.
5. Споживач використовує boolean-результат для прийняття рішення про застосування правила.

### Граничні випадки

- **Порожній `ignoredDirNames`** (`new Set()`): функція обходить геть усе дерево, включно з `node_modules` тощо — це коректно, але повільно.
- **Неіснуючий `root`**: `readdirSync` кидає `ENOENT`, помилка ловиться, функція повертає `false`.
- **`root` не є директорією** (наприклад, шлях до файлу): `readdirSync` кидає `ENOTDIR`, ловиться `catch`, повертається `false`.
- **Cargo.toml як директорія**: пропускається, бо перевірка йде через `entry.isFile() && entry.name === 'Cargo.toml'` — потрібен саме файл.
- **Симлінки**: `withFileTypes: true` повертає `Dirent` з власними методами; `entry.isFile()` для симлінка поверне `false`, `entry.isDirectory()` — теж `false` (метод `isSymbolicLink()` тут не перевіряється), тож симлінки фактично не переходяться. Це свідомий вибір — захист від циклів через симлінки.
- **Cargo.toml у самому корені**: знаходиться на першому ж рівні без рекурсії.
- **Cargo.toml виключно всередині `node_modules`** (за умови, що `node_modules` у `ignoredDirNames`): не знаходиться, функція повертає `false`. Це навмисна поведінка: ні правила, ні авто-обхід не повинні «бачити» вендоровані Rust-маніфести з npm-залежностей.

### Контекст у проєкті

Згідно з заголовним коментарем файлу:

- Утиліта живе у `npm/rules/rust/lib/` як rule-local — лише `rust`-правило її використовує.
- Список ігнорованих директорій має узгоджуватися зі списком у `npm/scripts/auto-rules.mjs`.
- Інваріант масштабування: при появі другого споживача — підняти модуль у спільний `npm/scripts/lib/`.

## Rebuild Test

За цією документацією модуль можна відтворити: один ES-модуль з імпортами `readdirSync` із `node:fs` та `join` із `node:path`, що експортує іменовану функцію `hasCargoTomlInTree(root, ignoredDirNames)`. Усередині — внутрішня рекурсивна функція `walk(dir)` із синхронним `readdirSync(dir, { withFileTypes: true })`, обгорнутим у `try/catch`, ітерацією по `Dirent`-записах, перевіркою `isFile() && name === 'Cargo.toml'` для негайного `true`, та рекурсією у підкаталоги через `join(dir, entry.name)` за умови `isDirectory() && !ignoredDirNames.has(name)`. Поведінкові інваріанти: ранній return при знахідці; повернення `false` при помилці читання каталогу; пропуск ігнорованих директорій; обхід не йде у симлінки (бо `isDirectory()` для симлінка повертає `false`); жодних побічних ефектів окрім читання структури директорій. Сигнатура: `(string, Set<string>) => boolean`.
