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,113 @@
|
|
|
1
|
+
import { Model } from "@typespec/compiler";
|
|
2
|
+
import { Resolvable } from "../Resolvable.js";
|
|
3
|
+
import {
|
|
4
|
+
Resolver,
|
|
5
|
+
ResolverOptions,
|
|
6
|
+
ResolverResult,
|
|
7
|
+
} from "../Resolvable_helpers.js";
|
|
8
|
+
|
|
9
|
+
enum Type {
|
|
10
|
+
Array,
|
|
11
|
+
Record,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class IndexedModel extends Resolvable<Model> {
|
|
15
|
+
constructor(t: Model, r: Resolver) {
|
|
16
|
+
super(t, r);
|
|
17
|
+
if (t.name === "Array") {
|
|
18
|
+
this._indexedModelKind = Type.Array;
|
|
19
|
+
} else if (t.name === "Record") {
|
|
20
|
+
this._indexedModelKind = Type.Record;
|
|
21
|
+
} else
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
`Cannot create IndexedModel on type of name ${t.name}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private _indexedModelKind: Type;
|
|
28
|
+
protected expectedTypeKind: string = "Model";
|
|
29
|
+
|
|
30
|
+
public override async hasVisibility(
|
|
31
|
+
opts: ResolverOptions<Resolver>,
|
|
32
|
+
out: ResolverResult<Resolver>,
|
|
33
|
+
): Promise<boolean> {
|
|
34
|
+
if (await super.hasVisibility(opts, out)) return true;
|
|
35
|
+
if (
|
|
36
|
+
await Resolvable.hasVisibility(this._t.indexer!.value, opts, out, this._t)
|
|
37
|
+
)
|
|
38
|
+
return true;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected async type(
|
|
43
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
44
|
+
out: ResolverResult<Resolver.Type>,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const resolved = await this.resolveNested(
|
|
47
|
+
this._t.indexer!.value,
|
|
48
|
+
opts,
|
|
49
|
+
out,
|
|
50
|
+
false,
|
|
51
|
+
);
|
|
52
|
+
switch (this._indexedModelKind) {
|
|
53
|
+
case Type.Array:
|
|
54
|
+
out.resolved.append(`(${resolved.resolved})[]`);
|
|
55
|
+
break;
|
|
56
|
+
case Type.Record:
|
|
57
|
+
out.resolved.append(`{[k: string]: ${resolved.resolved}}`);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
out.imports.push(...resolved.imports);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected async typeguard(
|
|
64
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
65
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
const oldAccessor = opts.accessor;
|
|
68
|
+
opts.accessor = this._indexedModelKind === Type.Array ? "v" : "v[1]";
|
|
69
|
+
const resolved = await this.resolveNested(
|
|
70
|
+
this._t.indexer!.value,
|
|
71
|
+
opts,
|
|
72
|
+
out,
|
|
73
|
+
);
|
|
74
|
+
opts.accessor = oldAccessor;
|
|
75
|
+
if (resolved.resolved.value.endsWith("\n")) resolved.resolved.dropLast();
|
|
76
|
+
|
|
77
|
+
switch (this._indexedModelKind) {
|
|
78
|
+
case Type.Array:
|
|
79
|
+
out.resolved.append(
|
|
80
|
+
`Array.isArray(${opts.accessor}) && ${opts.accessor}.every((v) => ${resolved.resolved})`,
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
case Type.Record:
|
|
84
|
+
out.resolved.append(
|
|
85
|
+
`typeof ${opts.accessor} === 'object' && Object.entries(${opts.accessor} as Record<string, any>).every((v) => ${resolved.resolved})`,
|
|
86
|
+
);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
out.imports.push(...resolved.imports);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected validate(): void {
|
|
93
|
+
if (this._t.name !== "Array" && this._t.name !== "Record") {
|
|
94
|
+
this.diagnostic(
|
|
95
|
+
"typeclass-modeltype-mismatch",
|
|
96
|
+
"Tried creating IndexedModel for non-array and non-record model type",
|
|
97
|
+
"error",
|
|
98
|
+
);
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Tried creating IndexedModel for non-array and non-record model type",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (!this._t.indexer) {
|
|
104
|
+
this.diagnostic(
|
|
105
|
+
"no-indexer",
|
|
106
|
+
"Tried creating IndexedModel for type without indexer",
|
|
107
|
+
"error",
|
|
108
|
+
);
|
|
109
|
+
throw new Error("Tried creating IndexedModel for type without indexer");
|
|
110
|
+
}
|
|
111
|
+
super.validate();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { getDoc, Model, ModelProperty, Program } from "@typespec/compiler";
|
|
2
|
+
import { isVisible, Visibility } from "@typespec/http";
|
|
3
|
+
import { AppendableString } from "../../helpers/appendableString.js";
|
|
4
|
+
import { compareArrays } from "../../helpers/arrays.js";
|
|
5
|
+
import { namespaceListFromNamespace } from "../../helpers/namespaces.js";
|
|
6
|
+
import { Resolvable } from "../Resolvable.js";
|
|
7
|
+
import {
|
|
8
|
+
Resolver,
|
|
9
|
+
ResolverOptions,
|
|
10
|
+
ResolverResult,
|
|
11
|
+
} from "../Resolvable_helpers.js";
|
|
12
|
+
|
|
13
|
+
type VisibilityType = "Read" | "Create" | "Update" | "Delete" | "Query";
|
|
14
|
+
|
|
15
|
+
export class ShapedModel extends Resolvable<Model> {
|
|
16
|
+
protected expectedTypeKind: string = "Model";
|
|
17
|
+
|
|
18
|
+
private getProps(): ModelProperty[] {
|
|
19
|
+
return Array.from(this._t.properties).map((p) => p[1]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private getPropVisibilityType(
|
|
23
|
+
program: Program,
|
|
24
|
+
prop: ModelProperty,
|
|
25
|
+
): VisibilityType[] | null {
|
|
26
|
+
if (
|
|
27
|
+
!prop.decorators.some(
|
|
28
|
+
(decorator) => decorator.definition?.name === "@visibility",
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
return null;
|
|
32
|
+
const ret: VisibilityType[] = [];
|
|
33
|
+
if (isVisible(program, prop, Visibility.Read)) ret.push("Read");
|
|
34
|
+
if (isVisible(program, prop, Visibility.Create)) ret.push("Create");
|
|
35
|
+
if (isVisible(program, prop, Visibility.Update)) ret.push("Update");
|
|
36
|
+
if (isVisible(program, prop, Visibility.Delete)) ret.push("Delete");
|
|
37
|
+
if (isVisible(program, prop, Visibility.Query)) ret.push("Query");
|
|
38
|
+
return ret;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether or not any of the model's properties has @visibility modifier.
|
|
43
|
+
* Also returns true if any base model (in the entire chain) has a visibility OR if
|
|
44
|
+
* any nested type (in the model's properties) has a visibility.
|
|
45
|
+
*/
|
|
46
|
+
public override async hasVisibility(
|
|
47
|
+
opts: ResolverOptions<Resolver>,
|
|
48
|
+
out: ResolverResult<Resolver>,
|
|
49
|
+
): Promise<boolean> {
|
|
50
|
+
if (await super.hasVisibility(opts, out)) return true;
|
|
51
|
+
// resolve base
|
|
52
|
+
if (
|
|
53
|
+
this._t.baseModel &&
|
|
54
|
+
(await Resolvable.hasVisibility(this._t.baseModel, opts, out, this._t))
|
|
55
|
+
)
|
|
56
|
+
return true;
|
|
57
|
+
// resolve props: check for decorators
|
|
58
|
+
const props = this.getProps();
|
|
59
|
+
if (
|
|
60
|
+
props.some((prop) =>
|
|
61
|
+
prop.decorators.some(
|
|
62
|
+
(decorator) => decorator.definition?.name === "@visibility",
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
return true;
|
|
67
|
+
// resolve props: check if any resolves to visibility
|
|
68
|
+
for (const prop of props) {
|
|
69
|
+
// check if this prop is somewhere up the resolution chain -> recursion -> skip (defaults to false)
|
|
70
|
+
const propNamespaces = namespaceListFromNamespace(
|
|
71
|
+
(prop.type as any).namespace,
|
|
72
|
+
);
|
|
73
|
+
if (
|
|
74
|
+
opts.parents &&
|
|
75
|
+
opts.parents.some((parent) => {
|
|
76
|
+
const parentNamespaces = namespaceListFromNamespace(
|
|
77
|
+
(parent as any).namespace,
|
|
78
|
+
);
|
|
79
|
+
return (
|
|
80
|
+
parent.kind === prop.type.kind &&
|
|
81
|
+
(prop.type as any).name &&
|
|
82
|
+
(parent as any).name === (prop.type as any).name &&
|
|
83
|
+
propNamespaces &&
|
|
84
|
+
parentNamespaces &&
|
|
85
|
+
compareArrays(propNamespaces, parentNamespaces)
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
continue;
|
|
90
|
+
// not recursion, resolve
|
|
91
|
+
if (await Resolvable.hasVisibility(prop.type, opts, out, this._t))
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
// nothing true
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected async type(
|
|
99
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
100
|
+
out: ResolverResult<Resolver.Type>,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
if (opts.emitDocs) {
|
|
103
|
+
out.doc = getDoc(opts.program, this._t);
|
|
104
|
+
}
|
|
105
|
+
const resolution = new AppendableString();
|
|
106
|
+
// check for base model (extended)
|
|
107
|
+
if (this._t.baseModel) {
|
|
108
|
+
const resolvedBase = await this.resolveNested(
|
|
109
|
+
this._t.baseModel,
|
|
110
|
+
opts,
|
|
111
|
+
out,
|
|
112
|
+
);
|
|
113
|
+
out.imports.push(...resolvedBase.imports);
|
|
114
|
+
resolution.append(`${resolvedBase.resolved} & `);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const props = this.getProps();
|
|
118
|
+
|
|
119
|
+
const propVisibilityTypes: Record<
|
|
120
|
+
string,
|
|
121
|
+
{ type: VisibilityType[] | null; nested: string }
|
|
122
|
+
> = {};
|
|
123
|
+
|
|
124
|
+
if (props.length === 0) {
|
|
125
|
+
out.resolved.append("{}");
|
|
126
|
+
} else {
|
|
127
|
+
resolution.append("{\n");
|
|
128
|
+
for (let i = 0; i < props.length; i++) {
|
|
129
|
+
const prop = props[i];
|
|
130
|
+
// add doc strings
|
|
131
|
+
if (opts.emitDocs) {
|
|
132
|
+
const doc = getDoc(opts.program, prop);
|
|
133
|
+
if (doc) resolution.addLine(`/** ${doc} */`, opts.nestlevel + 1);
|
|
134
|
+
}
|
|
135
|
+
// resolve prop
|
|
136
|
+
const resolved = await this.resolveNested(prop.type, opts, out);
|
|
137
|
+
resolution.addLine(
|
|
138
|
+
`${prop.name}${prop.optional ? "?" : ""}: ${resolved.resolved}${i + 1 < this._t.properties.size ? "," : ""}`,
|
|
139
|
+
opts.nestlevel + 1,
|
|
140
|
+
);
|
|
141
|
+
out.imports.push(...resolved.imports);
|
|
142
|
+
// if prop has visibility, save to vismap
|
|
143
|
+
const propVis = this.getPropVisibilityType(opts.program, prop);
|
|
144
|
+
if (propVis !== null || resolved.hasVisibility) {
|
|
145
|
+
propVisibilityTypes[prop.name] = { type: null, nested: "" };
|
|
146
|
+
propVisibilityTypes[prop.name].type = propVis;
|
|
147
|
+
propVisibilityTypes[prop.name].nested = resolved.visibilityMap;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// build prop visibility map
|
|
152
|
+
const visMap = new AppendableString();
|
|
153
|
+
const propVisibilityTypesArr = Object.entries(propVisibilityTypes).filter(
|
|
154
|
+
(p) => p[1].type !== null || p[1].nested,
|
|
155
|
+
);
|
|
156
|
+
if (propVisibilityTypesArr.length > 0) {
|
|
157
|
+
visMap.addLine("{");
|
|
158
|
+
let i = 1;
|
|
159
|
+
for (const [prop, propvis] of propVisibilityTypesArr) {
|
|
160
|
+
const visLine = `vis: [${(propvis.type ?? []).map((t) => `Lifecycle.${t}`).join(", ")}]`;
|
|
161
|
+
if (propvis.type !== null && !propvis.nested) {
|
|
162
|
+
visMap.addLine(
|
|
163
|
+
`'${prop}': {${visLine}}`,
|
|
164
|
+
opts.nestlevel + 1,
|
|
165
|
+
"continued",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (propvis.nested) {
|
|
169
|
+
visMap.addLine(`'${prop}': {`, opts.nestlevel + 1);
|
|
170
|
+
if (propvis.type) visMap.addLine(`${visLine},`, opts.nestlevel + 2);
|
|
171
|
+
visMap.addLine(
|
|
172
|
+
`nested: ${propvis.nested
|
|
173
|
+
.split("\n")
|
|
174
|
+
.map((l) => ` ${l}`)
|
|
175
|
+
.join("\n")
|
|
176
|
+
.trim()}`,
|
|
177
|
+
opts.nestlevel + 2,
|
|
178
|
+
);
|
|
179
|
+
visMap.addLine("}", opts.nestlevel + 1, "continued");
|
|
180
|
+
}
|
|
181
|
+
visMap.addLine(i < propVisibilityTypesArr.length ? "," : "");
|
|
182
|
+
i++;
|
|
183
|
+
}
|
|
184
|
+
visMap.addLine("}", opts.nestlevel, "continued");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
resolution.addLine("}", opts.nestlevel, "continued");
|
|
188
|
+
|
|
189
|
+
out.visibilityMap = visMap.value;
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
opts.parents &&
|
|
193
|
+
opts.parents.every(
|
|
194
|
+
(p) =>
|
|
195
|
+
p.kind !== "Model" || p.name === "Array" || p.name === "Record",
|
|
196
|
+
) &&
|
|
197
|
+
visMap.value
|
|
198
|
+
) {
|
|
199
|
+
// this model is nested in something that's not a model and has visibility; inline filters
|
|
200
|
+
out.resolved.append(`FilterLifecycle<${resolution}, ${visMap}, V>`);
|
|
201
|
+
// this model is the root type or nested in a model; do not inline visibility filter
|
|
202
|
+
} else {
|
|
203
|
+
out.resolved.append(resolution);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected async typeguard(
|
|
209
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
210
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
211
|
+
): Promise<void> {
|
|
212
|
+
out.resolved.append("\n");
|
|
213
|
+
|
|
214
|
+
if (this._t.baseModel) {
|
|
215
|
+
const resolvedBase = await this.resolveNested(
|
|
216
|
+
this._t.baseModel,
|
|
217
|
+
opts,
|
|
218
|
+
out,
|
|
219
|
+
);
|
|
220
|
+
out.resolved.append(
|
|
221
|
+
`${" ".repeat(opts.nestlevel)}${resolvedBase.resolved} &&`,
|
|
222
|
+
);
|
|
223
|
+
out.imports.push(...resolvedBase.imports);
|
|
224
|
+
// derived type does not have extended properties; fix by casting
|
|
225
|
+
opts.accessor = `(${opts.accessor} as any)`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const props = this.getProps();
|
|
229
|
+
const propGuards: string[] = [];
|
|
230
|
+
for (const prop of props) {
|
|
231
|
+
const oldAccessor = opts.accessor;
|
|
232
|
+
opts.accessor = `${opts.accessor}['${prop.name}']`;
|
|
233
|
+
const resolved = await this.resolveNested(prop.type, opts, out);
|
|
234
|
+
opts.accessor = oldAccessor;
|
|
235
|
+
out.imports.push(...resolved.imports);
|
|
236
|
+
|
|
237
|
+
const visibility = this.getPropVisibilityType(opts.program, prop);
|
|
238
|
+
|
|
239
|
+
let guard = " ".repeat(
|
|
240
|
+
out.resolved.value.endsWith("&") ? 1 : opts.nestlevel * 2,
|
|
241
|
+
);
|
|
242
|
+
if (visibility !== null) {
|
|
243
|
+
guard += `((vis as any) !== Lifecycle.All && ![${(visibility ?? []).map((t) => `Lifecycle.${t}`).join(", ")}].includes(vis) ? !('${prop.name}' in ${opts.accessor}) : (`;
|
|
244
|
+
}
|
|
245
|
+
if (
|
|
246
|
+
prop.type.kind === "Intrinsic" &&
|
|
247
|
+
["never", "void"].includes(prop.type.name)
|
|
248
|
+
) {
|
|
249
|
+
// some special intrinsic types need special treatment
|
|
250
|
+
switch (prop.type.name) {
|
|
251
|
+
case "never":
|
|
252
|
+
guard += `!('${prop.name}' in ${opts.accessor})`;
|
|
253
|
+
break;
|
|
254
|
+
case "void":
|
|
255
|
+
guard += `${opts.accessor}['${prop.name}'] === undefined`;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
guard += prop.optional
|
|
260
|
+
? `${opts.accessor}['${prop.name}'] === undefined || `
|
|
261
|
+
: `${opts.accessor}['${prop.name}'] !== undefined && `;
|
|
262
|
+
guard += `(${resolved.resolved}`;
|
|
263
|
+
guard += resolved.resolved.value.endsWith("\n")
|
|
264
|
+
? " ".repeat(opts.nestlevel)
|
|
265
|
+
: "";
|
|
266
|
+
guard += ")";
|
|
267
|
+
}
|
|
268
|
+
if (visibility !== null) guard += "))";
|
|
269
|
+
propGuards.push(guard);
|
|
270
|
+
}
|
|
271
|
+
out.resolved.append(propGuards.join(" &&\n"));
|
|
272
|
+
out.resolved.addLine("", opts.nestlevel);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
protected transformKnownType(
|
|
276
|
+
opts: ResolverOptions<Resolver>,
|
|
277
|
+
out: ResolverResult<Resolver>,
|
|
278
|
+
): void {
|
|
279
|
+
// If this is a known type, it can't be the root of this resolution tree,
|
|
280
|
+
// because of the "originalTypeReady" flag.
|
|
281
|
+
// If it also has visibility, it must have its own visibility map,
|
|
282
|
+
// which is baked into the type definition, so it shouldn't be returned now.
|
|
283
|
+
|
|
284
|
+
if (this._r === Resolver.Type && out.hasVisibility) {
|
|
285
|
+
out.visibilityMap = "";
|
|
286
|
+
const newResolved = `${out.resolved}<V>`;
|
|
287
|
+
out.resolved.clear().append(newResolved);
|
|
288
|
+
// ret.visibilityMap = `${(t as any).name}_VisMap`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Scalar } 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 ResolvableScalar extends Resolvable<Scalar> {
|
|
10
|
+
protected expectedTypeKind = "Scalar";
|
|
11
|
+
private dateTypes = [
|
|
12
|
+
"offsetDateTime",
|
|
13
|
+
"plainDate",
|
|
14
|
+
"utcDateTime",
|
|
15
|
+
"unixTimestamp32",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
protected async type(
|
|
19
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
20
|
+
out: ResolverResult<Resolver.Type>,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
if (this.dateTypes.includes(this._t.name)) {
|
|
23
|
+
switch (this._t.name) {
|
|
24
|
+
case "offsetDateTime":
|
|
25
|
+
case "plainDate":
|
|
26
|
+
case "utcDateTime":
|
|
27
|
+
out.resolved.append(
|
|
28
|
+
opts.options["serializable-date-types"] ? "string" : "Date",
|
|
29
|
+
);
|
|
30
|
+
break;
|
|
31
|
+
case "unixTimestamp32":
|
|
32
|
+
out.resolved.append(
|
|
33
|
+
opts.options["serializable-date-types"] ? "number" : "Date",
|
|
34
|
+
);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
} else if (this._t.baseScalar) {
|
|
38
|
+
out.resolved.append(
|
|
39
|
+
(await this.resolveNested(this._t.baseScalar, opts, out)).resolved,
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
switch (this._t.name) {
|
|
43
|
+
case "boolean":
|
|
44
|
+
out.resolved.append("boolean");
|
|
45
|
+
break;
|
|
46
|
+
case "bytes":
|
|
47
|
+
out.resolved.append("Uint8Array");
|
|
48
|
+
break;
|
|
49
|
+
case "duration":
|
|
50
|
+
out.resolved.append("number");
|
|
51
|
+
break;
|
|
52
|
+
case "numeric":
|
|
53
|
+
out.resolved.append("number");
|
|
54
|
+
break;
|
|
55
|
+
case "plainTime":
|
|
56
|
+
out.resolved.append("string");
|
|
57
|
+
break;
|
|
58
|
+
case "string":
|
|
59
|
+
out.resolved.append("string");
|
|
60
|
+
break;
|
|
61
|
+
case "url":
|
|
62
|
+
out.resolved.append("string");
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
default:
|
|
66
|
+
this.diagnostic(
|
|
67
|
+
"resolve-unresolved-scalar",
|
|
68
|
+
`Could not resolve scalar ${this._t.name}`,
|
|
69
|
+
"error",
|
|
70
|
+
);
|
|
71
|
+
out.resolved.append("unknown");
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected async typeguard(
|
|
78
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
79
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
if (this.dateTypes.includes(this._t.name)) {
|
|
82
|
+
switch (this._t.name) {
|
|
83
|
+
case "offsetDateTime":
|
|
84
|
+
case "plainDate":
|
|
85
|
+
case "utcDateTime":
|
|
86
|
+
out.resolved.append(
|
|
87
|
+
opts.options["serializable-date-types"]
|
|
88
|
+
? `typeof ${opts.accessor} === 'string'`
|
|
89
|
+
: `${opts.accessor} instanceof Date`,
|
|
90
|
+
);
|
|
91
|
+
break;
|
|
92
|
+
case "unixTimestamp32":
|
|
93
|
+
out.resolved.append(
|
|
94
|
+
opts.options["serializable-date-types"]
|
|
95
|
+
? `typeof ${opts.accessor} === 'number'`
|
|
96
|
+
: `${opts.accessor} instanceof Date`,
|
|
97
|
+
);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
} else if (this._t.baseScalar) {
|
|
101
|
+
out.resolved.append(
|
|
102
|
+
(await this.resolveNested(this._t.baseScalar, opts, out)).resolved,
|
|
103
|
+
);
|
|
104
|
+
} else if (this._t.name === "bytes") {
|
|
105
|
+
out.resolved.append(`${opts.accessor} instanceof Uint8Array`);
|
|
106
|
+
} else {
|
|
107
|
+
out.resolved.append(`typeof ${opts.accessor} === '`);
|
|
108
|
+
switch (this._t.name) {
|
|
109
|
+
case "boolean":
|
|
110
|
+
out.resolved.append("boolean");
|
|
111
|
+
break;
|
|
112
|
+
case "duration":
|
|
113
|
+
out.resolved.append("number");
|
|
114
|
+
break;
|
|
115
|
+
case "numeric":
|
|
116
|
+
out.resolved.append("number");
|
|
117
|
+
break;
|
|
118
|
+
case "plainTime":
|
|
119
|
+
out.resolved.append("string");
|
|
120
|
+
break;
|
|
121
|
+
case "string":
|
|
122
|
+
out.resolved.append("string");
|
|
123
|
+
break;
|
|
124
|
+
case "url":
|
|
125
|
+
out.resolved.append("string");
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
default:
|
|
129
|
+
this.diagnostic(
|
|
130
|
+
"resolve-unresolved-scalar",
|
|
131
|
+
`Could not create typeguard for scalar ${this._t.name}`,
|
|
132
|
+
"error",
|
|
133
|
+
);
|
|
134
|
+
out.resolved.append("unknown");
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
out.resolved.append("'");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntrinsicType,
|
|
3
|
+
NumericLiteral,
|
|
4
|
+
StringLiteral,
|
|
5
|
+
Type,
|
|
6
|
+
} from "@typespec/compiler";
|
|
7
|
+
import { Resolvable } from "../Resolvable.js";
|
|
8
|
+
import {
|
|
9
|
+
Resolver,
|
|
10
|
+
ResolverOptions,
|
|
11
|
+
ResolverResult,
|
|
12
|
+
} from "../Resolvable_helpers.js";
|
|
13
|
+
|
|
14
|
+
const allowedTypeKinds = [
|
|
15
|
+
"Boolean",
|
|
16
|
+
"Intrinsic",
|
|
17
|
+
"Number",
|
|
18
|
+
"String",
|
|
19
|
+
"TemplateParameter",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export class ResolvableSimple extends Resolvable<Type> {
|
|
23
|
+
protected expectedTypeKind = ""; // placeholder as required; validate is overridden
|
|
24
|
+
public static AllowedTypeKinds = allowedTypeKinds;
|
|
25
|
+
|
|
26
|
+
private static typeMap: Record<
|
|
27
|
+
(typeof allowedTypeKinds)[number],
|
|
28
|
+
(t: Type) => string
|
|
29
|
+
> = {
|
|
30
|
+
Boolean: (t) => ((t as any).value ? "true" : "false"),
|
|
31
|
+
Intrinsic: (t) => (t as IntrinsicType).name,
|
|
32
|
+
Number: (t) => (t as NumericLiteral).valueAsString,
|
|
33
|
+
String: (t) => `'${(t as StringLiteral).value}'`,
|
|
34
|
+
TemplateParameter: () => "unknown",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
protected async type(
|
|
38
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
39
|
+
out: ResolverResult<Resolver.Type>,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const mapped =
|
|
42
|
+
ResolvableSimple.typeMap[
|
|
43
|
+
this._t.kind as (typeof allowedTypeKinds)[number]
|
|
44
|
+
]; // check is in validate()
|
|
45
|
+
out.resolved.append(mapped(this._t));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected async typeguard(
|
|
49
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
50
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
const mapped =
|
|
53
|
+
ResolvableSimple.typeMap[
|
|
54
|
+
this._t.kind as (typeof allowedTypeKinds)[number]
|
|
55
|
+
]; // check is in validate()
|
|
56
|
+
const v = mapped(this._t);
|
|
57
|
+
|
|
58
|
+
// some special intrinsic types are handled directly in the model property
|
|
59
|
+
switch (v) {
|
|
60
|
+
case "unknown":
|
|
61
|
+
out.resolved.append("true");
|
|
62
|
+
break;
|
|
63
|
+
case "never":
|
|
64
|
+
out.resolved.append("false");
|
|
65
|
+
break; // instantly fails all type checks
|
|
66
|
+
case "void":
|
|
67
|
+
out.resolved.append(`${opts.accessor} === undefined`);
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
out.resolved.append(`${opts.accessor} === ${v}`);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected validate(): void {
|
|
77
|
+
if (!ResolvableSimple.AllowedTypeKinds.includes(this._t.kind as any)) {
|
|
78
|
+
this.diagnostic(
|
|
79
|
+
"typeclass-mismatch",
|
|
80
|
+
`Tried resolving type ${this._t.kind} as primitive`,
|
|
81
|
+
"error",
|
|
82
|
+
);
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
`Type-kind/class mismatch: tried resolving type ${this._t.kind} as primitive`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Tuple } 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 ResolvableTuple extends Resolvable<Tuple> {
|
|
10
|
+
protected expectedTypeKind = "Tuple";
|
|
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 this._t.values) {
|
|
18
|
+
if (await Resolvable.hasVisibility(v, opts, out, this._t)) return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected async type(
|
|
24
|
+
opts: ResolverOptions<Resolver.Type>,
|
|
25
|
+
out: ResolverResult<Resolver.Type>,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const results: string[] = [];
|
|
28
|
+
for (const v of this._t.values) {
|
|
29
|
+
const resolved = await this.resolveNested(v, opts, out);
|
|
30
|
+
out.imports.push(...resolved.imports);
|
|
31
|
+
results.push(resolved.resolved.value);
|
|
32
|
+
}
|
|
33
|
+
out.resolved.append(`[${results.join(", ")}]`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected async typeguard(
|
|
37
|
+
opts: ResolverOptions<Resolver.Typeguard>,
|
|
38
|
+
out: ResolverResult<Resolver.Typeguard>,
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const results: string[] = [];
|
|
41
|
+
const oldAccessor = opts.accessor;
|
|
42
|
+
let i = 0;
|
|
43
|
+
for (const v of this._t.values) {
|
|
44
|
+
opts.accessor = `${opts.accessor}[${i}]`;
|
|
45
|
+
const resolved = await this.resolveNested(v, opts, out);
|
|
46
|
+
opts.accessor = oldAccessor;
|
|
47
|
+
out.imports.push(...resolved.imports);
|
|
48
|
+
results.push(resolved.resolved.value);
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
opts.accessor = oldAccessor;
|
|
52
|
+
out.resolved.append(
|
|
53
|
+
`Array.isArray(${opts.accessor}) && ${results.map((g) => `(${g})`).join(" && ")}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|