Skip to content

Commit 043c4af

Browse files
Fix barrel self-import when a route name is both a leaf and a prefix … (#317)
* Fix barrel self-import when a route name is both a leaf and a prefix of "index" A barrel child named "index" is written to "index/index.ts", but the generated import referenced "./index". Every resolver prefers the sibling "index.ts", so the barrel imported itself. With `photos.index` and `photos.index.window`, `photos/index.ts` emitted `import index<hash> from './index'`, producing TS7022 and TS2303 under tsc. Bundlers accepted it silently because `Object.assign(index, undefined)` is a no-op, so `photos.index.window` was dropped with no warning. When the "index" prefix has no leaf route of its own, such as `albums.index.recent`, the previous `$key !== 'index'` filter removed the import altogether while still emitting `Object.assign(index, index)`, leaving `index` undeclared: TS2304, and a ReferenceError at runtime. Import such a child by its full path instead, and narrow the filter so it only drops an "index" child that has no grandchildren, keeping the actions path unchanged. * just do map and implode once * Update GenerateCommand.php --------- Co-authored-by: Joe Tannenbaum <joe.tannenbaum@laravel.com>
1 parent eeb2e52 commit 043c4af

3 files changed

Lines changed: 28 additions & 5 deletions

File tree

src/GenerateCommand.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,17 @@ private function writeBarrelFiles(array|Collection $children, string $parent): v
398398
];
399399
});
400400

401-
if (! ($this->content[$indexPath] ?? false)) {
402-
$imports = $childKeys->filter(fn ($_, $key) => $key !== 'index')->map(fn ($alias, $key) => "import {$alias['safe']} from './{$key}'")->implode(PHP_EOL);
403-
} else {
404-
$imports = $childKeys->only($keysWithGrandkids->keys())->map(fn ($alias, $key) => "import {$alias['safe']} from './{$key}'")->implode(PHP_EOL);
405-
}
401+
// A child named "index" is written to "index/index.ts", but every resolver
402+
// prefers the sibling "index.ts" for "./index", so it has to be imported
403+
// by its full path or the barrel silently ends up importing itself.
404+
$importPath = fn ($key) => $key === 'index' ? './index/index' : "./{$key}";
405+
406+
$importable = ($this->content[$indexPath] ?? false)
407+
? $childKeys->only($keysWithGrandkids->keys())
408+
// A childless "index" is a leaf written into this same file, so it needs no import.
409+
: $childKeys->filter(fn ($_, $key) => $key !== 'index' || $keysWithGrandkids->has($key));
410+
411+
$imports = $importable->map(fn ($alias, $key) => "import {$alias['safe']} from '{$importPath($key)}'")->implode(PHP_EOL);
406412

407413
if ($imports) {
408414
$this->prependContent($indexPath, $imports);

tests/IndexNamedRoute.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect, it } from "vitest";
2+
import albums from "../workbench/resources/js/routes/albums";
3+
import photos from "../workbench/resources/js/routes/photos";
4+
5+
it("can handle a route name that is both a leaf and a prefix of 'index'", () => {
6+
expect(photos.index().url).toBe("/photos");
7+
expect(photos.index.window().url).toBe("/photos/window");
8+
});
9+
10+
it("can handle an 'index' prefix with no leaf route of its own", () => {
11+
expect(albums.index.recent().url).toBe("/albums/recent");
12+
});

workbench/routes/web.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,8 @@
104104
Route::get('/', fn () => 'ok')->name('index');
105105
});
106106
});
107+
108+
Route::get('/photos', fn () => 'ok')->name('photos.index');
109+
Route::get('/photos/window', fn () => 'ok')->name('photos.index.window');
110+
111+
Route::get('/albums/recent', fn () => 'ok')->name('albums.index.recent');

0 commit comments

Comments
 (0)