From 20222433eb50a8c9f01f70fee17e471e37d2907d Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 21 Jan 2025 12:03:45 -0800 Subject: [PATCH 1/2] add type mapping tests and improve boolean mapping --- .gitignore | 1 + openrewrite/src/javascript/typeMapping.ts | 188 ++++++++++-------- .../test/javascript/typeMapping.test.ts | 182 +++++++++++++++++ 3 files changed, 288 insertions(+), 83 deletions(-) create mode 100644 openrewrite/test/javascript/typeMapping.test.ts diff --git a/.gitignore b/.gitignore index 9e3f518f..73eb0b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ bin/ .vscode/* /rewrite-javascript-remote-server/src/main/resources/node-server/node_modules/ /rewrite-javascript-remote-server/src/main/resources/node-server/package-lock.json +.DS_Store \ No newline at end of file diff --git a/openrewrite/src/javascript/typeMapping.ts b/openrewrite/src/javascript/typeMapping.ts index 9b9e0a3c..5e4cbc59 100644 --- a/openrewrite/src/javascript/typeMapping.ts +++ b/openrewrite/src/javascript/typeMapping.ts @@ -1,104 +1,126 @@ import * as ts from "typescript"; -import {JavaType} from "../java"; +import { JavaType } from "../java"; export class JavaScriptTypeMapping { + private readonly typeCache: Map = new Map(); + private readonly regExpSymbol: ts.Symbol | undefined; - private readonly typeCache: Map = new Map(); - private readonly regExpSymbol: ts.Symbol | undefined; + constructor(private readonly checker: ts.TypeChecker) { + this.regExpSymbol = checker.resolveName( + "RegExp", + undefined, + ts.SymbolFlags.Type, + false + ); + } - constructor(private readonly checker: ts.TypeChecker) { - this.regExpSymbol = checker.resolveName('RegExp', undefined, ts.SymbolFlags.Type, false); + type(node: ts.Node): JavaType | null { + let type: ts.Type | undefined; + if (ts.isExpression(node)) { + type = this.checker.getTypeAtLocation(node); + } else if (ts.isTypeNode(node)) { + type = this.checker.getTypeFromTypeNode(node); } - type(node: ts.Node): JavaType | null { - let type: ts.Type | undefined; - if (ts.isExpression(node)) { - type = this.checker.getTypeAtLocation(node); - } else if (ts.isTypeNode(node)) { - type = this.checker.getTypeFromTypeNode(node); - } + return type ? this.getType(type) : null; + } - return type ? this.getType(type) : null; + private getType(type: ts.Type) { + const signature = this.getSignature(type); + const existing = this.typeCache.get(signature); + if (existing) { + return existing; } + const result = this.createType(type, signature); + this.typeCache.set(signature, result); + return result; + } - private getType(type: ts.Type) { - const signature = this.getSignature(type); - const existing = this.typeCache.get(signature); - if (existing) { - return existing; - } - const result = this.createType(type, signature); - this.typeCache.set(signature, result); - return result; - } + private getSignature(type: ts.Type) { + // FIXME for classes we need to include the containing module / package in the signature and probably include in the qualified name + return this.checker.typeToString(type); + } - private getSignature(type: ts.Type) { - // FIXME for classes we need to include the containing module / package in the signature and probably include in the qualified name - return this.checker.typeToString(type); - } + primitiveType(node: ts.Node): JavaType.Primitive { + const type = this.type(node); + return type instanceof JavaType.Primitive + ? type + : JavaType.Primitive.of(JavaType.PrimitiveKind.None); + } - primitiveType(node: ts.Node): JavaType.Primitive { - const type = this.type(node); - return type instanceof JavaType.Primitive ? type : JavaType.Primitive.of(JavaType.PrimitiveKind.None); + variableType(node: ts.NamedDeclaration): JavaType.Variable | null { + if (ts.isVariableDeclaration(node)) { + const symbol = this.checker.getSymbolAtLocation(node.name); + if (symbol) { + const type = this.checker.getTypeOfSymbolAtLocation(symbol, node); + } } + return null; + } - variableType(node: ts.NamedDeclaration): JavaType.Variable | null { - if (ts.isVariableDeclaration(node)) { - const symbol = this.checker.getSymbolAtLocation(node.name); - if (symbol) { - const type = this.checker.getTypeOfSymbolAtLocation(symbol, node); - } - } - return null; - } + methodType(node: ts.Node): JavaType.Method | null { + return null; + } - methodType(node: ts.Node): JavaType.Method | null { - return null; + private createType(type: ts.Type, signature: string): JavaType { + if (type.isLiteral()) { + if (type.isNumberLiteral()) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Double); + } else if (type.isStringLiteral()) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.String); + } } - private createType(type: ts.Type, signature: string): JavaType { - if (type.isLiteral()) { - if (type.isNumberLiteral()) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.Double); - } else if (type.isStringLiteral()) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.String); - } - } - - if (type.flags === ts.TypeFlags.Null) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.Null); - } else if (type.flags === ts.TypeFlags.Undefined) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.None); - } else if (type.flags === ts.TypeFlags.Number) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.Double); - } else if (type.flags === ts.TypeFlags.String) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.String); - } else if (type.flags === ts.TypeFlags.BooleanLiteral) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.Boolean); - } else if (type.flags === ts.TypeFlags.Void) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.Void); - } else if (type.symbol === this.regExpSymbol) { - return JavaType.Primitive.of(JavaType.PrimitiveKind.String); - } + if (type.flags === ts.TypeFlags.Null) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Null); + } else if (type.flags === ts.TypeFlags.Undefined) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.None); + } else if (type.flags === ts.TypeFlags.Number) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Double); + } else if (type.flags === ts.TypeFlags.String) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.String); + } else if (type.flags === ts.TypeFlags.Void) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Void); + } else if ( + (type.symbol !== undefined && type.symbol === this.regExpSymbol) || + this.checker.typeToString(type) === "RegExp" + ) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.String); + } - if (type.isUnion()) { - let result = new JavaType.Union(); - this.typeCache.set(signature, result); + /** + * TypeScript may assign multiple flags to a single type (e.g., Boolean + Union). + * Using a bitwise check ensures we detect Boolean even if other flags are set. + */ + if ( + type.flags & ts.TypeFlags.Boolean || + type.flags & ts.TypeFlags.BooleanLiteral + ) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Boolean); + } - const types = type.types.map(t => this.getType(t)); - result.unsafeSet(types); - return result; - } else if (type.isClass()) { - // FIXME flags - let result = new JavaType.Class(0, type.symbol.name, JavaType.Class.Kind.Class); - this.typeCache.set(signature, result); - // FIXME unsafeSet - return result; - } + if (type.isUnion()) { + let result = new JavaType.Union(); + this.typeCache.set(signature, result); - // if (ts.isRegularExpressionLiteral(node)) { - // return JavaType.Primitive.of(JavaType.PrimitiveKind.String); - // } - return JavaType.Unknown.INSTANCE; + const types = type.types.map((t) => this.getType(t)); + result.unsafeSet(types); + return result; + } else if (type.isClass()) { + // FIXME flags + let result = new JavaType.Class( + 0, + type.symbol.name, + JavaType.Class.Kind.Class + ); + this.typeCache.set(signature, result); + // FIXME unsafeSet + return result; } -} \ No newline at end of file + + // if (ts.isRegularExpressionLiteral(node)) { + // return JavaType.Primitive.of(JavaType.PrimitiveKind.String); + // } + return JavaType.Unknown.INSTANCE; + } +} diff --git a/openrewrite/test/javascript/typeMapping.test.ts b/openrewrite/test/javascript/typeMapping.test.ts new file mode 100644 index 00000000..ed088c2c --- /dev/null +++ b/openrewrite/test/javascript/typeMapping.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, test } from "@jest/globals"; +import * as ts from "typescript"; +import { JavaScriptTypeMapping } from "../../src/javascript/typeMapping"; +import { JavaType } from "../../src/java"; + +function getFirstVariableInitializer(sourceText: string): ts.Expression { + const sourceFile = ts.createSourceFile( + "test.ts", + sourceText, + ts.ScriptTarget.Latest, + true + ); + const [firstStatement] = sourceFile.statements; + if (!ts.isVariableStatement(firstStatement)) { + throw new Error( + "Expected the first statement to be a variable declaration, but got " + + ts.SyntaxKind[firstStatement.kind] + ); + } + const [firstDeclaration] = firstStatement.declarationList.declarations; + if (!firstDeclaration.initializer) { + throw new Error("The variable has no initializer."); + } + return firstDeclaration.initializer; +} + +function getFirstVariableTypeNode(sourceText: string): ts.TypeNode { + const sourceFile = ts.createSourceFile( + "test.ts", + sourceText, + ts.ScriptTarget.Latest, + true + ); + + const firstStatement = sourceFile.statements[0]; + if (!ts.isVariableStatement(firstStatement)) { + throw new Error( + `Expected first statement to be a variable statement, but got: ${ + ts.SyntaxKind[firstStatement.kind] + }` + ); + } + + const declaration = firstStatement.declarationList.declarations[0]; + if (!declaration.type) { + throw new Error( + `No explicit type declared for: ${declaration.name.getText()}` + ); + } + + return declaration.type; +} + +const createTypeChecker = (sourceText: string): ts.TypeChecker => { + const sourceFile = ts.createSourceFile( + "test.ts", + sourceText, + ts.ScriptTarget.Latest, + true + ); + + const compilerHost = ts.createCompilerHost({}); + compilerHost.getSourceFile = (fileName) => + fileName === "test.ts" ? sourceFile : undefined; + + const program = ts.createProgram({ + rootNames: ["test.ts"], + options: { lib: ["lib.esnext.d.ts"], strict: true }, + host: compilerHost, + }); + + return program.getTypeChecker(); +}; + +describe("JavaScriptTypeMapping", () => { + describe("literal types", () => { + test("number literal", () => { + const source = `const x = 42;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const initializer = getFirstVariableInitializer(source); + + const result = mapping.type(initializer); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.Double + ); + }); + + test("string literal", () => { + const source = `const x = "hello";`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const initializer = getFirstVariableInitializer(source); + + const result = mapping.type(initializer); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.String + ); + }); + + test("boolean literal", () => { + const source = `const x = true;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const initializer = getFirstVariableInitializer(source); + + const result = mapping.type(initializer); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.Boolean + ); + }); + }); + + describe("explicity declared types", () => { + test("number type", () => { + const source = `const x: number = 42;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.Double + ); + }); + + test("string type", () => { + const source = `const x: string = "hello";`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.String + ); + }); + + test("boolean type", () => { + const source = `const x: boolean = true;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.Boolean + ); + }); + }); + + test("string | number union", () => { + const source = `let x: string | number;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + expect(result).toBeInstanceOf(JavaType.Union); + const unionType = result as JavaType.Union; + expect(unionType.types).toHaveLength(2); + }); + + test("RegExp type", () => { + const source = `let x: RegExp;`; + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe( + JavaType.PrimitiveKind.String + ); + }); +}); From b7cbfedda78a9a136fe33baee61a455ee0ed4d3a Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 21 Jan 2025 17:56:16 -0800 Subject: [PATCH 2/2] add more test and bigint mapping --- openrewrite/src/javascript/typeMapping.ts | 22 +- .../test/javascript/typeMapping.test.ts | 237 ++++++++++-------- 2 files changed, 154 insertions(+), 105 deletions(-) diff --git a/openrewrite/src/javascript/typeMapping.ts b/openrewrite/src/javascript/typeMapping.ts index 5e4cbc59..acbdad34 100644 --- a/openrewrite/src/javascript/typeMapping.ts +++ b/openrewrite/src/javascript/typeMapping.ts @@ -75,12 +75,26 @@ export class JavaScriptTypeMapping { return JavaType.Primitive.of(JavaType.PrimitiveKind.Null); } else if (type.flags === ts.TypeFlags.Undefined) { return JavaType.Primitive.of(JavaType.PrimitiveKind.None); - } else if (type.flags === ts.TypeFlags.Number) { + } else if ( + type.flags === ts.TypeFlags.Number || + type.flags === ts.TypeFlags.NumberLiteral || + type.flags === ts.TypeFlags.NumberLike + ) { return JavaType.Primitive.of(JavaType.PrimitiveKind.Double); - } else if (type.flags === ts.TypeFlags.String) { + } else if ( + type.flags === ts.TypeFlags.String || + type.flags === ts.TypeFlags.StringLiteral || + type.flags === ts.TypeFlags.StringLike + ) { return JavaType.Primitive.of(JavaType.PrimitiveKind.String); } else if (type.flags === ts.TypeFlags.Void) { return JavaType.Primitive.of(JavaType.PrimitiveKind.Void); + } else if ( + type.flags === ts.TypeFlags.BigInt || + type.flags === ts.TypeFlags.BigIntLiteral || + type.flags === ts.TypeFlags.BigIntLike + ) { + return JavaType.Primitive.of(JavaType.PrimitiveKind.Long); } else if ( (type.symbol !== undefined && type.symbol === this.regExpSymbol) || this.checker.typeToString(type) === "RegExp" @@ -94,7 +108,8 @@ export class JavaScriptTypeMapping { */ if ( type.flags & ts.TypeFlags.Boolean || - type.flags & ts.TypeFlags.BooleanLiteral + type.flags & ts.TypeFlags.BooleanLiteral || + type.flags & ts.TypeFlags.BooleanLike ) { return JavaType.Primitive.of(JavaType.PrimitiveKind.Boolean); } @@ -121,6 +136,7 @@ export class JavaScriptTypeMapping { // if (ts.isRegularExpressionLiteral(node)) { // return JavaType.Primitive.of(JavaType.PrimitiveKind.String); // } + return JavaType.Unknown.INSTANCE; } } diff --git a/openrewrite/test/javascript/typeMapping.test.ts b/openrewrite/test/javascript/typeMapping.test.ts index ed088c2c..1a9cf0b5 100644 --- a/openrewrite/test/javascript/typeMapping.test.ts +++ b/openrewrite/test/javascript/typeMapping.test.ts @@ -51,7 +51,7 @@ function getFirstVariableTypeNode(sourceText: string): ts.TypeNode { return declaration.type; } -const createTypeChecker = (sourceText: string): ts.TypeChecker => { +function createTypeChecker(sourceText: string): ts.TypeChecker { const sourceFile = ts.createSourceFile( "test.ts", sourceText, @@ -59,103 +59,149 @@ const createTypeChecker = (sourceText: string): ts.TypeChecker => { true ); - const compilerHost = ts.createCompilerHost({}); - compilerHost.getSourceFile = (fileName) => - fileName === "test.ts" ? sourceFile : undefined; + const options: ts.CompilerOptions = { + strict: true, + lib: ["lib.esnext.d.ts"], + }; - const program = ts.createProgram({ - rootNames: ["test.ts"], - options: { lib: ["lib.esnext.d.ts"], strict: true }, - host: compilerHost, - }); + const compilerHost = ts.createCompilerHost(options); + const defaultGetSourceFile = compilerHost.getSourceFile; + + compilerHost.getSourceFile = (fileName, languageVersion) => { + if (fileName.endsWith("test.ts")) { + return sourceFile; + } + return defaultGetSourceFile(fileName, languageVersion); + }; + + const program = ts.createProgram(["test.ts"], options, compilerHost); return program.getTypeChecker(); -}; +} describe("JavaScriptTypeMapping", () => { - describe("literal types", () => { - test("number literal", () => { - const source = `const x = 42;`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const initializer = getFirstVariableInitializer(source); - - const result = mapping.type(initializer); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.Double - ); - }); - - test("string literal", () => { - const source = `const x = "hello";`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const initializer = getFirstVariableInitializer(source); - - const result = mapping.type(initializer); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.String - ); - }); - - test("boolean literal", () => { - const source = `const x = true;`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const initializer = getFirstVariableInitializer(source); - - const result = mapping.type(initializer); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.Boolean - ); - }); + describe("explicitly declared types", () => { + const expectedTypeMapping: Record< + string, + JavaType.PrimitiveKind | JavaType.Unknown + > = { + // JavaScript primitive types + string: JavaType.PrimitiveKind.String, + number: JavaType.PrimitiveKind.Double, + boolean: JavaType.PrimitiveKind.Boolean, + bigint: JavaType.PrimitiveKind.Long, + symbol: JavaType.Unknown, + + // JavaScript special value types + null: JavaType.PrimitiveKind.Null, + undefined: JavaType.PrimitiveKind.None, + void: JavaType.PrimitiveKind.Void, + // TODO this one feels kind of strange + RegExp: JavaType.PrimitiveKind.String, + + // TypeScript meta types + any: JavaType.Unknown, + unknown: JavaType.Unknown, + never: JavaType.Unknown, + "unique symbol": JavaType.Unknown, + + // Literal types + 1: JavaType.PrimitiveKind.Double, + "'someStringLiteral'": JavaType.PrimitiveKind.String, + true: JavaType.PrimitiveKind.Boolean, + false: JavaType.PrimitiveKind.Boolean, + }; + + for (const [key, value] of Object.entries(expectedTypeMapping)) { + const source = `const x: ${key};`; + test(source, () => { + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + if (value === JavaType.Unknown) { + expect(result).toBeInstanceOf(JavaType.Unknown); + } else { + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe(value); + } + }); + } }); - describe("explicity declared types", () => { - test("number type", () => { - const source = `const x: number = 42;`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const node = getFirstVariableTypeNode(source); - - const result = mapping.type(node); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.Double - ); - }); - - test("string type", () => { - const source = `const x: string = "hello";`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const node = getFirstVariableTypeNode(source); - - const result = mapping.type(node); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.String - ); - }); - - test("boolean type", () => { - const source = `const x: boolean = true;`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const node = getFirstVariableTypeNode(source); - - const result = mapping.type(node); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.Boolean - ); - }); + describe("inferred type of initializer", () => { + const expectedTypeMapping: Record< + string, + JavaType.PrimitiveKind | JavaType.Unknown + > = { + "'abc'": JavaType.PrimitiveKind.String, + 123: JavaType.PrimitiveKind.Double, + true: JavaType.PrimitiveKind.Boolean, + false: JavaType.PrimitiveKind.Boolean, + "123n": JavaType.PrimitiveKind.Long, + "Symbol()": JavaType.Unknown, + null: JavaType.PrimitiveKind.Null, + undefined: JavaType.PrimitiveKind.None, + }; + + for (const [key, value] of Object.entries(expectedTypeMapping)) { + const source = `const x = ${key};`; + test(source, () => { + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableInitializer(source); + + const result = mapping.type(node); + if (value === JavaType.Unknown) { + expect(result).toBeInstanceOf(JavaType.Unknown); + } else { + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe(value); + } + }); + } }); - test("string | number union", () => { + describe("common object types that for now we map to unknown", () => { + const expectedTypeMapping: Record< + string, + JavaType.PrimitiveKind | JavaType.Unknown + > = { + Function: JavaType.Unknown, + object: JavaType.Unknown, + "string[]": JavaType.Unknown, + "number[]": JavaType.Unknown, + "any[]": JavaType.Unknown, + "Array": JavaType.Unknown, + "Array": JavaType.Unknown, + "Array": JavaType.Unknown, + "[string, number]": JavaType.Unknown, + "Record": JavaType.Unknown, + "Promise": JavaType.Unknown, + "Promise": JavaType.Unknown, + "unique symbol": JavaType.Unknown, + }; + + for (const [key, value] of Object.entries(expectedTypeMapping)) { + const source = `let x: ${key};`; + test(source, () => { + const checker = createTypeChecker(source); + const mapping = new JavaScriptTypeMapping(checker); + const node = getFirstVariableTypeNode(source); + + const result = mapping.type(node); + if (value === JavaType.Unknown) { + expect(result).toBeInstanceOf(JavaType.Unknown); + } else { + expect(result).toBeInstanceOf(JavaType.Primitive); + expect((result as JavaType.Primitive).kind).toBe(value); + } + }); + } + }); + + test("union type", () => { const source = `let x: string | number;`; const checker = createTypeChecker(source); const mapping = new JavaScriptTypeMapping(checker); @@ -166,17 +212,4 @@ describe("JavaScriptTypeMapping", () => { const unionType = result as JavaType.Union; expect(unionType.types).toHaveLength(2); }); - - test("RegExp type", () => { - const source = `let x: RegExp;`; - const checker = createTypeChecker(source); - const mapping = new JavaScriptTypeMapping(checker); - const node = getFirstVariableTypeNode(source); - - const result = mapping.type(node); - expect(result).toBeInstanceOf(JavaType.Primitive); - expect((result as JavaType.Primitive).kind).toBe( - JavaType.PrimitiveKind.String - ); - }); });