feat(unist): add generic types for better type checking#54413
feat(unist): add generic types for better type checking#54413typescript-bot merged 7 commits intoDefinitelyTyped:masterfrom JounQin:feat/unist_generic
Conversation
|
@JounQin Thank you for submitting this PR! This is a live comment which I will keep updated. 1 package in this PR
@JounQin: I see that you have added yourself as an owner, are you sure you want to become an owner? Code ReviewsBecause you edited one package and updated the tests (👏), I can help you merge this PR once someone else signs off on it. You can test the changes of this PR in the Playground. Status
All of the items on the list are green. To merge, you need to post a comment including the string "Ready to merge" to bring in your changes. Diagnostic Information: What the bot saw about this PR{
"type": "info",
"now": "-",
"pr_number": 54413,
"author": "JounQin",
"headCommitOid": "b6a5006fa5f11643892420bc198b94ebc0458716",
"lastPushDate": "2021-07-10T12:18:02.000Z",
"lastActivityDate": "2021-07-15T00:19:15.000Z",
"maintainerBlessed": "Waiting for Code Reviews",
"mergeOfferDate": "2021-07-13T01:52:30.000Z",
"mergeRequestDate": "2021-07-15T00:19:15.000Z",
"mergeRequestUser": "JounQin",
"hasMergeConflict": false,
"isFirstContribution": false,
"tooManyFiles": false,
"popularityLevel": "Critical",
"pkgInfo": [
{
"name": "unist",
"kind": "edit",
"files": [
{
"path": "types/unist/index.d.ts",
"kind": "definition"
},
{
"path": "types/unist/unist-tests.ts",
"kind": "test"
}
],
"owners": [
"bizen241",
"lujun2",
"hrajchert",
"wooorm",
"rokt33r",
"GuiltyDolphin"
],
"addedOwners": [
"JounQin"
],
"deletedOwners": [],
"popularityLevel": "Critical"
}
],
"reviews": [
{
"type": "approved",
"reviewer": "GuiltyDolphin",
"date": "2021-07-10T12:46:21.000Z",
"isMaintainer": false
},
{
"type": "stale",
"reviewer": "remcohaszing",
"date": "2021-07-10T11:01:56.000Z",
"abbrOid": "afe2c0e"
},
{
"type": "stale",
"reviewer": "ChristianMurphy",
"date": "2021-07-10T05:58:03.000Z",
"abbrOid": "afe2c0e"
},
{
"type": "stale",
"reviewer": "wooorm",
"date": "2021-07-09T19:40:50.000Z",
"abbrOid": "138b76f"
}
],
"mainBotCommentID": 877339624,
"ciResult": "pass"
} |
|
🔔 @bizen241 @lujun2 @hrajchert @wooorm @Rokt33r @GuiltyDolphin — please review this PR in the next few days. Be sure to explicitly select |
|
@JounQin The CI build failed! Please review the logs for more information. Once you've pushed the fixes, the build will automatically re-run. Thanks! Note: builds which are failing do not end up on the list of PRs for the DT maintainers to review. |
|
@JounQin what do you mean by "for better type checking"? As far as I can tell, the type-checking capabilities remain the same here - for both That said, I've used a similar solution in my own programming (though not with |
const assertStringLiertal = (node: Node): node is Liertal<string> => 'value' in node && typeof (node as Literal).value === 'string'
// before
const assertStringLiertal = (node: Node): node is Liertal & { value: string } => 'value' in node && typeof (node as Literal).value === 'string'
declare const parent: Parent
const literals = parent.children as Literal[] // error now
// after
declare const parent: Parent<Literal>
const literals = parent.children // already `Literal[]`, no error |
|
@JounQin The CI build failed! Please review the logs for more information. Once you've pushed the fixes, the build will automatically re-run. Thanks! Note: builds which are failing do not end up on the list of PRs for the DT maintainers to review. |
|
@wooorm Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review? |
ChristianMurphy
left a comment
There was a problem hiding this comment.
Another consideration here, the unist and unified community use significant amount of JSDoc based TypeScript, where generics can be a bit more ackward and conferencing is leaned on more heavily than TypeScript with TS-syntax.
Could we add some test cases for inferencing?
Something like
function exampleNodeUtil(node: Node) {}
const inferredNode = { type: 'example' }
const inferredNotNode = { notType: 'whoops' }
exampleNodeUtil(inferredNode)
// $ExpectError
exampleNodeUtil(inferredNotNode)for Node, Literal, and Parent?
|
@ChristianMurphy, @wooorm Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review? |
@ChristianMurphy Done. |
|
@wooorm Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review? |
|
@wooorm, @ChristianMurphy Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review? |
ChristianMurphy
left a comment
There was a problem hiding this comment.
Thanks @JounQin!
|
@remcohaszing is the only other person who hasn’t approved or rejected, so maybe he has more thoughts. |
I'm of the same mind - I'm not convinced the change really adds anything that can't already be achieved by using extending interfaces (which may actually be simpler/cleaner to use in general), but it doesn't break anything (functional) AFAICS... So yeah, up to @remcohaszing at this point I think. Having said that, this does introduce the issue with error messages I mentioned before (the main one that would be encountered is probably that lots of default parameters would start showing in types/error messages), which could be a potential downside. I don't know how long it'd take for error messages to get fixed though, and that doesn't really affect the functionality. |
|
I agree with @wooorm and @GuiltyDolphin. Earlier I had ideas very similar to this PR. However, the same goal can be achieved by extending Because of the more verbose error messages I’m thinking it might be better not to merge this. |
|
@remcohaszing Extending |
That’s true. I’m not necessarily for or against these changes. Feel free to merge. |
|
Yeah we can see how this goes and what it does to the error messages, might be fine. |
|
OK, then I'm going to merge. |
|
Ready to merge |
|
I just published |
|
This is breaking stuff like: https://github.com/unifiedjs/unified/runs/3074196319?check_suite_focus=true#step:5:12 |
|
@wooorm I think that is due to
The error is coming from |
|
Close, it’s because |
|
|
Weird. My editor shows |
|
This PR: I think the lowercase |
No, of course not. It means a true |
|
There is a test case: // $ExpectError
type DataType = NodeData<Node<string>>;Because |
Maybe a bug of TypeScript itself? |
|
@wooorm Maybe you can try I believe it will produce better |
|
All the code in unifiedjs/unified is checked already: https://github.com/unifiedjs/unified/blob/2323d38e2c74708f5a453ebfea42f80e0454a435/tsconfig.json#L9. This might indeed be a bug with TS checking JS. In typescript the value does become The reason I don’t use |
|
|
@wooorm Quick example of In the first case, the typechecker is saying "Oh, we aren't allowed to make any assumptions about x's type here (without narrowing), so we don't know this is a number, so this is bad." In the second case, the typechecker is saying "I'm not even going to check whether this operation is okay, because you told me using |
|
Apparently, when checking JS, TS does see |
|
@wooorm I don't think so. As you said And for interface A {
key: string
}
type B<T extends Record<string, unknown> = T
type C = B<A> // error, A doesn't have index signature
type D<T extends object> = T
type E = D<A> // no errorSo I disagree with |
Generics were added in DefinitelyTypedGH-54413. This commit undoes generics. I was neutral on generics 2 years ago, but now that our entire ecosystem is typed, I am against. There were three generics: * `children` and `value`: unist is supposed to be an abstract interface that has to be extended before being used. *Those* types are supposed to choose a `type` field, specify which other fields exist, and specify particular children or value. * `data`: I don’t think generic data is a good idea. `Data` is a *shared* space that *several* plugins/utilities can access to store whatever they want. A generic makes it possible to hide conflicts. Generics also hide what is actually supported. We’ve been adding an alternative in other places (micromark, vfile, mdast/hast/etc for content types of nodes), and I think we should do the same for data: use an interface that can be registered onto. While data is not useful on `unist`, I plan to do the same to `mdast` and `hast`, where it gets very useful: `mdast-util-to-hast` for example sets a [`meta` field on `hast`s `element.data`](https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd60673/lib/handlers/code.js#L41), and it adds support for `hProperties`/`hChildren`/`hName` on all mdast nodes. If that utility registers with `hast` `Element.Data` and `mdast` `Node.data`, all other utilities and plugins can get typed data. Lastly, I searched GH for `unist` and `Node<` and did not see much use, expect for ugly generated types, such as: https://github.com/nachoaldamav/hardlinks-test/blob/f40b5ca/data/unist-util-map/index.d.ts#L33.
Please fill in this template.
npm test <package to test>.Select one of these and delete the others:
If adding a new definition:
.d.tsfiles generated via--declarationdts-gen --dt, not by basing it on an existing project.tslint.jsonshould contain{ "extends": "dtslint/dt.json" }, and no additional rules.tsconfig.jsonshould havenoImplicitAny,noImplicitThis,strictNullChecks, andstrictFunctionTypesset totrue.If changing an existing definition:
If removing a declaration:
notNeededPackages.json.