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 all commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,10 @@
## 1.56.2

### Embedded Sass

* The embedded compiler now supports version 1.2.0 of [the embedded
protocol](https://github.com/sass/embedded-protocol).

## 1.56.1

### Embedded Sass
Expand Down
14 changes: 14 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 'utils.dart';
import 'value.dart';

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

/// Creates a callable with a single [signature] and a single [callback].
///
/// Throws a [SassFormatException] if parsing fails.
factory Callable.fromSignature(
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 '../utils.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 callable with a single [signature] and a single [callback].
///
/// Throws a [SassFormatException] if parsing fails.
factory AsyncCallable.fromSignature(
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) {
late Callable callable;
callable = Callable.fromSignature(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 {
late AsyncCallable callable;
callable = AsyncCallable.fromSignature(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.fromSignature(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.fromSignature(
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.fromSignature(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
22 changes: 22 additions & 0 deletions lib/src/utils.dart
Expand Up @@ -10,7 +10,11 @@ import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:string_scanner/string_scanner.dart';
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:tuple/tuple.dart';

import 'ast/sass.dart';
import 'exception.dart';
import 'parse/scss.dart';
import 'util/character.dart';

/// The URL used in stack traces when no source URL is available.
Expand Down Expand Up @@ -469,3 +473,21 @@ extension IterableExtension<E> on Iterable<E> {
return take(size);
}
}

/// 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,
{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);
}
}
4 changes: 4 additions & 0 deletions pkg/sass_api/CHANGELOG.md
@@ -1,3 +1,7 @@
## 4.1.2

* No user-visible changes.

## 4.1.1

* No user-visible changes.
Expand Down
4 changes: 2 additions & 2 deletions pkg/sass_api/pubspec.yaml
Expand Up @@ -2,15 +2,15 @@ name: sass_api
# Note: Every time we add a new Sass AST node, we need to bump the *major*
# version because it's a breaking change for anyone who's implementing the
# visitor interface(s).
version: 4.1.1
version: 4.1.2
description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass

environment:
sdk: ">=2.17.0 <3.0.0"

dependencies:
sass: 1.56.1
sass: 1.56.2

dev_dependencies:
dartdoc: ^5.0.0
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,5 +1,5 @@
name: sass
version: 1.56.1
version: 1.56.2
description: A Sass implementation in Dart.
homepage: https://github.com/sass/dart-sass

Expand Down