Skip to content
This repository was archived by the owner on May 15, 2023. It is now read-only.

Commit 26127d8

Browse files
authored
Treat invalid function signatures as function errors (#58)
See sass/embedded-protocol#85
1 parent 1cbb0c5 commit 26127d8

File tree

5 files changed

+135
-57
lines changed

5 files changed

+135
-57
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.0.0-beta.15
2+
3+
* Support version 1.0.0-beta.16 of the Sass embedded protocol:
4+
* Treat invalid host function signatures as function errors rather than
5+
protocol errors.
6+
17
## 1.0.0-beta.14
28

39
* Support `FileImporter`s.

bin/dart_sass_embedded.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,13 @@ void main(List<String> args) {
5454
_decodeImporter(dispatcher, request, importer) ??
5555
(throw mandatoryError("Importer.importer")));
5656

57-
var globalFunctions = request.globalFunctions.map((signature) =>
58-
hostCallable(dispatcher, functions, request.id, signature));
57+
var globalFunctions = request.globalFunctions.map((signature) {
58+
try {
59+
return hostCallable(dispatcher, functions, request.id, signature);
60+
} on sass.SassException catch (error) {
61+
throw paramsError('CompileRequest.global_functions: $error');
62+
}
63+
});
5964

6065
late sass.CompileResult result;
6166
switch (request.whichInput()) {

lib/src/host_callable.dart

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:cli';
66
import 'dart:io';
77

88
import 'package:sass_api/sass_api.dart' as sass;
9+
import 'package:source_span/source_span.dart';
910

1011
import 'dispatcher.dart';
1112
import 'embedded_sass.pb.dart';
@@ -20,60 +21,54 @@ import 'utils.dart';
2021
/// anonymous functions defined on the host). Otherwise, it will be called using
2122
/// the name defined in the [signature].
2223
///
23-
/// Throws a [ProtocolError] if [signature] is invalid.
24+
/// Throws a [sass.SassException] if [signature] is invalid.
2425
sass.Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions,
2526
int compilationId, String signature,
2627
{int? id}) {
2728
var openParen = signature.indexOf('(');
28-
if (openParen == -1) {
29-
throw paramsError(
30-
'CompileRequest.global_functions: "$signature" is missing "("');
31-
}
29+
if (openParen == -1)
30+
throw sass.SassException('"$signature" is missing "("',
31+
SourceFile.fromString(signature).span(0));
3232

3333
if (!signature.endsWith(")")) {
34-
throw paramsError(
35-
'CompileRequest.global_functions: "$signature" doesn\'t end with '
36-
'")"');
34+
throw sass.SassException('"$signature" doesn\'t end with ")"',
35+
SourceFile.fromString(signature).span(signature.length));
3736
}
3837

3938
var name = signature.substring(0, openParen);
40-
try {
41-
return sass.Callable.function(
42-
name, signature.substring(openParen + 1, signature.length - 1),
43-
(arguments) {
44-
var protofier = Protofier(dispatcher, functions, compilationId);
45-
var request = OutboundMessage_FunctionCallRequest()
46-
..compilationId = compilationId
47-
..arguments.addAll(
48-
[for (var argument in arguments) protofier.protofy(argument)]);
39+
return sass.Callable.function(
40+
name, signature.substring(openParen + 1, signature.length - 1),
41+
(arguments) {
42+
var protofier = Protofier(dispatcher, functions, compilationId);
43+
var request = OutboundMessage_FunctionCallRequest()
44+
..compilationId = compilationId
45+
..arguments.addAll(
46+
[for (var argument in arguments) protofier.protofy(argument)]);
4947

50-
if (id != null) {
51-
request.functionId = id;
52-
} else {
53-
request.name = name;
54-
}
48+
if (id != null) {
49+
request.functionId = id;
50+
} else {
51+
request.name = name;
52+
}
5553

56-
var response = waitFor(dispatcher.sendFunctionCallRequest(request));
57-
try {
58-
switch (response.whichResult()) {
59-
case InboundMessage_FunctionCallResponse_Result.success:
60-
return protofier.deprotofyResponse(response);
54+
var response = waitFor(dispatcher.sendFunctionCallRequest(request));
55+
try {
56+
switch (response.whichResult()) {
57+
case InboundMessage_FunctionCallResponse_Result.success:
58+
return protofier.deprotofyResponse(response);
6159

62-
case InboundMessage_FunctionCallResponse_Result.error:
63-
throw response.error;
60+
case InboundMessage_FunctionCallResponse_Result.error:
61+
throw response.error;
6462

65-
case InboundMessage_FunctionCallResponse_Result.notSet:
66-
throw mandatoryError('FunctionCallResponse.result');
67-
}
68-
} on ProtocolError catch (error) {
69-
error.id = errorId;
70-
stderr.writeln("Host caused ${error.type.name.toLowerCase()} error: "
71-
"${error.message}");
72-
dispatcher.sendError(error);
73-
throw error.message;
63+
case InboundMessage_FunctionCallResponse_Result.notSet:
64+
throw mandatoryError('FunctionCallResponse.result');
7465
}
75-
});
76-
} on sass.SassException catch (error) {
77-
throw paramsError('CompileRequest.global_functions: $error');
78-
}
66+
} on ProtocolError catch (error) {
67+
error.id = errorId;
68+
stderr.writeln("Host caused ${error.type.name.toLowerCase()} error: "
69+
"${error.message}");
70+
dispatcher.sendError(error);
71+
throw error.message;
72+
}
73+
});
7974
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass_embedded
2-
version: 1.0.0-beta.14
2+
version: 1.0.0-dev
33
description: An implementation of the Sass embedded protocol using Dart Sass.
44
homepage: https://github.com/sass/dart-sass-embedded
55

test/function_test.dart

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,64 @@ void main() {
2121
_process = await EmbeddedProcess.start();
2222
});
2323

24-
group("emits a protocol error", () {
25-
test("for an empty signature", () async {
24+
group("emits a protocol error for a custom function with a signature", () {
25+
test("that's empty", () async {
2626
_process.inbound.add(compileString("a {b: c}", functions: [r""]));
2727
await expectParamsError(
28-
_process, 0, 'CompileRequest.global_functions: "" is missing "("');
28+
_process,
29+
0,
30+
'CompileRequest.global_functions: Error: "" is missing "("\n'
31+
' ╷\n'
32+
'1 │ \n'
33+
' │ ^\n'
34+
' ╵\n'
35+
' - 1:1 root stylesheet');
2936
await _process.kill();
3037
});
3138

32-
test("for a signature with just a name", () async {
39+
test("that's just a name", () async {
3340
_process.inbound.add(compileString("a {b: c}", functions: [r"foo"]));
3441
await expectParamsError(
35-
_process, 0, 'CompileRequest.global_functions: "foo" is missing "("');
42+
_process,
43+
0,
44+
'CompileRequest.global_functions: Error: "foo" is missing "("\n'
45+
' ╷\n'
46+
'1 │ foo\n'
47+
' │ ^^^\n'
48+
' ╵\n'
49+
' - 1:1 root stylesheet');
3650
await _process.kill();
3751
});
3852

39-
test("for a signature without a closing paren", () async {
53+
test("without a closing paren", () async {
4054
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($bar"]));
41-
await expectParamsError(_process, 0,
42-
'CompileRequest.global_functions: "foo(\$bar" doesn\'t end with ")"');
55+
await expectParamsError(
56+
_process,
57+
0,
58+
'CompileRequest.global_functions: Error: "foo(\$bar" doesn\'t end with ")"\n'
59+
' ╷\n'
60+
'1 │ foo(\$bar\n'
61+
' │ ^\n'
62+
' ╵\n'
63+
' - 1:9 root stylesheet');
4364
await _process.kill();
4465
});
4566

46-
test("for a signature with text after the closing paren", () async {
67+
test("with text after the closing paren", () async {
4768
_process.inbound.add(compileString("a {b: c}", functions: [r"foo() "]));
48-
await expectParamsError(_process, 0,
49-
'CompileRequest.global_functions: "foo() " doesn\'t end with ")"');
69+
await expectParamsError(
70+
_process,
71+
0,
72+
'CompileRequest.global_functions: Error: "foo() " doesn\'t end with ")"\n'
73+
' ╷\n'
74+
'1 │ foo() \n'
75+
' │ ^\n'
76+
' ╵\n'
77+
' - 1:7 root stylesheet');
5078
await _process.kill();
5179
});
5280

53-
test("for a signature with invalid arguments", () async {
81+
test("with invalid arguments", () async {
5482
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($)"]));
5583
await expectParamsError(
5684
_process,
@@ -1797,6 +1825,50 @@ void main() {
17971825
expect(_process.kill(), completes);
17981826
});
17991827
});
1828+
1829+
group("reports a compilation error for a function with a signature", () {
1830+
Future<void> expectSignatureError(
1831+
String signature, Object message) async {
1832+
_process.inbound.add(
1833+
compileString("a {b: inspect(foo())}", functions: [r"foo()"]));
1834+
1835+
var request = getFunctionCallRequest(await _process.outbound.next);
1836+
expect(request.arguments, isEmpty);
1837+
_process.inbound.add(InboundMessage()
1838+
..functionCallResponse = (InboundMessage_FunctionCallResponse()
1839+
..id = request.id
1840+
..success = (Value()
1841+
..hostFunction = (Value_HostFunction()
1842+
..id = 1234
1843+
..signature = signature))));
1844+
1845+
var failure = await getCompileFailure(await _process.outbound.next);
1846+
expect(failure.message, message);
1847+
expect(_process.kill(), completes);
1848+
}
1849+
1850+
test("that's empty", () async {
1851+
await expectSignatureError("", '"" is missing "("');
1852+
});
1853+
1854+
test("that's just a name", () async {
1855+
await expectSignatureError("foo", '"foo" is missing "("');
1856+
});
1857+
1858+
test("without a closing paren", () async {
1859+
await expectSignatureError(
1860+
r"foo($bar", '"foo(\$bar" doesn\'t end with ")"');
1861+
});
1862+
1863+
test("with text after the closing paren", () async {
1864+
await expectSignatureError(
1865+
r"foo() ", '"foo() " doesn\'t end with ")"');
1866+
});
1867+
1868+
test("with invalid arguments", () async {
1869+
await expectSignatureError(r"foo($)", 'Expected identifier.');
1870+
});
1871+
});
18001872
});
18011873
});
18021874
}

0 commit comments

Comments
 (0)