@@ -160,63 +160,23 @@ export class Session {
160160 } ;
161161 }
162162
163- private async runNgcc ( configFilePath : string ) {
164- this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
165- done : false ,
166- configFilePath,
167- message : `Running ngcc for project ${ configFilePath } ` ,
168- } ) ;
169-
170- let success = false ;
171-
172- try {
173- await resolveAndRunNgcc ( configFilePath , {
174- report : ( msg : string ) => {
175- this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
176- done : false ,
177- configFilePath,
178- message : msg ,
179- } ) ;
180- } ,
181- } ) ;
182- success = true ;
183- } catch ( e ) {
184- this . error (
185- `Failed to run ngcc for ${ configFilePath } :\n` +
186- ` ${ e . message } \n` +
187- ` Language service will remain disabled.` ) ;
188- } finally {
189- this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
190- done : true ,
191- configFilePath,
192- success,
193- } ) ;
194- }
195-
196- // Re-enable language service even if ngcc fails, because users could fix
197- // the problem by running ngcc themselves. If we keep language service
198- // disabled, there's no way users could use the extension even after
199- // resolving ngcc issues. On the client side, we will warn users about
200- // potentially degraded experience.
201-
202- this . reenableLanguageServiceForProject ( configFilePath ) ;
203- }
204-
205- private reenableLanguageServiceForProject ( configFilePath : string ) {
206- const project = this . projectService . findProject ( configFilePath ) ;
207- if ( ! project ) {
208- this . error (
209- `Failed to find project for ${ configFilePath } returned by ngcc.\n` +
210- `Language service will remain disabled.` ) ;
163+ private enableLanguageServiceForProject ( project : ts . server . Project , angularCore : string ) {
164+ const { projectName} = project ;
165+ if ( ! project . languageServiceEnabled ) {
166+ project . enableLanguageService ( ) ;
167+ // When the language service got disabled, the program was discarded via
168+ // languageService.cleanupSemanticCache(). However, the program is not
169+ // recreated when the language service is re-enabled. We manually mark the
170+ // project as dirty to force update the graph.
171+ project . markAsDirty ( ) ;
172+ }
173+ if ( ! this . ivy ) {
174+ // Immediately enable Legacy / View Engine language service
175+ this . info ( `Enabling View Engine language service for ${ projectName } .` ) ;
176+ this . promptToEnableIvyIfAvailable ( project , angularCore ) ;
211177 return ;
212178 }
213- project . enableLanguageService ( ) ;
214- // When the language service got disabled, the program was discarded via
215- // languageService.cleanupSemanticCache(). However, the program is not
216- // recreated when the language service is re-enabled. We manually mark the
217- // project as dirty to force update the graph.
218- project . markAsDirty ( ) ;
219- this . info ( `Enabling Ivy language service for ${ project . projectName } .` ) ;
179+ this . info ( `Enabling Ivy language service for ${ projectName } .` ) ;
220180 this . handleCompilerOptionsDiagnostics ( project ) ;
221181 // Send diagnostics since we skipped this step when opening the file
222182 // (because language service was disabled while waiting for ngcc).
@@ -276,7 +236,16 @@ export class Session {
276236 this . isProjectLoading = false ;
277237 this . connection . sendNotification ( ProjectLoadingFinish ) ;
278238 }
279- this . checkProject ( event . data . project ) ;
239+ const { project} = event . data ;
240+ const angularCore = this . findAngularCoreOrDisableLanguageService ( project ) ;
241+ if ( angularCore ) {
242+ if ( this . ivy && isExternalAngularCore ( angularCore ) ) {
243+ // Do not wait on this promise otherwise we'll be blocking other requests
244+ this . runNgcc ( project , angularCore ) ;
245+ } else {
246+ this . enableLanguageServiceForProject ( project , angularCore ) ;
247+ }
248+ }
280249 break ;
281250 }
282251 case ts . server . ProjectsUpdatedInBackgroundEvent :
@@ -853,42 +822,91 @@ export class Session {
853822 }
854823
855824 /**
856- * Disable the language service if the specified `project` is not Angular or
857- * Ivy mode is enabled.
825+ * Find the main declaration file for `@angular/core` in the specified
826+ * `project`. If found, return the declaration file. Otherwise, disable the
827+ * language service and return undefined.
828+ *
829+ * @returns main declaration file in `@angular/core`.
858830 */
859- private checkProject ( project : ts . server . Project ) {
831+ private findAngularCoreOrDisableLanguageService ( project : ts . server . Project ) : string | undefined {
860832 const { projectName} = project ;
861833 if ( ! project . languageServiceEnabled ) {
862834 this . info (
863835 `Language service is already disabled for ${ projectName } . ` +
864836 `This could be due to non-TS files that exceeded the size limit (${
865837 ts . server . maxProgramSizeForNonTsFiles } bytes).` +
866838 `Please check log file for details.` ) ;
867-
868839 return ;
869840 }
841+ if ( ! project . hasRoots ( ) || project . isNonTsProject ( ) ) {
842+ return undefined ;
843+ }
844+ const angularCore = project . getFileNames ( ) . find ( isAngularCore ) ;
845+ if ( angularCore === undefined ) {
846+ project . disableLanguageService ( ) ;
847+ this . info (
848+ `Disabling language service for ${ projectName } because it is not an Angular project ` +
849+ `('@angular/core' could not be found).` ) ;
850+ if ( project . getExcludedFiles ( ) . some ( isAngularCore ) ) {
851+ this . info (
852+ `Please check your tsconfig.json to make sure 'node_modules' directory is not excluded.` ) ;
853+ }
854+ }
855+ return angularCore ;
856+ }
870857
871- const coreDts = this . checkIsAngularProject ( project ) ;
872- if ( coreDts === undefined ) {
858+ /**
859+ * Disable the language service, run ngcc, then re-enable language service.
860+ */
861+ private async runNgcc ( project : ts . server . Project , angularCore : string ) : Promise < void > {
862+ if ( ! isConfiguredProject ( project ) ) {
873863 return ;
874864 }
865+ // Disable language service until ngcc is completed.
866+ project . disableLanguageService ( ) ;
867+ const configFilePath = project . getConfigFilePath ( ) ;
875868
876- if ( this . ivy && isConfiguredProject ( project ) ) {
877- // Keep language service disabled until ngcc is completed.
878- project . disableLanguageService ( ) ;
879- // Do not wait on this promise otherwise we'll be blocking other requests
880- this . runNgcc ( project . getConfigFilePath ( ) ) . catch ( ( error : Error ) => {
881- this . error ( error . toString ( ) ) ;
869+ this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
870+ done : false ,
871+ configFilePath,
872+ message : `Running ngcc for ${ configFilePath } ` ,
873+ } ) ;
874+
875+ let success = false ;
876+
877+ try {
878+ await resolveAndRunNgcc ( configFilePath , {
879+ report : ( msg : string ) => {
880+ this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
881+ done : false ,
882+ configFilePath,
883+ message : msg ,
884+ } ) ;
885+ } ,
886+ } ) ;
887+ success = true ;
888+ } catch ( e ) {
889+ this . error (
890+ `Failed to run ngcc for ${ configFilePath } :\n` +
891+ ` ${ e . message } \n` +
892+ ` Language service will remain disabled.` ) ;
893+ } finally {
894+ this . connection . sendProgress ( NgccProgressType , NgccProgressToken , {
895+ done : true ,
896+ configFilePath,
897+ success,
882898 } ) ;
883- } else {
884- // Immediately enable Legacy/ViewEngine language service
885- this . info ( `Enabling VE language service for ${ projectName } .` ) ;
886- this . promptToEnableIvyIfAvailable ( project , coreDts ) ;
887899 }
900+
901+ // Re-enable language service even if ngcc fails, because users could fix
902+ // the problem by running ngcc themselves. If we keep language service
903+ // disabled, there's no way users could use the extension even after
904+ // resolving ngcc issues. On the client side, we will warn users about
905+ // potentially degraded experience.
906+ this . enableLanguageServiceForProject ( project , angularCore ) ;
888907 }
889908
890- private promptToEnableIvyIfAvailable (
891- project : ts . server . Project , coreDts : ts . server . NormalizedPath ) {
909+ private promptToEnableIvyIfAvailable ( project : ts . server . Project , coreDts : string ) : void {
892910 let angularCoreVersion = this . angularCoreVersionMap . get ( project ) ;
893911 if ( angularCoreVersion === undefined ) {
894912 angularCoreVersion = resolve ( '@angular/core' , coreDts ) ?. version ;
@@ -901,37 +919,6 @@ export class Session {
901919 } ) ;
902920 }
903921 }
904-
905- /**
906- * Determine if the specified `project` is Angular, and disable the language
907- * service if not.
908- *
909- * @returns The `ts.server.NormalizedPath` to the `@angular/core/core.d.ts` file.
910- */
911- private checkIsAngularProject ( project : ts . server . Project ) : ts . server . NormalizedPath | undefined {
912- const { projectName} = project ;
913- const NG_CORE = '@angular/core/core.d.ts' ;
914- const ngCoreDts = project . getFileNames ( ) . find ( f => f . endsWith ( NG_CORE ) ) ;
915- const isAngularProject =
916- project . hasRoots ( ) && ! project . isNonTsProject ( ) && ngCoreDts !== undefined ;
917-
918- if ( isAngularProject ) {
919- return ngCoreDts ;
920- }
921-
922- project . disableLanguageService ( ) ;
923- this . info (
924- `Disabling language service for ${ projectName } because it is not an Angular project ` +
925- `('${ NG_CORE } ' could not be found). ` +
926- `If you believe you are seeing this message in error, please reinstall the packages in your package.json.` ) ;
927-
928- if ( project . getExcludedFiles ( ) . some ( f => f . endsWith ( NG_CORE ) ) ) {
929- this . info (
930- `Please check your tsconfig.json to make sure 'node_modules' directory is not excluded.` ) ;
931- }
932-
933- return undefined ;
934- }
935922}
936923
937924function toArray < T > ( it : ts . Iterator < T > ) : T [ ] {
@@ -942,6 +929,19 @@ function toArray<T>(it: ts.Iterator<T>): T[] {
942929 return results ;
943930}
944931
932+ // TODO: Replace with `isNgLanguageService` from `@angular/language-service`.
945933function isNgLs ( ls : ts . LanguageService | NgLanguageService ) : ls is NgLanguageService {
946934 return 'getTcb' in ls ;
947- }
935+ }
936+
937+ function isAngularCore ( path : string ) : boolean {
938+ return isExternalAngularCore ( path ) || isInternalAngularCore ( path ) ;
939+ }
940+
941+ function isExternalAngularCore ( path : string ) : boolean {
942+ return path . endsWith ( '@angular/core/core.d.ts' ) ;
943+ }
944+
945+ function isInternalAngularCore ( path : string ) : boolean {
946+ return path . endsWith ( 'angular2/rc/packages/core/index.d.ts' ) ;
947+ }
0 commit comments