-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Convert core-data selectors from jsDoc annotations into TypeScript type signatures #40025
Conversation
I'm confused by this: why create new types out of band instead of adding type annotation to the actual selectors file? |
@dmsnell I just started this one without realizing selectors.js can now be easily renamed to selectors.ts. I had a much harder time doing that last month :-) Let's rebase and apply these changes directly. |
0f1e344
to
759d009
Compare
Ah yes, our doc building tool throw an error when the TS signatures are mixed with jsDoc:
I'll just convert all the jsDoc annotations into TypeScript ones, then. |
It seems like it's not about mixing the jsDoc annotations with the TypeScript syntax. It's about the TypeScript syntax. I converted the entire selectors.ts file to rely on it, yet the error persists. A quick grep tells me this is the first attempt to use autogenerated annotations with a
|
That thing keeps giving us more and more trouble; @mirka recently raised an issue for React components wanting to add descriptions to arguments inline. /** Does a thing */
function ({
/** Name of thing */
name: string,
/** How many times you want to do the thing. */
count: number,
}) => { … } cc: @sarayourfriend. I'm not sure what to do because I don't want to divert energy to the docs generation, but I feel like this is going to hit us more and more as we move to TypeScript |
In #40025 we ran into an obstacle when transitioning code into TypeScript, namely that our `docgen` tool is unable to find the associated types for parameters which are documented in JSDoc comments if the parameters are for the returned function from a call to some wrapping function. In this patch we're adding two special cases for selectors that call `createSelector` and `createRegistrySelector` to allow our `docgen` tool to analyze those inner functions which represent the actual selector. Fundamentally we should be asking TypeScript for the inferred types of the function and its parameters but given that we don't have a current mechanism to do that this issue remains a blocker for broader TypeScript work. Because of that we're introducing hard-coded special cases for these common selector wrappers so that we can unblock the TypeScript work without introducing a generic compromise with potentially-harmful side-effects, such as might happen if we were to always return the first argument of a call expression.
The main issue with In #40236 I've proposed a hard-coded solution to |
In #40025 we ran into an obstacle when transitioning code into TypeScript, namely that our `docgen` tool is unable to find the associated types for parameters which are documented in JSDoc comments if the parameters are for the returned function from a call to some wrapping function. In this patch we're adding two special cases for selectors that call `createSelector` and `createRegistrySelector` to allow our `docgen` tool to analyze those inner functions which represent the actual selector. Fundamentally we should be asking TypeScript for the inferred types of the function and its parameters but given that we don't have a current mechanism to do that this issue remains a blocker for broader TypeScript work. Because of that we're introducing hard-coded special cases for these common selector wrappers so that we can unblock the TypeScript work without introducing a generic compromise with potentially-harmful side-effects, such as might happen if we were to always return the first argument of a call expression.
…#40236) In #40025 we ran into an obstacle when transitioning code into TypeScript, namely that our `docgen` tool is unable to find the associated types for parameters which are documented in JSDoc comments if the parameters are for the returned function from a call to some wrapping function. In this patch we're adding two special cases for selectors that call `createSelector` and `createRegistrySelector` to allow our `docgen` tool to analyze those inner functions which represent the actual selector. Fundamentally we should be asking TypeScript for the inferred types of the function and its parameters but given that we don't have a current mechanism to do that this issue remains a blocker for broader TypeScript work. Because of that we're introducing hard-coded special cases for these common selector wrappers so that we can unblock the TypeScript work without introducing a generic compromise with potentially-harmful side-effects, such as might happen if we were to always return the first argument of a call expression.
3a37e20
to
85df8a7
Compare
…ess/gutenberg into ts/core-data-selectors-signatures
@dmsnell Hm it is quite clever, still I'd rather just call that argument
This would be pretty great! And I think we'll get there when integrating the types from
I don't like using these lengthy infer traps, but I believe that's the way here. |
- _kind_ `string`: Entity kind. | ||
- _name_ `string`: Entity name. | ||
- _recordId_ `string`: Record's id. | ||
- _recordId_ `RecordKey`: Record's id. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like how it says RecordKey
here, I'd rather have it say string | number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not? because of the auto-generated documentation?
if that's the case I don't think we can care too much about this problem unless and until we radically change our docs. this is going to be a problem with more and more TypeScript code since this is a win for the types but masks the actual type in the docs. we need hyper-links in our docs to jump to and provide a tooltip description for the types like RecordKey
here.
unless we get those hyperlinks/tooltips I don't know if there's a good workaround that we should chase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just don't like how it doesn't tell me what a RecordKey is. How do I construct this type as a user of this code? You nailed it here:
we need hyper-links in our docs to jump to and provide a tooltip description for the types like RecordKey here.
Once we have that, we're golden. I don't think it needs to be necessarily hard, too. Perhaps there are even existing documentation tools that do that and that wouldn't be too hard to leverage.
@dmsnell Yay, all checks are finally green here. Thank you so much for your help with the documentation tools! I'd say this is ready to be reviewed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is quite clever, still I'd rather just call that argument name, otherwise I'm not sure what it means as I read the code.
it wasn't the point of the work, just a side-effect. style preferences aside, some languages offer this as a first-class element to the language as a way of giving more appropriate names to parameters from the outside and inside.
for example,
function compute( $raw_value: value ) {
$value = clean( $raw_value );
…
}
Again, not a big deal or the goal, just a side-effect that has its uses.
Things mostly look good here. There's a lot of room for improvement which I think is fine to do in follow-up work.
- Get rid of
Object
- Cleanup use of
RecordKey
- Expand
State
- Audit
any | ?
types.
The any | ?
types could honestly go in now before merging and it would be good. I get the purpose of any | null
(or at least I can think of a reasonable use-case) but I don't think it warrants the oddity in the type. A stop-gap could be the use of a nullable type like Maybe
or Optional
.
type Optional<T> = T | null;
And with that we still get any | null
if we do Optional<any>
but at least we can see that it was intentional and not an oversight.
The state
parameter in there for docgen
is the other thing I'd like to double-check before merge. Is that still necessary? If it is then please go ahead and merge; if it isn't let's get that out before merging.
Either way, thanks for moving on this very large task. Can't wait until we're getting to the even-more exciting bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to mark as approved in my submission…
…ess/gutenberg into ts/core-data-selectors-signatures
0fb2ada
to
63274af
Compare
Thank you for explaining, I misunderstood your intention. It is a nice feature for sure, and it could come handy for our work here. Also, I've addressed all your notes, included the additional State types – let's get this one in and address any follow-up work in a follow-up PR. |
…ndants In #40025 we ran into a situation where a `createSelector()` selector is building an opaque value memoized on state values as dependencies. While this is an unexpected use of a selector it's legitimate and required leaving some additional code and an explanatory comment in order to avoid breaking the `docgen` process. In this patch we're adding recognition for that second argument to the `createSelector()` function, the `getDependants()` function, and if that function has more parameters than the `selector` itself we'll cheat and act like its parameters were listed on the selector. This will likely only happen in practice when the selector ignores `state` but it's pluasible someone might go further and use other inputs in the dependency selection but ignore them on the actual state creation.
…ndants In #40025 we ran into a situation where a `createSelector()` selector is building an opaque value memoized on state values as dependencies. While this is an unexpected use of a selector it's legitimate and required leaving some additional code and an explanatory comment in order to avoid breaking the `docgen` process. In this patch we're adding recognition for that second argument to the `createSelector()` function, the `getDependants()` function, and if that function has more parameters than the `selector` itself we'll cheat and act like its parameters were listed on the selector. This will likely only happen in practice when the selector ignores `state` but it's pluasible someone might go further and use other inputs in the dependency selection but ignore them on the actual state creation.
*/ | ||
export function getCurrentUser( state ) { | ||
export function getCurrentUser( state: State ): User< 'edit' > { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context here should be 'view'
instead of 'edit'
, otherwise it will set the wrong types/properties which are not available in current user object, for example, user.capabilities
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #68045 to fix it.
What?
This is a subset of #39025 – a mega branch that proposes TypeScript signatures for all
@wordpress/core-data
selectors.This PR removes the jsDoc annotations like
@param {Object}
and@return {string}
in favor of their TypeScript counterparts. This is the first step towards introducing exhaustive type signatures after #40024 is merged.There are two shortcomings for now:
The doc tool that handles(edit: should be fixed by docgen: Unwrap wrapped selectors when inferring types of JSDoc params #40236)START TOKEN(Autogenerated selectors
fails. Apparently this is the first instance of mixing it with TypeScript files in the repo.createRegistrySelector
isn't typed at the moment, which means that calling it likeconst mySelector = createRegistrySelector( select => ( state: State ) => null )
produces amySelector
variable of typeany
. We could solve it here by adding a.d.ts
file to provide the missing signature, or we could solve this problem in@wordpress/data
instead.Testing Instructions
Confirm the checks are green and that no entity configuration got changed when I split the large array into atomic declarations. The changes here should only affect the type system so there is nothing to test in the browser.
cc @dmsnell @jsnajdr @youknowriad @sarayourfriend @getdave @draganescu @scruffian