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

Commit bec6b3d

Browse files
authored
Do not throw ProtocolErrorType.PARAMS for custom functions and importers (#118)
1 parent 46f42cb commit bec6b3d

File tree

8 files changed

+75
-128
lines changed

8 files changed

+75
-128
lines changed

bin/dart_sass_embedded.dart

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

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

6661
late sass.CompileResult result;
6762
switch (request.whichInput()) {

lib/src/host_callable.dart

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

99
import 'package:sass_api/sass_api.dart' as sass;
10-
import 'package:source_span/source_span.dart';
1110

1211
import 'dispatcher.dart';
1312
import 'embedded_sass.pb.dart';
@@ -26,21 +25,8 @@ import 'utils.dart';
2625
sass.Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions,
2726
int compilationId, String signature,
2827
{int? id}) {
29-
var openParen = signature.indexOf('(');
30-
if (openParen == -1) {
31-
throw sass.SassException('"$signature" is missing "("',
32-
SourceFile.fromString(signature).span(0));
33-
}
34-
35-
if (!signature.endsWith(")")) {
36-
throw sass.SassException('"$signature" doesn\'t end with ")"',
37-
SourceFile.fromString(signature).span(signature.length));
38-
}
39-
40-
var name = signature.substring(0, openParen);
41-
return sass.Callable.function(
42-
name, signature.substring(openParen + 1, signature.length - 1),
43-
(arguments) {
28+
late sass.Callable callable;
29+
callable = sass.Callable.fromSignature(signature, (arguments) {
4430
var protofier = Protofier(dispatcher, functions, compilationId);
4531
var request = OutboundMessage_FunctionCallRequest()
4632
..compilationId = compilationId
@@ -50,7 +36,7 @@ sass.Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions,
5036
if (id != null) {
5137
request.functionId = id;
5238
} else {
53-
request.name = name;
39+
request.name = callable.name;
5440
}
5541

5642
// ignore: deprecated_member_use
@@ -74,4 +60,5 @@ sass.Callable hostCallable(Dispatcher dispatcher, FunctionRegistry functions,
7460
throw error.message;
7561
}
7662
});
63+
return callable;
7764
}

lib/src/importer/base.dart

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import 'package:meta/meta.dart';
66
import 'package:sass_api/sass_api.dart' as sass;
77

88
import '../dispatcher.dart';
9-
import '../embedded_sass.pb.dart' hide SourceSpan;
10-
import '../utils.dart';
119

1210
/// An abstract base class for importers that communicate with the host in some
1311
/// way.
@@ -21,25 +19,17 @@ abstract class ImporterBase extends sass.Importer {
2119
/// Parses [url] as a [Uri] and throws an error if it's invalid or relative
2220
/// (including root-relative).
2321
///
24-
/// The [field] name is used in the error message if one is thrown.
22+
/// The [source] name is used in the error message if one is thrown.
2523
@protected
26-
Uri parseAbsoluteUrl(String field, String url) {
24+
Uri parseAbsoluteUrl(String source, String url) {
2725
Uri parsedUrl;
2826
try {
2927
parsedUrl = Uri.parse(url);
3028
} on FormatException catch (error) {
31-
sendAndThrow(paramsError("$field is invalid: $error"));
29+
throw '$source must return a URL, was "$url"';
3230
}
3331

3432
if (parsedUrl.scheme.isNotEmpty) return parsedUrl;
35-
sendAndThrow(paramsError('$field must be absolute, was "$parsedUrl"'));
36-
}
37-
38-
/// Sends [error] to the remote endpoint, and also throws it so that the Sass
39-
/// compilation fails.
40-
@protected
41-
Never sendAndThrow(ProtocolError error) {
42-
dispatcher.sendError(error);
43-
throw "Protocol error: ${error.message}";
33+
throw '$source must return an absolute URL, was "$parsedUrl"';
4434
}
4535
}

lib/src/importer/file.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:sass_api/sass_api.dart' as sass;
99

1010
import '../dispatcher.dart';
1111
import '../embedded_sass.pb.dart' hide SourceSpan;
12-
import '../utils.dart';
1312
import 'base.dart';
1413

1514
/// A filesystem importer to use for most implementation details of
@@ -44,11 +43,9 @@ class FileImporter extends ImporterBase {
4443

4544
switch (response.whichResult()) {
4645
case InboundMessage_FileImportResponse_Result.fileUrl:
47-
var url =
48-
parseAbsoluteUrl("FileImportResponse.file_url", response.fileUrl);
46+
var url = parseAbsoluteUrl("The file importer", response.fileUrl);
4947
if (url.scheme != 'file') {
50-
sendAndThrow(paramsError(
51-
'FileImportResponse.file_url must be a file: URL, was "$url"'));
48+
throw 'The file importer must return a file: URL, was "$url"';
5249
}
5350

5451
return _filesystemImporter.canonicalize(url);

lib/src/importer/host.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class HostImporter extends ImporterBase {
3535

3636
switch (response.whichResult()) {
3737
case InboundMessage_CanonicalizeResponse_Result.url:
38-
return parseAbsoluteUrl("CanonicalizeResponse.url", response.url);
38+
return parseAbsoluteUrl("The importer", response.url);
3939

4040
case InboundMessage_CanonicalizeResponse_Result.error:
4141
throw response.error;
@@ -60,8 +60,8 @@ class HostImporter extends ImporterBase {
6060
return sass.ImporterResult(response.success.contents,
6161
sourceMapUrl: response.success.sourceMapUrl.isEmpty
6262
? null
63-
: parseAbsoluteUrl("ImportResponse.success.source_map_url",
64-
response.success.sourceMapUrl),
63+
: parseAbsoluteUrl(
64+
"The importer", response.success.sourceMapUrl),
6565
syntax: syntaxToSyntax(response.success.syntax));
6666

6767
case InboundMessage_ImportResponse_Result.error:

test/file_importer_test.dart

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ void main() {
5454
"InboundMessage_CanonicalizeResponse.");
5555
await process.kill();
5656
});
57+
});
58+
59+
group("emits a compile failure", () {
60+
late OutboundMessage_FileImportRequest request;
61+
62+
setUp(() async {
63+
process.inbound.add(compileString("@import 'other'", importers: [
64+
InboundMessage_CompileRequest_Importer()..fileImporterId = 1
65+
]));
66+
67+
request = getFileImportRequest(await process.outbound.next);
68+
});
5769

5870
group("for a FileImportResponse with a URL", () {
5971
test("that's empty", () async {
@@ -62,8 +74,8 @@ void main() {
6274
..id = request.id
6375
..fileUrl = ""));
6476

65-
await _expectImportParamsError(
66-
process, 'FileImportResponse.file_url must be absolute, was ""');
77+
await _expectImportError(
78+
process, 'The file importer must return an absolute URL, was ""');
6779
await process.kill();
6880
});
6981

@@ -73,8 +85,8 @@ void main() {
7385
..id = request.id
7486
..fileUrl = "foo"));
7587

76-
await _expectImportParamsError(
77-
process, 'FileImportResponse.file_url must be absolute, was "foo"');
88+
await _expectImportError(process,
89+
'The file importer must return an absolute URL, was "foo"');
7890
await process.kill();
7991
});
8092

@@ -84,8 +96,8 @@ void main() {
8496
..id = request.id
8597
..fileUrl = "other:foo"));
8698

87-
await _expectImportParamsError(process,
88-
'FileImportResponse.file_url must be a file: URL, was "other:foo"');
99+
await _expectImportError(process,
100+
'The file importer must return a file: URL, was "other:foo"');
89101
await process.kill();
90102
});
91103
});
@@ -268,14 +280,10 @@ void main() {
268280
});
269281
}
270282

