Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,18 @@ starter/package-lock.json

# Allow GitHub workflows (override ".*" ignore rule)
!.github/
!.github/**
!.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
2 changes: 1 addition & 1 deletion modules/auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion modules/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion modules/chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion modules/ensemble/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion modules/ensemble_bluetooth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion modules/ensemble_ts_interpreter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Function> getters() {
Expand All @@ -107,14 +109,14 @@ class JSCustomException with Invokable implements Exception {
if (value is JSCustomException) {
return value.value;
}
return value;
return value??'';
}
};
}

@override
Map<String, Function> methods() {
return {'init': ([message]) => JSCustomException(message)};
return {'init': ([message]) => JSCustomException(message, isErrorObject: true)};
}

@override
Expand All @@ -124,7 +126,7 @@ class JSCustomException with Invokable implements Exception {

@override
String toString() {
return value;
return value??'';
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1231,10 +1231,16 @@ class JSInterpreter extends RecursiveVisitor<dynamic> {
rethrow;
} catch (e) {
if (node.handler != null) {
dynamic exceptionValue = getExceptionValue(e);
Map<String, dynamic> 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
Expand Down
111 changes: 111 additions & 0 deletions modules/ensemble_ts_interpreter/test/new_interpreter_tests.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -3159,5 +3160,115 @@ function createRandomizedTiles() {
.evaluate(),
throwsA(isA<JSException>()));
});

test('throwing Error object and accessing message property', () {
String codeToEvaluate = """
try {
throw new Error('Something went wrong');
} catch (error) {
var errorMessage = error.message;
}
""";
Map<String, dynamic> 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<String, dynamic> 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<JSCustomException>());
});

test('throwing primitive string unwraps to primitive', () {
String codeToEvaluate = """
try {
throw 'Plain string error';
} catch (e) {
var caught = e;
}
""";
Map<String, dynamic> 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<String>());
});

test('throwing primitive number unwraps to primitive', () {
String codeToEvaluate = """
try {
throw 42;
} catch (e) {
var caught = e;
}
""";
Map<String, dynamic> context = initContext();
JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate();
expect(context['caught'], 42);
expect(context['caught'], isA<int>());
});

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<String, dynamic> 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<String, dynamic> 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<String, dynamic> context = initContext();
JSInterpreter.fromCode(codeToEvaluate, SimpleContext(context)).evaluate();
expect(context['results'], ['Error object', 'String primitive']);
});
});
}
2 changes: 1 addition & 1 deletion modules/file_manager/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion modules/location/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions packages/ensemble_ts_interpreter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/ensemble_ts_interpreter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Function> getters() {
Expand All @@ -198,14 +201,14 @@ class JSCustomException with Invokable implements Exception {
if (value is JSCustomException) {
return value.value;
}
return value;
return value??'';
}
};
}

@override
Map<String, Function> methods() {
return {'init': ([message]) => JSCustomException(message)};
return {'init': ([message]) => JSCustomException(message, isErrorObject: true)};
}

@override
Expand All @@ -215,7 +218,7 @@ class JSCustomException with Invokable implements Exception {

@override
String toString() {
return value;
return value??'';
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1279,8 +1279,14 @@ class JSInterpreter extends RecursiveVisitor<dynamic> {
if (node.handler != null) {
dynamic exceptionValue = getExceptionValue(e);
Map<String, dynamic> 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 =
Expand Down
2 changes: 1 addition & 1 deletion packages/ensemble_ts_interpreter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down