@imaginary-dev/typescript-transformer
Advanced tools
Comparing version 0.0.5 to 0.0.6
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defineTests = void 0; | ||
const globals_1 = require("@jest/globals"); | ||
const util_1 = require("./util"); | ||
function defineTests(compiler) { | ||
(0, globals_1.describe)("imaginary transformer basic transforms", () => { | ||
(0, globals_1.test)("should transform an function with @imaginary TsDoc tag", () => { | ||
describe("imaginary transformer basic transforms", () => { | ||
test("should not transform or throw an error on a function with type params and without @imaginary TsDoc tag (bug #182)", () => { | ||
const actual = (0, util_1.compile)(compiler, ` | ||
@@ -13,8 +12,17 @@ /** | ||
* @returns a random string. | ||
*/ | ||
declare function doSomething<T extends string>(arg1: T) : Promise<number>;`); | ||
expect(actual).not.toMatch(util_1.CALL_IMAGINARY_FUNCTION_NAME); | ||
}); | ||
test("should transform an function with @imaginary TsDoc tag", () => { | ||
const actual = (0, util_1.compile)(compiler, ` | ||
/** | ||
* This function returns a random string. | ||
* @returns a random string. | ||
* @imaginary | ||
*/ | ||
declare function doSomething() : Promise<number>;`); | ||
(0, globals_1.expect)(actual).toMatch(util_1.CALL_IMAGINARY_FUNCTION_NAME); | ||
expect(actual).toMatch(util_1.CALL_IMAGINARY_FUNCTION_NAME); | ||
}); | ||
(0, globals_1.test)("should not transform an function without @imaginary TsDoc tag", () => { | ||
test("should not transform an function without @imaginary TsDoc tag", () => { | ||
const actual = (0, util_1.compile)(compiler, ` | ||
@@ -26,7 +34,7 @@ /** | ||
declare function doSomething() : Promise<number>;`); | ||
(0, globals_1.expect)(actual).not.toMatch(util_1.CALL_IMAGINARY_FUNCTION_NAME); | ||
(0, globals_1.expect)(actual.trim()).toHaveLength(0); | ||
expect(actual).not.toMatch(util_1.CALL_IMAGINARY_FUNCTION_NAME); | ||
expect(actual.trim()).toHaveLength(0); | ||
}); | ||
(0, globals_1.test)("should not throw when there are two declarations of a non-imaginary function", () => { | ||
(0, globals_1.expect)(() => (0, util_1.compile)(compiler, ` | ||
test("should not throw when there are two declarations of a non-imaginary function", () => { | ||
expect(() => (0, util_1.compile)(compiler, ` | ||
/** | ||
@@ -40,4 +48,4 @@ * This function returns a random string. | ||
}); | ||
(0, globals_1.test)("Should throw when type variables are provided", () => { | ||
(0, globals_1.expect)(() => (0, util_1.compile)(compiler, ` | ||
test("Should throw when type variables are provided", () => { | ||
expect(() => (0, util_1.compile)(compiler, ` | ||
/** | ||
@@ -49,4 +57,4 @@ * @imaginary | ||
// TODO: is this the right behavior, or should we throw an error? | ||
(0, globals_1.test)("should get the right declaration of a non-imaginary function", () => { | ||
const mock = globals_1.jest.fn(); | ||
test("should get the right declaration of a non-imaginary function", () => { | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -63,15 +71,15 @@ /** | ||
doSomething();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("This function returns a random string."); | ||
(0, globals_1.expect)(mock.mock.calls[0][1]).toEqual("doSomething"); | ||
(0, globals_1.expect)(mock.mock.calls[0][2]).toEqual([]); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ type: "string" }); | ||
expect(mock.mock.calls[0][0]).toMatch("This function returns a random string."); | ||
expect(mock.mock.calls[0][1]).toEqual("doSomething"); | ||
expect(mock.mock.calls[0][2]).toEqual([]); | ||
expect(mock.mock.calls[0][3]).toEqual({ type: "string" }); | ||
}); | ||
(0, globals_1.test)("should be fine with non-TsDoc comments before a function", async () => { | ||
test("should be fine with non-TsDoc comments before a function", async () => { | ||
// When I first wrote the transformer, the TsDoc compiler freaked out when it encountered a | ||
// non-TsDoc comment before a function. | ||
(0, globals_1.expect)(() => (0, util_1.compile)(compiler, ` | ||
expect(() => (0, util_1.compile)(compiler, ` | ||
// not a TsDoc comment | ||
declare function doSomething() : Promise<number>;`)).not.toThrow(); | ||
}); | ||
(0, globals_1.test)("should be ok when the function declare comes right after the TsDoc comment", async () => { | ||
test("should be ok when the function declare comes right after the TsDoc comment", async () => { | ||
// had an off-by-one error where the "declare" was getting cut off to "eclare" | ||
@@ -84,7 +92,7 @@ // when it was right after the TsDoc comment. | ||
*/declare function doSomething() : Promise<number>;`); | ||
(0, globals_1.expect)(actual).toMatch("doSomething"); | ||
(0, globals_1.expect)(actual).toMatch("uniqueword1"); | ||
expect(actual).toMatch("doSomething"); | ||
expect(actual).toMatch("uniqueword1"); | ||
}); | ||
(0, globals_1.test)("should throw when there are >1 TsDoc @imaginary comments", async () => { | ||
(0, globals_1.expect)(() => (0, util_1.compile)(compiler, ` | ||
test("should throw when there are >1 TsDoc @imaginary comments", async () => { | ||
expect(() => (0, util_1.compile)(compiler, ` | ||
/** | ||
@@ -100,5 +108,5 @@ * TsDoc comment uniqueword1 | ||
}); | ||
(0, globals_1.test)("should be fine with TsDoc comments and normal comments mixed", async () => { | ||
test("should be fine with TsDoc comments and normal comments mixed", async () => { | ||
// anything from other comments should not be included in the translation. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -122,14 +130,14 @@ // not a TsDoc comment uniqueword1 | ||
const imaginaryComment = mock.mock.calls[0][0]; | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword1"); | ||
(0, globals_1.expect)(imaginaryComment).toMatch("uniqueword2"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword3"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword4"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword5"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword6"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword7"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword8"); | ||
(0, globals_1.expect)(imaginaryComment).not.toMatch("uniqueword9"); | ||
expect(imaginaryComment).not.toMatch("uniqueword1"); | ||
expect(imaginaryComment).toMatch("uniqueword2"); | ||
expect(imaginaryComment).not.toMatch("uniqueword3"); | ||
expect(imaginaryComment).not.toMatch("uniqueword4"); | ||
expect(imaginaryComment).not.toMatch("uniqueword5"); | ||
expect(imaginaryComment).not.toMatch("uniqueword6"); | ||
expect(imaginaryComment).not.toMatch("uniqueword7"); | ||
expect(imaginaryComment).not.toMatch("uniqueword8"); | ||
expect(imaginaryComment).not.toMatch("uniqueword9"); | ||
}); | ||
(0, globals_1.test)("should pass the function comment to callImaginaryFunction", async () => { | ||
const mock = globals_1.jest.fn(); | ||
test("should pass the function comment to callImaginaryFunction", async () => { | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -142,6 +150,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("Supercalifragilisticexpialidocious"); | ||
expect(mock.mock.calls[0][0]).toMatch("Supercalifragilisticexpialidocious"); | ||
}); | ||
(0, globals_1.test)("should hoist imports so that imaginary functions further down can be called", async () => { | ||
const mock = globals_1.jest.fn(); | ||
test("should hoist imports so that imaginary functions further down can be called", async () => { | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -153,5 +161,5 @@ foo(); | ||
declare function foo(): Promise<string>;`, mock); | ||
(0, globals_1.expect)(mock).toHaveBeenCalled(); | ||
expect(mock).toHaveBeenCalled(); | ||
}); | ||
(0, globals_1.test)("Should ignore fatal TsDoc errors when something is not imaginary", () => { | ||
test("Should ignore fatal TsDoc errors when something is not imaginary", () => { | ||
const actual = (0, util_1.compile)(compiler, ` | ||
@@ -163,6 +171,6 @@ /** | ||
declare function doSomething() : Promise<number>;`); | ||
(0, globals_1.expect)(actual.trim()).toHaveLength(0); | ||
expect(actual.trim()).toHaveLength(0); | ||
}); | ||
(0, globals_1.test)("Should handle unusual characters in comments", async () => { | ||
const mock = globals_1.jest.fn(); | ||
test("Should handle unusual characters in comments", async () => { | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -185,6 +193,6 @@ foo(); | ||
declare function foo(): Promise<string>;`, mock); | ||
(0, globals_1.expect)(mock).toHaveBeenCalled(); | ||
expect(mock).toHaveBeenCalled(); | ||
}); | ||
(0, globals_1.test)("Should fail for truly breaking syntax in imaginary functions", async () => { | ||
(0, globals_1.expect)(() => { | ||
test("Should fail for truly breaking syntax in imaginary functions", async () => { | ||
expect(() => { | ||
const actual = (0, util_1.compile)(compiler, ` | ||
@@ -201,4 +209,4 @@ foo(); | ||
// TODO: enable when Map rehydration is ready | ||
globals_1.test.skip("Should support Map", async () => { | ||
const mock = globals_1.jest.fn(); | ||
test.skip("Should support Map", async () => { | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, ` | ||
@@ -211,3 +219,3 @@ foo(); | ||
declare function foo(): Promise<Map<string, number>>;`, mock); | ||
(0, globals_1.expect)(mock).toHaveBeenCalled(); | ||
expect(mock).toHaveBeenCalled(); | ||
}); | ||
@@ -214,0 +222,0 @@ }); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defineTests = void 0; | ||
const globals_1 = require("@jest/globals"); | ||
const util_1 = require("./util"); | ||
@@ -9,5 +8,5 @@ function defineTests(compiler, supportNamedTypes = true, supportAliasedPrimitives = true) { | ||
const testWithNamedPrimitives = (0, util_1.makeConditionalTest)(supportAliasedPrimitives, "types must be inlined"); | ||
(0, globals_1.describe)("imaginary transform return types", () => { | ||
describe("imaginary transform return types", () => { | ||
testWithNamedPrimitives("should inline aliased literal string type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = "happy"; | ||
@@ -19,6 +18,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ const: "happy" }); | ||
expect(mock.mock.calls[0][3]).toEqual({ const: "happy" }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased literal number type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = 42; | ||
@@ -30,6 +29,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ const: 42 }); | ||
expect(mock.mock.calls[0][3]).toEqual({ const: 42 }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased literal boolean type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = false; | ||
@@ -41,6 +40,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ const: false }); | ||
expect(mock.mock.calls[0][3]).toEqual({ const: false }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased primitive string type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = string; | ||
@@ -52,6 +51,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ type: "string" }); | ||
expect(mock.mock.calls[0][3]).toEqual({ type: "string" }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased primitive number type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = number; | ||
@@ -63,6 +62,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ type: "number" }); | ||
expect(mock.mock.calls[0][3]).toEqual({ type: "number" }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased primitive boolean type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = boolean; | ||
@@ -74,6 +73,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ type: "boolean" }); | ||
expect(mock.mock.calls[0][3]).toEqual({ type: "boolean" }); | ||
}); | ||
testWithNamedPrimitives("should inline aliased primitive null type", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = null; | ||
@@ -85,6 +84,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ type: "null" }); | ||
expect(mock.mock.calls[0][3]).toEqual({ type: "null" }); | ||
}); | ||
testWithNamedPrimitives("should inline arrays of aliased types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = string; | ||
@@ -96,3 +95,3 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "array", | ||
@@ -103,3 +102,3 @@ items: { type: "string" }, | ||
testWithNamedTypes("should inline aliased enum number types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `enum MyEnum { A, B, C }; | ||
@@ -111,6 +110,6 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ enum: [0, 1, 2] }); | ||
expect(mock.mock.calls[0][3]).toEqual({ enum: [0, 1, 2] }); | ||
}); | ||
testWithNamedTypes("should inline aliased enum string types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `enum MyEnum { | ||
@@ -127,3 +126,3 @@ A = "car", | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
enum: ["car", "wheels", "gravel", "road"], | ||
@@ -133,3 +132,3 @@ }); | ||
testWithNamedTypes("should inline aliased string constant union types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = "car" | "wheels" | "gravel" | "road"; | ||
@@ -141,3 +140,3 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
anyOf: [ | ||
@@ -152,3 +151,3 @@ { const: "car" }, | ||
testWithNamedTypes("should inline aliased union types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = string | number | ||
@@ -160,3 +159,3 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
anyOf: [{ type: "string" }, { type: "number" }], | ||
@@ -166,3 +165,3 @@ }); | ||
testWithNamedTypes("should inline aliased interface types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `interface MyInterface {name?: string; age: number}; | ||
@@ -174,3 +173,3 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "object", | ||
@@ -182,3 +181,3 @@ required: ["age"], | ||
testWithNamedTypes("should inline aliased object types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType = {name?: string; age: number}; | ||
@@ -190,3 +189,3 @@ /** | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "object", | ||
@@ -198,3 +197,3 @@ required: ["age"], | ||
testWithNamedTypes("should inline doubly aliased object types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type MyType1 = {name?: string; age: number}; | ||
@@ -207,3 +206,3 @@ type MyType2 = MyType1; | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "object", | ||
@@ -215,3 +214,3 @@ required: ["age"], | ||
testWithNamedTypes("should inline nested aliased object types", () => { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `type NameType = {firstName: string; lastName: string;}; | ||
@@ -224,3 +223,3 @@ type MyType = {name?: NameType; age: number}; | ||
foo();`, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "object", | ||
@@ -227,0 +226,0 @@ required: ["age"], |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defineTests = void 0; | ||
const globals_1 = require("@jest/globals"); | ||
const util_1 = require("./util"); | ||
@@ -9,7 +8,7 @@ function defineTests(compiler, supportNamedTypes = true, supportReExports = true) { | ||
const testWithExports = (0, util_1.makeConditionalTest)(supportReExports, "is not a function"); | ||
(0, globals_1.describe)("imaginary transform multi file", () => { | ||
describe("imaginary transform multi file", () => { | ||
testWithNamedTypes("should describe a simple object return type in JSON schema", () => { | ||
// test helper that takes in a type as it will be described in the function declaration | ||
// and the JSON schema that should be output in the compiled script. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, { | ||
@@ -27,3 +26,3 @@ projectFiles: { | ||
}, mock); | ||
(0, globals_1.expect)(mock.mock.calls[0][3]).toEqual({ | ||
expect(mock.mock.calls[0][3]).toEqual({ | ||
type: "object", | ||
@@ -34,6 +33,6 @@ required: ["foo", "bar"], | ||
}); | ||
(0, globals_1.test)("should be able to export declare an imaginary function in one file and use in another", () => { | ||
test("should be able to export declare an imaginary function in one file and use in another", () => { | ||
// test helper that takes in a type as it will be described in the function declaration | ||
// and the JSON schema that should be output in the compiled script. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, { | ||
@@ -56,8 +55,8 @@ projectFiles: { | ||
// just make sure that foo got called and passed the comment in. | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
expect(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
}); | ||
(0, globals_1.test)("should be able to declare an imaginary function and then export in one file and use in another", () => { | ||
test("should be able to declare an imaginary function and then export in one file and use in another", () => { | ||
// test helper that takes in a type as it will be described in the function declaration | ||
// and the JSON schema that should be output in the compiled script. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, { | ||
@@ -82,3 +81,3 @@ projectFiles: { | ||
// just make sure that foo got called and passed the comment in. | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
expect(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
}); | ||
@@ -88,3 +87,3 @@ testWithExports("should be able to declare an imaginary function and then export as another name in one file and use in another", () => { | ||
// and the JSON schema that should be output in the compiled script. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, { | ||
@@ -109,3 +108,3 @@ projectFiles: { | ||
// just make sure that foo got called and passed the comment in. | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
expect(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
}); | ||
@@ -115,3 +114,3 @@ testWithExports("should be able to declare an imaginary function and then export as default in one file and use in another", () => { | ||
// and the JSON schema that should be output in the compiled script. | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, { | ||
@@ -136,3 +135,3 @@ projectFiles: { | ||
// just make sure that foo got called and passed the comment in. | ||
(0, globals_1.expect)(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
expect(mock.mock.calls[0][0]).toMatch("super duper foo"); | ||
}); | ||
@@ -139,0 +138,0 @@ }); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defineTests = void 0; | ||
const globals_1 = require("@jest/globals"); | ||
const util_1 = require("./util"); | ||
@@ -10,7 +9,7 @@ function defineTests(compiler, supportNamedTypes = true, supportNamedPrimitives = true) { | ||
const testWithInlineError = (0, util_1.makeBimodalErrorTest)(supportNamedPrimitives, /computed enums|return values from imaginary functions must be JSON-serializable and cannot be classes|types must be inlined/, "types must be inlined"); | ||
(0, globals_1.describe)("imaginary transform return types", () => { | ||
describe("imaginary transform return types", () => { | ||
// test helper that takes in a type as it will be described in the function declaration | ||
// and the JSON schema that should be output in the compiled script. | ||
function testReturnValueTyping(typeAsString, typeAsJSONSchema, preamble) { | ||
const mock = globals_1.jest.fn(); | ||
const mock = jest.fn(); | ||
(0, util_1.compileAndRunWithCallImaginaryMock)(compiler, `${preamble ?? ""} | ||
@@ -25,17 +24,17 @@ /** | ||
// toEqualJSONSchema. Would be nice to fix this. | ||
return (0, globals_1.expect)(mock.mock.calls[0][3]).toEqualJSONSchema(typeAsJSONSchema); | ||
return expect(mock.mock.calls[0][3]).toEqualJSONSchema(typeAsJSONSchema); | ||
} | ||
(0, globals_1.test)("should describe a string return type in JSON schema", () => { | ||
test("should describe a string return type in JSON schema", () => { | ||
testReturnValueTyping("string", { type: "string" }); | ||
}); | ||
(0, globals_1.test)("should describe a number return type in JSON schema", () => { | ||
test("should describe a number return type in JSON schema", () => { | ||
testReturnValueTyping("number", { type: "number" }); | ||
}); | ||
(0, globals_1.test)("should describe a boolean return type in JSON schema", () => { | ||
test("should describe a boolean return type in JSON schema", () => { | ||
testReturnValueTyping("boolean", { type: "boolean" }); | ||
}); | ||
(0, globals_1.test)("should describe a null return type in JSON schema", () => { | ||
test("should describe a null return type in JSON schema", () => { | ||
testReturnValueTyping("null", { type: "null" }); | ||
}); | ||
(0, globals_1.test)("should describe a constant string value return type in JSON schema", () => { | ||
test("should describe a constant string value return type in JSON schema", () => { | ||
testReturnValueTyping('"ConstantString"', { | ||
@@ -45,3 +44,3 @@ const: "ConstantString", | ||
}); | ||
(0, globals_1.test)("should describe a constant string value return type in JSON schema", () => { | ||
test("should describe a constant string value return type in JSON schema", () => { | ||
testReturnValueTyping("345", { const: 345 }); | ||
@@ -64,3 +63,3 @@ }); | ||
}); | ||
(0, globals_1.test)("should describe an ad-hoc string enum with >2 values return type in JSON schema", () => { | ||
test("should describe an ad-hoc string enum with >2 values return type in JSON schema", () => { | ||
testReturnValueTyping("'green' | 'red' | 'blue'", { | ||
@@ -70,3 +69,3 @@ anyOf: [{ const: "green" }, { const: "red" }, { const: "blue" }], | ||
}); | ||
(0, globals_1.test)("should describe an ad-hoc numeric enum with >2 values return type in JSON schema", () => { | ||
test("should describe an ad-hoc numeric enum with >2 values return type in JSON schema", () => { | ||
testReturnValueTyping("12 | 1345 | 877", { | ||
@@ -76,3 +75,3 @@ anyOf: [{ const: 12 }, { const: 1345 }, { const: 877 }], | ||
}); | ||
(0, globals_1.test)("should describe an ad-hoc multi typed enum with >2 values return type in JSON schema", () => { | ||
test("should describe an ad-hoc multi typed enum with >2 values return type in JSON schema", () => { | ||
testReturnValueTyping("12 | 'green' | null", { | ||
@@ -95,3 +94,3 @@ anyOf: [{ type: "null" }, { const: 12 }, { const: "green" }], | ||
}); | ||
(0, globals_1.test)("should describe a simple inline object return type in JSON schema", () => { | ||
test("should describe a simple inline object return type in JSON schema", () => { | ||
testReturnValueTyping("{foo: string; bar: number}", { | ||
@@ -110,3 +109,3 @@ type: "object", | ||
}); | ||
(0, globals_1.test)("should describe a simple array return type in JSON schema", () => { | ||
test("should describe a simple array return type in JSON schema", () => { | ||
testReturnValueTyping("string[]", { | ||
@@ -117,3 +116,3 @@ type: "array", | ||
}); | ||
(0, globals_1.test)("should describe an Array return type in JSON schema", () => { | ||
test("should describe an Array return type in JSON schema", () => { | ||
testReturnValueTyping("Array<string>", { | ||
@@ -125,3 +124,3 @@ type: "array", | ||
// TODO: enable when Map rehydration is ready | ||
globals_1.test.skip("should describe a Map return type in JSON schema", () => { | ||
test.skip("should describe a Map return type in JSON schema", () => { | ||
testReturnValueTyping("Map<string, number>", { | ||
@@ -132,4 +131,4 @@ type: "object", | ||
}); | ||
globals_1.test.skip("should fail if Map is not string-based", () => { | ||
(0, globals_1.expect)(() => { | ||
test.skip("should fail if Map is not string-based", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -141,3 +140,3 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("should describe a Record return type in JSON schema", () => { | ||
test("should describe a Record return type in JSON schema", () => { | ||
testReturnValueTyping("Record<string, number>", { | ||
@@ -148,4 +147,4 @@ type: "object", | ||
}); | ||
(0, globals_1.test)("should fail if Record is not string-based", () => { | ||
(0, globals_1.expect)(() => { | ||
test("should fail if Record is not string-based", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -157,3 +156,3 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("should describe a object return type with property names with spaces in JSON schema", () => { | ||
test("should describe a object return type with property names with spaces in JSON schema", () => { | ||
testReturnValueTyping('{"name with space": string;}', { | ||
@@ -198,3 +197,3 @@ type: "object", | ||
}); | ||
(0, globals_1.test)("should support parenthesesed types", () => { | ||
test("should support parenthesesed types", () => { | ||
testReturnValueTyping('("a"|"b"|"c")[]', { | ||
@@ -217,3 +216,3 @@ type: "array", | ||
}); | ||
(0, globals_1.test)("should describe a object return type with optional and required members in JSON schema", () => { | ||
test("should describe a object return type with optional and required members in JSON schema", () => { | ||
testReturnValueTyping("{name?: string; age: number}", { | ||
@@ -225,3 +224,3 @@ type: "object", | ||
}); | ||
(0, globals_1.test)("should describe a object return type with ALL optional members in JSON schema", () => { | ||
test("should describe a object return type with ALL optional members in JSON schema", () => { | ||
testReturnValueTyping("{name?: string;}", { | ||
@@ -239,3 +238,3 @@ type: "object", | ||
}); | ||
(0, globals_1.test)("should describe a union type in JSON schema", () => { | ||
test("should describe a union type in JSON schema", () => { | ||
testReturnValueTyping("string | number", { | ||
@@ -245,4 +244,4 @@ anyOf: [{ type: "string" }, { type: "number" }], | ||
}); | ||
(0, globals_1.test)("should fail for intersection types", () => { | ||
(0, globals_1.expect)(() => { | ||
test("should fail for intersection types", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -254,3 +253,3 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("should describe a union type with null in JSON schema", () => { | ||
test("should describe a union type with null in JSON schema", () => { | ||
testReturnValueTyping("string | null", { | ||
@@ -260,3 +259,3 @@ anyOf: [{ type: "null", nullable: true }, { type: "string" }], | ||
}); | ||
(0, globals_1.test)("should describe a >2 union type in JSON schema", () => { | ||
test("should describe a >2 union type in JSON schema", () => { | ||
// TODO: this should probably return {type: "boolean"} rather than anyOf(true, false) | ||
@@ -267,3 +266,3 @@ testReturnValueTyping("string | number | boolean", { | ||
}); | ||
(0, globals_1.test)("should describe a more complex union type in JSON schema", () => { | ||
test("should describe a more complex union type in JSON schema", () => { | ||
testReturnValueTyping("{name?: string; age: number}|string[]", { | ||
@@ -290,4 +289,4 @@ anyOf: [ | ||
}); | ||
(0, globals_1.test)("throws an error if return type isn't a Promise", () => { | ||
(0, globals_1.expect)(() => { | ||
test("throws an error if return type isn't a Promise", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -299,4 +298,4 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("throws an error if return type is a Promise and has more than one type argument", () => { | ||
(0, globals_1.expect)(() => { | ||
test("throws an error if return type is a Promise and has more than one type argument", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -308,4 +307,4 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("throws an error if return type is a Promise and has no type argument", () => { | ||
(0, globals_1.expect)(() => { | ||
test("throws an error if return type is a Promise and has no type argument", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, `/** | ||
@@ -334,4 +333,4 @@ * @imaginary | ||
}); | ||
(0, globals_1.test)("throws an error if return type is function", () => { | ||
(0, globals_1.expect)(() => { | ||
test("throws an error if return type is function", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, ` | ||
@@ -344,4 +343,4 @@ /** | ||
}); | ||
(0, globals_1.test)("throws an error if return type is intersection", () => { | ||
(0, globals_1.expect)(() => { | ||
test("throws an error if return type is intersection", () => { | ||
expect(() => { | ||
(0, util_1.compile)(compiler, ` | ||
@@ -348,0 +347,0 @@ \ /** |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.compileAndRunWithCallImaginaryMock = exports.compileAndRun = exports.runCompiledImaginaryScript = exports.compile = exports.CALL_IMAGINARY_FUNCTION_NAME = exports.makeBimodalErrorTest = exports.makeConditionalTest = void 0; | ||
const globals_1 = require("@jest/globals"); | ||
const path_1 = require("path"); | ||
@@ -9,5 +8,5 @@ const vm_1 = require("vm"); | ||
return supported | ||
? globals_1.test | ||
: (name, fn) => (0, globals_1.test)(name, () => { | ||
(0, globals_1.expect)(fn).toThrow(throwString); | ||
? test | ||
: (name, fn) => test(name, () => { | ||
expect(fn).toThrow(throwString); | ||
}); | ||
@@ -18,4 +17,4 @@ } | ||
return (name, fn) => { | ||
(0, globals_1.test)(name, () => { | ||
(0, globals_1.expect)(fn).toThrow(supported ? errorIfSupported : errorIfNotSupported); | ||
test(name, () => { | ||
expect(fn).toThrow(supported ? errorIfSupported : errorIfNotSupported); | ||
}); | ||
@@ -22,0 +21,0 @@ }; |
@@ -20,5 +20,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./ts-node-to-json-schema"), exports); | ||
__exportStar(require("./ts-prompt-transformer"), exports); | ||
__exportStar(require("./ts-type-to-json-schema"), exports); | ||
const ts_prompt_transformer_1 = __importDefault(require("./ts-prompt-transformer")); | ||
exports.default = ts_prompt_transformer_1.default; | ||
//# sourceMappingURL=index.js.map |
@@ -29,12 +29,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getImaginaryTsDocComments = void 0; | ||
const util_1 = require("@imaginary-dev/util"); | ||
const tsdoc_1 = require("@microsoft/tsdoc"); | ||
const ts = __importStar(require("typescript")); | ||
const json_object_to_ts_ast_1 = __importDefault(require("./json-object-to-ts-ast")); | ||
const tsdocConfig = new tsdoc_1.TSDocConfiguration(); | ||
tsdocConfig.addTagDefinition(new tsdoc_1.TSDocTagDefinition({ | ||
tagName: "@imaginary", | ||
syntaxKind: tsdoc_1.TSDocTagSyntaxKind.ModifierTag, | ||
allowMultiple: false, | ||
})); | ||
const ts_type_to_json_schema_1 = require("./ts-type-to-json-schema"); | ||
const transformer = (program) => (context) => { | ||
@@ -49,27 +44,30 @@ return (sourceFile) => { | ||
if (ts.isFunctionDeclaration(node)) { | ||
if (node.typeParameters) { | ||
throw new Error("Imaginary function compile error: Functions cannot include type variables."); | ||
} | ||
const imaginaryComments = getImaginaryTsDocComments(node, sourceFile); | ||
// maybe in the future we could merge multiple @imaginary comments, but for now we throw an | ||
// error. | ||
if (imaginaryComments.length > 1) { | ||
throw new Error(`Imaginary function compile error: imaginary functions should only have one @imaginary TsDoc comment, but the function "${node.getText(sourceFile)}" had ${imaginaryComments.length} @imaginary TsDoc comments.`); | ||
if (imaginaryComments.length > 0) { | ||
if (node.typeParameters) { | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compile error: Functions cannot include type variables.`, node); | ||
} | ||
// maybe in the future we could merge multiple @imaginary comments, but for now we throw an | ||
// error. | ||
if (imaginaryComments.length > 1) { | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compile error: imaginary functions should only have one @imaginary TsDoc comment, but the function "${node.getText(sourceFile)}" had ${imaginaryComments.length} @imaginary TsDoc comments.`, node); | ||
} | ||
if (imaginaryComments.length === 1) { | ||
const imaginaryComment = imaginaryComments[0]; | ||
const serviceParameters = (0, util_1.extractServiceParameters)((0, util_1.makeTSDocParser)(), imaginaryComment); | ||
const paramNames = node.parameters.map((paramDeclaration) => paramDeclaration.name.getText()); | ||
const typeChecker = program.getTypeChecker(); | ||
const type = typeChecker.getTypeAtLocation(node); | ||
const promisedType = getPromisedReturnType(node, typeChecker); | ||
addedImports = true; | ||
const functionName = getFunctionName(node, typeChecker); | ||
const paramsAsStrings = node.parameters.map((paramDeclaration) => ({ | ||
name: paramDeclaration.name.getText(), | ||
type: paramDeclaration.type | ||
? typeChecker.getTypeAtLocation(paramDeclaration.type) | ||
: undefined, | ||
})); | ||
return getASTForName(promptEngineIdentifier, imaginaryComment, functionName, paramsAsStrings, serviceParameters, promisedType, typeChecker, node); | ||
} | ||
} | ||
if (imaginaryComments.length === 1) { | ||
const imaginaryComment = imaginaryComments[0]; | ||
const paramNames = node.parameters.map((paramDeclaration) => paramDeclaration.name.getText()); | ||
const typeChecker = program.getTypeChecker(); | ||
const type = typeChecker.getTypeAtLocation(node); | ||
const promisedType = getPromisedReturnType(node, typeChecker); | ||
addedImports = true; | ||
const functionName = getFunctionName(node, typeChecker); | ||
const paramsAsStrings = node.parameters.map((paramDeclaration) => ({ | ||
name: paramDeclaration.name.getText(), | ||
type: paramDeclaration.type | ||
? typeChecker.getTypeAtLocation(paramDeclaration.type) | ||
: undefined, | ||
})); | ||
return getASTForName(promptEngineIdentifier, imaginaryComment, functionName, paramsAsStrings, getBottomedOutType(promisedType, typeChecker), promisedType, typeChecker); | ||
} | ||
} | ||
@@ -110,11 +108,9 @@ return ts.visitEachChild(node, visitor, context); | ||
if (!callSignature) { | ||
throw new Error(`Internal compilation error: could not find the call signature for "${typeChecker.typeToString(type)}" for this node. Please file a bug with the code that caused this compilation error.`); | ||
throw new ts_type_to_json_schema_1.NodeError(`Internal compilation error: could not find the call signature for "${typeChecker.typeToString(type)}" for this node. Please file a bug with the code that caused this compilation error.`, node); | ||
} | ||
const returnType = type | ||
.getCallSignatures()[0] | ||
.getReturnType(); | ||
const returnType = type.getCallSignatures()[0].getReturnType(); | ||
// Check to make sure that the return value is a Promise. This is how you make | ||
// async functions in TypeScript function declarations. | ||
if (!isPromise(returnType)) { | ||
throw new Error(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, but the return type of ${typeChecker.typeToString(type)} is not a Promise.`); | ||
if (!(0, ts_type_to_json_schema_1.isPromise)(returnType)) { | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, but the return type of ${typeChecker.typeToString(type)} is not a Promise.`, node); | ||
} | ||
@@ -124,10 +120,10 @@ // Find the type encased in the promise. | ||
returnType.typeArguments.length === 0) { | ||
throw new Error(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, where T is the wrapped type that is returned by the function. This imaginary function has no wrapped type.`); | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, where T is the wrapped type that is returned by the function. This imaginary function has no wrapped type.`, node); | ||
} | ||
if (returnType.typeArguments.length > 1) { | ||
throw new Error(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, with one type wrapped by a Promise. This imaginary function has more than one type wrapped by the Promise.`); | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compilation error: all imaginary functions must have a return type of Promise<T>, with one type wrapped by a Promise. This imaginary function has more than one type wrapped by the Promise.`, node); | ||
} | ||
const promisedType = returnType.typeArguments[0]; | ||
if (!isAllowedType(promisedType)) { | ||
throw new Error(`Imaginary function compilation error: return types must be inlined.`); | ||
throw new ts_type_to_json_schema_1.NodeError(`Imaginary function compilation error: return types must be inlined.`, node); | ||
} | ||
@@ -150,7 +146,7 @@ return promisedType; | ||
// takes in a JSON-compatible type and follows aliases and interfaces | ||
function getBottomedOutType(type, typeChecker) { | ||
function getBottomedOutType(type, typeChecker, node) { | ||
// It's admittedly a little silly to take a TypeScript AST type and convert it into JSON Schema in | ||
// order to turn it back into TypeScript text, but that's the easiest path I have right now, given that | ||
// TS ASTs are so complicated. | ||
return (0, util_1.jsonSchemaToTypeScriptText)(tsTypeToJsonSchema(type, typeChecker)); | ||
return (0, util_1.jsonSchemaToTypeScriptText)((0, ts_type_to_json_schema_1.tsTypeToJsonSchema)(type, typeChecker, node)); | ||
} | ||
@@ -170,3 +166,3 @@ // The version of the function declaration that we need to hand to GPT for imaginary implementation is | ||
let promisedReturnType = getPromisedReturnType(node, typeChecker); | ||
const returnTypeText = getBottomedOutType(promisedReturnType, typeChecker); | ||
const returnTypeText = getBottomedOutType(promisedReturnType, typeChecker, node); | ||
return `declare function ${functionName}(${paramNamesAndTypes.map(({ name, type }) => type ? `${name}: ${typeChecker.getTypeAtLocation(type)}` : name)}) : ${returnTypeText}`; | ||
@@ -178,3 +174,3 @@ } | ||
if (typeof functionName === "undefined") { | ||
throw new Error(`Internal imaginary function compilation error: could not retrieve the function name of ${typeChecker.typeToString(type)}. Please report this as a bug.`); | ||
throw new ts_type_to_json_schema_1.NodeError(`Internal imaginary function compilation error: could not retrieve the function name of ${typeChecker.typeToString(type)}. Please report this as a bug.`, node); | ||
} | ||
@@ -198,203 +194,3 @@ return functionName; | ||
} | ||
const tsTypeToJsonSchema = (type, typeChecker, path = []) => { | ||
const readablePath = path.join(".") || "<root>"; | ||
if (type.getFlags() & ts.TypeFlags.BooleanLiteral) { | ||
// It's weirdly hard to get the value of a boolean literal because boolean literals are not | ||
// represented by LiteralType but rather by IntrinsicType, which is not exposed to users. | ||
// I'm using a hack from this github issue: | ||
// https://github.com/microsoft/TypeScript/issues/22269#issue-301529155 | ||
// See also this github issue: https://github.com/microsoft/TypeScript/issues/22269 | ||
if (typeChecker.typeToString(type) === "true") | ||
return { const: true }; | ||
if (typeChecker.typeToString(type) === "false") | ||
return { const: false }; | ||
throw new Error(`Internal error trying to parse boolean literal with type ${type} and symbol ${type | ||
?.getSymbol() | ||
?.getName()}`); | ||
} | ||
if (type.getCallSignatures().length > 0) { | ||
throw new Error(`Imaginary function error: return values from imaginary functions must be JSON-serializable and cannot have function properties. (at path '${readablePath}')`); | ||
} | ||
if (type.isClass()) { | ||
throw new Error(`Imaginary function error: return values from imaginary functions must be JSON-serializable and cannot be classes. (at path '${readablePath}')`); | ||
} | ||
if (type.isIntersection()) { | ||
throw new Error(`Imaginary function error: we do not yet support intersection types. (at path '${readablePath}')`); | ||
} | ||
if (type.getFlags() & ts.TypeFlags.EnumLike) { | ||
// a multi-valued constant enum is a union type of all the literal values in it with its constituent | ||
// unioned types as literals that are accessed at .types, but a single-valued constant | ||
// enum is just a literal value of the only enum value. we need to standardize this into one array. | ||
let literalTypesInEnum = []; | ||
if (type.isUnion()) { | ||
literalTypesInEnum = type.types; | ||
} | ||
else if (type.isLiteral()) { | ||
literalTypesInEnum = [type]; | ||
} | ||
else { | ||
throw new Error(`Imaginary function error: we do not yet support computed enums. (at path '${readablePath}').`); | ||
} | ||
return { | ||
enum: literalTypesInEnum.map((type) => { | ||
if (type.isStringLiteral()) { | ||
return type.value; | ||
} | ||
else if (type.isNumberLiteral()) { | ||
return type.value; | ||
} | ||
throw new Error(`Internal imaginary function compilation error: encountered an enum type member which is not a string or number, which we don't support (at path '${readablePath}'). Please report this as a bug.`); | ||
}), | ||
}; | ||
} | ||
// single-value enums are classified **both** as EnumLiteral **and** NumberLiteral, so if this block is above the Enum block, | ||
// it will grab hold of single-valued numeric enums. | ||
if (type.isLiteral()) { | ||
if (type.isStringLiteral()) { | ||
return { const: type.value }; | ||
} | ||
else if (type.isNumberLiteral()) { | ||
return { const: type.value }; | ||
} | ||
throw new Error(`Imaginary function error: we do not yet support literal types other than string or number. (at path '${readablePath}')`); | ||
} | ||
if (type.getFlags() & ts.TypeFlags.Null) { | ||
return { type: "null" }; | ||
} | ||
if (type.getFlags() & ts.TypeFlags.Any) { | ||
throw new Error(`Imaginary function error: we do not yet support any types; just use string if you want something unstructured. (at path '${readablePath}')`); | ||
} | ||
if (type.getFlags() & ts.TypeFlags.Boolean) { | ||
// this block must be above the union bloc, because boolean | ||
// is implemented as a union of true and false | ||
return { type: "boolean" }; | ||
} | ||
if (type.isUnion()) { | ||
// In strictNullChecks mode, optional types get sent in as (type | undefined), but we already | ||
// handle that in the object type with the required array. So we do a special thing here where | ||
// we strip out the undefined type, and if there's only one type left, we don't do a union at all. | ||
const unionTypesWithoutUndefined = type.types.filter((type) => !(type.getFlags() & ts.TypeFlags.Undefined)); | ||
if (unionTypesWithoutUndefined.length === 0) { | ||
throw new Error(`Imaginary function compiler: internal error of union type with only undefined members.`); | ||
} | ||
if (unionTypesWithoutUndefined.length === 1) | ||
return tsTypeToJsonSchema(unionTypesWithoutUndefined[0], typeChecker, path); | ||
// if we are unioning together { const: false } and { const: true }, replace that with | ||
// { type: "boolean" } | ||
const jsonSchemaTypesWithoutUndefined = unionTypesWithoutUndefined.map((t) => tsTypeToJsonSchema(t, typeChecker, path)); | ||
const falseTypeIndex = jsonSchemaTypesWithoutUndefined.findIndex((jsonSchema) => jsonSchema?.const === false); | ||
const trueTypeIndex = jsonSchemaTypesWithoutUndefined.findIndex((jsonSchema) => jsonSchema?.const === true); | ||
if (falseTypeIndex !== -1 && trueTypeIndex !== -1) { | ||
jsonSchemaTypesWithoutUndefined.splice(falseTypeIndex, 1, { | ||
type: "boolean", | ||
}); | ||
jsonSchemaTypesWithoutUndefined.splice(trueTypeIndex, 1); | ||
} | ||
return { | ||
anyOf: jsonSchemaTypesWithoutUndefined, | ||
}; | ||
} | ||
if (type.isIntersection()) { | ||
throw new Error("Imaginary function compiler: intersection types are not supported."); | ||
} | ||
if (type.getFlags() & ts.TypeFlags.String) { | ||
return { type: "string" }; | ||
} | ||
if (type.getFlags() & ts.TypeFlags.Number) { | ||
return { type: "number" }; | ||
} | ||
if (type.getFlags() & ts.TypeFlags.Object) { | ||
if (isArray(type)) { | ||
const typeArguments = type.typeArguments; | ||
if (typeof typeArguments === "undefined") { | ||
throw new Error(`Internal imaginary function compiler error: found an array with indeterminate item type, called ${typeChecker.typeToString(type)}. Please report this as a bug.`); | ||
} | ||
if (typeArguments.length !== 1) { | ||
throw new Error(`Internal imaginary function compiler error. Found an array with multiple or no types, called ${typeChecker.typeToString(type)}`); | ||
} | ||
return { | ||
type: "array", | ||
items: tsTypeToJsonSchema(typeArguments[0], typeChecker, [ | ||
...path, | ||
"[]", | ||
]), | ||
}; | ||
} | ||
if (isBuiltInWithName(type, "Record") | ||
// TODO: enable when Map rehydration is ready | ||
// || isBuiltInWithName(type, "Map") | ||
) { | ||
const { typeArguments, typeName } = getBuiltinTypeInfo(type); | ||
if (typeof typeArguments === "undefined") { | ||
throw new Error(`Internal imaginary function compiler error: found a ${typeName} with indeterminate item type, called ${typeChecker.typeToString(type)}. Please report this as a bug.`); | ||
} | ||
if (typeArguments.length !== 2) { | ||
throw new Error(`Internal imaginary function compiler error. Found a ${typeName} with too many or no types, called ${typeChecker.typeToString(type)}`); | ||
} | ||
if (!(typeArguments[0].getFlags() & ts.TypeFlags.String)) { | ||
throw new Error(`Imaginary function compiler: ${typeName} key type must be 'string'. (at path '${readablePath}')`); | ||
} | ||
return { | ||
type: "object", | ||
additionalProperties: tsTypeToJsonSchema(typeArguments[1], typeChecker, [...path, typeName ?? "???"]), | ||
}; | ||
} | ||
// object type | ||
const requiredPropNames = type | ||
.getProperties() | ||
.filter((symbol) => !(symbol.getFlags() & ts.SymbolFlags.Optional)) | ||
.map((symbol) => symbol.getName()); | ||
return Object.assign({ | ||
type: "object", | ||
properties: Object.fromEntries(type.getProperties().map((symbol) => { | ||
const symbolDeclaration = symbol.getDeclarations()?.[0]; | ||
if (typeof symbolDeclaration === "undefined") { | ||
throw new Error(`Internal imaginary function compiler error: symbol '${symbol.getName()}' did not have a declaration. Please report this as a bug.`); | ||
} | ||
const propertyType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbolDeclaration); | ||
return [ | ||
symbol.getName(), | ||
tsTypeToJsonSchema(propertyType, typeChecker, [ | ||
...path, | ||
symbol.getName(), | ||
]), | ||
]; | ||
})), | ||
}, requiredPropNames.length > 0 ? { required: requiredPropNames } : {}); | ||
} | ||
throw new Error(`Imaginary function unexpected error: we do not yet support this type: ${typeChecker.typeToString(type)}. (at path '${readablePath}')`); | ||
}; | ||
const isArray = (type) => { | ||
return isBuiltInWithName(type, "Array"); | ||
}; | ||
const isPromise = (type) => { | ||
return isBuiltInWithName(type, "Promise"); | ||
}; | ||
const isBuiltInWithName = (type, name) => { | ||
return (isBuiltInSymbol(type.getSymbol(), name) || | ||
isBuiltInSymbol(type.aliasSymbol, name)); | ||
}; | ||
function getBuiltinTypeInfo(type) { | ||
// sometimes type arguments hide out on the alias: | ||
const { aliasTypeArguments, typeArguments } = type; | ||
if (aliasTypeArguments) { | ||
return { | ||
typeArguments: aliasTypeArguments, | ||
typeName: type.aliasSymbol.getName(), | ||
}; | ||
} | ||
return { | ||
typeArguments, | ||
typeName: type.getSymbol()?.getName(), | ||
}; | ||
} | ||
function isBuiltInSymbol(symbol, name) { | ||
// there may be a better way to do this, but I've spent quite a lot of time trying to figure this | ||
// out, and it's time to do the quick way. | ||
// this is inspired by https://stackoverflow.com/questions/67537309/typescript-compile-api-detect-builtins-like-math | ||
return (symbol?.getName() === name && | ||
!!symbol | ||
?.getDeclarations() | ||
?.some((s) => s.getSourceFile().fileName.includes("/node_modules/typescript/lib/"))); | ||
} | ||
exports.getImaginaryTsDocComments = getImaginaryTsDocComments; | ||
// this was generated (and then extensively modified) from https://ts-creator.js.org/ for | ||
@@ -407,3 +203,3 @@ // the following code: | ||
// } | ||
function getASTForName(promptEngineIdentifier, imaginaryComment, functionName, paramTypes, returnType, promisedType, typeChecker) { | ||
function getASTForName(promptEngineIdentifier, imaginaryComment, functionName, paramTypes, serviceParameters, promisedType, typeChecker, node) { | ||
return ts.factory.createFunctionDeclaration([ | ||
@@ -419,7 +215,10 @@ ts.factory.createModifier(ts.SyntaxKind.ExportKeyword), | ||
name, | ||
type: type ? tsTypeToJsonSchema(type, typeChecker) : {}, | ||
type: type | ||
? (0, ts_type_to_json_schema_1.tsTypeToJsonSchema)(type, typeChecker, node) | ||
: {}, | ||
}; | ||
})), | ||
(0, json_object_to_ts_ast_1.default)(tsTypeToJsonSchema(promisedType, typeChecker)), | ||
(0, json_object_to_ts_ast_1.default)((0, ts_type_to_json_schema_1.tsTypeToJsonSchema)(promisedType, typeChecker, node)), | ||
ts.factory.createObjectLiteralExpression(paramTypes.map(({ name: paramName }) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(paramName), undefined)), false), | ||
(0, json_object_to_ts_ast_1.default)(serviceParameters), | ||
]))), | ||
@@ -426,0 +225,0 @@ ], true)); |
@@ -6,4 +6,17 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.hashFunctionDefinition = exports.AI_SERVICES = void 0; | ||
exports.hashFunctionDefinition = exports.AI_SERVICES = exports.getSafeOpenAIServiceParameters = void 0; | ||
const object_hash_1 = __importDefault(require("object-hash")); | ||
const safeServiceParameterKeys = [ | ||
"temperature", | ||
"max_tokens", | ||
]; | ||
/** Extract only the exact keys that we should be passing to OpenAI's api calls */ | ||
function getSafeOpenAIServiceParameters(serviceParameters) { | ||
const { max_tokens, temperature } = serviceParameters.openai ?? {}; | ||
return { | ||
...(max_tokens !== undefined ? { max_tokens } : {}), | ||
...(temperature !== undefined ? { temperature } : {}), | ||
}; | ||
} | ||
exports.getSafeOpenAIServiceParameters = getSafeOpenAIServiceParameters; | ||
exports.AI_SERVICES = ["openai"]; | ||
@@ -10,0 +23,0 @@ /** Come up with a unique hash to represent an imaginary function definition, for use in tracking changes, etc */ |
{ | ||
"name": "@imaginary-dev/typescript-transformer", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "A TypeScript plugin for transforming imaginary functions", | ||
@@ -5,0 +5,0 @@ "files": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
174753
74
2234