Skip to content

Commit

Permalink
Merge pull request #616 from stof/location_aware_logger
Browse files Browse the repository at this point in the history
Add an API for location-aware loggers
  • Loading branch information
stof committed Sep 17, 2022
2 parents 846d1b7 + 5824713 commit f8eb36f
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 22 deletions.
76 changes: 76 additions & 0 deletions src/Logger/AdaptingLogger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/**
* SCSSPHP
*
* @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/

namespace ScssPhp\ScssPhp\Logger;

use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\StackTrace\Trace;
use ScssPhp\ScssPhp\Util;
use ScssPhp\ScssPhp\Util\Path;

/**
* @internal
*/
final class AdaptingLogger implements LocationAwareLoggerInterface
{
/**
* @var LoggerInterface
* @readonly
*/
private $logger;

private function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

public static function adaptLogger(LoggerInterface $logger): LocationAwareLoggerInterface
{
if ($logger instanceof LocationAwareLoggerInterface) {
return $logger;
}

return new self($logger);
}

public function warn(string $message, bool $deprecation = false, ?FileSpan $span = null, ?Trace $trace = null)
{
if ($span === null) {
$formattedMessage = $message;
} elseif ($trace !== null) {
// If there's a span and a trace, the span's location information is
// probably duplicated in the trace, so we just use it for highlighting.
$formattedMessage = $message;
// TODO implement the highlight of a span
} else {
$formattedMessage = ' on ' . $span->message("\n" . $message);
}

if ($trace !== null) {
$formattedMessage .= "\n" . Util::indent(rtrim($trace->getFormattedTrace()), 4);
}

$this->logger->warn($formattedMessage, $deprecation);
}

public function debug(string $message, FileSpan $span = null)
{
$location = '';
if ($span !== null) {
$url = $span->getStart()->getSourceUrl() === null ? '-' : Path::prettyUri($span->getStart()->getSourceUrl());
$line = $span->getStart()->getLine() + 1;
$location = "$url:$line ";
}

$this->logger->debug(sprintf("%sDEBUG: %s", $location, $message));
}
}
49 changes: 49 additions & 0 deletions src/Logger/LocationAwareLoggerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/**
* SCSSPHP
*
* @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/

namespace ScssPhp\ScssPhp\Logger;

use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\StackTrace\Trace;

/**
* Interface implemented by loggers for warnings and debug messages.
*
* The official Sass implementation recommends that loggers report the
* messages immediately rather than waiting for the end of the
* compilation, to provide a better debugging experience when the
* compilation does not end (error or infinite loop after the warning
* for instance).
*/
interface LocationAwareLoggerInterface extends LoggerInterface
{
/**
* Emits a warning with the given message.
*
* If $span is passed, it's the location in the Sass source that generated
* the warning. If $trace is passed, it's the Sass stack trace when the
* warning was issued.
* If $deprecation is true, it indicates that this is a deprecation
* warning. Implementations should surface all this information to
* the end user.
*
* @return void
*/
public function warn(string $message, bool $deprecation = false, ?FileSpan $span = null, ?Trace $trace = null);

/**
* Emits a debugging message associated with the given span.
*
* @return void
*/
public function debug(string $message, FileSpan $span = null);
}
9 changes: 6 additions & 3 deletions src/Logger/QuietLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@

namespace ScssPhp\ScssPhp\Logger;

use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\StackTrace\Trace;

/**
* A logger that silently ignores all messages.
*/
final class QuietLogger implements LoggerInterface
final class QuietLogger implements LocationAwareLoggerInterface
{
public function warn(string $message, bool $deprecation = false)
public function warn(string $message, bool $deprecation = false, ?FileSpan $span = null, ?Trace $trace = null): void
{
}

public function debug(string $message)
public function debug(string $message, FileSpan $span = null): void
{
}
}
2 changes: 2 additions & 0 deletions src/Logger/StreamLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

