Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a factory method for creating host callable #1829

Merged
merged 6 commits into from Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/src/callable.dart
Expand Up @@ -3,10 +3,13 @@
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:tuple/tuple.dart';

import 'ast/sass.dart';
import 'callable/async.dart';
import 'callable/built_in.dart';
import 'exception.dart';
import 'util/function_signature.dart';
import 'value.dart';

export 'callable/async.dart';
Expand Down Expand Up @@ -117,4 +120,14 @@ abstract class Callable extends AsyncCallable {
factory Callable.function(String name, String arguments,
Value callback(List<Value> arguments)) =>
BuiltInCallable.function(name, arguments, callback);

/// Creates a host callable with a single [signature] and a single [callback].
ntkme marked this conversation as resolved.
Show resolved Hide resolved
///
/// Throws a [SassFormatException] if parsing fails.
factory Callable.host(String signature, Value callback(List<Value> arguments),
{bool requireParens = true}) {
Tuple2<String, ArgumentDeclaration> tuple =
parseSignature(signature, requireParens: requireParens);
return BuiltInCallable.parsed(tuple.item1, tuple.item2, callback);
}
}
14 changes: 14 additions & 0 deletions lib/src/callable/async.dart
Expand Up @@ -5,8 +5,11 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:tuple/tuple.dart';

import '../ast/sass.dart';
import '../exception.dart';
import '../util/function_signature.dart';
import '../value.dart';
import 'async_built_in.dart';

Expand Down Expand Up @@ -40,4 +43,15 @@ abstract class AsyncCallable {
factory AsyncCallable.function(String name, String arguments,
FutureOr<Value> callback(List<Value> arguments)) =>
AsyncBuiltInCallable.function(name, arguments, callback);

/// Creates a host callable with a single [signature] and a single [callback].
///
/// Throws a [SassFormatException] if parsing fails.
factory AsyncCallable.host(
String signature, FutureOr<Value> callback(List<Value> arguments),
{bool requireParens = true}) {
Tuple2<String, ArgumentDeclaration> tuple =
parseSignature(signature, requireParens: requireParens);
return AsyncBuiltInCallable.parsed(tuple.item1, tuple.item2, callback);
}
}
35 changes: 11 additions & 24 deletions lib/src/node/compile.dart
Expand Up @@ -6,22 +6,16 @@ import 'package:js/js.dart';
import 'package:node_interop/js.dart';
import 'package:node_interop/util.dart' hide futureToPromise;
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:tuple/tuple.dart';

