Emit LogEvents (#6)

This commit is contained in:
Natalie Weizenbaum 2019-11-05 13:51:08 -08:00 committed by GitHub
parent 3de78be2f0
commit ec05600af2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 167 additions and 22 deletions

View File

@ -7,12 +7,11 @@ import 'dart:convert';
import 'package:sass/sass.dart' as sass; import 'package:sass/sass.dart' as sass;
import 'package:source_maps/source_maps.dart' as source_maps; import 'package:source_maps/source_maps.dart' as source_maps;
import 'package:source_span/source_span.dart';
import 'package:stream_channel/stream_channel.dart'; import 'package:stream_channel/stream_channel.dart';
import 'package:sass_embedded/src/dispatcher.dart'; import 'package:sass_embedded/src/dispatcher.dart';
import 'package:sass_embedded/src/embedded_sass.pb.dart' as proto; import 'package:sass_embedded/src/embedded_sass.pb.dart';
import 'package:sass_embedded/src/embedded_sass.pb.dart' hide SourceSpan; import 'package:sass_embedded/src/logger.dart';
import 'package:sass_embedded/src/util/length_delimited_transformer.dart'; import 'package:sass_embedded/src/util/length_delimited_transformer.dart';
import 'package:sass_embedded/src/utils.dart'; import 'package:sass_embedded/src/utils.dart';
@ -35,6 +34,7 @@ void main(List<String> args) {
request.style == InboundMessage_CompileRequest_OutputStyle.COMPRESSED request.style == InboundMessage_CompileRequest_OutputStyle.COMPRESSED
? sass.OutputStyle.compressed ? sass.OutputStyle.compressed
: sass.OutputStyle.expanded; : sass.OutputStyle.expanded;
var logger = Logger(dispatcher, request.id);
try { try {
String result; String result;
@ -46,6 +46,7 @@ void main(List<String> args) {
case InboundMessage_CompileRequest_Input.string: case InboundMessage_CompileRequest_Input.string:
var input = request.string; var input = request.string;
result = sass.compileString(input.source, result = sass.compileString(input.source,
logger: logger,
syntax: _syntaxToSyntax(input.syntax), syntax: _syntaxToSyntax(input.syntax),
style: style, style: style,
url: input.url.isEmpty ? null : input.url, url: input.url.isEmpty ? null : input.url,
@ -55,7 +56,7 @@ void main(List<String> args) {
case InboundMessage_CompileRequest_Input.path: case InboundMessage_CompileRequest_Input.path:
try { try {
result = sass.compile(request.path, result = sass.compile(request.path,
style: style, sourceMap: sourceMapCallback); logger: logger, style: style, sourceMap: sourceMapCallback);
} on FileSystemException catch (error) { } on FileSystemException catch (error) {
return OutboundMessage_CompileResponse() return OutboundMessage_CompileResponse()
..failure = (OutboundMessage_CompileResponse_CompileFailure() ..failure = (OutboundMessage_CompileResponse_CompileFailure()
@ -79,7 +80,7 @@ void main(List<String> args) {
return OutboundMessage_CompileResponse() return OutboundMessage_CompileResponse()
..failure = (OutboundMessage_CompileResponse_CompileFailure() ..failure = (OutboundMessage_CompileResponse_CompileFailure()
..message = error.message ..message = error.message
..span = _protofySpan(error.span) ..span = protofySpan(error.span)
..stackTrace = error.trace.toString()); ..stackTrace = error.trace.toString());
} }
}); });
@ -98,18 +99,3 @@ sass.Syntax _syntaxToSyntax(InboundMessage_Syntax syntax) {
throw "Unknown syntax $syntax."; throw "Unknown syntax $syntax.";
} }
} }
/// Converts a Dart source span to a protocol buffer source span.
proto.SourceSpan _protofySpan(SourceSpanWithContext span) => proto.SourceSpan()
..text = span.text
..start = _protofyLocation(span.start)
..end = _protofyLocation(span.end)
..url = span.sourceUrl?.toString() ?? ""
..context = span.context;
/// Converts a Dart source location to a protocol buffer source location.
SourceSpan_SourceLocation _protofyLocation(SourceLocation location) =>
SourceSpan_SourceLocation()
..offset = location.offset
..line = location.line
..column = location.column;

View File

@ -90,6 +90,10 @@ class Dispatcher {
}); });
} }
/// Sends [event] to the host.
void sendLog(OutboundMessage_LogEvent event) =>
_send(OutboundMessage()..logEvent = event);
/// Sends [message] to the host. /// Sends [message] to the host.
void _send(OutboundMessage message) => void _send(OutboundMessage message) =>
_channel.sink.add(message.writeToBuffer()); _channel.sink.add(message.writeToBuffer());

