Skip to content

Commit

Permalink
TL-40011: Adding in webkit-style transformation support
Browse files Browse the repository at this point in the history
  • Loading branch information
codyfinegan committed Apr 18, 2024
1 parent a9ede0d commit 8763d10
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 9 deletions.
80 changes: 71 additions & 9 deletions src/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use ScssPhp\ScssPhp\Logger\StreamLogger;
use ScssPhp\ScssPhp\Node\Number;
use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
use ScssPhp\ScssPhp\Transforms\Transformer;
use ScssPhp\ScssPhp\Util\Path;

/**
Expand Down Expand Up @@ -326,10 +327,8 @@ class Compiler
* The directory of the currently processed file
*
* @var string|null
*
* Changed to protected by Totara
*/
protected $currentDirectory;
private $currentDirectory;

/**
* The directory of the input file
Expand All @@ -353,6 +352,20 @@ class Compiler
*/
private $warnedChildFunctions = [];

/**
* Optional file loader
*
* @var callable|null
*/
private $fileLoader = null;

/**
* Used to handle transformations of the tree.
*
* @var Transformer
*/
private Transformer $transformer;

/**
* Constructor
*
Expand All @@ -371,6 +384,7 @@ public function __construct($cacheOptions = null)
}

$this->logger = new StreamLogger(fopen('php://stderr', 'w'), true);
$this->transformer = new Transformer();
}

/**
Expand Down Expand Up @@ -5750,6 +5764,8 @@ public function addFeature($name)
*/
protected function importFile($path, OutputBlock $out)
{
[$path, $transforms] = $this->transformer->extractTransformsFromPath($path);

$this->pushCallStack('import ' . $this->getPrettyPath($path));
// see if tree is cached
$realPath = realpath($path);
Expand All @@ -5758,6 +5774,8 @@ protected function importFile($path, OutputBlock $out)
$realPath = $path;
}

$cacheKey = ($transforms ? implode('!', $transforms) : '') . $realPath;

if (substr($path, -5) === '.sass') {
$this->sourceIndex = \count($this->sourceNames);
$this->sourceNames[] = $path;
Expand All @@ -5767,16 +5785,28 @@ protected function importFile($path, OutputBlock $out)
throw $this->error('The Sass indented syntax is not implemented.');
}

if (isset($this->importCache[$realPath])) {
if (isset($this->importCache[$cacheKey])) {
$this->handleImportLoop($realPath);

$tree = $this->importCache[$realPath];
$tree = $this->importCache[$cacheKey];
} else {
$code = file_get_contents($path);
// Allow the custom file loaders
if ($this->fileLoader !== null) {
$code = call_user_func($this->fileLoader, $path);
} else {
$code = file_get_contents($path);
}

$parser = $this->parserFactory($path);
$tree = $parser->parse($code);

$this->importCache[$realPath] = $tree;
// Apply webpack-style transforms to the tree
if ($transforms) {
// Apply the named transformations
$tree = $this->transformer->applyTransformations($transforms, $path, $tree);
}

$this->importCache[$cacheKey] = $tree;
}

$currentDirectory = $this->currentDirectory;
Expand Down Expand Up @@ -5819,7 +5849,9 @@ public static function isCssImport($url)
}

