-
Notifications
You must be signed in to change notification settings - Fork 209
feat: Improved Promise handling to support packages like Prisma #1924
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
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
717bb1a
fix: errors for sourceless nodes
arthurfiorette 68b6c0b
initial promise support
arthurfiorette 09ff47a
Merge remote-tracking branch 'upstream/next' into next
arthurfiorette 19e481f
revert
arthurfiorette 2d36fbf
feat: final implementation
arthurfiorette 2517d66
Merge remote-tracking branch 'upstream/next' into next
arthurfiorette a13545c
fix: imports
arthurfiorette c78f38c
code
arthurfiorette 8f80837
lint
arthurfiorette 624736e
code
arthurfiorette 7944bc7
fix: tests
arthurfiorette 697805f
expose internals
arthurfiorette a05e492
i was wrong
arthurfiorette 9ca48e0
fix: entire check
arthurfiorette ec698e2
fix: lint
arthurfiorette 3b74479
type
arthurfiorette a4a497f
somehow tests were getting this out of order
arthurfiorette 2a3e4ce
fix: removed unused method
arthurfiorette 42d2bd9
feat: added unnamed class test
arthurfiorette e5daa4a
fix: test
arthurfiorette d6ed5a7
documented promise unwrapping
arthurfiorette 6b11f65
Update src/NodeParser/FunctionNodeParser.ts
arthurfiorette 8100f43
fix: removed allowUnionTypes
arthurfiorette e804590
chore: style
arthurfiorette File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,6 @@ node_modules/ | |
# local config for auto | ||
.env | ||
|
||
# Other package managers | ||
pnpm-lock.yaml | ||
package-lock.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ | |
"@types/jest": "^29.5.12", | ||
"@types/node": "^20.12.7", | ||
"@types/normalize-path": "^3.0.2", | ||
"@types/ts-expose-internals": "npm:ts-expose-internals@^5.4.5", | ||
"ajv": "^8.12.0", | ||
"ajv-formats": "^3.0.1", | ||
"auto": "^11.1.6", | ||
|
@@ -94,6 +95,5 @@ | |
"debug": "tsx --inspect-brk ts-json-schema-generator.ts", | ||
"run": "tsx ts-json-schema-generator.ts", | ||
"release": "yarn build && auto shipit" | ||
}, | ||
"packageManager": "[email protected]" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import ts from "typescript"; | ||
import { Context, type NodeParser } from "../NodeParser.js"; | ||
import type { SubNodeParser } from "../SubNodeParser.js"; | ||
import { AliasType } from "../Type/AliasType.js"; | ||
import type { BaseType } from "../Type/BaseType.js"; | ||
import { DefinitionType } from "../Type/DefinitionType.js"; | ||
import { getKey } from "../Utils/nodeKey.js"; | ||
|
||
/** | ||
* Needs to be registered before 261, 260, 230, 262 node kinds | ||
*/ | ||
export class PromiseNodeParser implements SubNodeParser { | ||
public constructor( | ||
protected typeChecker: ts.TypeChecker, | ||
protected childNodeParser: NodeParser, | ||
) {} | ||
|
||
public supportsNode(node: ts.Node): boolean { | ||
if ( | ||
// 261 interface PromiseInterface extends Promise<T> | ||
!ts.isInterfaceDeclaration(node) && | ||
// 260 class PromiseClass implements Promise<T> | ||
!ts.isClassDeclaration(node) && | ||
// 230 Promise<T> | ||
!ts.isExpressionWithTypeArguments(node) && | ||
// 262 type PromiseAlias = Promise<T>; | ||
!ts.isTypeAliasDeclaration(node) | ||
) { | ||
return false; | ||
} | ||
|
||
const type = this.typeChecker.getTypeAtLocation(node); | ||
|
||
const awaitedType = this.typeChecker.getAwaitedType(type); | ||
|
||
// ignores non awaitable types | ||
if (!awaitedType) { | ||
return false; | ||
} | ||
|
||
// If the awaited type differs from the original type, the type extends promise | ||
// Awaited<Promise<T>> -> T (Promise<T> !== T) | ||
// Awaited<Y> -> Y (Y === Y) | ||
if (awaitedType === type) { | ||
return false; | ||
} | ||
|
||
// In types like: A<T> = T, type C = A<1>, C has the same type as A<1> and 1, | ||
// the awaitedType is NOT the same reference as the type, so a assignability | ||
// check is needed | ||
return ( | ||
!this.typeChecker.isTypeAssignableTo(type, awaitedType) && | ||
!this.typeChecker.isTypeAssignableTo(awaitedType, type) | ||
); | ||
} | ||
|
||
public createType( | ||
node: ts.InterfaceDeclaration | ts.ClassDeclaration | ts.ExpressionWithTypeArguments | ts.TypeAliasDeclaration, | ||
context: Context, | ||
): BaseType { | ||
const type = this.typeChecker.getTypeAtLocation(node); | ||
const awaitedType = this.typeChecker.getAwaitedType(type)!; // supportsNode ensures this | ||
const awaitedNode = this.typeChecker.typeToTypeNode(awaitedType, undefined, ts.NodeBuilderFlags.IgnoreErrors); | ||
|
||
if (!awaitedNode) { | ||
throw new Error( | ||
`Could not find awaited node for type ${node.pos === -1 ? "<unresolved>" : node.getText()}`, | ||
); | ||
} | ||
|
||
const baseNode = this.childNodeParser.createType(awaitedNode, new Context(node)); | ||
|
||
const name = this.getNodeName(node); | ||
|
||
// Nodes without name should just be their awaited type | ||
// export class extends Promise<T> {} -> T | ||
// export class A extends Promise<T> {} -> A (ref to T) | ||
if (!name) { | ||
return baseNode; | ||
} | ||
|
||
return new DefinitionType(name, new AliasType(`promise-${getKey(node, context)}`, baseNode)); | ||
} | ||
|
||
private getNodeName( | ||
node: ts.InterfaceDeclaration | ts.ClassDeclaration | ts.ExpressionWithTypeArguments | ts.TypeAliasDeclaration, | ||
) { | ||
if (ts.isExpressionWithTypeArguments(node)) { | ||
if (!ts.isHeritageClause(node.parent)) { | ||
throw new Error("Expected ExpressionWithTypeArguments to have a HeritageClause parent"); | ||
} | ||
|
||
return node.parent.parent.name?.getText(); | ||
} | ||
|
||
return node.name?.getText(); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
export type A = { a: string; b: number[] }; | ||
|
||
export type PromiseAlias = Promise<A>; | ||
|
||
export class PromiseClass extends Promise<A> {} | ||
|
||
export interface PromiseInterface extends Promise<A> {} | ||
|
||
export type LikeType = PromiseLike<A>; | ||
|
||
export type PromiseOrAlias = Promise<A> | A; | ||
|
||
export type LikeOrType = PromiseLike<A> | A; | ||
|
||
export type AndPromise = Promise<A> & { a: string }; | ||
|
||
export type AndLikePromise = PromiseLike<A> & { a: string }; | ||
|
||
// Should not be present | ||
export default class extends Promise<A> {} | ||
|
||
export class LikeClass implements PromiseLike<A> { | ||
then<TResult1 = A, TResult2 = never>( | ||
onfulfilled?: ((value: A) => TResult1 | PromiseLike<TResult1>) | null | undefined, | ||
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined | ||
): PromiseLike<TResult1 | TResult2> { | ||
return new Promise(() => {}); | ||
} | ||
} | ||
|
||
export abstract class LikeAbstractClass implements PromiseLike<A> { | ||
abstract then<TResult1 = A, TResult2 = never>( | ||
onfulfilled?: ((value: A) => TResult1 | PromiseLike<TResult1>) | null | undefined, | ||
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined | ||
); | ||
} | ||
|
||
export interface LikeInterface extends PromiseLike<A> {} | ||
|
||
// Prisma has a base promise type just like this | ||
export interface WithProperty extends Promise<A> { | ||
[Symbol.toStringTag]: "WithProperty"; | ||
} | ||
|
||
export interface ThenableInterface { | ||
then<TResult1 = A, TResult2 = never>( | ||
onfulfilled?: ((value: A) => TResult1 | PromiseLike<TResult1>) | null | undefined, | ||
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined | ||
): PromiseLike<TResult1 | TResult2>; | ||
} | ||
|
||
export class ThenableClass { | ||
then<TResult1 = A, TResult2 = never>( | ||
onfulfilled?: ((value: A) => TResult1 | PromiseLike<TResult1>) | null | undefined, | ||
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined | ||
): PromiseLike<TResult1 | TResult2> { | ||
return new Promise(() => {}); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"definitions": { | ||
"A": { | ||
"additionalProperties": false, | ||
"properties": { | ||
"a": { | ||
"type": "string" | ||
}, | ||
"b": { | ||
"items": { | ||
"type": "number" | ||
}, | ||
"type": "array" | ||
} | ||
}, | ||
"required": [ | ||
"a", | ||
"b" | ||
], | ||
"type": "object" | ||
}, | ||
"AndLikePromise": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"AndPromise": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"LikeAbstractClass": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"LikeClass": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"LikeInterface": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"LikeOrType": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"LikeType": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"PromiseAlias": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"PromiseClass": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"PromiseInterface": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"PromiseOrAlias": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"ThenableClass": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"ThenableInterface": { | ||
"$ref": "#/definitions/A" | ||
}, | ||
"WithProperty": { | ||
"$ref": "#/definitions/A" | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27149,6 +27149,8 @@ | |
}, | ||
"SingleDefUnitChannel": { | ||
"enum": [ | ||
"text", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somehow this type started getting out of order and breaking CI: https://github.com/vega/ts-json-schema-generator/actions/runs/9105537200/job/25031273060#step:7:25 I changed its order so tests can pass, I'm also almost sure |
||
"shape", | ||
"x", | ||
"y", | ||
"xOffset", | ||
|
@@ -27173,9 +27175,7 @@ | |
"strokeDash", | ||
"size", | ||
"angle", | ||
"shape", | ||
"key", | ||
"text", | ||
"href", | ||
"url", | ||
"description" | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Can we move this logic into the PromiseNodeParser?
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.
Actually not, It might be a skill issue of mine but I couldn't get it to work without this statement.
When I remove it, this is the error thrown: