skir-java-gen 0.0.2
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/README.md +424 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +730 -0
- package/dist/index.js.map +1 -0
- package/dist/naming.d.ts +20 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +138 -0
- package/dist/naming.js.map +1 -0
- package/dist/type_speller.d.ts +15 -0
- package/dist/type_speller.d.ts.map +1 -0
- package/dist/type_speller.js +167 -0
- package/dist/type_speller.js.map +1 -0
- package/package.json +50 -0
- package/src/index.ts +1124 -0
- package/src/naming.ts +161 -0
- package/src/type_speller.ts +185 -0
package/src/naming.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Field, RecordLocation, convertCase } from "skir-internal";
|
|
2
|
+
|
|
3
|
+
export interface ClassName {
|
|
4
|
+
/** The name right after the 'class' keyword. */
|
|
5
|
+
name: string;
|
|
6
|
+
/**
|
|
7
|
+
* Fully qualified class name.
|
|
8
|
+
* Examples: 'skirout.Foo', 'skirout.Foo.Bar'
|
|
9
|
+
*/
|
|
10
|
+
qualifiedName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Namer {
|
|
14
|
+
private readonly genPackageFirstName: string;
|
|
15
|
+
|
|
16
|
+
constructor(private readonly packagePrefix: string) {
|
|
17
|
+
if (packagePrefix.length <= 0) {
|
|
18
|
+
this.genPackageFirstName = "skirout";
|
|
19
|
+
} else {
|
|
20
|
+
this.genPackageFirstName = packagePrefix.split(".")[0]!;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
structFieldToJavaName(field: Field | string): string {
|
|
25
|
+
const skirName = typeof field === "string" ? field : field.name.text;
|
|
26
|
+
const lowerCamel = convertCase(skirName, "lowerCamel");
|
|
27
|
+
const nameConflict =
|
|
28
|
+
JAVA_HARD_KEYWORDS.has(lowerCamel) ||
|
|
29
|
+
TOP_LEVEL_PACKAGE_NAMES.has(lowerCamel) ||
|
|
30
|
+
JAVA_OBJECT_SYMBOLS.has(lowerCamel) ||
|
|
31
|
+
GENERATED_STRUCT_SYMBOLS.has(lowerCamel) ||
|
|
32
|
+
lowerCamel === this.genPackageFirstName;
|
|
33
|
+
return nameConflict ? lowerCamel + "_" : lowerCamel;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Returns the name of the frozen Java class for the given record. */
|
|
37
|
+
getClassName(record: RecordLocation): ClassName {
|
|
38
|
+
const { recordAncestors } = record;
|
|
39
|
+
const parts: string[] = [];
|
|
40
|
+
const seenNames = new Set<string>();
|
|
41
|
+
for (let i = 0; i < recordAncestors.length; ++i) {
|
|
42
|
+
const record = recordAncestors[i]!;
|
|
43
|
+
let name = record.name.text;
|
|
44
|
+
if (
|
|
45
|
+
name === "Kind" ||
|
|
46
|
+
name === "Builder" ||
|
|
47
|
+
/[^a-z]Wrapper$/.test(name) ||
|
|
48
|
+
(i === 0 && (name === "Constants" || name === "Methods"))
|
|
49
|
+
) {
|
|
50
|
+
name += "_";
|
|
51
|
+
}
|
|
52
|
+
while (seenNames.has(name)) {
|
|
53
|
+
name += "_";
|
|
54
|
+
}
|
|
55
|
+
seenNames.add(name);
|
|
56
|
+
parts.push(name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const name = parts.at(-1)!;
|
|
60
|
+
|
|
61
|
+
const path = record.modulePath;
|
|
62
|
+
const importPath = path.replace(/\.skir$/, "").replace("/", ".");
|
|
63
|
+
const qualifiedName = `${this.packagePrefix}skirout.${importPath}.${parts.join(".")}`;
|
|
64
|
+
|
|
65
|
+
return { name, qualifiedName };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// TODO: update
|
|
70
|
+
const JAVA_HARD_KEYWORDS: ReadonlySet<string> = new Set([
|
|
71
|
+
"abstract",
|
|
72
|
+
"assert",
|
|
73
|
+
"boolean",
|
|
74
|
+
"break",
|
|
75
|
+
"byte",
|
|
76
|
+
"case",
|
|
77
|
+
"catch",
|
|
78
|
+
"char",
|
|
79
|
+
"class",
|
|
80
|
+
"const",
|
|
81
|
+
"continue",
|
|
82
|
+
"default",
|
|
83
|
+
"do",
|
|
84
|
+
"double",
|
|
85
|
+
"else",
|
|
86
|
+
"enum",
|
|
87
|
+
"exports",
|
|
88
|
+
"extends",
|
|
89
|
+
"false",
|
|
90
|
+
"final",
|
|
91
|
+
"finally",
|
|
92
|
+
"float",
|
|
93
|
+
"for",
|
|
94
|
+
"goto",
|
|
95
|
+
"if",
|
|
96
|
+
"implements",
|
|
97
|
+
"import",
|
|
98
|
+
"instanceof",
|
|
99
|
+
"int",
|
|
100
|
+
"interface",
|
|
101
|
+
"long",
|
|
102
|
+
"module",
|
|
103
|
+
"native",
|
|
104
|
+
"new",
|
|
105
|
+
"null",
|
|
106
|
+
"package",
|
|
107
|
+
"private",
|
|
108
|
+
"protected",
|
|
109
|
+
"public",
|
|
110
|
+
"return",
|
|
111
|
+
"short",
|
|
112
|
+
"static",
|
|
113
|
+
"strictfp",
|
|
114
|
+
"super",
|
|
115
|
+
"switch",
|
|
116
|
+
"synchronized",
|
|
117
|
+
"this",
|
|
118
|
+
"throw",
|
|
119
|
+
"throws",
|
|
120
|
+
"transient",
|
|
121
|
+
"true",
|
|
122
|
+
"try",
|
|
123
|
+
"void",
|
|
124
|
+
"volatile",
|
|
125
|
+
"while",
|
|
126
|
+
"with",
|
|
127
|
+
"yield",
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const TOP_LEVEL_PACKAGE_NAMES: ReadonlySet<string> = new Set<string>([
|
|
131
|
+
"build",
|
|
132
|
+
"java",
|
|
133
|
+
"kotlin",
|
|
134
|
+
"land",
|
|
135
|
+
"okio",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const JAVA_OBJECT_SYMBOLS: ReadonlySet<string> = new Set([
|
|
139
|
+
"clone",
|
|
140
|
+
"equals",
|
|
141
|
+
"finalize",
|
|
142
|
+
"getClass",
|
|
143
|
+
"hashCode",
|
|
144
|
+
"notify",
|
|
145
|
+
"notifyAll",
|
|
146
|
+
"toString",
|
|
147
|
+
"wait",
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
const GENERATED_STRUCT_SYMBOLS: ReadonlySet<string> = new Set([
|
|
151
|
+
"builder",
|
|
152
|
+
"partialBuilder",
|
|
153
|
+
"toBuilder",
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
export function toEnumConstantName(field: Field): string {
|
|
157
|
+
const skirName = field.name.text;
|
|
158
|
+
return skirName === "SERIALIZER" || skirName === "TYPE_DESCRIPTOR"
|
|
159
|
+
? `${skirName}_`
|
|
160
|
+
: skirName;
|
|
161
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { RecordKey, RecordLocation, ResolvedType } from "skir-internal";
|
|
2
|
+
import { ClassName, Namer } from "./naming.js";
|
|
3
|
+
|
|
4
|
+
export type TypeFlavor = "initializer" | "frozen" | "frozen-key";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transforms a type found in a `.skir` file into a Java type.
|
|
8
|
+
*/
|
|
9
|
+
export class TypeSpeller {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly recordMap: ReadonlyMap<RecordKey, RecordLocation>,
|
|
12
|
+
private readonly namer: Namer,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
getJavaType(
|
|
16
|
+
type: ResolvedType,
|
|
17
|
+
flavor: TypeFlavor,
|
|
18
|
+
mustBeObject?: "must-be-object",
|
|
19
|
+
): string {
|
|
20
|
+
switch (type.kind) {
|
|
21
|
+
case "record": {
|
|
22
|
+
const recordLocation = this.recordMap.get(type.key)!;
|
|
23
|
+
const record = recordLocation.record;
|
|
24
|
+
const className = this.namer.getClassName(recordLocation).qualifiedName;
|
|
25
|
+
if (record.recordType === "struct") {
|
|
26
|
+
return className;
|
|
27
|
+
}
|
|
28
|
+
// An enum.
|
|
29
|
+
const _: "enum" = record.recordType;
|
|
30
|
+
if (flavor === "initializer" || flavor === "frozen") {
|
|
31
|
+
return className;
|
|
32
|
+
} else if (flavor === "frozen-key") {
|
|
33
|
+
return `${className}.Kind`;
|
|
34
|
+
} else {
|
|
35
|
+
const _: never = flavor;
|
|
36
|
+
throw TypeError();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
case "array": {
|
|
40
|
+
const itemType = this.getJavaType(type.item, flavor, "must-be-object");
|
|
41
|
+
if (flavor === "initializer") {
|
|
42
|
+
return `java.lang.Iterable<? extends ${itemType}>`;
|
|
43
|
+
} else if (flavor === "frozen" || flavor === "frozen-key") {
|
|
44
|
+
if (type.key) {
|
|
45
|
+
const { keyType } = type.key;
|
|
46
|
+
const javaKeyType = this.getJavaType(
|
|
47
|
+
keyType,
|
|
48
|
+
"frozen-key",
|
|
49
|
+
"must-be-object",
|
|
50
|
+
);
|
|
51
|
+
return `build.skir.KeyedList<${itemType}, ${javaKeyType}>`;
|
|
52
|
+
} else {
|
|
53
|
+
return `java.util.List<${itemType}>`;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
const _: "kind" = flavor;
|
|
57
|
+
throw TypeError();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
case "optional": {
|
|
61
|
+
const otherType = this.getJavaType(
|
|
62
|
+
type.other,
|
|
63
|
+
flavor,
|
|
64
|
+
"must-be-object",
|
|
65
|
+
);
|
|
66
|
+
return `java.util.Optional<${otherType}>`;
|
|
67
|
+
}
|
|
68
|
+
case "primitive": {
|
|
69
|
+
const { primitive } = type;
|
|
70
|
+
if (mustBeObject) {
|
|
71
|
+
switch (primitive) {
|
|
72
|
+
case "bool":
|
|
73
|
+
return "java.lang.Boolean";
|
|
74
|
+
case "int32":
|
|
75
|
+
return "java.lang.Integer";
|
|
76
|
+
case "int64":
|
|
77
|
+
case "uint64":
|
|
78
|
+
return "java.lang.Long";
|
|
79
|
+
case "float32":
|
|
80
|
+
return "java.lang.Float";
|
|
81
|
+
case "float64":
|
|
82
|
+
return "java.lang.Double";
|
|
83
|
+
case "timestamp":
|
|
84
|
+
return "java.time.Instant";
|
|
85
|
+
case "string":
|
|
86
|
+
return "java.lang.String";
|
|
87
|
+
case "bytes":
|
|
88
|
+
return "okio.ByteString";
|
|
89
|
+
default: {
|
|
90
|
+
const _: never = primitive;
|
|
91
|
+
throw TypeError();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
switch (primitive) {
|
|
96
|
+
case "bool":
|
|
97
|
+
return "boolean";
|
|
98
|
+
case "int32":
|
|
99
|
+
return "int";
|
|
100
|
+
case "int64":
|
|
101
|
+
case "uint64":
|
|
102
|
+
return "long";
|
|
103
|
+
case "float32":
|
|
104
|
+
return "float";
|
|
105
|
+
case "float64":
|
|
106
|
+
return "double";
|
|
107
|
+
case "timestamp":
|
|
108
|
+
return "java.time.Instant";
|
|
109
|
+
case "string":
|
|
110
|
+
return "java.lang.String";
|
|
111
|
+
case "bytes":
|
|
112
|
+
return "okio.ByteString";
|
|
113
|
+
default: {
|
|
114
|
+
const _: never = primitive;
|
|
115
|
+
throw TypeError();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getClassName(recordKey: RecordKey): ClassName {
|
|
124
|
+
const record = this.recordMap.get(recordKey)!;
|
|
125
|
+
return this.namer.getClassName(record);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getSerializerExpression(type: ResolvedType): string {
|
|
129
|
+
switch (type.kind) {
|
|
130
|
+
case "primitive": {
|
|
131
|
+
switch (type.primitive) {
|
|
132
|
+
case "bool":
|
|
133
|
+
return "build.skir.Serializers.bool()";
|
|
134
|
+
case "int32":
|
|
135
|
+
return "build.skir.Serializers.int32()";
|
|
136
|
+
case "int64":
|
|
137
|
+
return "build.skir.Serializers.int64()";
|
|
138
|
+
case "uint64":
|
|
139
|
+
return "build.skir.Serializers.javaUint64()";
|
|
140
|
+
case "float32":
|
|
141
|
+
return "build.skir.Serializers.float32()";
|
|
142
|
+
case "float64":
|
|
143
|
+
return "build.skir.Serializers.float64()";
|
|
144
|
+
case "timestamp":
|
|
145
|
+
return "build.skir.Serializers.timestamp()";
|
|
146
|
+
case "string":
|
|
147
|
+
return "build.skir.Serializers.string()";
|
|
148
|
+
case "bytes":
|
|
149
|
+
return "build.skir.Serializers.bytes()";
|
|
150
|
+
}
|
|
151
|
+
const _: never = type.primitive;
|
|
152
|
+
throw TypeError();
|
|
153
|
+
}
|
|
154
|
+
case "array": {
|
|
155
|
+
if (type.key) {
|
|
156
|
+
const keyChain = type.key.path.map((f) => f.name.text).join(".");
|
|
157
|
+
const path = type.key.path
|
|
158
|
+
.map((f) => this.namer.structFieldToJavaName(f.name.text) + "()")
|
|
159
|
+
.join(".");
|
|
160
|
+
return (
|
|
161
|
+
"build.skir.internal.ListSerializerKt.keyedListSerializer(\n" +
|
|
162
|
+
this.getSerializerExpression(type.item) +
|
|
163
|
+
`,\n"${keyChain}",\n(it) -> it.${path}\n)`
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
return (
|
|
167
|
+
"build.skir.Serializers.list(\n" +
|
|
168
|
+
this.getSerializerExpression(type.item) +
|
|
169
|
+
"\n)"
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
case "optional": {
|
|
174
|
+
return (
|
|
175
|
+
`build.skir.Serializers.javaOptional(\n` +
|
|
176
|
+
this.getSerializerExpression(type.other) +
|
|
177
|
+
`\n)`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
case "record": {
|
|
181
|
+
return this.getClassName(type.key).qualifiedName + ".SERIALIZER";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|