@@ -354,7 +354,7 @@ internal static async ValueTask<bool> HasChangedOrAddedDocumentsAsync(Project ol
354354 foreach ( var documentId in newProject . State . DocumentStates . GetChangedStateIds ( oldProject . State . DocumentStates , ignoreUnchangedContent : true ) )
355355 {
356356 var document = newProject . GetRequiredDocument ( documentId ) ;
357- if ( document . State . Attributes . DesignTimeOnly )
357+ if ( ! document . State . SupportsEditAndContinue ( ) )
358358 {
359359 continue ;
360360 }
@@ -375,7 +375,7 @@ internal static async ValueTask<bool> HasChangedOrAddedDocumentsAsync(Project ol
375375 foreach ( var documentId in newProject . State . DocumentStates . GetAddedStateIds ( oldProject . State . DocumentStates ) )
376376 {
377377 var document = newProject . GetRequiredDocument ( documentId ) ;
378- if ( document . State . Attributes . DesignTimeOnly )
378+ if ( ! document . State . SupportsEditAndContinue ( ) )
379379 {
380380 continue ;
381381 }
@@ -827,6 +827,30 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
827827 using var _5 = ArrayBuilder < Document > . GetInstance ( out var changedOrAddedDocuments ) ;
828828 using var _6 = ArrayBuilder < ( DocumentId , ImmutableArray < RudeEditDiagnostic > ) > . GetInstance ( out var documentsWithRudeEdits ) ;
829829 using var _7 = ArrayBuilder < ProjectId > . GetInstance ( out var projectsToStale ) ;
830+
831+ // After all projects have been analyzed "true" value indicates changed document that is only included in stale projects.
832+ var changedDocumentsStaleness = new Dictionary < string , bool > ( SolutionState . FilePathComparer ) ;
833+
834+ void UpdateChangedDocumentsStaleness ( bool isStale )
835+ {
836+ foreach ( var changedDocument in changedOrAddedDocuments )
837+ {
838+ var path = changedDocument . FilePath ;
839+
840+ // Only documents that support EnC (have paths) are added to the list.
841+ Contract . ThrowIfNull ( path ) ;
842+
843+ if ( isStale )
844+ {
845+ _ = changedDocumentsStaleness . TryAdd ( path , true ) ;
846+ }
847+ else
848+ {
849+ changedDocumentsStaleness [ path ] = false ;
850+ }
851+ }
852+ }
853+
830854 Diagnostic ? syntaxError = null ;
831855
832856 var oldSolution = DebuggingSession . LastCommittedSolution ;
@@ -860,13 +884,6 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
860884 continue ;
861885 }
862886
863- // We don't track document changes in stale projects until they are rebuilt (removed from stale set).
864- if ( oldSolution . IsStaleProject ( newProject . Id ) )
865- {
866- Log . Write ( $ "EnC state of { newProject . Name } '{ newProject . FilePath } ' queried: project is stale") ;
867- continue ;
868- }
869-
870887 await PopulateChangedAndAddedDocumentsAsync ( Log , oldProject , newProject , changedOrAddedDocuments , diagnostics , cancellationToken ) . ConfigureAwait ( false ) ;
871888 if ( changedOrAddedDocuments . IsEmpty )
872889 {
@@ -875,6 +892,16 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
875892
876893 Log . Write ( $ "Found { changedOrAddedDocuments . Count } potentially changed document(s) in project { newProject . Name } '{ newProject . FilePath } '") ;
877894
895+ var isStaleProject = oldSolution . IsStaleProject ( newProject . Id ) ;
896+
897+ // We don't consider document changes in stale projects until they are rebuilt (removed from stale set).
898+ if ( isStaleProject )
899+ {
900+ Log . Write ( $ "EnC state of { newProject . Name } '{ newProject . FilePath } ' queried: project is stale") ;
901+ UpdateChangedDocumentsStaleness ( isStale : true ) ;
902+ continue ;
903+ }
904+
878905 var ( mvid , mvidReadError ) = await DebuggingSession . GetProjectModuleIdAsync ( newProject , cancellationToken ) . ConfigureAwait ( false ) ;
879906 if ( mvidReadError != null )
880907 {
@@ -891,6 +918,7 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
891918 if ( mvid == Guid . Empty )
892919 {
893920 Log . Write ( $ "Changes not applied to { newProject . Name } '{ newProject . FilePath } ': project not built") ;
921+ UpdateChangedDocumentsStaleness ( isStale : true ) ;
894922 continue ;
895923 }
896924
@@ -931,10 +959,15 @@ public async ValueTask<SolutionUpdate> EmitSolutionUpdateAsync(Solution solution
931959 // The project is considered stale as long as it has at least one document that is out-of-sync.
932960 // Treat the project the same as if it hasn't been built. We won't produce delta for it until it gets rebuilt.
933961 Log . Write ( $ "Changes not applied to { newProject . Name } '{ newProject . FilePath } ': binaries not up-to-date") ;
962+
934963 projectsToStale . Add ( newProject . Id ) ;
964+ UpdateChangedDocumentsStaleness ( isStale : true ) ;
965+
935966 continue ;
936967 }
937968
969+ UpdateChangedDocumentsStaleness ( isStale : false ) ;
970+
938971 foreach ( var changedDocumentAnalysis in changedDocumentAnalyses )
939972 {
940973 if ( changedDocumentAnalysis . SyntaxError != null )
@@ -1160,6 +1193,22 @@ async ValueTask LogDocumentChangesAsync(int? generation, CancellationToken cance
11601193 }
11611194 }
11621195
1196+ // Report stale document updates.
1197+ // We report a warning when a changed/added document is only included in (linked to) stale projects.
1198+
1199+ foreach ( var ( documentPath , isStale ) in changedDocumentsStaleness )
1200+ {
1201+ if ( isStale )
1202+ {
1203+ foreach ( var documentId in solution . GetDocumentIdsWithFilePath ( documentPath ) )
1204+ {
1205+ var descriptor = EditAndContinueDiagnosticDescriptors . GetDescriptor ( EditAndContinueErrorCode . UpdatingDocumentInStaleProject ) ;
1206+ var diagnostic = Diagnostic . Create ( descriptor , Location . Create ( documentPath , textSpan : default , lineSpan : default ) , [ documentPath ] ) ;
1207+ diagnostics . Add ( new ProjectDiagnostics ( documentId . ProjectId , [ diagnostic ] ) ) ;
1208+ }
1209+ }
1210+ }
1211+
11631212 // log capabilities for edit sessions with changes or reported errors:
11641213 if ( blockUpdates || deltas . Count > 0 )
11651214 {
0 commit comments