-
Notifications
You must be signed in to change notification settings - Fork 3
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
Implement streamed input with tsc -w
capacity
#15
base: master
Are you sure you want to change the base?
Conversation
Thanks for the PR! I appreciate you also adding tests 🎉 I will take a closer look at the code tomorrow. Just eyeballing the diff from that single commit, I see it includes some stylistic changes (adding dots at the end of logs/error messages) with functional changes (adding stdin streaming). Could you split it into 2 commits, so the commits are more atomic? Moreover, I would appreciate if you followed the conventional commits convention I have been using in the project. That would make those commit messages fit better |
Happy to make those changes :) |
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 am almost done with the review. I have just a handful of files to review. I will finish it tomorrow or on Wednesday. Overall, great work! I particularly like the idea to use flags to compress the 4 possible variants of an error 🚀
I added some mostly-minor comments. The comments I left earlier still apply (but don't worry, there is no pressure to address them in a time-sensitive way)
); | ||
}; | ||
|
||
export default updateConfigFiles; |
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.
This project uses named exports. Could you use named exports in the code that you add as well? I want to maintain consistency in the project.
) => { | ||
updateLooselyTypeCheckedFilePaths( | ||
cliDependencies, | ||
new Set(looselyTypeCheckedFilePaths), |
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.
Minor:
Looks like we should be able to avoid the unnecessary set copying
new Set(looselyTypeCheckedFilePaths), | |
looselyTypeCheckedFilePaths, |
|
||
if (tscError !== this.lastTscError) { | ||
const errorClassification = classifyTscError( | ||
tscError as TscError, |
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.
We could avoid a potentially-unsafe type assertion by adding a && tscError !== undefined
in the condition of the if
above. I don't think it would hurt the if
in any way, and it would make it possible to remove the 2 as
type assertions here and 5 lines lower, which I'm always happy to do
const errorCodeMatch = ignoredErrorCodesSet.has(tscError.tscErrorCode); | ||
const filePathMatch = filePathMatcher.matches(tscError.filePath); | ||
|
||
let matchFlags = TscErrorMatchFlags.filePath & TscErrorMatchFlags.errorCode; |
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.
Very clever way of using bit flags instead of having 4 separate buckets like I had before 🎉
Minor:
Could you set this to 0
explicitly? I had to think a bit why this is initialized the way it is, and then we use bitwise or. Only after a moment it occurred to me that this is simply a verbose way of initializing this to 0
let matchFlags = TscErrorMatchFlags.filePath & TscErrorMatchFlags.errorCode; | |
let matchFlags = 0; |
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 agree that it's a bit cryptic. I considered a none = 0
flag but ultimately leaned away from it because it was more of a pseudo-flag than an actual flag. I can resurrect that or find a better way to indicate the absence of any flags. Setting it to 0
explicitly works for me as well.
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.
IMO a none = 0
case works well. Alternatively, add a const noMatchFlags = 0;
next to the enum definition and use that as the initial value here. I reckon that would be less surprising and would also link the 2 parts of code together
); | ||
|
||
expect(errorClassification).toBe( | ||
TscErrorMatchFlags.errorCode & TscErrorMatchFlags.filePath, |
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 you just say 0
instead of a bitwise and of 2 values that compute to 0
? IMO just 0
is less confusing and less typo prone
TscErrorMatchFlags.errorCode & TscErrorMatchFlags.filePath, | |
0, |
@@ -0,0 +1,65 @@ | |||
import WatchModeSpy from './watch-mode-spy'; | |||
|
|||
describe('watchModeSpy', () => { |
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.
Regarding writing integration tests, you could add such integration tests in jest, if you feel like it. It would involve:
- Creating a new test temporary directory
- Starting a new
tsc
process in watch mode
and then, depending on the level of integration you want the tests to be at, you could either:
-
Read the stdout of that
tsc
process and invoke correspondingWatchModeSpy
methods, making assertions such as:- Initially, it should
detectWatchModeCompilationStartOutput
anddetectWatchModeCompilationFinishedOutput
- After the code modifies a file in the filesystem, it should
detectWatchModeFileChangeDetectedOutput
and thendetectWatchModeCompilationFinishedOutput
This sounds testable. It tests
WatchModeSpy
kind of in isolation from the rest of the program, but that is ok. It gives us confidence that this part works, and the rest of the program is tested with existing integration tests. - Initially, it should
-
Pass the output directly to
program
and make assertions about theprogram
's outputThis is more involved, gives us more confidence that the entire tool works end-to-end. If there are problems, it will be very coarse and not give us precise signs where the root of the problem is, just that something is broken.
I believe testing WatchModeSpy
in isolation against the output of tsc -w
is good enough.
It is also fine if you want to skip adding these tests in this PR. They can be added later, either by me or by you, or by some other contributor
Appreciate the review comments. I'll implement the changes sometime this week and also adjust the commits to comply with repo standards. |
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've reviewed the entire PR. I will say that again, great job! I have yet to run it myself to assess how it works for me with watch mode. It looks very promising, though!
I want to emphasize that the review would be much easier if the changes that extract unmodified existing parts of the code were separated from the commits that add new features or modify the existing logic. Having these in individual commits would be easier to see how the changes are related to one another. Right now when I was reviewing the code I had to pay close attention to the methods like generateReport
or parseTscLine
to see if this is new code or existing code in a different packaging.
I'm saying that to make you aware how it looks from the reviewer's perspective
I'm looking forward to seeing more changes in this PR
class ErrorStore { | ||
private tscErrors: Map<TscErrorMatchFlags, TscError[]> = new Map(); | ||
|
||
public getAllErrors(): TscError[] { |
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.
This method seems to only be used in the tests. If we don't need it in production code, let's remove it
return errors; | ||
} | ||
|
||
public getValidErrors(): TscError[] { |
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.
Minor:
All errors are valid, but some are ignored, and some are not ignored. Can we use the name
public getValidErrors(): TscError[] { | |
public getUnignoredErrors(): TscError[] { |
or use some other word to avoid the confusion around calling them valid errors 😅
I know this probably was the name used in my initial implementation, but this occurred to me as I was reading this code. Feel free to skip this suggestion
this.tscErrors.get(errorMatchFlags)?.push(error) || | ||
this.tscErrors.set(errorMatchFlags, [error]); |
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.
Quite clever syntax for pushing to an array with a fallback when the key does not exist. I like it 👍
expect(errorStore.getCouldBeIgnoredErrors()).toHaveLength(1); | ||
}); | ||
|
||
it('should reset buckets.', () => { |
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.
Minor:
You may as well combine these 2 tests by adding the reset
operation at the end of the previous test and asserting buckets are empty. I'm suggesting this because these tests share the same setup code, so they share quite a bit of assumtpions
import { TscError, TscErrorMatchFlags } from './types'; | ||
|
||
class ErrorStore { | ||
private tscErrors: Map<TscErrorMatchFlags, TscError[]> = new Map(); |
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.
Minor:
May as well throw a readonly
here to prevent accidental reassigning of this property
These changes introduce input streaming from
tsc
output with the ability to handletsc -w
. Description of major file changes/additions:index.ts
readline
interface and delegates to program.ts for event handlingprogram.ts
line
andclose
eventserror-store.ts
watch-mode.spy.ts
tsc
output looking for specific lines to determine if we're in watch modeclassify-tsc-error.ts
update-config-files.ts
process-compilation-results.ts
tsc
compilingOther touches of files are mainly adding periods to the end of lines. Sorry for the PR pollution!