-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Delay "File change detected" reporting until createProgram #47427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State management seems rough here. The solution seems okay, but maybe others on the team have better strategies.
If this is less desirable, an alternative change that I considered was to, in But, I feel like there are other cases that are not handled there either, e.g. consider a new file that is supported, or a new directory, but that aren't actually in the program (thanks to FWIW, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't use watch mode very often, so I may be out of touch, but I think I'd rather see "detected change... ignoring change" than nothing if it's actually doing work.
src/compiler/watchPublic.ts
Outdated
@@ -434,15 +435,22 @@ namespace ts { | |||
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution || changesAffectResolution); | |||
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { | |||
if (hasChangedConfigFileParsingErrors) { | |||
if (reportWatchDiagnosticOnCreateProgram) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this has to be true? How could the config file change without triggering a program refresh?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm mistaken, hasChangedConfigFileParsingErrors
can be true if reloadFileNamesFromConfigFile
was called, which can happen if the refresh level was ConfigFileProgramReloadLevel.Partial
(and then those call sites are outside of the watcher changes, entirely, I think). I'll remove the if and see if there are any tests for this, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like that if there's a case where hasChangedConfigFileParsingErrors
is true but reportWatchDiagnosticOnCreateProgram
is false, there's no test for it.
I can change this to unconditionally report the message, but I'm not entirely confident that there's no code path where this breaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar from this code - I'm just pointing out that, abstractly, it seems like a config file change will always be a relevant file change (i.e. will require the message to be printed).
@jakebailey Do you have a link to the kick-out that's causing the problem (i.e. preventing the second log message from being printed)? |
I believe it's TypeScript/src/compiler/watchUtilities.ts Line 503 in 28c2084
(I can check when I have a chance and I'm back at my machine) |
This is a little tricky due to how this thing is printed; it goes via |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd rather see "detected change... ignoring change" than nothing if it's actually doing work.
This is a little tricky due to how this thing is printed; it goes via
afterProgramCreate
below which is a result of theemitFilesAndReportErrors
callback.
I might be missing something obvious, but wouldn't it just be a matter of updating synchronizeProgram
to print "ignoring" in cases where this PR does not print "change detected"?
createNewProgram(hasInvalidatedResolution); | ||
} | ||
|
||
changesAffectResolution = false; // reset for next sync | ||
reportFileChangeDetectedOnCreateProgram = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be cleared in updateProgramWithWatchStatus
? I think that would make its lifetime/scope much clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can clear it there instead, but I had been modelling changesAffectResolution
.
My main concern was a case where it may be left over for another synchronize later, but I don't know why I thought that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I think it's clearer to set it and call it in the same place, but I'm also fine with following local conventions. If this is where things are cleared after sync, then go for it.
@@ -434,15 +435,22 @@ namespace ts { | |||
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution || changesAffectResolution); | |||
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { | |||
if (hasChangedConfigFileParsingErrors) { | |||
if (reportFileChangeDetectedOnCreateProgram) { | |||
reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much work is done between where this used to be reported and here? I'm concerned that it will look like the compiler is frozen if it reacts "late" to a file change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question; my skimming says that isProgramUptoDate
will:
- Compare a few arrays for equality
- Check for not-up-to-date source files, which appears to be checking a number of caches
- Check for files that the program thought were missing on disk
- Compare compiler options
- Compare tsconfig contents by text for position changes for diagnostics
I'm not sure that it's a huge amount of work.
I could try and have it unconditionally run afterProgramCreate
, but based on #37266, it seems like this was an intentional change (but, hard to tell entirely, given it's one part of a lot of other tweaks). I'll try it and see if anything breaks catastrophically, but I don't know exactly how to double check that I don't undo a perf fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we convinced ourselves that it should be cheap. I'm fine bugging this into shape if we get complaints.
It does mean it's not ignored (so the watcher will emit the event), but in this case, adding the extensionless file doesn't actually cause the program to change. I debugged and I did note the correct return statement. It occurs to me that my alternative fix (using the stat info) is likely to not work right when that same extensionless file is deleted later, since we won't have any idea if that was a directory or not that changed. |
@jakebailey's offline explanation - we can't break existing consumers by changing our output - convinced me that printing an explicit ignore message would be worse. |
@@ -84,9 +84,6 @@ Input:: | |||
//// [/a/b/tsconfig.json] deleted | |||
|
|||
Output:: | |||
>> Screen clear | |||
[[90m12:00:24 AM[0m] File change detected. Starting incremental compilation... | |||
|
|||
[91merror[0m[90m TS5083: [0mCannot read file '/a/b/tsconfig.json'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to tell in the diff here, but after this message is printed, the process exits (see exitCode below), so it's sort of pointless if this message is printed or not here other than to maybe indicate that we noticed because of a file watcher update.
This same error is printed if we start with a missing config file in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for investigating!
There are cases where the file/directory watching handler can't determine whether or not a change should be ignored or not. For files with unsupported extensions (like
.txt
), the watcher never calls the handler. However, extensionless files do cause the watcher to call the handler (among other cases).Rather than trying to perfect the watcher logic, delay printing of the "file change detected" message until we actually know we're going to create a new program (i.e. after it's determined that something important changed, and therefore a new program needs to be created).
Fixes #46949