Skip to content

Commit edbc6c4

Browse files
authored
fix: handle Babel transpilation syntax errors gracefully (#1718)
1 parent 18714ea commit edbc6c4

5 files changed

Lines changed: 102 additions & 13 deletions

File tree

mobile-app/lib/service/learn/learn_file_service.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,12 @@ class LearnFileService {
274274
if (babelRes?.error != null) {
275275
throw Exception('Babel transpilation failed: ${babelRes?.error}');
276276
}
277-
fileContents = babelRes?.value;
277+
278+
final result = babelRes?.value as Map<dynamic, dynamic>?;
279+
if (result?['success'] == false) {
280+
throw Exception('Babel transpilation failed: ${result?['error']}');
281+
}
282+
fileContents = result?['code'];
278283

279284
Document document = parse(challengeContent);
280285

mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -702,13 +702,34 @@ class ChallengeViewModel extends BaseViewModel {
702702
_userConsoleMessages = [];
703703
setTestConsoleMessages = ['<p>// running tests</p>'];
704704

705-
// Get user code console messages
705+
String userCode;
706+
try {
707+
userCode = await builder.buildUserCode(
708+
challenge!,
709+
_babelWebView.webViewController,
710+
);
711+
} catch (e) {
712+
String errorMessage = e.toString();
713+
if (errorMessage.contains('Babel transpilation failed')) {
714+
errorMessage = errorMessage.replaceFirst('Exception: ', '');
715+
}
716+
String userFriendlyMessage = parseSyntaxError(errorMessage);
717+
718+
setPanelType = PanelType.hint;
719+
setHint = '<p>$userFriendlyMessage</p>';
720+
setTestConsoleMessages = [
721+
...testConsoleMessages,
722+
'<p>$userFriendlyMessage</p>',
723+
'<p>// tests completed</p>',
724+
];
725+
setIsRunningTests = false;
726+
_scaffoldKey.currentState?.openEndDrawer();
727+
return;
728+
}
729+
706730
if ([1, 26, 28].contains(challenge!.challengeType)) {
707731
final evalResult = await testController!.callAsyncJavaScript(
708-
functionBody: await builder.buildUserCode(
709-
challenge!,
710-
_babelWebView.webViewController,
711-
),
732+
functionBody: userCode,
712733
);
713734
if (evalResult != null && evalResult.error != null) {
714735
setUserConsoleMessages = [
@@ -724,10 +745,7 @@ class ChallengeViewModel extends BaseViewModel {
724745
final updateTestRunnerRes = await testController!.callAsyncJavaScript(
725746
functionBody: ScriptBuilder.runnerScript,
726747
arguments: {
727-
'userCode': await builder.buildUserCode(
728-
challenge!,
729-
_babelWebView.webViewController,
730-
),
748+
'userCode': userCode,
731749
'workerType': builder.getWorkerType(challenge!.challengeType),
732750
'combinedCode': await builder.combinedCode(challenge!),
733751
'editableRegionContent': editableRegionContent,

mobile-app/lib/ui/views/learn/test_runner.dart

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@ import 'package:freecodecamp/service/learn/learn_file_service.dart';
77
class ScriptBuilder {
88
final LearnFileService fileService = locator<LearnFileService>();
99

10-
static const transpileScript =
11-
'return Babel.transform(code, { presets: ["env"] }).code';
10+
static const transpileScript = '''
11+
try {
12+
return { success: true, code: Babel.transform(code, { presets: ["env"] }).code };
13+
} catch (e) {
14+
let errorMsg = e.message || e.toString();
15+
if (e.loc) {
16+
errorMsg = "SyntaxError: " + e.message + " (" + e.loc.line + ":" + e.loc.column + ")";
17+
}
18+
return { success: false, error: errorMsg };
19+
}
20+
''';
1221

1322
static const hideFccHeaderStyle = '''
1423
<style class="fcc-hide-header">
@@ -71,7 +80,12 @@ return testRes;
7180
throw Exception('Babel transpilation failed: ${babelRes?.error}');
7281
}
7382

74-
return babelRes?.value ?? challengeFile;
83+
final result = babelRes?.value as Map<dynamic, dynamic>?;
84+
if (result?['success'] == false) {
85+
throw Exception('Babel transpilation failed: ${result?['error']}');
86+
}
87+
88+
return result?['code'] ?? challengeFile;
7589
case 20:
7690
case 23:
7791
case 27:

mobile-app/lib/ui/views/learn/utils/challenge_utils.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,19 @@ DateTime parseMonthYear(String monthYear) {
7272
String formatChallengeDate(DateTime date) {
7373
return '${date.year.toString().padLeft(4, '0')}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
7474
}
75+
76+
String parseSyntaxError(String errorMessage) {
77+
final syntaxErrorRegex = RegExp(r'SyntaxError:.*?\((\d+):(\d+)\)');
78+
final match = syntaxErrorRegex.firstMatch(errorMessage);
79+
80+
if (match != null) {
81+
final line = int.parse(match.group(1)!) - 1;
82+
return 'There is a syntax error on line $line. Please check for missing brackets, quotes, or other syntax issues.';
83+
}
84+
85+
if (errorMessage.contains('Babel transpilation failed')) {
86+
return 'There is a syntax error in your code. Please check for missing brackets, quotes, or other syntax issues.';
87+
}
88+
89+
return errorMessage;
90+
}

mobile-app/test/unit/challenge_utils_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,40 @@ void main() {
114114
expect(formatChallengeDate(date), '2025-12-31');
115115
});
116116
});
117+
118+
group('parseSyntaxError', () {
119+
test('parses syntax error with line number', () {
120+
final errorMessage =
121+
'Exception: Babel transpilation failed: SyntaxError: unknown: Unexpected token (4:0)';
122+
final result = parseSyntaxError(errorMessage);
123+
expect(
124+
result,
125+
'There is a syntax error on line 3. Please check for missing brackets, quotes, or other syntax issues.',
126+
);
127+
});
128+
129+
test('parses syntax error with different line number', () {
130+
final errorMessage = 'SyntaxError: Unexpected identifier (15:22)';
131+
final result = parseSyntaxError(errorMessage);
132+
expect(
133+
result,
134+
'There is a syntax error on line 14. Please check for missing brackets, quotes, or other syntax issues.',
135+
);
136+
});
137+
138+
test('returns generic message for Babel error without line numbers', () {
139+
final errorMessage = 'Babel transpilation failed: unknown error';
140+
final result = parseSyntaxError(errorMessage);
141+
expect(
142+
result,
143+
'There is a syntax error in your code. Please check for missing brackets, quotes, or other syntax issues.',
144+
);
145+
});
146+
147+
test('returns original message for non-syntax errors', () {
148+
final errorMessage = 'Some other error occurred';
149+
final result = parseSyntaxError(errorMessage);
150+
expect(result, 'Some other error occurred');
151+
});
152+
});
117153
}

0 commit comments

Comments
 (0)