
# 文件遍历器

每种 Greph 模式（文本、AST、重写、索引文本、索引 AST、缓存 AST、`rg` 包装器、`sg` 包装器）都会先遍历文件树并生成一个 `Greph\Walker\FileList`。遍历器由所有模式共享，因此各处使用相同的标志和行为。

## 默认行为

遍历器开箱即用时会：

- 递归进入所请求路径下的每个目录。
- 在每一层遵循 `.gitignore` 和 `.grephignore` 规则，包括嵌套覆盖和否定规则。
- 跳过 `.git` 目录。
- 跳过隐藏文件和目录（名称以 `.` 开头的内容）。
- 跳过二进制文件（通过文件前 512 个字节中的空字节检测）。
- 跳过大于 10 MiB 的文件。
- 不跟随符号链接。

## 覆盖标志

| 标志 | 效果 |
|---|---|
| `--no-ignore` | 停止遵循 `.gitignore` 和 `.grephignore` |
| `--hidden` | 包含隐藏文件和目录 |
| `--type NAME` | 仅包含与指定类型别名匹配的文件 |
| `--type-not NAME` | 排除与指定类型别名匹配的文件 |
| `--glob GLOB` | 仅包含路径与 GLOB 匹配的文件。可重复指定。 |

这些选项也通过 `Greph\Walker\WalkOptions` 提供，以便以编程方式使用。

```php
use Greph\Greph;
use Greph\Walker\FileTypeFilter;
use Greph\Walker\WalkOptions;

$files = Greph::walk('src', new WalkOptions(
    respectIgnore: false,
    includeHidden: true,
    fileTypeFilter: new FileTypeFilter(['php']),
    globPatterns: ['src/**/*.php'],
));
```

## 文件类型别名

Greph 附带一组小型命名文件类型。每个别名都对应一个扩展名列表：

| 别名 | 扩展名 |
|---|---|
| `css` | `css`、`sass`、`scss` |
| `html` | `htm`、`html`、`phtml` |
| `js` | `cjs`、`js`、`mjs` |
| `json` | `json` |
| `md` | `markdown`、`md` |
| `php` | `inc`、`php`、`php3`、`php4`、`php5`、`php7`、`php8`、`phpt`、`phtml` |
| `txt` | `txt` |
| `ts` | `ts`、`tsx` |
| `xml` | `xml` |
| `yaml` | `yaml`、`yml` |

完整列表见 `Greph\Walker\FileTypeFilter::TYPE_MAP`。

## .gitignore 语义

Greph 使用纯 PHP 实现 gitignore 匹配。其语义与 `git check-ignore` 保持一致：

- 模式相对于其所在目录进行求值。
- 嵌套的 `.gitignore` 文件会覆盖父级规则。
- 否定规则（`!pattern`）会重新包含先前排除的路径。
- 末尾的 `/` 将模式限定为目录。
- 开头的 `/` 将模式锚定到 `.gitignore` 所在目录。
- `**` 可匹配任意数量的路径段。

`.grephignore` 使用相同语法，并叠加在 `.gitignore` 之上。你可以在此添加不想纳入版本控制的工具专属排除规则。

实现位于 `Greph\Walker\GitignoreFilter`。

## 二进制文件检测

遍历器会检查每个文件前 512 个字节中是否含有空字节。如果存在空字节，该文件就会被视为二进制文件并跳过。这能捕获常见情况：仓库在已编入索引的目录中包含图像、归档或编译产物。

若仍要搜索二进制文件，请在相关的 `Options` 对象上设置 `skipBinaryFiles: false`。CLI 目前没有提供此标志；请使用 facade。

## 符号链接

默认情况下不会跟随符号链接。设置 `followSymlinks: true`（或在 `rg` 包装器上传入 `-L` / `--follow`）即可跟随符号链接。遍历器会跟踪已访问的目录，因此符号链接循环不会导致无限递归。

## 最大文件大小

大于 `maxFileSizeBytes`（默认 10 MiB）的文件会被跳过。这可以避免意外将 1 GB 的日志文件读入内存，从而保护扫描过程。需要处理更大的输入时，可在 `Options` 对象上调整此限制。

## 为什么要自定义遍历器

Greph 可以依赖 `glob()` 或 `RecursiveDirectoryIterator`，但两者都没有实现 gitignore 语义，也都无法识别 `.grephignore`，并且都无法与工作池顺畅集成。只编写一次遍历器并在各模式间共享，也意味着每种模式应用的过滤规则完全相同，这对基于 oracle 的测试策略至关重要：要与 `grep`、`ripgrep` 和 `ast-grep` 比较，唯一办法是遍历这些工具所遍历的同一组文件。
