diff --git a/packages/schematics/angular/utility/ast-utils.ts b/packages/schematics/angular/utility/ast-utils.ts index df1f28a8eeb1..9f866dcd7cfc 100644 --- a/packages/schematics/angular/utility/ast-utils.ts +++ b/packages/schematics/angular/utility/ast-utils.ts @@ -323,12 +323,13 @@ export function getDecoratorMetadata( .filter((node) => { return ( node.kind == ts.SyntaxKind.Decorator && + (node as ts.Decorator).expression && (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression ); }) .map((node) => (node as ts.Decorator).expression as ts.CallExpression) .filter((expr) => { - if (expr.expression.kind == ts.SyntaxKind.Identifier) { + if (expr.expression && expr.expression.kind == ts.SyntaxKind.Identifier) { const id = expr.expression as ts.Identifier; return id.text == identifier && angularImports[id.text] === module; @@ -336,7 +337,7 @@ export function getDecoratorMetadata( // This covers foo.NgModule when importing * as foo. const paExpr = expr.expression as ts.PropertyAccessExpression; // If the left expression is not an identifier, just give up at that point. - if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) { + if (!paExpr.expression || paExpr.expression.kind !== ts.SyntaxKind.Identifier) { return false; } diff --git a/packages/schematics/angular/utility/ast-utils_spec.ts b/packages/schematics/angular/utility/ast-utils_spec.ts index e8688844f2cd..d71951f3783d 100644 --- a/packages/schematics/angular/utility/ast-utils_spec.ts +++ b/packages/schematics/angular/utility/ast-utils_spec.ts @@ -16,6 +16,7 @@ import { addRouteDeclarationToModule, addSymbolToNgModuleMetadata, findNodes, + getDecoratorMetadata, hasTopLevelIdentifier, insertAfterLastOccurrence, insertImport, @@ -839,4 +840,53 @@ describe('ast utils', () => { expect(hasTopLevelIdentifier(source, 'FooInterface', '@foo/interfaces')).toBe(false); }); }); + + describe('getDecoratorMetadata', () => { + const filePath = './src/app.module.ts'; + + it('should return decorator metadata for a valid NgModule', () => { + const fileContent = ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + imports: [] + }) + export class AppModule { } + `; + const source = getTsSource(filePath, fileContent); + const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); + + expect(nodes.length).toBe(1); + expect(nodes[0].kind).toBe(ts.SyntaxKind.ObjectLiteralExpression); + }); + + it('should not crash when processing files without decorators', () => { + const fileContent = ` + export const environment = { + production: false + }; + `; + const source = getTsSource(filePath, fileContent); + const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); + + expect(nodes.length).toBe(0); + }); + + it('should handle namespace imports correctly', () => { + const fileContent = ` + import * as ng from '@angular/core'; + + @ng.NgModule({ + declarations: [] + }) + export class AppModule { } + `; + const source = getTsSource(filePath, fileContent); + const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); + + expect(nodes.length).toBe(1); + expect(nodes[0].kind).toBe(ts.SyntaxKind.ObjectLiteralExpression); + }); + }); });