diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index 190583890..3508574f1 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -15,6 +15,7 @@ import 'package:sass/sass.dart' as sass; import 'package:sass/src/importer/node_package.dart' as npi; import '../logger.dart'; +import '../util/trace.dart'; import '../value/function.dart'; import '../value/mixin.dart'; import 'embedded_sass.pb.dart'; @@ -241,14 +242,14 @@ final class CompilationDispatcher { ..loadedUrls.addAll(result.loadedUrls.map((url) => url.toString())); } on sass.SassException catch (error) { var formatted = withGlyphs( - () => error.toString(color: request.alertColor), + () => error.toString(color: request.alertColor, prettyUri: false), ascii: request.alertAscii, ); return OutboundMessage_CompileResponse() ..failure = (OutboundMessage_CompileResponse_CompileFailure() ..message = error.message ..span = protofySpan(error.span) - ..stackTrace = error.trace.toString() + ..stackTrace = error.trace.printString(prettyUri: false) ..formatted = formatted) ..loadedUrls.addAll(error.loadedUrls.map((url) => url.toString())); } diff --git a/lib/src/embedded/logger.dart b/lib/src/embedded/logger.dart index c99b0823e..2b79ec129 100644 --- a/lib/src/embedded/logger.dart +++ b/lib/src/embedded/logger.dart @@ -2,13 +2,12 @@ // 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:source_span/source_span.dart'; import 'package:stack_trace/stack_trace.dart'; import '../deprecation.dart'; import '../logger.dart'; -import '../util/nullable.dart'; +import '../util/trace.dart'; import '../utils.dart'; import 'compilation_dispatcher.dart'; import 'embedded_sass.pb.dart' hide SourceSpan; @@ -35,7 +34,9 @@ final class EmbeddedLogger extends LoggerWithDeprecationType { ..type = LogEventType.DEBUG ..message = message ..span = protofySpan(span) - ..formatted = (span.start.sourceUrl.andThen(p.prettyUri) ?? '-') + + ..formatted = (span.start.sourceUrl == null + ? '-' + : span.start.sourceUrl.toString()) + ':${span.start.line + 1} ' + (_color ? '\u001b[1mDebug\u001b[0m' : 'DEBUG') + ': $message\n', @@ -70,7 +71,8 @@ final class EmbeddedLogger extends LoggerWithDeprecationType { buffer.writeln(' on ${span.message("\n" + message, color: _color)}'); } if (trace != null) { - buffer.writeln(indent(trace.toString().trimRight(), 4)); + buffer.writeln( + indent(trace.printString(prettyUri: false).trimRight(), 4)); } return buffer.toString(); }, ascii: _ascii); @@ -82,7 +84,7 @@ final class EmbeddedLogger extends LoggerWithDeprecationType { ..message = message ..formatted = formatted; if (span != null) event.span = protofySpan(span); - if (trace != null) event.stackTrace = trace.toString(); + if (trace != null) event.stackTrace = trace.printString(prettyUri: false); if (deprecation != null) event.deprecationType = deprecation.id; _dispatcher.sendLog(event); } diff --git a/lib/src/exception.dart b/lib/src/exception.dart index 6a46fcc25..a97ddfc71 100644 --- a/lib/src/exception.dart +++ b/lib/src/exception.dart @@ -9,6 +9,7 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:term_glyph/term_glyph.dart' as term_glyph; import 'util/nullable.dart'; +import 'util/trace.dart'; import 'utils.dart'; import 'value.dart'; @@ -53,12 +54,12 @@ class SassException extends SourceSpanException { SassException withLoadedUrls(Iterable loadedUrls) => SassException(message, span, loadedUrls); - String toString({Object? color}) { + String toString({Object? color, bool prettyUri = true}) { var buffer = StringBuffer() ..writeln("Error: $message") ..write(span.highlight(color: color)); - for (var frame in trace.toString().split("\n")) { + for (var frame in trace.printString(prettyUri: prettyUri).split("\n")) { if (frame.isEmpty) continue; buffer.writeln(); buffer.write(" $frame"); @@ -158,7 +159,8 @@ class MultiSpanSassException extends SassException loadedUrls, ); - String toString({Object? color, String? secondaryColor}) { + String toString( + {Object? color, String? secondaryColor, bool prettyUri = true}) { var useColor = false; String? primaryColor; if (color is String) { @@ -180,7 +182,7 @@ class MultiSpanSassException extends SassException ) .andThen(buffer.write); - for (var frame in trace.toString().split("\n")) { + for (var frame in trace.printString(prettyUri: prettyUri).split("\n")) { if (frame.isEmpty) continue; buffer.writeln(); buffer.write(" $frame"); diff --git a/lib/src/util/trace.dart b/lib/src/util/trace.dart new file mode 100644 index 000000000..a50ad6bdc --- /dev/null +++ b/lib/src/util/trace.dart @@ -0,0 +1,47 @@ +// Copyright 2026 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 'dart:math' as math; + +import 'package:stack_trace/stack_trace.dart'; + +/// Extensions for printing Trace +extension TraceExtensions on Trace { + /// Returns a human-readable representation of this trace. + /// + /// This will not use `path.prettyUri()` when [prettyUri] is false. + String printString({bool prettyUri = true}) { + if (prettyUri) return toString(); + + // Figure out the longest path so we know how much to pad. + var longest = + frames.map((frame) => frame._location.length).fold(0, math.max); + + // Print out the stack trace nicely formatted. + return frames.map((frame) { + if (frame is UnparsedFrame) return '$frame\n'; + return '${frame._location.padRight(longest)} ${frame.member}\n'; + }).join(); + } +} + +/// Extensions for printing Frame +extension FrameExtensions on Frame { + /// Returns a human-friendly description of the library that this stack frame + /// comes from. + /// + /// This will usually be the string form of [uri]. Absolute URIs will not be + /// converted to relative URIs. Data URIs will be truncated. + String get _library { + if (uri.scheme == 'data') return 'data:...'; + return uri.toString(); + } + + /// A human-friendly description of the code location. + String get _location { + if (line == null) return _library; + if (column == null) return '$_library $line'; + return '$_library $line:$column'; + } +} diff --git a/test/embedded/protocol_test.dart b/test/embedded/protocol_test.dart index 5a0a41a12..4e05d92ff 100644 --- a/test/embedded/protocol_test.dart +++ b/test/embedded/protocol_test.dart @@ -604,7 +604,8 @@ void main() { var failure = await getCompileFailure(process); expect(p.fromUri(failure.span.url), equalsPath(path)); expect(failure.stackTrace, endsWith(" 1:7 root stylesheet\n")); - expect(failure.stackTrace.split(" ").first, equalsPath(path)); + expect( + p.fromUri(failure.stackTrace.split(" ").first), equalsPath(path)); await process.close(); }); });