Skip to content

Commit f3293db

Browse files
authoredNov 3, 2022
JS API: Validate that importer result 'contents' is a string and improve ArgumentError output (#1816)
* Validate ImporterResult 'contents' and improve ArgumentError output * only use JS stuff in the nodejs bindings * handle non-string contents for legacy importer too * make it work with node 12
1 parent 00c3517 commit f3293db

File tree

8 files changed

+70
-1
lines changed

8 files changed

+70
-1
lines changed
 

‎CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
* Emit a deprecation warning when passing a `sassIndex` with units to
6161
`Value.sassIndexToListIndex()`. This will be an error in Dart Sass 2.0.0.
6262

63+
### JS API
64+
65+
* Importer results now validate whether `contents` is actually a string type.
66+
67+
* Importer result argument errors are now rendered correctly.
68+
6369
## 1.55.0
6470

6571
* **Potentially breaking bug fix:** Sass numbers are now universally stored as

‎lib/src/importer/legacy_node/implementation.dart

+5
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ class NodeImporter {
183183

184184
var file = value.file;
185185
var contents = value.contents;
186+
if (contents != null && !isJsString(contents)) {
187+
jsThrow(ArgumentError.value(contents, 'contents',
188+
'must be a string but was: ${jsType(contents)}'));
189+
}
190+
186191
if (file == null) {
187192
return Tuple2(contents ?? '', url);
188193
} else if (contents != null) {

‎lib/src/importer/node_to_dart/async.dart

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class NodeToDartAsyncImporter extends AsyncImporter {
4343

4444
result as NodeImporterResult;
4545
var contents = result.contents;
46+
if (!isJsString(contents)) {
47+
jsThrow(ArgumentError.value(contents, 'contents',
48+
'must be a string but was: ${jsType(contents)}'));
49+
}
50+
4651
var syntax = result.syntax;
4752
if (contents == null || syntax == null) {
4853
jsThrow(JsError("The load() function must return an object with contents "

‎lib/src/importer/node_to_dart/sync.dart

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class NodeToDartImporter extends Importer {
4848

4949
result as NodeImporterResult;
5050
var contents = result.contents;
51+
if (!isJsString(contents)) {
52+
jsThrow(ArgumentError.value(contents, 'contents',
53+
'must be a string but was: ${jsType(contents)}'));
54+
}
55+
5156
var syntax = result.syntax;
5257
if (contents == null || syntax == null) {
5358
jsThrow(JsError("The load() function must return an object with contents "

‎lib/src/node/function.dart

+25
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ import 'package:js/js.dart';
66

77
@JS("Function")
88
class JSFunction {
9+
/// Creates a [JS function].
10+
///
11+
/// The **last** argument is the function body. The other arguments become the
12+
/// function's parameters.
13+
///
14+
/// The function body must declare a `return` statement in order to return a
15+
/// value, otherwise it returns [JSNull].
16+
///
17+
/// Note: The function body must be compatible with Node 12. Null coalescing
18+
/// and optional chaining features are not supported.
19+
///
20+
/// Examples:
21+
/// ```dart
22+
/// var sum = JSFunction('a', 'b', 'return a + b');
23+
/// sum.call(13, 29) as int; // 42
24+
///
25+
/// var isJsString = JSFunction('a', 'return typeof a === "string"');
26+
/// isJsString.call(42) as bool; // false
27+
/// isJsString.call('42') as bool; // true
28+
///
29+
/// var sayHi = JSFunction('console.log("Hi!")');
30+
/// sayHi.call(); // Logs "Hi!"
31+
/// ```
32+
///
33+
/// [JS Function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function#syntax
934
external JSFunction(String arg1, [String? arg2, String? arg3]);
1035

1136
// Note that this just invokes the function with the given arguments, rather

‎lib/src/node/utils.dart

+19
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ void jsForEach(Object object, void callback(String key, Object? value)) {
7272
/// returns `null`.
7373
Object? jsEval(String js) => JSFunction('', js).call();
7474

75+
/// Returns whether the [object] is a JS `string`.
76+
bool isJsString(Object? object) => _jsTypeOf(object) == 'string';
77+
78+
/// Returns the [object]'s `typeof` according to the JS engine.
79+
String _jsTypeOf(Object? object) =>
80+
JSFunction("value", "return typeof value").call(object) as String;
81+
82+
/// Returns `typeof value` if [value] is a native type, otherwise returns the
83+
/// [value]'s JS class name.
84+
String jsType(Object? value) {
85+
var typeOf = _jsTypeOf(value);
86+
return typeOf != 'object' ? typeOf : JSFunction('value', '''
87+
if (value && value.constructor && value.constructor.name) {
88+
return value.constructor.name;
89+
}
90+
return "object";
91+
''').call(value) as String;
92+
}
93+
7594
@JS("Object.defineProperty")
7695
external void _defineProperty(
7796
Object object, String name, _PropertyDescriptor prototype);

‎lib/src/visitor/async_evaluate.dart

+2
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,8 @@ class _EvaluateVisitor
16451645
}
16461646
} on SassException catch (error, stackTrace) {
16471647
throwWithTrace(_exception(error.message, error.span), stackTrace);
1648+
} on ArgumentError catch (error, stackTrace) {
1649+
throwWithTrace(_exception(error.toString()), stackTrace);
16481650
} catch (error, stackTrace) {
16491651
String? message;
16501652
try {

‎lib/src/visitor/evaluate.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: c70a4193cc291f298f601a5cc371be9eac71fb74
8+
// Checksum: a14e075a5435c7457d1d1371d8b97dd327a66ec4
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1643,6 +1643,8 @@ class _EvaluateVisitor
16431643
}
16441644
} on SassException catch (error, stackTrace) {
16451645
throwWithTrace(_exception(error.message, error.span), stackTrace);
1646+
} on ArgumentError catch (error, stackTrace) {
1647+
throwWithTrace(_exception(error.toString()), stackTrace);
16461648
} catch (error, stackTrace) {
16471649
String? message;
16481650
try {

0 commit comments

Comments
 (0)
Please sign in to comment.