43
lib/src/logger.dart Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2019 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:sass/sass.dart' as sass;
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
import 'dispatcher.dart';
import 'embedded_sass.pb.dart' hide SourceSpan;
import 'utils.dart';
/// A Sass logger that sends log messages as `LogEvent`s.
class Logger implements sass.Logger {
/// The [Dispatcher] to which to send events.
final Dispatcher _dispatcher;
/// The ID of the compilation to which this logger is passed.
final int _compilationId;
Logger(this._dispatcher, this._compilationId);
void debug(String message, SourceSpan span) {
_dispatcher.sendLog(OutboundMessage_LogEvent()
..compilationId = _compilationId
..type = OutboundMessage_LogEvent_Type.DEBUG
..message = message
..span = protofySpan(span));
}
void warn(String message,
{FileSpan span, Trace trace, bool deprecation = false}) {
var event = OutboundMessage_LogEvent()
..compilationId = _compilationId
..type = deprecation
? OutboundMessage_LogEvent_Type.DEPRECATION_WARNING
: OutboundMessage_LogEvent_Type.WARNING
..message = message;
if (span != null) event.span = protofySpan(span);
if (trace != null) event.stackTrace = trace.toString();
_dispatcher.sendLog(event);
}
}

View File

@ -2,10 +2,31 @@
// MIT-style license that can be found in the LICENSE file or at // MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'embedded_sass.pb.dart'; import 'package:source_span/source_span.dart';
import 'embedded_sass.pb.dart' as proto;
import 'embedded_sass.pb.dart' hide SourceSpan;
/// Returns a [ProtocolError] indicating that a mandatory field with the givne /// Returns a [ProtocolError] indicating that a mandatory field with the givne
/// [fieldName] was missing. /// [fieldName] was missing.
ProtocolError mandatoryError(String fieldName) => ProtocolError() ProtocolError mandatoryError(String fieldName) => ProtocolError()
..type = ProtocolError_ErrorType.PARAMS ..type = ProtocolError_ErrorType.PARAMS
..message = "Missing mandatory field $fieldName"; ..message = "Missing mandatory field $fieldName";
/// Converts a Dart source span to a protocol buffer source span.
proto.SourceSpan protofySpan(SourceSpan span) {
var protoSpan = proto.SourceSpan()
..text = span.text
..start = _protofyLocation(span.start)
..end = _protofyLocation(span.end)
..url = span.sourceUrl?.toString() ?? "";
if (span is SourceSpanWithContext) protoSpan.context = span.context;
return protoSpan;
}
/// Converts a Dart source location to a protocol buffer source location.
SourceSpan_SourceLocation _protofyLocation(SourceLocation location) =>
SourceSpan_SourceLocation()
..offset = location.offset
..line = location.line
..column = location.column;

View File

