Skip to content

Commit

Permalink
Formatted alerts (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
Awjin Ahn authored Feb 4, 2021
1 parent b068083 commit 831e5e9
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 42 deletions.
11 changes: 9 additions & 2 deletions bin/dart_sass_embedded.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ void main(List<String> args) {
request.style == InboundMessage_CompileRequest_OutputStyle.COMPRESSED
? sass.OutputStyle.compressed
: sass.OutputStyle.expanded;
var logger = Logger(dispatcher, request.id);
var color = request.alertColor ?? false;
var ascii = request.alertAscii ?? false;
var logger = Logger(dispatcher, request.id, color: color, ascii: ascii);

try {
String result;
Expand All @@ -59,6 +61,7 @@ void main(List<String> args) {
case InboundMessage_CompileRequest_Input.string:
var input = request.string;
result = sass.compileString(input.source,
color: color,
logger: logger,
importers: importers,
importer: _decodeImporter(dispatcher, request, input.importer),
Expand All @@ -72,6 +75,7 @@ void main(List<String> args) {
case InboundMessage_CompileRequest_Input.path:
try {
result = sass.compile(request.path,
color: color,
logger: logger,
importers: importers,
functions: globalFunctions,
Expand All @@ -97,11 +101,14 @@ void main(List<String> args) {
}
return OutboundMessage_CompileResponse()..success = success;
} on sass.SassException catch (error) {
var formatted = withGlyphs(() => error.toString(color: color),
ascii: request.alertAscii);
return OutboundMessage_CompileResponse()
..failure = (OutboundMessage_CompileResponse_CompileFailure()
..message = error.message
..span = protofySpan(error.span)
..stackTrace = error.trace.toString());
..stackTrace = error.trace.toString()
..formatted = formatted);
}
});
}
Expand Down
48 changes: 45 additions & 3 deletions lib/src/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:path/path.dart' as p;
import 'package:sass/sass.dart' as sass;
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
Expand All @@ -18,24 +19,65 @@ class Logger implements sass.Logger {
/// The ID of the compilation to which this logger is passed.
final int _compilationId;

Logger(this._dispatcher, this._compilationId);
/// Whether the formatted message should contain terminal colors.
final bool _color;

/// Whether the formatted message should use ASCII encoding.
final bool _ascii;

Logger(this._dispatcher, this._compilationId,
{bool color = false, bool ascii = false})
: _color = color,
_ascii = ascii;

void debug(String message, SourceSpan span) {
var url =
span.start.sourceUrl == null ? '-' : p.prettyUri(span.start.sourceUrl);
var buffer = StringBuffer()
..write('$url:${span.start.line + 1} ')
..write(_color ? '\u001b[1mDebug\u001b[0m' : 'DEBUG')
..writeln(': $message');

_dispatcher.sendLog(OutboundMessage_LogEvent()
..compilationId = _compilationId
..type = OutboundMessage_LogEvent_Type.DEBUG
..message = message
..span = protofySpan(span));
..span = protofySpan(span)
..formatted = buffer.toString());
}

void warn(String message,
{FileSpan span, Trace trace, bool deprecation = false}) {
var formatted = withGlyphs(() {
var buffer = new StringBuffer();
if (_color) {
buffer.write('\u001b[33m\u001b[1m');
if (deprecation) buffer.write('Deprecation ');
buffer.write('Warning\u001b[0m');
} else {
if (deprecation) buffer.write('DEPRECATION ');
buffer.write('WARNING');
}
if (span == null) {
buffer.writeln(': $message');
} else if (trace != null) {
buffer.writeln(': $message\n\n${span.highlight(color: _color)}');
} else {
buffer.writeln(' on ${span.message("\n" + message, color: _color)}');
}
if (trace != null) {
buffer.writeln(indent(trace.toString().trimRight(), 4));
}
return buffer.toString();
}, ascii: _ascii);

var event = OutboundMessage_LogEvent()
..compilationId = _compilationId
..type = deprecation
? OutboundMessage_LogEvent_Type.DEPRECATION_WARNING
: OutboundMessage_LogEvent_Type.WARNING
..message = message;
..message = message
..formatted = formatted;
if (span != null) event.span = protofySpan(span);
if (trace != null) event.stackTrace = trace.toString();
_dispatcher.sendLog(event);
Expand Down
16 changes: 16 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';
import 'package:sass/sass.dart' as sass;
import 'package:source_span/source_span.dart';
import 'package:term_glyph/term_glyph.dart' as term_glyph;

import 'embedded_sass.pb.dart' as proto;
import 'embedded_sass.pb.dart' hide SourceSpan;
Expand Down Expand Up @@ -57,3 +59,17 @@ sass.Syntax syntaxToSyntax(InboundMessage_Syntax syntax) {
throw "Unknown syntax $syntax.";
}
}

/// Returns [string] with every line indented [indentation] spaces.
String indent(String string, int indentation) =>
string.split("\n").map((line) => (" " * indentation) + line).join("\n");

/// Returns the result of running [callback] with the global ASCII config set
/// to [ascii].
T withGlyphs<T>(T callback(), {@required bool ascii}) {
var currentConfig = term_glyph.ascii;
term_glyph.ascii = ascii;
var result = callback();
term_glyph.ascii = currentConfig;
return result;
}
150 changes: 113 additions & 37 deletions test/protocol_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,22 +97,6 @@ void main() {
await expectLater(process.outbound, emits(isSuccess(equals("a{b:3px}"))));
await process.kill();
});

test("expanded mode when nested mode is passed", () async {
process.inbound.add(compileString("a {b: 1px + 2px}",
style: InboundMessage_CompileRequest_OutputStyle.NESTED));
await expectLater(
process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}"))));
await process.kill();
});

test("expanded mode when compact mode is passed", () async {
process.inbound.add(compileString("a {b: 1px + 2px}",
style: InboundMessage_CompileRequest_OutputStyle.COMPACT));
await expectLater(
process.outbound, emits(isSuccess(equals("a {\n b: 3px;\n}"))));
await process.kill();
});
});

