Skip to content

Commit e509952

Browse files
committed
fix(router): resolve non-relative spreads via baseUrl
closes #1308
1 parent fd97255 commit e509952

5 files changed

Lines changed: 80 additions & 20 deletions

File tree

src/utils/router-parser.util.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,7 +1146,8 @@ export class RouterParserUtil {
11461146

11471147
/**
11481148
* Resolve a TypeScript path alias (tsconfig compilerOptions.paths) to an absolute file path.
1149-
* Returns undefined when no alias matches or the tsconfig is unavailable.
1149+
* Also supports non-relative imports resolved via compilerOptions.baseUrl.
1150+
* Returns undefined when no mapping matches or the tsconfig is unavailable.
11501151
*/
11511152
private resolvePathAlias(moduleSpecifier: string): string | undefined {
11521153
try {
@@ -1155,7 +1156,7 @@ export class RouterParserUtil {
11551156

11561157
const tsconfig = readConfig(tsconfigPath);
11571158
const compilerOptions = tsconfig?.compilerOptions;
1158-
if (!compilerOptions?.paths) return undefined;
1159+
if (!compilerOptions) return undefined;
11591160

11601161
const baseUrl = compilerOptions.baseUrl
11611162
? path.resolve(
@@ -1164,27 +1165,40 @@ export class RouterParserUtil {
11641165
)
11651166
: path.dirname(tsconfigPath);
11661167

1167-
for (const [pattern, replacements] of Object.entries(
1168-
compilerOptions.paths as Record<string, string[]>,
1169-
)) {
1170-
if (!Array.isArray(replacements) || replacements.length === 0)
1171-
continue;
1168+
if (compilerOptions.paths) {
1169+
for (const [pattern, replacements] of Object.entries(
1170+
compilerOptions.paths as Record<string, string[]>,
1171+
)) {
1172+
if (
1173+
!Array.isArray(replacements) ||
1174+
replacements.length === 0
1175+
)
1176+
continue;
11721177

1173-
// Convert glob pattern to regex: "@shared/*" → /^@shared\/(.*)$/
1174-
const regexStr = pattern
1175-
.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&")
1176-
.replace(/\*/g, "(.*)");
1177-
const regex = new RegExp("^" + regexStr + "$");
1178-
const match = moduleSpecifier.match(regex);
1179-
1180-
if (match) {
1181-
const resolved = (replacements[0] as string).replace(
1182-
/\*/g,
1183-
match[1] ?? "",
1184-
);
1185-
return path.resolve(baseUrl, resolved);
1178+
// Convert glob pattern to regex: "@shared/*" → /^@shared\/(.*)$/
1179+
const regexStr = pattern
1180+
.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&")
1181+
.replace(/\*/g, "(.*)");
1182+
const regex = new RegExp("^" + regexStr + "$");
1183+
const match = moduleSpecifier.match(regex);
1184+
1185+
if (match) {
1186+
const resolved = (replacements[0] as string).replace(
1187+
/\*/g,
1188+
match[1] ?? "",
1189+
);
1190+
return path.resolve(baseUrl, resolved);
1191+
}
11861192
}
11871193
}
1194+
1195+
const isNonRelative =
1196+
!moduleSpecifier.startsWith("./") &&
1197+
!moduleSpecifier.startsWith("../") &&
1198+
!path.isAbsolute(moduleSpecifier);
1199+
if (isNonRelative) {
1200+
return path.resolve(baseUrl, moduleSpecifier);
1201+
}
11881202
} catch (_e) {
11891203
// silently skip — tsconfig may not be readable at this point
11901204
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Route } from "@angular/router";
2+
3+
export const customCategoryRoutes: Route[] = [
4+
{ path: "category", component: "CategoryComponent" as any },
5+
];
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Routes } from "@angular/router";
2+
import { customCategoryRoutes } from "features/category/custom-category-routing";
3+
4+
export const routes: Routes = [...customCategoryRoutes];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "src/app"
4+
}
5+
}

test/src/utils/router-parser.util.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ const routes = [
497497
__dirname,
498498
"../../../../../test/fixtures/path-alias-spread",
499499
);
500+
const baseUrlFixtureRoot = path.join(
501+
__dirname,
502+
"../../../../../test/fixtures/baseurl-spread",
503+
);
500504
const relativeFixtureRoot = path.join(
501505
__dirname,
502506
"../../../../../test/fixtures/relative-spread-import",
@@ -607,5 +611,33 @@ export const routes: Routes = [{ path: 'root', children: [...langs.map(lang => (
607611
routerParser.scannedFiles = previousScannedFiles;
608612
}
609613
});
614+
615+
it("should resolve spread imports via tsconfig baseUrl in nested routing files (issue #1308)", () => {
616+
const previousScannedFiles = routerParser.scannedFiles;
617+
try {
618+
routerParser.scannedFiles = [];
619+
Configuration.mainData.tsconfig = path.join(
620+
baseUrlFixtureRoot,
621+
"tsconfig.json",
622+
);
623+
624+
const project = new Project();
625+
const sourceFile = project.addSourceFileAtPath(
626+
path.join(
627+
baseUrlFixtureRoot,
628+
"src/app/sections/main/main-routing.module.ts",
629+
),
630+
);
631+
expect(() =>
632+
routerParser.cleanFileSpreads(sourceFile),
633+
).not.to.throw();
634+
expect(sourceFile.getText()).not.to.contain(
635+
"...customCategoryRoutes",
636+
);
637+
expect(sourceFile.getText()).to.contain('path: "category"');
638+
} finally {
639+
routerParser.scannedFiles = previousScannedFiles;
640+
}
641+
});
610642
});
611643
});

0 commit comments

Comments
 (0)