@ -155,6 +155,85 @@ void main() {
await process.kill(); await process.kill();
}); });
group("emits a log event", () {
test("for a @debug rule", () async {
process.inbound.add(compileString("a {@debug hello}"));
var logEvent = getLogEvent(await process.outbound.next);
expect(logEvent.compilationId, equals(0));
expect(logEvent.type, equals(OutboundMessage_LogEvent_Type.DEBUG));
expect(logEvent.message, equals("hello"));
expect(logEvent.span.text, equals("@debug hello"));
expect(logEvent.span.start, equals(location(3, 0, 3)));
expect(logEvent.span.end, equals(location(15, 0, 15)));
expect(logEvent.span.context, equals("a {@debug hello}"));
expect(logEvent.stackTrace, isEmpty);
await process.kill();
});
test("for a @warn rule", () async {
process.inbound.add(compileString("a {@warn hello}"));
var logEvent = getLogEvent(await process.outbound.next);
expect(logEvent.compilationId, equals(0));
expect(logEvent.type, equals(OutboundMessage_LogEvent_Type.WARNING));
expect(logEvent.message, equals("hello"));
expect(logEvent.span, equals(SourceSpan()));
expect(logEvent.stackTrace, equals("- 1:4 root stylesheet\n"));
await process.kill();
});
test("for a parse-time deprecation warning", () async {
process.inbound.add(compileString("@if true {} @elseif true {}"));
var logEvent = getLogEvent(await process.outbound.next);
expect(logEvent.compilationId, equals(0));
expect(logEvent.type,
equals(OutboundMessage_LogEvent_Type.DEPRECATION_WARNING));
expect(
logEvent.message,
equals(
'@elseif is deprecated and will not be supported in future Sass '
'versions.\n'
'Use "@else if" instead.'));
expect(logEvent.span.text, equals("@elseif"));
expect(logEvent.span.start, equals(location(12, 0, 12)));
expect(logEvent.span.end, equals(location(19, 0, 19)));
expect(logEvent.span.context, equals("@if true {} @elseif true {}"));
expect(logEvent.stackTrace, isEmpty);
await process.kill();
});
test("for a runtime deprecation warning", () async {
process.inbound.add(compileString("a {\$var: value !global}"));
var logEvent = getLogEvent(await process.outbound.next);
expect(logEvent.compilationId, equals(0));
expect(logEvent.type,
equals(OutboundMessage_LogEvent_Type.DEPRECATION_WARNING));
expect(
logEvent.message,
equals("As of Dart Sass 2.0.0, !global assignments won't be able to\n"
"declare new variables. Consider adding `\$var: null` at the "
"root of the\n"
"stylesheet."));
expect(logEvent.span.text, equals("\$var: value !global"));
expect(logEvent.span.start, equals(location(3, 0, 3)));
expect(logEvent.span.end, equals(location(22, 0, 22)));
expect(logEvent.span.context, equals("a {\$var: value !global}"));
expect(logEvent.stackTrace, "- 1:4 root stylesheet\n");
await process.kill();
});
test("with the same ID as the CompileRequest", () async {
process.inbound.add(compileString("@debug hello", id: 12345));
var logEvent = getLogEvent(await process.outbound.next);
expect(logEvent.compilationId, equals(12345));
await process.kill();
});
});
group("gracefully handles an error", () { group("gracefully handles an error", () {
test("from invalid syntax", () async { test("from invalid syntax", () async {
process.inbound.add(compileString("a {b: }")); process.inbound.add(compileString("a {b: }"));

View File

@ -90,7 +90,8 @@ void _ensureUpToDate(String path, String commandToRun) {
/// Returns a [InboundMessage] that compiles the given plain CSS /// Returns a [InboundMessage] that compiles the given plain CSS
/// string. /// string.
InboundMessage compileString(String css, InboundMessage compileString(String css,
{InboundMessage_Syntax syntax, {int id,
InboundMessage_Syntax syntax,
InboundMessage_CompileRequest_OutputStyle style, InboundMessage_CompileRequest_OutputStyle style,
String url, String url,
bool sourceMap}) { bool sourceMap}) {
@ -99,6 +100,7 @@ InboundMessage compileString(String css,
if (url != null) input.url = url; if (url != null) input.url = url;
var request = InboundMessage_CompileRequest()..string = input; var request = InboundMessage_CompileRequest()..string = input;
if (id != null) request.id = id;
if (style != null) request.style = style; if (style != null) request.style = style;
if (sourceMap != null) request.sourceMap = sourceMap; if (sourceMap != null) request.sourceMap = sourceMap;
@ -146,6 +148,16 @@ OutboundMessage_CompileResponse getCompileResponse(value) {
return message.compileResponse; return message.compileResponse;
} }
/// Asserts that [message] is an [OutboundMessage] with a `LogEvent` and
/// returns it.
OutboundMessage_LogEvent getLogEvent(value) {
expect(value, isA<OutboundMessage>());
var message = value as OutboundMessage;
expect(message.hasLogEvent(), isTrue,
reason: "Expected $message to have a LogEvent");
return message.logEvent;
}
/// Asserts that an [OutboundMessage] is a `CompileResponse` with CSS that /// Asserts that an [OutboundMessage] is a `CompileResponse` with CSS that
/// matches [css], with a source map that matches [sourceMap] (if passed). /// matches [css], with a source map that matches [sourceMap] (if passed).
/// ///