@@ -41,6 +41,75 @@ export function registerLocale(options: Schema): Rule {
4141 } ;
4242}
4343
44+ /**
45+ * Safely creates an import change, returning NoopChange if the moduleSource is invalid
46+ * or if the import already exists.
47+ */
48+ function safeInsertImport ( moduleSource : ts . SourceFile | undefined , filePath : string , symbolName : string , fileName : string , isDefault = false ) : Change {
49+ if ( ! moduleSource ) {
50+ console . log ( ) ;
51+ console . log ( yellow ( `Could not insert import for ${ symbolName } in file (${ blue ( filePath ) } ).` ) ) ;
52+ console . log ( yellow ( `The source file is invalid.` ) ) ;
53+ return new NoopChange ( ) ;
54+ }
55+
56+ // Check if the import already exists
57+ const allImports = findNodes ( moduleSource , ts . SyntaxKind . ImportDeclaration ) ;
58+ if ( ! allImports ) {
59+ return new NoopChange ( ) ;
60+ }
61+
62+ const importExists = allImports . some ( node => {
63+ // Make sure it's an import declaration
64+ if ( ! ts . isImportDeclaration ( node ) ) {
65+ return false ;
66+ }
67+
68+ if ( ! node . moduleSpecifier ) {
69+ return false ;
70+ }
71+
72+ // Check if this import is from the same file
73+ if ( ! ts . isStringLiteral ( node . moduleSpecifier ) ) {
74+ return false ;
75+ }
76+ const importPath = node . moduleSpecifier . text ;
77+ if ( importPath !== fileName ) {
78+ return false ;
79+ }
80+
81+ // Check if the symbol is already imported
82+ if ( ! node . importClause ) {
83+ return false ;
84+ }
85+ const namedBindings = node . importClause . namedBindings ;
86+ if ( ! namedBindings ) {
87+ return false ;
88+ }
89+
90+ if ( ts . isNamedImports ( namedBindings ) ) {
91+ return namedBindings . elements . some ( element =>
92+ element . name . text === symbolName
93+ ) ;
94+ }
95+
96+ return false ;
97+ } ) ;
98+
99+ if ( importExists ) {
100+ return new NoopChange ( ) ;
101+ }
102+
103+ try {
104+ return insertImport ( moduleSource , filePath , symbolName , fileName , isDefault ) ;
105+ } catch ( e ) {
106+ console . log ( ) ;
107+ console . log ( yellow ( `Could not insert import for ${ symbolName } in file (${ blue ( filePath ) } ).` ) ) ;
108+ console . log ( yellow ( `Error: ${ e . message } ` ) ) ;
109+ return new NoopChange ( ) ;
110+ }
111+ }
112+
44113function registerLocaleInAppModule ( mainFile : string , options : Schema ) : Rule {
45114 return async ( host : Tree ) => {
46115 const appModulePath = getAppModulePath ( host , mainFile ) ;
@@ -50,13 +119,13 @@ function registerLocaleInAppModule(mainFile: string, options: Schema): Rule {
50119 const localePrefix = locale . split ( '_' ) [ 0 ] ;
51120
52121 applyChangesToFile ( host , appModulePath , [
53- insertImport ( moduleSource , appModulePath , 'provideNzI18n' ,
122+ safeInsertImport ( moduleSource , appModulePath , 'provideNzI18n' ,
54123 'ng-zorro-antd/i18n' ) ,
55- insertImport ( moduleSource , appModulePath , locale ,
124+ safeInsertImport ( moduleSource , appModulePath , locale ,
56125 'ng-zorro-antd/i18n' ) ,
57- insertImport ( moduleSource , appModulePath , 'registerLocaleData' ,
126+ safeInsertImport ( moduleSource , appModulePath , 'registerLocaleData' ,
58127 '@angular/common' ) ,
59- insertImport ( moduleSource , appModulePath , localePrefix ,
128+ safeInsertImport ( moduleSource , appModulePath , localePrefix ,
60129 `@angular/common/locales/${ localePrefix } ` , true ) ,
61130 registerLocaleData ( moduleSource , appModulePath , localePrefix ) ,
62131 ...insertI18nTokenProvide ( moduleSource , appModulePath , locale )
@@ -69,18 +138,40 @@ function registerLocaleInStandaloneApp(mainFile: string, options: Schema): Rule
69138
70139 return chain ( [
71140 async ( host : Tree ) => {
72- const bootstrapCall = findBootstrapApplicationCall ( host , mainFile ) ;
73- const appConfig = findAppConfig ( bootstrapCall , host , mainFile ) ;
74- const appConfigFile = appConfig . filePath ;
75- const appConfigSource = parseSourceFile ( host , appConfig . filePath ) ;
76- const localePrefix = locale . split ( '_' ) [ 0 ] ;
77-
78- applyChangesToFile ( host , appConfigFile , [
79- insertImport ( appConfigSource , appConfigFile , locale , 'ng-zorro-antd/i18n' ) ,
80- insertImport ( appConfigSource , appConfigFile , 'registerLocaleData' , '@angular/common' ) ,
81- insertImport ( appConfigSource , appConfigFile , localePrefix , `@angular/common/locales/${ localePrefix } ` , true ) ,
82- registerLocaleData ( appConfigSource , appConfigFile , localePrefix )
83- ] ) ;
141+ try {
142+ const bootstrapCall = findBootstrapApplicationCall ( host , mainFile ) ;
143+ if ( ! bootstrapCall ) {
144+ console . log ( ) ;
145+ console . log ( yellow ( `Could not find bootstrap application call in file (${ blue ( mainFile ) } ).` ) ) ;
146+ return void 0 ;
147+ }
148+
149+ const appConfig = findAppConfig ( bootstrapCall , host , mainFile ) ;
150+ if ( ! appConfig || ! appConfig . filePath ) {
151+ console . log ( ) ;
152+ console . log ( yellow ( `Could not find app config in file (${ blue ( mainFile ) } ).` ) ) ;
153+ return void 0 ;
154+ }
155+
156+ const appConfigFile = appConfig . filePath ;
157+ const appConfigSource = parseSourceFile ( host , appConfig . filePath ) ;
158+ if ( ! appConfigSource ) {
159+ console . log ( ) ;
160+ console . log ( yellow ( `Could not parse app config file (${ blue ( appConfigFile ) } ).` ) ) ;
161+ return void 0 ;
162+ }
163+
164+ const localePrefix = locale . split ( '_' ) [ 0 ] ;
165+
166+ applyChangesToFile ( host , appConfigFile , [
167+ safeInsertImport ( appConfigSource , appConfigFile , locale , 'ng-zorro-antd/i18n' ) ,
168+ safeInsertImport ( appConfigSource , appConfigFile , 'registerLocaleData' , '@angular/common' ) ,
169+ safeInsertImport ( appConfigSource , appConfigFile , localePrefix , `@angular/common/locales/${ localePrefix } ` , true ) ,
170+ registerLocaleData ( appConfigSource , appConfigFile , localePrefix )
171+ ] ) ;
172+ } catch ( e ) {
173+ console . log ( yellow ( `Error registering locale in standalone app: ${ e . message } ` ) ) ;
174+ }
84175 } ,
85176 addRootProvider ( options . project , ( { code, external } ) => {
86177 return code `${ external ( 'provideNzI18n' , 'ng-zorro-antd/i18n' ) } (${ locale } )` ;
@@ -92,16 +183,41 @@ function registerLocaleData(moduleSource: ts.SourceFile, modulePath: string, loc
92183 const allImports = findNodes ( moduleSource , ts . SyntaxKind . ImportDeclaration ) ;
93184 const allFun = findNodes ( moduleSource , ts . SyntaxKind . ExpressionStatement ) ;
94185
186+ // Check if allImports is valid before proceeding
187+ if ( ! allImports || allImports . length === 0 ) {
188+ console . log ( yellow ( `Could not add the registerLocaleData to file (${ blue ( modulePath ) } ).` +
189+ `because no import declarations were found.` ) ) ;
190+ console . log ( yellow ( `Please manually add the following code:` ) ) ;
191+ console . log ( cyan ( `registerLocaleData(${ locale } );` ) ) ;
192+ return new NoopChange ( ) ;
193+ }
194+
195+ // Safely filter the expression statements
95196 const registerLocaleDataFun = allFun . filter ( node => {
96- const fun = node . getChildren ( ) ;
97- return fun [ 0 ] . getChildren ( ) [ 0 ] ?. getText ( ) === 'registerLocaleData' ;
197+ if ( ! node ) return false ;
198+ const children = node . getChildren ( ) ;
199+ if ( ! children || children . length === 0 ) {
200+ return false ;
201+ }
202+ const firstChild = children [ 0 ] ;
203+ if ( ! firstChild ) {
204+ return false ;
205+ }
206+ const firstChildChildren = firstChild . getChildren ( ) ;
207+ if ( ! firstChildChildren || firstChildChildren . length === 0 ) {
208+ return false ;
209+ }
210+ const firstChildFirstChild = firstChildChildren [ 0 ] ;
211+ if ( ! firstChildFirstChild ) {
212+ return false ;
213+ }
214+ return firstChildFirstChild . getText ( ) === 'registerLocaleData' ;
98215 } ) ;
99216
100217 if ( registerLocaleDataFun . length === 0 ) {
101218 return insertAfterLastOccurrence ( allImports , `\n\nregisterLocaleData(${ locale } );` ,
102219 modulePath , 0 ) as InsertChange ;
103220 } else {
104- console . log ( ) ;
105221 console . log ( yellow ( `Could not add the registerLocaleData to file (${ blue ( modulePath ) } ).` +
106222 `because there is already a registerLocaleData function.` ) ) ;
107223 console . log ( yellow ( `Please manually add the following code:` ) ) ;
@@ -113,6 +229,15 @@ function registerLocaleData(moduleSource: ts.SourceFile, modulePath: string, loc
113229function insertI18nTokenProvide ( moduleSource : ts . SourceFile , modulePath : string , locale : string ) : Change [ ] {
114230 const metadataField = 'providers' ;
115231 const nodes = getDecoratorMetadata ( moduleSource , 'NgModule' , '@angular/core' ) ;
232+
233+ // Check if nodes are valid
234+ if ( ! nodes || nodes . length === 0 ) {
235+ console . log ( yellow ( `Could not find NgModule decorator in file (${ blue ( modulePath ) } ).` ) ) ;
236+ console . log ( yellow ( `Please manually add the following code to your providers:` ) ) ;
237+ console . log ( cyan ( `provideNzI18n(${ locale } )` ) ) ;
238+ return [ ] ;
239+ }
240+
116241 const addProvide = addSymbolToNgModuleMetadata (
117242 moduleSource ,
118243 modulePath ,
@@ -127,10 +252,19 @@ function insertI18nTokenProvide(moduleSource: ts.SourceFile, modulePath: string,
127252 return [ ] ;
128253 }
129254
255+ // Check if a node has properties
256+ if ( ! node . properties ) {
257+ console . log ( yellow ( `Could not find properties in NgModule decorator in file (${ blue ( modulePath ) } ).` ) ) ;
258+ console . log ( yellow ( `Please manually add the following code to your providers:` ) ) ;
259+ console . log ( cyan ( `provideNzI18n(${ locale } )` ) ) ;
260+ return [ ] ;
261+ }
262+
130263 const matchingProperties : ts . ObjectLiteralElement [ ] =
131264 ( node as ts . ObjectLiteralExpression ) . properties
132- . filter ( prop => prop . kind === ts . SyntaxKind . PropertyAssignment )
265+ . filter ( prop => prop && prop . kind === ts . SyntaxKind . PropertyAssignment )
133266 . filter ( ( prop : ts . PropertyAssignment ) => {
267+ if ( ! prop || ! prop . name ) return false ;
134268 const name = prop . name ;
135269 switch ( name . kind ) {
136270 case ts . SyntaxKind . Identifier :
@@ -148,15 +282,16 @@ function insertI18nTokenProvide(moduleSource: ts.SourceFile, modulePath: string,
148282
149283 if ( matchingProperties . length ) {
150284 const assignment = matchingProperties [ 0 ] as ts . PropertyAssignment ;
151- if ( assignment . initializer . kind !== ts . SyntaxKind . ArrayLiteralExpression ) {
285+ if ( ! assignment || ! assignment . initializer || assignment . initializer . kind !== ts . SyntaxKind . ArrayLiteralExpression ) {
152286 return [ ] ;
153287 }
154288 const arrLiteral = assignment . initializer as ts . ArrayLiteralExpression ;
155- if ( arrLiteral . elements . length === 0 ) {
289+ if ( ! arrLiteral . elements || arrLiteral . elements . length === 0 ) {
156290 return addProvide ;
157291 } else {
158- const provideWithToken = arrLiteral . elements . some ( e => e . getText ?.( ) . includes ( 'NZ_I18N' ) ) ;
159- const provideWithFunc = arrLiteral . elements . some ( e => e . getText ?.( ) . includes ( 'provideNzI18n' ) ) ;
292+ // Safely check for getText method before calling it
293+ const provideWithToken = arrLiteral . elements . some ( e => e && typeof e . getText === 'function' && e . getText ( ) . includes ( 'NZ_I18N' ) ) ;
294+ const provideWithFunc = arrLiteral . elements . some ( e => e && typeof e . getText === 'function' && e . getText ( ) . includes ( 'provideNzI18n' ) ) ;
160295
161296 if ( ! provideWithFunc && ! provideWithToken ) {
162297 return addProvide ;
0 commit comments