/**
* Return the file path for an import url if it exists
* Return the file path for an import url if it exists.
*
* This includes an override supporting webkit transformations.
*
* @internal
*
Expand All @@ -5830,6 +5862,16 @@ public static function isCssImport($url)
*/
public function findImport($url, $currentDir = null)
{
$pos = strrpos($url, '!');
if ($pos !== false) {
[$path, $transforms] = $this->transformer->extractTransformsFromPath($url);
$result = $this->findImport($path, $currentDir);
if ($result === null) {
throw $this->error("`$path` file not found for @import");
}
return implode('!', $transforms) . "!$result";
}

// Vanilla css and external requests. These are not meant to be Sass imports.
// Callback importers are still called for BC.
if (self::isCssImport($url)) {
Expand Down Expand Up @@ -6252,7 +6294,7 @@ protected function handleImportLoop($name)
continue;
}

$file = $this->sourceNames[$env->block->sourceIndex] ?? '';
$file = $this->sourceNames[$env->block->sourceIndex] ?? null;

if ($file === null) {
continue;
Expand Down Expand Up @@ -10472,4 +10514,24 @@ protected function libScssphpGlob($args)

return [Type::T_LIST, ',', $listParts];
}

/**
* Set the file loader.
*
* @param callable|null $fileLoader
* @return void
*/
public function setFileLoader(?callable $fileLoader): void
{
$this->fileLoader = $fileLoader;
}

/**
* @param Transformer $transformer
* @return void
*/
public function setTransformer(Transformer $transformer): void
{
$this->transformer = $transformer;
}
}
47 changes: 47 additions & 0 deletions src/Transforms/Resource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace ScssPhp\ScssPhp\Transforms;

use ScssPhp\ScssPhp\Block;

/**
* A resource represents the scss file that is in the process of transforming.
*/
class Resource
{
private bool $modified = false;

public function __construct(protected string $path, protected Block $ast)
{
}

/**
* Return the modified Tree
*
* @return Block
*/
public function getAst(): Block
{
return $this->ast;
}

/**
* Marks the AST has been modified
*
* @return void
*/
public function markASTModified(): void
{
$this->modified = true;
}

/**
* Indicates if the resource AST has been modified or not.
*
* @return bool
*/
public function isASTOnly(): bool
{
return $this->modified;
}
}
18 changes: 18 additions & 0 deletions src/Transforms/ResourceFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace ScssPhp\ScssPhp\Transforms;

use ScssPhp\ScssPhp\Block;

class ResourceFactory
{
/**
* Create a new resource instance to be used in transformations.
*
* @return Resource
*/
public function createResource(string $path, Block $ast): Resource
{
return new Resource($path, $ast);
}
}
12 changes: 12 additions & 0 deletions src/Transforms/Transform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace ScssPhp\ScssPhp\Transforms;

interface Transform
{
/**
* @param Resource $resource
* @return void
*/
public function execute(\ScssPhp\ScssPhp\Transforms\Resource $resource): void;
}
66 changes: 66 additions & 0 deletions src/Transforms/Transformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace ScssPhp\ScssPhp\Transforms;

use ScssPhp\ScssPhp\Block;

class Transformer
{
/**
* @param ResourceFactory|null $factory
* @param array<string, Transform> $transforms
*/
public function __construct(protected ?ResourceFactory $factory = null, protected array $transforms = [])
{
}

public function setResourceFactory(?ResourceFactory $factory): void
{
$this->factory = $factory;
}

public function registerTransform(string $name, Transform $transform): void
{
$this->transforms[$name] = $transform;
}

/**
* @param string[] $transforms
* @param string $path
* @param Block $tree
* @return Block
*/
public function applyTransformations(array $transforms, string $path, Block $tree): Block
{
// Make the resource
$resource = ($this->factory ?? new ResourceFactory())->createResource($path, $tree);

// transforms execute from right to left (like webpack)
$transforms = array_reverse($transforms, true);
foreach ($transforms as $name) {
if (!isset($this->transforms[$name])) {
throw new \Exception('Unknown transform "' . $name . '"');
}
$this->transforms[$name]->execute($resource);
}

return $resource->getAst();
}

/**
* @param string $path
* @return array{0: string, 1: string[]}
*/
public function extractTransformsFromPath(string $path): array
{
$pos = strrpos($path, '!');
$transforms = [];

if ($pos !== false) {
$pathTransforms = substr($path, 0, $pos);
$transforms = !empty($pathTransforms) ? explode('!', $pathTransforms) : [];
$path = substr($path, $pos + 1);
}
return [$path, $transforms];
}
}
Loading

0 comments on commit 8763d10

Please sign in to comment.