test("doesn't include a source map by default", () async {
Expand Down Expand Up @@ -145,31 +129,74 @@ void main() {
});

group("emits a log event", () {
test("for a @debug rule", () async {
process.inbound.add(compileString("a {@debug hello}"));
group("for a @debug rule", () {
test("with correct fields", () 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);
expect(logEvent.formatted, equals('-:1 DEBUG: hello\n'));
await process.kill();
});

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("formatted with terminal colors", () async {
process.inbound
.add(compileString("a {@debug hello}", alertColor: true));
var logEvent = getLogEvent(await process.outbound.next);
expect(
logEvent.formatted, equals('-:1 \u001b[1mDebug\u001b[0m: hello\n'));
await process.kill();
});
});

test("for a @warn rule", () async {
process.inbound.add(compileString("a {@warn hello}"));
group("for a @warn rule", () {
test("with correct fields", () 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();
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"));
expect(
logEvent.formatted,
equals('WARNING: hello\n'
' - 1:4 root stylesheet\n'));
await process.kill();
});

test("formatted with terminal colors", () async {
process.inbound.add(compileString("a {@warn hello}", alertColor: true));
var logEvent = getLogEvent(await process.outbound.next);
expect(
logEvent.formatted,
equals('\x1B[33m\x1B[1mWarning\x1B[0m: hello\n'
' - 1:4 root stylesheet\n'));
await process.kill();
});

test("encoded in ASCII", () async {
process.inbound
.add(compileString("a {@debug a && b}", alertAscii: true));
var logEvent = getLogEvent(await process.outbound.next);
expect(
logEvent.formatted,
equals('WARNING on line 1, column 13: \n'
'In Sass, "&&" means two copies of the parent selector. You probably want to use "and" instead.\n'
' ,\n'
'1 | a {@debug a && b}\n'
' | ^^\n'
' \'\n'));
await process.kill();
});
});

test("for a parse-time deprecation warning", () async {
Expand Down Expand Up @@ -343,5 +370,54 @@ a {
expect(failure.stackTrace, equals("- 1:11 root stylesheet\n"));
await process.kill();
});

group("and provides a formatted", () {
test("message", () async {
process.inbound.add(compileString("a {b: 1px + 1em}"));

var failure = getCompileFailure(await process.outbound.next);
expect(
failure.formatted,
equals('Error: 1px and 1em have incompatible units.\n'
' ╷\n'
'1 │ a {b: 1px + 1em}\n'
' │ ^^^^^^^^^\n'
' ╵\n'
' - 1:7 root stylesheet'));
await process.kill();
});

test("message with terminal colors", () async {
process.inbound
.add(compileString("a {b: 1px + 1em}", alertColor: true));

var failure = getCompileFailure(await process.outbound.next);
expect(
failure.formatted,
equals('Error: 1px and 1em have incompatible units.\n'
'\x1B[34m ╷\x1B[0m\n'
'\x1B[34m1 │\x1B[0m a {b: \x1B[31m1px + 1em\x1B[0m}\n'
'\x1B[34m │\x1B[0m \x1B[31m ^^^^^^^^^\x1B[0m\n'
'\x1B[34m ╵\x1B[0m\n'
' - 1:7 root stylesheet'));
await process.kill();
});

test("message with ASCII encoding", () async {
process.inbound
.add(compileString("a {b: 1px + 1em}", alertAscii: true));

var failure = getCompileFailure(await process.outbound.next);
expect(
failure.formatted,
equals('Error: 1px and 1em have incompatible units.\n'
' ,\n'
'1 | a {b: 1px + 1em}\n'
' | ^^^^^^^^^\n'
' \'\n'
' - 1:7 root stylesheet'));
await process.kill();
});
});
});
}
4 changes: 4 additions & 0 deletions test/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'embedded_process.dart';
/// string.
InboundMessage compileString(String css,
{int id,
bool alertColor,
bool alertAscii,
InboundMessage_Syntax syntax,
InboundMessage_CompileRequest_OutputStyle style,
String url,
Expand All @@ -32,6 +34,8 @@ InboundMessage compileString(String css,
if (style != null) request.style = style;
if (sourceMap != null) request.sourceMap = sourceMap;
if (functions != null) request.globalFunctions.addAll(functions);
if (alertColor != null) request.alertColor = alertColor;
if (alertAscii != null) request.alertAscii = alertAscii;

return InboundMessage()..compileRequest = request;
}
Expand Down

0 comments on commit 831e5e9

Please sign in to comment.