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

add document for typescript guidelines #80

Merged
merged 6 commits into from
Mar 4, 2024
Merged

Conversation

DDDDDanica
Copy link
Contributor

@DDDDDanica DDDDDanica commented Feb 28, 2024

@DDDDDanica DDDDDanica self-assigned this Feb 28, 2024
@DDDDDanica DDDDDanica requested a review from a team as a code owner February 28, 2024 12:13
Copy link
Contributor

@NicolasMassart NicolasMassart left a comment

Choose a reason for hiding this comment

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

Nice work!
Will definitely help us.
I just pointed places where, as a new TS dev, I could be helped by some examples.

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
Copy link
Contributor

@MajorLift MajorLift left a comment

Choose a reason for hiding this comment

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

Hi, great work putting this together!

I noticed that you included my work in this (#69 (comment)). These entries are still WIP, and not intended to be published yet. I'd appreciate if you could remove them. I'll submit them when they're ready.

Also, a heads-up that my PR for #47 is almost ready for review, so any content here relating to any, type assertions, type annotations may be edited to remove duplication and make the doc read coherently.

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
@DDDDDanica
Copy link
Contributor Author

Hey @MajorLift thanks for your review, when i was taking the ticket, i just collected the points in this ticket and didn't notice your work, will remove now

Copy link
Contributor

@MajorLift MajorLift left a comment

Choose a reason for hiding this comment

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

@DDDDDanica No worries, thanks for responding quickly! I had a few more suggestions and corrections:

Comment on lines +128 to +159
#### Avoid type annotations

Type annotations act like contracts with the compiler, ensuring a variable sticks to a specific data type its entire life.

In rare cases, using type annotations with `reduce` can be beneficial. It helps tp clarify complex accumulator types, enhance readability and ensure the compiler understands its intended use.

If you need to create a data structure with specific values, but ensure that it's declared with a more abstract type. Create a function that takes an argument of the type you want, then use it to build the object.

```typescript
interface Person {
name: string;
age: number;
occupation?: string; // Optional property
}

// Function with type annotations:
function createPerson<T extends { name: string; age: number }>(
data: T,
): Person {
// Infer types from the data argument
const { name, age, occupation = 'Unknown' } = data;

// Ensure properties adhere to the Person interface
return { name, age, occupation };
}

// Usage:
const person1 = createPerson({ name: 'Alice', age: 30 }); // { name: "Alice", age: 30, occupation: "Unknown" }
const person2 = createPerson({ name: 'Bob', age: 25, occupation: 'Developer' }); // { name: "Bob", age: 25, occupation: "Developer" }
```

TypeScript is planning to introduce the `satisfies` keyword, which will provide a more flexible way to declare that a data structure conforms to a specific type without sacrificing readability or maintainability. However, this feature is not yet available.
Copy link
Contributor

Choose a reason for hiding this comment

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

Type annotations prevent inference-based narrowing of the user-supplied type. This is why they should be avoided, or at least why type inference should generally be preferred (unless supplying the annotation narrows the type further than the inferred result).

In contrast, the satisfies operator enables the user to assign a wider/abstract type to a value as a constraint, while also allowing the value's type to be further narrowed.

It lets us avoid wrapping specific types in a function to assign a wider type.

Need to create a data structure with specific values, but ensure that it's declared with a more abstract type? Create a function that takes an argument of the type you want, then use it to build the object. (#69 (comment))

const limitExceeded = () =>
    ({
      jsonrpc: '2.0',
      id: IdGenerator(),
      error: {
        code: -32005,
        message: 'Limit exceeded',
      },
    }),

I'll update this section with my PR for #47, since I already cover these points there.

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
Copy link
Contributor

@MajorLift MajorLift left a comment

Choose a reason for hiding this comment

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

Thanks for applying the suggestions! Some more notes:

Comment on lines 7 to 9
## **Type**

By defining expected object structures with interfaces, TypeScript promotes developer intent and unlocks powerful IDE features like accurate completion and context-aware navigation.

This comment was marked as resolved.

Copy link
Contributor

@MajorLift MajorLift Feb 29, 2024

Choose a reason for hiding this comment

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

Removing the second entry is also acceptable since @typescript-eslint/consistent-type-definitions does a good job of enforcing the practice without everyone having to know the reasoning behind its adoption.


(The following is just me writing out my thoughts, not aimed at you in particular)

In general, the guidelines doesn't need to cover conventions that we already enforce using linter rules or static code analysis in too much detail. This doc isn't intended as a comprehensive collection of every practice or preference we have agreed upon. The priority should be on covering principles or processes that aren't reducible to simple rulesets and rely on a deeper understanding from the developers to be effective.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for all the recommendations, i've added your suggestions, and changed the section title as Implements

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
Copy link
Contributor

@MajorLift MajorLift left a comment

Choose a reason for hiding this comment

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

Looks like we're almost there! Just one important fix and some linting issues left.

On the subject of linting, I recommend setting up the markdownlint extension in your IDE. Its ruleset is mostly consistent with our prettier config and a lot more comprehensive.

Having a shared set of conventions on how to structure and format these docs helps future collaborators add their contributions without feeling like they're stepping on any toes.

On why consistency in the heading hierarchy is a good thing, check out the outline view in the preview for this doc: https://github.com/MetaMask/contributor-docs/blob/feature/69-typescript/docs/typescript.md. You can enable it by clicking on the unordered-list icon on the top right (Button.types__StyledButton-sc-ws60qy-0).

I think we should be ready to merge after this next round of fixes. Thanks for bearing with me through all these comments.

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
MajorLift
MajorLift previously approved these changes Mar 1, 2024
Copy link
Contributor

@MajorLift MajorLift left a comment

Choose a reason for hiding this comment

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

LGTM!

Added a commit 5d990cd before leaving the approval so you could merge without more back and forth.

Feel free to ping me for another approval if there are more changes you want to add.

docs/typescript.md Outdated Show resolved Hide resolved
docs/typescript.md Outdated Show resolved Hide resolved
Comment on lines +149 to +195
#### Avoid type assertions, or explain their purpose whenever necessary.

Type assertions inform TypeScript about a variable's actual type, using the `as` syntax.
While type assertions offer flexibility, they can bypass TypeScript's safety net. This means the compiler won't catch potential type mismatches, leaving you responsible for ensuring correctness.

While it's generally recommended to avoid type assertions in TypeScript, here are two common scenarios where they might be necessary:

1. Using reduce:

Imagine you have an array of User objects and want to find the youngest user using reduce. You start with an empty accumulator `({})`, but the final result should be a User object.

```typescript
interface User {
name: string;
age: number;
}

const users: User[] = [
// ... user data
];

const youngestUser = users.reduce((acc, user) => {
if (!acc.age || user.age < acc.age) {
acc = user; // Here, `acc` might be undefined initially.
}
return acc;
}, {} as User); // Assert `{}` as `User` to satisfy the return type.

console.log(youngestUser); // { name: "...", age: maxAge }
```

2. Iterating over Enum Keys:
When iterating over enum keys using `Object.keys`, the resulting array is of type `string[]`. However, if you want to access enum member values directly, you might need a type assertion.

```typescript
enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right',
}
const directions: Direction[] = [Direction.Up, Direction.Down];
for (const key of Object.keys(directions)) {
const direction = directions[key as keyof typeof directions];
console.log(direction); // Output: Direction.Up, Direction.Down }
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Heads-up that I'll be integrating this section into https://github.com/MetaMask/contributor-docs/pull/74/files#diff-ae03a9f2eb638a25c1d8d7ba853c78220f1ed44a19ad97a8d780cbc0c00a4260R161-R239, since this topic falls under the scope of #47.

Comment on lines +197 to +228
#### Avoid type annotations

Type annotations act like contracts with the compiler, ensuring a variable sticks to a specific data type its entire life.

In rare cases, using type annotations with `reduce` can be beneficial. It helps tp clarify complex accumulator types, enhance readability and ensure the compiler understands its intended use.

If you need to create a data structure with specific values, but ensure that it's declared with a more abstract type. Create a function that takes an argument of the type you want, then use it to build the object.

```typescript
interface Person {
name: string;
age: number;
occupation?: string; // Optional property
}

// Function with type annotations:
function createPerson<T extends { name: string; age: number }>(
data: T,
): Person {
// Infer types from the data argument
const { name, age, occupation = 'Unknown' } = data;

// Ensure properties adhere to the Person interface
return { name, age, occupation };
}

// Usage:
const person1 = createPerson({ name: 'Alice', age: 30 }); // { name: "Alice", age: 30, occupation: "Unknown" }
const person2 = createPerson({ name: 'Bob', age: 25, occupation: 'Developer' }); // { name: "Bob", age: 25, occupation: "Developer" }
```

TypeScript is planning to introduce the `satisfies` keyword, which will provide a more flexible way to declare that a data structure conforms to a specific type without sacrificing readability or maintainability. However, this feature is not yet available.
Copy link
Contributor

@MajorLift MajorLift Mar 1, 2024

Choose a reason for hiding this comment

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

#### `implements` keyword

The `implements` keyword enables us to define and enforce interfaces, i.e. strict contracts consisting of expected object and class properties and abstract method signatures.
Writing an interface to establish the specifications of a class that external code can interact while without being aware of internal implementation details is encouraged as sound OOP development practice.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: phrasing, grammar

Suggested change
Writing an interface to establish the specifications of a class that external code can interact while without being aware of internal implementation details is encouraged as sound OOP development practice.
We encourage writing interfaces to establish the specifications of a class. This is sound OOP development practice, as it enables external code to interact with the class without requiring awareness of internal implementation details.


#### Don't use numeric enums

Numeric enums are misleading because it creates a reverse mapping from value to property name, and when using `Object.values` to access member names, it will return the numerical values instead of the member names, potentially causing unexpected behavior.
Copy link
Contributor

Choose a reason for hiding this comment

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

TODO for future iterations:

What unexpected behavior? Why is this practice problematic? Do we need an example for what "reverse mapping" means and why it causes confusion?

The correct example is ambiguous because the corresponding keys and values are identical. It's also unclear why it's better than the incorrect example.

@DDDDDanica
Copy link
Contributor Author

@MajorLift thanks for your review and approval, they are really helpful ! I'll draft another PR to address the rest

@DDDDDanica DDDDDanica merged commit cd90dc3 into main Mar 4, 2024
6 checks passed
@DDDDDanica DDDDDanica deleted the feature/69-typescript branch March 4, 2024 11:12
@DDDDDanica
Copy link
Contributor Author

Have a conversation with @MajorLift offline to merge his work first, then add more for that he will leave for me

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

Successfully merging this pull request may close these issues.

3 participants