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
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
|
+
}
|