import '../../sass.dart';
import '../ast/sass.dart';
import '../callable.dart';
import '../exception.dart';
import '../importer/no_op.dart';
import '../importer/node_to_dart/async.dart';
import '../importer/node_to_dart/async_file.dart';
import '../importer/node_to_dart/file.dart';
import '../importer/node_to_dart/sync.dart';
import '../io.dart';
import '../logger/node_to_dart.dart';
import '../parse/scss.dart';
import '../util/nullable.dart';
import '../utils.dart';
import 'compile_options.dart';
import 'compile_result.dart';
import 'exception.dart';
Expand Down Expand Up @@ -236,42 +230,35 @@ List<AsyncCallable> _parseFunctions(Object? functions, {bool asynch = false}) {

var result = <AsyncCallable>[];
jsForEach(functions, (signature, callback) {
Tuple2<String, ArgumentDeclaration> tuple;
try {
tuple = ScssParser(signature).parseSignature();
} on SassFormatException catch (error, stackTrace) {
throwWithTrace(
SassFormatException(
'Invalid signature "$signature": ${error.message}', error.span),
stackTrace);
}

if (!asynch) {
result.add(BuiltInCallable.parsed(tuple.item1, tuple.item2, (arguments) {
Callable? callable;
ntkme marked this conversation as resolved.
Show resolved Hide resolved
callable = Callable.host(signature, (arguments) {
var result = (callback as Function)(toJSArray(arguments));
if (result is Value) return result;
if (isPromise(result)) {
throw 'Invalid return value for custom function '
'"${tuple.item1}":\n'
'"${callable!.name}":\n'
'Promises may only be returned for sass.compileAsync() and '
'sass.compileStringAsync().';
} else {
throw 'Invalid return value for custom function '
'"${tuple.item1}": $result is not a sass.Value.';
'"${callable!.name}": $result is not a sass.Value.';
}
}));
});
result.add(callable);
} else {
result.add(AsyncBuiltInCallable.parsed(tuple.item1, tuple.item2,
(arguments) async {
AsyncCallable? callable;
callable = AsyncCallable.host(signature, (arguments) async {
var result = (callback as Function)(toJSArray(arguments));
if (isPromise(result)) {
result = await promiseToFuture<Object>(result as Promise);
}

if (result is Value) return result;
throw 'Invalid return value for custom function '
'"${tuple.item1}": $result is not a sass.Value.';
}));
'"${callable!.name}": $result is not a sass.Value.';
});
result.add(callable);
}
});
return result;
Expand Down
30 changes: 8 additions & 22 deletions lib/src/node/legacy.dart
Expand Up @@ -10,9 +10,7 @@ import 'dart:typed_data';
import 'package:js/js.dart';
import 'package:node_interop/js.dart';
import 'package:path/path.dart' as p;
import 'package:tuple/tuple.dart';

import '../ast/sass.dart';
import '../callable.dart';
import '../compile.dart';
import '../compile_result.dart';
Expand All @@ -21,7 +19,6 @@ import '../importer/legacy_node.dart';
import '../io.dart';
import '../logger.dart';
import '../logger/node_to_dart.dart';
import '../parse/scss.dart';
import '../syntax.dart';
import '../util/nullable.dart';
import '../utils.dart';
Expand Down Expand Up @@ -208,22 +205,12 @@ List<AsyncCallable> _parseFunctions(RenderOptions options, DateTime start,

var result = <AsyncCallable>[];
jsForEach(functions, (signature, callback) {
Tuple2<String, ArgumentDeclaration> tuple;
try {
tuple = ScssParser(signature).parseSignature(requireParens: false);
} on SassFormatException catch (error, stackTrace) {
throwWithTrace(
SassFormatException(
'Invalid signature "$signature": ${error.message}', error.span),
stackTrace);
}

var context = RenderContext(options: _contextOptions(options, start));
context.options.context = context;

var fiber = options.fiber;
if (fiber != null) {
result.add(BuiltInCallable.parsed(tuple.item1, tuple.item2, (arguments) {
result.add(Callable.host(signature, (arguments) {
var currentFiber = fiber.current;
var jsArguments = [
...arguments.map(wrapValue),
Expand All @@ -240,16 +227,15 @@ List<AsyncCallable> _parseFunctions(RenderOptions options, DateTime start,
// `Zone.current` in an inconsistent state.
? runZoned(() => fiber.yield())
: result);
}));
}, requireParens: false));
} else if (!asynch) {
result.add(BuiltInCallable.parsed(
tuple.item1,
tuple.item2,
result.add(Callable.host(
signature,
(arguments) => unwrapValue((callback as JSFunction)
.apply(context, arguments.map(wrapValue).toList()))));
.apply(context, arguments.map(wrapValue).toList())),
requireParens: false));
} else {
result.add(AsyncBuiltInCallable.parsed(tuple.item1, tuple.item2,
(arguments) async {
result.add(AsyncCallable.host(signature, (arguments) async {
var completer = Completer<Object?>();
var jsArguments = [
...arguments.map(wrapValue),
Expand All @@ -258,7 +244,7 @@ List<AsyncCallable> _parseFunctions(RenderOptions options, DateTime start,
var result = (callback as JSFunction).apply(context, jsArguments);
return unwrapValue(
isUndefined(result) ? await completer.future : result);
}));
}, requireParens: false));
}
});
return result;
Expand Down
28 changes: 28 additions & 0 deletions lib/src/util/function_signature.dart
@@ -0,0 +1,28 @@
// Copyright 2022 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:tuple/tuple.dart';

import '../ast/sass.dart';
import '../exception.dart';
import '../parse/scss.dart';
import '../utils.dart';

/// Parses a function signature of the format allowed by Node Sass's functions
/// option and returns its name and declaration.
///
/// If [requireParens] is `false`, this allows parentheses to be omitted.
///
/// Throws a [SassFormatException] if parsing fails.
Tuple2<String, ArgumentDeclaration> parseSignature(String signature,
ntkme marked this conversation as resolved.
Show resolved Hide resolved
{bool requireParens = true}) {
try {
return ScssParser(signature).parseSignature(requireParens: requireParens);
} on SassFormatException catch (error, stackTrace) {
throwWithTrace(
SassFormatException(
'Invalid signature "$signature": ${error.message}', error.span),
stackTrace);
}
}