/**
* A logger that prints to a PHP stream (for instance stderr)
*
* TODO implement LocationAwareLoggerInterface once the compiler is migrated to actually provide the location
*/
final class StreamLogger implements LoggerInterface
{
Expand Down
8 changes: 5 additions & 3 deletions src/Parser/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
namespace ScssPhp\ScssPhp\Parser;

use ScssPhp\ScssPhp\Exception\SassFormatException;
use ScssPhp\ScssPhp\Logger\AdaptingLogger;
use ScssPhp\ScssPhp\Logger\LocationAwareLoggerInterface;
use ScssPhp\ScssPhp\Logger\LoggerInterface;
use ScssPhp\ScssPhp\Logger\QuietLogger;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
Expand All @@ -32,7 +34,7 @@ class Parser
protected $scanner;

/**
* @var LoggerInterface
* @var LocationAwareLoggerInterface
* @readonly
*/
protected $logger;
Expand Down Expand Up @@ -70,7 +72,7 @@ public static function isIdentifier(string $text, ?LoggerInterface $logger = nul
public function __construct(string $contents, ?LoggerInterface $logger = null, ?string $sourceUrl = null)
{
$this->scanner = new StringScanner($contents);
$this->logger = $logger ?: new QuietLogger();
$this->logger = AdaptingLogger::adaptLogger($logger ?? new QuietLogger());
$this->sourceUrl = $sourceUrl;
}

Expand Down Expand Up @@ -887,7 +889,7 @@ protected function rawText(callable $consumer): string
*/
protected function warn(string $message, FileSpan $span): void
{
$this->logger->warn($span->message($message));
$this->logger->warn($message, false, $span);
}

/**
Expand Down
14 changes: 1 addition & 13 deletions src/Parser/ScssParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,7 @@ protected function scanElse(int $ifIndentation): bool
}

if ($this->scanIdentifier('elseif', true)) {
$span = $this->scanner->spanFrom($beforeAt);
$line = $span->getStart()->getLine() + 1;
$column = $span->getStart()->getColumn() + 1;
$sourceUrl = $this->sourceUrl;

$message = "@elseif is deprecated and will not be supported in future Sass versions.\nUse \"@else if\" instead.";
$message .= "\n on line $line, column $column";

if ($sourceUrl !== null) {
$message .= " of $sourceUrl";
}

$this->logger->warn($message, true);
$this->logger->warn("@elseif is deprecated and will not be supported in future Sass versions.\n\nRecommendation: @else if", true, $this->scanner->spanFrom($beforeAt));

$this->scanner->setPosition($this->scanner->getPosition() - 2);

Expand Down
6 changes: 3 additions & 3 deletions src/Parser/StylesheetParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1592,7 +1592,7 @@ protected function mozDocumentRule(int $start, Interpolation $name): AtRule

return $this->withChildren($this->statementCallable, $start, function (array $children, FileSpan $span) use ($name, $value, $needsDeprecationWarning) {
if ($needsDeprecationWarning) {
$this->logger->warn($span->message("@-moz-document is deprecated and support will be removed in Dart Sass 2.0.0.\n\nFor details, see http://bit.ly/MozDocument."), true);
$this->logger->warn("@-moz-document is deprecated and support will be removed in Dart Sass 2.0.0.\n\nFor details, see http://bit.ly/MozDocument.", true, $span);
}

return new AtRule($name, $span, $value, $children);
Expand Down Expand Up @@ -4022,12 +4022,12 @@ private function mediaInParens(InterpolationBuffer $buffer): void
$expression = $this->expressionUntilComparison();

if ($needsParenDeprecation || $needsNotDeprecation) {
$this->logger->warn($expression->getSpan()->message(sprintf(
$this->logger->warn(sprintf(
"Starting a @media query with \"%s\" is deprecated because it conflicts with official CSS syntax.\n\nTo preserve existing behavior: #{%s}\nTo migrate to new behavior: #{\"%s\"}\n\nFor details, see https://sass-lang.com/d/media-logic",
$needsParenDeprecation ? '(' : 'not',
$expression,
$expression
)), true);
), true, $expression->getSpan());
}

$buffer->add($expression);
Expand Down
11 changes: 11 additions & 0 deletions src/SourceSpan/SourceLocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,24 @@ public function getOffset(): int
return $this->offset;
}

/**
* The 0-based line of that location
*/
public function getLine(): int
{
return $this->file->getLine($this->offset);
}

/**
* The 0-based column of that location
*/
public function getColumn(): int
{
return $this->file->getColumn($this->offset);
}

public function getSourceUrl(): ?string
{
return $this->file->getSourceUrl();
}
}
123 changes: 123 additions & 0 deletions src/StackTrace/Frame.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

/**
* SCSSPHP
*
* @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/

namespace ScssPhp\ScssPhp\StackTrace;

use ScssPhp\ScssPhp\Util\Path;

/**
* A single stack frame. Each frame points to a precise location in Sass code.
*/
final class Frame
{
/**
* The URI of the file in which the code is located.
*
* @var string
* @readonly
*/
private $url;

/**
* The line number on which the code location is located.
*
* This can be null, indicating that the line number is unknown or
* unimportant.
*
* @var int|null
* @readonly
*/
private $line;

/**
* The column number of the code location.
*
* This can be null, indicating that the column number is unknown or
* unimportant.
*
* @var int|null
* @readonly
*/
private $column;

/**
* The name of the member in which the code location occurs.
*
* @var string|null
* @readonly
*/
private $member;

public function __construct(string $url, ?int $line, ?int $column, ?string $member)
{
$this->url = $url;
$this->line = $line;
$this->column = $column;
$this->member = $member;
}

/**
* The URI of the file in which the code is located.
*/
public function getUrl(): string
{
return $this->url;
}

/**
* The line number on which the code location is located.
*
* This can be null, indicating that the line number is unknown or
* unimportant.
*/
public function getLine(): ?int
{
return $this->line;
}

/**
* The column number of the code location.
*
* This can be null, indicating that the column number is unknown or
* unimportant.
*/
public function getColumn(): ?int
{
return $this->column;
}

/**
* The name of the member in which the code location occurs.
*/
public function getMember(): ?string
{
return $this->member;
}

/**
* A human-friendly description of the code location.
*/
public function getLocation(): string
{
$library = Path::prettyUri($this->url);

if ($this->line === null) {
return $library;
}

if ($this->column === null) {
return $library . ' ' . $this->line;
}

return $library . ' ' . $this->line . ':' . $this->column;
}
}

0 comments on commit f8eb36f

Please sign in to comment.