From afd550581f7f6d847983ad78d0a43fed0eef9769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Mon, 19 Jun 2023 17:01:48 +0100 Subject: [PATCH 01/11] Create first version of TypeScript Guidelines --- contributingGuides/TYPESCRIPT_STYLE.md | 857 +++++++++++++++++++++++++ 1 file changed, 857 insertions(+) create mode 100644 contributingGuides/TYPESCRIPT_STYLE.md diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md new file mode 100644 index 000000000000..7be99ac48441 --- /dev/null +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -0,0 +1,857 @@ +# TypeScript Coding Standards + +For our Javascript code style rules, refer to the [JavaScript Style Guide](contributingGuides/STYLE.md). + +You can also refer to [the TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) for more information on how to use TypeScript. + +## Organizing Type Definitions + +### File structure + +- Reusable type definitions, such as models, must have their own file and be placed in a shared directory, e.g. `src/types/`. +- Types specific to a single file should remain inside that file to keep their scope limited and their usage clear. +- Types specific to a component/file with platform-specific variants should have their own file and be placed in the same folder. + +### File Extensions + +For TypeScript files containing JSX code, use the `.tsx` file extension. For other TypeScript code use the `.ts` file extension. + +Files containing only type declarations should **not** use the `.d.ts` extension. The only exception is when defining a common interface for platform-specific implementations (see below). + +### Platform-Specific Variants + +In most cases, the code written for this repo should be platform-independent. In such cases, each module should have a single file, `index.ts`, which defines the module's exports. There are, however, some cases in which a feature is intrinsically tied to the underlying platform. In such cases, the following file extensions can be used to export platform-specific code from a module: + +- `index.native.ts` - Native (iOS and Android) +- `index.android.ts` - Android +- `index.ios.ts` - iOS +- `index.website.ts` - Web +- `index.desktop.ts` - Desktop + +Note that `index.ts` should contain the default implementation, and only platform-specific implementations should be done in their respective files. i.e: If you have mobile-specific implementation in `index.native.ts`, then the desktop/web implementation can be contained in a shared `index.ts`. + +For each platform-specific module, create shared type definitions in a separate `types.ts` file and place it in the same folder. `types.ts` has to export shared types which are **compatible with all platform-specific implementations**. Declare component props, return types, and other common types in this file. + +```ts +type ComponentProps = { + foo: string; + bar: number; +}; + +type Helpers = { + doSomething: () => void; +}; + +export type { ComponentProps, Helpers }; +``` + +In case there is no default implementation, you have to create a `index.d.ts` declaration file. When importing the module from other files, TypeScript will automatically pick up the type definitions from `index.d.ts`. Be careful when defining `index.d.ts` as declaration files aren't checked by the TypeScript compiler (with `skipLibCheck: true` - [source](https://www.typescriptlang.org/tsconfig#skipLibCheck)). + +Example with a default implementation: + +```ts +// types.ts +type VisibilityInterface = { + isVisible: () => boolean; + hasFocus: () => boolean; +}; + +export default VisibilityInterface; + +// index.ts +function isVisible() { + return document.visibilityState === 'visible'; +} + +function hasFocus() { + return document.hasFocus(); +} + +const Visibility: VisibilityInterface = { + isVisible, + hasFocus, +}; + +export default Visibility; + +// index.native.ts +function isVisible() { + return AppState.currentState === 'active'; +} + +function hasFocus() { + return true; +} + +const Visibility: VisibilityInterface = { + isVisible, + hasFocus, +}; + +export default Visibility; +``` + +Example with no default implementation: + +```ts +// types.ts +type Platform = 'android' | 'ios' | 'web' | 'desktop'; +export default Platform; + +// index.d.ts +export default function getPlatform(): Platform; + +// index.ios.ts +export default function getPlatform(): Platform { + return "ios"; +} + +// index.android.ts +export default function getPlatform(): Platform { + return "android"; +} + +// index.desktop.ts +export default function getPlatform(): Platform { + return "desktop"; +} + +// index.website.ts +export default function getPlatform(): Platform { + return "website"; +} +``` + +Refer to the React Native documentation for more information about [Platform Specific Code](https://reactnative.dev/docs/platform-specific-code). + +## Naming Conventions + +Use **PascalCase** for class, type, interface, and enum names, as well as enum members and type parameters (generics). This improves readability and conforms to widely accepted TypeScript standards. + + +```ts +// Bad +class someService {} +type some_type = number | string; +interface some_interface {} +enum some_enum { some_value } +function example(): Some_type {} + +// Good +class SomeService {} +type SomeType = number | string; +interface SomeInterface {} +enum SomeEnum { SomeValue } +function example(): SomeType {} +``` + +Use **camelCase** for variables, parameters, functions, methods, properties, and module aliases. + +```ts +// Bad +const user_Name = "John"; +function process_Data(input_data: string) {} +class BadExample { + do_Work() {} + WorkDone: boolean; +} +import * as Some_Module from "some-module"; + +// Good +const userName = "John"; +function processData(inputData: string) {} +class GoodExample { + doWork() {} + workDone: boolean; +} +import * as someModule from "some-module"; + +``` + +Use **CONSTANT_CASE** for global constant values to distinguish them from other variables and emphasize their fixed state. +```ts +// Bad +const someConst = { config: “value” } as const; + +// Good +const SOME_CONST = { config: “value” } as const; +``` + +Avoid using common generic words such as data, state, amount, number, value, manager, engine, object, entity, instance, helper, util, broker, metadata, process, handle, and context. + +Especially do not include "Type" at the end of your type files and definitions. + +```ts +// Bad: ReportType.ts +// Good: Report.ts + +// Bad +type ReportType = { + // properties +} + +// Good +type Report = { + // properties +} +``` + +Remove any words that are already clear from the variable's type declaration. + +```ts +// Bad +let nameString: string; +let holidayDateList: Array; + +// Good +let name: string; +let holidays: Array; +``` + +Don't include overly specific names that make the code harder to read. + +```ts +// Bad +type Weather = { + weatherApiTemperature: number; + weatherApiCity: string; +} + +// Good +type Weather = { + temperature: number; + city: string; +} +``` + +## Types and Interfaces + +Use **type** by default. + +```ts +// Bad +interface Report { + // properties +} + +// Good +type Report = { + // properties +} +``` + +TypeScript supports both type and interface expressions. Types can be used more broadly: to name primitives, unions, objects and any other type. As these two forms are practically equivalent, it's best to select one over the other to maintain consistency and reduce variation. + +One drawback of using interfaces is that they can be declared multiple times and are merged. This can lead to unintended behavior. + +```ts +// Merging interfaces +interface Foo { + a: number; +} +interface Foo { + b: number; +} + +// Foo is now { a: number; b: number; } +``` + +Use interfaces only when you encounter a situation where `type` does not fulfill your requirements. Example scenario is when you need to modify or extend an interface from a third-party library that lacks typings or has incorrect typings. + +When extending or correcting interfaces from third-party libraries, always use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to maintain code consistency and avoid potential issues. + +```ts +// Third-party library with missing or incorrect typings +interface LibraryComponentProps { + // Existing incorrect or incomplete typings +} + +// Your code +declare module 'library/path' { + interface LibraryComponentProps { + // Add or modify the typings + additionalProp: string; + } +} +``` + +## Enums and Union Types + +Use **union types** instead of enums. Opt for union types whenever possible. Union types provide flexibility and can handle varying data types and dynamic value sets. + +```ts +// Bad +enum Colors { + Red, + Green, + Blue +} + +// Good +type Colors = 'red' | 'green' | 'blue'; +``` +You can also define a union type by deriving the type from an array. It's a great choice when seeking an easily iterable and directly usable array in code while keeping one source of truth. + +```ts +// Bad +const COLORS = ['red', 'green', 'blue'] as const; +type Colors = 'red' | 'green' | 'blue'; + +// Good +import { TupleToUnion } from 'type-fest'; + +const COLORS = ['red', 'green', 'blue'] as const; +type Colors = TupleToUnion; +``` + +Defining union types using an object offers several benefits. This approach it's a great choice when **enum-like identifiers are needed**, while maintaining one source of truth and improving readability. + +```ts +// Bad +const COLORS = { + Red: 'red', + Green: 'green', + Blue: 'blue', +} as const; +type Colors = 'red' | 'green' | 'blue'; + +// Good +import { ValueOf } from 'type-fest'; + +const COLORS = { + Red: 'red', + Green: 'green', + Blue: 'blue', +} as const; +type Colors = ValueOf +``` + +If using enum is necessary, utilize string enum. Do not use numeric and heterogenous enums. This can help prevent potential bugs at runtime. + +```ts +// Bad +enum Colors { + Red, + Green, + Blue, +} +enum Colors { + Red = 1, + Green = 2, + Blue = 3, +} + +// Good +enum Colors { + Red = 'red', + Green = 'green', + Blue = 'blue', +} +``` + +## Usage of type `any` and `unknown` + +The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, in most cases use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. + +```ts +// Bad +const danger: any = 'danger'; +danger.invalidProperty // No TypeScript error, but will be `undefined` at runtime +danger.invalidProperty.nestedInvalidProperty // No TypeScript error, but will throw TypeError at runtime + +// Good +const safer: unknown = 'safer'; + +// Using `unknown` requires type narrowing before access +safer.nonExistentProperty // TypeScript error, forces proper type checking + +// Narrowing the type before access +if (typeof safer === 'string') { + safer.toUpperCase(); // Allowed after narrowing the type +} +``` + +Usage of `any` is forbidden unless there is a very good reason for it. The `any` type bypass TypeScript type-checking system, thus making the code less safe and more prone to bugs. Any usage of `any` type must be followed with a comment explaining why you are using it. + +```ts +// Bad +function processAPIResponse(response: any) { + // your code +} + +// Good +// As we are just going to persist the response in Onyx and +// it would be very hard to type all possible data structures, +// we just leave `response` as `any`. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function processAPIResponse (response: any) { + // your code +} +``` + +## TypeScript Annotations + +### Usage of `@ts-expect-error` + +During migration, it is expected to find TS errors revealing underlying JS issues. Treat significant errors as separate issues linked to the TS migration issue, unless the fix is minor and verifiable (document it in the PR). Use `@ts-expect-error` for such cases. + +### Usage of `@ts-ignore` + +`@ts-ignore` should not be used. If the usage of `@ts-ignore` cannot be avoided for rare situations, explain the reason in a comment. + +## Optional chaining and nullish coalescing + +Avoid utilizing `lodashGet` in the future. When working with TypeScript files, opt for the combination of optional chaining (`?.`) and nullish coalescing (`??`) rather than using `lodashGet`. + +```ts +// Bad +const name = lodashGet(user, 'name', 'Unknown'); + +// Good +const name = user?.name ?? 'Unknown'; +``` + +## Type inference + +Code may rely on type inference as implemented by the TypeScript compiler for all type expressions (variables, fields, return types, etc). + +```ts +// Bad +const name: string = 'name'; +const [counter, setCounter] = useState(0); + +// Good +const name = 'name'; +const [counter, setCounter] = useState(0); +``` + +### Return types + +For function return types, depending on the clarity of the function use inferred types. Provide explicit typing when it enhances code readability. + +```tsx +// Bad +function Component() { + return ( + setTextValue(newTextValue)} /> + ); +} + +// Good +function Component() { + return ( + setTextValue(newTextValue)} /> + ); +} +``` + +Benefits of explicitly typing return values: +- More precise documentation to benefit readers of the code. +- Surface potential type errors faster in the future if there are code changes that change the return type of the function. + +```ts +interface User { + id: number; + name: string; + age: number; + active: boolean; +} + +// Bad +const getUsersSummary = (users: User[], minAge: number, isActive: boolean) => { + return users + .filter(user => user.age >= minAge && user.active === isActive) + .map(user => `${user.name} (${user.age})`); +}; + +// Good +const getUsersSummary = (users: User[], minAge: number, isActive: boolean): string[] => { + return users + .filter(user => user.age >= minAge && user.active === isActive) + .map(user => `${user.name} (${user.age})`); +}; +``` + +## Const assertions + +Const assertions allow you to get more specific types for your constants. TypeScript will infer the most specific type for literals, making them `readonly` and removing wider types. + +Always use const assertions when declaring global constant values. + +```ts +// Bad +const CONST = { + // ... +}; + +// Good +const CONST = { + // ... +} as const; +``` + +## Types during error handling + +Errors in try/catch clauses are typed as `unknown`, if the developer needs to use the error data they must conditionally check the type of the data first. Use `instanceof` to ensure your error object is an `Error` before accessing its properties. + +```ts +try { + // your code +} catch (e) { // At this point, the type of `e` is `unknown`. + if (e instanceof Error) { + // Now I can safely access all properties from `e` because + // I ensured `e` is an `Error` object. + console.error(e.message); + } +} +``` + +### Custom type guards + +A type guard is a check that narrows the type of a variable within a certain scope. + +The `typeof` keyword must be used when you need to narrow the type to primitive structures, like `string` or `number`. + +```ts +function padLeft(value: string, padding: string | number) { + if (typeof padding === "number") { + return Array(padding + 1).join(" ") + value; + } + if (typeof padding === "string") { + return padding + value; + } + throw new Error(`Expected string or number, got '${padding}'.`); +} + +padLeft("Hello world", 4); // returns " Hello world" +``` + +We can also create user-defined type guards which are functions that perform a runtime check that guarantees the type in a certain scope. + +```ts +type PolicyRoomReport = { + type: 'policyRoom'; + id: string; + roomId: string; +}; + +type PolicyExpenseChatReport = { + type: 'policyExpenseChat'; + id: string; + expenseChatId: string; +}; + +type ChatReport = PolicyRoomReport | PolicyExpenseChatReport; + +function isPolicyRoomReport(report: ChatReport): report is PolicyRoomReport { + return report.type === 'policyRoom'; +} + +if (isPolicyRoomReport(report)) { + // I'm guaranteed to have access to PolicyRoomReport's properties e.g. "roomId". + // report.roomId +} else { + // If this is not a PolicyRoomReport, then I'm guaranteed that this is a PolicyExpenseChatReport, so I'll have access to it's properties e.g. "expenseChatId" + // report.expenseChatId +} +``` + +In this example, we have the `ChatReport` type which can be two possible types of object, `PolicyRoomReport` or `PolicyExpenseChatReport`. The `isPolicyRoomReport` function will accept `report` as a parameter and will narrow the type of `report` to `PolicyRoomReport` if the condition inside it is true, and to PolicyExpenseChatReport if the condition is false. + +## Usage of “defaultProps” + +To achieve better and safer typing of components with default prop values, all usages `defaultProps` shall be removed and replaced by using prop destructuring. Please head to **Typing components** section to understand how you can convert your implementation. + +## JSDoc annotations + +JSDoc is a markup language used to annotate JavaScript files. Using comments containing JSDoc, developers can add documentation describing the application programming interface of the code they are creating. + +While useful in a JS project, JSDoc will not add any value to our project anymore as we are using TypeScript to provide type check and safety now. + +With that in mind, we shall remove all JSDoc annotations that specify which kind of object is expected or returned, as they won’t be necessary anymore. + +```ts +// Bad +/** + * Do something. + * @param {Boolean} param1 + * @returns {Boolean} + */ +function fooMethod(param1) { + return !param1; +} + +// Good +/** + * Do something. + */ +function fooMethod(param1: boolean) { + return !param1; +} +``` + +## Typing Components + +### Transitioning from PropTypes to TypeScript + +TypeScript provides a more efficient and comprehensive solution for managing prop types in our codebase. When developing new components or updating existing ones to TypeScript, do not use PropTypes. Instead, use `types` to define the structure of your component's props. + +When converting a `propTypes` object to a `type`, use the following table to help and guide you in the process: + +| PropTypes | TypeScript | Instructions | +|----------------------------------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `PropTypes.any` | `any` | Figure out what would be the correct data type, if not possible convert to `any` and leave a comment explaining the reason. | +| `PropTypes.array` or `PropTypes.arrayOf(T)` | `Array` | Convert to `Array`, where `T` is the data type of the array.

If `T` isn't a primitive type, create a separate `type` for the object structure of your prop and use it. | +| `PropTypes.bool` | `boolean` | Convert to `boolean`. | +| `PropTypes.func` | `(args: any) => any` | Convert to the function signature of your prop e.g. `(value: string) => void`. | +| `PropTypes.number` | `number` | Convert to `number`. | +| `PropTypes.object`, `PropTypes.shape(T)` or `PropTypes.exact(T)` | `T` | If `T` isn't a primitive type, create a separate `type` for the `T` object structure of your prop and use it.

If you want an empty object, use `EmptyObject` from `type-fest` library.

If you want an object but isn't possible to determine the internal structure, use `Record`. | +| `PropTypes.objectOf(T)` | `Record` | Convert to a `Record` where `T` is the data type of your dictionary.

If `T` isn't a primitive type, create a separate `type` for the object structure and use it. | +| `PropTypes.string` | `string` | Convert to `string`. | +| `PropTypes.node` | `React.ReactNode` | Convert to `React.ReactNode`. | +| `PropTypes.element` | `React.ReactElement` | Convert to `React.ReactElement`. | +| `PropTypes.symbol` | `symbol` | Convert to `symbol`. | +| `PropTypes.elementType` | `React.ElementType` | Convert to `React.ElementType`. | +| `PropTypes.instanceOf(T)` | `T` | Convert to `T`. | +| `PropTypes.oneOf([T, U, ...])` or `PropTypes.oneOfType([T, U, ...])` | `T \| U \| ...` | Convert to a union type e.g. `T \| U \| ...`. | + +```ts +// Before +const propTypes = { + unknownData: PropTypes.any, + anotherUnknownData: PropTypes.any, + indexes: PropTypes.arrayOf(PropTypes.number), + items: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string, + label: PropTypes.string, + })), + shouldShowIcon: PropTypes.bool, + onChangeText: PropTypes.func, + count: PropTypes.number, + session: PropTypes.shape({ + authToken: PropTypes.string, + accountID: PropTypes.number, + }), + errors: PropTypes.objectOf(PropTypes.string), + inputs: PropTypes.objectOf(PropTypes.shape({ + id: PropTypes.string, + label: PropTypes.string, + })), + label: PropTypes.string, + anchor: PropTypes.node, + footer: PropTypes.element, + uniqSymbol: PropTypes.symbol, + icon: PropTypes.elementType, + date: PropTypes.instanceOf(Date), + size: PropTypes.oneOf(['small', 'medium', 'large']), +}; + +// After +type Item = { + value: string; + label: string; +}; + +type Session = { + authToken: string; + accountID: number; +}; + +type Input = { + id: string; + label: string; +}; + +type Size = 'small' | 'medium' | 'large'; + +type Props = { + unknownData: Array; + + // It's not possible to infer the data as it can be anything because of reasons X, Y and Z. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + anotherUnknownData: any; + + indexes: Array; + items: Array; + shouldShowIcon: boolean; + onChangeText: (value: string) => void; + count: number; + session: Session; + errors: Record; + inputs: Record; + label: string; + anchor: React.ReactNode; + footer: React.ReactElement; + uniqSymbol: symbol; + icon: React.ElementType; + date: Date; + size: Size; +}; +``` + +Please note that you may encounter some PropTypes marked as `isRequired`, but when migrating to `type` you shall first consider all PropTypes as required ones and then inspect the component's source code and usage to identify which ones are really required or optional. + +```ts +// Before +const propTypes = { + isVisible: PropTypes.bool.isRequired, + // `confirmText` prop is not marked as required here, theoretically it is optional. + confirmText: PropTypes.string, +}; + +// After +type Props = { + isVisible: boolean; + // Consider it as required unless you have proof that it is indeed an optional prop. + confirmText: string; +}; +``` + +### Default props + +As **defaultProps** are not allowed anymore in the project, use object destructuring in your `Props` in order to set default values to them. The props that have default value must also be marked as optional in your `Props` type. + +```tsx +// Before +const propTypes = { + requiredProp: PropTypes.string.isRequired, + optionalPropWithDefaultValue: PropTypes.number, + optionalProp: PropTypes.bool, +} + +const defaultProps = { + optionalPropWithDefaultValue: 42, +}; + +function Foo(props) { + // your component's code +} + +Foo.propTypes = propTypes; +Foo.defaultProps = defaultProps; + +export default Foo; + +// After +type Props = { + requiredProp: string; + optionalPropWithDefaultValue?: number; + optionalProp?: boolean; +} + +function Foo({ requiredProp, optionalPropWithDefaultValue = 42, optionalProp }: Props) { + // your component's code +} + +export default Foo; +``` + +### Style props + +When converting or typing style props, use `StyleProp` type where `T` is the type of styles related to the component your prop is going to apply. + +- If your style prop is going to be applied to a `View`, use `StyleProps`. +- If your style prop is going to be applied to a `Text`, use `StyleProps`. +- If your style prop is going to be applied to a `Image`, use `StyleProps`. + +```ts +// Before +const propTypes = { + containerStyle: PropTypes.arrayOf(PropTypes.object), + textStyle: PropTypes.arrayOf(PropTypes.object), + imageStyle: PropTypes.arrayOf(PropTypes.object), +}; + +// After +import {StyleProp, ViewStyle, TextStyle, ImageStyle} from 'react-native' + +type Props = { + containerStyle: StyleProp; + textStyle: StyleProp; + + // If the styling is optional, mark the prop as optional too. + imageStyle?: StyleProp; +}; +``` + +### Animated styles + +If you need to pass style props to an Animated component, use `Animated.WithAnimatedValue` to augment and add support for animated values in your prop. + +```tsx +import {useRef} from 'react'; +import {Animated, StyleProp, ViewStyle} from 'react-native'; + +type Props = { + style?: Animated.WithAnimatedValue>; +}; + +function Component(props: Props) { + return ; +} + +function App() { + const anim = useRef(new Animated.Value(0)).current; + return ; +} +``` + +### Refs + +Use `React.forwardRef` to pass refs between parent and child components. Utilize `React.forwardRef` generic parameters to provide types. The first parameter specifies the **ref's element type** - common components such as `View`, `TextInput` and `Text`. The second parameter is the **props type of the wrapped component**. + +```tsx +const ChildComponent = React.forwardRef((props, ref) => ( + + Component + +)); + +function ParentComponent() { + const viewRef = React.useRef(null); + + return ( + + + + ); +}; +``` + +### Children and render props + +To type children props, use `React.ReactNode`. + +```tsx +type Props = { + children?: React.ReactNode; +}; + +function Component(props: Props) { + return props.children; +} + +function App() { + return ( + + + + ); +} +``` + +To type render props, use `() => React.ReactNode`. + +```tsx +type Props = { + children: (label: string) => React.ReactNode; +}; + +function Component(props: Props) { + return props.children('Component label'); +} + +function App() { + return {(label) => }; +} +``` From 0191990345c038067f86c094fef1dfe14ff5d90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 20 Jun 2023 15:17:48 +0100 Subject: [PATCH 02/11] Address reviews and improve TS guidelines --- contributingGuides/TYPESCRIPT_STYLE.md | 127 +++++++++++++++---------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index 7be99ac48441..5ee35c5aef98 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -1,6 +1,6 @@ # TypeScript Coding Standards -For our Javascript code style rules, refer to the [JavaScript Style Guide](contributingGuides/STYLE.md). +For our Javascript code style rules, refer to the [JavaScript Style Guide](STYLE.md). You can also refer to [the TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) for more information on how to use TypeScript. @@ -8,7 +8,7 @@ You can also refer to [the TypeScript Handbook](https://www.typescriptlang.org/d ### File structure -- Reusable type definitions, such as models, must have their own file and be placed in a shared directory, e.g. `src/types/`. +- Reusable type definitions, such as models (e.g. Report), must have their own file and be placed in a shared directory, e.g. `src/types/`. - Types specific to a single file should remain inside that file to keep their scope limited and their usage clear. - Types specific to a component/file with platform-specific variants should have their own file and be placed in the same folder. @@ -201,11 +201,11 @@ Remove any words that are already clear from the variable's type declaration. ```ts // Bad let nameString: string; -let holidayDateList: Array; +let holidayDateList: Date[]; // Good let name: string; -let holidays: Array; +let holidays: Date[]; ``` Don't include overly specific names that make the code harder to read. @@ -258,7 +258,7 @@ interface Foo { Use interfaces only when you encounter a situation where `type` does not fulfill your requirements. Example scenario is when you need to modify or extend an interface from a third-party library that lacks typings or has incorrect typings. -When extending or correcting interfaces from third-party libraries, always use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to maintain code consistency and avoid potential issues. +When extending or correcting interfaces from third-party libraries, always use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to maintain code consistency and avoid potential issues, and declare it inside the `global.d.ts` file. ```ts // Third-party library with missing or incorrect typings @@ -323,7 +323,7 @@ const COLORS = { Green: 'green', Blue: 'blue', } as const; -type Colors = ValueOf +type Colors = ValueOf ``` If using enum is necessary, utilize string enum. Do not use numeric and heterogenous enums. This can help prevent potential bugs at runtime. @@ -351,7 +351,25 @@ enum Colors { ## Usage of type `any` and `unknown` -The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, in most cases use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. +Usage of `any` is forbidden unless there is a very good reason for it. The `any` type bypass TypeScript type-checking system, thus making the code less safe and more prone to bugs. Any usage of `any` type must be followed with a comment explaining why you are using it. + +```ts +// Bad +function processAPIResponse(response: any) { + // your code +} + +// Good +// As we are just going to persist the response in Onyx and +// it would be very hard to type all possible data structures, +// we just leave `response` as `any`. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function processAPIResponse (response: any) { + // your code +} +``` + +The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, and in most cases, use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. When you know that the type structure is a object but you don't have context about the content, use `Record`. ```ts // Bad @@ -371,33 +389,25 @@ if (typeof safer === 'string') { } ``` -Usage of `any` is forbidden unless there is a very good reason for it. The `any` type bypass TypeScript type-checking system, thus making the code less safe and more prone to bugs. Any usage of `any` type must be followed with a comment explaining why you are using it. +## TypeScript Annotations -```ts -// Bad -function processAPIResponse(response: any) { - // your code -} +### Usage of `@ts-expect-error` -// Good -// As we are just going to persist the response in Onyx and -// it would be very hard to type all possible data structures, -// we just leave `response` as `any`. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function processAPIResponse (response: any) { - // your code -} -``` +The `@ts-expect-error` annotation tells the TS compiler to ignore any errors in the following line. However, if there's no error on the following line, TypeScript will raise an error about the comment itself. -## TypeScript Annotations +```ts +// @ts-expect-error +const x: number = 'This is a string'; // No TS error raised -### Usage of `@ts-expect-error` +// @ts-expect-error +const y: number = 123; // TS error: Unused '@ts-expect-error' directive. +``` During migration, it is expected to find TS errors revealing underlying JS issues. Treat significant errors as separate issues linked to the TS migration issue, unless the fix is minor and verifiable (document it in the PR). Use `@ts-expect-error` for such cases. ### Usage of `@ts-ignore` -`@ts-ignore` should not be used. If the usage of `@ts-ignore` cannot be avoided for rare situations, explain the reason in a comment. +The `@ts-ignore` annotation will act in the same way that `@ts-expect-error` does, with the difference that it won't raise an error in the comment if there isn't errors anymore in the code, and for this reason it should not be used. If the usage of `@ts-ignore` cannot be avoided for rare situations, explain the reason in a comment. ## Optional chaining and nullish coalescing @@ -423,11 +433,14 @@ const [counter, setCounter] = useState(0); // Good const name = 'name'; const [counter, setCounter] = useState(0); + +// In this case I need to specify the type because `hint` state can be either a `string` or `undefined`. +const [hint, setHint] = useState(undefined); ``` -### Return types +### Parameter and return types -For function return types, depending on the clarity of the function use inferred types. Provide explicit typing when it enhances code readability. +For function parameter types, depending on the clarity of the function use inferred types. Provide explicit typing when it enhances code readability. ```tsx // Bad @@ -480,12 +493,12 @@ Always use const assertions when declaring global constant values. ```ts // Bad -const CONST = { +const SOMETHING = { // ... }; // Good -const CONST = { +const SOMETHING = { // ... } as const; ``` @@ -556,7 +569,23 @@ if (isPolicyRoomReport(report)) { } ``` -In this example, we have the `ChatReport` type which can be two possible types of object, `PolicyRoomReport` or `PolicyExpenseChatReport`. The `isPolicyRoomReport` function will accept `report` as a parameter and will narrow the type of `report` to `PolicyRoomReport` if the condition inside it is true, and to PolicyExpenseChatReport if the condition is false. +In this example, we have the `ChatReport` type which can be two possible types of object, `PolicyRoomReport` or `PolicyExpenseChatReport`. The `isPolicyRoomReport` function will accept `report` as a parameter and will narrow the type of `report` to `PolicyRoomReport` if the condition inside it is true, and to `PolicyExpenseChatReport` if the condition is false. + +## Typing arrays + +To type arrays, use `T[]` for simple types (i.e. types which are just primitive names or type references). Use `Array` for all other types (union types, intersection types, object types, function types, etc). + +```ts +// T[] +const items: Item[] = []; +const labels: string[] = ['name', 'city']; +const indexes: number[] = [1, 2, 3]; + +// Array +const items: Array> = []; +const ids: Array = ['some_id', 42]; +const colors: Array> = []; +``` ## Usage of “defaultProps” @@ -598,22 +627,22 @@ TypeScript provides a more efficient and comprehensive solution for managing pro When converting a `propTypes` object to a `type`, use the following table to help and guide you in the process: -| PropTypes | TypeScript | Instructions | -|----------------------------------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `PropTypes.any` | `any` | Figure out what would be the correct data type, if not possible convert to `any` and leave a comment explaining the reason. | -| `PropTypes.array` or `PropTypes.arrayOf(T)` | `Array` | Convert to `Array`, where `T` is the data type of the array.

