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

chore: improve pkg/driver types #21197

Merged
merged 19 commits into from May 13, 2022
Merged

Conversation

sainthkh
Copy link
Contributor

User facing changelog

N/A. It's adding types for internal use.

Additional details

  • Why was this change necessary? => Remove anys and make code easier to navigate.
  • What is affected by this change? => N/A
  • Any implementation details to explain? => N/A

How has the user experience changed?

N/A

PR Tasks

  • [na] Have tests been added/updated?
  • Has the original issue (or this PR, if no issue exists) been tagged with a release in ZenHub? (user-facing changes only)
  • [na] Has a PR for user-facing changes been opened in cypress-documentation?
  • [na] Have API changes been updated in the type definitions?
  • [na] Have new configuration options been added to the cypress.schema.json?

@cypress-bot
Copy link
Contributor

cypress-bot bot commented Apr 26, 2022

Thanks for taking the time to open a PR!

Comment on lines -148 to -156
// mixin lodash methods
_.each(['pick'], (method) => {
return $Command.prototype[method] = function (...args) {
args.unshift(this.attributes)

return _[method].apply(_, args)
}
})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code is added 5 years ago and no other lodash function is added to $Command. I guess it wouldn't later. So, I moved it inside the class for type.

@@ -0,0 +1,35 @@
/// <reference path="../../types/cy/commands/session.d.ts" />
Copy link
Contributor Author

@sainthkh sainthkh Apr 26, 2022

Choose a reason for hiding this comment

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

New file is created because

  1. types defined in .ts cannot be imported to d.ts files without breaking d.ts's auto import feature.
  2. d.ts files automatically changed undefined or misspelled types to any.

@sainthkh sainthkh marked this pull request as ready for review April 26, 2022 07:30
@sainthkh sainthkh requested a review from a team as a code owner April 26, 2022 07:30
@sainthkh sainthkh requested review from jennifer-shehane and removed request for a team April 26, 2022 07:30
@jennifer-shehane jennifer-shehane removed their request for review April 27, 2022 15:51
@jennifer-shehane
Copy link
Member

@sainthkh Thanks for the updates!

@flotwig flotwig requested a review from BlueWinds May 4, 2022 15:32
@@ -347,6 +349,8 @@ export const create = (Cypress, cy) => {
if (_.isFunction(onRetry)) {
return cy.retry(onRetry, options)
}

return
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this trailing empty return? It looks very unusual to my eyes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's because TypeScript wants all return paths.

As the type of cy.retry became specific, TypeScript learned that this function returns a Promise above but nothing at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without this it's an error.

Comment on lines +137 to +141
pick (...args) {
args.unshift(this.attributes)

return _.pick.apply(_, args as [Record<string, any>, any[]])
}
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 necessarily think we need to be added this to every command instance. Why can't we use a static method?

Copy link
Contributor Author

@sainthkh sainthkh May 9, 2022

Choose a reason for hiding this comment

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

It's used in the line below:

_.defaults(obj, current != null ? current.pick('name', 'type') : undefined)

And because of the this in the this.attributes. You cannot use non-static fields with this inside static functions.

Copy link
Member

@emilyrohrbough emilyrohrbough left a comment

Choose a reason for hiding this comment

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

@sainthkh Can you share the long-term intended type changes? I'm having a hard time understanding the end-goal for the driver package.

It seems like our types patterns are getting inconsistent where some components are exporting their types from their file where other types are being managed in a standalone file, while other types are overriding the CLI types within ./driver/types

@sainthkh
Copy link
Contributor Author

sainthkh commented May 9, 2022

AFAIK, there is no rule or documentation about where and how we should define our types. But as a person who wrote a lot of type definitions for Cypress, here are my rules and goals:

  • Use cli/types when the type should be provided to the users.
  • Use driver/types when we're extending the provided types (e.g. Window) or it's not defined in our code.
  • Use each ts file and import/import type syntax when it's defined in our code.

The reasons are below. It's a bit long because it's related with the history of Cypress code base.

Why are we using TypeScript?

Maybe, we should start with the benefit of TypeScript over plain JavaScript. I think there are 2 benefits:

  • Static types make you think about the correct input and output of everything. This can help reduce bugs and write easier-to-read code.
  • Thanks to more info, tools can help us navigate and write code faster with intellisense.

The goal of these type PRs is related with the second reason, making Cypress driver code easier to navigate.

Without types, when a developer wants to know where the definition of some functions are, they have to search the entire project and read all the result. With types, they can save much time by just clicking the name.

cli/types - public types for users.

driver imports types from 3 sources:

  • cli/types - d.ts
  • driver/types - d.ts
  • each file

cli/types were first created because some users wanted TypeScript support of Cypress while the code base was mostly written in CoffeeScript. It was inevitable because it's the only way to use TypeScript and CoffeeScript together.

There was an issue like #6683 that the types should be generated from the driver directly. But it's not possible in the near future because cy commands are added dynamically to the class $Cy like below:

// perf loop
for (let cmd of builtInCommands) {
// support "export default" syntax
cmd = cmd.default || cmd
cmd(Commands, Cypress, cy, state, config)
}

When removing type todos in #20601, I thought about moving command options to driver and adding them to the cli/type on the build. But it would make the problem more complicated because public and internal interfaces are mixed in the project. So, I decided not to do that and imported types from cli/types when it's public.

And removing it is a bit hard, because there are some APIs we need to hide from our definition. For example of #20556, $Cy uses eventemitter2 internally, but it should not expose waitFor because it can confuse users.

driver/types

The next type file created is driver/types. It is created mainly to avoid type problems in driver when we're moving JavaScript to TypeScript.

But there are some limitation of this file because:

  • type check isn't stronger than plain .ts files. For example, there is currently ActiveSessionsSessionData in internal-types.d.ts. But this type doesn't exist. It should be Cypress.Commands.Session.ActiveSessions. But TypeScript doesn't alarm because non-existent types are any in d.ts files.
  • It doesn't help navigating files. When you want to learn more about something, it doesn't lead to the real definition, but the d.ts. It's not what we wanted.

So, whenever possible, I try to define types inside the .ts files, and use import or import type directly from the defined .ts file to help navigate.

But removing these d.ts files isn't the goal, because they are sometimes necessary when we're extending the given types like Window in types/window.d.ts in this PR.

Copy link
Contributor

@rachelruderman rachelruderman left a comment

Choose a reason for hiding this comment

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

LGTM, thank you for your contribution and the in-depth explanation in the comments 🙂

@flotwig flotwig merged commit 38bd1bb into cypress-io:develop May 13, 2022
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.

None yet

6 participants