Replace dynamic diagnostic styles with fixed ANSI prefixes, reducing `oxfmt` and `oxlint` release text size.
AI assistance: Codex was used. The contributor remains responsible for this change.
Possibly fixes#25259, and partial revert of #25133.
---
- `.gitignore` applies only to discovery (walking directory targets, cwd, globs)
- An explicitly named file target is linted / formatted even when gitignored
- = Restoring the pre-[#25133]() behavior for files, while fully keeping the [#25107]() fix for walk roots
| Target | Current | This PR |
| --- | --- | --- |
| Explicit file (`oxlint dist/a.js`) | skipped | linted |
| Explicit directory (`oxlint dist`) | skipped | skipped |
| cwd inside ignored tree | skipped | skipped |
The underlying principle:
> `.gitignore` is a message addressed to git, not to tools.
> Borrowing it to scope discovery is a useful heuristic, and a heuristic may narrow what the walk finds, but never refuse what the user names.
> Only tool's own ignore config can refuse. (which remain hard for the husky staged-file case).
This also re-aligns the CLI with stdin and LSP, which deliberately do not consult `.gitignore` (#19206).
👉🏻 This is the main motivation.
### Why not full revert
A directory target names where to search, not what to process.
The files under it are still discovered by the walk, so discovery rules apply.
Reverting that would bring back [#25107](), whose old behavior was not even a statable spec: whether running inside an ignored tree processed anything depended on the ignore pattern's form (bare `generated` did, `generated/**` didn't), an artifact of walker pruning, not a design.
### Regarding to `--no-ignore`
This flag remains orthogonal as a walk-level opt-out (`--no-ignore=vcs dist`) and for filling gap between existing tools.
However, with explicit files working again, I'm inclined to close those PRs and revisit if demand shows up.
For [#25259]()'s use case: name the files, e.g. `oxlint dist/foo.js` (or shell-expanded glob).
Fixes#25107.
The `ignore` crate walker enforces gitignore's "everything under an ignored directory is ignored" rule only by pruning ignored directories during traversal.
And it never filters its walk roots.
So when a walk target (based on cwd or an explicit CLI target path) is already inside a gitignored tree, pruning never happens and a bare directory pattern like `generated` matches nothing file-level. (even though `git check-ignore` says they are ignored)
See also BurntSushi/ripgrep#2595
### Changes
- oxc_config: added `GitignoreChecker`
- a pre-walk check that replays git's resolution order over the ancestor chain
- Matchers and `.git`/`.jj` probes are cached per directory
- oxlint / oxfmt: filter all CLI targets through this check before walking
### Behavior changes
- Explicitly passed gitignored files are now skipped (previously always linted/formatted)
- `oxlint --no-ignore` no longer force-lints gitignored targets
- The same behavior as `--no-ignore` does not disable during walk `.gitignore`
<details>
<summary>FYI: Current our ignore model overview</summary>
Two independent families, AND-ed (a path excluded by any layer is skipped):
| Layer | Source | oxlint | oxfmt |
| -------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| git-derived (always on, not user-disablable) | nested `.gitignore`, `.git/info/exclude` (during walk) | walker via shared `configure_walk_builder` | same (shared) |
| | walk roots / explicit CLI targets | `GitignoreChecker` (this PR) | `GitignoreChecker` (this PR) |
| tool-owned | ignore file | `.eslintignore` / `--ignore-path`; disabled by `--no-ignore` | `.prettierignore` / `--ignore-path`; no disable flag |
| | CLI patterns | `--ignore-pattern`; disabled by `--no-ignore` | `!pattern` path args |
| | config `ignorePatterns` | `LintIgnoreMatcher` (already containment-correct via `matched_path_or_any_parents`); always on | scope-local `is_path_ignored` (same); always on |
NOTE: `ignorePatterns` never had this bug, only the walker-backed gitignore layer did; this PR brings it to the same level.
A `!` negation in `ignorePatterns` cannot re-include gitignored paths (unchanged; the layers are independent).
</details>
Fixes#24612
- Update `fast-glob` to `1.1.0` which exposes `validate()` api
- Use it in `oxc_config`, apps will be validated through `deserialize`
- Oxfmt supports globs for CLI paths, also validated
Race probe: A protective measure required to correctly locate the nearest config file without a pre-scan.
The `ignore` crate traverses directories in parallel. Because this behavior is somewhat akin to a DFS and is further influenced by factors like OS resource availability, it is unpredictable.
Consequently, when each file attempts to locate its nearest config, we must prevent it from "settling" for a config further up the directory tree and accidentally adopting the wrong one.
Fixes#22250
> I'm benchmarking oxfmt on a big repo with around 50k directories.
> https://github.com/oxc-project/oxc/issues/22225
>
> The changes in this PR improve the times from 3.4s -> 2.8s, so 600ms savings 🎉
> https://github.com/oxc-project/oxc/pull/22258#issuecomment-4406579633
When I tested this on a local repo of about 20k, I didn't see as much improvement as reported, but there was definitely an improvement.
---
> [!WARNING]
> This PR changes the CLI behavior in the following nitch cases.
- When you have nested configs,
- and one of which is invalid,
- and you run it in "write" mode
Previously, the CLI would pre-scan the all config files, so an error would be reported immediately and no files would be formatted at all.
With this PR, while it will still report an error and abort upon finding an invalid config file, it will now proceed to format the files for any parts where a valid config can be applied.
In other words, it is highly unlikely that you will encounter this change.
Fixes https://github.com/oxc-project/oxc/issues/22225
Optimizes `oxfmt` nested config discovery by replacing the sequential
per-directory config file probing with a parallel entry-based pre-scan.
Previously, `prescan_config_locations()` checked every directory for
each supported config filename, causing many `fs::metadata` calls in
larger repos. The new approach walks entries in parallel, only inspects
config-looking files, deduplicates their parent directories, and keeps
existing nested config loading semantics unchanged.
In an ideal world we could probably do this even more lazy but I wasn't
sure how to best change that with the current architecture, this fix is
deliberately minimal
> [!NOTE]
> This PR was authored by an me and reviewed by an LLM, which rightfully
decided to update the comment at the top
Fixes#22034.
Oxfmt now respects the same Git ignore rules as Git itself:
- Reads `.gitignore` from parent directories (up to the repository boundary)
- Reads `.git/info/exclude`
like Oxlint does.
We can't determine whether a file path should be formatted or ignored without resolving the options, including the `overrides`.
Since there's no longer any point in keeping these separate, merging them into one.
Prepare for Svelte and Astro support.
In order to enable or disable the detection of `.svelte` files based on the presence of the `svelte` option with the `overrides` resolved, we need to resolve the final options during the walk phase.
Fixes#21651
Currently, we validate the config in advance (so, the root won't panic).
However, validating `overrides` beforehand is difficult.
- Since they are cumulative, the final result depends on specific file
paths
- The merged result can sometimes lead to invalid combinations (e.g.
`sortImports`)
Essentially, we have no choice but to return an error when an invalid
option is encountered for each target file, much like how we detect
syntax errors.
But I don't think abandoning pre-validation entirely is a good idea.
We have nested configs now, and cases where `overrides` cause errors
shouldn't happen very frequently.
---
👉🏻 Keep the current pre-validation (error early if possible (walk root,
stdin), validate `overrides` in separate files and report errors
individually (walk overrides, lsp).
As for changes from the current state, ensure that it does not panic at
the very least.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Previously, `ignorePatterns` could not skip directories in `filter_entry()` because doing so might hide nested configs beneath them.
This PR pre-scans config locations before the walk, so `filter_entry()` can safely skip `ignorePatterns` directories that have no nested config beneath them.
With all configs known upfront, the walk also resolves each file's scope dynamically from a pre-loaded scope map, replacing the sequential child-scope walks (`run_child_scope()` recursion) with a single parallel walk.
The pre-scan also preserves current streaming architecture (walk → channel → format workers), keeping format workers busy sooner than a batch-collect alternative would.
Fixes#19509
Initially, I thought about sharing the implementation with Oxlint, but finally I decided not to do it due to constraints and structural differences.
- Strategy
- Oxlint: Collects all file paths into memory upfront
- for cross-module analysis?
- then filters by `ignorePatterns` afterward
- Oxfmt: Streams files to the formatter as they are discovered while walking
- Scope-based walk resolves config at walk time, avoiding a separate post-walk filtering pass
- Config
- Oxlint: There is a difference in what can be configured in the root config versus the rest
- Oxfmt: No difference
Fixes#21107
Messages that were previously omitted when `no_error_on_unmatched_pattern` is specified will now be displayed, resulting in more consistent behavior.
- Before: Resolved beforehand and treated as a simple list of paths
- = 2-pass
- After: Walked through like a standard path, with processing handled by a custom matcher
- = 1-pass
- To support per-directory config, we need to locate the config files during the directory walk; glob is no exception
This is an internal change and generally has no impact on users.
(In niche cases, the content of the log output will change, but it will result in more consistent.)
For example, in cases where a read-only file is encountered.
- Before: this would trigger a panic
- After: it is reported as a write error, consistent with how parsing errors are handled
NOTE: I'll do a self-review and add the test tomorrow.
<details>
<summary>Prompt</summary>
```
# Support `vite.config.*` as a Configuration File
## Target Files
- `vite.config.ts`
- `vite.config.mts`
- `vite.config.cts`
- `vite.config.js`
- `vite.config.mjs`
- `vite.config.cjs`
## Auto-Discovery Priority
When searching for a config file from `cwd` upwards, the following priority applies within the same directory:
1. Tool-specific JSON/JSONC config files (e.g. `.oxfmtrc.json`, `.oxlintrc.json`)
2. Tool-specific JS/TS config files (e.g. `oxfmt.config.ts`, `oxlint.config.ts`)
3. `vite.config.*`
## Extracting Configuration
The default export of `vite.config.*` is expected to contain tool-specific configuration under a dedicated field:
- oxfmt: `.fmt`
- oxlint: `.lint`
If the field does not exist, an error is returned (e.g. `Expected a 'fmt' field in the default export.`).
## Explicit `--config` Flag
When `--config vite.config.ts` is explicitly specified, the same behavior applies. The file name is used to determine whether to extract a nested field.
## LSP
`vite.config.*` files are added to the file watch patterns so that configuration changes trigger a reload.
```
</details>