If `T` isn't a primitive type, create a separate `type` for the object structure of your prop and use it. | -| `PropTypes.bool` | `boolean` | Convert to `boolean`. | -| `PropTypes.func` | `(args: any) => any` | Convert to the function signature of your prop e.g. `(value: string) => void`. | -| `PropTypes.number` | `number` | Convert to `number`. | -| `PropTypes.object`, `PropTypes.shape(T)` or `PropTypes.exact(T)` | `T` | If `T` isn't a primitive type, create a separate `type` for the `T` object structure of your prop and use it.

If you want an empty object, use `EmptyObject` from `type-fest` library.

If you want an object but isn't possible to determine the internal structure, use `Record`. | -| `PropTypes.objectOf(T)` | `Record` | Convert to a `Record` where `T` is the data type of your dictionary.

If `T` isn't a primitive type, create a separate `type` for the object structure and use it. | -| `PropTypes.string` | `string` | Convert to `string`. | -| `PropTypes.node` | `React.ReactNode` | Convert to `React.ReactNode`. | -| `PropTypes.element` | `React.ReactElement` | Convert to `React.ReactElement`. | -| `PropTypes.symbol` | `symbol` | Convert to `symbol`. | -| `PropTypes.elementType` | `React.ElementType` | Convert to `React.ElementType`. | -| `PropTypes.instanceOf(T)` | `T` | Convert to `T`. | -| `PropTypes.oneOf([T, U, ...])` or `PropTypes.oneOfType([T, U, ...])` | `T \| U \| ...` | Convert to a union type e.g. `T \| U \| ...`. | +| PropTypes | TypeScript | Instructions | +|----------------------------------------------------------------------|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `PropTypes.any` | `T`, `Record` or `any` | Figure out what would be the correct data type and use it.

If you know that it's a object but isn't possible to determine the internal structure, use `Record`.

As a last resource, use `any` and leave a comment explaining the reason. | +| `PropTypes.array` or `PropTypes.arrayOf(T)` | `T[]` or `Array` | Convert to `T[]` or `Array`, where `T` is the data type of the array.

If `T` isn't a primitive type, create a separate `type` for the object structure of your prop and use it. | +| `PropTypes.bool` | `boolean` | Convert to `boolean`. | +| `PropTypes.func` | `(args: any) => any` | Convert to the function signature of your prop e.g. `(value: string) => void`. | +| `PropTypes.number` | `number` | Convert to `number`. | +| `PropTypes.object`, `PropTypes.shape(T)` or `PropTypes.exact(T)` | `T` | If `T` isn't a primitive type, create a separate `type` for the `T` object structure of your prop and use it.

If you want an empty object, use `EmptyObject` from `type-fest` library.

If you want an object but isn't possible to determine the internal structure, use `Record`. | +| `PropTypes.objectOf(T)` | `Record` | Convert to a `Record` where `T` is the data type of your dictionary.

If `T` isn't a primitive type, create a separate `type` for the object structure and use it. | +| `PropTypes.string` | `string` | Convert to `string`. | +| `PropTypes.node` | `React.ReactNode` | Convert to `React.ReactNode`. | +| `PropTypes.element` | `React.ReactElement` | Convert to `React.ReactElement`. | +| `PropTypes.symbol` | `symbol` | Convert to `symbol`. | +| `PropTypes.elementType` | `React.ElementType` | Convert to `React.ElementType`. | +| `PropTypes.instanceOf(T)` | `T` | Convert to `T`. | +| `PropTypes.oneOf([T, U, ...])` or `PropTypes.oneOfType([T, U, ...])` | `T \| U \| ...` | Convert to a union type e.g. `T \| U \| ...`. | ```ts // Before @@ -665,14 +694,14 @@ type Input = { type Size = 'small' | 'medium' | 'large'; type Props = { - unknownData: Array; + unknownData: string[]; // It's not possible to infer the data as it can be anything because of reasons X, Y and Z. // eslint-disable-next-line @typescript-eslint/no-explicit-any anotherUnknownData: any; - indexes: Array; - items: Array; + indexes: number[]; + items: Item[]; shouldShowIcon: boolean; onChangeText: (value: string) => void; count: number; From 5510899c03080812c7fa17abb7cb3c9c984a67d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 21 Jun 2023 11:15:37 +0100 Subject: [PATCH 03/11] Addressed more reviews and removed "props: Props" usages --- contributingGuides/TYPESCRIPT_STYLE.md | 47 +++++++++++++++----------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index 5ee35c5aef98..b696920a5d63 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -326,21 +326,30 @@ const COLORS = { type Colors = ValueOf ``` -If using enum is necessary, utilize string enum. Do not use numeric and heterogenous enums. This can help prevent potential bugs at runtime. +If using enum is necessary, utilize string enum. Do not use numeric and heterogenous enums as they auto increment its numeric values in unexpected ways, are hard to understand when they have mixed value types and have reverse mappings. This can help prevent potential bugs at runtime. ```ts // Bad -enum Colors { - Red, - Green, - Blue, +enum Colors1 { + Red, // when not assigning anything, the value will start with 0. + Green, // and will increment, 1. + Blue, // 2. } -enum Colors { - Red = 1, - Green = 2, - Blue = 3, + +enum Colors2 { + Red = 10, // 10. + Green, // will increment to 11. + Blue = 30, // 30. + Yellow, // same, will increment to 31. + Magenta = 'magenta', // "magenta". + Brown = 100, // 100. + Black, // 101. } +// Bad - We also end up having reverse mappings when using numeric values in enums. +Colors2.Green; // 11. +Colors2[11]; // "Green". + // Good enum Colors { Red = 'red', @@ -351,7 +360,7 @@ enum Colors { ## Usage of type `any` and `unknown` -Usage of `any` is forbidden unless there is a very good reason for it. The `any` type bypass TypeScript type-checking system, thus making the code less safe and more prone to bugs. Any usage of `any` type must be followed with a comment explaining why you are using it. +Usage of `any` is forbidden unless there is a very good reason for it. The `any` type bypasses the TypeScript type-checking system, thus making the code less safe and more prone to bugs. Any usage of `any` type must be followed with a comment explaining why you are using it. ```ts // Bad @@ -463,7 +472,7 @@ Benefits of explicitly typing return values: - Surface potential type errors faster in the future if there are code changes that change the return type of the function. ```ts -interface User { +type User = { id: number; name: string; age: number; @@ -569,7 +578,7 @@ if (isPolicyRoomReport(report)) { } ``` -In this example, we have the `ChatReport` type which can be two possible types of object, `PolicyRoomReport` or `PolicyExpenseChatReport`. The `isPolicyRoomReport` function will accept `report` as a parameter and will narrow the type of `report` to `PolicyRoomReport` if the condition inside it is true, and to `PolicyExpenseChatReport` if the condition is false. +In this example, we have the `ChatReport` type which can be two possible types of object, `PolicyRoomReport` or `PolicyExpenseChatReport`. The `isPolicyRoomReport` function accepts `report` as a parameter and its return type is a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates), which means that it will narrow the type of `report` to `PolicyRoomReport` if the condition inside it is `true`, and to `PolicyExpenseChatReport` if the condition is `false`. ## Typing arrays @@ -589,7 +598,7 @@ const colors: Array> = []; ## Usage of “defaultProps” -To achieve better and safer typing of components with default prop values, all usages `defaultProps` shall be removed and replaced by using prop destructuring. Please head to **Typing components** section to understand how you can convert your implementation. +To achieve better and safer typing of components with default prop values, all usages `defaultProps` shall be removed and replaced by prop destructuring. Please head to **Typing components** section to understand how you can convert your implementation. ## JSDoc annotations @@ -815,8 +824,8 @@ type Props = { style?: Animated.WithAnimatedValue>; }; -function Component(props: Props) { - return ; +function Component({ style }: Props) { + return ; } function App() { @@ -856,8 +865,8 @@ type Props = { children?: React.ReactNode; }; -function Component(props: Props) { - return props.children; +function Component({ children }: Props) { + return children; } function App() { @@ -876,8 +885,8 @@ type Props = { children: (label: string) => React.ReactNode; }; -function Component(props: Props) { - return props.children('Component label'); +function Component({ children }: Props) { + return children('Component label'); } function App() { From a88503449ac78765cf9cfc1a0cd384f5bcaf6844 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 22 Jun 2023 13:45:04 +0200 Subject: [PATCH 04/11] Clarify that the Record key can also be a number with Record --- contributingGuides/TYPESCRIPT_STYLE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index b696920a5d63..0131950a59b7 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -378,7 +378,9 @@ function processAPIResponse (response: any) { } ``` -The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, and in most cases, use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. When you know that the type structure is a object but you don't have context about the content, use `Record`. +The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, and in most cases, use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. + +When you know that the type structure is a object but you don't have context about the content, use `Record`. Note that numeric keys are allowed with the `Record` type. Numeric keys are implicitly converted to strings during property assignment and access. To read more about this, see [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/2/objects.html#dynamically-adding-properties). ```ts // Bad From 5e89c4966405b834cb31854bd4a46807b50ac362 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 13:08:51 +0200 Subject: [PATCH 05/11] Format table in ts guidelines --- contributingGuides/TYPESCRIPT_STYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index 0131950a59b7..dff162e3d5b1 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -639,7 +639,7 @@ TypeScript provides a more efficient and comprehensive solution for managing pro When converting a `propTypes` object to a `type`, use the following table to help and guide you in the process: | PropTypes | TypeScript | Instructions | -|----------------------------------------------------------------------|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| -------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PropTypes.any` | `T`, `Record` or `any` | Figure out what would be the correct data type and use it.

