diff --git a/CHANGELOG.md b/CHANGELOG.md index 609d86637..5e475b539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.43.5 + +### JS API + +* Print more detailed JS stack traces. This is mostly useful for the Sass team's + own debugging purposes. + ## 1.43.4 ### JS API diff --git a/bin/sass.dart b/bin/sass.dart index b49cdd4e2..a95ad65ba 100644 --- a/bin/sass.dart +++ b/bin/sass.dart @@ -16,6 +16,7 @@ import 'package:sass/src/executable/watch.dart'; import 'package:sass/src/import_cache.dart'; import 'package:sass/src/io.dart'; import 'package:sass/src/stylesheet_graph.dart'; +import 'package:sass/src/utils.dart'; Future main(List args) async { var printedError = false; @@ -79,7 +80,7 @@ Future main(List args) async { }(); printError(error.toString(color: options.color), - options.trace ? stackTrace : null); + options.trace ? getTrace(error) ?? stackTrace : null); // Exit code 65 indicates invalid data per // http://www.freebsd.org/cgi/man.cgi?query=sysexits. @@ -93,7 +94,7 @@ Future main(List args) async { path == null ? error.message : "Error reading ${p.relative(path)}: ${error.message}.", - options.trace ? stackTrace : null); + options.trace ? getTrace(error) ?? stackTrace : null); // Error 66 indicates no input. exitCode = 66; @@ -114,7 +115,7 @@ Future main(List args) async { buffer.writeln(); buffer.writeln(error); - printError(buffer.toString(), stackTrace); + printError(buffer.toString(), getTrace(error) ?? stackTrace); exitCode = 255; } } diff --git a/lib/src/executable/repl.dart b/lib/src/executable/repl.dart index eb3e4fca4..d460b40e0 100644 --- a/lib/src/executable/repl.dart +++ b/lib/src/executable/repl.dart @@ -14,6 +14,7 @@ import '../import_cache.dart'; import '../importer/filesystem.dart'; import '../logger/tracking.dart'; import '../parse/parser.dart'; +import '../utils.dart'; import '../visitor/evaluate.dart'; /// Runs an interactive SassScript shell according to [options]. @@ -42,7 +43,8 @@ Future repl(ExecutableOptions options) async { print(evaluator.evaluate(Expression.parse(line, logger: logger))); } } on SassException catch (error, stackTrace) { - _logError(error, stackTrace, line, repl, options, logger); + _logError( + error, getTrace(error) ?? stackTrace, line, repl, options, logger); } } } diff --git a/lib/src/executable/watch.dart b/lib/src/executable/watch.dart index 36e74fa1c..8989ab50e 100644 --- a/lib/src/executable/watch.dart +++ b/lib/src/executable/watch.dart @@ -14,6 +14,7 @@ import '../importer/filesystem.dart'; import '../io.dart'; import '../stylesheet_graph.dart'; import '../util/multi_dir_watcher.dart'; +import '../utils.dart'; import 'compile_stylesheet.dart'; import 'options.dart'; @@ -78,7 +79,8 @@ class _Watcher { return true; } on SassException catch (error, stackTrace) { if (!_options.emitErrorCss) _delete(destination); - _printError(error.toString(color: _options.color), stackTrace); + _printError( + error.toString(color: _options.color), getTrace(error) ?? stackTrace); exitCode = 65; return false; } on FileSystemException catch (error, stackTrace) { @@ -87,7 +89,7 @@ class _Watcher { path == null ? error.message : "Error reading ${p.relative(path)}: ${error.message}.", - stackTrace); + getTrace(error) ?? stackTrace); exitCode = 66; return false; } diff --git a/lib/src/extend/extension_store.dart b/lib/src/extend/extension_store.dart index 013d65d47..1378bf7ac 100644 --- a/lib/src/extend/extension_store.dart +++ b/lib/src/extend/extension_store.dart @@ -187,11 +187,13 @@ class ExtensionStore { try { selector = _extendList( originalSelector, selectorSpan, _extensions, mediaContext); - } on SassException catch (error) { - throw SassException( - "From ${error.span.message('')}\n" - "${error.message}", - error.span); + } on SassException catch (error, stackTrace) { + throwWithTrace( + SassException( + "From ${error.span.message('')}\n" + "${error.message}", + error.span), + stackTrace); } } @@ -330,11 +332,13 @@ class ExtensionStore { selectors = _extendComplex(extension.extender.selector, extension.extender.span, newExtensions, extension.mediaContext); if (selectors == null) continue; - } on SassException catch (error) { - throw SassException( - "From ${extension.extender.span.message('')}\n" - "${error.message}", - error.span); + } on SassException catch (error, stackTrace) { + throwWithTrace( + SassException( + "From ${extension.extender.span.message('')}\n" + "${error.message}", + error.span), + stackTrace); } var containsExtension = selectors.first == extension.extender.selector; @@ -391,12 +395,14 @@ class ExtensionStore { try { selector.value = _extendList(selector.value, selector.span, newExtensions, _mediaContexts[selector]); - } on SassException catch (error) { + } on SassException catch (error, stackTrace) { // TODO(nweiz): Make this a MultiSpanSassException. - throw SassException( - "From ${selector.span.message('')}\n" - "${error.message}", - error.span); + throwWithTrace( + SassException( + "From ${selector.span.message('')}\n" + "${error.message}", + error.span), + stackTrace); } // If no extends actually happened (for example because unification diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index d6ab96c1c..bdc89916b 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -12,6 +12,7 @@ import 'package:source_span/source_span.dart'; import 'package:watcher/watcher.dart'; import '../exception.dart'; +import '../utils.dart'; export 'dart:io' show exitCode, FileSystemException; @@ -41,7 +42,7 @@ String readFile(String path) { try { return utf8.decode(bytes); - } on FormatException catch (error) { + } on FormatException catch (error, stackTrace) { var decodedUntilError = utf8.decode(bytes.sublist(0, error.offset), allowMalformed: true); var stringOffset = decodedUntilError.length; @@ -49,8 +50,10 @@ String readFile(String path) { var decoded = utf8.decode(bytes, allowMalformed: true); var sourceFile = SourceFile.fromString(decoded, url: p.toUri(path)); - throw SassException( - "Invalid UTF-8.", sourceFile.location(stringOffset).pointSpan()); + throwWithTrace( + SassException( + "Invalid UTF-8.", sourceFile.location(stringOffset).pointSpan()), + stackTrace); } } diff --git a/lib/src/node/compile.dart b/lib/src/node/compile.dart index aeddbf7be..ede641627 100644 --- a/lib/src/node/compile.dart +++ b/lib/src/node/compile.dart @@ -4,7 +4,7 @@ import 'package:js/js.dart'; import 'package:node_interop/js.dart'; -import 'package:node_interop/util.dart'; +import 'package:node_interop/util.dart' hide futureToPromise; import 'package:term_glyph/term_glyph.dart' as glyph; import '../../sass.dart'; @@ -42,8 +42,8 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { ascii: ascii), importers: options?.importers?.map(_parseImporter)); return _convertResult(result); - } on SassException catch (error) { - throwNodeException(error, color: color, ascii: ascii); + } on SassException catch (error, stackTrace) { + throwNodeException(error, color: color, ascii: ascii, trace: stackTrace); } } @@ -70,8 +70,8 @@ NodeCompileResult compileString(String text, [CompileStringOptions? options]) { importer: options?.importer.andThen(_parseImporter) ?? (options?.url == null ? NoOpImporter() : null)); return _convertResult(result); - } on SassException catch (error) { - throwNodeException(error, color: color, ascii: ascii); + } on SassException catch (error, stackTrace) { + throwNodeException(error, color: color, ascii: ascii, trace: stackTrace); } } diff --git a/lib/src/node/exception.dart b/lib/src/node/exception.dart index 24945f809..c177714d3 100644 --- a/lib/src/node/exception.dart +++ b/lib/src/node/exception.dart @@ -5,14 +5,20 @@ import 'dart:js_util'; import 'package:js/js.dart'; +import 'package:node_interop/node_interop.dart'; import 'package:term_glyph/term_glyph.dart' as glyph; import '../exception.dart'; +import '../utils.dart'; import 'utils.dart'; @JS() @anonymous -class _NodeException { +class _NodeException extends JsError { + // Fake constructor to silence the no_generative_constructor_in_superclass + // error. + external factory _NodeException(); + external SassException get _dartException; } @@ -58,15 +64,20 @@ var exceptionConstructor = () { /// /// If [ascii] is `false`, the thrown exception uses non-ASCII characters in its /// stringification. +/// +/// If [trace] is passed, it's used as the stack trace for the JS exception. Never throwNodeException(SassException exception, - {required bool color, required bool ascii}) { + {required bool color, required bool ascii, StackTrace? trace}) { var wasAscii = glyph.ascii; glyph.ascii = ascii; try { - jsThrow(callConstructor(exceptionConstructor, [ + var jsException = callConstructor(exceptionConstructor, [ exception, exception.toString(color: color).replaceFirst('Error: ', '') - ]) as _NodeException); + ]) as _NodeException; + trace = getTrace(exception) ?? trace; + if (trace != null) attachJsStack(jsException, trace); + jsThrow(jsException); } finally { glyph.ascii = wasAscii; } diff --git a/lib/src/node/legacy.dart b/lib/src/node/legacy.dart index 7a6e705e3..303633d82 100644 --- a/lib/src/node/legacy.dart +++ b/lib/src/node/legacy.dart @@ -24,6 +24,7 @@ import '../logger/node_to_dart.dart'; import '../parse/scss.dart'; import '../syntax.dart'; import '../util/nullable.dart'; +import '../utils.dart'; import '../value.dart'; import '../visitor/serialize.dart'; import 'function.dart'; @@ -56,9 +57,12 @@ void render( callback(null, result); }, onError: (Object error, StackTrace stackTrace) { if (error is SassException) { - callback(_wrapException(error), null); + callback(_wrapException(error, stackTrace), null); } else { - callback(_newRenderError(error.toString(), status: 3), null); + callback( + _newRenderError(error.toString(), getTrace(error) ?? stackTrace, + status: 3), + null); } }); } @@ -158,15 +162,16 @@ RenderResult renderSync(RenderOptions options) { } return _newRenderResult(options, result, start); - } on SassException catch (error) { - jsThrow(_wrapException(error)); - } catch (error) { - jsThrow(_newRenderError(error.toString(), status: 3)); + } on SassException catch (error, stackTrace) { + jsThrow(_wrapException(error, stackTrace)); + } catch (error, stackTrace) { + jsThrow(_newRenderError(error.toString(), getTrace(error) ?? stackTrace, + status: 3)); } } /// Converts an exception to a [JsError]. -JsError _wrapException(Object exception) { +JsError _wrapException(Object exception, StackTrace stackTrace) { if (exception is SassException) { String file; var url = exception.span.sourceUrl; @@ -179,12 +184,15 @@ JsError _wrapException(Object exception) { } return _newRenderError(exception.toString().replaceFirst("Error: ", ""), + getTrace(exception) ?? stackTrace, line: exception.span.start.line + 1, column: exception.span.start.column + 1, file: file, status: 1); } else { - return JsError(exception.toString()); + var error = JsError(exception.toString()); + attachJsStack(error, getTrace(exception) ?? stackTrace); + return error; } } @@ -203,9 +211,11 @@ List _parseFunctions(RenderOptions options, DateTime start, Tuple2 tuple; try { tuple = ScssParser(signature as String).parseSignature(); - } on SassFormatException catch (error) { - throw SassFormatException( - 'Invalid signature "$signature": ${error.message}', error.span); + } on SassFormatException catch (error, stackTrace) { + throwWithTrace( + SassFormatException( + 'Invalid signature "$signature": ${error.message}', error.span), + stackTrace); } var context = RenderContext(options: _contextOptions(options, start)); @@ -417,7 +427,7 @@ bool _enableSourceMaps(RenderOptions options) => /// Creates a [JsError] with the given fields added to it so it acts like a Node /// Sass error. -JsError _newRenderError(String message, +JsError _newRenderError(String message, StackTrace stackTrace, {int? line, int? column, String? file, int? status}) { var error = JsError(message); setProperty(error, 'formatted', 'Error: $message'); @@ -425,5 +435,6 @@ JsError _newRenderError(String message, if (column != null) setProperty(error, 'column', column); if (file != null) setProperty(error, 'file', file); if (status != null) setProperty(error, 'status', status); + attachJsStack(error, stackTrace); return error; } diff --git a/lib/src/node/utils.dart b/lib/src/node/utils.dart index e6572c27c..e041388b0 100644 --- a/lib/src/node/utils.dart +++ b/lib/src/node/utils.dart @@ -11,6 +11,7 @@ import 'package:js/js.dart'; import 'package:js/js_util.dart'; import '../syntax.dart'; +import '../utils.dart'; import 'array.dart'; import 'function.dart'; import 'url.dart'; @@ -44,6 +45,20 @@ external Function get jsErrorConstructor; /// Returns whether [value] is a JS Error object. bool isJSError(Object value) => instanceof(value, jsErrorConstructor); +/// Attaches [trace] to [error] as its stack trace. +void attachJsStack(JsError error, StackTrace trace) { + // Stack traces in v8 contain the error message itself as well as the stack + // information, so we trim that out if it exists so we don't double-print it. + var traceString = trace.toString(); + var firstRealLine = traceString.indexOf('\n at'); + if (firstRealLine != -1) { + // +1 to account for the newline before the first line. + traceString = traceString.substring(firstRealLine + 1); + } + + setProperty(error, 'stack', "Error: ${error.message}\n$traceString"); +} + /// Invokes [function] with [thisArg] as `this`. Object? call2(JSFunction function, Object thisArg, Object arg1, Object arg2) => function.apply(thisArg, [arg1, arg2]); @@ -169,6 +184,17 @@ external Function get _promiseClass; bool isPromise(Object? object) => object != null && instanceof(object, _promiseClass); +/// Like [futureToPromise] from `node_interop`, but stores the stack trace for +/// errors using [throwWithTrace]. +Promise futureToPromise(Future future) => Promise(allowInterop( + (void Function(Object?) resolve, void Function(Object?) reject) { + future.then((result) => resolve(result), + onError: (Object error, StackTrace stackTrace) { + attachTrace(error, stackTrace); + reject(error); + }); + })); + @JS('URL') external Function get _urlClass; diff --git a/lib/src/parse/keyframe_selector.dart b/lib/src/parse/keyframe_selector.dart index f19dfaa0a..a301cb486 100644 --- a/lib/src/parse/keyframe_selector.dart +++ b/lib/src/parse/keyframe_selector.dart @@ -60,7 +60,9 @@ class KeyframeSelectorParser extends Parser { if (scanIdentChar($e)) { buffer.writeCharCode($e); var next = scanner.peekChar(); - if (next == $plus || next == $minus) buffer.writeCharCode(scanner.readChar()); + if (next == $plus || next == $minus) { + buffer.writeCharCode(scanner.readChar()); + } if (!isDigit(scanner.peekChar())) scanner.error("Expected digit."); while (isDigit(scanner.peekChar())) { diff --git a/lib/src/parse/parser.dart b/lib/src/parse/parser.dart index e5cbaa686..f60a6cdbc 100644 --- a/lib/src/parse/parser.dart +++ b/lib/src/parse/parser.dart @@ -631,9 +631,17 @@ class Parser { void warn(String message, FileSpan span) => logger.warn(message, span: span); /// Throws an error associated with [span]. + /// + /// If [trace] is passed, attaches it as the error's stack trace. @protected - Never error(String message, FileSpan span) => - throw StringScannerException(message, span, scanner.string); + Never error(String message, FileSpan span, [StackTrace? trace]) { + var exception = StringScannerException(message, span, scanner.string); + if (trace == null) { + throw exception; + } else { + throwWithTrace(exception, trace); + } + } /// Runs callback and, if it throws a [SourceSpanFormatException], rethrows it /// with [message] as its message. @@ -641,8 +649,10 @@ class Parser { T withErrorMessage(String message, T callback()) { try { return callback(); - } on SourceSpanFormatException catch (error) { - throw SourceSpanFormatException(message, error.span, error.source); + } on SourceSpanFormatException catch (error, stackTrace) { + throwWithTrace( + SourceSpanFormatException(message, error.span, error.source), + stackTrace); } } @@ -665,7 +675,7 @@ class Parser { T wrapSpanFormatException(T callback()) { try { return callback(); - } on SourceSpanFormatException catch (error) { + } on SourceSpanFormatException catch (error, stackTrace) { var span = error.span as FileSpan; if (startsWithIgnoreCase(error.message, "expected") && span.length == 0) { var startPosition = _firstNewlineBefore(span.start.offset); @@ -674,7 +684,7 @@ class Parser { } } - throw SassFormatException(error.message, span); + throwWithTrace(SassFormatException(error.message, span), stackTrace); } } diff --git a/lib/src/parse/sass.dart b/lib/src/parse/sass.dart index 3fc0d9943..a5b3343d6 100644 --- a/lib/src/parse/sass.dart +++ b/lib/src/parse/sass.dart @@ -110,8 +110,8 @@ class SassParser extends StylesheetParser { } else { try { return DynamicImport(parseImportUrl(url), span); - } on FormatException catch (innerError) { - error("Invalid URL: ${innerError.message}", span); + } on FormatException catch (innerError, stackTrace) { + error("Invalid URL: ${innerError.message}", span, stackTrace); } } } diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 3b44357d1..7a104a378 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -696,7 +696,7 @@ abstract class StylesheetParser extends Parser { var state = scanner.state; try { return _variableDeclarationWithNamespace(); - } on SourceSpanFormatException catch (variableDeclarationError) { + } on SourceSpanFormatException catch (variableDeclarationError, stackTrace) { scanner.state = state; // If a variable declaration failed to parse, it's possible the user @@ -712,7 +712,8 @@ abstract class StylesheetParser extends Parser { error( "@function rules may not contain " "${statement is StyleRule ? "style rules" : "declarations"}.", - statement.span); + statement.span, + stackTrace); } } @@ -1103,8 +1104,8 @@ abstract class StylesheetParser extends Parser { } else { try { return DynamicImport(parseImportUrl(url), urlSpan); - } on FormatException catch (innerError) { - error("Invalid URL: ${innerError.message}", urlSpan); + } on FormatException catch (innerError, stackTrace) { + error("Invalid URL: ${innerError.message}", urlSpan, stackTrace); } } } @@ -3733,8 +3734,9 @@ abstract class StylesheetParser extends Parser { var url = string(); try { return Uri.parse(url); - } on FormatException catch (innerError) { - error("Invalid URL: ${innerError.message}", scanner.spanFrom(start)); + } on FormatException catch (innerError, stackTrace) { + error("Invalid URL: ${innerError.message}", scanner.spanFrom(start), + stackTrace); } } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 72cea60f2..07f7fc046 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -16,6 +16,9 @@ import 'util/character.dart'; /// The URL used in stack traces when no source URL is available. final _noSourceUrl = Uri.parse("-"); +/// Stack traces associated with exceptions thrown with [throwWithTrace]. +final _traces = Expando(); + /// Converts [iter] into a sentence, separating each word with [conjunction]. String toSentence(Iterable iter, [String? conjunction]) { conjunction ??= "and"; @@ -416,6 +419,34 @@ int consumeEscapedCharacter(StringScanner scanner) { } } +// TODO(nweiz): Use a built-in solution for this when dart-lang/sdk#10297 is +// fixed. +/// Throws [error] with [trace] stored as its stack trace. +/// +/// Note that [trace] is only accessible via [getTrace]. +Never throwWithTrace(Object error, StackTrace trace) { + attachTrace(error, trace); + throw error; +} + +/// Attaches [trace] to [error] so that it may be retrieved using [getTrace]. +/// +/// In most cases, [throwWithTrace] should be used instead of this. +void attachTrace(Object error, StackTrace trace) { + if (error is String || error is num || error is bool) return; + + // Non-`Error` objects thrown in Node will have empty stack traces. We don't + // want to store these because they don't have any useful information. + if (trace.toString().isEmpty) return; + + _traces[error] ??= trace; +} + +/// Returns the stack trace associated with error using [throwWithTrace], or +/// [defaultTrace] if it was thrown normally. +StackTrace? getTrace(Object error) => + error is String || error is num || error is bool ? null : _traces[error]; + extension MapExtension on Map { /// If [this] doesn't contain the given [key], sets that key to [value] and /// returns it. diff --git a/lib/src/value.dart b/lib/src/value.dart index f7d531434..847e790b2 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; import 'ast/selector.dart'; import 'exception.dart'; +import 'utils.dart'; import 'value/boolean.dart'; import 'value/calculation.dart'; import 'value/color.dart'; @@ -202,10 +203,12 @@ abstract class Value { var string = _selectorString(name); try { return SelectorList.parse(string, allowParent: allowParent); - } on SassFormatException catch (error) { + } on SassFormatException catch (error, stackTrace) { // TODO(nweiz): colorize this if we're running in an environment where // that works. - throw _exception(error.toString().replaceFirst("Error: ", ""), name); + throwWithTrace( + _exception(error.toString().replaceFirst("Error: ", ""), name), + stackTrace); } } @@ -226,10 +229,12 @@ abstract class Value { var string = _selectorString(name); try { return SimpleSelector.parse(string, allowParent: allowParent); - } on SassFormatException catch (error) { + } on SassFormatException catch (error, stackTrace) { // TODO(nweiz): colorize this if we're running in an environment where // that works. - throw _exception(error.toString().replaceFirst("Error: ", ""), name); + throwWithTrace( + _exception(error.toString().replaceFirst("Error: ", ""), name), + stackTrace); } } @@ -250,10 +255,12 @@ abstract class Value { var string = _selectorString(name); try { return CompoundSelector.parse(string, allowParent: allowParent); - } on SassFormatException catch (error) { + } on SassFormatException catch (error, stackTrace) { // TODO(nweiz): colorize this if we're running in an environment where // that works. - throw _exception(error.toString().replaceFirst("Error: ", ""), name); + throwWithTrace( + _exception(error.toString().replaceFirst("Error: ", ""), name), + stackTrace); } } diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 573b240c0..96ce6eddf 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -622,16 +622,24 @@ class _EvaluateVisitor await callback(module); } on SassRuntimeException { rethrow; - } on MultiSpanSassException catch (error) { - throw MultiSpanSassRuntimeException(error.message, error.span, - error.primaryLabel, error.secondarySpans, _stackTrace(error.span)); - } on SassException catch (error) { - throw _exception(error.message, error.span); - } on MultiSpanSassScriptException catch (error) { - throw _multiSpanException( - error.message, error.primaryLabel, error.secondarySpans); - } on SassScriptException catch (error) { - throw _exception(error.message); + } on MultiSpanSassException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + error.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(error.span)), + stackTrace); + } on SassException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, error.span), stackTrace); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + _multiSpanException( + error.message, error.primaryLabel, error.secondarySpans), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace(_exception(error.message), stackTrace); } }); } @@ -1596,16 +1604,16 @@ class _EvaluateVisitor } else { throw "Can't find stylesheet to import."; } - } on SassException catch (error) { - throw _exception(error.message, error.span); - } catch (error) { + } on SassException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, error.span), stackTrace); + } catch (error, stackTrace) { String? message; try { message = (error as dynamic).message as String; } catch (_) { message = error.toString(); } - throw _exception(message); + throwWithTrace(_exception(message), stackTrace); } finally { _importSpan = null; } @@ -2226,12 +2234,12 @@ class _EvaluateVisitor default: throw UnsupportedError('Unknown calculation name "${node.name}".'); } - } on SassScriptException catch (error) { + } on SassScriptException catch (error, stackTrace) { // The simplification logic in the [SassCalculation] static methods will // throw an error if the arguments aren't compatible, but we have access // to the original spans so we can throw a more informative error. _verifyCompatibleNumbers(arguments, node.arguments); - throw _exception(error.message, node.span); + throwWithTrace(_exception(error.message, node.span), stackTrace); } } @@ -2581,24 +2589,32 @@ class _EvaluateVisitor result = await callback(evaluated.positional); } on SassRuntimeException { rethrow; - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassRuntimeException( - error.message, - nodeWithSpan.span, - error.primaryLabel, - error.secondarySpans, - _stackTrace(nodeWithSpan.span)); - } on MultiSpanSassException catch (error) { - throw MultiSpanSassRuntimeException(error.message, error.span, - error.primaryLabel, error.secondarySpans, _stackTrace(error.span)); - } catch (error) { + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + nodeWithSpan.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(nodeWithSpan.span)), + stackTrace); + } on MultiSpanSassException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + error.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(error.span)), + stackTrace); + } catch (error, stackTrace) { String? message; try { message = (error as dynamic).message as String; } catch (_) { message = error.toString(); } - throw _exception(message, nodeWithSpan.span); + throwWithTrace(_exception(message, nodeWithSpan.span), stackTrace); } _callableNode = oldCallableNode; @@ -3302,7 +3318,7 @@ class _EvaluateVisitor T _adjustParseError(AstNode nodeWithSpan, T callback()) { try { return callback(); - } on SassFormatException catch (error) { + } on SassFormatException catch (error, stackTrace) { var errorText = error.span.file.getText(0); var span = nodeWithSpan.span; var syntheticFile = span.file @@ -3312,7 +3328,7 @@ class _EvaluateVisitor SourceFile.fromString(syntheticFile, url: span.file.url).span( span.start.offset + error.span.start.offset, span.start.offset + error.span.end.offset); - throw _exception(error.message, syntheticSpan); + throwWithTrace(_exception(error.message, syntheticSpan), stackTrace); } } @@ -3325,15 +3341,17 @@ class _EvaluateVisitor T _addExceptionSpan(AstNode nodeWithSpan, T callback()) { try { return callback(); - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassRuntimeException( - error.message, - nodeWithSpan.span, - error.primaryLabel, - error.secondarySpans, - _stackTrace(nodeWithSpan.span)); - } on SassScriptException catch (error) { - throw _exception(error.message, nodeWithSpan.span); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + nodeWithSpan.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(nodeWithSpan.span)), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, nodeWithSpan.span), stackTrace); } } @@ -3342,15 +3360,17 @@ class _EvaluateVisitor AstNode nodeWithSpan, FutureOr callback()) async { try { return await callback(); - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassRuntimeException( - error.message, - nodeWithSpan.span, - error.primaryLabel, - error.secondarySpans, - _stackTrace(nodeWithSpan.span)); - } on SassScriptException catch (error) { - throw _exception(error.message, nodeWithSpan.span); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + nodeWithSpan.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(nodeWithSpan.span)), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, nodeWithSpan.span), stackTrace); } } @@ -3360,10 +3380,11 @@ class _EvaluateVisitor Future _addErrorSpan(AstNode nodeWithSpan, Future callback()) async { try { return await callback(); - } on SassRuntimeException catch (error) { + } on SassRuntimeException catch (error, stackTrace) { if (!error.span.text.startsWith("@error")) rethrow; - throw SassRuntimeException( - error.message, nodeWithSpan.span, _stackTrace()); + throwWithTrace( + SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), + stackTrace); } } } diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 752c8db61..00dfec164 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 5cdb3467b517bf381d525a1a4bc4f9b6a0eeefad +// Checksum: 75f2c75c86bcf5397b054a6e88d94e44e59512cf // // ignore_for_file: unused_import @@ -627,16 +627,24 @@ class _EvaluateVisitor callback(module); } on SassRuntimeException { rethrow; - } on MultiSpanSassException catch (error) { - throw MultiSpanSassRuntimeException(error.message, error.span, - error.primaryLabel, error.secondarySpans, _stackTrace(error.span)); - } on SassException catch (error) { - throw _exception(error.message, error.span); - } on MultiSpanSassScriptException catch (error) { - throw _multiSpanException( - error.message, error.primaryLabel, error.secondarySpans); - } on SassScriptException catch (error) { - throw _exception(error.message); + } on MultiSpanSassException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + error.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(error.span)), + stackTrace); + } on SassException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, error.span), stackTrace); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + _multiSpanException( + error.message, error.primaryLabel, error.secondarySpans), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace(_exception(error.message), stackTrace); } }); } @@ -1594,16 +1602,16 @@ class _EvaluateVisitor } else { throw "Can't find stylesheet to import."; } - } on SassException catch (error) { - throw _exception(error.message, error.span); - } catch (error) { + } on SassException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, error.span), stackTrace); + } catch (error, stackTrace) { String? message; try { message = (error as dynamic).message as String; } catch (_) { message = error.toString(); } - throw _exception(message); + throwWithTrace(_exception(message), stackTrace); } finally { _importSpan = null; } @@ -2215,12 +2223,12 @@ class _EvaluateVisitor default: throw UnsupportedError('Unknown calculation name "${node.name}".'); } - } on SassScriptException catch (error) { + } on SassScriptException catch (error, stackTrace) { // The simplification logic in the [SassCalculation] static methods will // throw an error if the arguments aren't compatible, but we have access // to the original spans so we can throw a more informative error. _verifyCompatibleNumbers(arguments, node.arguments); - throw _exception(error.message, node.span); + throwWithTrace(_exception(error.message, node.span), stackTrace); } } @@ -2564,24 +2572,32 @@ class _EvaluateVisitor result = callback(evaluated.positional); } on SassRuntimeException { rethrow; - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassRuntimeException( - error.message, - nodeWithSpan.span, - error.primaryLabel, - error.secondarySpans, - _stackTrace(nodeWithSpan.span)); - } on MultiSpanSassException catch (error) { - throw MultiSpanSassRuntimeException(error.message, error.span, - error.primaryLabel, error.secondarySpans, _stackTrace(error.span)); - } catch (error) { + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + nodeWithSpan.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(nodeWithSpan.span)), + stackTrace); + } on MultiSpanSassException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + error.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(error.span)), + stackTrace); + } catch (error, stackTrace) { String? message; try { message = (error as dynamic).message as String; } catch (_) { message = error.toString(); } - throw _exception(message, nodeWithSpan.span); + throwWithTrace(_exception(message, nodeWithSpan.span), stackTrace); } _callableNode = oldCallableNode; @@ -3272,7 +3288,7 @@ class _EvaluateVisitor T _adjustParseError(AstNode nodeWithSpan, T callback()) { try { return callback(); - } on SassFormatException catch (error) { + } on SassFormatException catch (error, stackTrace) { var errorText = error.span.file.getText(0); var span = nodeWithSpan.span; var syntheticFile = span.file @@ -3282,7 +3298,7 @@ class _EvaluateVisitor SourceFile.fromString(syntheticFile, url: span.file.url).span( span.start.offset + error.span.start.offset, span.start.offset + error.span.end.offset); - throw _exception(error.message, syntheticSpan); + throwWithTrace(_exception(error.message, syntheticSpan), stackTrace); } } @@ -3295,15 +3311,17 @@ class _EvaluateVisitor T _addExceptionSpan(AstNode nodeWithSpan, T callback()) { try { return callback(); - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassRuntimeException( - error.message, - nodeWithSpan.span, - error.primaryLabel, - error.secondarySpans, - _stackTrace(nodeWithSpan.span)); - } on SassScriptException catch (error) { - throw _exception(error.message, nodeWithSpan.span); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassRuntimeException( + error.message, + nodeWithSpan.span, + error.primaryLabel, + error.secondarySpans, + _stackTrace(nodeWithSpan.span)), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace(_exception(error.message, nodeWithSpan.span), stackTrace); } } @@ -3313,10 +3331,11 @@ class _EvaluateVisitor T _addErrorSpan(AstNode nodeWithSpan, T callback()) { try { return callback(); - } on SassRuntimeException catch (error) { + } on SassRuntimeException catch (error, stackTrace) { if (!error.span.text.startsWith("@error")) rethrow; - throw SassRuntimeException( - error.message, nodeWithSpan.span, _stackTrace()); + throwWithTrace( + SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), + stackTrace); } } } diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 9fe7b5a53..8b73f2f2a 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -346,11 +346,14 @@ class _SerializeVisitor try { _buffer.forSpan( node.valueSpanForMap, () => node.value.value.accept(this)); - } on MultiSpanSassScriptException catch (error) { - throw MultiSpanSassException(error.message, node.value.span, - error.primaryLabel, error.secondarySpans); - } on SassScriptException catch (error) { - throw SassException(error.message, node.value.span); + } on MultiSpanSassScriptException catch (error, stackTrace) { + throwWithTrace( + MultiSpanSassException(error.message, node.value.span, + error.primaryLabel, error.secondarySpans), + stackTrace); + } on SassScriptException catch (error, stackTrace) { + throwWithTrace( + SassException(error.message, node.value.span), stackTrace); } } } diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 5cbce6a04..4c1c7253f 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0-beta.19 + +* No user-visible changes. + ## 1.0.0-beta.18 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 9e1c8c80f..f24025031 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 1.0.0-beta.18 +version: 1.0.0-beta.19 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: - sass: 1.43.4 + sass: 1.43.5 dependency_overrides: sass: {path: ../..} diff --git a/pubspec.yaml b/pubspec.yaml index a6663d9eb..33834263c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.43.4 +version: 1.43.5-dev description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass