typespec-typescript-emitter 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +2 -1
- package/.prettierignore +2 -1
- package/CHANGELOG.md +19 -24
- package/CODE_OF_CONDUCT.md +128 -0
- package/README.md +362 -219
- package/dist/src/emit_routedTypemap.d.ts +4 -0
- package/dist/src/emit_routedTypemap.js +83 -0
- package/dist/src/emit_routedTypemap.js.map +1 -0
- package/dist/src/emit_routes.d.ts +3 -2
- package/dist/src/emit_routes.js +73 -47
- package/dist/src/emit_routes.js.map +1 -1
- package/dist/src/emit_types.d.ts +3 -9
- package/dist/src/emit_types.js +109 -74
- package/dist/src/emit_types.js.map +1 -1
- package/dist/src/emitter.d.ts +2 -6
- package/dist/src/emitter.js +18 -76
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/helpers/appendableString.d.ts +15 -0
- package/dist/src/helpers/appendableString.js +41 -0
- package/dist/src/helpers/appendableString.js.map +1 -0
- package/dist/src/helpers/arrays.d.ts +3 -0
- package/dist/src/helpers/arrays.js +23 -0
- package/dist/src/helpers/arrays.js.map +1 -0
- package/dist/src/{helper_autogenerateWarning.d.ts → helpers/autogenerateWarning.d.ts} +1 -1
- package/dist/src/{helper_autogenerateWarning.js → helpers/autogenerateWarning.js} +1 -2
- package/dist/src/helpers/autogenerateWarning.js.map +1 -0
- package/dist/src/helpers/buildTypeMap.d.ts +12 -0
- package/dist/src/helpers/buildTypeMap.js +44 -0
- package/dist/src/helpers/buildTypeMap.js.map +1 -0
- package/dist/src/helpers/diagnostics.d.ts +4 -0
- package/dist/src/helpers/diagnostics.js +15 -0
- package/dist/src/helpers/diagnostics.js.map +1 -0
- package/dist/src/helpers/getImports.d.ts +2 -0
- package/dist/src/helpers/getImports.js +3 -0
- package/dist/src/helpers/getImports.js.map +1 -0
- package/dist/src/helpers/namespaces.d.ts +4 -0
- package/dist/src/helpers/namespaces.js +14 -0
- package/dist/src/helpers/namespaces.js.map +1 -0
- package/dist/src/helpers/visibilityHelperFile.d.ts +4 -0
- package/dist/src/helpers/visibilityHelperFile.js +57 -0
- package/dist/src/helpers/visibilityHelperFile.js.map +1 -0
- package/dist/src/lib.d.ts +4 -1
- package/dist/src/lib.js +14 -3
- package/dist/src/lib.js.map +1 -1
- package/dist/src/parseOptions.d.ts +8 -0
- package/dist/src/parseOptions.js +26 -0
- package/dist/src/parseOptions.js.map +1 -0
- package/dist/src/resolve/Resolvable.d.ts +32 -0
- package/dist/src/resolve/Resolvable.js +180 -0
- package/dist/src/resolve/Resolvable.js.map +1 -0
- package/dist/src/resolve/Resolvable_helpers.d.ts +42 -0
- package/dist/src/resolve/Resolvable_helpers.js +19 -0
- package/dist/src/resolve/Resolvable_helpers.js.map +1 -0
- package/dist/src/resolve/operationTypemap.d.ts +21 -0
- package/dist/src/resolve/operationTypemap.js +138 -0
- package/dist/src/resolve/operationTypemap.js.map +1 -0
- package/dist/src/resolve/types/Enum.d.ts +21 -0
- package/dist/src/resolve/types/Enum.js +61 -0
- package/dist/src/resolve/types/Enum.js.map +1 -0
- package/dist/src/resolve/types/Model.Indexed.d.ts +12 -0
- package/dist/src/resolve/types/Model.Indexed.js +69 -0
- package/dist/src/resolve/types/Model.Indexed.js.map +1 -0
- package/dist/src/resolve/types/Model.Shaped.d.ts +17 -0
- package/dist/src/resolve/types/Model.Shaped.js +210 -0
- package/dist/src/resolve/types/Model.Shaped.js.map +1 -0
- package/dist/src/resolve/types/Scalar.d.ts +9 -0
- package/dist/src/resolve/types/Scalar.js +109 -0
- package/dist/src/resolve/types/Scalar.js.map +1 -0
- package/dist/src/resolve/types/Simple.d.ts +11 -0
- package/dist/src/resolve/types/Simple.js +49 -0
- package/dist/src/resolve/types/Simple.js.map +1 -0
- package/dist/src/resolve/types/Tuple.d.ts +9 -0
- package/dist/src/resolve/types/Tuple.js +38 -0
- package/dist/src/resolve/types/Tuple.js.map +1 -0
- package/dist/src/resolve/types/Union.d.ts +9 -0
- package/dist/src/resolve/types/Union.js +36 -0
- package/dist/src/resolve/types/Union.js.map +1 -0
- package/eslint.config.js +12 -4
- package/package.json +2 -2
- package/src/emit_routedTypemap.ts +128 -0
- package/src/emit_routes.ts +108 -61
- package/src/emit_types.ts +137 -92
- package/src/emitter.ts +19 -107
- package/src/helpers/appendableString.ts +52 -0
- package/src/helpers/arrays.ts +21 -0
- package/src/{helper_autogenerateWarning.ts → helpers/autogenerateWarning.ts} +0 -1
- package/src/helpers/buildTypeMap.ts +72 -0
- package/src/helpers/diagnostics.ts +19 -0
- package/src/helpers/getImports.ts +9 -0
- package/src/helpers/namespaces.ts +18 -0
- package/src/helpers/visibilityHelperFile.ts +63 -0
- package/src/lib.ts +25 -4
- package/src/parseOptions.ts +26 -0
- package/src/resolve/Resolvable.ts +267 -0
- package/src/resolve/Resolvable_helpers.ts +65 -0
- package/src/resolve/operationTypemap.ts +212 -0
- package/src/resolve/types/Enum.ts +92 -0
- package/src/resolve/types/Model.Indexed.ts +113 -0
- package/src/resolve/types/Model.Shaped.ts +291 -0
- package/src/resolve/types/Scalar.ts +140 -0
- package/src/resolve/types/Simple.ts +88 -0
- package/src/resolve/types/Tuple.ts +56 -0
- package/src/resolve/types/Union.ts +52 -0
- package/test/helpers/integrationTest-novis.tsp +51 -0
- package/test/helpers/integrationTest.tsp +53 -0
- package/test/helpers/largeModel.tsp +40 -0
- package/test/{runner.ts → helpers/runner.ts} +1 -1
- package/test/helpers/ts.ts +11 -0
- package/test/helpers/wrapper.ts +144 -0
- package/test/routes/routes.target.ts +35 -0
- package/test/routes/routes.test.ts +22 -0
- package/test/typeguards/combined.test.ts +78 -0
- package/test/typeguards/enum.test.ts +10 -0
- package/test/typeguards/model.indexed.test.ts +68 -0
- package/test/typeguards/model.shaped.test.ts +38 -0
- package/test/typeguards/scalar.test.ts +62 -0
- package/test/typeguards/simple.test.ts +35 -0
- package/test/typeguards/tuple.test.ts +34 -0
- package/test/typeguards/union.test.ts +29 -0
- package/test/typemap/typemap-novis.target.ts +38 -0
- package/test/typemap/typemap.target.ts +39 -0
- package/test/typemap/typemap.test.ts +48 -0
- package/test/types/combined.test.ts +71 -0
- package/test/types/enum.test.ts +57 -0
- package/test/types/model.indexed.test.ts +46 -0
- package/test/types/model.shaped.test.ts +23 -0
- package/test/types/scalar.test.ts +53 -0
- package/test/types/simple.test.ts +20 -0
- package/test/types/tuple.test.ts +29 -0
- package/test/types/union.test.ts +20 -0
- package/tsconfig.json +1 -0
- package/dist/src/emit_mapped_types.d.ts +0 -2
- package/dist/src/emit_mapped_types.js +0 -124
- package/dist/src/emit_mapped_types.js.map +0 -1
- package/dist/src/emit_types_resolve.d.ts +0 -22
- package/dist/src/emit_types_resolve.js +0 -217
- package/dist/src/emit_types_resolve.js.map +0 -1
- package/dist/src/emit_types_typeguards.d.ts +0 -17
- package/dist/src/emit_types_typeguards.js +0 -121
- package/dist/src/emit_types_typeguards.js.map +0 -1
- package/dist/src/helper_autogenerateWarning.js.map +0 -1
- package/src/emit_mapped_types.ts +0 -155
- package/src/emit_types_resolve.ts +0 -280
- package/src/emit_types_typeguards.ts +0 -178
- package/test/main.test.ts +0 -83
- package/test/out/.gitkeep +0 -0
- package/test/targets/enum.routed-types.ts +0 -59
- package/test/targets/enum.routes.ts +0 -29
- package/test/targets/enum.target.ts +0 -39
- package/test/targets/enum.tsp +0 -36
- package/test/targets/pr8.routed-types.ts +0 -131
- package/test/targets/pr8.routes.ts +0 -30
- package/test/targets/pr8.target.ts +0 -97
- package/test/targets/pr8.tsp +0 -62
- package/test/targets/simple-routes.routed-types.ts +0 -64
- package/test/targets/simple-routes.routes.ts +0 -29
- package/test/targets/simple-routes.target.ts +0 -21
- package/test/targets/simple-routes.tsp +0 -23
- package/test/targets/union.routed-types.ts +0 -59
- package/test/targets/union.routes.ts +0 -23
- package/test/targets/union.target.ts +0 -59
- package/test/targets/union.tsp +0 -38
- package/test/targets/visibility.routed-types.ts +0 -81
- package/test/targets/visibility.routes.ts +0 -38
- package/test/targets/visibility.target.ts +0 -36
- package/test/targets/visibility.tsp +0 -49
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getDoc, Union } from "@typespec/compiler";
|
|
2
|
+
import { Resolvable } from "../Resolvable.js";
|
|
3
|
+
import {
|
|
4
|
+
Resolver,
|
|
5
|
+
ResolverOptions,
|
|
6
|
+
ResolverResult,
|
|
7
|
+
} from "../Resolvable_helpers.js";
|
|
8
|
+
|
|
9
|
+
export class ResolvableUnion extends Resolvable<Union> {
|
|
10
|
+
protected expectedTypeKind = "Union";
|
|
11
|
+
|
|
12
|
+
public override async hasVisibility(
|
|
13
|
+
opts: ResolverOptions<Resolver>,
|
|
14
|
+
out: ResolverResult<Resolver>,
|
|
15
|
+
): Promise<boolean> {
|
|
16
|
+
if (await super.hasVisibility(opts, out)) return true;
|
|
17
|
+
for (const v of Array.from(this._t.variants)) {
|
|
18
|
+
if (await Resolvable.hasVisibility(v[1].type, opts, out, this._t))
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected async type(
|
|
25
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
26
|
+
out: ResolverResult<Resolver.Type>,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (opts.emitDocs) {
|
|
29
|
+
out.doc = getDoc(opts.program, this._t);
|
|
30
|
+
}
|
|
31
|
+
const results: string[] = [];
|
|
32
|
+
for (const v of Array.from(this._t.variants)) {
|
|
33
|
+
const resolved = await this.resolveNested(v[1].type, opts, out, false);
|
|
34
|
+
out.imports.push(...resolved.imports);
|
|
35
|
+
results.push(resolved.resolved.value);
|
|
36
|
+
}
|
|
37
|
+
out.resolved.append(results.join(" | "));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected async typeguard(
|
|
41
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
42
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const results: string[] = [];
|
|
45
|
+
for (const v of Array.from(this._t.variants)) {
|
|
46
|
+
const resolved = await this.resolveNested(v[1].type, opts, out);
|
|
47
|
+
out.imports.push(...resolved.imports);
|
|
48
|
+
results.push(`(${resolved.resolved.value})`);
|
|
49
|
+
}
|
|
50
|
+
out.resolved.append(results.join(" || "));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import "@typespec/http";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.Http;
|
|
4
|
+
|
|
5
|
+
@service()
|
|
6
|
+
namespace test {
|
|
7
|
+
enum AdminLevel {
|
|
8
|
+
Level1,
|
|
9
|
+
Level2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
scalar Name extends string;
|
|
13
|
+
alias Timestamp = unixTimestamp32;
|
|
14
|
+
|
|
15
|
+
model Resource {
|
|
16
|
+
id_onlyRead: uint32,
|
|
17
|
+
name: Name,
|
|
18
|
+
metadata: {
|
|
19
|
+
created_onlyRead: Timestamp,
|
|
20
|
+
user: uint32,
|
|
21
|
+
username: string,
|
|
22
|
+
adminLevel?: AdminLevel | null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Retrieves an instance of the ressource */
|
|
27
|
+
@get
|
|
28
|
+
op getResource(): {
|
|
29
|
+
@statusCode status: 200,
|
|
30
|
+
@body body: Resource
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Creates a resource */
|
|
34
|
+
@put
|
|
35
|
+
op postResource(@body body: Resource | inner.InnerModel): OkResponse;
|
|
36
|
+
|
|
37
|
+
@route("/inner")
|
|
38
|
+
namespace inner {
|
|
39
|
+
model InnerModel {
|
|
40
|
+
resourceName: string,
|
|
41
|
+
resource: test.Resource
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
model InnerModel2 {
|
|
45
|
+
level: AdminLevel
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@delete
|
|
49
|
+
op del(@body body: InnerModel | InnerModel2): OkResponse | UnauthorizedResponse;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import "@typespec/http";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.Http;
|
|
4
|
+
|
|
5
|
+
@service()
|
|
6
|
+
namespace test {
|
|
7
|
+
enum AdminLevel {
|
|
8
|
+
Level1,
|
|
9
|
+
Level2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
scalar Name extends string;
|
|
13
|
+
alias Timestamp = unixTimestamp32;
|
|
14
|
+
|
|
15
|
+
model Resource {
|
|
16
|
+
@visibility(Lifecycle.Read)
|
|
17
|
+
id_onlyRead: uint32,
|
|
18
|
+
name: Name,
|
|
19
|
+
metadata: {
|
|
20
|
+
@visibility
|
|
21
|
+
created_onlyRead: Timestamp,
|
|
22
|
+
user: uint32,
|
|
23
|
+
username: string,
|
|
24
|
+
adminLevel?: AdminLevel | null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Retrieves an instance of the ressource */
|
|
29
|
+
@get
|
|
30
|
+
op getResource(): {
|
|
31
|
+
@statusCode status: 200,
|
|
32
|
+
@body body: Resource
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Creates a resource */
|
|
36
|
+
@put
|
|
37
|
+
op postResource(@body body: Resource | inner.InnerModel): OkResponse;
|
|
38
|
+
|
|
39
|
+
@route("/inner")
|
|
40
|
+
namespace inner {
|
|
41
|
+
model InnerModel {
|
|
42
|
+
resourceName: string,
|
|
43
|
+
resource: test.Resource
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
model InnerModel2 {
|
|
47
|
+
level: AdminLevel
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@delete
|
|
51
|
+
op del(@body body: InnerModel | InnerModel2): OkResponse | UnauthorizedResponse;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
scalar dateType extends unixTimestamp32;
|
|
2
|
+
alias aliasedString = string;
|
|
3
|
+
alias aliasedModel = {str: string, fl: float, nested: {int: int32}};
|
|
4
|
+
|
|
5
|
+
model test {
|
|
6
|
+
@visibility(Lifecycle.Read)
|
|
7
|
+
id_onlyRead: uint64,
|
|
8
|
+
name: string,
|
|
9
|
+
aliasedName?: aliasedString,
|
|
10
|
+
date: utcDateTime,
|
|
11
|
+
time: plainTime,
|
|
12
|
+
@visibility(Lifecycle.Create)
|
|
13
|
+
created_onlyCreate: unixTimestamp32,
|
|
14
|
+
@visibility(Lifecycle.Read, Lifecycle.Query)
|
|
15
|
+
tuple_strIntModel_readQuery: [string, int16, {inmodel_union: null | unknown}],
|
|
16
|
+
nested?: {
|
|
17
|
+
availability: boolean
|
|
18
|
+
},
|
|
19
|
+
array: string[],
|
|
20
|
+
array_union: (string | int16)[],
|
|
21
|
+
record: Record<string>,
|
|
22
|
+
record_union: Record<string | int16>,
|
|
23
|
+
@visibility(Lifecycle.Delete, Lifecycle.Query)
|
|
24
|
+
nested_deleteQuery: {
|
|
25
|
+
@visibility(Lifecycle.Query)
|
|
26
|
+
str_onlyQuery: string,
|
|
27
|
+
aliasedModel: aliasedModel,
|
|
28
|
+
modelWithVis: {
|
|
29
|
+
a: "a",
|
|
30
|
+
@visibility(Lifecycle.Delete)
|
|
31
|
+
seven_onlyDelete: 7
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
nestedInnerVis: {
|
|
35
|
+
unk?: unknown,
|
|
36
|
+
@visibility(Lifecycle.Update)
|
|
37
|
+
void_onlyUpdate: void
|
|
38
|
+
},
|
|
39
|
+
tupleWithUnion: [string, int32 | boolean]
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export const validateTS = (code: string): true | string => {
|
|
4
|
+
const out = ts.transpileModule(code, {
|
|
5
|
+
reportDiagnostics: true,
|
|
6
|
+
compilerOptions: { noEmit: true },
|
|
7
|
+
});
|
|
8
|
+
return out.diagnostics === undefined || out.diagnostics.length === 0
|
|
9
|
+
? true
|
|
10
|
+
: out.diagnostics.map((d) => d.messageText).join(", ");
|
|
11
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Type } from "@typespec/compiler";
|
|
2
|
+
import { resolveVirtualPath } from "@typespec/compiler/testing";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
import { EmitterOptions } from "../../src/lib";
|
|
5
|
+
import { Resolvable } from "../../src/resolve/Resolvable";
|
|
6
|
+
import { Resolver, ResolverResult } from "../../src/resolve/Resolvable_helpers";
|
|
7
|
+
import { runner } from "./runner";
|
|
8
|
+
import { validateTS } from "./ts";
|
|
9
|
+
|
|
10
|
+
export const defaultConfig: Omit<EmitterOptions, "out-dir"> = {
|
|
11
|
+
"root-namespaces": ["test"],
|
|
12
|
+
"enable-types": true,
|
|
13
|
+
"enable-typeguards": false,
|
|
14
|
+
"enable-routes": false,
|
|
15
|
+
"enable-routed-typemap": false,
|
|
16
|
+
"string-nominal-enums": true,
|
|
17
|
+
"serializable-date-types": true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type Filename = string;
|
|
21
|
+
export const expectEmit = <T extends string | Record<Filename, string>>(
|
|
22
|
+
desc: string,
|
|
23
|
+
input: T,
|
|
24
|
+
target: T,
|
|
25
|
+
config?: Partial<typeof defaultConfig>,
|
|
26
|
+
outFilename?: T extends string ? string : never,
|
|
27
|
+
): void => {
|
|
28
|
+
it(desc, async () => {
|
|
29
|
+
if (!outFilename) outFilename = "test.ts" as any;
|
|
30
|
+
const emitter = await runner.emit("typespec-typescript-emitter", {
|
|
31
|
+
...structuredClone(defaultConfig),
|
|
32
|
+
...(config ?? {}),
|
|
33
|
+
});
|
|
34
|
+
const instance = await emitter.createInstance();
|
|
35
|
+
const result = await instance.compileAndDiagnose(input);
|
|
36
|
+
|
|
37
|
+
// check for diagnostics
|
|
38
|
+
if (result[1].length > 0) console.error(result[1]);
|
|
39
|
+
expect(result[1].length).toBe(0);
|
|
40
|
+
|
|
41
|
+
// check all files against target
|
|
42
|
+
if (typeof target === "string") {
|
|
43
|
+
if (!result[0].outputs[outFilename!])
|
|
44
|
+
console.error(
|
|
45
|
+
`${outFilename!} not found in ${Object.keys(result[0].outputs)}`,
|
|
46
|
+
);
|
|
47
|
+
expect(result[0].outputs[outFilename!].replaceAll("\r", "").trim()).toBe(
|
|
48
|
+
target.replaceAll("\r", "").trim(),
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
for (const t of Object.entries(target)) {
|
|
52
|
+
expect(
|
|
53
|
+
(
|
|
54
|
+
await result[0].program.host.readFile(
|
|
55
|
+
resolveVirtualPath(t[0]).replaceAll("\r", "").trim(),
|
|
56
|
+
)
|
|
57
|
+
).text.trim(),
|
|
58
|
+
).toBe(t[1].replaceAll("\r", "").trim());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const expectResolution = (
|
|
65
|
+
r: Resolver,
|
|
66
|
+
args: {
|
|
67
|
+
/** Kind of the type we expect to get (@typespec/compiler.Type['kind']) */
|
|
68
|
+
type: string;
|
|
69
|
+
/** Description of the test, only used for test output */
|
|
70
|
+
desc?: string;
|
|
71
|
+
/** Source TSP */
|
|
72
|
+
source: string;
|
|
73
|
+
/** Expected TS output */
|
|
74
|
+
target: string;
|
|
75
|
+
/** Either `true` (all okay) or error message */
|
|
76
|
+
test?: (t: Type, r: ResolverResult<Resolver.Type>) => true | string;
|
|
77
|
+
/** Name of the type to compile; used to get type from program. */
|
|
78
|
+
typename?: string;
|
|
79
|
+
/** Partion EmitterOptions config object */
|
|
80
|
+
config?: Partial<typeof defaultConfig>;
|
|
81
|
+
/**
|
|
82
|
+
* If set, the TSP output will be put through this before checking typescript validity.
|
|
83
|
+
* Default is `type test = ${output}`.
|
|
84
|
+
* If set to null, typescript syntax will not be checked.
|
|
85
|
+
*/
|
|
86
|
+
typescriptTransformer?: ((tsp: string) => string) | null;
|
|
87
|
+
/** If set, does not check if resolution output is "truthy" */
|
|
88
|
+
noTruthyCheck?: boolean;
|
|
89
|
+
},
|
|
90
|
+
) => {
|
|
91
|
+
if (!args.typename) args.typename = "test";
|
|
92
|
+
if (!args.config) {
|
|
93
|
+
args.config = defaultConfig;
|
|
94
|
+
} else {
|
|
95
|
+
args.config = { ...defaultConfig, ...args.config };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Removes all leading and trailing whitespace on each line. */
|
|
99
|
+
const transformResult = (s: string): string =>
|
|
100
|
+
s
|
|
101
|
+
.split("\n")
|
|
102
|
+
.map((l) => l.trim())
|
|
103
|
+
.join("");
|
|
104
|
+
if (args.typescriptTransformer === undefined)
|
|
105
|
+
args.typescriptTransformer = (tsp) => `type test = ${tsp};`;
|
|
106
|
+
|
|
107
|
+
it(`${r === Resolver.Type ? "type" : "typeguard"}: ${args.type} ${args.desc ?? ""}`, async () => {
|
|
108
|
+
const { program } = await runner.compile(
|
|
109
|
+
args.source,
|
|
110
|
+
args.config as Record<string, any>,
|
|
111
|
+
);
|
|
112
|
+
// get compiled type
|
|
113
|
+
const result = program.resolveTypeReference(args.typename!);
|
|
114
|
+
// check for diagnostics
|
|
115
|
+
if (result[1].length > 0) console.error(result[1]);
|
|
116
|
+
expect(result[1].length).toBe(0); // no diagnostics
|
|
117
|
+
// type compiled and expected type.kind?
|
|
118
|
+
expect(result[0]).toBeDefined();
|
|
119
|
+
expect(result[0]?.kind).toBe(args.type);
|
|
120
|
+
// resolve to typescript
|
|
121
|
+
const resolved = await Resolvable.resolve(r, result[0]!, {
|
|
122
|
+
program,
|
|
123
|
+
options: args.config as EmitterOptions,
|
|
124
|
+
nestlevel: 0,
|
|
125
|
+
rootType: null,
|
|
126
|
+
typemap: [],
|
|
127
|
+
emitDocs: false,
|
|
128
|
+
});
|
|
129
|
+
// check if valid typescript
|
|
130
|
+
if (args.typescriptTransformer !== null) {
|
|
131
|
+
const tsCode = args.typescriptTransformer!(resolved.resolved.value);
|
|
132
|
+
let tsValidity = validateTS(tsCode);
|
|
133
|
+
if (tsValidity !== true) tsValidity += `\nTypescript: ${tsCode}`;
|
|
134
|
+
expect(tsValidity).toBe(true);
|
|
135
|
+
}
|
|
136
|
+
if (!args.noTruthyCheck) expect(resolved.resolved.value).toBeTruthy();
|
|
137
|
+
// check generated typescript content
|
|
138
|
+
expect(transformResult(resolved.resolved.value)).toBe(
|
|
139
|
+
transformResult(transformResult(args.target)),
|
|
140
|
+
);
|
|
141
|
+
if (args.test !== undefined)
|
|
142
|
+
expect(await args.test(result[0]!, resolved)).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* This file is automatically generated by typespec-typescript-emitter.
|
|
6
|
+
*
|
|
7
|
+
* You should not change or manipulate this file, as it will be overwritten.
|
|
8
|
+
* Instead, change the underlying spec.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export const routes_test = {
|
|
13
|
+
/** Retrieves an instance of the ressource */
|
|
14
|
+
getResource: {
|
|
15
|
+
verb: 'GET',
|
|
16
|
+
path: '/',
|
|
17
|
+
getUrl: (): string => `/`,
|
|
18
|
+
auth: [null]
|
|
19
|
+
},
|
|
20
|
+
/** Creates a resource */
|
|
21
|
+
postResource: {
|
|
22
|
+
verb: 'PUT',
|
|
23
|
+
path: '/',
|
|
24
|
+
getUrl: (): string => `/`,
|
|
25
|
+
auth: [null]
|
|
26
|
+
},
|
|
27
|
+
inner: {
|
|
28
|
+
del: {
|
|
29
|
+
verb: 'DELETE',
|
|
30
|
+
path: '/inner',
|
|
31
|
+
getUrl: (): string => `/inner`,
|
|
32
|
+
auth: [null]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} as const;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { expectEmit } from "../helpers/wrapper";
|
|
4
|
+
|
|
5
|
+
const files: Record<string, string> = {
|
|
6
|
+
["main.tsp"]: readFileSync(
|
|
7
|
+
join(__dirname, "..", "helpers", "integrationTest.tsp"),
|
|
8
|
+
"utf8",
|
|
9
|
+
),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
expectEmit(
|
|
13
|
+
"route object",
|
|
14
|
+
files,
|
|
15
|
+
{
|
|
16
|
+
"routes_test.ts": `/* eslint-disable */${readFileSync(
|
|
17
|
+
join(__dirname, "routes.target.ts"),
|
|
18
|
+
"utf8",
|
|
19
|
+
)}`,
|
|
20
|
+
},
|
|
21
|
+
{ "enable-routes": true },
|
|
22
|
+
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { Resolver } from "../../src/resolve/Resolvable_helpers";
|
|
4
|
+
import { expectResolution } from "../helpers/wrapper";
|
|
5
|
+
|
|
6
|
+
const input = readFileSync(
|
|
7
|
+
join(__dirname, "..", "helpers", "largeModel.tsp"),
|
|
8
|
+
"utf8",
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const target = `
|
|
12
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Read].includes(vis) ? !('id_onlyRead' in undefined) : (undefined['id_onlyRead'] !== undefined && (typeof undefined['id_onlyRead'] === 'number'))) &&
|
|
13
|
+
undefined['name'] !== undefined && (typeof undefined['name'] === 'string') &&
|
|
14
|
+
undefined['aliasedName'] === undefined || (typeof undefined['aliasedName'] === 'string') &&
|
|
15
|
+
undefined['date'] !== undefined && (undefined['date'] instanceof Date) &&
|
|
16
|
+
undefined['time'] !== undefined && (typeof undefined['time'] === 'string') &&
|
|
17
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Create].includes(vis) ? !('created_onlyCreate' in undefined) : (
|
|
18
|
+
undefined['created_onlyCreate'] !== undefined && (undefined['created_onlyCreate'] instanceof Date)
|
|
19
|
+
)) &&
|
|
20
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Read, Lifecycle.Query].includes(vis) ? !('tuple_strIntModel_readQuery' in undefined) : (
|
|
21
|
+
undefined['tuple_strIntModel_readQuery'] !== undefined && (
|
|
22
|
+
Array.isArray(undefined['tuple_strIntModel_readQuery']) && (
|
|
23
|
+
typeof undefined['tuple_strIntModel_readQuery'][0] === 'string'
|
|
24
|
+
) && (
|
|
25
|
+
typeof undefined['tuple_strIntModel_readQuery'][1] === 'number'
|
|
26
|
+
) && (
|
|
27
|
+
undefined['tuple_strIntModel_readQuery'][2]['inmodel_union'] !== undefined && (
|
|
28
|
+
(undefined['tuple_strIntModel_readQuery'][2]['inmodel_union'] === null) || (true)
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
)) &&
|
|
33
|
+
undefined['nested'] === undefined || (undefined['nested']['availability'] !== undefined && (typeof undefined['nested']['availability'] === 'boolean')) &&
|
|
34
|
+
undefined['array'] !== undefined && (Array.isArray(undefined['array']) && undefined['array'].every((v) => typeof v === 'string')) &&
|
|
35
|
+
undefined['array_union'] !== undefined && (Array.isArray(undefined['array_union']) && undefined['array_union'].every((v) => (typeof v === 'string') || (typeof v === 'number'))) &&
|
|
36
|
+
undefined['record'] !== undefined && (typeof undefined['record'] === 'object' && Object.entries(undefined['record'] as Record<string, any>).every((v) => typeof v[1] === 'string')) &&
|
|
37
|
+
undefined['record_union'] !== undefined && (typeof undefined['record_union'] === 'object' && Object.entries(undefined['record_union'] as Record<string, any>).every((v) => (typeof v[1] === 'string') || (typeof v[1] === 'number'))) &&
|
|
38
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Delete, Lifecycle.Query].includes(vis) ? !('nested_deleteQuery' in undefined) : (
|
|
39
|
+
undefined['nested_deleteQuery'] !== undefined && (
|
|
40
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Query].includes(vis) ? !('str_onlyQuery' in undefined['nested_deleteQuery']) : (
|
|
41
|
+
undefined['nested_deleteQuery']['str_onlyQuery'] !== undefined && (typeof undefined['nested_deleteQuery']['str_onlyQuery'] === 'string')
|
|
42
|
+
)) &&
|
|
43
|
+
undefined['nested_deleteQuery']['aliasedModel'] !== undefined && (
|
|
44
|
+
undefined['nested_deleteQuery']['aliasedModel']['str'] !== undefined && (typeof undefined['nested_deleteQuery']['aliasedModel']['str'] === 'string') &&
|
|
45
|
+
undefined['nested_deleteQuery']['aliasedModel']['fl'] !== undefined && (typeof undefined['nested_deleteQuery']['aliasedModel']['fl'] === 'number') &&
|
|
46
|
+
undefined['nested_deleteQuery']['aliasedModel']['nested'] !== undefined && (
|
|
47
|
+
undefined['nested_deleteQuery']['aliasedModel']['nested']['int'] !== undefined && (typeof undefined['nested_deleteQuery']['aliasedModel']['nested']['int'] === 'number')
|
|
48
|
+
)
|
|
49
|
+
) &&
|
|
50
|
+
undefined['nested_deleteQuery']['modelWithVis'] !== undefined && (
|
|
51
|
+
undefined['nested_deleteQuery']['modelWithVis']['a'] !== undefined && (undefined['nested_deleteQuery']['modelWithVis']['a'] === 'a') &&
|
|
52
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Delete].includes(vis) ? !('seven_onlyDelete' in undefined['nested_deleteQuery']['modelWithVis']) : (
|
|
53
|
+
undefined['nested_deleteQuery']['modelWithVis']['seven_onlyDelete'] !== undefined && (undefined['nested_deleteQuery']['modelWithVis']['seven_onlyDelete'] === 7)
|
|
54
|
+
))
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
)) &&
|
|
58
|
+
undefined['nestedInnerVis'] !== undefined && (
|
|
59
|
+
undefined['nestedInnerVis']['unk'] === undefined || (true) &&
|
|
60
|
+
((vis as any) !== Lifecycle.All && ![Lifecycle.Update].includes(vis) ? !('void_onlyUpdate' in undefined['nestedInnerVis']) : (
|
|
61
|
+
undefined['nestedInnerVis']['void_onlyUpdate'] === undefined
|
|
62
|
+
))
|
|
63
|
+
) &&
|
|
64
|
+
undefined['tupleWithUnion'] !== undefined && (
|
|
65
|
+
Array.isArray(undefined['tupleWithUnion']) && (typeof undefined['tupleWithUnion'][0] === 'string') && ((typeof undefined['tupleWithUnion'][1] === 'number') || (typeof undefined['tupleWithUnion'][1] === 'boolean'))
|
|
66
|
+
)
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
expectResolution(Resolver.Typeguard, {
|
|
70
|
+
type: "Model",
|
|
71
|
+
desc: "combined",
|
|
72
|
+
source: input,
|
|
73
|
+
target: target,
|
|
74
|
+
config: {
|
|
75
|
+
"serializable-date-types": false,
|
|
76
|
+
},
|
|
77
|
+
typescriptTransformer: null,
|
|
78
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Resolver } from "../../src/resolve/Resolvable_helpers";
|
|
2
|
+
import { expectResolution } from "../helpers/wrapper";
|
|
3
|
+
|
|
4
|
+
expectResolution(Resolver.Typeguard, {
|
|
5
|
+
type: "Enum",
|
|
6
|
+
source: "enum test {val1, val2}",
|
|
7
|
+
target: "", // enums don't have typeguards
|
|
8
|
+
typescriptTransformer: null,
|
|
9
|
+
noTruthyCheck: true,
|
|
10
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Resolver } from "../../src/resolve/Resolvable_helpers";
|
|
2
|
+
import { expectResolution } from "../helpers/wrapper";
|
|
3
|
+
|
|
4
|
+
type Name = string;
|
|
5
|
+
type Source = string;
|
|
6
|
+
type Target = string;
|
|
7
|
+
|
|
8
|
+
// because there is no accessor, we are "testing" `undefined`
|
|
9
|
+
const tests: [Name, string, Source, Target][] = [
|
|
10
|
+
[
|
|
11
|
+
"Array",
|
|
12
|
+
"simple",
|
|
13
|
+
"string[]",
|
|
14
|
+
"Array.isArray(undefined) && undefined.every((v) => typeof v === 'string')",
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"Array",
|
|
18
|
+
"union",
|
|
19
|
+
"(string | int32)[]",
|
|
20
|
+
"Array.isArray(undefined) && undefined.every((v) => (typeof v === 'string') || (typeof v === 'number'))",
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"Array",
|
|
24
|
+
"tuple",
|
|
25
|
+
"[string, int32][]",
|
|
26
|
+
"Array.isArray(undefined) && undefined.every((v) => Array.isArray(v) && (typeof v[0] === 'string') && (typeof v[1] === 'number'))",
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
"Array",
|
|
30
|
+
"model",
|
|
31
|
+
"{str: string}[]",
|
|
32
|
+
"Array.isArray(undefined) && undefined.every((v) =>v['str'] !== undefined && (typeof v['str'] === 'string') )",
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
"Record",
|
|
36
|
+
"simple",
|
|
37
|
+
"Record<string>",
|
|
38
|
+
"typeof undefined === 'object' && Object.entries(undefined as Record<string, any>).every((v) => typeof v[1] === 'string')",
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"Record",
|
|
42
|
+
"union",
|
|
43
|
+
"Record<string | int32>",
|
|
44
|
+
"typeof undefined === 'object' && Object.entries(undefined as Record<string, any>).every((v) => (typeof v[1] === 'string') || (typeof v[1] === 'number'))",
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"Record",
|
|
48
|
+
"tuple",
|
|
49
|
+
"Record<[string, int32]>",
|
|
50
|
+
"typeof undefined === 'object' && Object.entries(undefined as Record<string, any>).every((v) => Array.isArray(v[1]) && (typeof v[1][0] === 'string') && (typeof v[1][1] === 'number'))",
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
"Record",
|
|
54
|
+
"model",
|
|
55
|
+
"Record<{str?: string}>",
|
|
56
|
+
"typeof undefined === 'object' && Object.entries(undefined as Record<string, any>).every((v) =>v[1]['str'] === undefined || (typeof v[1]['str'] === 'string') )",
|
|
57
|
+
],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
tests.forEach((test) => {
|
|
61
|
+
expectResolution(Resolver.Typeguard, {
|
|
62
|
+
type: "Model",
|
|
63
|
+
desc: `(indexed: ${test[0]}): ${test[1]}`,
|
|
64
|
+
source: `alias test = ${test[2]};`,
|
|
65
|
+
target: test[3],
|
|
66
|
+
typescriptTransformer: null,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Resolver } from "../../src/resolve/Resolvable_helpers";
|
|
2
|
+
import { expectResolution } from "../helpers/wrapper";
|
|
3
|
+
|
|
4
|
+
const tests: [string, string][] = [
|
|
5
|
+
[
|
|
6
|
+
"{s: string}",
|
|
7
|
+
"undefined['s'] !== undefined && (typeof undefined['s'] === 'string')",
|
|
8
|
+
],
|
|
9
|
+
[
|
|
10
|
+
"{s?: string}",
|
|
11
|
+
"undefined['s'] === undefined || (typeof undefined['s'] === 'string')",
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"{m: {s: string | int32}}",
|
|
15
|
+
"undefined['m'] !== undefined && (undefined['m']['s'] !== undefined && ((typeof undefined['m']['s'] === 'string') || (typeof undefined['m']['s'] === 'number')))",
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"{a: {s: utcDateTime}[]}",
|
|
19
|
+
"undefined['a'] !== undefined && (Array.isArray(undefined['a']) && undefined['a'].every((v) =>v['s'] !== undefined && (v['s'] instanceof Date) ))",
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"{r: Record<[string, int32]>, a: string[], f: never}",
|
|
23
|
+
"undefined['r'] !== undefined && (typeof undefined['r'] === 'object' && Object.entries(undefined['r'] as Record<string, any>).every((v) => Array.isArray(v[1]) && (typeof v[1][0] === 'string') && (typeof v[1][1] === 'number'))) &&" +
|
|
24
|
+
"undefined['a'] !== undefined && (Array.isArray(undefined['a']) && undefined['a'].every((v) => typeof v === 'string')) &&" +
|
|
25
|
+
"!('f' in undefined)",
|
|
26
|
+
],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
tests.forEach((test) => {
|
|
30
|
+
expectResolution(Resolver.Typeguard, {
|
|
31
|
+
type: "Model",
|
|
32
|
+
desc: test[0],
|
|
33
|
+
source: `alias test = ${test[0]};`,
|
|
34
|
+
target: test[1],
|
|
35
|
+
typescriptTransformer: null,
|
|
36
|
+
config: { "serializable-date-types": false },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Scalar } from "@typespec/compiler";
|
|
2
|
+
import { EmitterOptions } from "../../src/lib";
|
|
3
|
+
import { Resolver } from "../../src/resolve/Resolvable_helpers";
|
|
4
|
+
import { expectResolution } from "../helpers/wrapper";
|
|
5
|
+
|
|
6
|
+
type Name = string;
|
|
7
|
+
type Target = string;
|
|
8
|
+
|
|
9
|
+
const tests: [Name, Target, Partial<EmitterOptions>?][] = [
|
|
10
|
+
["boolean", "typeof undefined === 'boolean'"],
|
|
11
|
+
["bytes", "undefined instanceof Uint8Array"],
|
|
12
|
+
["duration", "typeof undefined === 'number'"],
|
|
13
|
+
["numeric", "typeof undefined === 'number'"],
|
|
14
|
+
["plainTime", "typeof undefined === 'string'"],
|
|
15
|
+
["string", "typeof undefined === 'string'"],
|
|
16
|
+
["url", "typeof undefined === 'string'"],
|
|
17
|
+
|
|
18
|
+
// date / time types
|
|
19
|
+
...["offsetDateTime", "plainDate", "utcDateTime"].map(
|
|
20
|
+
(t) =>
|
|
21
|
+
[
|
|
22
|
+
t,
|
|
23
|
+
"undefined instanceof Date",
|
|
24
|
+
{ "serializable-date-types": false },
|
|
25
|
+
] as [string, string, any],
|
|
26
|
+
),
|
|
27
|
+
...["offsetDateTime", "plainDate", "utcDateTime"].map(
|
|
28
|
+
(t) =>
|
|
29
|
+
[
|
|
30
|
+
t,
|
|
31
|
+
"typeof undefined === 'string'",
|
|
32
|
+
{ "serializable-date-types": true },
|
|
33
|
+
] as [string, string, any],
|
|
34
|
+
),
|
|
35
|
+
[
|
|
36
|
+
"unixTimestamp32",
|
|
37
|
+
"undefined instanceof Date",
|
|
38
|
+
{ "serializable-date-types": false },
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"unixTimestamp32",
|
|
42
|
+
"typeof undefined === 'number'",
|
|
43
|
+
{ "serializable-date-types": true },
|
|
44
|
+
],
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
tests.forEach((test) => {
|
|
48
|
+
expectResolution(Resolver.Typeguard, {
|
|
49
|
+
type: "Scalar",
|
|
50
|
+
desc: test[2]
|
|
51
|
+
? `{${Object.entries(test[2]).map((p) => `${p[0]}: ${p[1]}`)}}`
|
|
52
|
+
: undefined,
|
|
53
|
+
source: `alias test = ${test[0]};`,
|
|
54
|
+
target: test[1],
|
|
55
|
+
test: (t) =>
|
|
56
|
+
(t as Scalar).name === test[0]
|
|
57
|
+
? true
|
|
58
|
+
: `scalar name was ${(t as Scalar).name}`,
|
|
59
|
+
config: test[2],
|
|
60
|
+
typescriptTransformer: null,
|
|
61
|
+
});
|
|
62
|
+
});
|