[JS interop] Expose more JavaScript operators in js_util

Exposes helper functions in js_util to access the JavaScript operators
`delete`, `||`, `&&`, `!`, `!!`, and `typeof`.
Change-Id: I0676b143aa004c7b4ed1c6b695b8d1e78a60778d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/276028
Commit-Queue: Riley Porter <rileyporter@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Riley Porter 2022-12-17 00:31:27 +00:00 committed by Commit Queue
parent 0c4c372d5e
commit b6388f2ea0
7 changed files with 340 additions and 0 deletions

View file

@ -47,6 +47,11 @@
removed. See [#49536](https://github.com/dart-lang/sdk/issues/49536) for
details.
#### `dart:js_util`
- Added several helper functions to access more JavaScript operator, like
`delete` and the `typeof` functionality.
#### `dart:async`
- **Breaking change** [#49529][]:

View file

@ -345,6 +345,42 @@ bool lessThanOrEqual<T>(Object? first, Object? second) {
return JS<bool>('bool', '# <= #', first, second);
}
@patch
@pragma('dart2js:tryInline')
bool typeofEquals<T>(Object? o, String type) {
return JS<bool>('bool', 'typeof # == #', o, type);
}
@patch
@pragma('dart2js:tryInline')
T not<T>(Object? o) {
return JS<dynamic>('Object', '!#', o);
}
@patch
@pragma('dart2js:tryInline')
bool isTruthy<T>(Object? o) {
return JS<bool>('bool', '!!#', o);
}
@patch
@pragma('dart2js:tryInline')
T or<T>(Object? first, Object? second) {
return JS<dynamic>('Object|bool', '# || #', first, second);
}
@patch
@pragma('dart2js:tryInline')
T and<T>(Object? first, Object? second) {
return JS<dynamic>('Object|bool', '# && #', first, second);
}
@patch
@pragma('dart2js:tryInline')
bool delete<T>(Object o, Object property) {
return JS<bool>('bool', 'delete #[#]', o, property);
}
@patch
Future<T> promiseToFuture<T>(Object jsPromise) {
final completer = Completer<T>();

View file

@ -97,6 +97,27 @@ external bool lessThan<T>(Object? first, Object? second);
/// Perform JavaScript less than or equal comparison (`<=`) of two values.
external bool lessThanOrEqual<T>(Object? first, Object? second);
/// Perform JavaScript `typeof` operator on the given object and determine if
/// the result is equal to the given type. Exposes the whole `typeof` equal
/// expression to maximize browser optimization.
external bool typeofEquals<T>(Object? o, String type);
/// Perform JavaScript logical not (`!`) on the given object.
external T not<T>(Object? o);
/// Determines if the given object is truthy or falsy.
external bool isTruthy<T>(Object? o);
/// Perform JavaScript logical or comparison (`||`) of two expressions.
external T or<T>(Object? first, Object? second);
/// Perform JavaScript logical and comparison (`&&`) of two expressions.
external T and<T>(Object? first, Object? second);
/// Perform JavaScript delete operator (`delete`) on the given property of the
/// given object.
external bool delete<T>(Object o, Object property);
/// Exception for when the promise is rejected with a `null` or `undefined`
/// value.
///

View file

@ -0,0 +1,138 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests invoking JS operators through js_util.
@JS()
library js_util_operator_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
@JS()
external Object get undefinedObject;
@JS()
class Foo {
external Foo();
}
main() {
eval(r"""
function Foo() {}
""");
test('typeofEquals', () {
expect(js_util.typeofEquals(5, 'number'), isTrue);
expect(js_util.typeofEquals(5, 'string'), isFalse);
expect(js_util.typeofEquals('foo', 'string'), isTrue);
expect(js_util.typeofEquals('foo', 'number'), isFalse);
expect(js_util.typeofEquals(null, 'object'), isTrue);
expect(js_util.typeofEquals(null, 'boolean'), isFalse);
expect(js_util.typeofEquals(true, 'boolean'), isTrue);
expect(js_util.typeofEquals(true, 'number'), isFalse);
expect(js_util.typeofEquals(Foo(), 'object'), isTrue);
expect(js_util.typeofEquals(Foo(), 'function'), isFalse);
expect(js_util.typeofEquals(js_util.newObject(), 'object'), isTrue);
expect(js_util.typeofEquals(js_util.newObject(), 'function'), isFalse);
expect(js_util.typeofEquals([], 'object'), isTrue);
expect(js_util.typeofEquals([], 'function'), isFalse);
expect(js_util.typeofEquals(undefinedObject, 'undefined'), isTrue);
expect(js_util.typeofEquals(undefinedObject, 'object'), isFalse);
expect(
js_util.typeofEquals(
js_util.getProperty(js_util.globalThis, 'Foo'), 'function'),
isTrue);
});
test('not', () {
expect(js_util.not(true), isFalse);
expect(js_util.not(false), isTrue);
expect(js_util.not(null), isTrue);
expect(js_util.not(''), isTrue);
expect(js_util.not(0), isTrue);
expect(js_util.not(undefinedObject), isTrue);
expect(js_util.not([]), isFalse);
expect(js_util.not({}), isFalse);
expect(js_util.not(js_util.newObject()), isFalse);
expect(js_util.not(Foo()), isFalse);
expect(js_util.not('foo'), isFalse);
expect(js_util.not(5), isFalse);
});
test('isTruthy', () {
expect(js_util.isTruthy(true), isTrue);
expect(js_util.isTruthy(false), isFalse);
expect(js_util.isTruthy(null), isFalse);
expect(js_util.isTruthy(''), isFalse);
expect(js_util.isTruthy(0), isFalse);
expect(js_util.isTruthy(undefinedObject), isFalse);
expect(js_util.isTruthy([]), isTrue);
expect(js_util.isTruthy({}), isTrue);
expect(js_util.isTruthy(js_util.newObject()), isTrue);
expect(js_util.isTruthy(Foo()), isTrue);
expect(js_util.isTruthy('foo'), isTrue);
expect(js_util.isTruthy(5), isTrue);
});
test('or', () {
expect(js_util.or(true, false), isTrue);
expect(js_util.or(true, true), isTrue);
expect(js_util.or(false, true), isTrue);
expect(js_util.or(false, false), isFalse);
expect(js_util.or('foo', 'bar'), equals('foo'));
expect(js_util.or(null, 'foo'), equals('foo'));
expect(js_util.or(undefinedObject, 'foo'), equals('foo'));
expect(js_util.or(0, 'foo'), equals('foo'));
expect(js_util.or('', 'foo'), equals('foo'));
expect(js_util.or([], 'bar'), equals([]));
var o = js_util.newObject();
expect(js_util.or(o, 'foo'), equals(o));
});
test('and', () {
expect(js_util.and(true, false), isFalse);
expect(js_util.and(true, true), isTrue);
expect(js_util.and(false, true), isFalse);
expect(js_util.and(false, false), isFalse);
expect(js_util.and('foo', 'bar'), equals('bar'));
expect(js_util.and(null, 'foo'), equals(null));
// Should be undefined if we had JS types
expect(js_util.and(undefinedObject, 'foo'), equals(null));
expect(js_util.and(0, 'foo'), equals(0));
expect(js_util.and([], 'bar'), equals('bar'));
var o = js_util.newObject();
expect(js_util.and(o, 'foo'), equals('foo'));
});
test('delete', () {
var f = Foo();
expect(js_util.delete(f, 'unknownProperty'), isTrue);
expect(js_util.getProperty(f, 'a'), equals(null));
js_util.setProperty(f, 'a', 'foo');
expect(js_util.getProperty(f, 'a'), equals('foo'));
expect(js_util.delete(f, 'a'), isTrue);
expect(js_util.getProperty(f, 'a'), equals(null));
});
}

View file

@ -52,6 +52,7 @@ js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected
js/js_util/implicit_downcast_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/javascriptobject_extensions_test.dart: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/operator_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/promise_reject_null_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/properties_implicit_checks_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/properties_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code

View file

@ -0,0 +1,138 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests invoking JS operators through js_util.
@JS()
library js_util_operator_test;
import 'package:js/js.dart';
import 'package:js/js_util.dart' as js_util;
import 'package:expect/minitest.dart';
@JS()
external void eval(String code);
@JS()
external Object get undefinedObject;
@JS()
class Foo {
external Foo();
}
main() {
eval(r"""
function Foo() {}
""");
test('typeofEquals', () {
expect(js_util.typeofEquals(5, 'number'), isTrue);
expect(js_util.typeofEquals(5, 'string'), isFalse);
expect(js_util.typeofEquals('foo', 'string'), isTrue);
expect(js_util.typeofEquals('foo', 'number'), isFalse);
expect(js_util.typeofEquals(null, 'object'), isTrue);
expect(js_util.typeofEquals(null, 'boolean'), isFalse);
expect(js_util.typeofEquals(true, 'boolean'), isTrue);
expect(js_util.typeofEquals(true, 'number'), isFalse);
expect(js_util.typeofEquals(Foo(), 'object'), isTrue);
expect(js_util.typeofEquals(Foo(), 'function'), isFalse);
expect(js_util.typeofEquals(js_util.newObject(), 'object'), isTrue);
expect(js_util.typeofEquals(js_util.newObject(), 'function'), isFalse);
expect(js_util.typeofEquals([], 'object'), isTrue);
expect(js_util.typeofEquals([], 'function'), isFalse);
expect(js_util.typeofEquals(undefinedObject, 'undefined'), isTrue);
expect(js_util.typeofEquals(undefinedObject, 'object'), isFalse);
expect(
js_util.typeofEquals(
js_util.getProperty(js_util.globalThis, 'Foo'), 'function'),
isTrue);
});
test('not', () {
expect(js_util.not(true), isFalse);
expect(js_util.not(false), isTrue);
expect(js_util.not(null), isTrue);
expect(js_util.not(''), isTrue);
expect(js_util.not(0), isTrue);
expect(js_util.not(undefinedObject), isTrue);
expect(js_util.not([]), isFalse);
expect(js_util.not({}), isFalse);
expect(js_util.not(js_util.newObject()), isFalse);
expect(js_util.not(Foo()), isFalse);
expect(js_util.not('foo'), isFalse);
expect(js_util.not(5), isFalse);
});
test('isTruthy', () {
expect(js_util.isTruthy(true), isTrue);
expect(js_util.isTruthy(false), isFalse);
expect(js_util.isTruthy(null), isFalse);
expect(js_util.isTruthy(''), isFalse);
expect(js_util.isTruthy(0), isFalse);
expect(js_util.isTruthy(undefinedObject), isFalse);
expect(js_util.isTruthy([]), isTrue);
expect(js_util.isTruthy({}), isTrue);
expect(js_util.isTruthy(js_util.newObject()), isTrue);
expect(js_util.isTruthy(Foo()), isTrue);
expect(js_util.isTruthy('foo'), isTrue);
expect(js_util.isTruthy(5), isTrue);
});
test('or', () {
expect(js_util.or(true, false), isTrue);
expect(js_util.or(true, true), isTrue);
expect(js_util.or(false, true), isTrue);
expect(js_util.or(false, false), isFalse);
expect(js_util.or('foo', 'bar'), equals('foo'));
expect(js_util.or(null, 'foo'), equals('foo'));
expect(js_util.or(undefinedObject, 'foo'), equals('foo'));
expect(js_util.or(0, 'foo'), equals('foo'));
expect(js_util.or('', 'foo'), equals('foo'));
expect(js_util.or([], 'bar'), equals([]));
var o = js_util.newObject();
expect(js_util.or(o, 'foo'), equals(o));
});
test('and', () {
expect(js_util.and(true, false), isFalse);
expect(js_util.and(true, true), isTrue);
expect(js_util.and(false, true), isFalse);
expect(js_util.and(false, false), isFalse);
expect(js_util.and('foo', 'bar'), equals('bar'));
expect(js_util.and(null, 'foo'), equals(null));
// Should be undefined if we had JS types
expect(js_util.and(undefinedObject, 'foo'), equals(null));
expect(js_util.and(0, 'foo'), equals(0));
expect(js_util.and([], 'bar'), equals('bar'));
var o = js_util.newObject();
expect(js_util.and(o, 'foo'), equals('foo'));
});
test('delete', () {
var f = Foo();
expect(js_util.delete(f, 'unknownProperty'), isTrue);
expect(js_util.getProperty(f, 'a'), equals(null));
js_util.setProperty(f, 'a', 'foo');
expect(js_util.getProperty(f, 'a'), equals('foo'));
expect(js_util.delete(f, 'a'), isTrue);
expect(js_util.getProperty(f, 'a'), equals(null));
});
}

View file

@ -52,6 +52,7 @@ js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected
js/js_util/implicit_downcast_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/javascriptobject_extensions_test.dart: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/operator_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/promise_reject_null_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/properties_implicit_checks_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/js_util/properties_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code