271-
/// Asserts that [process] emits a [ProtocolError] params error with the given
283+
/// Asserts that [process] emits a [CompileFailure] result with the given
272284
/// [message] on its protobuf stream and causes the compilation to fail.
273-
Future<void> _expectImportParamsError(
274-
EmbeddedProcess process, Object message) async {
275-
await expectLater(process.outbound,
276-
emits(isProtocolError(errorId, ProtocolErrorType.PARAMS, message)));
277-
285+
Future<void> _expectImportError(EmbeddedProcess process, Object message) async {
278286
var failure = getCompileFailure(await process.outbound.next);
279-
expect(failure.message, equals('Protocol error: $message'));
287+
expect(failure.message, equals(message));
280288
expect(failure.span.text, equals("'other'"));
281289
}

test/function_test.dart

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

24-
group("emits a protocol error for a custom function with a signature", () {
24+
group("emits a compile failure for a custom function with a signature", () {
2525
test("that's empty", () async {
2626
_process.inbound.add(compileString("a {b: c}", functions: [r""]));
27-
await expectParamsError(
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');
27+
await _expectFunctionError(
28+
_process, r'Invalid signature "": Expected identifier.');
3629
await _process.kill();
3730
});
3831

3932
test("that's just a name", () async {
4033
_process.inbound.add(compileString("a {b: c}", functions: [r"foo"]));
41-
await expectParamsError(
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');
34+
await _expectFunctionError(
35+
_process, r'Invalid signature "foo": expected "(".');
5036
await _process.kill();
5137
});
5238

5339
test("without a closing paren", () async {
5440
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($bar"]));
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');
41+
await _expectFunctionError(
42+
_process, r'Invalid signature "foo($bar": expected ")".');
6443
await _process.kill();
6544
});
6645

6746
test("with text after the closing paren", () async {
6847
_process.inbound.add(compileString("a {b: c}", functions: [r"foo() "]));
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');
48+
await _expectFunctionError(
49+
_process, r'Invalid signature "foo() ": expected no more input.');
7850
await _process.kill();
7951
});
8052

8153
test("with invalid arguments", () async {
8254
_process.inbound.add(compileString("a {b: c}", functions: [r"foo($)"]));
83-
await expectParamsError(
84-
_process,
85-
0,
86-
'CompileRequest.global_functions: Error: Expected identifier.\n'
87-
' ╷\n'
88-
'1 │ @function foo(\$) {\n'
89-
' │ ^\n'
90-
' ╵\n'
91-
' - 1:16 root stylesheet');
55+
await _expectFunctionError(
56+
_process, r'Invalid signature "foo($)": Expected identifier.');
9257
await _process.kill();
9358
});
9459
});
@@ -1848,25 +1813,28 @@ void main() {
18481813
}
18491814

18501815
test("that's empty", () async {
1851-
await expectSignatureError("", '"" is missing "("');
1816+
await expectSignatureError(
1817+
"", r'Invalid signature "": Expected identifier.');
18521818
});
18531819

18541820
test("that's just a name", () async {
1855-
await expectSignatureError("foo", '"foo" is missing "("');
1821+
await expectSignatureError(
1822+
"foo", r'Invalid signature "foo": expected "(".');
18561823
});
18571824

18581825
test("without a closing paren", () async {
18591826
await expectSignatureError(
1860-
r"foo($bar", '"foo(\$bar" doesn\'t end with ")"');
1827+
r"foo($bar", r'Invalid signature "foo($bar": expected ")".');
18611828
});
18621829

18631830
test("with text after the closing paren", () async {
1864-
await expectSignatureError(
1865-
r"foo() ", '"foo() " doesn\'t end with ")"');
1831+
await expectSignatureError(r"foo() ",
1832+
r'Invalid signature "foo() ": expected no more input.');
18661833
});
18671834

18681835
test("with invalid arguments", () async {
1869-
await expectSignatureError(r"foo($)", 'Expected identifier.');
1836+
await expectSignatureError(
1837+
r"foo($)", r'Invalid signature "foo($)": Expected identifier.');
18701838
});
18711839
});
18721840
});
@@ -1989,3 +1957,11 @@ Value _hsl(num hue, num saturation, num lightness, double alpha) => Value()
19891957
..saturation = saturation * 1.0
19901958
..lightness = lightness * 1.0
19911959
..alpha = alpha);
1960+
1961+
/// Asserts that [process] emits a [CompileFailure] result with the given
1962+
/// [message] on its protobuf stream and causes the compilation to fail.
1963+
Future<void> _expectFunctionError(
1964+
EmbeddedProcess process, Object message) async {
1965+
var failure = getCompileFailure(await process.outbound.next);
1966+
expect(failure.message, equals(message));
1967+
}

0 commit comments

Comments
 (0)