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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/bin/cli.js +2 -0
  4. package/build/cli.d.ts +1 -0
  5. package/build/cli.js +35 -0
  6. package/build/cli.js.map +1 -0
  7. package/build/commands/commandUtils.d.ts +46 -0
  8. package/build/commands/commandUtils.js +274 -0
  9. package/build/commands/commandUtils.js.map +1 -0
  10. package/build/commands/generateJSXIntrinsicsCommand.d.ts +2 -0
  11. package/build/commands/generateJSXIntrinsicsCommand.js +28 -0
  12. package/build/commands/generateJSXIntrinsicsCommand.js.map +1 -0
  13. package/build/commands/generateMocksForFileCommand.d.ts +2 -0
  14. package/build/commands/generateMocksForFileCommand.js +22 -0
  15. package/build/commands/generateMocksForFileCommand.js.map +1 -0
  16. package/build/commands/generateModuleTypesCommand.d.ts +2 -0
  17. package/build/commands/generateModuleTypesCommand.js +32 -0
  18. package/build/commands/generateModuleTypesCommand.js.map +1 -0
  19. package/build/commands/generateViewTypesCommand.d.ts +2 -0
  20. package/build/commands/generateViewTypesCommand.js +28 -0
  21. package/build/commands/generateViewTypesCommand.js.map +1 -0
  22. package/build/commands/inlineModulesInterfaceCommand.d.ts +2 -0
  23. package/build/commands/inlineModulesInterfaceCommand.js +129 -0
  24. package/build/commands/inlineModulesInterfaceCommand.js.map +1 -0
  25. package/build/commands/moduleInterfaceCommand.d.ts +2 -0
  26. package/build/commands/moduleInterfaceCommand.js +46 -0
  27. package/build/commands/moduleInterfaceCommand.js.map +1 -0
  28. package/build/commands/shortModuleInterfaceCommand.d.ts +2 -0
  29. package/build/commands/shortModuleInterfaceCommand.js +17 -0
  30. package/build/commands/shortModuleInterfaceCommand.js.map +1 -0
  31. package/build/commands/typeInformationCommand.d.ts +2 -0
  32. package/build/commands/typeInformationCommand.js +24 -0
  33. package/build/commands/typeInformationCommand.js.map +1 -0
  34. package/build/index.d.ts +3 -0
  35. package/build/index.js +28 -0
  36. package/build/index.js.map +1 -0
  37. package/build/mockgen.d.ts +4 -0
  38. package/build/mockgen.js +204 -0
  39. package/build/mockgen.js.map +1 -0
  40. package/build/swift/sourcekittenTypeInformation.d.ts +6 -0
  41. package/build/swift/sourcekittenTypeInformation.js +875 -0
  42. package/build/swift/sourcekittenTypeInformation.js.map +1 -0
  43. package/build/typeInformation.d.ts +209 -0
  44. package/build/typeInformation.js +157 -0
  45. package/build/typeInformation.js.map +1 -0
  46. package/build/types.d.ts +59 -0
  47. package/build/types.js +3 -0
  48. package/build/types.js.map +1 -0
  49. package/build/typescriptGeneration.d.ts +61 -0
  50. package/build/typescriptGeneration.js +696 -0
  51. package/build/typescriptGeneration.js.map +1 -0
  52. package/build/utils.d.ts +6 -0
  53. package/build/utils.js +44 -0
  54. package/build/utils.js.map +1 -0
  55. package/jest.config.js +6 -0
  56. package/package.json +46 -5
  57. package/src/cli.ts +38 -0
  58. package/src/commands/commandUtils.ts +352 -0
  59. package/src/commands/generateJSXIntrinsicsCommand.ts +38 -0
  60. package/src/commands/generateMocksForFileCommand.ts +30 -0
  61. package/src/commands/generateModuleTypesCommand.ts +39 -0
  62. package/src/commands/generateViewTypesCommand.ts +39 -0
  63. package/src/commands/inlineModulesInterfaceCommand.ts +175 -0
  64. package/src/commands/moduleInterfaceCommand.ts +56 -0
  65. package/src/commands/shortModuleInterfaceCommand.ts +26 -0
  66. package/src/commands/typeInformationCommand.ts +35 -0
  67. package/src/index.ts +9 -0
  68. package/src/mockgen.ts +338 -0
  69. package/src/swift/sourcekittenTypeInformation.ts +1173 -0
  70. package/src/typeInformation.ts +326 -0
  71. package/src/types.ts +68 -0
  72. package/src/typescriptGeneration.ts +1179 -0
  73. package/src/utils.ts +44 -0
  74. package/tests/TestModule.swift +175 -0
  75. package/tests/__snapshots__/typeInformation.test.ts.snap +1578 -0
  76. package/tests/typeInformation.test.ts +134 -0
  77. package/tsconfig.json +11 -0
