Repository navigation
Conversation
…s a path With an .oxlintrc.json, a tsconfig.json without `include` applies to the files below its directory, and an `include` or `exclude` entry that names a file applies to that file. oxlint compares paths in both cases. `bun lint` compiled the directory and the entry as one pattern, so a directory named `[x]` or a file named `[id].ts` was a character class there. With settings["import/resolver"].typescript, the directory of a tsconfig.json is a path in its `include` and `exclude` patterns, as in eslint-import-resolver-typescript. For the files that go to the installed ESLint, the patterns of `--ignore-pattern` and of bunfig.toml get their directory as `basePath` if that ESLint is 9.30 or later, which is what ESLint passes itself. An older ESLint gets the directory in front of each pattern, as before.
…orking directory as a path
`import/no-restricted-paths`, `import/no-extraneous-dependencies` and
`import/no-unassigned-import` join the working directory in front of the
paths and globs of their options, and compile the result. In a directory
named `[proj]`, `{a,b}` or `c++(old)` a zone then matched no file, and
neither did a glob.
eslint-plugin-import does the same, so with an ESLint or oxlint config
nothing changes. Without one, the working directory is compared as a
path, and only what the options say is a pattern.
The docs examples that are their own project now run below a directory
named `[p]{a,b}+(c)`. The page of `import/no-restricted-paths` says how
a folder named `[id]` is written in a zone.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Path splitting primitives src/lint/paths.rs, src/lint/glob.rs, src/lint/driver/for_tests.rs |
Path helpers split joined paths using platform separators. The test runner reports split results for resolved, joined, and included paths. |
TypeScript project scope matching src/lint/graph/lib.rs, test/cli/lint/oracle/plugins/oxlint/*, test/cli/lint/glob.test.ts |
Project scopes distinguish pattern-based and path-based interpretation. TypeScript resolver selection uses path-based scopes. Fixtures cover glob-like project and file paths. |
CWD-aware import-rule glob matching src/lint/plugins/import_minimatch.rs, src/lint/plugins/rules/import_*.rs, test/cli/lint/glob.test.ts, docs/lint/rules/import/no-restricted-paths.mdx, docs/lint/rules/reports.json, src/codegen/lint-rule-docs.ts |
Import-rule matching uses the file’s path mode and supports patterns joined below the working directory. Tests and documentation cover glob-like directory names and restricted-path patterns. |
ESLint override version handling src/lint/js_plugin/eslint.rs, src/lint/js_plugin/worker/eslint.js, test/cli/lint/glob.test.ts |
Override conversion passes basePath to supported ESLint versions and rewrites ignore patterns for older versions. Tests cover versions 8, 9, and 10. |
Suggested reviewers: jarred-sumner
Priority:
Merge Risk: 
Minimal · up to 10ac5
-
No actionable issue remains from this review; the change is ready for normal checks.
Pre-merge checks: 
4
-
Full details: Title check
-
Explanation
The title names the core change: a joined directory is compared as a path rather than as glob syntax in
bun lint. The raw summary, objectives, and description all describe this same change to directory and pattern handling (tsconfig scope, ESLint basePath, import rules). The title is specific and concise, and it is not vague.
Full details: Description check
-
Explanation
The description is complete and directly addresses the required content. It explains what the PR changes, the problem, scope, tradeoffs, remaining limitations, and verification results. It does not use the template headings exactly, but it includes equivalent information, including how the changes were verified.
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
|
Updated 3:01 PM PT - Oct 25th, 2026
✅ @robobun, your commit 10ac5fb32680e3c22414c6635816d5507ae5ea3b passed in 🧪 To try this PR locally: bunx bun-pr 46913That installs a local version of the PR into your bun-46913 --bun |
|
Status How I reproduced it: the example of the For the tsconfig scope and for PR: #46913 |
…in its path On Windows `path.join` makes `pkg\eslint.config.mjs` of it, and the harness only makes the directory of a file whose path has a `/`.
The worker reads the ESLint version in `reviveOverride`, where it writes `basePath`. The record of `eslintFrom` is as on main.
Problem
[proj], theimport/no-restricted-pathsdocs example printsNo problems in 2 filesat exit 0 with only abunfig.toml.[proj]is a class:Zone::new(src/lint/plugins/rules/import_no_restricted_paths.rs:87),Scope::new(src/lint/graph/lib.rs:254) andmovedPattern(src/lint/js_plugin/worker/eslint.js:101).Fix
basePathof ignore patterns (ESLint 9.30+). The second commit does the same for the import rules without an ESLint or oxlint config.[proj]now gives whatprojgives. oxlint 1.89 agrees in 6 of 6 rows (main 1). ESLint 9.39.5 agrees in 8 of 8 folder names (main 3).test/cli/lint/glob.test.ts(41 new cases fail on main), 12 oxlint 1.87.0 oracle rows, all oftest/cli/lint.bun_glob(Do not read the directory of a sideEffects glob or a --filter path as glob syntax #44939's files), Windows share roots (no machine).Background
has_no_foreign_config: without an ESLint or oxlint config, no rule must match an installed package. With one, the import rules still equal eslint-plugin-import.literal_base_len. From insideapp/[locale]it reads that option text as a path.Downsides
tsconfig.jsonentry that namessrc/[id].tsapplies to that file, as in oxlint.[proj]now shares the faults ofprojwith twoimport/resolverprojects per file: 12 of 90 rows no longer equal the resolver, and the exit code can differ between runs.Notes
Where this comes from. There is no user report. The fault showed up during the work on #44939, which covers
sideEffectsglobs and--filterpaths. #46439 is open to shipbun lintin release builds.Commits.
lint: read the directory of a tsconfig.json and of --ignore-pattern as a path: conformance with oxlint and ESLint.lint: without an ESLint or oxlint config, the import rules read the working directory as a path: Bun's own behaviour, and the commit that fixes the docs example. Without this part, the first part is still complete.srcin total: +269 -79 in 11 files.The places, against the tool that each one follows.
Scope::new, with an.oxlintrc.jsonreviveOverride(worker)'**'then'!keep.txt'Scope::new, withsettings["import/resolver"].typescriptand 2 or more projectss*.[proj]gives whatprojgivesZone::new,GlobsFromCwdwith an ESLint configZone::new,GlobsFromCwdwithout a configprojWhat splits a joined pattern.
paths::split_joinedtakes one name off the directory for each..that leads out of the option text. What is left of the directory is a path. The rest is what the option says, so it stays a pattern.../lib/srcin[ws]/appkeeps[ws]as a path. A bunfig.toml entryapp/[locale]/test/**is a pattern from every working directory, also from insideapp/[locale], where the driver writes../../app/[locale]/test/**. An absolute option is a pattern as a whole in a zone. Atsconfig.jsonentry that is absolute is cut only if it goes through the directory of the file.And #44939. Its
bun_glob::literal_base_lentakes the longest prefix that the two strings share. The two rules differ where text leaves the directory and comes back by name:../[x]inside[x]is a path there and a pattern here. With the shared-prefix rule, the test with four working directories fails from insideapp/[locale]. The tree needs one rule. This branch does not touchsrc/glob/lib.rs, so #44939 is the place to settle it.split_joinedcan move tobun_globthen, andUnderstays in lint.Considered and dropped. The import-rule change under every config, and a change of the ESLint 8 ignore lists. In both, ESLint equals main: with a flat config and the real plugin,
bun lintequals ESLint 9.39.5 and 10.13.0 inproj,[proj],{a,b},c++(old)andapp@(v2), on main and with this PR. AlsobasePathonly for folder names with glob syntax: then'**'with'!keep.txt'stays wrong in a plain folder.tsconfig scope, with an
.oxlintrc.json.tsconfig.jsonas a path where noincludeis written, and it compares an entry with the file path before it matches the entry as a pattern. Both are ported.test/cli/lint/oracle/plugins/oxlint/projects.ts, recorded with oxlint 1.87.0 and equal with 1.89.0:h21toh28,r11tor14.include: ["src/a.ts", "src/[id].ts"]andexclude: ["src/[id].ts"]now apply to the filesrc/[id].ts.[x], oxlint applies noincludepattern and noexcludepattern.bun lintkeeps both (rowsh12,h13,h27,h28,r13). So atsconfig.jsonwithoutincludeand withexclude: ["b"]gives more reports in[x]than inx, as in oxlint.s*orC#, oxlint 1.89.0 drops thepathsof atsconfig.json.bun lintdoes not, on main and here. That is not ported: the[x]case has recorded rows, this one is a loss of every alias.tsconfig scope, with the TypeScript resolver.
tsconfig.jsonis a path now. What follows it is still a pattern as oxc-resolver reads it. The resolver uses get-tsconfig, where a[or a{is never syntax.includeforms) against the real tools. 7 rows are ina?b, where Bun cannot load a config file (seebun testfails while searching for test files if path contains '?' #7928, Decide in_resolvewhether a?is part of a path or starts a query #36627, bun -e: keep the whole path of the entry module when the cwd has a?#45048). Of the other 70, main differs in 21 and this PR in 7, all ins*.include: ["app/[locale]"].bun lintuses the closest project that includes the file, alone. The resolver does that once the project has resolved an import. Before, it also tries the next projects. 90 rows with two projects for one file: main differs from the resolver in 42, this PR in 30. 12 rows were equal on main and differ now, all in[ws]and{a,b}, where the first import of the file is an alias of the farther project. 24 rows go the other way. Rows that depend on the folder name: 36 on main, 0 here.bun lintgives the result of a plain folder everywhere.includenames one of two files of a directory, the exit code changes from run to run inproj, on main too.[proj]now does the same. The cause is a cache with one entry for each directory. A probe withinclude: ["a", "b/t.ts"]and the same import inb/t.tsandb/u.ts, 24 runs each: main exits with 1 in 7 runs inprojand in 0 runs in[proj]. This PR exits with 1 in 5 and in 6 runs.eslint b/t.ts b/u.tsreports nothing andeslint b/u.ts b/t.tsreports both files, because eslint-module-utils 2.14.0 caches a resolution by the directory of the file. A cache entry for each file would giveb/u.tsalone, which is what ESLint reports when it lints each file alone. That rule changes plain folders, so it is not in this PR.createFilesMatcher, which the resolver calls. It changes results in plain folders, so it is a change of its own.bun_sema'sRoots::hasis a port of TypeScript'sgetFileNamesFromConfigSpecs, which is another rule.ESLint worker.
{ basePath: cwd, ignores: ignorePatterns }(lib/config/config-loader.js). ESLint 9.29.0 joins the path into each pattern.bun lintnow passesbasePathwhen the installed ESLint is 9.30 or later and the directory is not the base path of the config array. That is for--ignore-patternand forignorePatternsof bunfig.toml, from below and from above the config file.--ignore-pattern '**' --ignore-pattern '!keep.txt'fromsub: ESLint lintskeep.txt, main printsNo matching configuration found. Written assub/**, the first pattern ignores the directory itself.[pkg]a file that a pattern lets in and that goes to that ESLint getsNo matching configuration found. ESLint 9.29.0 does not lint it.Import rules.
reads_cwd_as_path).devDependencies = ["**/test/**"]a project below a folder namedtestloses the report, on main and here. The keys aredevDependencies,optionalDependencies,peerDependencies,bundledDependencies,allowofimport/no-unassigned-import,exceptofimport/no-restricted-paths,exceptionsofimport/no-import-module-exports, andallowandforbidofimport/no-internal-modules..oxlintrc.json: unchanged. oxlint 1.89.0 does not haveimport/no-restricted-pathsorimport/no-extraneous-dependencies, so no tool says what the result is there."root": trueare the ones whose options are paths. They now run below a directory named[p]{a,b}+(c).rule-docs.test.tsfails on main forimport/no-restricted-pathsand passes here.Measured. Release builds with
--lto=offof main 179664e and of this PR.bun lint --threads=1on 3,000 files, user instructions per file fromperf_event_open, the minimum of 11 interleaved runs. The working directory has 24 or 224 bytes.import/no-restricted-pathsofftargettargetdevDependenciesglobs.eslintrc.json, 8 zones with a globtarget.eslintrc.json, 8devDependenciesglobs.oxlintrc.json, notsconfig.json.oxlintrc.json,tsconfig.jsonwithoutinclude.oxlintrc.json,include: ["src"]devDependenciescost 6 to 8 more. Rows that run the same code on both sides move by up to 67.mi_mallocentries, 200 and 400 files): the same count per file in all nine corpora. valgrind is not in the build container, so there are no dhat numbers.size: text 102,140,288 to 102,143,312 bytes, data and bss unchanged.--ignore-pattern 'gen/**'from a directory below the config file, every file sent to ESLint. 300 files: 1.043 G to 1.091 G instructions. 600 files: 1.228 G to 1.289 G. Medians of 15 and 9 interleaved runs. Two control runs withoutbasePathmove by 2 M and 5 M. The cost is what ESLint's config array does for an object with abasePath.Tests.
glob.test.ts: 60 new cases (55 on Windows). 41 fail on main. The other 19 are plain names and pins: what ESLint reports with an ESLint config in[proj], abasePathwith a wildcard, an absolutetarget, a bunfig.toml entry that namesapp/[locale]from 4 working directories,my.app,include: ["src"]in[proj], a file that no project includes, ESLint 9.29 and 8.57.js-parsers.test.tsdoes. It ignores like@eslint/config-array0.21.2: the last pattern decides, an object with abasePathis for what is below it, and nothing below an ignored directory is linted. It says whether it got abasePath.test/cli/lintpass. One timing test of the React Compiler rules fails in the full parallel run, on main too, and passes alone.split_joinedhas rows in the Windows style. From the code, not from a run: on a Windows share, atsconfig.jsonwithoutincludenow applies in a plain folder, and itsincludeandexcludepatterns stay what they are on main.Still folder-dependent, on purpose or left for later.
**of minimatch does not cross it.includeandexcludepatterns next to an.oxlintrc.jsonin[x], as in oxlint.projectlist of the TypeScript resolver.full_globfor.editorconfiginbun fmt, for a backslash in a folder name only.Not done from the self-review.
Roots::has, or the port ofcreateFilesMatcher): it changes plain folders, so it is a change of its own.bun_glob, shared with Do not read the directory of a sideEffects glob or a --filter path as glob syntax #44939: see "And Do not read the directory of a sideEffects glob or a --filter path as glob syntax #44939".tsconfig.json: not testable without a Windows machine.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/lint/glob.test.ts