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
package/src/emit_mapped_types.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EmitContext,
|
|
3
|
-
isTemplateDeclaration,
|
|
4
|
-
Namespace,
|
|
5
|
-
Operation,
|
|
6
|
-
Type,
|
|
7
|
-
} from "@typespec/compiler";
|
|
8
|
-
import {
|
|
9
|
-
getHttpOperation,
|
|
10
|
-
resolveRequestVisibility,
|
|
11
|
-
Visibility,
|
|
12
|
-
} from "@typespec/http";
|
|
13
|
-
import { resolveType } from "./emit_types_resolve.js";
|
|
14
|
-
|
|
15
|
-
export const emitRoutedTypemap = (
|
|
16
|
-
context: EmitContext,
|
|
17
|
-
namespace: Namespace,
|
|
18
|
-
): string => {
|
|
19
|
-
const ops: {
|
|
20
|
-
[path: string]: {
|
|
21
|
-
[verb: string]: {
|
|
22
|
-
// "string" in these does not refer to the type "string"! It's the typescript code as string.
|
|
23
|
-
request: string;
|
|
24
|
-
response: Array<{ status: number | "unknown"; body: string }>;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
} = {};
|
|
28
|
-
|
|
29
|
-
const traverseNamespace = (n: Namespace): void => {
|
|
30
|
-
// operations
|
|
31
|
-
const processOp = (op: Operation) => {
|
|
32
|
-
const httpOp = getHttpOperation(context.program, op);
|
|
33
|
-
const path = httpOp[0].path;
|
|
34
|
-
const verb = httpOp[0].verb.toUpperCase();
|
|
35
|
-
if (!ops[path]) ops[path] = {};
|
|
36
|
-
ops[path][verb] = {
|
|
37
|
-
request: "null",
|
|
38
|
-
response: [{ status: 200, body: "{}" }],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// request
|
|
42
|
-
let request = "null";
|
|
43
|
-
if (httpOp[0].parameters.body) {
|
|
44
|
-
request = resolveType(httpOp[0].parameters.body.type, {
|
|
45
|
-
nestlevel: 2,
|
|
46
|
-
currentNamespace: namespace,
|
|
47
|
-
context,
|
|
48
|
-
visibility: resolveRequestVisibility(
|
|
49
|
-
context.program,
|
|
50
|
-
op,
|
|
51
|
-
httpOp[0].verb,
|
|
52
|
-
),
|
|
53
|
-
resolveEvenWithName: true,
|
|
54
|
-
}).replaceAll("\n", "\n ");
|
|
55
|
-
}
|
|
56
|
-
ops[path][verb].request = request;
|
|
57
|
-
|
|
58
|
-
// response
|
|
59
|
-
if (op.returnType && op.returnType.kind) {
|
|
60
|
-
const getReturnType = (
|
|
61
|
-
t: Type,
|
|
62
|
-
): (typeof ops)[string][string]["response"] => {
|
|
63
|
-
const ret: (typeof ops)[string][string]["response"] = [];
|
|
64
|
-
if (t.kind === "Model") {
|
|
65
|
-
// if the return type is a model, it may have a fully qualified body
|
|
66
|
-
const modelret: (typeof ret)[number] = {
|
|
67
|
-
status: 200,
|
|
68
|
-
body: "{}",
|
|
69
|
-
};
|
|
70
|
-
let wasQualifiedBody = false;
|
|
71
|
-
t.properties.forEach((prop) => {
|
|
72
|
-
prop.decorators.forEach((dec) => {
|
|
73
|
-
// one of the properties may be the status code
|
|
74
|
-
if (
|
|
75
|
-
dec.definition?.name === "@statusCode" &&
|
|
76
|
-
prop.type.kind === "Number"
|
|
77
|
-
)
|
|
78
|
-
modelret.status = prop.type.value;
|
|
79
|
-
// one of the properties may be the body definition
|
|
80
|
-
if (dec.definition?.name === "@body") {
|
|
81
|
-
modelret.body = resolveType(prop.type, {
|
|
82
|
-
nestlevel: 2,
|
|
83
|
-
currentNamespace: namespace,
|
|
84
|
-
context,
|
|
85
|
-
visibility: Visibility.Read,
|
|
86
|
-
resolveEvenWithName: true,
|
|
87
|
-
}).replaceAll("\n", "\n ");
|
|
88
|
-
wasQualifiedBody = true;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
// ... if not, we assume status 200 and treat the model as the body
|
|
93
|
-
if (!wasQualifiedBody) {
|
|
94
|
-
modelret.body = resolveType(t, {
|
|
95
|
-
nestlevel: 2,
|
|
96
|
-
currentNamespace: namespace,
|
|
97
|
-
context,
|
|
98
|
-
visibility: Visibility.Read,
|
|
99
|
-
resolveEvenWithName: true,
|
|
100
|
-
}).replaceAll("\n", "\n ");
|
|
101
|
-
}
|
|
102
|
-
ret.push(modelret);
|
|
103
|
-
} else if (t.kind === "Union") {
|
|
104
|
-
// if the return type is a union, we have to check and resolve all variants
|
|
105
|
-
// the union could either be a body-only definition or fully qualified (see above)
|
|
106
|
-
t.variants.forEach((variant) => {
|
|
107
|
-
ret.push(...getReturnType(variant.type));
|
|
108
|
-
});
|
|
109
|
-
} else
|
|
110
|
-
ret.push({
|
|
111
|
-
status: 200,
|
|
112
|
-
body: resolveType(t, {
|
|
113
|
-
nestlevel: 1,
|
|
114
|
-
currentNamespace: namespace,
|
|
115
|
-
context,
|
|
116
|
-
visibility: Visibility.Read,
|
|
117
|
-
resolveEvenWithName: true,
|
|
118
|
-
}),
|
|
119
|
-
});
|
|
120
|
-
return ret;
|
|
121
|
-
};
|
|
122
|
-
ops[path][verb].response = getReturnType(op.returnType);
|
|
123
|
-
}
|
|
124
|
-
}; // end operations
|
|
125
|
-
|
|
126
|
-
n.operations.forEach(processOp);
|
|
127
|
-
n.interfaces.forEach((itf) => {
|
|
128
|
-
if (!isTemplateDeclaration(itf)) itf.operations.forEach(processOp);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// get and traverse all namespaces
|
|
132
|
-
n.namespaces.forEach((ns) => traverseNamespace(ns));
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
traverseNamespace(namespace);
|
|
136
|
-
let out = `export type types_${context.options["root-namespace"]} = {\n`;
|
|
137
|
-
out += Object.entries(ops)
|
|
138
|
-
.map((path) => {
|
|
139
|
-
let pathret = ` ['${path[0]}']: {\n`;
|
|
140
|
-
pathret += Object.entries(path[1])
|
|
141
|
-
.map((verb) => {
|
|
142
|
-
let verbret = ` ['${verb[0]}']: {\n`;
|
|
143
|
-
verbret += ` request: ${verb[1].request}\n`;
|
|
144
|
-
verbret += ` response: ${verb[1].response.map((res) => `{status: ${res.status}, body: ${res.body}}`).join(" | ")}\n`;
|
|
145
|
-
verbret += " }";
|
|
146
|
-
return verbret;
|
|
147
|
-
})
|
|
148
|
-
.join(",\n");
|
|
149
|
-
pathret += "\n }";
|
|
150
|
-
return pathret;
|
|
151
|
-
})
|
|
152
|
-
.join(",\n");
|
|
153
|
-
out += "\n};\n";
|
|
154
|
-
return out;
|
|
155
|
-
};
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrayModelType,
|
|
3
|
-
EmitContext,
|
|
4
|
-
Enum,
|
|
5
|
-
getDoc,
|
|
6
|
-
Model,
|
|
7
|
-
Namespace,
|
|
8
|
-
RecordModelType,
|
|
9
|
-
Scalar,
|
|
10
|
-
Tuple,
|
|
11
|
-
Type,
|
|
12
|
-
Union,
|
|
13
|
-
} from "@typespec/compiler";
|
|
14
|
-
import { isVisible, Visibility } from "@typespec/http";
|
|
15
|
-
|
|
16
|
-
type CommonOptions = {
|
|
17
|
-
nestlevel: number;
|
|
18
|
-
currentNamespace: Namespace;
|
|
19
|
-
context: EmitContext;
|
|
20
|
-
visibility?: Visibility;
|
|
21
|
-
resolveEvenWithName?: boolean;
|
|
22
|
-
isNamespaceRoot?: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const resolveType = (t: Type, opts: CommonOptions): string => {
|
|
26
|
-
let typeStr = "unknown";
|
|
27
|
-
switch (t.kind) {
|
|
28
|
-
case "Model":
|
|
29
|
-
if (t.name === "Array") {
|
|
30
|
-
typeStr = resolveArray(t as ArrayModelType, opts);
|
|
31
|
-
} else if (t.name === "Record") {
|
|
32
|
-
typeStr = resolveRecord(t as RecordModelType, opts);
|
|
33
|
-
} else typeStr = resolveModel(t, opts);
|
|
34
|
-
break;
|
|
35
|
-
case "Boolean":
|
|
36
|
-
typeStr = "boolean";
|
|
37
|
-
break;
|
|
38
|
-
case "Enum":
|
|
39
|
-
typeStr = resolveEnum(t, opts);
|
|
40
|
-
break;
|
|
41
|
-
case "Intrinsic":
|
|
42
|
-
typeStr = t.name;
|
|
43
|
-
break;
|
|
44
|
-
case "Number":
|
|
45
|
-
typeStr = t.valueAsString;
|
|
46
|
-
break;
|
|
47
|
-
case "Scalar":
|
|
48
|
-
typeStr = resolveScalar(
|
|
49
|
-
t,
|
|
50
|
-
!!opts.context.options["serializable-date-types"],
|
|
51
|
-
);
|
|
52
|
-
break;
|
|
53
|
-
case "String":
|
|
54
|
-
typeStr = `'${t.value}'`;
|
|
55
|
-
break;
|
|
56
|
-
case "Tuple":
|
|
57
|
-
typeStr = resolveTuple(t, opts);
|
|
58
|
-
break;
|
|
59
|
-
case "Union":
|
|
60
|
-
typeStr = resolveUnion(t, opts);
|
|
61
|
-
break;
|
|
62
|
-
case "EnumMember":
|
|
63
|
-
if (opts.resolveEvenWithName) {
|
|
64
|
-
// If we're at routed typemap we will emit enum either value or index / name (as string if configured)
|
|
65
|
-
const value = resolveEnumMemberValue(t.enum, t.name);
|
|
66
|
-
if (value) {
|
|
67
|
-
typeStr = typeof value === "string" ? `'${value}'` : value.toString();
|
|
68
|
-
} else if (opts.context.options["string-nominal-enums"]) {
|
|
69
|
-
typeStr = `'${t.name}'`;
|
|
70
|
-
} else {
|
|
71
|
-
const index = resolveEnumMemberIndex(t.enum, t.name);
|
|
72
|
-
if (index !== undefined) {
|
|
73
|
-
typeStr = index.toString();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
typeStr = `${t.enum.name}.${t.name}`;
|
|
79
|
-
break;
|
|
80
|
-
default:
|
|
81
|
-
console.warn("Could not resolve type:", t.kind);
|
|
82
|
-
}
|
|
83
|
-
return typeStr;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const resolveArray = (
|
|
87
|
-
a: ArrayModelType,
|
|
88
|
-
opts: CommonOptions,
|
|
89
|
-
): string => {
|
|
90
|
-
if (a.name !== "Array")
|
|
91
|
-
throw new Error(`Trying to parse model ${a.name} as Array`);
|
|
92
|
-
return `(${resolveType(a.indexer.value, { ...opts, isNamespaceRoot: false })})[]`;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export const resolveRecord = (
|
|
96
|
-
a: RecordModelType,
|
|
97
|
-
opts: CommonOptions,
|
|
98
|
-
): string => {
|
|
99
|
-
if (a.name !== "Record")
|
|
100
|
-
throw new Error(`Trying to parse model ${a.name} as Record`);
|
|
101
|
-
return `{[k: string]: ${resolveType(a.indexer.value, opts)}}`;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export const resolveEnum = (e: Enum, opts: CommonOptions): string => {
|
|
105
|
-
if (
|
|
106
|
-
e.name &&
|
|
107
|
-
!opts.isNamespaceRoot &&
|
|
108
|
-
opts.currentNamespace.enums.has(e.name) &&
|
|
109
|
-
!opts.resolveEvenWithName
|
|
110
|
-
) {
|
|
111
|
-
return e.name;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (opts.resolveEvenWithName) {
|
|
115
|
-
return resolveEnumAsUnion(e, opts);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let ret = "{\n";
|
|
119
|
-
let i = 1;
|
|
120
|
-
e.members.forEach((p) => {
|
|
121
|
-
const val =
|
|
122
|
-
p.value === undefined
|
|
123
|
-
? ""
|
|
124
|
-
: " = " +
|
|
125
|
-
(typeof p.value === "string" ? `'${p.value}'` : p.value.toString());
|
|
126
|
-
ret = ret.addLine(
|
|
127
|
-
`${p.name.includes("-") ? `'${p.name}'` : p.name}${val}${i < e.members.size ? "," : ""}`,
|
|
128
|
-
opts.nestlevel + 1,
|
|
129
|
-
);
|
|
130
|
-
i++;
|
|
131
|
-
});
|
|
132
|
-
ret = ret.addLine("}", opts.nestlevel, true);
|
|
133
|
-
return ret;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
export const resolveTuple = (t: Tuple, opts: CommonOptions): string => {
|
|
137
|
-
return `[${t.values.map((v) => resolveType(v, opts)).join(", ")}]`;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
export const resolveUnion = (u: Union, opts: CommonOptions): string => {
|
|
141
|
-
if (
|
|
142
|
-
u.name &&
|
|
143
|
-
!opts.isNamespaceRoot &&
|
|
144
|
-
u.namespace?.unions.has(u.name) &&
|
|
145
|
-
!opts.resolveEvenWithName
|
|
146
|
-
)
|
|
147
|
-
return u.name;
|
|
148
|
-
return Array.from(u.variants)
|
|
149
|
-
.map((v) => {
|
|
150
|
-
const variantType = v[1].type;
|
|
151
|
-
// If variant is a named model in the current namespace
|
|
152
|
-
// and it's not in routed typemap, reference it by name:
|
|
153
|
-
if (
|
|
154
|
-
!opts.resolveEvenWithName &&
|
|
155
|
-
variantType.kind === "Model" &&
|
|
156
|
-
variantType.name &&
|
|
157
|
-
opts.currentNamespace.models.has(variantType.name)
|
|
158
|
-
) {
|
|
159
|
-
return variantType.name;
|
|
160
|
-
}
|
|
161
|
-
// Otherwise resolve type inline
|
|
162
|
-
return resolveType(variantType, { ...opts, isNamespaceRoot: false });
|
|
163
|
-
})
|
|
164
|
-
.join(" | ");
|
|
165
|
-
};
|
|
166
|
-
export const resolveScalar = (
|
|
167
|
-
s: Scalar,
|
|
168
|
-
serializableDates: boolean,
|
|
169
|
-
): string => {
|
|
170
|
-
let ret = "unknown";
|
|
171
|
-
switch (s.name) {
|
|
172
|
-
case "boolean":
|
|
173
|
-
ret = "boolean";
|
|
174
|
-
break;
|
|
175
|
-
case "bytes":
|
|
176
|
-
ret = "Uint8Array";
|
|
177
|
-
break;
|
|
178
|
-
case "duration":
|
|
179
|
-
case "numeric":
|
|
180
|
-
ret = "number";
|
|
181
|
-
break;
|
|
182
|
-
case "plainTime":
|
|
183
|
-
case "string":
|
|
184
|
-
case "url":
|
|
185
|
-
ret = "string";
|
|
186
|
-
break;
|
|
187
|
-
case "offsetDateTime":
|
|
188
|
-
case "plainDate":
|
|
189
|
-
case "utcDateTime":
|
|
190
|
-
ret = serializableDates ? "string" : "Date";
|
|
191
|
-
break;
|
|
192
|
-
case "unixTimestamp32":
|
|
193
|
-
ret = serializableDates ? "number" : "Date";
|
|
194
|
-
break;
|
|
195
|
-
default:
|
|
196
|
-
console.warn("Could not resolve scalar:", s.name);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (serializableDates && s.name === "unixTimestamp32") {
|
|
200
|
-
// If we want to use serializable date types baseScalar should be skipped
|
|
201
|
-
// for unixTimestamp32 (since it is utcDateTime and would emit a string)
|
|
202
|
-
return ret;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return s.baseScalar ? resolveScalar(s.baseScalar, serializableDates) : ret;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
export const resolveModel = (m: Model, opts: CommonOptions): string => {
|
|
209
|
-
if (
|
|
210
|
-
m.name &&
|
|
211
|
-
!opts.isNamespaceRoot &&
|
|
212
|
-
opts.currentNamespace.models.has(m.name) &&
|
|
213
|
-
!opts.resolveEvenWithName
|
|
214
|
-
)
|
|
215
|
-
return m.name;
|
|
216
|
-
let ret = "{\n";
|
|
217
|
-
let i = 1;
|
|
218
|
-
m.properties.forEach((p) => {
|
|
219
|
-
if (
|
|
220
|
-
opts.visibility === undefined ||
|
|
221
|
-
isVisible(opts.context.program, p, opts.visibility)
|
|
222
|
-
) {
|
|
223
|
-
if (opts.context) {
|
|
224
|
-
const doc = getDoc(opts.context.program, p);
|
|
225
|
-
if (doc) ret = ret.addLine(`/** ${doc} */`, opts.nestlevel! + 1);
|
|
226
|
-
}
|
|
227
|
-
const typeStr = resolveType(p.type, {
|
|
228
|
-
...opts,
|
|
229
|
-
nestlevel: opts.nestlevel + 1,
|
|
230
|
-
isNamespaceRoot: false,
|
|
231
|
-
});
|
|
232
|
-
if (typeStr.includes("unknown"))
|
|
233
|
-
console.warn(`Could not resolve property ${p.name} on ${m.name}`);
|
|
234
|
-
ret = ret.addLine(
|
|
235
|
-
`${p.name}${p.optional ? "?" : ""}: ${typeStr}${i < m.properties.size ? "," : ""}`,
|
|
236
|
-
opts.nestlevel + 1,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
i++;
|
|
240
|
-
});
|
|
241
|
-
ret = ret.addLine("}", opts.nestlevel, true);
|
|
242
|
-
return ret;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
export const resolveEnumMemberValue = (
|
|
246
|
-
e: Enum,
|
|
247
|
-
name: string,
|
|
248
|
-
): string | number | undefined => {
|
|
249
|
-
const member = e.members.get(name);
|
|
250
|
-
if (!member) {
|
|
251
|
-
console.warn("Missing enum member under name:", name);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
return member?.value;
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
export const resolveEnumAsUnion = (e: Enum, opts: CommonOptions): string => {
|
|
258
|
-
return Array.from(e.members.values())
|
|
259
|
-
.map((member, index) => {
|
|
260
|
-
const fallback = opts.context.options["string-nominal-enums"]
|
|
261
|
-
? member.name
|
|
262
|
-
: index;
|
|
263
|
-
const value = resolveEnumMemberValue(e, member.name) ?? fallback;
|
|
264
|
-
return typeof value === "string" ? `'${value}'` : value.toString();
|
|
265
|
-
})
|
|
266
|
-
.join(" | ");
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
export const resolveEnumMemberIndex = (
|
|
270
|
-
e: Enum,
|
|
271
|
-
name: string,
|
|
272
|
-
): number | undefined => {
|
|
273
|
-
const index = Array.from(e.members.values()).findIndex((member) => {
|
|
274
|
-
return member.name === name;
|
|
275
|
-
});
|
|
276
|
-
if (index === -1) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
return index;
|
|
280
|
-
};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { Model, Namespace, Type } from "@typespec/compiler";
|
|
2
|
-
import { resolveScalar } from "./emit_types_resolve.js";
|
|
3
|
-
|
|
4
|
-
export const getTypeguardModel = (
|
|
5
|
-
m: Model,
|
|
6
|
-
accessor: string,
|
|
7
|
-
nestingLevel = 1,
|
|
8
|
-
currentNamespace: Namespace,
|
|
9
|
-
serializableDates: boolean,
|
|
10
|
-
knownGuards?: Array<{ filename: string; name: string }>,
|
|
11
|
-
): [string, string[]] => {
|
|
12
|
-
const imports: string[] = [];
|
|
13
|
-
return [
|
|
14
|
-
Array.from(m.properties)
|
|
15
|
-
.map((property) => {
|
|
16
|
-
const guard = getTypeguard(
|
|
17
|
-
property[1].type,
|
|
18
|
-
`${accessor}['${property[1].name}']`,
|
|
19
|
-
nestingLevel + 1,
|
|
20
|
-
currentNamespace,
|
|
21
|
-
serializableDates,
|
|
22
|
-
knownGuards,
|
|
23
|
-
);
|
|
24
|
-
imports.push(...guard[1]);
|
|
25
|
-
let ret = " ".repeat(nestingLevel);
|
|
26
|
-
ret += property[1].optional
|
|
27
|
-
? `${accessor}['${property[1].name}'] === undefined || `
|
|
28
|
-
: `${accessor}['${property[1].name}'] !== undefined && `;
|
|
29
|
-
ret += `(${guard[0]})`;
|
|
30
|
-
return ret;
|
|
31
|
-
})
|
|
32
|
-
.filter((x) => !!x)
|
|
33
|
-
.join(" &&\n"),
|
|
34
|
-
imports,
|
|
35
|
-
];
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates the function body for a typeguard
|
|
40
|
-
* @param t Type to create guards for
|
|
41
|
-
* @param accessor String by which the type-to-test can be accessed by the code
|
|
42
|
-
* @param nestingLevel
|
|
43
|
-
* @param knownGuards Array of names of known typeguards; if type is found in those, no new typeguard will be created and instead a reference to the existing one is produced
|
|
44
|
-
* @returns Tuple: [function body of the typeguard, array of import filenames (not unique!)]
|
|
45
|
-
*/
|
|
46
|
-
export const getTypeguard = (
|
|
47
|
-
t: Type,
|
|
48
|
-
accessor: string,
|
|
49
|
-
nestingLevel = 1,
|
|
50
|
-
currentNamespace: Namespace,
|
|
51
|
-
serializableDates: boolean,
|
|
52
|
-
knownGuards?: Array<{ filename: string; name: string }>,
|
|
53
|
-
): [string, string[]] => {
|
|
54
|
-
switch (t.kind) {
|
|
55
|
-
case "Model":
|
|
56
|
-
if (t.name === "Array") {
|
|
57
|
-
const guard = getTypeguard(
|
|
58
|
-
t.indexer!.value,
|
|
59
|
-
"v",
|
|
60
|
-
nestingLevel,
|
|
61
|
-
currentNamespace,
|
|
62
|
-
serializableDates,
|
|
63
|
-
knownGuards,
|
|
64
|
-
);
|
|
65
|
-
if (guard[0].endsWith("\n"))
|
|
66
|
-
guard[0] = guard[0].substring(0, guard[0].length - 1);
|
|
67
|
-
return [
|
|
68
|
-
`Array.isArray(${accessor}) && ${accessor}.every((v) => ${guard[0]})`,
|
|
69
|
-
guard[1],
|
|
70
|
-
];
|
|
71
|
-
} else if (t.name === "Record") {
|
|
72
|
-
const guard = getTypeguard(
|
|
73
|
-
t.indexer!.value,
|
|
74
|
-
"v",
|
|
75
|
-
nestingLevel,
|
|
76
|
-
currentNamespace,
|
|
77
|
-
serializableDates,
|
|
78
|
-
knownGuards,
|
|
79
|
-
);
|
|
80
|
-
if (guard[0].endsWith("\n"))
|
|
81
|
-
guard[0] = guard[0].substring(0, guard[0].length - 1);
|
|
82
|
-
return [
|
|
83
|
-
`typeof ${accessor} === 'object' && (Object.entries(${accessor}).length > 0 ? Object.values(${accessor} as Record<string, any>).every((v) => ${guard[0]}) : true)`,
|
|
84
|
-
guard[1],
|
|
85
|
-
];
|
|
86
|
-
} else if (knownGuards && knownGuards.some((x) => x.name === t.name)) {
|
|
87
|
-
return [
|
|
88
|
-
`is${t.name}(${accessor})`,
|
|
89
|
-
[
|
|
90
|
-
`import {is${t.name}} from './${knownGuards.find((x) => x.name === t.name)!.filename}';`,
|
|
91
|
-
],
|
|
92
|
-
];
|
|
93
|
-
} else if (
|
|
94
|
-
t.name &&
|
|
95
|
-
!knownGuards &&
|
|
96
|
-
currentNamespace.name === t.namespace!.name
|
|
97
|
-
) {
|
|
98
|
-
return [`is${t.name}(${accessor})`, []];
|
|
99
|
-
} else {
|
|
100
|
-
const guard = getTypeguardModel(
|
|
101
|
-
t,
|
|
102
|
-
accessor,
|
|
103
|
-
nestingLevel,
|
|
104
|
-
currentNamespace,
|
|
105
|
-
serializableDates,
|
|
106
|
-
knownGuards,
|
|
107
|
-
);
|
|
108
|
-
return [
|
|
109
|
-
`(\n${guard[0]}\n${" ".repeat(Math.max(nestingLevel - 1, 0))})`,
|
|
110
|
-
guard[1],
|
|
111
|
-
];
|
|
112
|
-
}
|
|
113
|
-
case "Boolean":
|
|
114
|
-
return [`typeof ${accessor} === 'boolean'`, []];
|
|
115
|
-
case "Intrinsic":
|
|
116
|
-
return [`${accessor} === ${t.name}`, []];
|
|
117
|
-
case "Number":
|
|
118
|
-
return [`typeof ${accessor} === 'number'`, []];
|
|
119
|
-
case "Scalar":
|
|
120
|
-
if (
|
|
121
|
-
// TODO: figure out how to support all the varieties of dates
|
|
122
|
-
// TODO: support byte arrays
|
|
123
|
-
resolveScalar(t, serializableDates) !== "Date" &&
|
|
124
|
-
resolveScalar(t, serializableDates) !== "Uint8Array"
|
|
125
|
-
)
|
|
126
|
-
return [
|
|
127
|
-
`typeof ${accessor} === '${resolveScalar(t, serializableDates)}'`,
|
|
128
|
-
[],
|
|
129
|
-
];
|
|
130
|
-
break;
|
|
131
|
-
case "String":
|
|
132
|
-
return [`typeof ${accessor} === 'string'`, []];
|
|
133
|
-
case "Tuple": {
|
|
134
|
-
// TODO: ['string1', 'string2'] gets resolved as [string, string] instead of literals. Why?
|
|
135
|
-
const imports: string[] = [];
|
|
136
|
-
return [
|
|
137
|
-
t.values
|
|
138
|
-
.map((v, i) => {
|
|
139
|
-
const guard = getTypeguard(
|
|
140
|
-
v,
|
|
141
|
-
`${accessor}[${i}]`,
|
|
142
|
-
nestingLevel,
|
|
143
|
-
currentNamespace,
|
|
144
|
-
serializableDates,
|
|
145
|
-
knownGuards,
|
|
146
|
-
);
|
|
147
|
-
imports.push(...guard[1]);
|
|
148
|
-
return `(${guard[0]})`;
|
|
149
|
-
})
|
|
150
|
-
.join(" && "),
|
|
151
|
-
imports,
|
|
152
|
-
];
|
|
153
|
-
}
|
|
154
|
-
case "Union": {
|
|
155
|
-
const imports: string[] = [];
|
|
156
|
-
return [
|
|
157
|
-
Array.from(t.variants)
|
|
158
|
-
.map((v) => {
|
|
159
|
-
const guard = getTypeguard(
|
|
160
|
-
v[1].type,
|
|
161
|
-
`${accessor}`,
|
|
162
|
-
nestingLevel,
|
|
163
|
-
currentNamespace,
|
|
164
|
-
serializableDates,
|
|
165
|
-
knownGuards,
|
|
166
|
-
);
|
|
167
|
-
imports.push(...guard[1]);
|
|
168
|
-
return `(${guard[0]})`;
|
|
169
|
-
})
|
|
170
|
-
.join(" || "),
|
|
171
|
-
imports,
|
|
172
|
-
];
|
|
173
|
-
}
|
|
174
|
-
default:
|
|
175
|
-
console.warn("Could not resolve type:", t.kind);
|
|
176
|
-
}
|
|
177
|
-
return ["true", []]; // fallback to not break everything in case of errors
|
|
178
|
-
};
|
package/test/main.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { expect, it } from "vitest";
|
|
4
|
-
import { runner } from "./runner.js";
|
|
5
|
-
|
|
6
|
-
const tests = ["simple-routes", "enum", "pr8", "union", "visibility"];
|
|
7
|
-
|
|
8
|
-
const getTestData = (
|
|
9
|
-
testName: (typeof tests)[number],
|
|
10
|
-
): {
|
|
11
|
-
input: string;
|
|
12
|
-
output: {
|
|
13
|
-
ts: string;
|
|
14
|
-
routes: string;
|
|
15
|
-
routedTypes: string;
|
|
16
|
-
};
|
|
17
|
-
} => {
|
|
18
|
-
const targetsFolder = "targets";
|
|
19
|
-
return {
|
|
20
|
-
input: readFileSync(join(__dirname, `${targetsFolder}/${testName}.tsp`), {
|
|
21
|
-
encoding: "utf8",
|
|
22
|
-
}),
|
|
23
|
-
output: {
|
|
24
|
-
ts: readFileSync(
|
|
25
|
-
join(__dirname, `${targetsFolder}/${testName}.target.ts`),
|
|
26
|
-
{ encoding: "utf8" },
|
|
27
|
-
)
|
|
28
|
-
.trim()
|
|
29
|
-
.replaceAll("\r\n", "\n"),
|
|
30
|
-
routes: readFileSync(
|
|
31
|
-
join(__dirname, `${targetsFolder}/${testName}.routes.ts`),
|
|
32
|
-
{ encoding: "utf8" },
|
|
33
|
-
)
|
|
34
|
-
.trim()
|
|
35
|
-
.replaceAll("\r\n", "\n"),
|
|
36
|
-
routedTypes: readFileSync(
|
|
37
|
-
join(__dirname, `${targetsFolder}/${testName}.routed-types.ts`),
|
|
38
|
-
{ encoding: "utf8" },
|
|
39
|
-
)
|
|
40
|
-
.trim()
|
|
41
|
-
.replaceAll("\r\n", "\n"),
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const emitterOptions = {
|
|
47
|
-
"root-namespace": "test",
|
|
48
|
-
"enable-routes": true,
|
|
49
|
-
"enable-types": true,
|
|
50
|
-
"enable-typeguards": true,
|
|
51
|
-
"enable-routed-typemap": true,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
tests.forEach((test) => {
|
|
55
|
-
it(`works for ${test}`, async () => {
|
|
56
|
-
const data = getTestData(test);
|
|
57
|
-
const emitter = await runner.emit(
|
|
58
|
-
"typespec-typescript-emitter",
|
|
59
|
-
emitterOptions,
|
|
60
|
-
);
|
|
61
|
-
const result = await emitter.compileAndDiagnose(data.input);
|
|
62
|
-
|
|
63
|
-
writeFileSync(
|
|
64
|
-
join(__dirname, `out/${test}.target.ts`),
|
|
65
|
-
result[0].outputs["Test.ts"],
|
|
66
|
-
);
|
|
67
|
-
writeFileSync(
|
|
68
|
-
join(__dirname, `out/${test}.routes.ts`),
|
|
69
|
-
result[0].outputs["routes_test.ts"],
|
|
70
|
-
);
|
|
71
|
-
writeFileSync(
|
|
72
|
-
join(__dirname, `out/${test}.routed-types.ts`),
|
|
73
|
-
result[0].outputs["routedTypemap_test.ts"],
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
expect(result[1].length).toBe(0);
|
|
77
|
-
expect(result[0].outputs["Test.ts"].trim()).toBe(data.output.ts);
|
|
78
|
-
expect(result[0].outputs["routes_test.ts"].trim()).toBe(data.output.routes);
|
|
79
|
-
expect(result[0].outputs["routedTypemap_test.ts"].trim()).toBe(
|
|
80
|
-
data.output.routedTypes,
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
});
|
package/test/out/.gitkeep
DELETED
|
File without changes
|