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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-type-information",
3
- "version": "0.0.8",
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": "b293744c7b199c6cbe910188863e93a174c21250",
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
- 'SIMPLE_INFERENCE'
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
- function returnExpressionEnd(fileContent: string, returnIndex: number): number {
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
- // TODO(@HubertBer): figure out what also changes the typical end of expression
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
- return i;
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
- // - return Expression end parses incorrectly in case of some strings (check how it parses expo-video)
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: returnExpressionEnd(fileContent, returnIndex),
1205
+ end: expressionEnd,
1206
+ spacesIndentation: getSpaceIndentationCount(fileContent, returnIndex),
1163
1207
  });
1164
- startPos = returnIndex + 1;
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
- `\nlet returnValueDeclaration_${start}_${end} = ${fileContent.substring(start + 6, end)}\n`
1219
+ `\n${spaces}let returnValueDeclaration_${start}_${end} = ${fileContent.substring(start + 6, end)}`
1173
1220
  );
1174
- newFileContent.push(`return returnValueDeclaration_${start}_${end}\n`);
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));
@@ -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({ exported, async: async && !declaration });
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(enumcase, ts.factory.createStringLiteral(enumcase))
745
+ ts.factory.createEnumMember(
746
+ enumcase,
747
+ enumType.stringBacked ? ts.factory.createStringLiteral(enumcase) : undefined
748
+ )
739
749
  )
740
750
  );
741
751
  }
@@ -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
+ }