Skip to content

fix(@schematics/angular): correctly detect modules using new file extension format #30471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions packages/schematics/angular/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,7 @@ export default function (options: ComponentOptions): Rule {
options.path = buildDefaultPath(project);
}

try {
options.module = findModuleFromOptions(host, options);
} catch {
options.module = findModuleFromOptions(host, {
...options,
moduleExt: '-module.ts',
routingModuleExt: '-routing-module.ts',
});
}

options.module = findModuleFromOptions(host, options);
// Schematic templates require a defined type value
options.type ??= '';

Expand Down
10 changes: 1 addition & 9 deletions packages/schematics/angular/directive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,7 @@ export default function (options: DirectiveOptions): Rule {
options.path = buildDefaultPath(project);
}

try {
options.module = findModuleFromOptions(host, options);
} catch {
options.module = findModuleFromOptions(host, {
...options,
moduleExt: '-module.ts',
routingModuleExt: '-routing-module.ts',
});
}
options.module = findModuleFromOptions(host, options);
const parsedPath = parseName(options.path, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
Expand Down
18 changes: 6 additions & 12 deletions packages/schematics/angular/module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import { addImportToModule, addRouteDeclarationToModule } from '../utility/ast-u
import { InsertChange } from '../utility/change';
import {
MODULE_EXT,
MODULE_EXT_LEGACY,
ROUTING_MODULE_EXT,
ROUTING_MODULE_EXT_LEGACY,
buildRelativePath,
findModuleFromOptions,
} from '../utility/find-module';
Expand Down Expand Up @@ -114,11 +116,11 @@ function addRouteDeclarationToNgModule(

function getRoutingModulePath(host: Tree, modulePath: string): string | undefined {
const routingModulePath =
modulePath.endsWith(ROUTING_MODULE_EXT) || modulePath.endsWith('-routing-module.ts')
modulePath.endsWith(ROUTING_MODULE_EXT_LEGACY) || modulePath.endsWith(ROUTING_MODULE_EXT)
? modulePath
: modulePath
.replace(MODULE_EXT, ROUTING_MODULE_EXT)
.replace('-module.ts', '-routing-module.ts');
.replace(MODULE_EXT_LEGACY, ROUTING_MODULE_EXT_LEGACY)
.replace(MODULE_EXT, ROUTING_MODULE_EXT);

return host.exists(routingModulePath) ? routingModulePath : undefined;
}
Expand All @@ -138,15 +140,7 @@ export default function (options: ModuleOptions): Rule {
}

if (options.module) {
try {
options.module = findModuleFromOptions(host, options);
} catch {
options.module = findModuleFromOptions(host, {
...options,
moduleExt: '-module.ts',
routingModuleExt: '-routing-module.ts',
});
}
options.module = findModuleFromOptions(host, options);
}

let routingModulePath;
Expand Down
11 changes: 1 addition & 10 deletions packages/schematics/angular/pipe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,7 @@ import { Schema as PipeOptions } from './schema';
export default function (options: PipeOptions): Rule {
return async (host: Tree) => {
options.path ??= await createDefaultPath(host, options.project);
try {
options.module = findModuleFromOptions(host, options);
} catch {
options.module = findModuleFromOptions(host, {
...options,
moduleExt: '-module.ts',
routingModuleExt: '-routing-module.ts',
});
}

options.module = findModuleFromOptions(host, options);
const parsedPath = parseName(options.path, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
Expand Down
43 changes: 28 additions & 15 deletions packages/schematics/angular/utility/find-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export interface ModuleOptions {
standalone?: boolean;
}

export const MODULE_EXT = '.module.ts';
export const ROUTING_MODULE_EXT = '-routing.module.ts';
export const MODULE_EXT = '-module.ts';
export const ROUTING_MODULE_EXT = '-routing-module.ts';
export const MODULE_EXT_LEGACY = '.module.ts';
export const ROUTING_MODULE_EXT_LEGACY = '-routing.module.ts';

/**
* Find the module referred by a set of options passed to the schematics.
Expand All @@ -31,13 +33,10 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path
return undefined;
}

const moduleExt = options.moduleExt || MODULE_EXT;
const routingModuleExt = options.routingModuleExt || ROUTING_MODULE_EXT;

if (!options.module) {
const pathToCheck = (options.path || '') + '/' + options.name;

return normalize(findModule(host, pathToCheck, moduleExt, routingModuleExt));
return normalize(findModule(host, pathToCheck, options.moduleExt, options.routingModuleExt));
} else {
const modulePath = normalize(`/${options.path}/${options.module}`);
const componentPath = normalize(`/${options.path}/${options.name}`);
Expand All @@ -53,14 +52,21 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path
}

const candidatesDirs = [...candidateSet].sort((a, b) => b.length - a.length);
for (const c of candidatesDirs) {
const candidateFiles = ['', `${moduleBaseName}.ts`, `${moduleBaseName}${moduleExt}`].map(
(x) => join(c, x),
const candidateFiles: string[] = ['', `${moduleBaseName}.ts`];
if (options.moduleExt) {
candidateFiles.push(`${moduleBaseName}${options.moduleExt}`);
} else {
candidateFiles.push(
`${moduleBaseName}${MODULE_EXT}`,
`${moduleBaseName}${MODULE_EXT_LEGACY}`,
);
}

for (const c of candidatesDirs) {
for (const sc of candidateFiles) {
if (host.exists(sc) && host.readText(sc).includes('@NgModule')) {
return normalize(sc);
const scPath = join(c, sc);
if (host.exists(scPath) && host.readText(scPath).includes('@NgModule')) {
return normalize(scPath);
}
}
}
Expand All @@ -78,15 +84,22 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path
export function findModule(
host: Tree,
generateDir: string,
moduleExt = MODULE_EXT,
routingModuleExt = ROUTING_MODULE_EXT,
moduleExt?: string,
routingModuleExt?: string,
): Path {
let dir: DirEntry | null = host.getDir('/' + generateDir);
let foundRoutingModule = false;

const moduleExtensions: string[] = moduleExt ? [moduleExt] : [MODULE_EXT, MODULE_EXT_LEGACY];
const routingModuleExtensions: string[] = routingModuleExt
? [routingModuleExt]
: [ROUTING_MODULE_EXT, ROUTING_MODULE_EXT_LEGACY];

while (dir) {
const allMatches = dir.subfiles.filter((p) => p.endsWith(moduleExt));
const filteredMatches = allMatches.filter((p) => !p.endsWith(routingModuleExt));
const allMatches = dir.subfiles.filter((p) => moduleExtensions.some((m) => p.endsWith(m)));
const filteredMatches = allMatches.filter(
(p) => !routingModuleExtensions.some((m) => p.endsWith(m)),
);

foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length;

Expand Down
9 changes: 9 additions & 0 deletions packages/schematics/angular/utility/find-module_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ describe('find-module', () => {
expect(modPath).toEqual('/projects/my-proj/src/admin/foo.module.ts');
});

it('should find a module in a sub dir when using the `-module` suffix', () => {
tree.create('/projects/my-proj/src/admin/foo-module.ts', '@NgModule');
options.name = 'other/test';
options.module = 'admin/foo';
options.path = '/projects/my-proj/src';
const modPath = findModuleFromOptions(tree, options) as string;
expect(modPath).toEqual('/projects/my-proj/src/admin/foo-module.ts');
});

it('should find a module in a sub dir (2)', () => {
tree.create('/projects/my-proj/src/admin/foo.module.ts', '@NgModule');
options.name = 'admin/hello';
Expand Down