From 5727b3c7a28b6a28b83071337a0785553b443e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Tue, 26 May 2026 16:40:19 -0700 Subject: [PATCH] Pretty URL --- lib/src/compiler/utils.ts | 21 ++++++++++++-- lib/src/exception.ts | 3 +- lib/src/utils.ts | 59 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/lib/src/compiler/utils.ts b/lib/src/compiler/utils.ts index 92ce8e42..4270d081 100644 --- a/lib/src/compiler/utils.ts +++ b/lib/src/compiler/utils.ts @@ -174,9 +174,25 @@ export function handleLogEvent( span: span!, }); } else { + const url = event.span?.url; + if (url) { + const index = formatted.indexOf(url); + if (index !== -1) { + const replacement = utils.prettyUrl(url); + if (url !== replacement) { + formatted = + formatted.slice(0, index) + + replacement + + formatted.slice(index + url.length); + } + } + } console.error(formatted); } } else { + let stack = event.stackTrace; + if (stack && options?.legacy) stack = removeLegacyImporter(stack); + if (options?.logger?.warn) { const params: ( | { @@ -192,14 +208,13 @@ export function handleLogEvent( : {deprecation: false}; if (span) params.span = span; - const stack = event.stackTrace; if (stack) { - params.stack = options?.legacy ? removeLegacyImporter(stack) : stack; + params.stack = stack; } options.logger.warn(message, params); } else { - console.error(formatted); + console.error(utils.prettyFormatted(formatted, stack)); } } } diff --git a/lib/src/exception.ts b/lib/src/exception.ts index 3e00f40e..8a30d6e5 100644 --- a/lib/src/exception.ts +++ b/lib/src/exception.ts @@ -5,6 +5,7 @@ import * as proto from './vendor/embedded_sass_pb'; import {Exception as SassException, SourceSpan} from './vendor/sass'; import {deprotofySourceSpan} from './deprotofy-span'; +import {prettyFormatted} from './utils'; export class Exception extends Error implements SassException { readonly sassMessage: string; @@ -12,7 +13,7 @@ export class Exception extends Error implements SassException { readonly span: SourceSpan; constructor(failure: proto.OutboundMessage_CompileResponse_CompileFailure) { - super(failure.formatted); + super(prettyFormatted(failure.formatted, failure.stackTrace)); this.sassMessage = failure.message; this.sassStack = failure.stackTrace; diff --git a/lib/src/utils.ts b/lib/src/utils.ts index 308f56a8..e3158668 100644 --- a/lib/src/utils.ts +++ b/lib/src/utils.ts @@ -124,6 +124,65 @@ export function fileUrlToPathCrossPlatform(fileUrl: url.URL | string): string { return /^\/[A-Za-z]:\//.test(path) ? path.substring(1) : path; } +/** + * Returns a pretty URL similar to Dart's `path.prettyUri`. + */ +export function prettyUrl(url: string): string { + if (!url.startsWith('file:')) return url; + + const absolutePath = fileUrlToPathCrossPlatform(url); + const relativePath = p.relative(process.cwd(), absolutePath); + return relativePath.split(p.sep).length > absolutePath.split(p.sep).length + ? absolutePath + : relativePath; +} + +/** + * Reformat URLs in formatted text from the embedded compiler. + */ +export function prettyFormatted(formatted: string, stack: string): string { + let longest = 0; + + const frames = stack + .split('\n') + .filter(frame => frame !== '') + .map(frame => { + let [location, member] = frame.split(/ +/, 2); + let [url, lineColumn] = location.split(' ', 2); + + url = prettyUrl(url); + location = lineColumn === undefined ? url : `${url} ${lineColumn}`; + + if (location.length > longest) { + longest = location.length; + } + return { + frame, + location, + member, + }; + }); + + let offset = formatted.length; + + frames.reverse().forEach(({frame, location, member}) => { + const index = formatted.lastIndexOf(frame, offset); + if (index !== -1) { + offset = index; + + const replacement = `${location.padEnd(longest)} ${member}`; + if (frame !== replacement) { + formatted = + formatted.slice(0, index) + + replacement + + formatted.slice(index + frame.length); + } + } + }); + + return formatted; +} + /** Returns `path` without an extension, if it had one. */ export function withoutExtension(path: string): string { const extension = p.extname(path);