package/src/mockgen.ts ADDED
@@ -0,0 +1,338 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import ts from 'typescript';
4
+
5
+ import {
6
+ BasicType,
7
+ ClassDeclaration,
8
+ EnumType,
9
+ FileTypeInformation,
10
+ FunctionDeclaration,
11
+ getFileTypeInformation,
12
+ IdentifierKind,
13
+ ModuleClassDeclaration,
14
+ OptionalType,
15
+ RecordType,
16
+ SumType,
17
+ Type,
18
+ TypeKind,
19
+ ViewDeclaration,
20
+ } from './typeInformation';
21
+ import {
22
+ getBasicTypesIdentifiers,
23
+ buildEnumTypeDeclaration,
24
+ buildViewPropsInterface,
25
+ buildRecordTypeAlias,
26
+ buildClass,
27
+ buildFunction,
28
+ getViewPropsTypeName,
29
+ joinTSNodesWithNewlines,
30
+ prettifyCode,
31
+ buildUnknownTypeAlias,
32
+ } from './typescriptGeneration';
33
+ import { taskAll } from './utils';
34
+
35
+ const prefix = `Automatically generated by expo-type-information.
36
+
37
+ This autogenerated file provides a mock for native Expo module,
38
+ and works out of the box with the expo jest preset.
39
+ `;
40
+
41
+ function getPrefix() {
42
+ return [ts.factory.createJSDocComment(prefix)];
43
+ }
44
+
45
+ let freeId = 0;
46
+
47
+ function getNextFreeId() {
48
+ freeId += 1;
49
+ return freeId;
50
+ }
51
+
52
+ function maybeWrapWithReturnStatement(
53
+ type: Type,
54
+ fileTypeInformation: FileTypeInformation
55
+ ): ts.ReturnStatement[] {
56
+ if (type.kind === TypeKind.BASIC) {
57
+ const basicType = type.type as BasicType;
58
+ if (basicType === BasicType.VOID || basicType === BasicType.ANY) {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ // TODO(@HubertBer): maybe add a comment when we couldn't create a mock for the return type
64
+ const returnExpression = getMockedValueForType(type, fileTypeInformation);
65
+ if (returnExpression || type.kind !== TypeKind.BASIC || type.type === BasicType.UNRESOLVED) {
66
+ return [ts.factory.createReturnStatement(returnExpression)];
67
+ }
68
+ return [];
69
+ }
70
+
71
+ function getBasicTypeMockLiteral(type: BasicType): ts.PrimaryExpression | undefined {
72
+ switch (type) {
73
+ case BasicType.STRING:
74
+ return ts.factory.createStringLiteral('');
75
+ case BasicType.BOOLEAN:
76
+ return ts.factory.createFalse();
77
+ case BasicType.NUMBER:
78
+ return ts.factory.createNumericLiteral(0);
79
+ case BasicType.VOID:
80
+ case BasicType.UNDEFINED:
81
+ case BasicType.ANY:
82
+ default:
83
+ return undefined;
84
+ }
85
+ }
86
+
87
+ function getBasicTypeFromString(basicType: string): BasicType | undefined {
88
+ switch (basicType) {
89
+ case 'any':
90
+ return BasicType.ANY;
91
+ case 'number':
92
+ return BasicType.NUMBER;
93
+ case 'string':
94
+ return BasicType.STRING;
95
+ case 'boolean':
96
+ return BasicType.BOOLEAN;
97
+ }
98
+ return undefined;
99
+ }
100
+
101
+ function getMockedEnumInstance(enumType: EnumType): ts.Expression | undefined {
102
+ const firstCase = enumType.cases[0];
103
+ if (!firstCase) {
104
+ return undefined;
105
+ }
106
+
107
+ return ts.factory.createPropertyAccessExpression(
108
+ ts.factory.createRegularExpressionLiteral(enumType.name),
109
+ firstCase
110
+ );
111
+ }
112
+
113
+ function getMockedRecordInstance(recordType: RecordType, fileTypeInformation: FileTypeInformation) {
114
+ return ts.factory.createObjectLiteralExpression(
115
+ recordType.fields.map((f) =>
116
+ ts.factory.createPropertyAssignment(
117
+ f.name ?? '_' + getNextFreeId(),
118
+ getMockedValueForType(f.type, fileTypeInformation) ?? ts.factory.createNull()
119
+ )
120
+ )
121
+ );
122
+ }
123
+
124
+ function getMockValueForIdentifier(
125
+ identifier: string,
126
+ fileTypeInformation: FileTypeInformation
127
+ ): ts.Expression | undefined {
128
+ if (!fileTypeInformation.typeIdentifierDefinitionMap.has(identifier)) {
129
+ return undefined;
130
+ }
131
+
132
+ const typeDefinition = fileTypeInformation.typeIdentifierDefinitionMap.get(identifier);
133
+ switch (typeDefinition?.kind) {
134
+ case IdentifierKind.BASIC: {
135
+ const basicType = getBasicTypeFromString(typeDefinition.definition as string);
136
+ if (basicType) {
137
+ return getBasicTypeMockLiteral(basicType);
138
+ }
139
+ return undefined;
140
+ }
141
+ case IdentifierKind.ENUM:
142
+ return getMockedEnumInstance(typeDefinition.definition as EnumType);
143
+ case IdentifierKind.RECORD:
144
+ return getMockedRecordInstance(typeDefinition.definition as RecordType, fileTypeInformation);
145
+ }
146
+ return undefined;
147
+ }
148
+
149
+ function getMockedValueForType(
150
+ type: Type,
151
+ fileTypeInformation: FileTypeInformation
152
+ ): ts.Expression | undefined {
153
+ switch (type.kind) {
154
+ case TypeKind.BASIC:
155
+ return getBasicTypeMockLiteral(type.type as BasicType);
156
+ case TypeKind.IDENTIFIER:
157
+ return getMockValueForIdentifier(type.type as string, fileTypeInformation);
158
+ case TypeKind.SUM: {
159
+ const firstType = (type.type as SumType).types[0];
160
+ return getMockedValueForType(
161
+ firstType ?? { kind: TypeKind.BASIC, type: BasicType.UNDEFINED },
162
+ fileTypeInformation
163
+ );
164
+ }
165
+ case TypeKind.PARAMETRIZED:
166
+ return ts.factory.createNull();
167
+ case TypeKind.OPTIONAL:
168
+ return getMockedValueForType(type.type as OptionalType, fileTypeInformation);
169
+ case TypeKind.ARRAY:
170
+ return ts.factory.createArrayLiteralExpression();
171
+ case TypeKind.DICTIONARY:
172
+ return ts.factory.createObjectLiteralExpression();
173
+ }
174
+ }
175
+
176
+ function getFunctionReturnBlock(
177
+ functionDeclaration: FunctionDeclaration,
178
+ fileTypeInformation: FileTypeInformation
179
+ ): ts.ReturnStatement[] {
180
+ return maybeWrapWithReturnStatement(functionDeclaration.returnType, fileTypeInformation);
181
+ }
182
+
183
+ function getMockedFunctionDeclaration(
184
+ functionDeclaration: FunctionDeclaration,
185
+ fileTypeInformation: FileTypeInformation,
186
+ async: boolean,
187
+ exported: boolean
188
+ ): ts.FunctionDeclaration {
189
+ return buildFunction({
190
+ functionDeclaration,
191
+ async,
192
+ method: false,
193
+ exported,
194
+ declaration: false,
195
+ returnStatement: maybeWrapWithReturnStatement(
196
+ functionDeclaration.returnType,
197
+ fileTypeInformation
198
+ ),
199
+ }) as ts.FunctionDeclaration;
200
+ }
201
+
202
+ function getMockedClass(
203
+ classDeclaration: ClassDeclaration,
204
+ fileInfo: FileTypeInformation
205
+ ): ts.ClassDeclaration {
206
+ return buildClass({
207
+ classDeclaration,
208
+ exported: true,
209
+ getFunctionReturnBlock: (func) => getFunctionReturnBlock(func, fileInfo),
210
+ });
211
+ }
212
+
213
+ function getMockedView(viewDeclaration: ViewDeclaration): ts.Node[] {
214
+ const propsTypeName = getViewPropsTypeName(viewDeclaration);
215
+ const propsType = buildViewPropsInterface(viewDeclaration, { exported: true });
216
+ const propsParameter = ts.factory.createParameterDeclaration(
217
+ undefined,
218
+ undefined,
219
+ 'props',
220
+ undefined,
221
+ ts.factory.createTypeReferenceNode(propsTypeName, undefined),
222
+ undefined
223
+ );
224
+ const viewFunction = ts.factory.createFunctionDeclaration(
225
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
226
+ undefined,
227
+ (viewDeclaration.name?.length ?? 0) > 0 ? viewDeclaration.name : 'View',
228
+ undefined,
229
+ [propsParameter],
230
+ undefined,
231
+ ts.factory.createBlock([])
232
+ );
233
+ return [...propsType, viewFunction];
234
+ }
235
+
236
+ function getMockForModule(
237
+ module: ModuleClassDeclaration,
238
+ typeInfo: FileTypeInformation
239
+ ): ts.Node[] {
240
+ const {
241
+ usedTypeIdentifiers,
242
+ declaredTypeIdentifiers,
243
+ inferredTypeParametersCount,
244
+ records,
245
+ enums,
246
+ } = typeInfo;
247
+
248
+ const undeclaredTypes: Set<string> = usedTypeIdentifiers
249
+ .difference(declaredTypeIdentifiers)
250
+ .difference(getBasicTypesIdentifiers());
251
+
252
+ const sections = [
253
+ getPrefix(),
254
+ [...undeclaredTypes].map((identifier) =>
255
+ buildUnknownTypeAlias(identifier, true, inferredTypeParametersCount)
256
+ ),
257
+ records.flatMap((record: RecordType) => buildRecordTypeAlias(record, true)),
258
+ enums.flatMap((e: EnumType) => buildEnumTypeDeclaration(e, true, false)),
259
+ module.functions.map((f) => getMockedFunctionDeclaration(f, typeInfo, false, true)),
260
+ module.asyncFunctions.map((f) => getMockedFunctionDeclaration(f, typeInfo, true, true)),
261
+ module.classes.map((c) => getMockedClass(c, typeInfo)),
262
+ module.views.flatMap((v) => getMockedView(v)),
263
+ ];
264
+
265
+ return joinTSNodesWithNewlines(sections).flat();
266
+ }
267
+
268
+ export function generateTSMockForModule(
269
+ module: ModuleClassDeclaration,
270
+ fileTypeInformation: FileTypeInformation,
271
+ includeTypes: boolean
272
+ ): string {
273
+ const mockFileName = module.name + (includeTypes ? '.ts' : '.js');
274
+ const mock = ts.factory.createNodeArray(getMockForModule(module, fileTypeInformation));
275
+
276
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
277
+ // get ts nodearray from getMockForModule(m) array
278
+ const resultFile = ts.createSourceFile(
279
+ mockFileName,
280
+ '',
281
+ ts.ScriptTarget.Latest,
282
+ false,
283
+ ts.ScriptKind.TSX
284
+ );
285
+ const printedTs = printer.printList(
286
+ ts.ListFormat.MultiLine + ts.ListFormat.PreserveLines,
287
+ mock,
288
+ resultFile
289
+ );
290
+
291
+ if (includeTypes) {
292
+ return printedTs;
293
+ }
294
+ return ts.transpileModule(printedTs, {
295
+ compilerOptions: {
296
+ module: ts.ModuleKind.ESNext,
297
+ target: ts.ScriptTarget.ESNext,
298
+ },
299
+ }).outputText;
300
+ }
301
+
302
+ const directoryPath = process.cwd();
303
+ const swiftFilesGlob = `${directoryPath}/**/*.swift`;
304
+
305
+ export async function generateMocks(
306
+ files: FileTypeInformation[],
307
+ outputLanguage: 'javascript' | 'typescript' = 'javascript'
308
+ ) {
309
+ const modules = files.flatMap((file) => file.moduleClasses.map((module) => ({ module, file })));
310
+
311
+ if (modules.length === 0) {
312
+ return;
313
+ }
314
+
315
+ const isTypeScript = outputLanguage === 'typescript';
316
+ const extension = isTypeScript ? '.ts' : '.js';
317
+ const mocksDir = path.join(directoryPath, 'mocks');
318
+ await fs.promises.mkdir(mocksDir, { recursive: true });
319
+
320
+ taskAll(modules, async ({ module, file }) => {
321
+ const code = generateTSMockForModule(module, file, isTypeScript);
322
+ const prettified = await prettifyCode(code, isTypeScript ? 'typescript' : undefined);
323
+ await fs.promises.writeFile(path.join(mocksDir, module.name + extension), prettified);
324
+ });
325
+ }
326
+
327
+ export async function getAllExpoModulesInWorkingDirectory(): Promise<FileTypeInformation[]> {
328
+ const files = fs.globSync(swiftFilesGlob);
329
+ return (
330
+ await Promise.all(
331
+ files.map((file) =>
332
+ getFileTypeInformation({
333
+ input: { type: 'file', inputFileAbsolutePaths: [fs.realpathSync(file)] },
334
+ })
335
+ )
336
+ )
337
+ ).filter((f) => f) as FileTypeInformation[];
338
+ }