From e8b5727d7d65827cdc130afebc35f1003a30dc44 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 9 Mar 2024 15:40:51 -0500 Subject: [PATCH] feat: `Type.prototype.isAssignableTo` (#1517) --- deno/ts_morph.d.ts | 4 ++++ deno/ts_morph.js | 6 ++++++ docs/details/types.md | 12 ++++++++++++ packages/ts-morph/lib/ts-morph.d.ts | 4 ++++ packages/ts-morph/src/compiler/tools/TypeChecker.ts | 5 +++++ packages/ts-morph/src/compiler/types/Type.ts | 7 +++++++ .../ts-morph/src/tests/compiler/type/typeTests.ts | 9 +++++++++ 7 files changed, 47 insertions(+) diff --git a/deno/ts_morph.d.ts b/deno/ts_morph.d.ts index aaaf9635a..13a79dd3c 100644 --- a/deno/ts_morph.d.ts +++ b/deno/ts_morph.d.ts @@ -9856,6 +9856,8 @@ export declare class TypeChecker { * @param typeReference - Type reference. */ getTypeArguments(typeReference: Type): Type[]; + /** Checks if a type is assignable to another type. */ + isTypeAssignableTo(sourceType: Type, targetType: Type): boolean; /** Gets the shorthand assignment value symbol of the provided node. */ getShorthandAssignmentValueSymbol(node: Node): Symbol | undefined; } @@ -10004,6 +10006,8 @@ export declare class Type { getSymbol(): Symbol | undefined; /** Gets the symbol of the type or throws. */ getSymbolOrThrow(message?: string | (() => string)): Symbol; + /** Gets if the type is assignable to another type. */ + isAssignableTo(target: Type): boolean; /** Gets if this is an anonymous type. */ isAnonymous(): boolean; /** Gets if this is an any type. */ diff --git a/deno/ts_morph.js b/deno/ts_morph.js index d602fc99c..441579b94 100644 --- a/deno/ts_morph.js +++ b/deno/ts_morph.js @@ -17876,6 +17876,9 @@ class TypeChecker { return this.compilerObject.getTypeArguments(typeReference.compilerType) .map(arg => this.#context.compilerFactory.getType(arg)); } + isTypeAssignableTo(sourceType, targetType) { + return this.compilerObject.isTypeAssignableTo(sourceType.compilerType, targetType.compilerType); + } #getDefaultTypeFormatFlags(enclosingNode) { let formatFlags = (TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.NoTruncation | TypeFormatFlags.UseFullyQualifiedType | TypeFormatFlags.WriteTypeArgumentsOfSignature); @@ -18313,6 +18316,9 @@ class Type { getSymbolOrThrow(message) { return errors.throwIfNullOrUndefined(this.getSymbol(), message ?? "Expected to find a symbol."); } + isAssignableTo(target) { + return this._context.typeChecker.isTypeAssignableTo(this, target); + } isAnonymous() { return this.#hasObjectFlag(ObjectFlags.Anonymous); } diff --git a/docs/details/types.md b/docs/details/types.md index c3cdddad8..a8c81c690 100644 --- a/docs/details/types.md +++ b/docs/details/types.md @@ -16,6 +16,18 @@ There are other ways for accessing a type. For example: const returnType = functionDeclaration.getReturnType(); ``` +### Checking type is assignable to another type + +Note: Requires ts-morph 22+ + +To check if a type is assignable to another type, use the `isAssignableTo` method: + +```ts +if (stringLitType.isAssignableTo(stringType)) { + // ... +} +``` + ### Compiler Type The underlying compiler type can be accessed via: diff --git a/packages/ts-morph/lib/ts-morph.d.ts b/packages/ts-morph/lib/ts-morph.d.ts index 3c2cb941a..a78772e9c 100644 --- a/packages/ts-morph/lib/ts-morph.d.ts +++ b/packages/ts-morph/lib/ts-morph.d.ts @@ -9856,6 +9856,8 @@ export declare class TypeChecker { * @param typeReference - Type reference. */ getTypeArguments(typeReference: Type): Type[]; + /** Checks if a type is assignable to another type. */ + isTypeAssignableTo(sourceType: Type, targetType: Type): boolean; /** Gets the shorthand assignment value symbol of the provided node. */ getShorthandAssignmentValueSymbol(node: Node): Symbol | undefined; } @@ -10004,6 +10006,8 @@ export declare class Type { getSymbol(): Symbol | undefined; /** Gets the symbol of the type or throws. */ getSymbolOrThrow(message?: string | (() => string)): Symbol; + /** Gets if the type is assignable to another type. */ + isAssignableTo(target: Type): boolean; /** Gets if this is an anonymous type. */ isAnonymous(): boolean; /** Gets if this is an any type. */ diff --git a/packages/ts-morph/src/compiler/tools/TypeChecker.ts b/packages/ts-morph/src/compiler/tools/TypeChecker.ts index 4ca6bcb67..a1e2cf2cc 100644 --- a/packages/ts-morph/src/compiler/tools/TypeChecker.ts +++ b/packages/ts-morph/src/compiler/tools/TypeChecker.ts @@ -253,6 +253,11 @@ export class TypeChecker { .map(arg => this.#context.compilerFactory.getType(arg)); } + /** Checks if a type is assignable to another type. */ + isTypeAssignableTo(sourceType: Type, targetType: Type) { + return this.compilerObject.isTypeAssignableTo(sourceType.compilerType, targetType.compilerType); + } + /** @internal */ #getDefaultTypeFormatFlags(enclosingNode?: Node) { let formatFlags = (TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.NoTruncation | TypeFormatFlags.UseFullyQualifiedType diff --git a/packages/ts-morph/src/compiler/types/Type.ts b/packages/ts-morph/src/compiler/types/Type.ts index 075198449..3b29d2031 100644 --- a/packages/ts-morph/src/compiler/types/Type.ts +++ b/packages/ts-morph/src/compiler/types/Type.ts @@ -365,6 +365,13 @@ export class Type { return errors.throwIfNullOrUndefined(this.getSymbol(), message ?? "Expected to find a symbol."); } + /** + * Gets if the type is assignable to another type. + */ + isAssignableTo(target: Type) { + return this._context.typeChecker.isTypeAssignableTo(this, target); + } + /** * Gets if this is an anonymous type. */ diff --git a/packages/ts-morph/src/tests/compiler/type/typeTests.ts b/packages/ts-morph/src/tests/compiler/type/typeTests.ts index dbb947dae..8c764b7ca 100644 --- a/packages/ts-morph/src/tests/compiler/type/typeTests.ts +++ b/packages/ts-morph/src/tests/compiler/type/typeTests.ts @@ -992,4 +992,13 @@ let unknownType: unknown; runTest("let myType: string;", undefined); }); }); + + describe(nameof("isAssignableTo"), () => { + it("should be assignable to when so", () => { + const { firstType, sourceFile } = getTypeFromText("let firstType: string; let secondType: 'test';"); + const secondType = sourceFile.getVariableDeclarationOrThrow("secondType").getType(); + expect(firstType.isAssignableTo(secondType)).to.be.false; + expect(secondType.isAssignableTo(firstType)).to.be.true; + }); + }); });