[Draft] plan of action for completing JS->TS #30813
Labels
process: contributing
Related to contributing to the Cypress codebase
type: chore
Work is required w/ no deliverable to end user
Success Criteria
.js
in the repository@typescript-eslint/recommended-type-checked
rule set@eslint/stylistic
to formatThere are no implicit dependencies on monorepo packages (e.g., importingAddressed in chore: enforce 'no-implicit-dependencies' on tslint #31022@packages/foo
from a file whosepackage.json
does not declare@packages/foo
as a dependency)There are no implicit dependencies on non-repo packages (e.g., relying on hoisting to provide a reference for anAddressed in chore: enforce 'no-implicit-dependencies' on tslint #31022import
, because the package being imported was declared a dependency in either the rootpackage.json
or a sibling'spackage.json
Preparation
Execution
For shared language purposes, both packages and files can be categorized as "leaf," "shared," or "root" nodes.
./cli
Style Guidance
Type Definitions
Prefer
interface
totype
when possible. Interfaces are never inlined, which solves some potential issues with generated.d.ts
files.When to use
type
:Object Literals
When working with a structural implementation, always declare the type.
Correct:
Incorrect:
Function Definitions
Always declare types for parameters and return values. Declaring parameter types ensures that callers are providing the correct parameters. Declaring the return type ensures that the function body is returning what is intended.
Exception: inlined single-line arrow functions can usually have their type declarations inferred. This is especially the case if the ultimate return value is declared. Example:
Prefer inline types. Parameter types and return types often do not need to be referenced outside of the function itself. Bag parameters (object literals with more than two properties) should be declared as a named interface. If possible, do not export this interface. If a dependent needs to reference the bag type because it is composing an object literal in preparation to call the function in question, that dependent should use
Parameters<>
instead. This way the interface name can change without necessitating a change to the dependent. Use this technique, example below, as a last resort. Prefer composing the parameter bag inline, rather than assigning it to an identifier.Choosing definition styles is almost more of an art than a science. Conciseness and flexibility often go hand in hand, but do not compromise on enforcing correctness to achieve conciseness. Fancy type manipulation with mapped types derived from templated types can be satisfying to figure out, but they often indicate leaky abstractions, vague overloads, and so forth. Use with caution.
Incorrect:
Better:
Best:
DRY
If you find yourself defining more than two very similar interfaces or types, consider what can be DRY'd. Is it purely the types, or is there further refactoring that should be done? When DRY, prefer defining the type as close to the implementation that depends on that type as possible. Define parameter bags next to the function they're an argument for, and export them only when necessary. New interfaces should almost never need to be declared for intermediary variables in a function - if you find yourself doing this, there are probably structural refactors that should be considered before DRYing the type definitions.
Exception to DRY:
Type declarations composed for public API consumption & definition don't need to be as rigorously DRY'd. Readability and understandability is preferred over conciseness.
Stage 1: Typescript Shift
Working through each package from leaf to shared to root nodes, and files within each package from leaf to shared to root files:
.js
files are renamed to.ts
any
type. Because we can warn on the use ofany
types, this serves as an implicit "todo" to come back and fix it.@ts-expect-error
directives as necessary - we will warn on them, so they become an implicit "todo".Stage 2: Reduce Warnings
Beginning again with packages from leaf to root, and within each from leaf file to root file, begin to address warnings. Addressing warnings should be prioritized based on code quality impact. Loosely:
no-require-imports
is a prerequisite to ESM. We userequire
intentionally in several places, so this requires additional consideration.no-explicit-any
ensures that we're defining types and interfaces for function parameters and return values. This is the number one tool to help catch bugs and incorrect code.any
types are a useful escape hatch when first converting a project, but quickly become a crutch. Do the easiest ones first - if you start in on one and realize it has a wide ranging domino effect, add a comment about which other files are involved and move on. That type may become more straightforward to define once other explicitany
types are filled in.require-await
,await-thenable
, andno-floating-promises
help reduce logical errors and unnecessary microtasks with asynchronous code.Stage 3: Enforce previously warned rules as errors
As warnings are removed from packages, those warnings should be converted to errors so they do not re-occur. These rules are specifically called out when overridden in the base eslint configuration.
Helpful Tips
any
types to help get to the finish line - we'll warn on them, and they can be fixed later.The text was updated successfully, but these errors were encountered: