skir-dart-gen 0.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/src/naming.ts ADDED
@@ -0,0 +1,163 @@
1
+ import {
2
+ Constant,
3
+ Field,
4
+ Module,
5
+ RecordLocation,
6
+ convertCase,
7
+ } from "skir-internal";
8
+
9
+ export function structFieldToDartName(field: Field | string): string {
10
+ const skirName = typeof field === "string" ? field : field.name.text;
11
+ const convertCaseResult = convertCase(skirName, "lowerCamel");
12
+ return DART_KEYWORDS.has(convertCaseResult) ||
13
+ DART_OBJECT_SYMBOLS.has(convertCaseResult) ||
14
+ GENERATED_STRUCT_SYMBOLS.has(convertCaseResult) ||
15
+ skirName.startsWith("mutable_")
16
+ ? convertCaseResult + "_"
17
+ : convertCaseResult;
18
+ }
19
+
20
+ export function enumFieldToDartName(field: Field): string {
21
+ const skirName = field.name.text;
22
+ const convertCaseResult = convertCase(skirName, "lowerCamel");
23
+ return DART_KEYWORDS.has(convertCaseResult) ||
24
+ DART_OBJECT_SYMBOLS.has(convertCaseResult) ||
25
+ GENERATED_ENUM_SYMBOLS.has(convertCaseResult) ||
26
+ skirName.startsWith("wrap_") ||
27
+ skirName.startsWith("create_")
28
+ ? convertCaseResult + "_"
29
+ : convertCaseResult;
30
+ }
31
+
32
+ export function toLowerCamel(field: Field): string {
33
+ const skirName = field.name.text;
34
+ return convertCase(skirName, "lowerCamel");
35
+ }
36
+
37
+ export function toUpperCamel(field: Field): string {
38
+ const skirName = field.name.text;
39
+ return convertCase(skirName, "UpperCamel");
40
+ }
41
+
42
+ export function toTopLevelConstantName(constant: Constant): string {
43
+ const skirName = constant.name.text;
44
+ const convertCaseResult = convertCase(skirName, "lowerCamel");
45
+ return DART_KEYWORDS.has(convertCaseResult) || skirName.endsWith("_METHOD")
46
+ ? convertCaseResult + "_"
47
+ : convertCaseResult;
48
+ }
49
+
50
+ /** Returns the name of the frozen Dart class for the given record. */
51
+ export function getClassName(
52
+ record: RecordLocation,
53
+ origin: {
54
+ origin: Module;
55
+ },
56
+ ): string {
57
+ const { recordAncestors } = record;
58
+ const parts: string[] = [];
59
+ for (let i = 0; i < recordAncestors.length; ++i) {
60
+ const record = recordAncestors[i]!;
61
+ parts.push(record.name.text);
62
+ }
63
+
64
+ const name = parts.join("_");
65
+
66
+ if (origin.origin.path === record.modulePath) {
67
+ return name;
68
+ } else {
69
+ const alias = getModuleAlias(record.modulePath);
70
+ return `${alias}.${name}`;
71
+ }
72
+ }
73
+
74
+ export function getModuleAlias(modulePath: string): string {
75
+ return "_lib_" + modulePath.replace(/\.skir$/, "").replace("/", "_");
76
+ }
77
+
78
+ const DART_KEYWORDS: ReadonlySet<string> = new Set([
79
+ "abstract",
80
+ "as",
81
+ "assert",
82
+ "async",
83
+ "await",
84
+ "break",
85
+ "case",
86
+ "catch",
87
+ "class",
88
+ "const",
89
+ "continue",
90
+ "covariant",
91
+ "default",
92
+ "deferred",
93
+ "do",
94
+ "dynamic",
95
+ "else",
96
+ "enum",
97
+ "export",
98
+ "extends",
99
+ "extension",
100
+ "external",
101
+ "factory",
102
+ "false",
103
+ "final",
104
+ "finally",
105
+ "for",
106
+ "Function",
107
+ "get",
108
+ "hide",
109
+ "if",
110
+ "implements",
111
+ "import",
112
+ "in",
113
+ "interface",
114
+ "is",
115
+ "late",
116
+ "library",
117
+ "mixin",
118
+ "new",
119
+ "null",
120
+ "on",
121
+ "operator",
122
+ "part",
123
+ "required",
124
+ "rethrow",
125
+ "return",
126
+ "set",
127
+ "show",
128
+ "static",
129
+ "super",
130
+ "switch",
131
+ "sync",
132
+ "this",
133
+ "throw",
134
+ "true",
135
+ "try",
136
+ "typedef",
137
+ "var",
138
+ "void",
139
+ "while",
140
+ "with",
141
+ "yield",
142
+ ]);
143
+
144
+ const DART_OBJECT_SYMBOLS: ReadonlySet<string> = new Set([
145
+ "hashCode",
146
+ "noSuchMethod",
147
+ "runtimeType",
148
+ "toString",
149
+ ]);
150
+
151
+ const GENERATED_STRUCT_SYMBOLS: ReadonlySet<string> = new Set([
152
+ "defaultInstance",
153
+ "mutable",
154
+ "serializer",
155
+ "toFrozen",
156
+ "toMutable",
157
+ ]);
158
+
159
+ const GENERATED_ENUM_SYMBOLS: ReadonlySet<string> = new Set([
160
+ "isUnknown",
161
+ "kind",
162
+ "serializer",
163
+ ]);
@@ -0,0 +1,232 @@
1
+ import type {
2
+ Module,
3
+ RecordKey,
4
+ RecordLocation,
5
+ ResolvedType,
6
+ } from "skir-internal";
7
+ import { getClassName, structFieldToDartName } from "./naming.js";
8
+
9
+ export type TypeFlavor =
10
+ | "initializer"
11
+ | "frozen"
12
+ | "maybe-mutable"
13
+ | "mutable"
14
+ | "kind";
15
+
16
+ /**
17
+ * Transforms a type found in a `.skir` file into a Dart type.
18
+ *
19
+ * The flavors are:
20
+ * · initializer
21
+ * The value can be passed by parameter to the constructor of a frozen class.
22
+ * · frozen:
23
+ * The type is deeply immutable. All the fields of a frozen class are also
24
+ * frozen.
25
+ * · maybe-mutable:
26
+ * Type union of the frozen type and the mutable type. All the fields of a
27
+ * mutable class are maybe-mutable.
28
+ * · mutable:
29
+ * A mutable value. Not all types found in `.skir` files support this, e.g.
30
+ * strings and numbers are always immutable.
31
+ */
32
+ export class TypeSpeller {
33
+ constructor(
34
+ readonly recordMap: ReadonlyMap<RecordKey, RecordLocation>,
35
+ private readonly origin: Module,
36
+ ) {}
37
+
38
+ getDartType(
39
+ type: ResolvedType,
40
+ flavor: "initializer" | "frozen" | "mutable",
41
+ allRecordsFrozen?: undefined,
42
+ ): string;
43
+
44
+ getDartType(
45
+ type: ResolvedType,
46
+ flavor: TypeFlavor,
47
+ // Only matters if mode is "maybe-mutable"
48
+ allRecordsFrozen: boolean | undefined,
49
+ ): string;
50
+
51
+ getDartType(
52
+ type: ResolvedType,
53
+ flavor: TypeFlavor,
54
+ // Only matters if mode is "maybe-mutable"
55
+ allRecordsFrozen: boolean | undefined,
56
+ ): string {
57
+ switch (type.kind) {
58
+ case "record": {
59
+ const recordLocation = this.recordMap.get(type.key)!;
60
+ const record = recordLocation.record;
61
+ const className = this.getClassName(recordLocation);
62
+ if (record.recordType === "struct") {
63
+ if (flavor === "frozen" || allRecordsFrozen) {
64
+ return className;
65
+ } else if (flavor === "maybe-mutable" || flavor === "initializer") {
66
+ return allRecordsFrozen ? className : `${className}_orMutable`;
67
+ } else if (flavor === "mutable") {
68
+ return `${className}_mutable`;
69
+ } else {
70
+ const _: "kind" = flavor;
71
+ throw TypeError();
72
+ }
73
+ }
74
+ // An enum.
75
+ const _: "enum" = record.recordType;
76
+ if (
77
+ flavor === "initializer" ||
78
+ flavor === "frozen" ||
79
+ flavor === "maybe-mutable" ||
80
+ flavor === "mutable"
81
+ ) {
82
+ return className;
83
+ } else if (flavor === "kind") {
84
+ return `${className}_kind`;
85
+ } else {
86
+ const _: never = flavor;
87
+ throw TypeError();
88
+ }
89
+ }
90
+ case "array": {
91
+ if (flavor === "initializer") {
92
+ const itemType = this.getDartType(
93
+ type.item,
94
+ "maybe-mutable",
95
+ allRecordsFrozen,
96
+ );
97
+ return `_core.Iterable<${itemType}>`;
98
+ } else if (flavor === "frozen") {
99
+ const itemType = this.getDartType(
100
+ type.item,
101
+ "frozen",
102
+ allRecordsFrozen,
103
+ );
104
+ if (type.key) {
105
+ const { keyType } = type.key;
106
+ let dartKeyType = this.getDartType(keyType, "frozen");
107
+ if (keyType.kind === "record") {
108
+ dartKeyType += "_kind";
109
+ }
110
+ return `_skir.KeyedIterable<${itemType}, ${dartKeyType}>`;
111
+ } else {
112
+ return `_core.Iterable<${itemType}>`;
113
+ }
114
+ } else if (flavor === "maybe-mutable") {
115
+ const itemType = this.getDartType(
116
+ type.item,
117
+ "maybe-mutable",
118
+ allRecordsFrozen,
119
+ );
120
+ return `_core.Iterable<${itemType}>`;
121
+ } else if (flavor === "mutable") {
122
+ const itemType = this.getDartType(
123
+ type.item,
124
+ "maybe-mutable",
125
+ allRecordsFrozen,
126
+ );
127
+ return `_core.List<${itemType}>`;
128
+ } else {
129
+ const _: "kind" = flavor;
130
+ throw TypeError();
131
+ }
132
+ }
133
+ case "optional": {
134
+ const otherType = this.getDartType(
135
+ type.other,
136
+ flavor,
137
+ allRecordsFrozen,
138
+ );
139
+ return `${otherType}?`;
140
+ }
141
+ case "primitive": {
142
+ const { primitive } = type;
143
+ switch (primitive) {
144
+ case "bool":
145
+ return "_core.bool";
146
+ case "int32":
147
+ case "int64":
148
+ return "_core.int";
149
+ case "uint64":
150
+ return "_core.BigInt";
151
+ case "float32":
152
+ case "float64":
153
+ return "_core.double";
154
+ case "timestamp":
155
+ return "_core.DateTime";
156
+ case "string":
157
+ return "_core.String";
158
+ case "bytes":
159
+ return "_skir.ByteString";
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ getClassName(recordOrKey: RecordKey | RecordLocation): string {
166
+ const record =
167
+ typeof recordOrKey === "string"
168
+ ? this.recordMap.get(recordOrKey)!
169
+ : recordOrKey;
170
+ return getClassName(record, { origin: this.origin });
171
+ }
172
+
173
+ getSerializerExpression(type: ResolvedType): string {
174
+ switch (type.kind) {
175
+ case "primitive": {
176
+ switch (type.primitive) {
177
+ case "bool":
178
+ return "_skir.Serializers.bool";
179
+ case "int32":
180
+ return "_skir.Serializers.int32";
181
+ case "int64":
182
+ return "_skir.Serializers.int64";
183
+ case "uint64":
184
+ return "_skir.Serializers.uint64";
185
+ case "float32":
186
+ return "_skir.Serializers.float32";
187
+ case "float64":
188
+ return "_skir.Serializers.float64";
189
+ case "timestamp":
190
+ return "_skir.Serializers.timestamp";
191
+ case "string":
192
+ return "_skir.Serializers.string";
193
+ case "bytes":
194
+ return "_skir.Serializers.bytes";
195
+ }
196
+ const _: never = type.primitive;
197
+ throw TypeError();
198
+ }
199
+ case "array": {
200
+ if (type.key) {
201
+ const keyChain = type.key.path.map((p) => p.name.text).join(".");
202
+ const path = type.key.path
203
+ .map((f) => structFieldToDartName(f.name.text))
204
+ .join(".");
205
+ const itemType = this.getDartType(type.item, "frozen");
206
+ return (
207
+ "_skir.Serializers.keyedIterable(\n" +
208
+ `${this.getSerializerExpression(type.item)},\n` +
209
+ `(${itemType} it) => it.${path},\n` +
210
+ `internal__getKeySpec: "${keyChain}",\n)`
211
+ );
212
+ } else {
213
+ return (
214
+ "_skir.Serializers.iterable(\n" +
215
+ this.getSerializerExpression(type.item) +
216
+ ",\n)"
217
+ );
218
+ }
219
+ }
220
+ case "optional": {
221
+ return (
222
+ `_skir.Serializers.optional(\n` +
223
+ this.getSerializerExpression(type.other) +
224
+ `,\n)`
225
+ );
226
+ }
227
+ case "record": {
228
+ return this.getClassName(type.key) + ".serializer";
229
+ }
230
+ }
231
+ }
232
+ }