expo-type-information 0.0.8 → 0.0.9
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/build/commands/commandUtils.js +1 -3
- package/build/commands/commandUtils.js.map +1 -1
- package/build/swift/sourcekittenTypeInformation.js +68 -28
- package/build/swift/sourcekittenTypeInformation.js.map +1 -1
- package/build/typeInformation.d.ts +2 -0
- package/build/typeInformation.js.map +1 -1
- package/build/typescriptGeneration.js +9 -2
- package/build/typescriptGeneration.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/commandUtils.ts +1 -2
- package/src/swift/sourcekittenTypeInformation.ts +78 -31
- package/src/typeInformation.ts +2 -0
- package/src/typescriptGeneration.ts +12 -2
- package/tests/TestModule.swift +16 -1
- package/tests/__snapshots__/typeInformation.test.ts.snap +179 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-type-information",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"react-native",
|
|
6
6
|
"expo",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"ts-jest": "^29.1.2",
|
|
37
37
|
"expo-module-scripts": "56.0.3"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "b2e161a54f90a778ab7e5560c0c8f021bbfcaae2",
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "expo-module build",
|
|
42
42
|
"clean": "expo-module clean",
|
|
@@ -59,9 +59,8 @@ export function addCommonOptions(command: commander.Command): commander.Command
|
|
|
59
59
|
)
|
|
60
60
|
.option(
|
|
61
61
|
'-t, --type-inference <typeInference>',
|
|
62
|
-
// TODO(@HubertBer) Fix the PREPROCESS_AND_INFERENCE option.
|
|
63
62
|
'Level of type inference: `NO_INFERENCE`, `SIMPLE_INFERENCE`, or `PREPROCESS_AND_INFERENCE`. Note that the `PREPROCESS_AND_INFERENCE` option can occasionally fail on some modules. If you encountered errors, fall back to `SIMPLE_INFERENCE` or `NO_INFERENCE`.',
|
|
64
|
-
'
|
|
63
|
+
'PREPROCESS_AND_INFERENCE'
|
|
65
64
|
)
|
|
66
65
|
.option(
|
|
67
66
|
'-s, --skip-unicode-character-mapping',
|
|
@@ -560,7 +560,8 @@ async function parseModuleClassStructure(
|
|
|
560
560
|
async function parseModuleFunctionSubstructure(
|
|
561
561
|
substructure: Structure,
|
|
562
562
|
file: FileType,
|
|
563
|
-
options: SwiftFileTypeInformationOptions
|
|
563
|
+
options: SwiftFileTypeInformationOptions,
|
|
564
|
+
isStatic: boolean
|
|
564
565
|
): Promise<FunctionDeclaration> {
|
|
565
566
|
const definitionParams = substructure['key.substructure'];
|
|
566
567
|
const nameSubstrucutre = definitionParams[0];
|
|
@@ -582,6 +583,7 @@ async function parseModuleFunctionSubstructure(
|
|
|
582
583
|
parameters: [], // TODO(@HubertBer): Module function is not generic. I think so. Check it
|
|
583
584
|
arguments: types?.parameters?.map(mapSourcekittenParameterToType) ?? [],
|
|
584
585
|
definitionOffset: substructure['key.offset'],
|
|
586
|
+
isStatic,
|
|
585
587
|
};
|
|
586
588
|
}
|
|
587
589
|
|
|
@@ -694,10 +696,12 @@ function parseEnumStructure(enumStructure: Structure): EnumType {
|
|
|
694
696
|
.flatMap((sub) => sub['key.substructure'])
|
|
695
697
|
.map((sub) => sub['key.name'].split('(', 1)[0])
|
|
696
698
|
.filter((enumcase) => enumcase !== undefined);
|
|
699
|
+
const stringBacked = !enumStructure['key.inheritedtypes']?.find((v) => v['key.name'] === 'Int');
|
|
697
700
|
|
|
698
701
|
return {
|
|
699
702
|
name: enumStructure['key.name'],
|
|
700
703
|
cases: enumcases,
|
|
704
|
+
stringBacked,
|
|
701
705
|
};
|
|
702
706
|
}
|
|
703
707
|
|
|
@@ -780,7 +784,7 @@ async function parseModuleStructure(
|
|
|
780
784
|
}
|
|
781
785
|
case 'Function': {
|
|
782
786
|
moduleClassDeclaration.functions.push(
|
|
783
|
-
await parseModuleFunctionSubstructure(structure, file, options)
|
|
787
|
+
await parseModuleFunctionSubstructure(structure, file, options, false)
|
|
784
788
|
);
|
|
785
789
|
break;
|
|
786
790
|
}
|
|
@@ -805,7 +809,17 @@ async function parseModuleStructure(
|
|
|
805
809
|
}
|
|
806
810
|
case 'AsyncFunction':
|
|
807
811
|
moduleClassDeclaration.asyncFunctions.push(
|
|
808
|
-
await parseModuleFunctionSubstructure(structure, file, options)
|
|
812
|
+
await parseModuleFunctionSubstructure(structure, file, options, false)
|
|
813
|
+
);
|
|
814
|
+
break;
|
|
815
|
+
case 'StaticAsyncFunction':
|
|
816
|
+
moduleClassDeclaration.asyncFunctions.push(
|
|
817
|
+
await parseModuleFunctionSubstructure(structure, file, options, true)
|
|
818
|
+
);
|
|
819
|
+
break;
|
|
820
|
+
case 'StaticFunction':
|
|
821
|
+
moduleClassDeclaration.functions.push(
|
|
822
|
+
await parseModuleFunctionSubstructure(structure, file, options, true)
|
|
809
823
|
);
|
|
810
824
|
break;
|
|
811
825
|
case 'Constructor':
|
|
@@ -1087,30 +1101,51 @@ function removeComments(fileContent: string): string {
|
|
|
1087
1101
|
});
|
|
1088
1102
|
}
|
|
1089
1103
|
|
|
1090
|
-
|
|
1104
|
+
// The assumption that after return there is an scope closing bracket '}' doesn't hold when switch cases and statements like `#if`, `#else` etc. are present
|
|
1105
|
+
// If they are we don't want to perform the substitution
|
|
1106
|
+
function stringSubstitutionIsValid(str: string): boolean {
|
|
1107
|
+
const dangerousSubstrings = ['case ', '#if', '#else', '#endif'];
|
|
1108
|
+
for (const substring of dangerousSubstrings) {
|
|
1109
|
+
if (str.indexOf(substring) !== -1) {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
type ExpressionEndResult = { expressionEnd: number; ok: boolean };
|
|
1117
|
+
function returnExpressionEnd(fileContent: string, returnIndex: number): ExpressionEndResult {
|
|
1091
1118
|
let inString = false;
|
|
1092
1119
|
let escaped = false;
|
|
1093
|
-
let parenCount = 0;
|
|
1094
1120
|
let braceCount = 0;
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1121
|
+
// Doing a little cheat here to simplify the logic, we assume that the expression end is right before the scope closing `}`.
|
|
1122
|
+
// While this isn't neccessarily true, in our case we only want to replace:
|
|
1123
|
+
//
|
|
1124
|
+
// return expression
|
|
1125
|
+
//
|
|
1126
|
+
// with:
|
|
1127
|
+
//
|
|
1128
|
+
// let return_expression = expression
|
|
1129
|
+
// return return_expression
|
|
1130
|
+
//
|
|
1131
|
+
// such that return_expression variable has the return type of the expression and the file still parses correctly (This might not even be neccessary,
|
|
1132
|
+
// sourcekitten seems to be quite flexible, it may parse part of malformed files just fine).
|
|
1097
1133
|
let i = returnIndex;
|
|
1098
1134
|
while (i < fileContent.length) {
|
|
1099
1135
|
const char = fileContent[i];
|
|
1100
1136
|
let escapedNow = false;
|
|
1137
|
+
if (inString && char !== '"') {
|
|
1138
|
+
i += 1;
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1101
1141
|
switch (char) {
|
|
1102
|
-
case '(':
|
|
1103
|
-
parenCount += 1;
|
|
1104
|
-
break;
|
|
1105
|
-
case ')':
|
|
1106
|
-
parenCount -= 1;
|
|
1107
|
-
break;
|
|
1108
1142
|
case '{':
|
|
1109
1143
|
braceCount += 1;
|
|
1110
1144
|
break;
|
|
1111
1145
|
case '}':
|
|
1112
1146
|
if (braceCount === 0) {
|
|
1113
|
-
|
|
1147
|
+
const ok = stringSubstitutionIsValid(fileContent.substring(returnIndex, i));
|
|
1148
|
+
return { expressionEnd: i, ok };
|
|
1114
1149
|
}
|
|
1115
1150
|
braceCount -= 1;
|
|
1116
1151
|
break;
|
|
@@ -1119,21 +1154,13 @@ function returnExpressionEnd(fileContent: string, returnIndex: number): number {
|
|
|
1119
1154
|
inString = !inString;
|
|
1120
1155
|
}
|
|
1121
1156
|
break;
|
|
1122
|
-
case ';':
|
|
1123
|
-
return i;
|
|
1124
|
-
case '\n':
|
|
1125
|
-
case '\r':
|
|
1126
|
-
if (!inString && parenCount === 0 && braceCount === 0) {
|
|
1127
|
-
return i;
|
|
1128
|
-
}
|
|
1129
|
-
break;
|
|
1130
1157
|
case '\\':
|
|
1131
1158
|
escapedNow = true;
|
|
1132
1159
|
}
|
|
1133
1160
|
escaped = escapedNow;
|
|
1134
1161
|
i += 1;
|
|
1135
1162
|
}
|
|
1136
|
-
return i;
|
|
1163
|
+
return { expressionEnd: i, ok: false };
|
|
1137
1164
|
}
|
|
1138
1165
|
|
|
1139
1166
|
type SourceKittenPreprocessingOptions = {
|
|
@@ -1141,37 +1168,57 @@ type SourceKittenPreprocessingOptions = {
|
|
|
1141
1168
|
mapUnicodeCharacters?: boolean;
|
|
1142
1169
|
};
|
|
1143
1170
|
|
|
1171
|
+
function getSpaceIndentationCount(fileContent: string, index: number): number {
|
|
1172
|
+
const lastNewLineIndex = fileContent.lastIndexOf('\n', index - 1);
|
|
1173
|
+
let spaceCount = 0;
|
|
1174
|
+
let i = lastNewLineIndex === -1 ? 0 : 1 + lastNewLineIndex;
|
|
1175
|
+
while (i < index && fileContent[i] === ' ') {
|
|
1176
|
+
spaceCount += 1;
|
|
1177
|
+
i += 1;
|
|
1178
|
+
}
|
|
1179
|
+
return spaceCount;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1144
1182
|
// Preprocessing to help sourcekitten functions
|
|
1145
1183
|
// For now we create a new variable for each return statement,
|
|
1146
1184
|
// we can find it's type easily with sourcekitten
|
|
1147
|
-
// TODO(@HubertBer): This has many problems which need fixing:
|
|
1185
|
+
// TODO(@HubertBer): This has many problems which may need fixing:
|
|
1148
1186
|
// - return can be inside a string
|
|
1149
|
-
// -
|
|
1187
|
+
// - the substitution may break the Swift code
|
|
1150
1188
|
function preprocessReturnStatements(originalFileContent: string): string {
|
|
1151
1189
|
const newFileContent: string[] = [];
|
|
1152
1190
|
const fileContent = removeComments(originalFileContent);
|
|
1153
|
-
const returnPositions: { start: number; end: number }[] = [];
|
|
1191
|
+
const returnPositions: { start: number; end: number; spacesIndentation: number }[] = [];
|
|
1154
1192
|
let startPos = 0;
|
|
1155
1193
|
while (startPos < fileContent.length) {
|
|
1156
1194
|
const returnIndex = fileContent.indexOf('return ', startPos);
|
|
1157
1195
|
if (returnIndex < 0 || returnIndex >= fileContent.length) {
|
|
1158
1196
|
break;
|
|
1159
1197
|
}
|
|
1198
|
+
const { expressionEnd, ok } = returnExpressionEnd(fileContent, returnIndex);
|
|
1199
|
+
if (!ok) {
|
|
1200
|
+
startPos += 1;
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1160
1203
|
returnPositions.push({
|
|
1161
1204
|
start: returnIndex,
|
|
1162
|
-
end:
|
|
1205
|
+
end: expressionEnd,
|
|
1206
|
+
spacesIndentation: getSpaceIndentationCount(fileContent, returnIndex),
|
|
1163
1207
|
});
|
|
1164
|
-
|
|
1208
|
+
// Going to expressionEnd instead of next index to avoid nested return statements, which in this simple method we cannot handle properly.
|
|
1209
|
+
startPos = expressionEnd + 1;
|
|
1165
1210
|
}
|
|
1166
1211
|
|
|
1167
1212
|
let prevEnd = 0;
|
|
1168
1213
|
|
|
1169
|
-
for (const { start, end } of returnPositions) {
|
|
1214
|
+
for (const { start, end, spacesIndentation } of returnPositions) {
|
|
1170
1215
|
newFileContent.push(fileContent.substring(prevEnd, start));
|
|
1216
|
+
// Make sure the return statement has the same indentation as in original file, to make debugging easier
|
|
1217
|
+
const spaces = ' '.repeat(spacesIndentation);
|
|
1171
1218
|
newFileContent.push(
|
|
1172
|
-
`\
|
|
1219
|
+
`\n${spaces}let returnValueDeclaration_${start}_${end} = ${fileContent.substring(start + 6, end)}`
|
|
1173
1220
|
);
|
|
1174
|
-
newFileContent.push(
|
|
1221
|
+
newFileContent.push(`\n${spaces}return returnValueDeclaration_${start}_${end}\n`);
|
|
1175
1222
|
prevEnd = end;
|
|
1176
1223
|
}
|
|
1177
1224
|
newFileContent.push(fileContent.substring(prevEnd, fileContent.length));
|
package/src/typeInformation.ts
CHANGED
|
@@ -54,6 +54,7 @@ export type EnumCase = string;
|
|
|
54
54
|
*/
|
|
55
55
|
export type EnumType = {
|
|
56
56
|
name: string;
|
|
57
|
+
stringBacked: boolean;
|
|
57
58
|
cases: EnumCase[];
|
|
58
59
|
};
|
|
59
60
|
|
|
@@ -171,6 +172,7 @@ export type FunctionDeclaration = {
|
|
|
171
172
|
returnType: Type;
|
|
172
173
|
arguments: Argument[];
|
|
173
174
|
parameters: Type[];
|
|
175
|
+
isStatic: boolean;
|
|
174
176
|
} & DefinitionOffset;
|
|
175
177
|
|
|
176
178
|
/**
|
|
@@ -31,6 +31,7 @@ const exportModifier = () => ts.factory.createModifier(ts.SyntaxKind.ExportKeywo
|
|
|
31
31
|
const declareModifier = () => ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword);
|
|
32
32
|
const asyncModifier = () => ts.factory.createModifier(ts.SyntaxKind.AsyncKeyword);
|
|
33
33
|
const readonlyModifier = () => ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword);
|
|
34
|
+
const staticModifier = () => ts.factory.createModifier(ts.SyntaxKind.StaticKeyword);
|
|
34
35
|
const constModifier = () => ts.factory.createModifier(ts.SyntaxKind.ConstKeyword);
|
|
35
36
|
const defaultModifier = () => ts.factory.createModifier(ts.SyntaxKind.DefaultKeyword);
|
|
36
37
|
|
|
@@ -108,12 +109,14 @@ function constructModifiersArray(modifiers: {
|
|
|
108
109
|
declare?: boolean;
|
|
109
110
|
async?: boolean;
|
|
110
111
|
readonly?: boolean;
|
|
112
|
+
isStatic?: boolean;
|
|
111
113
|
}): ts.Modifier[] {
|
|
112
114
|
const modifiersArray: ts.Modifier[] = [];
|
|
113
115
|
if (modifiers.exported) modifiersArray.push(exportModifier());
|
|
114
116
|
if (modifiers.declare) modifiersArray.push(declareModifier());
|
|
115
117
|
if (modifiers.async) modifiersArray.push(asyncModifier());
|
|
116
118
|
if (modifiers.readonly) modifiersArray.push(readonlyModifier());
|
|
119
|
+
if (modifiers.isStatic) modifiersArray.push(staticModifier());
|
|
117
120
|
return modifiersArray;
|
|
118
121
|
}
|
|
119
122
|
|
|
@@ -579,7 +582,11 @@ export function buildFunction({
|
|
|
579
582
|
overrideArgumentDeclarations,
|
|
580
583
|
omitReturnType,
|
|
581
584
|
}: buildFunctionOptions): ts.FunctionDeclaration | ts.MethodDeclaration {
|
|
582
|
-
const functionModifiers = constructModifiersArray({
|
|
585
|
+
const functionModifiers = constructModifiersArray({
|
|
586
|
+
exported,
|
|
587
|
+
async: async && !declaration,
|
|
588
|
+
isStatic: functionDeclaration.isStatic,
|
|
589
|
+
});
|
|
583
590
|
const customReturn = !!returnStatement;
|
|
584
591
|
const bareReturnTypeNode = mapTypeToTsTypeNode(functionDeclaration.returnType);
|
|
585
592
|
|
|
@@ -735,7 +742,10 @@ export function buildEnumTypeDeclaration(
|
|
|
735
742
|
constructModifiersArray({ exported, declare: declared }),
|
|
736
743
|
enumType.name,
|
|
737
744
|
enumType.cases.map((enumcase) =>
|
|
738
|
-
ts.factory.createEnumMember(
|
|
745
|
+
ts.factory.createEnumMember(
|
|
746
|
+
enumcase,
|
|
747
|
+
enumType.stringBacked ? ts.factory.createStringLiteral(enumcase) : undefined
|
|
748
|
+
)
|
|
739
749
|
)
|
|
740
750
|
);
|
|
741
751
|
}
|
package/tests/TestModule.swift
CHANGED
|
@@ -105,6 +105,12 @@ public class TestModule: Module {
|
|
|
105
105
|
AsyncFunction("TestAsyncFunction") { (a: Int) async -> String in
|
|
106
106
|
"string"
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
StaticAsyncFunction("TestStaticAsyncFunction") { (a: String) async -> Void in }
|
|
110
|
+
|
|
111
|
+
StaticFunction("TestStaticFunction") { () async -> String in
|
|
112
|
+
"string"
|
|
113
|
+
}
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
Class(TestEmptyClass.self) {
|
|
@@ -176,8 +182,17 @@ class TestRecordClass: Record {
|
|
|
176
182
|
var field2: String
|
|
177
183
|
}
|
|
178
184
|
|
|
179
|
-
enum TestEnum {
|
|
185
|
+
enum TestEnum: String {
|
|
180
186
|
case simpleCase
|
|
181
187
|
case multipleCases1, multipleCases2
|
|
182
188
|
case caseWithArgs1(Int, Double, String), caseWithArgs2(Double, String, Either<Int, String>)
|
|
183
189
|
}
|
|
190
|
+
|
|
191
|
+
enum IntBackedEnum1: Int {
|
|
192
|
+
case simpleCase
|
|
193
|
+
case multipleCases1, multipleCases2
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
enum IntBackedEnum2: Something, Int, SomethingElse {
|
|
197
|
+
case simpleCase
|
|
198
|
+
}
|