8000 chore: switch to guts for typescript types generation by Emyrk · Pull Request #15801 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

chore: switch to guts for typescript types generation #15801

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

Merged
merged 20 commits into from
Dec 11, 2024
Merged

Conversation

Emyrk
Copy link
Member
@Emyrk Emyrk commented Dec 9, 2024

What this does

Hopefully nothing. This moves to using guts for generating our typescript types. Our typescript generator has been made a package, with opinions removed.

Some changes

Explicit omitempty because undefined and null are different. Now it must be explicit. Added omitempty to some types to be consistent with how we were using them, used null for others.

Future work

Future PRs should fix when we use omitempty and pointers to distinguish null or ? in the typescript. The current code types seem arbitrary and do not distinguish between the two. This new type-gen is more accurate.

In golang, omitempty will marshal the json as {}, while nil will marshal as {"field":null}. We should be explicit when to do omitempty and when to leave it as <type> | null. Sometimes the field existing, but being null is preferred. We should migrate to null when we always want the field to exist.

I will not do this in this PR, but will make follow ups to fix certain areas over time.

Bug

Fixed if you remove a user from a group, it deletes the display name.

@Emyrk Emyrk force-pushed the stevenmasley/types_gen branch from ae94a07 to 6305d44 Compare December 9, 2024 18:54
@Emyrk Emyrk marked this pull request as ready for review December 9, 2024 22:00
@Emyrk Emyrk requested review from f0ssel and Parkreiner December 10, 2024 13:55
Copy link
Contributor
@f0ssel f0ssel left a comment

Choose a reason for hiding this comment

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

Go part is mostly what I reviewed, looks pretty simple since everything is in guts now, lgtm

# Future Ideas

- Use a yaml config for overriding certain types
Uses it's own `go.mod` to exclude goja deps from the main go.mod.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this matter in practice?

Copy link
Member Author

Choose a reason for hiding this comment

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

My understanding of go.mod isn't complete, but I did not want anyone importing coder/coder to see goja as an indirect dependency.

Doing it this way prevents goja from ever appearing in any go.mod/go.sum related to coder/coder. Given it's a JS runtime in Go, feels like a jank dep to include.

Comment on lines +1741 to +1754
display_name: null,
avatar_url: null,
quota_allowance: null,
});
};

removeMember = async (groupId: string, userId: string) => {
return this.patchGroup(groupId, {
name: "",
display_name: "",
add_users: [],
remove_users: [userId],
display_name: null,
avatar_url: null,
quota_allowance: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should these use omitempty instead of setting null?

Copy link
Member Author

Choose a reason for hiding this comment

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

I talked to @Parkreiner about this, and we have to do some overhauling of our types when it comes to omitempty vs null.

For requests, maybe omitempty is easier to use, but null forces the user to acknowledge the fields exist. This actually raised a group member bug.

This is very similar to what we did in our Golang linter for structs: #8403

For most objects returned by Golang, omitempty is incorrect, as we usually have a *string or some pointer to something. Go json marshals this as null, and the previous typescript code would treat the type as string | undefined, but at runtime the type is null. So it was just incorrect, however the truthy value is consistent.


tl;dr: We've been using undefined and null incorrectly in our typescript types, and it's too much to fix in this PR. I just had to make some changes to make the typescript compiler happy, even though nothing has functionally changed.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think the ?: syntax has better ergonomics in React component props, but in all other cases, having the explicit null case is better for making sure we don't have invalid/malformed requests

I'd rather we overhaul things so that we have to deal with explicit null cases for anything involving an API call

@Emyrk Emyrk merged commit 077e594 into main Dec 11, 2024
32 of 33 checks passed
@Emyrk Emyrk deleted the stevenmasley/types_gen branch December 11, 2024 20:39
@github-actions github-actions bot locked and limited conversation to collaborators Dec 11, 2024
Copy link
Member
@Parkreiner Parkreiner left a comment

Choose a reason for hiding this comment

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

I think there's a little bit of cleanup to do, but I think that this is perfectly fine. A lot of the comments I'm leaving are more for me to make sure I don't miss anything when I try to go in and fix things up later on

readonly dynamic: Fi 8000 elds<boolean, A, string, S>;
readonly comparable: boolean;
}
export type Custom = string | boolean | number | number | string[] | (number | null);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is a huge deal, but the TypeScript output here looks a little bit jank. I'm guessing the (number | null) is from a nullable int in Go, but the type number | (number | null) collapses down to just number | null

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 I can definitely unest these

readonly Slice: Readonly<Array<R>>;
readonly TwoD: Readonly<Array<Readonly<Array<R>>>>;
readonly Slice: readonly R[];
readonly TwoD: readonly R[][];
Copy link
Member

Choose a reason for hiding this comment

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

Can't remember if we talked about this being out of scope for this PR, but just in case, this 2D array isn't deeply readonly. It's a readonly array of mutable R arrays. The fully safe type would have to be readonly (readonly R[])[] (as ugly/clunky as it is)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, I need to recursively apply it 😢

@@ -150,7 +152,8 @@ export async function verifyConfigFlagArray(
);

// Verify array of options with simple dots
for (const item of opt.value) {
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
for (const item of opt.value as any) {
Copy link
Member

Choose a reason for hiding this comment

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

Kind of wish we could've at least written the expression as const item of opt.value as any[], to get some degree of LSP feedback. I don't think it's a huge deal, since this is a test file, but yeah, we'll definitely need to clean up our types later on

@@ -166,7 +169,8 @@ export async function verifyConfigFlagEntries(
);

// Verify array of options with green marks.
Object.entries(opt.value)
// biome-ignore lint/suspicious/noExplicitAny: opt.value is any
Object.entries(opt.value as any)
Copy link
Member

Choose a reason for hiding this comment

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

This whole expression is a bit janky. I'll see if I can't clean up some if it during the two work days we have during Christmas break

Comment on lines +1741 to +1754
display_name: null,
avatar_url: null,
quota_allowance: null,
});
};

removeMember = async (groupId: string, userId: string) => {
return this.patchGroup(groupId, {
name: "",
display_name: "",
add_users: [],
remove_users: [userId],
display_name: null,
avatar_url: null,
quota_allowance: null,
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think the ?: syntax has better ergonomics in React component props, but in all other cases, having the explicit null case is better for making sure we don't have invalid/malformed requests

I'd rather we overhaul things so that we have to deal with explicit null cases for anything involving an API call

@@ -85,7 +85,11 @@ export const DERPPage: FC = () => {
<section>
<SectionLabel>Regions</SectionLabel>
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
{Object.values(regions)
{Object.values(regions!)
Copy link
Member

Choose a reason for hiding this comment

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

Not a huge problem, but this ! isn't doing anything. At least from the type level, regions itself can't be nullish

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0