55 logger ,
66 parseJson ,
77 readNxJson ,
8- type ExpandedPluginConfiguration ,
98 type ProjectGraph ,
109 type ProjectGraphProjectNode ,
1110 type Tree ,
@@ -18,10 +17,6 @@ import {
1817 type SyncGeneratorResult ,
1918} from 'nx/src/utils/sync-generators' ;
2019import * as ts from 'typescript' ;
21- import {
22- PLUGIN_NAME ,
23- type TscPluginOptions ,
24- } from '../../plugins/typescript/plugin' ;
2520
2621interface Tsconfig {
2722 references ?: Array < { path : string } > ;
@@ -55,8 +50,9 @@ type TsconfigInfoCaches = {
5550 composite : Map < string , boolean > ;
5651 content : Map < string , string > ;
5752 exists : Map < string , boolean > ;
58- isFile : Map < string , boolean > ;
5953} ;
54+ type ChangedFileDetails = { missing : Set < string > ; stale : Set < string > } ;
55+ type ChangeType = keyof ChangedFileDetails ;
6056
6157export async function syncGenerator ( tree : Tree ) : Promise < SyncGeneratorResult > {
6258 // Ensure that the plugin has been wired up in nx.json
@@ -66,7 +62,6 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
6662 composite : new Map ( ) ,
6763 content : new Map ( ) ,
6864 exists : new Map ( ) ,
69- isFile : new Map ( ) ,
7065 } ;
7166 // Root tsconfig containing project references for the whole workspace
7267 const rootTsconfigPath = 'tsconfig.json' ;
@@ -120,23 +115,26 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
120115 // made by this generator to know if the TS config is out of sync with the
121116 // project graph. Therefore, we don't format the files if there were no changes
122117 // to avoid potential format-only changes that can lead to false positives.
123- let hasChanges = false ;
118+ const changedFiles = new Map < string , ChangedFileDetails > ( ) ;
124119
125120 if ( tsconfigProjectNodeValues . length > 0 ) {
126121 const referencesSet = new Set < string > ( ) ;
127122 for ( const ref of rootTsconfig . references ?? [ ] ) {
128123 // reference path is relative to the tsconfig file
129124 const resolvedRefPath = getTsConfigPathFromReferencePath (
130- tree ,
131125 rootTsconfigPath ,
132- ref . path ,
133- tsconfigInfoCaches
126+ ref . path
134127 ) ;
135128 if ( tsconfigExists ( tree , tsconfigInfoCaches , resolvedRefPath ) ) {
136129 // we only keep the references that still exist
137130 referencesSet . add ( normalizeReferencePath ( ref . path ) ) ;
138131 } else {
139- hasChanges = true ;
132+ addChangedFile (
133+ changedFiles ,
134+ rootTsconfigPath ,
135+ resolvedRefPath ,
136+ 'stale'
137+ ) ;
140138 }
141139 }
142140
@@ -145,11 +143,16 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
145143 // Skip the root tsconfig itself
146144 if ( node . data . root !== '.' && ! referencesSet . has ( normalizedPath ) ) {
147145 referencesSet . add ( normalizedPath ) ;
148- hasChanges = true ;
146+ addChangedFile (
147+ changedFiles ,
148+ rootTsconfigPath ,
149+ toFullProjectReferencePath ( node . data . root ) ,
150+ 'missing'
151+ ) ;
149152 }
150153 }
151154
152- if ( hasChanges ) {
155+ if ( changedFiles . size > 0 ) {
153156 const updatedReferences = Array . from ( referencesSet )
154157 // Check composite is true in the internal reference before proceeding
155158 . filter ( ( ref ) =>
@@ -181,7 +184,7 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
181184 } ;
182185
183186 const collectedDependencies = new Map < string , ProjectGraphProjectNode [ ] > ( ) ;
184- for ( const [ projectName , data ] of Object . entries ( projectGraph . dependencies ) ) {
187+ for ( const projectName of Object . keys ( projectGraph . dependencies ) ) {
185188 if (
186189 ! projectGraph . nodes [ projectName ] ||
187190 projectGraph . nodes [ projectName ] . data . root === '.'
@@ -224,39 +227,55 @@ export async function syncGenerator(tree: Tree): Promise<SyncGeneratorResult> {
224227 }
225228
226229 // Update project references for the runtime tsconfig
227- hasChanges =
228- updateTsConfigReferences (
229- tree ,
230- tsSysFromTree ,
231- tsconfigInfoCaches ,
232- runtimeTsConfigPath ,
233- dependencies ,
234- sourceProjectNode . data . root ,
235- projectRoots ,
236- runtimeTsConfigFileName ,
237- runtimeTsConfigFileNames
238- ) || hasChanges ;
239- }
240-
241- // Update project references for the tsconfig.json file
242- hasChanges =
243230 updateTsConfigReferences (
244231 tree ,
245232 tsSysFromTree ,
246233 tsconfigInfoCaches ,
247- sourceProjectTsconfigPath ,
234+ runtimeTsConfigPath ,
248235 dependencies ,
249236 sourceProjectNode . data . root ,
250- projectRoots
251- ) || hasChanges ;
237+ projectRoots ,
238+ changedFiles ,
239+ runtimeTsConfigFileName ,
240+ runtimeTsConfigFileNames
241+ ) ;
242+ }
243+
244+ // Update project references for the tsconfig.json file
245+ updateTsConfigReferences (
246+ tree ,
247+ tsSysFromTree ,
248+ tsconfigInfoCaches ,
249+ sourceProjectTsconfigPath ,
250+ dependencies ,
251+ sourceProjectNode . data . root ,
252+ projectRoots ,
253+ changedFiles
254+ ) ;
252255 }
253256
254- if ( hasChanges ) {
257+ if ( changedFiles . size > 0 ) {
255258 await formatFiles ( tree ) ;
256259
260+ const outOfSyncDetails : string [ ] = [ ] ;
261+ for ( const [ filePath , details ] of changedFiles ) {
262+ outOfSyncDetails . push ( `${ filePath } :` ) ;
263+ if ( details . missing . size > 0 ) {
264+ outOfSyncDetails . push (
265+ ` - Missing references: ${ Array . from ( details . missing ) . join ( ', ' ) } `
266+ ) ;
267+ }
268+ if ( details . stale . size > 0 ) {
269+ outOfSyncDetails . push (
270+ ` - Stale references: ${ Array . from ( details . stale ) . join ( ', ' ) } `
271+ ) ;
272+ }
273+ }
274+
257275 return {
258276 outOfSyncMessage :
259- 'Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.' ,
277+ 'Some TypeScript configuration files are missing project references to the projects they depend on or contain stale project references.' ,
278+ outOfSyncDetails,
260279 } ;
261280 }
262281}
@@ -306,9 +325,10 @@ function updateTsConfigReferences(
306325 dependencies : ProjectGraphProjectNode [ ] ,
307326 projectRoot : string ,
308327 projectRoots : Set < string > ,
328+ changedFiles : Map < string , ChangedFileDetails > ,
309329 runtimeTsConfigFileName ?: string ,
310330 possibleRuntimeTsConfigFileNames ?: string [ ]
311- ) : boolean {
331+ ) : void {
312332 const stringifiedJsonContents = readRawTsconfigContents (
313333 tree ,
314334 tsconfigInfoCaches ,
@@ -334,15 +354,11 @@ function updateTsConfigReferences(
334354
335355 // reference path is relative to the tsconfig file
336356 const resolvedRefPath = getTsConfigPathFromReferencePath (
337- tree ,
338357 tsConfigPath ,
339- ref . path ,
340- tsconfigInfoCaches
358+ ref . path
341359 ) ;
342360 if (
343361 isProjectReferenceWithinNxProject (
344- tree ,
345- tsconfigInfoCaches ,
346362 resolvedRefPath ,
347363 projectRoot ,
348364 projectRoots
@@ -353,6 +369,10 @@ function updateTsConfigReferences(
353369 references . push ( ref ) ;
354370 newReferencesSet . add ( normalizedPath ) ;
355371 }
372+
373+ if ( ! newReferencesSet . has ( normalizedPath ) ) {
374+ addChangedFile ( changedFiles , tsConfigPath , resolvedRefPath , 'stale' ) ;
375+ }
356376 }
357377
358378 let hasChanges = false ;
@@ -441,6 +461,12 @@ function updateTsConfigReferences(
441461 }
442462 if ( ! originalReferencesSet . has ( relativePathToTargetRoot ) ) {
443463 hasChanges = true ;
464+ addChangedFile (
465+ changedFiles ,
466+ tsConfigPath ,
467+ toFullProjectReferencePath ( referencePath ) ,
468+ 'missing'
469+ ) ;
444470 }
445471 }
446472
@@ -454,8 +480,6 @@ function updateTsConfigReferences(
454480 references
455481 ) ;
456482 }
457-
458- return hasChanges ;
459483}
460484
461485// TODO(leo): follow up with the TypeScript team to confirm if we really need
@@ -522,18 +546,20 @@ function normalizeReferencePath(path: string): string {
522546 . replace ( / ^ \. \/ / , '' ) ;
523547}
524548
549+ function toFullProjectReferencePath ( path : string ) : string {
550+ const normalizedPath = normalizeReferencePath ( path ) ;
551+
552+ return normalizedPath . endsWith ( '.json' )
553+ ? normalizedPath
554+ : joinPathFragments ( normalizedPath , 'tsconfig.json' ) ;
555+ }
556+
525557function isProjectReferenceWithinNxProject (
526- tree : Tree ,
527- tsconfigInfoCaches : TsconfigInfoCaches ,
528558 refTsConfigPath : string ,
529559 projectRoot : string ,
530560 projectRoots : Set < string >
531561) : boolean {
532- let currentPath = getTsConfigDirName (
533- tree ,
534- tsconfigInfoCaches ,
535- refTsConfigPath
536- ) ;
562+ let currentPath = getTsConfigDirName ( refTsConfigPath ) ;
537563
538564 if ( relative ( projectRoot , currentPath ) . startsWith ( '..' ) ) {
539565 // it's outside of the project root, so it's an external project reference
@@ -568,28 +594,22 @@ function isProjectReferenceIgnored(
568594 return ig . ignores ( refTsConfigPath ) ;
569595}
570596
571- function getTsConfigDirName (
572- tree : Tree ,
573- tsconfigInfoCaches : TsconfigInfoCaches ,
574- tsConfigPath : string
575- ) : string {
576- return tsconfigIsFile ( tree , tsconfigInfoCaches , tsConfigPath )
597+ function getTsConfigDirName ( tsConfigPath : string ) : string {
598+ return tsConfigPath . endsWith ( '.json' )
577599 ? dirname ( tsConfigPath )
578600 : normalize ( tsConfigPath ) ;
579601}
580602
581603function getTsConfigPathFromReferencePath (
582- tree : Tree ,
583604 ownerTsConfigPath : string ,
584- referencePath : string ,
585- tsconfigInfoCaches : TsconfigInfoCaches
605+ referencePath : string
586606) : string {
587607 const resolvedRefPath = joinPathFragments (
588608 dirname ( ownerTsConfigPath ) ,
589609 referencePath
590610 ) ;
591611
592- return tsconfigIsFile ( tree , tsconfigInfoCaches , resolvedRefPath )
612+ return resolvedRefPath . endsWith ( '.json' )
593613 ? resolvedRefPath
594614 : joinPathFragments ( resolvedRefPath , 'tsconfig.json' ) ;
595615}
@@ -640,22 +660,15 @@ function hasCompositeEnabled(
640660 return tsconfigInfoCaches . composite . get ( tsconfigPath ) ;
641661}
642662
643- function tsconfigIsFile (
644- tree : Tree ,
645- tsconfigInfoCaches : TsconfigInfoCaches ,
646- tsconfigPath : string
647- ) : boolean {
648- if ( tsconfigInfoCaches . isFile . has ( tsconfigPath ) ) {
649- return tsconfigInfoCaches . isFile . get ( tsconfigPath ) ;
650- }
651-
652- if ( tsconfigInfoCaches . content . has ( tsconfigPath ) ) {
653- // if it has content, it's a file
654- tsconfigInfoCaches . isFile . set ( tsconfigPath , true ) ;
655- return true ;
663+ function addChangedFile (
664+ changedFiles : Map < string , ChangedFileDetails > ,
665+ filePath : string ,
666+ referencePath : string ,
667+ type : ChangeType
668+ ) {
669+ if ( ! changedFiles . has ( filePath ) ) {
670+ changedFiles . set ( filePath , { missing : new Set ( ) , stale : new Set ( ) } ) ;
656671 }
657672
658- tsconfigInfoCaches . isFile . set ( tsconfigPath , tree . isFile ( tsconfigPath ) ) ;
659-
660- return tsconfigInfoCaches . isFile . get ( tsconfigPath ) ;
673+ changedFiles . get ( filePath ) [ type ] . add ( referencePath ) ;
661674}
0 commit comments