Skip to content
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

Remove as assertions, or fix to mitigate the risk of silent failure #158

Merged
merged 8 commits into from
Oct 17, 2023

Conversation

MajorLift
Copy link
Contributor

@MajorLift MajorLift commented Oct 13, 2023

Explanation

as assertions intended to exclude an empty/nullable type can be replaced by a nullish coalescing operator converting nullable values into an acceptable empty value that doesn't pollute the variable's type signature.

Sometimes TypeScript's type narrowing and inference doesn't fully work quite like it should. Even in these cases, using as assertions should be avoided.

It may seem like we know for certain what the type should be and there's no risk of it breaking, but there's always the possibility that some combination of changes elsewhere might affect the typing, in which case as will suppress the alerts TypeScript would otherwise provide us with. This may lead to code breaking silently.

In this example, adding type guards and null checks -- even if they're redundant -- is a small cost to avoid silent failure.

If anything changes in the typing of ChangeCategory or unreleasedChanges that make them inconsistent, this code will fail silently. Relying on type inference instead of explicit type casting will make this logic a little less brittle.

References

- `as` assertions intended to exclude an empty/nullable type can be replaced by a nullish coalescing operator converting nullable values into an acceptable empty value that doesn't pollute the variable's type signature.
- TODO: document and expound in MetaMask/contributor-docs#47
Sometimes TypeScript's type narrowing and inference doesn't fully work quite like it should. Even in these cases, using `as` assertions should be avoided.

It may seem like we know for certain what the type should be and there's no risk of it breaking, but there's always the possibility that some combination of changes elsewhere might affect the typing, in which case `as` will suppress the alerts TypeScript would otherwise provide us with. This may lead to code breaking silently.

In this case, adding type guards and null checks -- even if they're redundant -- is preferable to the above scenario.

- TODO: document and expound in MetaMask/contributor-docs#47
If anything changes in the typing of `ChangeCategory` or `unreleasedChanges` that make them inconsistent, this code will fail silently. Relying on type inference instead of explicit type casting will make these line a little less brittle.

- TODO: document and expound in MetaMask/contributor-docs#47
@MajorLift MajorLift added the enhancement New feature or request label Oct 13, 2023
@MajorLift MajorLift self-assigned this Oct 13, 2023
@MajorLift MajorLift requested a review from a team as a code owner October 13, 2023 21:34
mcmire
mcmire previously approved these changes Oct 16, 2023
Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion here that could remove all of the typecasts, but it is non-blocking. Thanks for doing this.

src/changelog.ts Outdated Show resolved Hide resolved
// Typecast: currentVersion will be defined here due to type guard at the
// top of this function.
changelog.addRelease({ version: currentVersion as Version });
changelog.addRelease({ version: currentVersion });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is referring to this code at the top of the function:

if (isReleaseCandidate && !currentVersion) {
  throw new Error(
    `A version must be specified if 'isReleaseCandidate' is set.`,
  );
}

Since we're checking for isReleaseCandidate here, there's no way that currentVersion is not set. Hence why I believe that the typecast was added originally. (Why TypeScript doesn't see this, I'm not sure.)

This change doesn't hurt, but I wanted to make sure you saw that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consolidated the conditional branches so that we can just rely on type narrowing here: 033cd4e. The tradeoff is that the errors now throw a bit later than they strictly should, but the overhead from that seems minimal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with these changes we are now always shelling out a couple of times to gather commit hashes and commits regardless of the arguments given. I gather that the current changes were motivated by principle, that generally, if there are invalid inputs, the user wants to know about it right away rather than have to wait a bit. I agree, though, that this doesn't matter, at least right now, so these changes seem fine.

…branches

- more consistent logic for `isReleaseCandidate` null check
- wasted processing overhead due to errors throwing later than strictly necessary
@kanthesha kanthesha requested a review from mcmire October 16, 2023 19:02
}
if (mostRecentTag === `${tagPrefixes[0]}${currentVersion ?? ''}`) {
throw new Error(
`Current version already has tag, which is unexpected for a release candidate.`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about, adding the tag value part of the error message?

Copy link
Contributor Author

@MajorLift MajorLift Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean something like this?

Suggested change
`Current version already has tag, which is unexpected for a release candidate.`,
`Current version already has a tag: '${mostRecentTag}'. This is unexpected for a release candidate.`,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kanthesha Let me know if a26b0bb looks good!

Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@MajorLift MajorLift merged commit 4804d3e into main Oct 17, 2023
19 checks passed
@MajorLift MajorLift deleted the 231013-remove-or-fix-as-assertions branch October 17, 2023 14:50
legobeat added a commit to legobeat/auto-changelog that referenced this pull request Oct 23, 2023
- Was added as devDependency while importing in runtime in MetaMask#158.
legobeat added a commit that referenced this pull request Oct 23, 2023
- Was added as devDependency while importing in runtime in #158.
MajorLift added a commit that referenced this pull request Dec 8, 2023
- `updateChangelog` returns results even if `isReleaseCandidate` is false
MajorLift added a commit that referenced this pull request Dec 8, 2023
- `updateChangelog` returns results even if `isReleaseCandidate` is false
MajorLift added a commit that referenced this pull request Dec 11, 2023
- `updateChangelog` returns results even if `isReleaseCandidate` is false
MajorLift added a commit that referenced this pull request Feb 8, 2024
- `updateChangelog` returns results even if `isReleaseCandidate` is false
MajorLift added a commit that referenced this pull request Feb 8, 2024
## Motivation

#158 incorrectly refactored the branches in updateChangelog, resulting in a state where new changelog entries weren't added to the return value if isReleaseCandidate is false.

## Explanation

- This error is fixed by moving the logic for retrieving new entries out of the if (isReleaseCandidate) block.
- The code for retrieving new entries is also extracted into its own method: getNewChangeEntries, abstracting away details that obscured the flow of the method's core logic.
- To verify that the buggy logic is correctly restored, it's necessary to compare the current state of updateChangelog to its state before the bug was introduced. For this, see: [diff link](https://github.com/MetaMask/auto-changelog/compare/e8df1ec717f534c8fe84c46ea86a847fa5a32973..da39a58a55571cf4e8a03e28096aa12c02c75f77#diff-4228e8302e41dd1c51e813af8368efdcb40b3d9db3e3f21f417ae87275d9f389R249-R316).

## References

- Closes #180
- Followed by #188
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants