diff --git a/.gitignore b/.gitignore index 43ce79e22..132e45778 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,18 @@ starter/package-lock.json # Allow GitHub workflows (override ".*" ignore rule) !.github/ -!.github/** \ No newline at end of file +!.github/** + +# Flutter generated files in example apps +**/example/**/Flutter/ephemeral/ +**/example/**/Flutter/Generated.xcconfig +**/example/**/Flutter/flutter_export_environment.sh +**/example/**/GeneratedPluginRegistrant.h +**/example/**/GeneratedPluginRegistrant.m +**/example/**/GeneratedPluginRegistrant.java +**/example/android/app/src/main/java/io/ + +# Starter app generated files +starter/android/app/src/main/java/io/ +starter/ios/Runner/GeneratedPluginRegistrant.h +starter/ios/Runner/GeneratedPluginRegistrant.m \ No newline at end of file diff --git a/modules/auth/pubspec.yaml b/modules/auth/pubspec.yaml index 6fa223042..19e77fe81 100644 --- a/modules/auth/pubspec.yaml +++ b/modules/auth/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 get_it: ^8.0.0 google_sign_in: ^6.1.4 diff --git a/modules/camera/pubspec.yaml b/modules/camera/pubspec.yaml index 9664c6dfc..c44a6156e 100644 --- a/modules/camera/pubspec.yaml +++ b/modules/camera/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: url: https://github.com/EnsembleUI/ensemble.git ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 # QR Code scanner module (for backward compatibility re-export) ensemble_qr_scanner: diff --git a/modules/chat/pubspec.yaml b/modules/chat/pubspec.yaml index d2afda9fa..df362f386 100644 --- a/modules/chat/pubspec.yaml +++ b/modules/chat/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 web_socket_client: ^0.2.1 yaml: ^3.1.2 diff --git a/modules/ensemble/pubspec.yaml b/modules/ensemble/pubspec.yaml index 7bd66eb74..5ec59a538 100644 --- a/modules/ensemble/pubspec.yaml +++ b/modules/ensemble/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: ensemble_icons: ^1.0.1 - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 event_bus: ^2.0.0 flutter_layout_grid: ^2.0.3 diff --git a/modules/ensemble_bluetooth/pubspec.yaml b/modules/ensemble_bluetooth/pubspec.yaml index 5166b8ee1..b82d4203a 100644 --- a/modules/ensemble_bluetooth/pubspec.yaml +++ b/modules/ensemble_bluetooth/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: url: https://github.com/EnsembleUI/ensemble.git ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 collection: ^1.17.1 flutter_blue_plus: ^1.33.4 diff --git a/modules/ensemble_ts_interpreter/README.md b/modules/ensemble_ts_interpreter/README.md index a277544e1..e4cef928b 100644 --- a/modules/ensemble_ts_interpreter/README.md +++ b/modules/ensemble_ts_interpreter/README.md @@ -25,7 +25,7 @@ Add this to your `pubspec.yaml`: ```yaml dependencies: - ensemble_ts_interpreter: ^1.0.3 + ensemble_ts_interpreter: ^1.0.7 ``` Then run: diff --git a/modules/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart b/modules/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart index e93493a3d..158806acf 100644 --- a/modules/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart +++ b/modules/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart @@ -98,7 +98,9 @@ class InvokableObject extends Object with Invokable { // Exception class to represent custom JavaScript exceptions class JSCustomException with Invokable implements Exception { final dynamic value; - JSCustomException(this.value); + final bool isErrorObject; // True if created via new Error(), false if wrapping a throw + + JSCustomException(this.value, {this.isErrorObject = false}); @override Map getters() { @@ -107,14 +109,14 @@ class JSCustomException with Invokable implements Exception { if (value is JSCustomException) { return value.value; } - return value; + return value??''; } }; } @override Map methods() { - return {'init': ([message]) => JSCustomException(message)}; + return {'init': ([message]) => JSCustomException(message, isErrorObject: true)}; } @override @@ -124,7 +126,7 @@ class JSCustomException with Invokable implements Exception { @override String toString() { - return value; + return value??''; } } diff --git a/modules/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart b/modules/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart index 296eb3819..192cd28ac 100644 --- a/modules/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart +++ b/modules/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart @@ -1231,10 +1231,16 @@ class JSInterpreter extends RecursiveVisitor { rethrow; } catch (e) { if (node.handler != null) { - dynamic exceptionValue = getExceptionValue(e); Map ctxMap = {}; if (node.handler!.param != null) { - ctxMap[node.handler!.param.value] = JSCustomException(exceptionValue); + // If it's an Error object (created via new Error()), keep it as JSCustomException + // If it's a wrapped primitive throw, unwrap it to the raw value + if (e is JSCustomException && e.isErrorObject) { + ctxMap[node.handler!.param.value] = e; + } else { + dynamic exceptionValue = getExceptionValue(e); + ctxMap[node.handler!.param.value] = exceptionValue; + } } Context context = SimpleContext(ctxMap); // Clone the interpreter with this new context diff --git a/modules/ensemble_ts_interpreter/test/new_interpreter_tests.dart b/modules/ensemble_ts_interpreter/test/new_interpreter_tests.dart index 8c6000015..a0c1064d9 100644 --- a/modules/ensemble_ts_interpreter/test/new_interpreter_tests.dart +++ b/modules/ensemble_ts_interpreter/test/new_interpreter_tests.dart @@ -1,6 +1,7 @@ import 'package:ensemble_ts_interpreter/errors.dart'; import 'package:ensemble_ts_interpreter/invokables/context.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; +import 'package:ensemble_ts_interpreter/invokables/invokablecommons.dart'; import 'package:ensemble_ts_interpreter/invokables/invokablecontroller.dart'; import 'package:ensemble_ts_interpreter/parser/newjs_interpreter.dart'; import 'package:json_path/json_path.dart'; @@ -3159,5 +3160,115 @@ function createRandomizedTiles() { .evaluate(), throwsA(isA())); }); + + test('throwing Error object and accessing message property', () { + String codeToEvaluate = """ + try { + throw new Error('Something went wrong'); + } catch (error) { + var errorMessage = error.message; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['errorMessage'], 'Something went wrong'); + }); + + test('throwing Error object preserves object nature', () { + String codeToEvaluate = """ + try { + throw new Error('Test error'); + } catch (e) { + var caught = e; + var msg = e.message; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['msg'], 'Test error'); + // The caught variable should be an Error object (JSCustomException) + expect(context['caught'], isA()); + }); + + test('throwing primitive string unwraps to primitive', () { + String codeToEvaluate = """ + try { + throw 'Plain string error'; + } catch (e) { + var caught = e; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + // Primitive throws should be unwrapped to the raw value + expect(context['caught'], 'Plain string error'); + expect(context['caught'], isA()); + }); + + test('throwing primitive number unwraps to primitive', () { + String codeToEvaluate = """ + try { + throw 42; + } catch (e) { + var caught = e; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['caught'], 42); + expect(context['caught'], isA()); + }); + + test('Error object message property in string concatenation', () { + String codeToEvaluate = """ + try { + throw new Error('Division by zero'); + } catch (error) { + var result = 'Caught an error: ' + error.message; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['result'], 'Caught an error: Division by zero'); + }); + + test('rethrowing Error object preserves message', () { + String codeToEvaluate = """ + var outerCaught = null; + try { + try { + throw new Error('Inner error'); + } catch (e) { + throw e; + } + } catch (outer) { + outerCaught = outer.message; + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['outerCaught'], 'Inner error'); + }); + + test('mixing Error objects and primitive throws', () { + String codeToEvaluate = """ + var results = []; + + try { + throw new Error('Error object'); + } catch (e) { + results.push(e.message); + } + + try { + throw 'String primitive'; + } catch (e) { + results.push(e); + } + """; + Map context = initContext(); + JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate(); + expect(context['results'], ['Error object', 'String primitive']); + }); }); } diff --git a/modules/file_manager/pubspec.yaml b/modules/file_manager/pubspec.yaml index d32a38dc4..1fe8f5cb4 100644 --- a/modules/file_manager/pubspec.yaml +++ b/modules/file_manager/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: url: https://github.com/EnsembleUI/ensemble.git ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 file_picker: ^10.1.2 vision_gallery_saver: ^3.1.1 diff --git a/modules/location/pubspec.yaml b/modules/location/pubspec.yaml index 2c520f88a..4ad662be2 100644 --- a/modules/location/pubspec.yaml +++ b/modules/location/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: ref: ensemble-v1.2.28 path: modules/ensemble - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 get_it: ^8.0.0 google_maps_flutter: ^2.6.0 diff --git a/packages/ensemble_ts_interpreter/CHANGELOG.md b/packages/ensemble_ts_interpreter/CHANGELOG.md index e564be376..1477a3eb4 100644 --- a/packages/ensemble_ts_interpreter/CHANGELOG.md +++ b/packages/ensemble_ts_interpreter/CHANGELOG.md @@ -1,3 +1,7 @@ +## [1.0.7] - Bug Fixes + +* Fixed issue where Error objects were not being properly unwrapped in catch clauses. + ## [1.0.6] - Allow Global code to overwrite imported functions * Allow Global code to overwrite imported functions, aligning with JavaScript behavior diff --git a/packages/ensemble_ts_interpreter/README.md b/packages/ensemble_ts_interpreter/README.md index 8989db794..e4cef928b 100644 --- a/packages/ensemble_ts_interpreter/README.md +++ b/packages/ensemble_ts_interpreter/README.md @@ -25,7 +25,7 @@ Add this to your `pubspec.yaml`: ```yaml dependencies: - ensemble_ts_interpreter: ^1.0.6 + ensemble_ts_interpreter: ^1.0.7 ``` Then run: diff --git a/packages/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart b/packages/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart index 757e0e9db..76e16598d 100644 --- a/packages/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart +++ b/packages/ensemble_ts_interpreter/lib/invokables/invokablecommons.dart @@ -189,7 +189,10 @@ class InvokableObject extends Object with Invokable { // Exception class to represent custom JavaScript exceptions class JSCustomException with Invokable implements Exception { final dynamic value; - JSCustomException(this.value); + final bool isErrorObject; // True if created via new Error(), false if wrapping a throw + + JSCustomException(this.value, {this.isErrorObject = false}); + @override Map getters() { @@ -198,14 +201,14 @@ class JSCustomException with Invokable implements Exception { if (value is JSCustomException) { return value.value; } - return value; + return value??''; } }; } @override Map methods() { - return {'init': ([message]) => JSCustomException(message)}; + return {'init': ([message]) => JSCustomException(message, isErrorObject: true)}; } @override @@ -215,7 +218,7 @@ class JSCustomException with Invokable implements Exception { @override String toString() { - return value; + return value??''; } } diff --git a/packages/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart b/packages/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart index af41ff795..f88b0cbe2 100644 --- a/packages/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart +++ b/packages/ensemble_ts_interpreter/lib/parser/newjs_interpreter.dart @@ -1279,8 +1279,14 @@ class JSInterpreter extends RecursiveVisitor { if (node.handler != null) { dynamic exceptionValue = getExceptionValue(e); Map ctxMap = {}; - // param is non-nullable; assign directly. - ctxMap[node.handler!.param.value] = JSCustomException(exceptionValue); + // If it's an Error object (created via new Error()), keep it as JSCustomException + // If it's a wrapped primitive throw, unwrap it to the raw value + if (e is JSCustomException && e.isErrorObject) { + ctxMap[node.handler!.param.value] = e; + } else { + dynamic exceptionValue = getExceptionValue(e); + ctxMap[node.handler!.param.value] = exceptionValue; + } Context context = SimpleContext(ctxMap); // Clone the interpreter with this new context JSInterpreter interpreter = diff --git a/packages/ensemble_ts_interpreter/pubspec.yaml b/packages/ensemble_ts_interpreter/pubspec.yaml index 826605659..eb2b0efee 100644 --- a/packages/ensemble_ts_interpreter/pubspec.yaml +++ b/packages/ensemble_ts_interpreter/pubspec.yaml @@ -1,6 +1,6 @@ name: ensemble_ts_interpreter description: A JavaScript (ES5) interpreter written entirely in Dart for Flutter applications. Execute JavaScript code inline within your Dart/Flutter app without external engines. -version: 1.0.6 +version: 1.0.7 homepage: https://ensembleui.com repository: https://github.com/EnsembleUI/ensemble/tree/main/modules/ensemble_ts_interpreter