expo-type-information 0.0.0 → 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/LICENSE +21 -0
- package/README.md +31 -0
- package/bin/cli.js +2 -0
- package/build/cli.d.ts +1 -0
- package/build/cli.js +35 -0
- package/build/cli.js.map +1 -0
- package/build/commands/commandUtils.d.ts +46 -0
- package/build/commands/commandUtils.js +274 -0
- package/build/commands/commandUtils.js.map +1 -0
- package/build/commands/generateJSXIntrinsicsCommand.d.ts +2 -0
- package/build/commands/generateJSXIntrinsicsCommand.js +28 -0
- package/build/commands/generateJSXIntrinsicsCommand.js.map +1 -0
- package/build/commands/generateMocksForFileCommand.d.ts +2 -0
- package/build/commands/generateMocksForFileCommand.js +22 -0
- package/build/commands/generateMocksForFileCommand.js.map +1 -0
- package/build/commands/generateModuleTypesCommand.d.ts +2 -0
- package/build/commands/generateModuleTypesCommand.js +32 -0
- package/build/commands/generateModuleTypesCommand.js.map +1 -0
- package/build/commands/generateViewTypesCommand.d.ts +2 -0
- package/build/commands/generateViewTypesCommand.js +28 -0
- package/build/commands/generateViewTypesCommand.js.map +1 -0
- package/build/commands/inlineModulesInterfaceCommand.d.ts +2 -0
- package/build/commands/inlineModulesInterfaceCommand.js +129 -0
- package/build/commands/inlineModulesInterfaceCommand.js.map +1 -0
- package/build/commands/moduleInterfaceCommand.d.ts +2 -0
- package/build/commands/moduleInterfaceCommand.js +46 -0
- package/build/commands/moduleInterfaceCommand.js.map +1 -0
- package/build/commands/shortModuleInterfaceCommand.d.ts +2 -0
- package/build/commands/shortModuleInterfaceCommand.js +17 -0
- package/build/commands/shortModuleInterfaceCommand.js.map +1 -0
- package/build/commands/typeInformationCommand.d.ts +2 -0
- package/build/commands/typeInformationCommand.js +24 -0
- package/build/commands/typeInformationCommand.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +28 -0
- package/build/index.js.map +1 -0
- package/build/mockgen.d.ts +4 -0
- package/build/mockgen.js +204 -0
- package/build/mockgen.js.map +1 -0
- package/build/swift/sourcekittenTypeInformation.d.ts +6 -0
- package/build/swift/sourcekittenTypeInformation.js +875 -0
- package/build/swift/sourcekittenTypeInformation.js.map +1 -0
- package/build/typeInformation.d.ts +209 -0
- package/build/typeInformation.js +157 -0
- package/build/typeInformation.js.map +1 -0
- package/build/types.d.ts +59 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/build/typescriptGeneration.d.ts +61 -0
- package/build/typescriptGeneration.js +696 -0
- package/build/typescriptGeneration.js.map +1 -0
- package/build/utils.d.ts +6 -0
- package/build/utils.js +44 -0
- package/build/utils.js.map +1 -0
- package/jest.config.js +6 -0
- package/package.json +46 -5
- package/src/cli.ts +38 -0
- package/src/commands/commandUtils.ts +352 -0
- package/src/commands/generateJSXIntrinsicsCommand.ts +38 -0
- package/src/commands/generateMocksForFileCommand.ts +30 -0
- package/src/commands/generateModuleTypesCommand.ts +39 -0
- package/src/commands/generateViewTypesCommand.ts +39 -0
- package/src/commands/inlineModulesInterfaceCommand.ts +175 -0
- package/src/commands/moduleInterfaceCommand.ts +56 -0
- package/src/commands/shortModuleInterfaceCommand.ts +26 -0
- package/src/commands/typeInformationCommand.ts +35 -0
- package/src/index.ts +9 -0
- package/src/mockgen.ts +338 -0
- package/src/swift/sourcekittenTypeInformation.ts +1173 -0
- package/src/typeInformation.ts +326 -0
- package/src/types.ts +68 -0
- package/src/typescriptGeneration.ts +1179 -0
- package/src/utils.ts +44 -0
- package/tests/TestModule.swift +175 -0
- package/tests/__snapshots__/typeInformation.test.ts.snap +1578 -0
- package/tests/typeInformation.test.ts +134 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import prettier from 'prettier';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Argument,
|
|
7
|
+
ArrayType,
|
|
8
|
+
BasicType,
|
|
9
|
+
ClassDeclaration,
|
|
10
|
+
ConstantDeclaration,
|
|
11
|
+
ConstructorDeclaration,
|
|
12
|
+
DictionaryType,
|
|
13
|
+
EnumType,
|
|
14
|
+
EventDeclaration,
|
|
15
|
+
FileTypeInformation,
|
|
16
|
+
FunctionDeclaration,
|
|
17
|
+
ModuleClassDeclaration,
|
|
18
|
+
ParametrizedType,
|
|
19
|
+
PropDeclaration,
|
|
20
|
+
PropertyDeclaration,
|
|
21
|
+
RecordType,
|
|
22
|
+
SumType,
|
|
23
|
+
Type,
|
|
24
|
+
TypeKind,
|
|
25
|
+
ViewDeclaration,
|
|
26
|
+
} from './typeInformation';
|
|
27
|
+
|
|
28
|
+
const prefix = `Automatically generated by expo-type-information.`;
|
|
29
|
+
|
|
30
|
+
const exportModifier = () => ts.factory.createModifier(ts.SyntaxKind.ExportKeyword);
|
|
31
|
+
const declareModifier = () => ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword);
|
|
32
|
+
const asyncModifier = () => ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword);
|
|
33
|
+
const readonlyModifier = () => ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword);
|
|
34
|
+
const constModifier = () => ts.factory.createModifier(ts.SyntaxKind.ConstKeyword);
|
|
35
|
+
const defaultModifier = () => ts.factory.createModifier(ts.SyntaxKind.DefaultKeyword);
|
|
36
|
+
|
|
37
|
+
const unknownKeywordType = () => ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
38
|
+
const anyKeywordType = () => ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
|
|
39
|
+
const voidKeywordType = () => ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
40
|
+
|
|
41
|
+
const newlineIdentifier = () => ts.factory.createIdentifier('\n\n');
|
|
42
|
+
|
|
43
|
+
export type OutputFile = {
|
|
44
|
+
content: string;
|
|
45
|
+
name: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface GenerationContext {
|
|
49
|
+
fileInfo: FileTypeInformation;
|
|
50
|
+
module: ModuleClassDeclaration;
|
|
51
|
+
view: ViewDeclaration | null;
|
|
52
|
+
missingTypes: Set<string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let freeId = 0;
|
|
56
|
+
function getNextFreeId() {
|
|
57
|
+
freeId += 1;
|
|
58
|
+
return freeId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createDefaultGenerationContext(
|
|
62
|
+
fileInfo: FileTypeInformation
|
|
63
|
+
): GenerationContext | null {
|
|
64
|
+
const module = fileInfo.moduleClasses[0];
|
|
65
|
+
if (!module) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const view = module.views[0] ?? null;
|
|
69
|
+
return {
|
|
70
|
+
fileInfo,
|
|
71
|
+
module,
|
|
72
|
+
view,
|
|
73
|
+
missingTypes: getMissingTypeIdentifiers(fileInfo),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getBasicTypesIdentifiers(): Set<string> {
|
|
78
|
+
return new Set<string>(['any', 'number', 'string', 'undefined', 'null', 'Map', 'Set', 'Promise']);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getAllNonBasicTypes(fileInfo: FileTypeInformation): Set<string> {
|
|
82
|
+
return fileInfo.declaredTypeIdentifiers
|
|
83
|
+
.union(fileInfo.usedTypeIdentifiers)
|
|
84
|
+
.difference(getBasicTypesIdentifiers());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createGeneratedPrefix(): ts.Node[] {
|
|
88
|
+
return [
|
|
89
|
+
ts.addSyntheticLeadingComment(
|
|
90
|
+
ts.factory.createIdentifier(''),
|
|
91
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
92
|
+
prefix
|
|
93
|
+
),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function constructModifiersArray(modifiers: {
|
|
98
|
+
exported?: boolean;
|
|
99
|
+
declare?: boolean;
|
|
100
|
+
async?: boolean;
|
|
101
|
+
readonly?: boolean;
|
|
102
|
+
}): ts.Modifier[] {
|
|
103
|
+
const modifiersArray: ts.Modifier[] = [];
|
|
104
|
+
if (modifiers.exported) modifiersArray.push(exportModifier());
|
|
105
|
+
if (modifiers.declare) modifiersArray.push(declareModifier());
|
|
106
|
+
if (modifiers.async) modifiersArray.push(asyncModifier());
|
|
107
|
+
if (modifiers.readonly) modifiersArray.push(readonlyModifier());
|
|
108
|
+
return modifiersArray;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function joinTSNodesWithNewlines(nodes: ts.Node[][]): ts.Node[] {
|
|
112
|
+
const new_nodes: ts.Node[] = [];
|
|
113
|
+
for (const node of nodes) {
|
|
114
|
+
if (node.length > 0) {
|
|
115
|
+
new_nodes.push(...node);
|
|
116
|
+
new_nodes.push(newlineIdentifier());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return new_nodes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mapBasicTypeToTypeNode(basicType: BasicType): ts.TypeNode {
|
|
123
|
+
if (basicType === BasicType.UNRESOLVED) {
|
|
124
|
+
return ts.addSyntheticTrailingComment(
|
|
125
|
+
unknownKeywordType(),
|
|
126
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
127
|
+
"The type couldn't be resolved automatically."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const BASIC_TYPE_MAP: Record<BasicType, ts.KeywordTypeSyntaxKind> = {
|
|
132
|
+
[BasicType.ANY]: ts.SyntaxKind.AnyKeyword,
|
|
133
|
+
[BasicType.BOOLEAN]: ts.SyntaxKind.BooleanKeyword,
|
|
134
|
+
[BasicType.NUMBER]: ts.SyntaxKind.NumberKeyword,
|
|
135
|
+
[BasicType.STRING]: ts.SyntaxKind.StringKeyword,
|
|
136
|
+
[BasicType.VOID]: ts.SyntaxKind.VoidKeyword,
|
|
137
|
+
[BasicType.UNDEFINED]: ts.SyntaxKind.UndefinedKeyword,
|
|
138
|
+
[BasicType.UNRESOLVED]: ts.SyntaxKind.UndefinedKeyword, // This is handled earlier
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return ts.factory.createKeywordTypeNode(BASIC_TYPE_MAP[basicType]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function mapTypeToTsTypeNode(type: Type): ts.TypeNode {
|
|
145
|
+
switch (type.kind) {
|
|
146
|
+
case TypeKind.BASIC:
|
|
147
|
+
return mapBasicTypeToTypeNode(type.type as BasicType);
|
|
148
|
+
case TypeKind.IDENTIFIER:
|
|
149
|
+
return ts.factory.createTypeReferenceNode(type.type as string);
|
|
150
|
+
case TypeKind.SUM:
|
|
151
|
+
return ts.factory.createUnionTypeNode((type.type as SumType).types.map(mapTypeToTsTypeNode));
|
|
152
|
+
case TypeKind.ARRAY:
|
|
153
|
+
return ts.factory.createArrayTypeNode(mapTypeToTsTypeNode(type.type as ArrayType));
|
|
154
|
+
case TypeKind.DICTIONARY: {
|
|
155
|
+
const dictionaryType = type.type as DictionaryType;
|
|
156
|
+
const name = 'key';
|
|
157
|
+
const typeNode = mapTypeToTsTypeNode(dictionaryType.key);
|
|
158
|
+
const valueType = mapTypeToTsTypeNode(dictionaryType.value);
|
|
159
|
+
return ts.factory.createTypeLiteralNode([
|
|
160
|
+
ts.factory.createIndexSignature(
|
|
161
|
+
undefined,
|
|
162
|
+
[createParameter({ name, type: typeNode })],
|
|
163
|
+
valueType
|
|
164
|
+
),
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
// Technically this one should only be the top one and it should be handled somewhere else
|
|
168
|
+
// for example when creating arguemnt adding the '?' token.
|
|
169
|
+
//
|
|
170
|
+
// However we can just make it (type | undefined) in here.
|
|
171
|
+
// TODO(@HubertBer): Maybe also need null?
|
|
172
|
+
case TypeKind.OPTIONAL:
|
|
173
|
+
return ts.factory.createUnionTypeNode([
|
|
174
|
+
mapTypeToTsTypeNode(type.type as Type),
|
|
175
|
+
mapBasicTypeToTypeNode(BasicType.UNDEFINED),
|
|
176
|
+
]);
|
|
177
|
+
case TypeKind.PARAMETRIZED:
|
|
178
|
+
return ts.factory.createTypeReferenceNode(
|
|
179
|
+
(type.type as ParametrizedType).name,
|
|
180
|
+
(type.type as ParametrizedType).types.map(mapTypeToTsTypeNode)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return mapBasicTypeToTypeNode(BasicType.UNRESOLVED);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//
|
|
187
|
+
// ts.factory wrapper functions
|
|
188
|
+
//
|
|
189
|
+
|
|
190
|
+
function createImportDeclaration({
|
|
191
|
+
defaultImportName,
|
|
192
|
+
namedImportsNames,
|
|
193
|
+
importFromName,
|
|
194
|
+
}: {
|
|
195
|
+
defaultImportName?: string;
|
|
196
|
+
namedImportsNames?: string[];
|
|
197
|
+
importFromName: string;
|
|
198
|
+
}): ts.Node[] {
|
|
199
|
+
const hasDefault = !!defaultImportName;
|
|
200
|
+
const hasNamed = namedImportsNames && namedImportsNames.length > 0;
|
|
201
|
+
|
|
202
|
+
if (!hasDefault && !hasNamed) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const defaultImport = hasDefault ? ts.factory.createIdentifier(defaultImportName) : undefined;
|
|
207
|
+
|
|
208
|
+
const namedImports = hasNamed
|
|
209
|
+
? ts.factory.createNamedImports(
|
|
210
|
+
namedImportsNames.map((name) =>
|
|
211
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(name))
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
: undefined;
|
|
215
|
+
|
|
216
|
+
return [
|
|
217
|
+
ts.factory.createImportDeclaration(
|
|
218
|
+
undefined,
|
|
219
|
+
ts.factory.createImportClause(undefined, defaultImport, namedImports),
|
|
220
|
+
ts.factory.createStringLiteral(importFromName)
|
|
221
|
+
),
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function createParameter({
|
|
226
|
+
modifiers,
|
|
227
|
+
name,
|
|
228
|
+
type,
|
|
229
|
+
questionToken,
|
|
230
|
+
dotDotDotToken,
|
|
231
|
+
initializer,
|
|
232
|
+
}: {
|
|
233
|
+
modifiers?: ts.Modifier[];
|
|
234
|
+
name: string | ts.BindingName;
|
|
235
|
+
type?: ts.TypeNode;
|
|
236
|
+
questionToken?: ts.QuestionToken;
|
|
237
|
+
dotDotDotToken?: ts.DotDotDotToken;
|
|
238
|
+
initializer?: ts.Expression;
|
|
239
|
+
}): ts.ParameterDeclaration {
|
|
240
|
+
return ts.factory.createParameterDeclaration(
|
|
241
|
+
modifiers,
|
|
242
|
+
dotDotDotToken,
|
|
243
|
+
name,
|
|
244
|
+
questionToken,
|
|
245
|
+
type,
|
|
246
|
+
initializer
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createProperty({
|
|
251
|
+
modifiers,
|
|
252
|
+
name,
|
|
253
|
+
typeNode,
|
|
254
|
+
initializer,
|
|
255
|
+
optional,
|
|
256
|
+
}: {
|
|
257
|
+
modifiers?: ts.Modifier[];
|
|
258
|
+
name: string | ts.PropertyName;
|
|
259
|
+
typeNode?: ts.TypeNode;
|
|
260
|
+
initializer?: ts.Expression;
|
|
261
|
+
optional?: boolean;
|
|
262
|
+
}): ts.PropertyDeclaration {
|
|
263
|
+
return ts.factory.createPropertyDeclaration(
|
|
264
|
+
modifiers,
|
|
265
|
+
name,
|
|
266
|
+
optional ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
|
|
267
|
+
typeNode,
|
|
268
|
+
initializer
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function createPropertySignature({
|
|
273
|
+
name,
|
|
274
|
+
typeNode,
|
|
275
|
+
optional,
|
|
276
|
+
modifiers,
|
|
277
|
+
}: {
|
|
278
|
+
name: string | ts.PropertyName;
|
|
279
|
+
typeNode?: ts.TypeNode;
|
|
280
|
+
optional?: boolean;
|
|
281
|
+
modifiers?: ts.Modifier[];
|
|
282
|
+
}): ts.PropertySignature {
|
|
283
|
+
return ts.factory.createPropertySignature(
|
|
284
|
+
modifiers,
|
|
285
|
+
name,
|
|
286
|
+
optional ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
|
|
287
|
+
typeNode
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function createCall({
|
|
292
|
+
expression,
|
|
293
|
+
args,
|
|
294
|
+
typeArgs,
|
|
295
|
+
}: {
|
|
296
|
+
expression: string | ts.Expression;
|
|
297
|
+
args?: ts.Expression[];
|
|
298
|
+
typeArgs?: ts.TypeNode[];
|
|
299
|
+
}): ts.CallExpression {
|
|
300
|
+
return ts.factory.createCallExpression(
|
|
301
|
+
typeof expression === 'string' ? ts.factory.createIdentifier(expression) : expression,
|
|
302
|
+
typeArgs,
|
|
303
|
+
args
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function createRequireNativeModuleExpression({
|
|
308
|
+
moduleType,
|
|
309
|
+
moduleName,
|
|
310
|
+
}: {
|
|
311
|
+
moduleType?: string;
|
|
312
|
+
moduleName: string;
|
|
313
|
+
}) {
|
|
314
|
+
return ts.factory.createCallExpression(
|
|
315
|
+
ts.factory.createIdentifier('requireNativeModule'),
|
|
316
|
+
moduleType ? [ts.factory.createTypeReferenceNode(moduleType)] : undefined,
|
|
317
|
+
[ts.factory.createStringLiteral(moduleName)]
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function createExportDefaultAsDeclaration({
|
|
322
|
+
exportAsName,
|
|
323
|
+
importFromName,
|
|
324
|
+
}: {
|
|
325
|
+
exportAsName: string;
|
|
326
|
+
importFromName: string;
|
|
327
|
+
}): ts.Node[] {
|
|
328
|
+
return [
|
|
329
|
+
ts.factory.createExportDeclaration(
|
|
330
|
+
undefined,
|
|
331
|
+
false,
|
|
332
|
+
ts.factory.createNamedExports([
|
|
333
|
+
ts.factory.createExportSpecifier(
|
|
334
|
+
false,
|
|
335
|
+
ts.factory.createIdentifier('default'),
|
|
336
|
+
ts.factory.createIdentifier(exportAsName)
|
|
337
|
+
),
|
|
338
|
+
]),
|
|
339
|
+
ts.factory.createStringLiteral(importFromName)
|
|
340
|
+
),
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function createTypeAlias({
|
|
345
|
+
exported,
|
|
346
|
+
alias,
|
|
347
|
+
typeParams,
|
|
348
|
+
type,
|
|
349
|
+
}: {
|
|
350
|
+
exported?: boolean;
|
|
351
|
+
alias: string;
|
|
352
|
+
typeParams?: ts.TypeParameterDeclaration[];
|
|
353
|
+
type: ts.TypeNode;
|
|
354
|
+
}) {
|
|
355
|
+
return ts.factory.createTypeAliasDeclaration(
|
|
356
|
+
constructModifiersArray({ exported }),
|
|
357
|
+
alias,
|
|
358
|
+
typeParams,
|
|
359
|
+
type
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function createRequireNativeViewDeclaration(module: ModuleClassDeclaration, view: ViewDeclaration) {
|
|
364
|
+
return [
|
|
365
|
+
createParameter({
|
|
366
|
+
modifiers: [constModifier()],
|
|
367
|
+
name: view.name,
|
|
368
|
+
initializer: createCall({
|
|
369
|
+
expression: 'requireNativeView',
|
|
370
|
+
typeArgs: [ts.factory.createTypeReferenceNode(getViewPropsTypeName(view))],
|
|
371
|
+
args: [
|
|
372
|
+
ts.factory.createStringLiteral(module.name),
|
|
373
|
+
ts.factory.createStringLiteral(view.name),
|
|
374
|
+
],
|
|
375
|
+
}),
|
|
376
|
+
}),
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function createExportAllDeclaration({
|
|
381
|
+
importFromName,
|
|
382
|
+
justTypes,
|
|
383
|
+
}: {
|
|
384
|
+
importFromName: string;
|
|
385
|
+
justTypes?: boolean;
|
|
386
|
+
}): ts.Node[] {
|
|
387
|
+
return [
|
|
388
|
+
ts.factory.createExportDeclaration(
|
|
389
|
+
undefined,
|
|
390
|
+
justTypes ?? false,
|
|
391
|
+
undefined,
|
|
392
|
+
ts.factory.createStringLiteral(importFromName)
|
|
393
|
+
),
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function createExportDefault(name: string = '_default'): ts.Node[] {
|
|
398
|
+
return [ts.factory.createExportDefault(ts.factory.createIdentifier(name))];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function createComponentType(propsTypeName: string) {
|
|
402
|
+
return ts.factory.createTypeReferenceNode('React.JSXElementConstructor', [
|
|
403
|
+
ts.factory.createTypeReferenceNode(propsTypeName),
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function getMissingTypeIdentifiers(fileTypeInformation: FileTypeInformation): Set<string> {
|
|
408
|
+
return fileTypeInformation.usedTypeIdentifiers
|
|
409
|
+
.difference(fileTypeInformation.declaredTypeIdentifiers)
|
|
410
|
+
.difference(getBasicTypesIdentifiers());
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function buildPropsMembers({ props, events }: ViewDeclaration): ts.TypeElement[] {
|
|
414
|
+
const buildEventPropertySignature = (eventDeclaration: EventDeclaration) => {
|
|
415
|
+
const name = eventDeclaration;
|
|
416
|
+
const typeNode = ts.factory.createFunctionTypeNode(
|
|
417
|
+
undefined,
|
|
418
|
+
[createParameter({ name: 'event', type: anyKeywordType() })],
|
|
419
|
+
voidKeywordType()
|
|
420
|
+
);
|
|
421
|
+
// TODO(@HubertBer) check whether we have ways of making events not optional
|
|
422
|
+
return createPropertySignature({ name, typeNode, optional: true });
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const buildPropPropertySignature = (propDeclaration: PropDeclaration) => {
|
|
426
|
+
const propTypeArgument = propDeclaration.arguments[1]?.type;
|
|
427
|
+
if (!propDeclaration || !propDeclaration.arguments || !propTypeArgument) {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
const name = propDeclaration.name;
|
|
431
|
+
const typeNode = mapTypeToTsTypeNode(propTypeArgument);
|
|
432
|
+
return createPropertySignature({ name, typeNode });
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
return [
|
|
436
|
+
...(props.map(buildPropPropertySignature).filter((p) => p) as ts.PropertySignature[]),
|
|
437
|
+
...events.map(buildEventPropertySignature),
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function buildViewPropsInterface(
|
|
442
|
+
view: ViewDeclaration | null,
|
|
443
|
+
options: { exported?: boolean }
|
|
444
|
+
): ts.Node[] {
|
|
445
|
+
if (!view) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
return [
|
|
449
|
+
ts.factory.createInterfaceDeclaration(
|
|
450
|
+
constructModifiersArray(options),
|
|
451
|
+
getViewPropsTypeName(view),
|
|
452
|
+
undefined,
|
|
453
|
+
[
|
|
454
|
+
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
|
455
|
+
ts.factory.createExpressionWithTypeArguments(
|
|
456
|
+
ts.factory.createIdentifier('ViewProps'),
|
|
457
|
+
undefined
|
|
458
|
+
),
|
|
459
|
+
]),
|
|
460
|
+
],
|
|
461
|
+
buildPropsMembers(view)
|
|
462
|
+
),
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function buildClassProperty(declaration: PropertyDeclaration): ts.PropertyDeclaration {
|
|
467
|
+
return createProperty({
|
|
468
|
+
modifiers: [readonlyModifier()],
|
|
469
|
+
name: declaration.name,
|
|
470
|
+
typeNode: mapTypeToTsTypeNode(declaration.type),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function buildNativeModuleClassDeclaration({
|
|
475
|
+
moduleClassDeclaration,
|
|
476
|
+
exportedModuleName,
|
|
477
|
+
}: {
|
|
478
|
+
moduleClassDeclaration: ModuleClassDeclaration;
|
|
479
|
+
exportedModuleName?: string;
|
|
480
|
+
}): ts.Node[] {
|
|
481
|
+
const buildClassTypeProperty = (classDeclaration: ClassDeclaration) =>
|
|
482
|
+
createProperty({
|
|
483
|
+
// TODO(@HubertBer): that's a hack, but I couldn't find a proper way to do this
|
|
484
|
+
// The problem is that declare class semantics seem somewhat different than class semantics.
|
|
485
|
+
name: classDeclaration.name,
|
|
486
|
+
typeNode: ts.factory.createTypeQueryNode(ts.factory.createIdentifier(classDeclaration.name)),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const buildSyncMethod = (functionDeclaration: FunctionDeclaration): ts.MethodDeclaration =>
|
|
490
|
+
buildFunction({
|
|
491
|
+
functionDeclaration,
|
|
492
|
+
method: true,
|
|
493
|
+
declaration: true,
|
|
494
|
+
}) as ts.MethodDeclaration;
|
|
495
|
+
|
|
496
|
+
const buildAsyncMethod = (functionDeclaration: FunctionDeclaration): ts.MethodDeclaration =>
|
|
497
|
+
buildFunction({
|
|
498
|
+
functionDeclaration,
|
|
499
|
+
async: true,
|
|
500
|
+
method: true,
|
|
501
|
+
declaration: true,
|
|
502
|
+
}) as ts.MethodDeclaration;
|
|
503
|
+
|
|
504
|
+
return [
|
|
505
|
+
ts.factory.createClassDeclaration(
|
|
506
|
+
[exportModifier(), declareModifier()],
|
|
507
|
+
exportedModuleName ?? `${moduleClassDeclaration.name}NativeModuleType`,
|
|
508
|
+
undefined,
|
|
509
|
+
[
|
|
510
|
+
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
|
511
|
+
ts.factory.createExpressionWithTypeArguments(
|
|
512
|
+
ts.factory.createIdentifier('NativeModule'),
|
|
513
|
+
undefined
|
|
514
|
+
),
|
|
515
|
+
]),
|
|
516
|
+
],
|
|
517
|
+
[
|
|
518
|
+
...moduleClassDeclaration.constants.map(buildClassProperty),
|
|
519
|
+
...moduleClassDeclaration.properties.map(buildClassProperty),
|
|
520
|
+
...moduleClassDeclaration.functions.map(buildSyncMethod),
|
|
521
|
+
...moduleClassDeclaration.asyncFunctions.map(buildAsyncMethod),
|
|
522
|
+
...moduleClassDeclaration.classes.map(buildClassTypeProperty),
|
|
523
|
+
]
|
|
524
|
+
),
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function buildArgumentDeclarationAndName(arg: Argument): {
|
|
529
|
+
argDeclaration: ts.ParameterDeclaration;
|
|
530
|
+
argName: string;
|
|
531
|
+
} {
|
|
532
|
+
const argName = arg.name ?? '_' + getNextFreeId();
|
|
533
|
+
const argDeclaration = createParameter({
|
|
534
|
+
name: argName,
|
|
535
|
+
type: mapTypeToTsTypeNode(arg.type),
|
|
536
|
+
});
|
|
537
|
+
return { argDeclaration, argName };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function buildArgumentDeclaration(arg: Argument): ts.ParameterDeclaration {
|
|
541
|
+
return buildArgumentDeclarationAndName(arg).argDeclaration;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export type buildFunctionOptions = {
|
|
545
|
+
functionDeclaration: FunctionDeclaration;
|
|
546
|
+
async?: boolean;
|
|
547
|
+
method?: boolean;
|
|
548
|
+
exported?: boolean;
|
|
549
|
+
declaration?: boolean;
|
|
550
|
+
returnStatement?: null | ts.ReturnStatement[];
|
|
551
|
+
overrideArgumentDeclarations?: ts.ParameterDeclaration[];
|
|
552
|
+
omitReturnType?: boolean;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
export function buildFunction({
|
|
556
|
+
functionDeclaration,
|
|
557
|
+
async,
|
|
558
|
+
method,
|
|
559
|
+
exported,
|
|
560
|
+
declaration,
|
|
561
|
+
returnStatement,
|
|
562
|
+
overrideArgumentDeclarations,
|
|
563
|
+
omitReturnType,
|
|
564
|
+
}: buildFunctionOptions): ts.FunctionDeclaration | ts.MethodDeclaration {
|
|
565
|
+
const functionModifiers = constructModifiersArray({ exported, async: async && !declaration });
|
|
566
|
+
const customReturn = !!returnStatement;
|
|
567
|
+
const bareReturnTypeNode = mapTypeToTsTypeNode(functionDeclaration.returnType);
|
|
568
|
+
|
|
569
|
+
const wrapWithPromiseType = (typeNode: ts.TypeNode): ts.TypeNode =>
|
|
570
|
+
ts.factory.createTypeReferenceNode('Promise', [typeNode]);
|
|
571
|
+
|
|
572
|
+
let returnTypeNode: ts.TypeNode | undefined = async
|
|
573
|
+
? wrapWithPromiseType(bareReturnTypeNode)
|
|
574
|
+
: bareReturnTypeNode;
|
|
575
|
+
if (omitReturnType) {
|
|
576
|
+
returnTypeNode = undefined;
|
|
577
|
+
}
|
|
578
|
+
const argumentDeclarations =
|
|
579
|
+
overrideArgumentDeclarations ?? functionDeclaration.arguments.map(buildArgumentDeclaration);
|
|
580
|
+
|
|
581
|
+
if (method) {
|
|
582
|
+
return ts.factory.createMethodDeclaration(
|
|
583
|
+
functionModifiers,
|
|
584
|
+
undefined,
|
|
585
|
+
functionDeclaration.name,
|
|
586
|
+
undefined,
|
|
587
|
+
undefined,
|
|
588
|
+
argumentDeclarations,
|
|
589
|
+
returnTypeNode,
|
|
590
|
+
declaration ? undefined : ts.factory.createBlock(customReturn ? returnStatement : [])
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
return ts.factory.createFunctionDeclaration(
|
|
594
|
+
functionModifiers,
|
|
595
|
+
undefined,
|
|
596
|
+
functionDeclaration.name,
|
|
597
|
+
undefined,
|
|
598
|
+
argumentDeclarations,
|
|
599
|
+
returnTypeNode,
|
|
600
|
+
declaration ? undefined : ts.factory.createBlock(customReturn ? returnStatement : [])
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function buildConstructor(
|
|
605
|
+
constructor: ConstructorDeclaration,
|
|
606
|
+
declaration: boolean
|
|
607
|
+
): ts.ClassElement {
|
|
608
|
+
return ts.factory.createConstructorDeclaration(
|
|
609
|
+
undefined,
|
|
610
|
+
constructor.arguments.map(buildArgumentDeclaration),
|
|
611
|
+
declaration ? undefined : ts.factory.createBlock([])
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
type BuildClassOptions = {
|
|
616
|
+
classDeclaration: ClassDeclaration;
|
|
617
|
+
exported?: boolean;
|
|
618
|
+
declaration?: boolean;
|
|
619
|
+
getFunctionReturnBlock?: (functionDeclaration: FunctionDeclaration) => ts.ReturnStatement[];
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// TODO(@HubertBer): figure out what about inheritance, should or should not inherit SharedObject
|
|
623
|
+
export function buildClass({
|
|
624
|
+
classDeclaration,
|
|
625
|
+
exported,
|
|
626
|
+
declaration,
|
|
627
|
+
getFunctionReturnBlock,
|
|
628
|
+
}: BuildClassOptions): ts.ClassDeclaration {
|
|
629
|
+
const getReturnStatement = (method: FunctionDeclaration) =>
|
|
630
|
+
!declaration && getFunctionReturnBlock ? getFunctionReturnBlock(method) : null;
|
|
631
|
+
|
|
632
|
+
const buildMethod = (method: FunctionDeclaration, async?: boolean) =>
|
|
633
|
+
buildFunction({
|
|
634
|
+
functionDeclaration: method,
|
|
635
|
+
method: true,
|
|
636
|
+
async,
|
|
637
|
+
declaration,
|
|
638
|
+
returnStatement: getReturnStatement(method),
|
|
639
|
+
}) as ts.MethodDeclaration;
|
|
640
|
+
|
|
641
|
+
const classMembers = [
|
|
642
|
+
...classDeclaration.methods.map((m) => buildMethod(m)),
|
|
643
|
+
...classDeclaration.asyncMethods.map((m) => buildMethod(m, true)),
|
|
644
|
+
...(declaration ? classDeclaration.properties.map(buildClassProperty) : []),
|
|
645
|
+
classDeclaration.constructor
|
|
646
|
+
? buildConstructor(classDeclaration.constructor, declaration ?? false)
|
|
647
|
+
: undefined,
|
|
648
|
+
].filter((x) => x !== undefined);
|
|
649
|
+
|
|
650
|
+
return ts.factory.createClassDeclaration(
|
|
651
|
+
constructModifiersArray({ exported, declare: declaration }),
|
|
652
|
+
ts.factory.createIdentifier(classDeclaration.name),
|
|
653
|
+
undefined,
|
|
654
|
+
[],
|
|
655
|
+
classMembers
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function buildModuleDefaultExport({
|
|
660
|
+
moduleName,
|
|
661
|
+
moduleType,
|
|
662
|
+
declaration,
|
|
663
|
+
}: {
|
|
664
|
+
moduleName: string;
|
|
665
|
+
moduleType?: string;
|
|
666
|
+
declaration?: boolean;
|
|
667
|
+
}): ts.Node[] {
|
|
668
|
+
const name = '_default';
|
|
669
|
+
const type = moduleType ? ts.factory.createTypeReferenceNode(moduleType) : undefined;
|
|
670
|
+
return [
|
|
671
|
+
createParameter({
|
|
672
|
+
modifiers: [constModifier()],
|
|
673
|
+
name,
|
|
674
|
+
type,
|
|
675
|
+
initializer: declaration
|
|
676
|
+
? undefined
|
|
677
|
+
: createRequireNativeModuleExpression({ moduleName, moduleType }),
|
|
678
|
+
}),
|
|
679
|
+
ts.factory.createExportDefault(ts.factory.createIdentifier('_default')),
|
|
680
|
+
];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
export function buildUnknownTypeAlias(
|
|
684
|
+
identifier: string,
|
|
685
|
+
exported: boolean,
|
|
686
|
+
inferredTypeParametersCount: Map<string, number>
|
|
687
|
+
): ts.Statement {
|
|
688
|
+
const paramCount = inferredTypeParametersCount.get(identifier);
|
|
689
|
+
const typeParamsList = [];
|
|
690
|
+
for (let i = 0; i < (paramCount ?? 0); i += 1) {
|
|
691
|
+
typeParamsList.push(ts.factory.createTypeParameterDeclaration(undefined, 'T' + i));
|
|
692
|
+
}
|
|
693
|
+
const typeParams = (paramCount ?? 0) === 0 ? undefined : typeParamsList;
|
|
694
|
+
return createTypeAlias({ exported, alias: identifier, type: unknownKeywordType(), typeParams });
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function buildRecordTypeAlias(recordType: RecordType, exported: boolean): ts.Node {
|
|
698
|
+
return createTypeAlias({
|
|
699
|
+
exported,
|
|
700
|
+
alias: recordType.name,
|
|
701
|
+
type: ts.factory.createTypeLiteralNode(
|
|
702
|
+
recordType.fields.map((field) => {
|
|
703
|
+
const optional = field.type.kind === TypeKind.OPTIONAL;
|
|
704
|
+
const typeNode = mapTypeToTsTypeNode(optional ? (field.type.type as Type) : field.type);
|
|
705
|
+
const name = field.name ?? '_' + getNextFreeId();
|
|
706
|
+
return createPropertySignature({ name, optional, typeNode });
|
|
707
|
+
})
|
|
708
|
+
),
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function buildEnumTypeDeclaration(
|
|
713
|
+
enumType: EnumType,
|
|
714
|
+
exported: boolean,
|
|
715
|
+
declared: boolean
|
|
716
|
+
): ts.Node {
|
|
717
|
+
return ts.factory.createEnumDeclaration(
|
|
718
|
+
constructModifiersArray({ exported, declare: declared }),
|
|
719
|
+
enumType.name,
|
|
720
|
+
enumType.cases.map((enumcase) => ts.factory.createEnumMember(enumcase))
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function buildMissingTypesDeclarations(ctx: GenerationContext): ts.Node[] {
|
|
725
|
+
if (ctx.missingTypes.size === 0) {
|
|
726
|
+
return [];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const header = ts.addSyntheticLeadingComment(
|
|
730
|
+
ts.factory.createIdentifier(''),
|
|
731
|
+
ts.SyntaxKind.SingleLineCommentTrivia,
|
|
732
|
+
` These types haven't been defined in provided file(s).`,
|
|
733
|
+
true
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const aliases = [...ctx.missingTypes].map((identifier) =>
|
|
737
|
+
buildUnknownTypeAlias(identifier, true, ctx.fileInfo.inferredTypeParametersCount)
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
return [header, ...aliases];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function buildDefaultViewComponent({
|
|
744
|
+
componentName,
|
|
745
|
+
propsTypeAlias,
|
|
746
|
+
}: {
|
|
747
|
+
componentName: string;
|
|
748
|
+
propsTypeAlias: string;
|
|
749
|
+
}) {
|
|
750
|
+
const jsxElement = ts.factory.createJsxSelfClosingElement(
|
|
751
|
+
ts.factory.createIdentifier(componentName),
|
|
752
|
+
undefined,
|
|
753
|
+
ts.factory.createJsxAttributes([
|
|
754
|
+
ts.factory.createJsxSpreadAttribute(ts.factory.createIdentifier('props')),
|
|
755
|
+
])
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
const functionBody = ts.factory.createBlock([ts.factory.createReturnStatement(jsxElement)]);
|
|
759
|
+
|
|
760
|
+
return [
|
|
761
|
+
ts.factory.createFunctionExpression(
|
|
762
|
+
[exportModifier(), defaultModifier()],
|
|
763
|
+
undefined,
|
|
764
|
+
componentName + 'Component',
|
|
765
|
+
undefined,
|
|
766
|
+
[
|
|
767
|
+
createParameter({
|
|
768
|
+
name: 'props',
|
|
769
|
+
type: ts.factory.createTypeReferenceNode(propsTypeAlias),
|
|
770
|
+
}),
|
|
771
|
+
],
|
|
772
|
+
undefined,
|
|
773
|
+
functionBody
|
|
774
|
+
),
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export function buildExposedTypesDeclarations(
|
|
779
|
+
ctx: GenerationContext,
|
|
780
|
+
options: { exported?: boolean; declare?: boolean }
|
|
781
|
+
): ts.Node[] {
|
|
782
|
+
const recordDeclarationMap = (recordType: RecordType) =>
|
|
783
|
+
buildRecordTypeAlias(recordType, options.exported ?? false);
|
|
784
|
+
const enumDeclarationMap = (enumType: EnumType) =>
|
|
785
|
+
buildEnumTypeDeclaration(enumType, options.exported ?? false, options.declare ?? false);
|
|
786
|
+
const classDeclarationMap = (classDeclaration: ClassDeclaration) =>
|
|
787
|
+
buildClass({ classDeclaration, exported: true, declaration: true });
|
|
788
|
+
|
|
789
|
+
return joinTSNodesWithNewlines([
|
|
790
|
+
createImportDeclaration({ namedImportsNames: ['NativeModule'], importFromName: 'expo' }),
|
|
791
|
+
buildMissingTypesDeclarations(ctx),
|
|
792
|
+
ctx.fileInfo.records.flatMap(recordDeclarationMap),
|
|
793
|
+
ctx.fileInfo.enums.flatMap(enumDeclarationMap),
|
|
794
|
+
ctx.module.classes.map(classDeclarationMap),
|
|
795
|
+
]);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function buildModuleDeclarationNodes(ctx: GenerationContext): ts.Node[] {
|
|
799
|
+
return joinTSNodesWithNewlines([
|
|
800
|
+
createGeneratedPrefix(),
|
|
801
|
+
buildExposedTypesDeclarations(ctx, { exported: true }),
|
|
802
|
+
buildNativeModuleClassDeclaration({ moduleClassDeclaration: ctx.module }),
|
|
803
|
+
buildModuleDefaultExport({
|
|
804
|
+
moduleName: ctx.module.name,
|
|
805
|
+
moduleType: ctx.module.name,
|
|
806
|
+
declaration: true,
|
|
807
|
+
}),
|
|
808
|
+
] as ts.Node[][]);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export function getViewPropsTypeName(view: ViewDeclaration): string {
|
|
812
|
+
return view.name + (view.name.endsWith('View') ? 'Props' : 'ViewProps');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function buildViewDeclarationNodes(ctx: GenerationContext): ts.Node[] {
|
|
816
|
+
if (!ctx.view) {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
const viewComponentType = createComponentType(getViewPropsTypeName(ctx.view));
|
|
820
|
+
const modifiers = [declareModifier(), constModifier()];
|
|
821
|
+
return joinTSNodesWithNewlines([
|
|
822
|
+
createGeneratedPrefix(),
|
|
823
|
+
createImportDeclaration({ namedImportsNames: ['ViewProps'], importFromName: 'react-native' }),
|
|
824
|
+
buildMissingTypesDeclarations(ctx),
|
|
825
|
+
buildViewPropsInterface(ctx.view, {}),
|
|
826
|
+
[createParameter({ modifiers, name: '_default', type: viewComponentType })],
|
|
827
|
+
createExportDefault(),
|
|
828
|
+
]);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function buildJSXIntrinsicsViewNodes(ctx: GenerationContext): ts.Node[] {
|
|
832
|
+
const name = ctx.module.name;
|
|
833
|
+
const propsTypeNode = ctx.view
|
|
834
|
+
? ts.factory.createTypeLiteralNode(buildPropsMembers(ctx.view))
|
|
835
|
+
: undefined;
|
|
836
|
+
|
|
837
|
+
const jsxIntrinsicElementsNodes = [];
|
|
838
|
+
if (ctx.view) {
|
|
839
|
+
const globalIdentifier = ts.factory.createIdentifier('global');
|
|
840
|
+
const jsxIdentifier = ts.factory.createIdentifier('JSX');
|
|
841
|
+
const intrinsicElementsIdentifier = ts.factory.createIdentifier('IntrinsicElements');
|
|
842
|
+
jsxIntrinsicElementsNodes.push(
|
|
843
|
+
ts.factory.createModuleDeclaration(
|
|
844
|
+
[declareModifier()],
|
|
845
|
+
globalIdentifier,
|
|
846
|
+
ts.factory.createModuleBlock([
|
|
847
|
+
ts.factory.createModuleDeclaration(
|
|
848
|
+
undefined,
|
|
849
|
+
jsxIdentifier,
|
|
850
|
+
ts.factory.createModuleBlock([
|
|
851
|
+
ts.factory.createInterfaceDeclaration(
|
|
852
|
+
undefined,
|
|
853
|
+
intrinsicElementsIdentifier,
|
|
854
|
+
undefined,
|
|
855
|
+
undefined,
|
|
856
|
+
[createPropertySignature({ name, typeNode: propsTypeNode })]
|
|
857
|
+
),
|
|
858
|
+
]),
|
|
859
|
+
ts.NodeFlags.Namespace
|
|
860
|
+
),
|
|
861
|
+
]),
|
|
862
|
+
ts.NodeFlags.GlobalAugmentation
|
|
863
|
+
)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return joinTSNodesWithNewlines([
|
|
868
|
+
buildExposedTypesDeclarations(ctx, { declare: true }),
|
|
869
|
+
jsxIntrinsicElementsNodes,
|
|
870
|
+
]);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function buildNativeModuleGeneratedNodes(ctx: GenerationContext): ts.Node[] {
|
|
874
|
+
return joinTSNodesWithNewlines([
|
|
875
|
+
createGeneratedPrefix(),
|
|
876
|
+
createImportDeclaration({ namedImportsNames: ['ViewProps'], importFromName: 'react-native' }),
|
|
877
|
+
buildExposedTypesDeclarations(ctx, { exported: true }),
|
|
878
|
+
buildViewPropsInterface(ctx.view, { exported: true }),
|
|
879
|
+
buildNativeModuleClassDeclaration({ moduleClassDeclaration: ctx.module }),
|
|
880
|
+
]);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function buildStableNativeModuleInterface(ctx: GenerationContext): ts.Node[] {
|
|
884
|
+
const generatedModuleAlias = ctx.module.name;
|
|
885
|
+
const generatedModuleTypeAlias = `${ctx.module.name}NativeModuleType`;
|
|
886
|
+
const generatedFilePath = `./${ctx.module.name}.generated`;
|
|
887
|
+
|
|
888
|
+
const exportedFunctionReturnStatement = (
|
|
889
|
+
functionDeclaration: FunctionDeclaration,
|
|
890
|
+
overrideArguments?: ts.Identifier[]
|
|
891
|
+
) => {
|
|
892
|
+
const expression = `${generatedModuleAlias}.${functionDeclaration.name}`;
|
|
893
|
+
const args =
|
|
894
|
+
overrideArguments ??
|
|
895
|
+
functionDeclaration.arguments.map((arg) =>
|
|
896
|
+
ts.factory.createIdentifier(arg.name ?? 'unnamedArgument')
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
return ts.factory.createReturnStatement(
|
|
900
|
+
createCall({
|
|
901
|
+
expression,
|
|
902
|
+
args,
|
|
903
|
+
})
|
|
904
|
+
);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
const mapFunctionDeclarationTemplate =
|
|
908
|
+
(isAsync: boolean) => (functionDeclaration: FunctionDeclaration) => {
|
|
909
|
+
const argumentDeclarations = [];
|
|
910
|
+
const argumentNames = [];
|
|
911
|
+
for (const arg of functionDeclaration.arguments) {
|
|
912
|
+
const { argDeclaration, argName } = buildArgumentDeclarationAndName(arg);
|
|
913
|
+
argumentDeclarations.push(argDeclaration);
|
|
914
|
+
argumentNames.push(argName);
|
|
915
|
+
}
|
|
916
|
+
return [
|
|
917
|
+
buildFunction({
|
|
918
|
+
functionDeclaration,
|
|
919
|
+
async: isAsync,
|
|
920
|
+
exported: true,
|
|
921
|
+
returnStatement: [
|
|
922
|
+
exportedFunctionReturnStatement(
|
|
923
|
+
functionDeclaration,
|
|
924
|
+
argumentNames.map(ts.factory.createIdentifier)
|
|
925
|
+
),
|
|
926
|
+
],
|
|
927
|
+
overrideArgumentDeclarations: argumentDeclarations,
|
|
928
|
+
omitReturnType: true,
|
|
929
|
+
}),
|
|
930
|
+
];
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const mapSyncFunctionDeclaration = mapFunctionDeclarationTemplate(false);
|
|
934
|
+
const mapAsyncFunctionDeclaration = mapFunctionDeclarationTemplate(true);
|
|
935
|
+
const buildConstantExportProperty = (constant: ConstantDeclaration): ts.Node => {
|
|
936
|
+
const typeNode = mapTypeToTsTypeNode(constant.type);
|
|
937
|
+
const modifiers = [exportModifier(), constModifier()];
|
|
938
|
+
const initializer = ts.factory.createIdentifier(`${generatedModuleAlias}.${constant.name}`);
|
|
939
|
+
return createProperty({ modifiers, name: constant.name, typeNode, initializer });
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
return joinTSNodesWithNewlines([
|
|
943
|
+
ctx.view
|
|
944
|
+
? createImportDeclaration({ importFromName: 'react', defaultImportName: 'React' })
|
|
945
|
+
: [],
|
|
946
|
+
|
|
947
|
+
createImportDeclaration({
|
|
948
|
+
namedImportsNames: [
|
|
949
|
+
...ctx.fileInfo.usedTypeIdentifiers.difference(getBasicTypesIdentifiers()),
|
|
950
|
+
...[generatedModuleTypeAlias, ctx.view ? getViewPropsTypeName(ctx.view) : null].filter(
|
|
951
|
+
(v) => v !== null
|
|
952
|
+
),
|
|
953
|
+
],
|
|
954
|
+
importFromName: generatedFilePath,
|
|
955
|
+
}),
|
|
956
|
+
|
|
957
|
+
createImportDeclaration({
|
|
958
|
+
namedImportsNames: ['requireNativeModule', 'requireNativeView'],
|
|
959
|
+
importFromName: 'expo',
|
|
960
|
+
}),
|
|
961
|
+
|
|
962
|
+
[
|
|
963
|
+
createParameter({
|
|
964
|
+
modifiers: [constModifier()],
|
|
965
|
+
name: ctx.module.name,
|
|
966
|
+
type: ts.factory.createTypeReferenceNode(generatedModuleTypeAlias),
|
|
967
|
+
initializer: createRequireNativeModuleExpression({
|
|
968
|
+
moduleName: ctx.module.name,
|
|
969
|
+
moduleType: generatedModuleTypeAlias,
|
|
970
|
+
}),
|
|
971
|
+
}),
|
|
972
|
+
],
|
|
973
|
+
|
|
974
|
+
ctx.view ? createRequireNativeViewDeclaration(ctx.module, ctx.view) : [],
|
|
975
|
+
|
|
976
|
+
ctx.module.constants.map(buildConstantExportProperty),
|
|
977
|
+
|
|
978
|
+
ctx.module.functions.flatMap(mapSyncFunctionDeclaration),
|
|
979
|
+
ctx.module.asyncFunctions.flatMap(mapAsyncFunctionDeclaration),
|
|
980
|
+
|
|
981
|
+
ctx.view
|
|
982
|
+
? buildDefaultViewComponent({
|
|
983
|
+
componentName: ctx.view.name,
|
|
984
|
+
propsTypeAlias: getViewPropsTypeName(ctx.view),
|
|
985
|
+
})
|
|
986
|
+
: [],
|
|
987
|
+
]);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async function tsNodesToString(elements: ts.Node[]): Promise<string> {
|
|
991
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
992
|
+
const resultFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TSX);
|
|
993
|
+
const viewTypes = ts.factory.createNodeArray(elements);
|
|
994
|
+
const printedTs = printer.printList(
|
|
995
|
+
ts.ListFormat.MultiLine | ts.ListFormat.PreserveLines,
|
|
996
|
+
viewTypes,
|
|
997
|
+
resultFile
|
|
998
|
+
);
|
|
999
|
+
return await prettifyCode(printedTs, 'typescript');
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export async function prettifyCode(text: string, parser: 'babel' | 'typescript' = 'babel') {
|
|
1003
|
+
return await prettier.format(text, {
|
|
1004
|
+
parser,
|
|
1005
|
+
tabWidth: 2,
|
|
1006
|
+
printWidth: 100,
|
|
1007
|
+
trailingComma: 'none',
|
|
1008
|
+
singleQuote: true,
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
export async function generateViewTypesFileContent(
|
|
1013
|
+
fileTypeInformation: FileTypeInformation
|
|
1014
|
+
): Promise<string | null> {
|
|
1015
|
+
const ctx = createDefaultGenerationContext(fileTypeInformation);
|
|
1016
|
+
if (!ctx) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
return tsNodesToString(buildViewDeclarationNodes(ctx));
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export async function generateJSXIntrinsicsFileContent(
|
|
1023
|
+
fileTypeInformation: FileTypeInformation
|
|
1024
|
+
): Promise<string | null> {
|
|
1025
|
+
const ctx = createDefaultGenerationContext(fileTypeInformation);
|
|
1026
|
+
if (!ctx) {
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
return tsNodesToString(buildJSXIntrinsicsViewNodes(ctx));
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
export async function generateModuleTypesFileContent(
|
|
1033
|
+
fileTypeInformation: FileTypeInformation
|
|
1034
|
+
): Promise<string | null> {
|
|
1035
|
+
const ctx = createDefaultGenerationContext(fileTypeInformation);
|
|
1036
|
+
if (!ctx) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
return tsNodesToString(buildModuleDeclarationNodes(ctx));
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export async function generateConciseTsInterface(
|
|
1043
|
+
fileTypeInformation: FileTypeInformation
|
|
1044
|
+
): Promise<{
|
|
1045
|
+
volatileGeneratedFileContent: string;
|
|
1046
|
+
moduleTypescriptInterfaceFileContent: string;
|
|
1047
|
+
}> {
|
|
1048
|
+
const ctx = createDefaultGenerationContext(fileTypeInformation);
|
|
1049
|
+
if (!ctx) {
|
|
1050
|
+
return { volatileGeneratedFileContent: '', moduleTypescriptInterfaceFileContent: '' };
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const volatileGeneratedFileContent = await tsNodesToString(buildNativeModuleGeneratedNodes(ctx));
|
|
1054
|
+
|
|
1055
|
+
const moduleTypescriptInterfaceFileContent = await tsNodesToString(
|
|
1056
|
+
buildStableNativeModuleInterface(ctx)
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
return {
|
|
1060
|
+
volatileGeneratedFileContent,
|
|
1061
|
+
moduleTypescriptInterfaceFileContent,
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export async function generateFullTsInterface(fileTypeInformation: FileTypeInformation): Promise<{
|
|
1066
|
+
moduleTypesFile: OutputFile;
|
|
1067
|
+
moduleViewsFiles: OutputFile[];
|
|
1068
|
+
moduleNativeFile: OutputFile;
|
|
1069
|
+
indexFile: OutputFile;
|
|
1070
|
+
} | null> {
|
|
1071
|
+
const ctx = createDefaultGenerationContext(fileTypeInformation);
|
|
1072
|
+
if (!ctx) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const moduleNativeFileImportName = `${ctx?.module.name}Module`;
|
|
1077
|
+
const moduleTypesFileImportName = `${ctx?.module.name}.types`;
|
|
1078
|
+
const moduleViewsFilesImportNames: string[] = [];
|
|
1079
|
+
const moduleTypesFileNodes = joinTSNodesWithNewlines([
|
|
1080
|
+
createGeneratedPrefix(),
|
|
1081
|
+
createImportDeclaration({ namedImportsNames: ['ViewProps'], importFromName: 'react-native' }),
|
|
1082
|
+
buildExposedTypesDeclarations(ctx, { exported: true }),
|
|
1083
|
+
...ctx.module.views.map((view) => buildViewPropsInterface(view, { exported: true })),
|
|
1084
|
+
]);
|
|
1085
|
+
|
|
1086
|
+
const moduleViewFilesNodes = [];
|
|
1087
|
+
for (const view of ctx.module.views) {
|
|
1088
|
+
const moduleViewFileNodes = joinTSNodesWithNewlines([
|
|
1089
|
+
createGeneratedPrefix(),
|
|
1090
|
+
createImportDeclaration({
|
|
1091
|
+
namedImportsNames: ['requireNativeView'],
|
|
1092
|
+
importFromName: 'expo',
|
|
1093
|
+
}),
|
|
1094
|
+
createImportDeclaration({
|
|
1095
|
+
namedImportsNames: [getViewPropsTypeName(view)],
|
|
1096
|
+
importFromName: `./${moduleTypesFileImportName}`,
|
|
1097
|
+
}),
|
|
1098
|
+
createRequireNativeViewDeclaration(ctx.module, view),
|
|
1099
|
+
|
|
1100
|
+
buildDefaultViewComponent({
|
|
1101
|
+
componentName: view.name,
|
|
1102
|
+
propsTypeAlias: getViewPropsTypeName(view),
|
|
1103
|
+
}),
|
|
1104
|
+
]);
|
|
1105
|
+
moduleViewFilesNodes.push(moduleViewFileNodes);
|
|
1106
|
+
moduleViewsFilesImportNames.push(`${view.name}View`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const moduleNativeModuleNodes = joinTSNodesWithNewlines([
|
|
1110
|
+
createGeneratedPrefix(),
|
|
1111
|
+
createImportDeclaration({
|
|
1112
|
+
namedImportsNames: ['requireNativeModule', 'NativeModule'],
|
|
1113
|
+
importFromName: 'expo',
|
|
1114
|
+
}),
|
|
1115
|
+
createImportDeclaration({
|
|
1116
|
+
namedImportsNames: [...getAllNonBasicTypes(ctx.fileInfo)],
|
|
1117
|
+
importFromName: `./${moduleTypesFileImportName}`,
|
|
1118
|
+
}),
|
|
1119
|
+
buildNativeModuleClassDeclaration({
|
|
1120
|
+
moduleClassDeclaration: ctx.module,
|
|
1121
|
+
exportedModuleName: ctx.module.name,
|
|
1122
|
+
}),
|
|
1123
|
+
buildModuleDefaultExport({ moduleName: ctx.module.name, moduleType: ctx.module.name }),
|
|
1124
|
+
]);
|
|
1125
|
+
|
|
1126
|
+
const indexFileNodes = joinTSNodesWithNewlines([
|
|
1127
|
+
createGeneratedPrefix(),
|
|
1128
|
+
createExportAllDeclaration({
|
|
1129
|
+
importFromName: `./${moduleTypesFileImportName}`,
|
|
1130
|
+
justTypes: true,
|
|
1131
|
+
}),
|
|
1132
|
+
createExportDefaultAsDeclaration({
|
|
1133
|
+
exportAsName: ctx.module.name,
|
|
1134
|
+
importFromName: `./${moduleNativeFileImportName}`,
|
|
1135
|
+
}),
|
|
1136
|
+
|
|
1137
|
+
...ctx.module.views.map((view, idx) =>
|
|
1138
|
+
createExportDefaultAsDeclaration({
|
|
1139
|
+
exportAsName: view.name,
|
|
1140
|
+
importFromName: `./${moduleViewsFilesImportNames[idx]}`,
|
|
1141
|
+
})
|
|
1142
|
+
),
|
|
1143
|
+
]);
|
|
1144
|
+
|
|
1145
|
+
const [
|
|
1146
|
+
moduleTypesFileContent,
|
|
1147
|
+
moduleViewFilesContents,
|
|
1148
|
+
moduleNativeFileContent,
|
|
1149
|
+
indexFileContent,
|
|
1150
|
+
] = await Promise.all([
|
|
1151
|
+
tsNodesToString(moduleTypesFileNodes),
|
|
1152
|
+
Promise.all(moduleViewFilesNodes.map(tsNodesToString)),
|
|
1153
|
+
tsNodesToString(moduleNativeModuleNodes),
|
|
1154
|
+
tsNodesToString(indexFileNodes),
|
|
1155
|
+
]);
|
|
1156
|
+
|
|
1157
|
+
const moduleTypesFile = {
|
|
1158
|
+
content: moduleTypesFileContent,
|
|
1159
|
+
name: `${moduleTypesFileImportName}.ts`,
|
|
1160
|
+
};
|
|
1161
|
+
const moduleViewsFiles = moduleViewFilesContents.map((moduleViewFileContent, idx) => {
|
|
1162
|
+
return {
|
|
1163
|
+
content: moduleViewFileContent,
|
|
1164
|
+
name: `${moduleViewsFilesImportNames[idx]}.tsx`,
|
|
1165
|
+
};
|
|
1166
|
+
});
|
|
1167
|
+
const moduleNativeFile = {
|
|
1168
|
+
content: moduleNativeFileContent,
|
|
1169
|
+
name: `${moduleNativeFileImportName}.ts`,
|
|
1170
|
+
};
|
|
1171
|
+
const indexFile = { content: indexFileContent, name: `index.ts` };
|
|
1172
|
+
|
|
1173
|
+
return {
|
|
1174
|
+
moduleTypesFile,
|
|
1175
|
+
moduleViewsFiles,
|
|
1176
|
+
moduleNativeFile,
|
|
1177
|
+
indexFile,
|
|
1178
|
+
};
|
|
1179
|
+
}
|