If you know that it's a object but isn't possible to determine the internal structure, use `Record`.

As a last resource, use `any` and leave a comment explaining the reason. | | `PropTypes.array` or `PropTypes.arrayOf(T)` | `T[]` or `Array` | Convert to `T[]` or `Array`, where `T` is the data type of the array.

If `T` isn't a primitive type, create a separate `type` for the object structure of your prop and use it. | | `PropTypes.bool` | `boolean` | Convert to `boolean`. | From d6e5f0456c6953f95733eb8e0cda4357990ccac6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 13:17:14 +0200 Subject: [PATCH 06/11] Cut off redundant part of Platform specific variants section --- contributingGuides/TYPESCRIPT_STYLE.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index dff162e3d5b1..6b4e15c73a24 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -20,15 +20,7 @@ Files containing only type declarations should **not** use the `.d.ts` extension ### Platform-Specific Variants -In most cases, the code written for this repo should be platform-independent. In such cases, each module should have a single file, `index.ts`, which defines the module's exports. There are, however, some cases in which a feature is intrinsically tied to the underlying platform. In such cases, the following file extensions can be used to export platform-specific code from a module: - -- `index.native.ts` - Native (iOS and Android) -- `index.android.ts` - Android -- `index.ios.ts` - iOS -- `index.website.ts` - Web -- `index.desktop.ts` - Desktop - -Note that `index.ts` should contain the default implementation, and only platform-specific implementations should be done in their respective files. i.e: If you have mobile-specific implementation in `index.native.ts`, then the desktop/web implementation can be contained in a shared `index.ts`. +Platform-specific TypeScript files follow the same naming conventions as [JavaScript](https://github.com/Expensify/App#platform-specific-file-extensions) files, except with the `.ts`/`.tsx` extension instead of `.js`. For each platform-specific module, create shared type definitions in a separate `types.ts` file and place it in the same folder. `types.ts` has to export shared types which are **compatible with all platform-specific implementations**. Declare component props, return types, and other common types in this file. From c8384b2666332fa57a2e7529f5ffcd6b600d8525 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 15:57:12 +0200 Subject: [PATCH 07/11] Make example any usage more generic --- contributingGuides/TYPESCRIPT_STYLE.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index 6b4e15c73a24..f4c879aa012c 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -356,16 +356,15 @@ Usage of `any` is forbidden unless there is a very good reason for it. The `any` ```ts // Bad -function processAPIResponse(response: any) { +function example(argument: any) { // your code } // Good -// As we are just going to persist the response in Onyx and -// it would be very hard to type all possible data structures, -// we just leave `response` as `any`. +// Argument comes from external sources, and it is not feasible to provide a strict type. +// As we have taken necessary precautions to validate and handle invalid configurations, we leave `argument` as `any`. // eslint-disable-next-line @typescript-eslint/no-explicit-any -function processAPIResponse (response: any) { +function example(argument: any) { // your code } ``` From 12cb275598ad2bcad2d248c4756faa54fed9b003 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 16:07:31 +0200 Subject: [PATCH 08/11] Add example for Reacord usage --- contributingGuides/TYPESCRIPT_STYLE.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index f4c879aa012c..fc077bbabcec 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -371,8 +371,6 @@ function example(argument: any) { The `any` type allows assignment to all types and dereference of any property, which is undesirable and should be avoided. Instead, and in most cases, use the `unknown` type which expresses a similar concept and is much safer as it requires narrowing the type before using it. -When you know that the type structure is a object but you don't have context about the content, use `Record`. Note that numeric keys are allowed with the `Record` type. Numeric keys are implicitly converted to strings during property assignment and access. To read more about this, see [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/2/objects.html#dynamically-adding-properties). - ```ts // Bad const danger: any = 'danger'; @@ -391,6 +389,22 @@ if (typeof safer === 'string') { } ``` +When you know that the type structure is an object, but you don't have context about the content, use `Record`. Note that numeric keys are allowed with the `Record` type. Numeric keys are implicitly converted to strings during property assignment and access. To read more about this, see [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/2/objects.html#dynamically-adding-properties). + +```ts +// Bad: These typings are either too permissive (any), not fully representative of the type (object), or don't fully support type checking (unknown). +function objectKeysToArray(obj: any): string[]; +function objectKeysToArray(obj: object): string[]; +function objectKeysToArray(obj: unknown): string[] { + return Object.keys(obj); +} + +// Good: Using Record to accurately represent that the input is an object with unknown properties. +function objectKeysToArray(obj: Record): string[] { + return Object.keys(obj); +} +``` + ## TypeScript Annotations ### Usage of `@ts-expect-error` From a0668e451666d7dfb4f446dd8111c2e8bd867214 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 16:09:02 +0200 Subject: [PATCH 09/11] Fix interpunction --- contributingGuides/TYPESCRIPT_STYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index fc077bbabcec..aa01bb5562d4 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -456,7 +456,7 @@ const [hint, setHint] = useState(undefined); ### Parameter and return types -For function parameter types, depending on the clarity of the function use inferred types. Provide explicit typing when it enhances code readability. +For function parameter types, depending on the clarity of the function, use inferred types. Provide explicit typing when it enhances code readability. ```tsx // Bad From e04b26755dd94caba3cca774c7ac86518c0e0959 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 23 Jun 2023 17:03:01 +0200 Subject: [PATCH 10/11] Improve naming conventions section --- contributingGuides/TYPESCRIPT_STYLE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index aa01bb5562d4..fe9d72689628 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -120,7 +120,6 @@ Refer to the React Native documentation for more information about [Platform Spe Use **PascalCase** for class, type, interface, and enum names, as well as enum members and type parameters (generics). This improves readability and conforms to widely accepted TypeScript standards. - ```ts // Bad class someService {} @@ -160,7 +159,8 @@ import * as someModule from "some-module"; ``` -Use **CONSTANT_CASE** for global constant values to distinguish them from other variables and emphasize their fixed state. +Use **CONSTANT_CASE** for global-level constant values to distinguish them from other variables and emphasize their fixed state. + ```ts // Bad const someConst = { config: “value” } as const; From bedf647e58ebbf4b0d9d5166c6a5138583bdb032 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 26 Jun 2023 14:02:10 +0200 Subject: [PATCH 11/11] Fix return value in example --- contributingGuides/TYPESCRIPT_STYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/TYPESCRIPT_STYLE.md b/contributingGuides/TYPESCRIPT_STYLE.md index fe9d72689628..b7783bde8fd3 100644 --- a/contributingGuides/TYPESCRIPT_STYLE.md +++ b/contributingGuides/TYPESCRIPT_STYLE.md @@ -110,7 +110,7 @@ export default function getPlatform(): Platform { // index.website.ts export default function getPlatform(): Platform { - return "website"; + return "web"; } ```