update lo less-php 5.31

Less-php is no nearly less 3.13-compatible
This commit is contained in:
gtbu 2025-06-22 17:28:43 +02:00
parent af8b95522b
commit b3f09c3a29
61 changed files with 2177 additions and 1398 deletions

View file

@ -1,187 +0,0 @@
# Changelog
## v4.4.1
Fixed:
* Update `Less_Version::version` and bump `Less_Version::cache_version` (Timo Tijhof)
## v4.4.0
Added:
* Add `image-size()` function, disable base64 for SVG `data-uri()` (Hannah Okwelum) [T353147](https://phabricator.wikimedia.org/T353147)
* Improve support for preserving `!important` via variables (Piotr Miazga) [T362341](https://phabricator.wikimedia.org/T362341)
* Add support for include path inside `data-uri()` (Hannah Okwelum) [T364871](https://phabricator.wikimedia.org/T364871)
Changed, to match Less.js 2.5.3:
* Fix multiplication of mixed units to preserve the first unit (Piotr Miazga) [T362341](https://phabricator.wikimedia.org/T362341)
Fixed:
* Fix checking of guard conditions in nested mixins (Hannah Okwelum) [T352867](https://phabricator.wikimedia.org/T352867)
* Less_Functions: Avoid clobbering `clamp()` with internal helper (Timo Tijhof) [T363728](https://phabricator.wikimedia.org/T363728)
## v4.3.0
Added:
* Support interpolated variable imports, via ImportVisitor (Hannah Okwelum) [T353133](https://phabricator.wikimedia.org/T353133)
* Support rulesets as default values of a mixin parameter (Hannah Okwelum) [T353143](https://phabricator.wikimedia.org/T353143)
* Support `...` expand operator in mixin calls (Piotr Miazga) [T352897](https://phabricator.wikimedia.org/T352897)
* Improve support for `@import (reference)` matching Less.js 2.x (Hannah Okwelum) [T362647](https://phabricator.wikimedia.org/T362647)
Changed:
* Improve `mix()` argument exception message to mention given arg type (Timo Tijhof)
* The `Less_Tree_Import->getPath()` method now reflects the path as written in the source code,
without auto-appended `.less` suffix, matching upstream Less.js 2.5.3 behaviour.
This internal detail is exposed via the deprecated `import_callback` parser option.
It is recommended to migrate to `Less_Parser->SetImportDirs`, which doesn't expose internals,
and is unaffected by this change.
Deprecated:
* Deprecate `import_callback` Less_Parser option. Use `Less_Parser->SetImportDirs` with callback instead.
* Deprecate `Less_Parser->SetInput()` as public method. Use `Less_Parser->parseFile()` instead.
* Deprecate `Less_Parser->CacheFile()` as public method. Use `Less_Cache` API instead.
* Deprecate `Less_Parser::AllParsedFiles()` as static method. Use `Less_Parser->getParsedFiles()` instead.
* Deprecate `Less_Parser->UnsetInput()` as public method, considered internal.
* Deprecate `Less_Parser->save()` as public method, considered internal.
Fixed:
* Fix `replace()` when passed multiple replacements (Roan Kattouw) [T358631](https://phabricator.wikimedia.org/T358631)
* Fix unexpected duplicating of uncalled mixin rules (Hannah Okwelum) [T363076](https://phabricator.wikimedia.org/T363076)
* Fix ParseError for comments after rule name or in `@keyframes` (Piotr Miazga) [T353131](https://phabricator.wikimedia.org/T353131)
* Fix ParseError for comments in more places and preserve them (Piotr Miazga) [T353132](https://phabricator.wikimedia.org/T353132)
* Fix ParseError effecting pseudo classes with `when` guards (Piotr Miazga) [T353144](https://phabricator.wikimedia.org/T353144)
* Fix preservation of units in some cases (Timo Tijhof) [T360065](https://phabricator.wikimedia.org/T360065)
* Less_Parser: Faster matching by inlining `matcher()` chains (Timo Tijhof)
* Less_Parser: Faster matching with `matchStr()` method (Timo Tijhof)
## v4.2.1
Added:
* Add support for `/deep/` selectors (Hannah Okwelum) [T352862](https://phabricator.wikimedia.org/T352862)
Fixed:
* Fix ParseError in some division expressions (Hannah Okwelum) [T358256](https://phabricator.wikimedia.org/T358256)
* Fix `when()` matching between string and non-string (Timo Tijhof) [T358159](https://phabricator.wikimedia.org/T358159)
* Preserve whitespace before `;` or `!` in simple rules (Hannah Okwelum) [T352911](https://phabricator.wikimedia.org/T352911)
## v4.2.0
Added:
* Add `isruleset()` function (Hannah Okwelum) [T354895](https://phabricator.wikimedia.org/T354895)
* Add source details to "Operation on an invalid type" error (Hannah Okwelum) [T344197](https://phabricator.wikimedia.org/T344197)
* Add support for `method=relative` parameter in color functions (Hannah Okwelum) [T354895](https://phabricator.wikimedia.org/T354895)
* Add support for comments in variables and function parameters (Hannah Okwelum) [T354895](https://phabricator.wikimedia.org/T354895)
* Less_Parser: Add `functions` parser option API (Hannah Okwelum)
Changed, to match Less.js 2.5.3:
* Preserve original color keywords and shorthand hex (Hannah Okwelum) [T352866](https://phabricator.wikimedia.org/T352866)
Fixed:
* Fix PHP Warning when using a dynamic variable name like `@@name` (Hannah Okwelum) [T352830](https://phabricator.wikimedia.org/T352830)
* Fix PHP Warning when `@extend` path contains non-quoted attribute (Gr8b) [T349433](https://phabricator.wikimedia.org/T349433)
* Less_Parser: Faster `skipWhitespace` by using native `strspn` (Umherirrender)
* Less_Parser: Fix Less_Tree_JavaScript references to consistently be in camel-case (Stefan Fröhlich)
* Fix `!important` in nested mixins (Hannah Okwelum) [T353141](https://phabricator.wikimedia.org/T353141)
* Fix crash when using recursive mixins (Timo Tijhof) [T352829](https://phabricator.wikimedia.org/T352829)
* Fix disappearing selectors in certain nested blocks (Hannah Okwelum) [T352859](https://phabricator.wikimedia.org/T352859)
* Fix Less_Exception_Compiler when passing unquoted value to `color()` (Hannah Okwelum) [T353289](https://phabricator.wikimedia.org/T353289)
* Fix order of comments in `@font-face` blocks (Timo Tijhof) [T356706](https://phabricator.wikimedia.org/T356706)
* Fix string comparison to ignore quote type (Timo Tijhof) [T357160](https://phabricator.wikimedia.org/T357160)
* Fix string interpolation in selectors (Hannah Okwelum) [T353142](https://phabricator.wikimedia.org/T353142)
## v4.1.1
* Less_Parser: Faster `MatchQuoted` by using native `strcspn`. (Thiemo Kreuz)
* Less_Parser: Faster `parseEntitiesQuoted` by inlining `MatchQuoted`. (Thiemo Kreuz)
* Less_Parser: Faster `parseUnicodeDescriptor` and `parseEntitiesJavascript` by first-char checks. (Thiemo Kreuz)
* Less_Tree_Mixin_Call: Include mixin name in error message (Jeremy P)
* Fix mismatched casing in class names to fix autoloading on case-sensitive filesystems (Jeremy P)
## v4.1.0
* Add support for `@supports` blocks. (Anne Tomasevich) [T332923](http://phabricator.wikimedia.org/T332923)
* Less_Parser: Returning a URI from `SetImportDirs()` callbacks is now optional. (Timo Tijhof)
## v4.0.0
* Remove support for PHP 7.2 and 7.3. Raise requirement to PHP 7.4+.
* Remove support for `cache_method=php` and `cache_method=var_export`, only the faster and more secure `cache_method=serialize` is now available. The built-in cache remains disabled by default.
* Fix `url(#myid)` to be treated as absolute URL. [T331649](https://phabricator.wikimedia.org/T331688)
* Fix "Undefined property" PHP 8.1 warning when `calc()` is used with CSS `var()`. [T331688](https://phabricator.wikimedia.org/T331688)
* Less_Parser: Improve performance by removing MatchFuncs and NewObj overhead. (Timo Tijhof)
## v3.2.1
* Tree_Ruleset: Fix support for nested parent selectors (Timo Tijhof) [T204816](https://phabricator.wikimedia.org/T204816)
* Fix ParseError when interpolating variable after colon in selector (Timo Tijhof) [T327163](https://phabricator.wikimedia.org/T327163)
* Functions: Fix "Undefined property" warning on bad minmax arg
* Tree_Call: Include previous exception when catching functions (Robert Frunzke)
## v3.2.0
* Fix "Implicit conversion" PHP 8.1 warnings (Ayokunle Odusan)
* Fix "Creation of dynamic property" PHP 8.2 warnings (Bas Couwenberg)
* Fix "Creation of dynamic property" PHP 8.2 warnings (Rajesh Kumar)
* Tree_Url: Add support for "Url" type to `Parser::getVariables()` (ciroarcadio) [#51](https://github.com/wikimedia/less.php/pull/51)
* Tree_Import: Add support for importing URLs without file extension (Timo Tijhof) [#27](https://github.com/wikimedia/less.php/issues/27)
## v3.1.0
* Add PHP 8.0 support: Drop use of curly braces for sub-string eval (James D. Forrester)
* Make `Directive::__construct` $rules arg optional (fix PHP 7.4 warning) (Sam Reed)
* ProcessExtends: Improve performance by using a map for selectors and parents (Andrey Legayev)
## v3.0.0
* Raise PHP requirement from 7.1 to 7.2.9 (James Forrester)
## v2.0.0
* Relax PHP requirement down to 7.1, from 7.2.9 (Franz Liedke)
* Reflect recent breaking changes properly with the semantic versioning (James Forrester)
## v1.8.2
* Require PHP 7.2.9+, up from 5.3+ (James Forrester)
* release: Update Version.php with the current release ID (COBadger)
* Fix access array offset on value of type null (Michele Locati)
* Fix test suite on PHP 7.4 (Sergei Morozov)
## v1.8.1
* Another PHP 7.3 compatibility tweak
## v1.8.0
Library forked by Wikimedia, from [oyejorge/less.php](https://github.com/oyejorge/less.php).
* Supports up to PHP 7.3
* No longer tested against PHP 5, though it's still remains allowed in `composer.json` for HHVM compatibility
* Switched to [semantic versioning](https://semver.org/), hence version numbers now use 3 digits
## v1.7.0.13
* Fix composer.json (PSR-4 was invalid)
## v1.7.0.12
* set bin/lessc bit executable
* Add `gettingVariables` method to `Less_Parser`
## v1.7.0.11
* Fix realpath issue (windows)
* Set Less_Tree_Call property back to public ( Fix 258 266 267 issues from oyejorge/less.php)
## v1.7.0.10
* Add indentation option
* Add `optional` modifier for `@import`
* Fix $color in Exception messages
* take relative-url into account when building the cache filename
* urlArgs should be string no array()
* fix missing on NameValue type [#269](https://github.com/oyejorge/less.php/issues/269)
## v1.7.0.9
* Remove space at beginning of Version.php
* Revert require() paths in test interface

View file

@ -1,78 +0,0 @@
# Maintainers guide
## Release process
1. **Changelog.** Add a new section to the top of `CHANGES.md` with the output from `composer changelog`.
Edit your new section by following the [Keep a changelog](https://keepachangelog.com/en/1.0.0/) conventions, where by bullet points are under one of the "Added", "Changed", "Fixed", "Deprecated", or "Removed" labels.
Review each point and make sure it is phrased in a way that explains the impact on end-users of the library. If the change does not affect the public API or CSS output, remove the bullet point.
2. **Version bump.** Update `/lib/Less/Version.php` and set `version` to the version that you're about to release. Also increase `cache_version` to increment the last number.
3. **Commit.** Stage and commit your changes with the message `Tag vX.Y.Z`, and then push the commit for review.
4. **Tag.** After the above release commit is merged, checkout the master branch and pull down the latest changes. Then create a `vX.Y.Z` tag and push the tag.
Remember to, after the commit is merged, first checkout the master branch and pull down the latest changes. This is to make sure you have the merged version and not the draft commit that you pushed for review.
## Internal overview
This is an overview of the high-level steps during the transformation
from Less to CSS, and how they compare between Less.js and Less.php.
Less.js:
* `less.render(input, { paths: … })`
* `Parser.parse` normalizes input
* `Parser.parse` parses input into rules via `parsers.primary`
* `Parser.parse` creates the "root" ruleset object
* `Parser.parse` applies ImportVisitor
* `ImportVisitor` applies these steps to each `Import` node:
* `ImportVisitor#processImportNode`
* `Import#evalForImport`
* `ImportVisitor` ends with `ImporVisitor#tryRun` loop (async, after last call to `ImportVisitor#onImported`.
* `less.render` callback
* `ParseTree.prototype.toCSS`
* `transformTree` applies pre-visitors, compiles all rules, and applies post-visitors.
* `ParseTree.prototype.toCSS` runs toCSS transform on the "root" ruleset.
* CSS result ready!
Less.php
* `Less_Parser->parseFile`
* `Less_Parser->_parse`
* `Less_Parser->GetRules` normalizes input (via `Less_Parser->SetInput`)
* `Less_Parser->GetRules` parses input into rules via `Less_Parser->parsePrimary`
* `Less_Parser->getCss`
* `Less_Parser->getCss` creates the "root" ruleset object
* `Less_Parser->getCss` applies Less_ImportVisitor
* `Less_ImportVisitor` applies these steps to each `Import` node:
* `ImportVisitor->processImportNode`
* `Less_Tree_Import->compileForImport`
* `ImportVisitor` ends with `ImporVisitor#tryRun` loop (all sync, no async needed).
* `Less_Parser->getCss` applies pre-visitors, compiles all rules, and applies post-visitors.
* `Less_Parser->getCss` runs toCSS transform on the "root" ruleset.
* CSS result ready!
## Compatibility
The `wikimedia/less.php` package inherits a long history of loosely compatible
and interchangable Less compilers written in PHP.
Starting with less.php v3.2.1 (released in 2023), the public API is more clearly
documented, and internal code is now consistently marked `@private`.
The public API includes the `Less_Parser` class and several of its public methods.
For legacy reasons, some of its internal methods remain public. Maintainers must
take care to search the following downstream applications when changing or
removing public methods. If a method has one or more references in the below
codebases, treat it as a breaking change and document a migration path in the
commit message (and later in CHANGES.md), even if the method was undocumented
or feels like it is for internal use only.
* [MediaWiki (source code)](https://codesearch.wmcloud.org/core/?q=Less_Parser&files=php%24)
* [Matomo (source code)](https://github.com/matomo-org/matomo/blob/5.0.2/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php)
* [Adobe Magento (source code)](https://github.com/magento/magento2/blob/2.4.6/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php)
* [Shopware 5 (source code)](https://github.com/shopware5/shopware/blob/5.7/engine/Shopware/Components/Theme/LessCompiler/Oyejorge.php)
* [Winter CMS Assetic (source code)](https://github.com/assetic-php/assetic/tree/v3.1.0/src/Assetic/Filter)

View file

@ -20,6 +20,9 @@ class Less_Cache {
*/
public static $gc_lifetime = 604800;
/** @var bool */
private static $gc_done = false;
/**
* Save and reuse the results of compiled less files.
* The first call to Get() will generate css and save it.
@ -56,7 +59,7 @@ class Less_Cache {
throw new Exception( 'prefix_vars not set' );
}
self::CheckCacheDir();
self::$cache_dir = self::CheckCacheDir();
$less_files = (array)$less_files;
// create a file for variables
@ -113,7 +116,12 @@ class Less_Cache {
file_put_contents( $output_file, $compiled );
// clean up
self::CleanCache();
// Garbage collection can be slow, so run it only on cache misses,
// and at most once per process.
if ( !self::$gc_done ) {
self::$gc_done = true;
self::CleanCache();
}
return basename( $output_file );
}
@ -180,45 +188,46 @@ class Less_Cache {
}
public static function SetCacheDir( $dir ) {
self::$cache_dir = $dir;
self::CheckCacheDir();
}
public static function CheckCacheDir() {
self::$cache_dir = str_replace( '\\', '/', self::$cache_dir );
self::$cache_dir = rtrim( self::$cache_dir, '/' ) . '/';
if ( !file_exists( self::$cache_dir ) ) {
if ( !mkdir( self::$cache_dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . self::$cache_dir );
}
} elseif ( !is_dir( self::$cache_dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . self::$cache_dir );
} elseif ( !is_writable( self::$cache_dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . self::$cache_dir );
}
self::$cache_dir = self::CheckCacheDir( $dir );
}
/**
* Delete unused less.php files
* @deprecated since 5.3.0 Internal for use by Less_Cache and Less_Parser only.
*/
public static function CleanCache() {
static $clean = false;
public static function CheckCacheDir( $dir = null ) {
$dir ??= self::$cache_dir;
$dir = Less_Parser::WinPath( $dir );
$dir = rtrim( $dir, '/' ) . '/';
if ( $clean || empty( self::$cache_dir ) ) {
return;
if ( !file_exists( $dir ) ) {
if ( !mkdir( $dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . $dir );
}
} elseif ( !is_dir( $dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . $dir );
} elseif ( !is_writable( $dir ) ) {
throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . $dir );
}
$clean = true;
return $dir;
}
/**
* @deprecated since 5.3.0 Called automatically. Internal for use by Less_Cache and Less_Parser only.
*/
public static function CleanCache( $dir = null ) {
$dir ??= self::$cache_dir;
if ( !$dir ) {
return;
}
// only remove files with extensions created by less.php
// css files removed based on the list files
$remove_types = [ 'lesscache' => 1, 'list' => 1, 'less' => 1, 'map' => 1 ];
$files = scandir( self::$cache_dir );
$files = scandir( $dir );
if ( !$files ) {
return;
}
@ -238,8 +247,8 @@ class Less_Cache {
continue;
}
$full_path = self::$cache_dir . $file;
$mtime = filemtime( $full_path );
$fullPath = $dir . $file;
$mtime = filemtime( $fullPath );
// don't delete if it's a relatively new file
if ( $mtime > $check_time ) {
@ -248,16 +257,16 @@ class Less_Cache {
// delete the list file and associated css file
if ( $type === 'list' ) {
self::ListFiles( $full_path, $list, $css_file_name );
self::ListFiles( $fullPath, $list, $css_file_name );
if ( $css_file_name ) {
$css_file = self::$cache_dir . $css_file_name;
$css_file = $dir . $css_file_name;
if ( file_exists( $css_file ) ) {
unlink( $css_file );
}
}
}
unlink( $full_path );
unlink( $fullPath );
}
}

View file

@ -20,6 +20,15 @@ class Less_Environment {
* @var array
*/
public $frames = [];
/** @var array */
public $importantScope = [];
/** @var bool */
public $inCalc = false;
/** @var bool */
public $mathOn = true;
/** @var true[] */
private $calcStack = [];
/** @var Less_Tree_Media[] */
public $mediaBlocks = [];
@ -29,9 +38,6 @@ class Less_Environment {
/** @var string[] */
public $imports = [];
/** @var array */
public $importantScope = [];
/**
* This is the equivalent of `importVisitor.onceFileDetectionMap`
* as used by the dynamic `importNode.skip` function.
@ -41,19 +47,27 @@ class Less_Environment {
*/
public $importVisitorOnceMap = [];
public static $parensStack = 0;
/** @var int */
public static $tabLevel = 0;
/** @var bool */
public static $lastRule = false;
/** @var array<string,true> */
public static $_noSpaceCombinators;
/** @var int */
public static $mixin_stack = 0;
public $strictMath = false;
/** @var int */
public $math = self::MATH_PARENS_DIVISION;
public $importCallback = null;
/** @var true[] */
public $parensStack = [];
public const MATH_ALWAYS = 0;
public const MATH_PARENS_DIVISION = 1;
public const MATH_PARENS = 2;
/**
* @var array
@ -61,7 +75,6 @@ class Less_Environment {
public $functions = [];
public function Init() {
self::$parensStack = 0;
self::$tabLevel = 0;
self::$lastRule = false;
self::$mixin_stack = 0;
@ -101,16 +114,41 @@ class Less_Environment {
$new_env = new self();
$new_env->frames = $frames;
$new_env->importantScope = $this->importantScope;
$new_env->strictMath = $this->strictMath;
$new_env->math = $this->math;
return $new_env;
}
/**
* @return bool
* @see Eval.prototype.isMathOn in less.js 3.0.0 https://github.com/less/less.js/blob/v3.0.0/dist/less.js#L1007
* @see less-3.13.1.js#Eval.prototype.isMathOn
*/
public function isMathOn() {
return $this->strictMath ? (bool)self::$parensStack : true;
public function isMathOn( $op = "" ) {
if ( !$this->mathOn ) {
return false;
}
if ( $op === '/' && $this->math !== $this::MATH_ALWAYS && !$this->parensStack ) {
return false;
}
if ( $this->math > $this::MATH_PARENS_DIVISION ) {
return (bool)$this->parensStack;
}
return true;
}
/**
* @see less-3.13.1.js#Eval.prototype.inParenthesis
*/
public function inParenthesis() {
// Optimization: We don't need undefined/null, always have an array
$this->parensStack[] = true;
}
/**
* @see less-3.13.1.js#Eval.prototype.inParenthesis
*/
public function outOfParenthesis() {
array_pop( $this->parensStack );
}
/**
@ -122,18 +160,15 @@ class Less_Environment {
return !preg_match( '/^(?:[a-z-]+:|\/|#)/', $path );
}
/**
* Apply legacy 'import_callback' option.
*
* See Less_Parser::$default_options to learn more about the 'import_callback' option.
* This option is deprecated in favour of Less_Parser::SetImportDirs.
*
* @param Less_Tree_Import $importNode
* @return array{0:string,1:string|null}|null Array containing path and (optional) uri or null
*/
public function callImportCallback( Less_Tree_Import $importNode ) {
if ( is_callable( $this->importCallback ) ) {
return ( $this->importCallback )( $importNode );
public function enterCalc() {
$this->calcStack[] = true;
$this->inCalc = true;
}
public function exitCalc() {
array_pop( $this->calcStack );
if ( !$this->calcStack ) {
$this->inCalc = false;
}
}

View file

@ -4,10 +4,13 @@
*/
class Less_Exception_Chunk extends Less_Exception_Parser {
/** @var int */
protected $parserCurrentIndex = 0;
/** @var int */
protected $emitFrom = 0;
/** @var int */
protected $input_len;
/**
@ -17,7 +20,7 @@ class Less_Exception_Chunk extends Less_Exception_Parser {
* @param array|null $currentFile The file
* @param int $code The exception code
*/
public function __construct( $input, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
public function __construct( $input, ?Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
$this->message = 'ParseError: Unexpected input'; // default message
$this->index = $index;

View file

@ -19,10 +19,9 @@ class Less_Exception_Parser extends Exception {
*/
public $index;
/** @var string|null */
protected $input;
protected $details = [];
/**
* @param string|null $message
* @param Exception|null $previous Previous exception
@ -30,7 +29,7 @@ class Less_Exception_Parser extends Exception {
* @param array|null $currentFile The file
* @param int $code The exception code
*/
public function __construct( $message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
public function __construct( $message = null, ?Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
parent::__construct( $message, $code, $previous );
$this->currentFile = $currentFile;

View file

@ -8,7 +8,7 @@ class Less_FileManager {
* @see less-node/FileManager.getPath https://github.com/less/less.js/blob/v2.5.3/lib/less-node/file-manager.js#L70
* @param string $filename
* @param null|array $currentFileInfo
* @return null|array
* @return null|array{0:string,1:string}
*/
public static function getFilePath( $filename, $currentFileInfo ) {
if ( !$filename ) {

View file

@ -6,7 +6,9 @@
*/
class Less_Functions {
/** @var Less_Environment */
public $env;
/** @var array|null */
public $currentFileInfo;
public function __construct( $env, ?array $currentFileInfo = null ) {
@ -105,7 +107,7 @@ class Less_Functions {
* @param float $a
*/
public function hsva( $h, $s, $v, $a ) {
$h = ( ( self::_number( $h ) % 360 ) / 360 ) * 360;
$h = ( ( (int)self::_number( $h ) % 360 ) / 360 ) * 360;
$s = self::_number( $s );
$v = self::_number( $v );
$a = self::_number( $a );
@ -139,7 +141,9 @@ class Less_Functions {
public function hue( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$c = $color->toHSL();
@ -148,7 +152,9 @@ class Less_Functions {
public function saturation( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$c = $color->toHSL();
@ -157,7 +163,9 @@ class Less_Functions {
public function lightness( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$c = $color->toHSL();
@ -166,7 +174,9 @@ class Less_Functions {
public function hsvhue( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsv = $color->toHSV();
@ -175,7 +185,9 @@ class Less_Functions {
public function hsvsaturation( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsv = $color->toHSV();
@ -184,7 +196,9 @@ class Less_Functions {
public function hsvvalue( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsv = $color->toHSV();
@ -193,7 +207,9 @@ class Less_Functions {
public function red( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return new Less_Tree_Dimension( $color->rgb[0] );
@ -201,7 +217,9 @@ class Less_Functions {
public function green( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return new Less_Tree_Dimension( $color->rgb[1] );
@ -209,7 +227,9 @@ class Less_Functions {
public function blue( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return new Less_Tree_Dimension( $color->rgb[2] );
@ -217,7 +237,9 @@ class Less_Functions {
public function alpha( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$c = $color->toHSL();
@ -226,7 +248,9 @@ class Less_Functions {
public function luma( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return new Less_Tree_Dimension( $color->luma() * $color->alpha * 100, '%' );
@ -234,13 +258,15 @@ class Less_Functions {
public function luminance( $color = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$luminance =
( 0.2126 * $color->rgb[0] / 255 )
+ ( 0.7152 * $color->rgb[1] / 255 )
+ ( 0.0722 * $color->rgb[2] / 255 );
+ ( 0.7152 * $color->rgb[1] / 255 )
+ ( 0.0722 * $color->rgb[2] / 255 );
return new Less_Tree_Dimension( $luminance * $color->alpha * 100, '%' );
}
@ -253,10 +279,14 @@ class Less_Functions {
}
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -273,13 +303,18 @@ class Less_Functions {
/**
* @param Less_Tree_Color|null $color
* @param Less_Tree_Dimension|null $amount
* @param Less_Tree_Quoted|Less_Tree_Color|Less_Tree_Keyword|null $method
*/
public function desaturate( $color = null, $amount = null, $method = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -297,10 +332,14 @@ class Less_Functions {
public function lighten( $color = null, $amount = null, $method = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -318,10 +357,14 @@ class Less_Functions {
public function darken( $color = null, $amount = null, $method = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -337,10 +380,14 @@ class Less_Functions {
public function fadein( $color = null, $amount = null, $method = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -357,10 +404,14 @@ class Less_Functions {
public function fadeout( $color = null, $amount = null, $method = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -377,10 +428,14 @@ class Less_Functions {
public function fade( $color = null, $amount = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -392,10 +447,14 @@ class Less_Functions {
public function spin( $color = null, $amount = null ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$amount instanceof Less_Tree_Dimension ) {
throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$hsl = $color->toHSL();
@ -419,18 +478,24 @@ class Less_Functions {
public function mix( $color1 = null, $color2 = null, $weight = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
$type = is_object( $color1 ) ? get_class( $color1 ) : gettype( $color1 );
throw new Less_Exception_Compiler( "The first argument must be a color, $type given" . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
"The first argument must be a color, $type given" . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
$type = is_object( $color2 ) ? get_class( $color2 ) : gettype( $color2 );
throw new Less_Exception_Compiler( "The second argument must be a color, $type given" . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
"The second argument must be a color, $type given" . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$weight ) {
$weight = new Less_Tree_Dimension( '50', '%' );
}
if ( !$weight instanceof Less_Tree_Dimension ) {
$type = is_object( $weight ) ? get_class( $weight ) : gettype( $weight );
throw new Less_Exception_Compiler( "The third argument must be a percentage, $type given" . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
"The third argument must be a percentage, $type given" . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
$p = $weight->value / 100.0;
@ -471,10 +536,14 @@ class Less_Functions {
}
if ( !$dark instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$light instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
// Figure out which is actually light and dark!
@ -504,7 +573,18 @@ class Less_Functions {
}
public function escape( $str ) {
$revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%3F' => '?', '%26' => '&', '%2C' => ',', '%2F' => '/', '%40' => '@', '%2B' => '+', '%24' => '$' ];
$revert = [
'%21' => '!',
'%2A' => '*',
'%27' => "'",
'%3F' => '?',
'%26' => '&',
'%2C' => ',',
'%2F' => '/',
'%40' => '@',
'%2B' => '+',
'%24' => '$'
];
return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
}
@ -562,7 +642,9 @@ class Less_Functions {
public function unit( $val, $unit = null ) {
if ( !( $val instanceof Less_Tree_Dimension ) ) {
throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
throw new Less_Exception_Compiler(
'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' )
);
}
if ( $unit ) {
@ -712,7 +794,7 @@ class Less_Functions {
$unit = $currentUnified->unit->toString();
}
if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
if ( ( $unit !== '' && !$unitStatic ) || ( $unit !== '' && $order[0]->unify()->unit->toString() === "" ) ) {
$unitStatic = $unit;
}
@ -769,7 +851,9 @@ class Less_Functions {
public function argb( $color ) {
if ( !$color instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return new Less_Tree_Anonymous( $color->toARGB() );
@ -856,27 +940,42 @@ class Less_Functions {
return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
}
public function extract( $values, $index ) {
$index = (int)$index->value - 1; // (1-based index)
/**
* @see less-3.13.1.js#getItemsFromNode
*/
private function getItemsFromNode( Less_Tree $node ) {
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
if ( !( $values instanceof Less_Tree_Color ) && is_array( $values->value ) ) {
if ( isset( $values->value[$index] ) ) {
return $values->value[$index];
}
return null;
} elseif ( (int)$index === 0 ) {
return $values;
}
return null;
//
// NOTE: Less.js uses duck-typing `isArray(node.value)`, which would cause warnings in PHP,
// and potentially bugs for Less_Tree classes with a $value that is only sometimes an array.
// Instead, check for Less_Tree classes that always implement an array $value.
return ( $node instanceof Less_Tree_Expression || $node instanceof Less_Tree_Value )
? $node->value
: [ $node ];
}
/**
* @see less-3.13.1.js#_SELF
*/
public function _self( $args ) {
return $args;
}
/**
* @see less-3.13.1.js#extract
*/
public function extract( $values, $index ) {
// (1-based index)
$index = (int)$index->value - 1;
return $this->getItemsFromNode( $values )[ $index ] ?? null;
}
/**
* @see less-3.13.1.js#length
*/
public function length( $values ) {
$n = ( $values instanceof Less_Tree_Expression || $values instanceof Less_Tree_Value ) ?
count( $values->value ) : 1;
return new Less_Tree_Dimension( $n );
return new Less_Tree_Dimension( count( $this->getItemsFromNode( $values ) ) );
}
/**
@ -974,7 +1073,9 @@ class Less_Functions {
$rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
break;
default:
throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
throw new Less_Exception_Compiler(
"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"
);
}
$returner = '<?xml version="1.0" ?>' .
@ -991,7 +1092,9 @@ class Less_Functions {
$position = null;
}
if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
if ( !( $color instanceof Less_Tree_Color ) ||
( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) )
) {
throw new Less_Exception_Compiler( $throw_message );
}
if ( $position ) {
@ -1002,12 +1105,24 @@ class Less_Functions {
$positionValue = '100%';
}
$alpha = $color->alpha;
$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' .
( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
}
$returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
$revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%26' => '&', '%2C' => ',', '%40' => '@', '%2B' => '+', '%24' => '$', '%28' => '(', '%29' => ')' ];
$revert = [
'%21' => '!',
'%2A' => '*',
'%27' => "'",
'%26' => '&',
'%2C' => ',',
'%40' => '@',
'%2B' => '+',
'%24' => '$',
'%28' => '(',
'%29' => ')'
];
$returner = strtr( rawurlencode( $returner ), $revert );
$returner = "data:image/svg+xml," . $returner;
@ -1091,10 +1206,14 @@ class Less_Functions {
public function multiply( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendMultiply' ], $color1, $color2 );
@ -1106,10 +1225,14 @@ class Less_Functions {
public function screen( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendScreen' ], $color1, $color2 );
@ -1121,10 +1244,14 @@ class Less_Functions {
public function overlay( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendOverlay' ], $color1, $color2 );
@ -1139,10 +1266,14 @@ class Less_Functions {
public function softlight( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendSoftlight' ], $color1, $color2 );
@ -1161,10 +1292,14 @@ class Less_Functions {
public function hardlight( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendHardlight' ], $color1, $color2 );
@ -1176,10 +1311,14 @@ class Less_Functions {
public function difference( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendDifference' ], $color1, $color2 );
@ -1191,10 +1330,14 @@ class Less_Functions {
public function exclusion( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendExclusion' ], $color1, $color2 );
@ -1206,10 +1349,14 @@ class Less_Functions {
public function average( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendAverage' ], $color1, $color2 );
@ -1222,10 +1369,14 @@ class Less_Functions {
public function negation( $color1 = null, $color2 = null ) {
if ( !$color1 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
if ( !$color2 instanceof Less_Tree_Color ) {
throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
throw new Less_Exception_Compiler(
'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' )
);
}
return $this->colorBlend( [ $this, 'colorBlendNegation' ], $color1, $color2 );

View file

@ -1,12 +1,19 @@
<?php
/**
* @private
*/
class Less_ImportVisitor extends Less_Visitor {
/** @var Less_Environment */
public $env;
/** @var array<array{function:string,args:array}> */
public $variableImports = [];
/** @var array<string,true> */
public $recursionDetector = [];
/** @var int */
public $_currentDepth = 0;
/** @var mixed */
public $importItem;
public function __construct( $env ) {
@ -101,16 +108,8 @@ class Less_ImportVisitor extends Less_Visitor {
$path = preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ? $path : $path . '.less';
}
$path_and_uri = $env->callImportCallback( $importNode );
if ( !$path_and_uri ) {
$path_and_uri = Less_FileManager::getFilePath( $path, $importNode->currentFileInfo );
}
if ( $path_and_uri ) {
[ $full_path, $uri ] = $path_and_uri;
} else {
$full_path = $uri = $importNode->getPath();
}
'@phan-var string $full_path';
[ $fullPath, $uri ] =
Less_FileManager::getFilePath( $path, $importNode->currentFileInfo ) ?? [ $path, $path ];
// @see less-2.5.3.js#ImportManager.prototype.push/loadFileCallback
@ -125,22 +124,22 @@ class Less_ImportVisitor extends Less_Visitor {
$e = null;
try {
if ( $importNode->options['inline'] ) {
if ( !file_exists( $full_path ) ) {
if ( !file_exists( $fullPath ) ) {
throw new Less_Exception_Parser(
sprintf( 'File `%s` not found.', $full_path ),
sprintf( 'File `%s` not found.', $fullPath ),
null,
$importNode->index,
$importNode->currentFileInfo
);
}
$root = file_get_contents( $full_path );
$root = file_get_contents( $fullPath );
} else {
$parser = new Less_Parser( $env );
// NOTE: Upstream sets `env->processImports = false` here to avoid
// running ImportVisitor again (infinite loop). We instead separate
// Less_Parser->parseFile() from Less_Parser->getCss(),
// and only getCss() runs ImportVisitor.
$root = $parser->parseFile( $full_path, $uri, true );
$root = $parser->parseFile( $fullPath, $uri, true );
}
} catch ( Less_Exception_Parser $err ) {
$e = $err;
@ -151,7 +150,7 @@ class Less_ImportVisitor extends Less_Visitor {
if ( $importNode->options['optional'] && $e ) {
$e = null;
$root = new Less_Tree_Ruleset( null, [] );
$full_path = null;
$fullPath = null;
}
// @see less-2.5.3.js#ImportVisitor.prototype.onImported
@ -165,7 +164,7 @@ class Less_ImportVisitor extends Less_Visitor {
throw $e;
}
$duplicateImport = isset( $this->recursionDetector[$full_path] );
$duplicateImport = isset( $this->recursionDetector[$fullPath] );
if ( !$env->importMultiple ) {
if ( $duplicateImport ) {
@ -178,16 +177,16 @@ class Less_ImportVisitor extends Less_Visitor {
}
}
if ( !$full_path && $importNode->options['optional'] ) {
if ( !$fullPath && $importNode->options['optional'] ) {
$importNode->doSkip = true;
}
if ( $root ) {
$importNode->root = $root;
$importNode->importedFilename = $full_path;
$importNode->importedFilename = $fullPath;
if ( !$inlineCSS && ( $env->importMultiple || !$duplicateImport ) && $full_path ) {
$this->recursionDetector[$full_path] = true;
if ( !$inlineCSS && ( $env->importMultiple || !$duplicateImport ) && $fullPath ) {
$this->recursionDetector[$fullPath] = true;
$oldContext = $this->env;
$this->env = $env;
$this->visitObj( $root );
@ -222,19 +221,19 @@ class Less_ImportVisitor extends Less_Visitor {
}
}
public function visitRule( $ruleNode, $visitDeeper ) {
public function visitDeclaration( $declNode, $visitDeeper ) {
// TODO: We might need upstream's `if (… DetachedRuleset) { this.context.frames.unshift(ruleNode); }`
$visitDeeper = false;
}
// TODO: We might need upstream's visitRuleOut()
// TODO: Implement less-3.13.1.js#ImportVisitor.prototype.visitDeclarationOut
// if (… DetachedRuleset) { this.context.frames.shift(); }
public function visitDirective( $directiveNode, $visitArgs ) {
array_unshift( $this->env->frames, $directiveNode );
public function visitAtRule( $atRuleNode, $visitArgs ) {
array_unshift( $this->env->frames, $atRuleNode );
}
public function visitDirectiveOut( $directiveNode ) {
public function visitAtRuleOut( $atRuleNode ) {
array_shift( $this->env->frames );
}

View file

@ -1,17 +0,0 @@
./Parser.php
./Colors.php
./Environment.php
./Functions.php
./Mime.php
./Tree.php
./Output.php
./Visitor.php
./VisitorReplacing.php
./Configurable.php
./Tree
./Visitor
./Exception/Parser.php
./Exception/
./Output
./SourceMap

View file

@ -63,8 +63,7 @@ class Less_Output_Mapped extends Less_Output {
$sourceLines = [];
$sourceColumns = ' ';
if ( $fileInfo ) {
if ( isset( $fileInfo['currentUri'] ) ) {
$url = $fileInfo['currentUri'];
if ( isset( $this->contentsMap[$url] ) ) {

File diff suppressed because it is too large Load diff

View file

@ -1,83 +0,0 @@
[![Packagist](https://img.shields.io/packagist/v/wikimedia/less.php.svg?style=flat)](https://packagist.org/packages/wikimedia/less.php)
Less.php
========
This is a PHP port of the [official LESS processor](https://lesscss.org).
## About
The code structure of Less.php mirrors that of upstream Less.js to ensure compatibility and help reduce maintenance. The port is currently compatible with Less.js 2.5.3. Please note that "inline JavaScript expressions" (via eval or backticks) are not supported.
* [API § Caching](./API.md#caching), Less.php includes a file-based cache.
* [API § Source maps](./API.md#source-maps), Less.php supports v3 sourcemaps.
* [API § Command line](./API.md#command-line), the `lessc` command includes a watch mode.
## Installation
You can install the library with Composer or standalone.
If you have [Composer](https://getcomposer.org/download/) installed:
1. Run `composer require wikimedia/less.php`
2. Use `Less_Parser` in your code.
Or standalone:
1. [Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz) and upload the PHP files to your server.
2. Include the library:
```php
require_once '[path to]/less.php/lib/Less/Autoloader.php';
Less_Autoloader::register();
```
3. Use `Less_Parser` in your code.
## Security
The LESS processor language is powerful and includes features that may read or embed arbitrary files that the web server has access to, and features that may be computationally exensive if misused.
In general you should treat LESS files as being in the same trust domain as other server-side executables, such as PHP code. In particular, it is not recommended to allow people that use your web service to provide arbitrary LESS code for server-side processing.
_See also [SECURITY](./SECURITY.md)._
## Who uses Less.php?
* **[Wikipedia](https://en.wikipedia.org/wiki/MediaWiki)** and the MediaWiki platform ([docs](https://www.mediawiki.org/wiki/ResourceLoader/Architecture#Resource:_Styles)).
* **[Matomo](https://en.wikipedia.org/wiki/Matomo_(software))** ([docs](https://developer.matomo.org/guides/asset-pipeline#vanilla-javascript-css-and-less-files)).
* **[Magento](https://en.wikipedia.org/wiki/Magento)** as part of Adobe Commerce ([docs](https://developer.adobe.com/commerce/frontend-core/guide/css/preprocess/)).
* **[Icinga](https://en.wikipedia.org/wiki/Icinga)** in Icinga Web ([docs](https://github.com/Icinga/icingaweb2)).
* **[Shopware](https://de.wikipedia.org/wiki/Shopware)** ([docs](https://developers.shopware.com/designers-guide/less/)).
* **[Winter CMS](https://wintercms.com/)** ([docs](https://wintercms.com/docs/v1.2/docs/themes/development))
## Integrations
Less.php has been integrated with various other projects.
#### Transitioning from Leafo/lessphp
If you're looking to transition from the [Leafo/lessphp](https://github.com/leafo/lessphp) library, use the `lessc.inc.php` adapter file that comes with Less.php.
This allows Less.php to be a drop-in replacement for Leafo/lessphp.
[Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz), unzip the files into your project, and include its `lessc.inc.php` instead.
Note: The `setPreserveComments` option is ignored. Less.php already preserves CSS block comments by default, and removes LESS inline comments.
#### Drupal
Less.php can be used with [Drupal's less module](https://drupal.org/project/less) via the `lessc.inc.php` adapter. [Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz) and unzip it so that `lessc.inc.php` is located at `sites/all/libraries/lessphp/lessc.inc.php`, then install the Drupal less module as usual.
#### WordPress
* [wp_enqueue_less](https://github.com/Ed-ITSolutions/wp_enqueue_less) is a Composer package for use in WordPress themes and plugins. It provides a `wp_enqueue_less()` function to automatically manage caching and compilation on-demand, and loads the compressed CSS on the page.
* [JBST framework](https://github.com/bassjobsen/jamedo-bootstrap-start-theme) bundles a copy of Less.php.
* The [lessphp plugin](https://wordpress.org/plugins/lessphp/) bundles a copy of Less.php for use in other plugins or themes. This dependency can also be combined with the [TGM Library](http://tgmpluginactivation.com/).
## Credits
Less.php was originally ported to PHP in 2011 by [Matt Agar](https://github.com/agar) and then updated by [Martin Jantošovič](https://github.com/Mordred) in 2012. From 2013 to 2017, [Josh Schmidt](https://github.com/oyejorge) lead development of the library. Since 2019, the library is maintained by Wikimedia Foundation.
## Contribute
* Issue tracker: https://phabricator.wikimedia.org/tag/less.php/
* Source code: https://gerrit.wikimedia.org/g/mediawiki/libs/less.php ([Get started with Gerrit](https://www.mediawiki.org/wiki/Gerrit/Tutorial/tl;dr))

View file

@ -20,25 +20,25 @@ class Less_SourceMap_Generator extends Less_Configurable {
// an optional source root, useful for relocating source files
// on a server or removing repeated values in the 'sources' entry.
// This value is prepended to the individual entries in the 'source' field.
'sourceRoot' => '',
'sourceRoot' => '',
// an optional name of the generated code that this source map is associated with.
'sourceMapFilename' => null,
'sourceMapFilename' => null,
// url of the map
'sourceMapURL' => null,
'sourceMapURL' => null,
// absolute path to a file to write the map to
'sourceMapWriteTo' => null,
'sourceMapWriteTo' => null,
// output source contents?
'outputSourceFiles' => false,
'outputSourceFiles' => false,
// base path for filename normalization
'sourceMapRootpath' => '',
'sourceMapRootpath' => '',
// base path for filename normalization
'sourceMapBasepath' => ''
'sourceMapBasepath' => ''
];
/**
@ -72,9 +72,10 @@ class Less_SourceMap_Generator extends Less_Configurable {
/**
* File to content map
*
* @var array
* @var array<string,string>
*/
protected $sources = [];
/** @var array<string,int> */
protected $source_keys = [];
/**
@ -117,10 +118,10 @@ class Less_SourceMap_Generator extends Less_Configurable {
// catch the output
$this->root->genCSS( $output );
$sourceMapUrl = $this->getOption( 'sourceMapURL' );
$sourceMapFilename = $this->getOption( 'sourceMapFilename' );
$sourceMapContent = $this->generateJson();
$sourceMapWriteTo = $this->getOption( 'sourceMapWriteTo' );
$sourceMapUrl = $this->getOption( 'sourceMapURL' );
$sourceMapFilename = $this->getOption( 'sourceMapFilename' );
$sourceMapContent = $this->generateJson();
$sourceMapWriteTo = $this->getOption( 'sourceMapWriteTo' );
if ( !$sourceMapUrl && $sourceMapFilename ) {
$sourceMapUrl = $this->normalizeFilename( $sourceMapFilename );
@ -204,10 +205,12 @@ class Less_SourceMap_Generator extends Less_Configurable {
'generated_column' => $generatedColumn,
'original_line' => $originalLine,
'original_column' => $originalColumn,
'source_file' => $fileInfo['currentUri']
'source_file' => $fileInfo['currentUri'] ?? null
];
$this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
if ( isset( $fileInfo['currentUri'] ) ) {
$this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
}
}
/**
@ -229,7 +232,8 @@ class Less_SourceMap_Generator extends Less_Configurable {
$sourceMap['file'] = $file;
}
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry.
// This value is prepended to the individual entries in the 'source' field.
$root = $this->getOption( 'sourceRoot' );
if ( $root ) {
$sourceMap['sourceRoot'] = $root;
@ -343,7 +347,7 @@ class Less_SourceMap_Generator extends Less_Configurable {
* @return int|false
*/
protected function findFileIndex( $filename ) {
return $this->source_keys[$filename];
return $this->source_keys[$filename] ?? false;
}
/**

View file

@ -5,9 +5,26 @@
*/
class Less_Tree {
/** @var bool */
public $parensInOp = false;
/** @var true|null */
public $extendOnEveryPath;
/** @var Less_Tree_Extend[] */
public $allExtends;
/**
* This is set to true to ensure visibility
* for all except Less_Tree_Anonymous where we decide
* if the the node should be visible or not
*
* @var bool
*/
public $nodeVisible = true;
/**
* @var Less_Parser
* @see less-3.13.1.js#Node.prototype.parse
*/
public static $parse;
/**
* @see less-2.5.3.js#Node.prototype.toCSS
@ -174,6 +191,7 @@ class Less_Tree {
public static function ReferencedArray( $rules ) {
foreach ( $rules as $rule ) {
if ( method_exists( $rule, 'markReferenced' ) ) {
// @phan-suppress-next-line PhanUndeclaredMethod False positive
$rule->markReferenced();
}
}
@ -191,4 +209,11 @@ class Less_Tree {
return $obj;
}
/**
* @see less-3.13.1.js#Node.prototype.isVisible
*/
public function isVisible() {
return $this->nodeVisible;
}
}

View file

@ -4,6 +4,7 @@
* @see less-2.5.3.js#Alpha.prototype
*/
class Less_Tree_Alpha extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string|Less_Tree */
public $value;
/**

View file

@ -4,13 +4,19 @@
* @see less-2.5.3.js#Anonymous.prototype
*/
class Less_Tree_Anonymous extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $value;
/** @var string|null */
public $quote;
/** @var int|null */
public $index;
/** @var bool|null */
public $mapLines;
/** @var array|null */
public $currentFileInfo;
/** @var bool */
public $rulesetLike;
/** @var bool */
public $isReferenced;
/**
@ -27,6 +33,8 @@ class Less_Tree_Anonymous extends Less_Tree implements Less_Tree_HasValuePropert
$this->mapLines = $mapLines;
$this->currentFileInfo = $currentFileInfo;
$this->rulesetLike = $rulesetLike;
// TODO: remove isReferenced and implement $visibilityInfo
// https://github.com/less/less.js/commit/ead3e29f7b79390ad3ac798bf42195b24919107d
$this->isReferenced = $referenced;
}
@ -37,18 +45,24 @@ class Less_Tree_Anonymous extends Less_Tree implements Less_Tree_HasValuePropert
/**
* @param Less_Tree|mixed $x
* @return int|null
* @see less-2.5.3.js#Anonymous.prototype.compare
* @see less-3.13.1.js#Anonymous.prototype.compare
*/
public function compare( $x ) {
return ( is_object( $x ) && $this->toCSS() === $x->toCSS() ) ? 0 : null;
return ( $x instanceof Less_Tree && $this->toCSS() === $x->toCSS() ) ? 0 : null;
}
public function isRulesetLike() {
return $this->rulesetLike;
}
/**
* @see less-3.13.1.js#Anonymous.prototype.genCSS
*/
public function genCSS( $output ) {
$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
$this->nodeVisible = $this->value !== "" && $this->value !== 0;
if ( $this->nodeVisible ) {
$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
}
}
public function markReferenced() {

View file

@ -4,7 +4,9 @@
* @see less-2.5.3.js#Assignment.prototype
*/
class Less_Tree_Assignment extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $key;
/** @var Less_Tree */
public $value;
public function __construct( string $key, Less_Tree $val ) {

View file

@ -1,20 +1,38 @@
<?php
/**
* @private
* @see less-2.5.3.js#Anonymous.prototype
* @see less-3.13.1.js#AtRule.prototype
*/
class Less_Tree_Directive extends Less_Tree implements Less_Tree_HasValueProperty {
class Less_Tree_AtRule extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $name;
/** @var Less_Tree|null */
public $value;
/** @var Less_Tree_Ruleset[]|null */
public $rules;
/** @var int|null */
public $index;
/** @var bool */
public $isReferenced;
/** @var bool */
public $isRooted;
/** @var array|null */
public $currentFileInfo;
/** @var mixed|null */
public $debugInfo;
public function __construct( $name, $value = null, $rules = null, $index = null, $isRooted = false, $currentFileInfo = null, $debugInfo = null, $isReferenced = false ) {
public function __construct(
$name,
$value = null,
$rules = null,
$index = null,
$isRooted = false,
$currentFileInfo = null,
$debugInfo = null,
$isReferenced = false
) {
$this->name = $name;
// TODO: Less.js 3.13 handles `$value instanceof Less_Tree` and creates Anonymous here.
$this->value = $value;
if ( $rules !== null ) {
@ -28,6 +46,7 @@ class Less_Tree_Directive extends Less_Tree implements Less_Tree_HasValuePropert
foreach ( $this->rules as $rule ) {
$rule->allowImports = true;
}
// TODO: Less.js 3.13 handles setParent() here
}
$this->index = $index;
@ -106,11 +125,18 @@ class Less_Tree_Directive extends Less_Tree implements Less_Tree_HasValuePropert
}
public function find( $selector ) {
// TODO: Less.js 3.13.1 adds multiple variadic arguments here
if ( $this->rules ) {
return $this->rules[0]->find( $selector, $this );
}
}
// TODO: Implement less-3.13.1.js#AtRule.prototype.rulesets
// Unused?
// TODO: Implement less-3.13.1.js#AtRule.prototype.outputRuleset
// We have ours in Less_Tree::outputRuleset instead.
public function markReferenced() {
$this->isReferenced = true;
if ( $this->rules ) {

View file

@ -4,8 +4,11 @@
* @see less-2.5.3.js#Attribute.prototype
*/
class Less_Tree_Attribute extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $key;
/** @var null|string */
public $op;
/** @var null|string|Less_Tree */
public $value;
/**
@ -41,7 +44,7 @@ class Less_Tree_Attribute extends Less_Tree implements Less_Tree_HasValuePropert
if ( $this->op ) {
$value .= $this->op;
$value .= ( is_object( $this->value ) ? $this->value->toCSS() : $this->value );
$value .= ( $this->value instanceof Less_Tree ? $this->value->toCSS() : $this->value );
}
return '[' . $value . ']';

View file

@ -1,22 +1,24 @@
<?php
/**
* @private
* @see less.tree.Call in less.js 3.0.0 https://github.com/less/less.js/blob/v3.0.0/dist/less.js#L6336
* @see less-3.13.1.js#Call.prototype
*/
class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
public $value;
class Less_Tree_Call extends Less_Tree {
/** @var string */
public $name;
/** @var Less_Tree[] */
public $args;
/** @var bool */
public $mathOn;
public $calc;
/** @var int */
public $index;
/** @var array|null */
public $currentFileInfo;
public function __construct( $name, $args, $index, $currentFileInfo = null ) {
$this->name = $name;
$this->args = $args;
$this->mathOn = ( $name !== 'calc' );
$this->calc = $name === 'calc';
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
@ -46,6 +48,17 @@ class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
return $function( ...$filtered );
}
/**
* @param Less_Environment $env
* @return void
*/
private function exitCalc( $env, $currentMathContext ) {
if ( $this->calc || $env->inCalc ) {
$env->exitCalc();
}
$env->mathOn = $currentMathContext;
}
//
// When evaluating a function call,
// we either find the function in Less_Functions,
@ -56,18 +69,24 @@ class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
// of them is a LESS variable that only PHP knows the value of,
// like: `saturate(@mycolor)`.
// The function should receive the value, not the variable.
//
// TODO less.js#3.13.1 provide better parity with upstream.
public function compile( $env ) {
// Turn off math for calc(). https://phabricator.wikimedia.org/T331688
$currentMathContext = $env->strictMath;
$env->strictMath = !$this->mathOn;
/**
* Turn off math for calc(), and switch back on for evaluating nested functions
*/
$currentMathContext = $env->mathOn;
$env->mathOn = !$this->calc;
if ( $this->calc || $env->inCalc ) {
$env->enterCalc();
}
$args = [];
foreach ( $this->args as $a ) {
$args[] = $a->compile( $env );
}
$env->strictMath = $currentMathContext;
$env->mathOn = $currentMathContext;
$nameLC = strtolower( $this->name );
switch ( $nameLC ) {
@ -114,6 +133,7 @@ class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
if ( $func ) {
try {
$result = $this->functionCaller( $func, $args );
$this->exitCalc( $env, $currentMathContext );
} catch ( Exception $e ) {
// Preserve original trace, especially from custom functions.
// https://github.com/wikimedia/less.php/issues/38
@ -129,7 +149,7 @@ class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
if ( $result !== null ) {
return $result;
}
$this->exitCalc( $env, $currentMathContext );
return new self( $this->name, $args, $this->index, $this->currentFileInfo );
}

View file

@ -1,37 +1,38 @@
<?php
/**
* @private
* @see less-3.13.1.js#Color.prototype
*/
class Less_Tree_Color extends Less_Tree {
/** @var array<int|float> */
public $rgb;
/** @var int */
public $alpha;
public $isTransparentKeyword;
/** @var null|string */
public $value;
public function __construct( $rgb, $a = 1, $isTransparentKeyword = null, $originalForm = null ) {
if ( $isTransparentKeyword ) {
$this->rgb = $rgb;
$this->alpha = $a;
$this->isTransparentKeyword = true;
return;
}
if ( isset( $originalForm ) ) {
$this->value = $originalForm;
}
$this->rgb = [];
public function __construct( $rgb, $a = null, ?string $originalForm = null ) {
if ( is_array( $rgb ) ) {
$this->rgb = $rgb;
} elseif ( strlen( $rgb ) == 6 ) {
// TODO: Less.js 3.13 supports 8-digit rgba as #RRGGBBAA
$this->rgb = [];
foreach ( str_split( $rgb, 2 ) as $c ) {
$this->rgb[] = hexdec( $c );
}
} else {
$this->rgb = [];
// TODO: Less.js 3.13 supports 4-digit short rgba as #RGBA
foreach ( str_split( $rgb, 1 ) as $c ) {
$this->rgb[] = hexdec( $c . $c );
}
}
$this->alpha = is_numeric( $a ) ? $a : 1;
if ( $originalForm !== null ) {
$this->value = $originalForm;
}
}
public function luma() {
@ -56,20 +57,21 @@ class Less_Tree_Color extends Less_Tree {
public function toCSS( $doNotCompress = false ) {
$compress = Less_Parser::$options['compress'] && !$doNotCompress;
$alpha = $this->fround( $this->alpha );
// `value` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
if ( $this->value ) {
return $this->value;
}
//
// If we have some transparency, the only way to represent it
// is via `rgba`. Otherwise, we use the hex representation,
// If we have alpha transparency other than 1.0, the only way to represent it
// is via rgba(). Otherwise, we use the hex representation,
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
//
// TODO: Less.js 3.13 supports hsla() and hsl() as well
if ( $alpha < 1 ) {
if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
return 'transparent';
}
$values = [];
foreach ( $this->rgb as $c ) {
$values[] = $this->clamp( round( $c ), 255 );
@ -78,30 +80,24 @@ class Less_Tree_Color extends Less_Tree {
$glue = ( $compress ? ',' : ', ' );
return "rgba(" . implode( $glue, $values ) . ")";
} else {
$color = $this->toRGB();
if ( $compress ) {
// Convert color to short format
if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
$color = '#' . $color[1] . $color[3] . $color[5];
}
}
return $color;
}
$color = $this->toRGB();
if ( $compress ) {
// Convert color to short format
if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
$color = '#' . $color[1] . $color[3] . $color[5];
}
}
return $color;
}
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
/**
* Operations have to be done per-channel, if not,
* channels will spill onto each other. Once we have
* our result, in the form of an integer triplet,
* we create a new Color node to hold the result.
*
* @param string $op
* @param self $other
*/
@ -129,8 +125,9 @@ class Less_Tree_Color extends Less_Tree {
$l = ( $max + $min ) / 2;
$d = $max - $min;
$h = $s = 0;
if ( $max !== $min ) {
if ( $max === $min ) {
$h = $s = 0;
} else {
$s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
switch ( $max ) {
@ -168,8 +165,9 @@ class Less_Tree_Color extends Less_Tree {
$s = $d / $max;
}
$h = 0;
if ( $max !== $min ) {
if ( $max === $min ) {
$h = 0;
} else {
switch ( $max ) {
case $r:
$h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
@ -194,14 +192,11 @@ class Less_Tree_Color extends Less_Tree {
/**
* @param mixed $x
* @return int|null
* @see less-2.5.3.js#Color.prototype.compare
* @see less-3.13.1.js#Color.prototype.compare
*/
public function compare( $x ) {
if ( !$x instanceof self ) {
return -1;
}
return ( $x->rgb[0] === $this->rgb[0] &&
return ( $x instanceof self &&
$x->rgb[0] === $this->rgb[0] &&
$x->rgb[1] === $this->rgb[1] &&
$x->rgb[2] === $this->rgb[2] &&
$x->alpha === $this->alpha ) ? 0 : null;
@ -211,7 +206,7 @@ class Less_Tree_Color extends Less_Tree {
* @param int|float $val
* @param int $max
* @return int|float
* @see less-2.5.3.js#Color.prototype
* @see less-3.13.1.js#Color.prototype
*/
private function clamp( $val, $max ) {
return min( max( $val, 0 ), $max );
@ -226,7 +221,6 @@ class Less_Tree_Color extends Less_Tree {
}
$ret .= dechex( $c );
}
return $ret;
}
@ -234,21 +228,19 @@ class Less_Tree_Color extends Less_Tree {
* @param string $keyword
*/
public static function fromKeyword( $keyword ) {
$c = $keyword = strtolower( $keyword );
$c = null;
$key = strtolower( $keyword );
if ( Less_Colors::hasOwnProperty( $keyword ) ) {
if ( Less_Colors::hasOwnProperty( $key ) ) {
// detect named color
$c = new self( substr( Less_Colors::color( $keyword ), 1 ) );
$c = new self( substr( Less_Colors::color( $key ), 1 ) );
} elseif ( $key === 'transparent' ) {
$c = new self( [ 0, 0, 0 ], 0 );
}
if ( $keyword === 'transparent' ) {
$c = new self( [ 0, 0, 0 ], 0, true );
}
if ( isset( $c ) && is_object( $c ) ) {
if ( $c instanceof self ) {
$c->value = $keyword;
return $c;
}
}
}

View file

@ -4,9 +4,13 @@
* @see less-2.5.3.js#Comment.prototype
*/
class Less_Tree_Comment extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $value;
/** @var bool */
public $isLineComment;
/** @var bool|null */
public $isReferenced;
/** @var array|null */
public $currentFileInfo;
public function __construct( $value, $isLineComment, $index = null, $currentFileInfo = null ) {

View file

@ -4,10 +4,15 @@
*/
class Less_Tree_Condition extends Less_Tree {
/** @var string */
public $op;
/** @var Less_Tree */
public $lvalue;
/** @var Less_Tree */
public $rvalue;
/** @var int */
public $index;
/** @var bool */
public $negate;
public function __construct( $op, $l, $r, $i = 0, $negate = false ) {

View file

@ -1,23 +1,39 @@
<?php
/**
* @private
* @see less-3.13.1.js#Declaration.prototype
* @todo check for feature parity with 3.13.1
*/
class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
class Less_Tree_Declaration extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string|array<Less_Tree_Keyword|Less_Tree_Variable> */
public $name;
/** @var Less_Tree */
/** @var Less_Tree[]|Less_Tree_Anonymous */
public $value;
/** @var string */
public $important;
/** @var null|false|string */
public $merge;
/** @var int|null */
public $index;
/** @var bool */
public $inline;
/** @var bool */
public $variable;
/** @var array|null */
public $currentFileInfo;
/**
* In the upstream `parsed` is stored in `Node`, but Less_Tree_Declaration is the only place
* that make use of it.
* @see less-3.13.1.js#Node.parsed
* @var bool
*/
public $parsed = false;
/**
* @param string|array<Less_Tree_Keyword|Less_Tree_Variable> $name
* @param mixed $value
* @param Less_Tree|string|null $value
* @param null|false|string $important
* @param null|false|string $merge
* @param int|null $index
@ -25,12 +41,20 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
* @param bool $inline
* @param bool|null $variable
*/
public function __construct( $name, $value = null, $important = null, $merge = null,
$index = null, $currentFileInfo = null, $inline = false, $variable = null ) {
public function __construct(
$name,
$value = null,
$important = null,
$merge = null,
$index = null,
$currentFileInfo = null,
$inline = false,
$variable = null
) {
$this->name = $name;
$this->value = ( $value instanceof Less_Tree )
? $value
: new Less_Tree_Value( [ $value ] );
: new Less_Tree_Value( [ $value ? new Less_Tree_Anonymous( $value ) : null ] );
$this->important = $important ? ' ' . trim( $important ) : '';
$this->merge = $merge;
$this->index = $index;
@ -56,7 +80,11 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
$e->currentFile = $this->currentFileInfo;
throw $e;
}
$output->add( $this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ), $this->currentFileInfo, $this->index );
$output->add(
$this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
$this->currentFileInfo,
$this->index
);
}
/**
@ -78,10 +106,11 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
$variable = false; // never treat expanded interpolation as new variable name
}
$strictMathBypass = false;
if ( $name === "font" && !$env->strictMath ) {
$strictMathBypass = true;
$env->strictMath = true;
$mathBypass = false;
$prevMath = $env->math;
if ( $name === "font" && $env->math === Less_Environment::MATH_ALWAYS ) {
$mathBypass = true;
$env->math = Less_Environment::MATH_PARENS_DIVISION;
}
try {
@ -99,7 +128,8 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
$important = $importantResult['important'];
}
$return = new Less_Tree_Rule( $name,
$return = new Less_Tree_Declaration(
$name,
$evaldValue,
$important,
$this->merge,
@ -118,8 +148,8 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
throw $e;
}
if ( $strictMathBypass ) {
$env->strictMath = false;
if ( $mathBypass ) {
$env->math = $prevMath;
}
return $return;
@ -141,7 +171,6 @@ class Less_Tree_Rule extends Less_Tree implements Less_Tree_HasValueProperty {
if ( !is_array( $this->value ) ) {
if ( method_exists( $value, 'markReferenced' ) ) {
// @phan-suppress-next-line PhanUndeclaredMethod
$value->markReferenced();
}
} else {

View file

@ -4,7 +4,9 @@
*/
class Less_Tree_DefaultFunc {
/** @var string|null */
private static $error_;
/** @var int|null */
private static $value_;
public static function compile() {

View file

@ -4,7 +4,9 @@
*/
class Less_Tree_DetachedRuleset extends Less_Tree {
/** @var Less_Tree_Ruleset */
public $ruleset;
/** @var array|null */
public $frames;
public function __construct( $ruleset, $frames = null ) {

View file

@ -7,6 +7,7 @@ class Less_Tree_Dimension extends Less_Tree implements Less_Tree_HasValuePropert
/** @var float */
public $value;
/** @var Less_Tree_Unit */
public $unit;
public function __construct( $value, $unit = null ) {
@ -34,7 +35,9 @@ class Less_Tree_Dimension extends Less_Tree implements Less_Tree_HasValuePropert
*/
public function genCSS( $output ) {
if ( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ) {
throw new Less_Exception_Compiler( "Multiple units in dimension. Correct the units or use the unit function. Bad unit: " . $this->unit->toString() );
throw new Less_Exception_Compiler(
"Multiple units in dimension. Correct the units or use the unit function. Bad unit: " . $this->unit->toString()
);
}
$value = $this->fround( $this->value );
@ -91,7 +94,10 @@ class Less_Tree_Dimension extends Less_Tree implements Less_Tree_HasValuePropert
$other = $other->convertTo( $this->unit->usedUnits() );
if ( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ) {
throw new Less_Exception_Compiler( "Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'." );
throw new Less_Exception_Compiler(
"Incompatible units. Change the units or use the unit function. Bad units: '" .
$unit->toString() . "' and " . $other->unit->toString() . "'."
);
}
$value = $this->_operate( $op, $this->value, $other->value );

View file

@ -10,11 +10,11 @@ class Less_Tree_Element extends Less_Tree implements Less_Tree_HasValueProperty
public $combinatorIsEmptyOrWhitespace;
/** @var string|Less_Tree */
public $value;
/** @var int|null */
public $index;
/** @var array|null */
public $currentFileInfo;
public $value_is_object = false;
/**
* @param null|string $combinator
* @param string|Less_Tree $value
@ -23,7 +23,6 @@ class Less_Tree_Element extends Less_Tree implements Less_Tree_HasValueProperty
*/
public function __construct( $combinator, $value, $index = null, $currentFileInfo = null ) {
$this->value = $value;
$this->value_is_object = is_object( $value );
// see less-2.5.3.js#Combinator
$this->combinator = $combinator ?? '';
@ -34,7 +33,7 @@ class Less_Tree_Element extends Less_Tree implements Less_Tree_HasValueProperty
}
public function accept( $visitor ) {
if ( $this->value_is_object ) { // object or string
if ( $this->value instanceof Less_Tree ) {
$this->value = $visitor->visitObj( $this->value );
}
}
@ -42,7 +41,7 @@ class Less_Tree_Element extends Less_Tree implements Less_Tree_HasValueProperty
public function compile( $env ) {
return new self(
$this->combinator,
( $this->value_is_object ? $this->value->compile( $env ) : $this->value ),
( $this->value instanceof Less_Tree ? $this->value->compile( $env ) : $this->value ),
$this->index,
$this->currentFileInfo
);
@ -56,14 +55,16 @@ class Less_Tree_Element extends Less_Tree implements Less_Tree_HasValueProperty
}
public function toCSS() {
if ( $this->value_is_object ) {
if ( $this->value instanceof Less_Tree ) {
$value = $this->value->toCSS();
} else {
$value = $this->value;
}
$spaceOrEmpty = ' ';
if ( Less_Parser::$options['compress'] || ( isset( Less_Environment::$_noSpaceCombinators[$this->combinator] ) && Less_Environment::$_noSpaceCombinators[$this->combinator] ) ) {
if ( Less_Parser::$options['compress'] ||
( isset( Less_Environment::$_noSpaceCombinators[$this->combinator] ) && Less_Environment::$_noSpaceCombinators[$this->combinator] )
) {
$spaceOrEmpty = '';
}

View file

@ -1,16 +1,20 @@
<?php
/**
* @private
* @see less-3.13.1.js#Expression.prototype
*/
class Less_Tree_Expression extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var Less_Tree[] */
public $value = [];
public $parens = false;
/** @var bool */
public $noSpacing;
/** @var true|null */
public $parens = null;
public function __construct( $value, $parens = null ) {
public function __construct( $value, $noSpacing = false ) {
$this->value = $value;
$this->parens = $parens;
$this->noSpacing = $noSpacing;
}
public function accept( $visitor ) {
@ -18,46 +22,42 @@ class Less_Tree_Expression extends Less_Tree implements Less_Tree_HasValueProper
}
public function compile( $env ) {
$mathOn = $env->isMathOn();
// NOTE: We don't support STRICT_LEGACY (Less.js 3.13)
$inParenthesis = $this->parens && ( true || !$this->parensInOp );
$doubleParen = false;
if ( $this->parens && !$this->parensInOp ) {
Less_Environment::$parensStack++;
if ( $inParenthesis ) {
$env->inParenthesis();
}
$returnValue = null;
if ( $this->value ) {
$count = count( $this->value );
if ( $count > 1 ) {
if ( count( $this->value ) > 1 ) {
$ret = [];
foreach ( $this->value as $e ) {
$ret[] = $e->compile( $env );
}
$returnValue = new self( $ret );
$returnValue = new self( $ret, $this->noSpacing );
} else {
if ( ( $this->value[0] instanceof self ) && $this->value[0]->parens && !$this->value[0]->parensInOp ) {
// Implied `if ( count() === 1 )`
if ( ( $this->value[0] instanceof self ) && $this->value[0]->parens && !$this->value[0]->parensInOp && !$env->inCalc ) {
$doubleParen = true;
}
$returnValue = $this->value[0]->compile( $env );
}
} else {
$returnValue = $this;
}
if ( $this->parens ) {
if ( !$this->parensInOp ) {
Less_Environment::$parensStack--;
if ( $inParenthesis ) {
$env->outOfParenthesis();
}
} elseif ( !$env->isMathOn() && !$doubleParen ) {
$returnValue = new Less_Tree_Paren( $returnValue );
}
if ( $this->parens && $this->parensInOp && !$mathOn && !$doubleParen &&
( !( $returnValue instanceof Less_Tree_Dimension ) )
) {
$returnValue = new Less_Tree_Paren( $returnValue );
}
return $returnValue;
}
@ -69,8 +69,13 @@ class Less_Tree_Expression extends Less_Tree implements Less_Tree_HasValueProper
$val_len = count( $this->value );
for ( $i = 0; $i < $val_len; $i++ ) {
$this->value[$i]->genCSS( $output );
if ( $i + 1 < $val_len ) {
$output->add( ' ' );
if ( !$this->noSpacing && ( $i + 1 < $val_len ) ) {
// NOTE: Comma handling backported from Less.js 4.2.1 (T386077)
if ( !( $this->value[$i + 1] instanceof Less_Tree_Anonymous )
|| ( $this->value[$i + 1] instanceof Less_Tree_Anonymous && $this->value[$i + 1]->value !== ',' )
) {
$output->add( ' ' );
}
}
}
}

View file

@ -4,16 +4,25 @@
*/
class Less_Tree_Extend extends Less_Tree {
/** @var Less_Tree_Selector */
public $selector;
/** @var string */
public $option;
/** @var int */
public $index;
/** @var Less_Tree_Selector[] */
public $selfSelectors = [];
/** @var bool */
public $allowBefore;
/** @var bool */
public $allowAfter;
/** @var bool */
public $firstExtendOnThisSelectorPath;
/** @var Less_Tree_Ruleset|null */
public $ruleset;
/** @var string */
public $object_id;
/** @var array<string,true> */
public $parent_ids = [];
/**

View file

@ -16,11 +16,17 @@
*/
class Less_Tree_Import extends Less_Tree {
/** @var array<string,bool> */
public $options;
/** @var int */
public $index;
/** @var Less_Tree_Quoted|Less_Tree_Url */
public $path;
/** @var Less_Tree_Value */
public $features;
/** @var array|null */
public $currentFileInfo;
/** @var bool|null */
public $css;
/** @var bool|null This is populated by Less_ImportVisitor */
public $doSkip = false;
@ -124,7 +130,7 @@ class Less_Tree_Import extends Less_Tree {
public function compileForImport( $env ) {
$path = $this->path;
if ( $path instanceof Less_Tree_Url ) {
$path = $path->value;
$path = $path->value;
}
return new self( $path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
}
@ -154,30 +160,15 @@ class Less_Tree_Import extends Less_Tree {
public function compile( $env ) {
$features = ( $this->features ? $this->features->compile( $env ) : null );
// TODO: Upstream doesn't do path resolution here. The reason we need it here is
// because skip() takes a $path_and_uri argument. Once the TODO in ImportVisitor
// about Less_Tree_Import::PathAndUri() is fixed, this can be removed by letting
// skip() call $this->PathAndUri() on its own.
// get path & uri
$path_and_uri = $env->callImportCallback( $this );
if ( !$path_and_uri ) {
$path_and_uri = Less_FileManager::getFilePath( $this->getPath(), $this->currentFileInfo );
}
if ( $path_and_uri ) {
[ $full_path, $uri ] = $path_and_uri;
} else {
$full_path = $uri = $this->getPath();
}
'@phan-var string $full_path';
// import once
if ( $this->skip( $full_path, $env ) ) {
if ( $this->skip( $env ) ) {
return [];
}
if ( $this->options['inline'] ) {
$contents = new Less_Tree_Anonymous( $this->root, 0,
$contents = new Less_Tree_Anonymous(
$this->root,
0,
[
'filename' => $this->importedFilename,
'reference' => $this->currentFileInfo['reference'] ?? null,
@ -208,21 +199,26 @@ class Less_Tree_Import extends Less_Tree {
/**
* Should the import be skipped?
*
* @param string|null $path
* @param Less_Environment $env
* @return bool|null
*/
public function skip( $path, $env ) {
public function skip( $env ) {
$path = $this->getPath();
// TODO: Since our Import->getPath() varies from upstream Less.js (ours can return null).
// we therefore need an empty string fallback here. Remove this fallback once getPath()
// is in sync with upstream.
$fullPath = Less_FileManager::getFilePath( $path, $this->currentFileInfo )[0] ?? $path ?? '';
if ( $this->doSkip !== null ) {
return $this->doSkip;
}
// @see less-2.5.3.js#ImportVisitor.prototype.onImported
if ( isset( $env->importVisitorOnceMap[$path] ) ) {
if ( isset( $env->importVisitorOnceMap[$fullPath] ) ) {
return true;
}
$env->importVisitorOnceMap[$path] = true;
$env->importVisitorOnceMap[$fullPath] = true;
return false;
}
}

View file

@ -1,19 +1,23 @@
<?php
/**
* @private
* @see less-3.13.1.js#JavaScript.prototype
*/
class Less_Tree_JavaScript extends Less_Tree {
/** @var bool */
public $escaped;
/** @var string */
public $expression;
/** @var int */
public $index;
/**
* @param string $string
* @param int $index
* @param bool $escaped
* @param int $index
*/
public function __construct( $string, $index, $escaped ) {
public function __construct( $string, $escaped, $index ) {
$this->escaped = $escaped;
$this->expression = $string;
$this->index = $index;

View file

@ -4,10 +4,15 @@
*/
class Less_Tree_Media extends Less_Tree {
/** @var Less_Tree_Value */
public $features;
/** @var Less_Tree_Ruleset[] */
public $rules;
/** @var int|null */
public $index;
/** @var array|null */
public $currentFileInfo;
/** @var bool|null */
public $isReferenced;
public function __construct( $value = [], $features = [], $index = null, $currentFileInfo = null ) {
@ -44,16 +49,16 @@ class Less_Tree_Media extends Less_Tree {
public function compile( $env ) {
$media = new self( [], [], $this->index, $this->currentFileInfo );
$strictMathBypass = false;
if ( !$env->strictMath ) {
$strictMathBypass = true;
$env->strictMath = true;
$mathBypass = false;
if ( !$env->mathOn ) {
$mathBypass = true;
$env->mathOn = true;
}
$media->features = $this->features->compile( $env );
if ( $strictMathBypass ) {
$env->strictMath = false;
if ( $mathBypass ) {
$env->mathOn = false;
}
$env->mediaPath[] = $media;
@ -134,7 +139,7 @@ class Less_Tree_Media extends Less_Tree {
foreach ( $permuted as $path ) {
for ( $i = 0, $len = count( $path ); $i < $len; $i++ ) {
$path[$i] = Less_Parser::is_method( $path[$i], 'toCSS' ) ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
$path[$i] = $path[$i] instanceof Less_Tree ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
}
for ( $i = count( $path ) - 1; $i > 0; $i-- ) {

View file

@ -4,11 +4,16 @@
*/
class Less_Tree_Mixin_Call extends Less_Tree {
/** @var Less_Tree_Selector */
public $selector;
/** @var array[] */
public $arguments;
/** @var int */
public $index;
/** @var array */
public $currentFileInfo;
/** @var bool */
public $important;
public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
@ -28,7 +33,7 @@ class Less_Tree_Mixin_Call extends Less_Tree {
$isOneFound = false;
$candidates = [];
$conditionResult = [];
$this->selector = $this->selector->compile( $env );
$args = [];
foreach ( $this->arguments as $a ) {
$argValue = $a['value']->compile( $env );
@ -153,10 +158,19 @@ class Less_Tree_Mixin_Call extends Less_Tree {
if ( $isOneFound ) {
$selectorName = $this->selector->toCSS();
throw new Less_Exception_Compiler( 'No matching definition was found for ' . $selectorName . ' with args `' . $this->Format( $args ) . '`', null, $this->index, $this->currentFileInfo );
throw new Less_Exception_Compiler(
'No matching definition was found for ' . $selectorName . ' with args `' . $this->Format( $args ) . '`',
null,
$this->index,
$this->currentFileInfo
);
} else {
throw new Less_Exception_Compiler( trim( $this->selector->toCSS() ) . " is undefined in " . $this->currentFileInfo['filename'], null, $this->index );
throw new Less_Exception_Compiler(
trim( $this->selector->toCSS() ) . " is undefined in " . $this->currentFileInfo['filename'],
null,
$this->index
);
}
}
@ -171,7 +185,7 @@ class Less_Tree_Mixin_Call extends Less_Tree {
if ( $a['name'] ) {
$argValue .= $a['name'] . ':';
}
if ( is_object( $a['value'] ) ) {
if ( $a['value'] instanceof Less_Tree ) {
$argValue .= $a['value']->toCSS();
} else {
$argValue .= '???';

View file

@ -3,16 +3,27 @@
* @private
*/
class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
/** @var string */
public $name;
/** @var Less_Tree_Selector[] */
public $selectors;
/** @var array[] */
public $params;
/** @var int */
public $arity = 0;
/** @var Less_Tree[] */
public $rules;
public $lookups = [];
public $required = 0;
public $frames = [];
/** @var array[][] */
public $lookups = [];
/** @var int */
public $required = 0;
/** @var array */
public $frames = [];
/** @var Less_Tree_Condition|null */
public $condition;
/** @var bool */
public $variadic;
/** @var array<string,true> */
public $optionalParameters = [];
public function __construct( $name, $params, $rules, $condition, $variadic = false, $frames = [] ) {
@ -60,7 +71,7 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
foreach ( $params as $j => $param ) {
if ( !isset( $evaldArguments[$j] ) && $arg['name'] === $param['name'] ) {
$evaldArguments[$j] = $arg['value']->compile( $env );
array_unshift( $frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile( $env ) ) );
array_unshift( $frame->rules, new Less_Tree_Declaration( $arg['name'], $arg['value']->compile( $env ) ) );
$isNamedFound = true;
break;
}
@ -92,12 +103,17 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
$varargs[] = $args[$j]['value']->compile( $env );
}
$expression = new Less_Tree_Expression( $varargs );
array_unshift( $frame->rules, new Less_Tree_Rule( $name, $expression->compile( $env ) ) );
array_unshift( $frame->rules, new Less_Tree_Declaration( $name, $expression->compile( $env ) ) );
} else {
$val = ( $arg && $arg['value'] ) ? $arg['value'] : false;
if ( $val ) {
$val = $val->compile( $env );
// This was a mixin call, pass in a detached ruleset of it's eval'd rules
if ( is_array( $val ) ) {
$val = new Less_Tree_DetachedRuleset( new Less_Tree_Ruleset( null, $val ) );
} else {
$val = $val->compile( $env );
}
} elseif ( isset( $param['value'] ) ) {
if ( !$mixinEnv ) {
@ -110,7 +126,7 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
throw new Less_Exception_Compiler( "Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")" );
}
array_unshift( $frame->rules, new Less_Tree_Rule( $name, $val ) );
array_unshift( $frame->rules, new Less_Tree_Declaration( $name, $val ) );
$evaldArguments[$i] = $val;
}
}
@ -156,7 +172,7 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
$frame = $this->compileParams( $env, $mixinFrames, $args, $_arguments );
$ex = new Less_Tree_Expression( $_arguments );
array_unshift( $frame->rules, new Less_Tree_Rule( '@arguments', $ex->compile( $env ) ) );
array_unshift( $frame->rules, new Less_Tree_Declaration( '@arguments', $ex->compile( $env ) ) );
$ruleset = new Less_Tree_Ruleset( null, $this->rules );
$ruleset->originalRuleset = $this->ruleset_id;
@ -185,7 +201,7 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
// set array to prevent error on array_merge
if ( !is_array( $this->frames ) ) {
$this->frames = [];
$this->frames = [];
}
$frame = $this->compileParams( $env, array_merge( $this->frames, $env->frames ), $args );
@ -205,7 +221,7 @@ class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
public function makeImportant() {
$important_rules = [];
foreach ( $this->rules as $rule ) {
if ( $rule instanceof Less_Tree_Rule || $rule instanceof self || $rule instanceof Less_Tree_NameValue ) {
if ( $rule instanceof Less_Tree_Declaration || $rule instanceof self || $rule instanceof Less_Tree_NameValue ) {
$important_rules[] = $rule->makeImportant();
} else {
$important_rules[] = $rule;

View file

@ -3,7 +3,7 @@
* A simple CSS name-value pair, e.g. `width: 100px;`
*
* In bootstrap, there are about 600-1000 simple name-value pairs (depending on
* how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule).
* how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Declaration).
*
* Using the name-value object can speed up bootstrap compilation slightly, but
* it breaks color keyword interpretation: `color: red` -> `color: #FF0000`.
@ -12,10 +12,15 @@
*/
class Less_Tree_NameValue extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $name;
/** @var string */
public $value;
/** @var int|null */
public $index;
/** @var array|null */
public $currentFileInfo;
/** @var string */
public $important = '';
public function __construct( $name, $value = null, $index = null, $currentFileInfo = null ) {
@ -28,11 +33,11 @@ class Less_Tree_NameValue extends Less_Tree implements Less_Tree_HasValuePropert
public function genCSS( $output ) {
$output->add(
$this->name
. ( Less_Parser::$options['compress'] ? ':' : ': ' )
. $this->value
. $this->important
. ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
$this->currentFileInfo, $this->index );
. ( Less_Parser::$options['compress'] ? ':' : ': ' )
. $this->value
. $this->important
. ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
$this->currentFileInfo, $this->index );
}
public function compile( $env ) {

View file

@ -0,0 +1,96 @@
<?php
/**
* @private
* @see less-3.13.1.js#NamespaceValue.prototype
*/
class Less_Tree_NamespaceValue extends Less_Tree {
/** @var Less_Tree_Mixin_Call|Less_Tree_VariableCall */
public $value;
/** @var int|null */
public $index;
/** @var string[] */
public $lookups;
/** @var array|null */
public $currentFileInfo;
public function __construct( $ruleCall, $lookups, $index = null, $currentFileInfo = null ) {
$this->value = $ruleCall;
$this->lookups = $lookups;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
public function compile( $env ) {
/** @var Less_Tree_Ruleset $rules */
$rules = $this->value->compile( $env );
foreach ( $this->lookups as $name ) {
/**
* Eval'd DRs return rulesets.
* Eval'd mixins return rules, so let's make a ruleset if we need it.
* We need to do this because of late parsing of values
*/
if ( is_array( $rules ) ) {
$rules = new Less_Tree_Ruleset( [ new Less_Tree_Selector( [] ) ], $rules );
}
if ( $name === '' ) {
$rules = $rules->lastDeclaration();
} elseif ( $name[0] === '@' ) {
if ( ( $name[1] ?? '' ) === '@' ) {
$variable = ( new Less_Tree_Variable( substr( $name, 1 ) ) )->compile( $env );
$name = "@" . $variable->value;
}
if ( $rules instanceof Less_Tree_Ruleset ) {
$rules = $rules->variable( $name );
}
if ( !$rules ) {
throw new Less_Exception_Compiler(
"Variable $name not found",
null,
$this->index,
$this->currentFileInfo
);
}
} else {
if ( strncmp( $name, '$@', 2 ) === 0 ) {
$variable = ( new Less_Tree_Variable( substr( $name, 1 ) ) )->compile( $env );
$name = "$" . $variable->value;
} else {
$name = $name[0] === '$' ? $name : ( '$' . $name );
}
if ( $rules instanceof Less_Tree_Ruleset ) {
$rules = $rules->property( $name );
}
if ( !$rules ) {
throw new Less_Exception_Compiler(
"Property $name not found",
null,
$this->index,
$this->currentFileInfo
);
}
// Properties are an array of values, since a ruleset can have multiple props.
// We pick the last one (the "cascaded" value)
if ( is_array( $rules ) ) { // to satisfy phan checks
$rules = $rules[ count( $rules ) - 1 ];
}
}
if ( $rules->value ) {
$rules = $rules->compile( $env )->value;
}
if ( $rules instanceof Less_Tree_DetachedRuleset && $rules->ruleset ) {
// @todo - looks like this is never evaluated, investigate later
// @see https://github.com/less/less.js/commit/29468bffcd8a9f2f
$rules = $rules->ruleset->compile( $env );
}
}
return $rules;
}
}

View file

@ -4,6 +4,7 @@
*/
class Less_Tree_Negative extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var Less_Tree */
public $value;
public function __construct( $node ) {

View file

@ -1,11 +1,15 @@
<?php
/**
* @private
* @see less-3.13.1.js#Operation.prototype
*/
class Less_Tree_Operation extends Less_Tree {
/** @var string */
public $op;
/** @var Less_Tree[] */
public $operands;
/** @var bool */
public $isSpaced;
/**
@ -29,25 +33,29 @@ class Less_Tree_Operation extends Less_Tree {
// For example, if one argument is a Less_Tree_Call like 'var(--foo)' then we
// preserve it as literal for native CSS.
// https://phabricator.wikimedia.org/T331688
if ( $env->isMathOn() ) {
if ( $env->isMathOn( $this->op ) ) {
$op = $this->op === './' ? '/' : $this->op;
if ( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ) {
$a = $a->toColor();
} elseif ( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ) {
$b = $b->toColor();
}
if ( !( $a instanceof Less_Tree_Dimension || $a instanceof Less_Tree_Color ) ) {
if ( !( $a instanceof Less_Tree_Dimension || $a instanceof Less_Tree_Color )
|| !( $b instanceof Less_Tree_Dimension || $b instanceof Less_Tree_Color )
) {
if ( $a instanceof Less_Tree_Operation && $a->op === '/' && $env->math === Less_Environment::MATH_PARENS_DIVISION
) {
return new self( $this->op, [ $a, $b ], $this->isSpaced );
}
throw new Less_Exception_Compiler( "Operation on an invalid type" );
}
if ( $b instanceof Less_Tree_Dimension || $b instanceof Less_Tree_Color ) {
return $a->operate( $this->op, $b );
}
return $a->operate( $op, $b );
} else {
return new self( $this->op, [ $a, $b ], $this->isSpaced );
}
return new self( $this->op, [ $a, $b ], $this->isSpaced );
}
/**

View file

@ -0,0 +1,77 @@
<?php
/**
* @private
* @see less-3.13.1.js#tree.Property
*/
class Less_Tree_Property extends Less_Tree {
/** @var string */
public $name;
/** @var int|null */
public $index;
/** @var array|null */
public $currentFileInfo;
/** @var bool */
public $evaluating = false;
/**
* @param string $name
*/
public function __construct( $name, $index = null, $currentFileInfo = null ) {
$this->name = $name;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
public function compile( $env ) {
$name = $this->name;
if ( $this->evaluating ) {
throw new Less_Exception_Compiler(
"Recursive property reference for " . $name,
null,
$this->index, $this->currentFileInfo
);
}
$property = null;
$this->evaluating = true;
/** @var Less_Tree_Ruleset $frame */
foreach ( $env->frames as $frame ) {
$vArr = $frame->property( $name );
if ( $vArr ) {
$size = count( $vArr );
for ( $i = 0; $i < $size; $i++ ) {
$v = $vArr[$i];
$vArr[$i] = new Less_Tree_Declaration(
$v->name,
$v->value,
$v->important,
$v->merge,
$v->index,
$v->currentFileInfo,
$v->inline,
$v->variable
);
}
Less_Visitor_toCSS::_mergeRules( $vArr );
$v = $vArr[ count( $vArr ) - 1 ];
if ( isset( $v->important ) && $v->important ) {
$importantScopeLength = count( $env->importantScope );
$env->importantScope[ $importantScopeLength - 1 ]['important'] = $v->important;
}
$property = $v->value->compile( $env );
break;
}
}
if ( $property ) {
$this->evaluating = false;
return $property;
} else {
throw new Less_Exception_Compiler( "property '" . $name . "' is undefined in file " .
$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
}
}
}

View file

@ -3,13 +3,22 @@
* @private
*/
class Less_Tree_Quoted extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var bool */
public $escaped;
/** @var string */
public $value;
/** @var string */
public $quote;
/** @var int|false */
public $index;
/** @var array|null */
public $currentFileInfo;
/** @var string */
public $variableRegex = '/@\{([\w-]+)\}/';
/** @var string */
public $propRegex = '/\$\{([\w-]+)\}/';
/**
* @param string $str
*/
@ -36,23 +45,17 @@ class Less_Tree_Quoted extends Less_Tree implements Less_Tree_HasValueProperty {
}
}
/**
* @see less-3.13.1.js#Quoted.prototype.containsVariables
*/
public function containsVariables() {
return preg_match( '/(`([^`]+)`)|@\{([\w-]+)\}/', $this->value );
return preg_match( $this->variableRegex, $this->value );
}
public function compile( $env ) {
$value = $this->value;
if ( preg_match_all( '/`([^`]+)`/', $this->value, $matches ) ) {
foreach ( $matches[1] as $i => $match ) {
$js = new Less_Tree_JavaScript( $match, $this->index, true );
$js = $js->compile( $env )->value;
$value = str_replace( $matches[0][$i], $js, $value );
}
}
$r = $value;
private function variableReplacement( $r, $env ) {
do {
$value = $r;
if ( preg_match_all( '/@\{([\w-]+)\}/', $value, $matches ) ) {
if ( preg_match_all( $this->variableRegex, $value, $matches ) ) {
foreach ( $matches[1] as $i => $match ) {
$v = new Less_Tree_Variable( '@' . $match, $this->index, $this->currentFileInfo );
$v = $v->compile( $env );
@ -61,8 +64,29 @@ class Less_Tree_Quoted extends Less_Tree implements Less_Tree_HasValueProperty {
}
}
} while ( $r != $value );
return $r;
}
return new self( $this->quote . $r . $this->quote, $r, $this->escaped, $this->index, $this->currentFileInfo );
private function propertyReplacement( $r, $env ) {
do {
$value = $r;
if ( preg_match_all( $this->propRegex, $value, $matches ) ) {
foreach ( $matches[1] as $i => $match ) {
$v = new Less_Tree_Property( '$' . $match, $this->index, $this->currentFileInfo );
$v = $v->compile( $env );
$v = ( $v instanceof self ) ? $v->value : $v->toCSS();
$r = str_replace( $matches[0][$i], $v, $r );
}
}
} while ( $r != $value );
return $r;
}
public function compile( $env ) {
$value = $this->value;
$value = $this->variableReplacement( $value, $env );
$value = $this->propertyReplacement( $value, $env );
return new self( $this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo );
}
/**
@ -75,7 +99,7 @@ class Less_Tree_Quoted extends Less_Tree implements Less_Tree_HasValueProperty {
return Less_Tree::numericCompare( $this->value, $other->value );
} else {
return (
Less_Parser::is_method( $other, 'toCSS' )
$other instanceof Less_Tree
&& $this->toCSS() === $other->toCSS()
) ? 0 : null;
}

View file

@ -4,19 +4,31 @@
*/
class Less_Tree_Ruleset extends Less_Tree {
/** @var array[][] */
protected $lookups;
/** @var array<string,Less_Tree_Declaration>|null */
public $_variables;
public $_rulesets;
/** @var array<string,Less_Tree_Declaration[]>|null */
public $_properties;
/** @var null|bool */
public $strictImports;
/** @var Less_Tree_Selector[]|null */
public $selectors;
/** @var Less_Tree[] */
public $rules;
/** @var true|null */
public $root;
/** @var true|null */
public $allowImports;
/** @var Less_Tree_Selector[][]|null */
public $paths;
/** @var true|null */
public $firstRoot;
/** @var true|null */
public $multiMedia;
/** @var Less_Tree_Extend[] */
public $allExtends;
/** @var int */
@ -24,6 +36,7 @@ class Less_Tree_Ruleset extends Less_Tree {
/** @var int */
public $originalRuleset;
/** @var array<string,true> */
public $first_oelements;
public function SetRulesetIndex() {
@ -111,7 +124,7 @@ class Less_Tree_Ruleset extends Less_Tree {
for ( $j = 0; $j < count( $rule->rules ); $j++ ) {
$subRule = $rule->rules[$j];
if ( !( $subRule instanceof Less_Tree_Rule ) || !$subRule->variable ) {
if ( !( $subRule instanceof Less_Tree_Declaration ) || !$subRule->variable ) {
array_splice( $ruleset->rules, ++$i, 0, [ $subRule ] );
$rsRuleCnt++;
}
@ -150,7 +163,7 @@ class Less_Tree_Ruleset extends Less_Tree {
$temp = [];
foreach ( $rule as $r ) {
if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
if ( ( $r instanceof Less_Tree_Declaration ) && $r->variable ) {
// do not pollute the scope if the variable is
// already there. consider returning false here
// but we need a way to "return" variable from mixins
@ -167,12 +180,12 @@ class Less_Tree_Ruleset extends Less_Tree {
$i += $temp_count;
$ruleset->resetCache();
} elseif ( $rule instanceof Less_Tree_RulesetCall ) {
} elseif ( $rule instanceof Less_Tree_VariableCall ) {
$rule = $rule->compile( $env );
$rules = [];
foreach ( $rule->rules as $r ) {
if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
if ( ( $r instanceof Less_Tree_Declaration ) && $r->variable ) {
continue;
}
$rules[] = $r;
@ -267,7 +280,7 @@ class Less_Tree_Ruleset extends Less_Tree {
public function makeImportant() {
$important_rules = [];
foreach ( $this->rules as $rule ) {
if ( $rule instanceof Less_Tree_Rule || $rule instanceof self || $rule instanceof Less_Tree_NameValue ) {
if ( $rule instanceof Less_Tree_Declaration || $rule instanceof self || $rule instanceof Less_Tree_NameValue ) {
$important_rules[] = $rule->makeImportant();
} else {
$important_rules[] = $rule;
@ -295,18 +308,17 @@ class Less_Tree_Ruleset extends Less_Tree {
}
public function resetCache() {
$this->_rulesets = null;
$this->_variables = null;
$this->lookups = [];
}
/**
* @see less-2.5.3.js#Ruleset.prototype.variables
* @see less-3.13.1.js#Ruleset.prototype.variables
*/
public function variables() {
$this->_variables = [];
foreach ( $this->rules as $r ) {
if ( $r instanceof Less_Tree_Rule && $r->variable === true ) {
if ( $r instanceof Less_Tree_Declaration && $r->variable === true ) {
$this->_variables[$r->name] = $r;
}
// when evaluating variables in an import statement, imports have not been eval'd
@ -322,15 +334,110 @@ class Less_Tree_Ruleset extends Less_Tree {
return $this->_variables;
}
/**
* @see less-3.13.1#Ruleset.prototype.properties
*/
public function properties() {
$this->_properties = [];
foreach ( $this->rules as $r ) {
if ( $r instanceof Less_Tree_Declaration && $r->variable !== true ) {
$name = is_array( $r->name ) && count( $r->name ) === 1 && $r->name[0] instanceof Less_Tree_Keyword
? $r->name[0]->value
: $r->name;
// Properties don't overwrite as they can merge
// TODO: differs from upstream. Upstream expects $r->name to be only a
// Less_Tree_Keyword but somehow our parser also returns Less_Tree_Property.
// Let's handle it for now, but we should debug why this happens
// caused by test/Fixtures/lessjs-3.13.1/less/_main/property-accessors.less:59
if ( is_array( $name ) && $name[0] instanceof Less_Tree_Property ) {
$name = $name[0]->name;
}
$idx = '$' . $name;
if ( !array_key_exists( $idx, $this->_properties ) ) {
$this->_properties[ $idx ] = [];
}
$this->_properties[ $idx ][] = $r;
}
}
return $this->_properties;
}
/**
* @param string $name
* @return Less_Tree_Rule|null
* @return Less_Tree_Declaration|null
* @see less-3.13.1#Ruleset.prototype.variable
*/
public function variable( $name ) {
if ( $this->_variables === null ) {
$this->variables();
}
return $this->_variables[$name] ?? null;
return array_key_exists( $name, $this->_variables )
? $this->parseValue( $this->_variables[ $name ] )
: null;
}
/**
* @param string $name
* @see less-3.13.1#Ruleset.prototype.property
*/
public function property( $name ) {
if ( $this->_properties === null ) {
$this->properties();
}
return array_key_exists( $name, $this->_properties )
? $this->parseValue( $this->_properties[ $name ] )
: null;
}
/**
* @param Less_Tree_Declaration $decl
* @return mixed
* @throws Less_Exception_Parser
*/
private function transformDeclaration( $decl ) {
if ( $decl->value instanceof Less_Tree_Anonymous && !$decl->parsed ) {
[ $err, $result ] = self::$parse->parseNode(
(string)$decl->value->value,
[ 'value', 'important' ],
$decl->value->index,
$decl->value->currentFileInfo ?? []
);
if ( $err ) {
$decl->parsed = true;
}
if ( $result ) {
$decl->value = $result[0];
$decl->important = $result[1] ?? '';
$decl->parsed = true;
}
return $decl;
} else {
return $decl;
}
}
public function lastDeclaration() {
for ( $i = count( $this->rules ); $i > 0; $i-- ) {
$decl = $this->rules[ $i - 1 ];
if ( $decl instanceof Less_Tree_Declaration ) {
return $this->parseValue( $decl );
}
}
}
private function parseValue( $toParse ) {
if ( !is_array( $toParse ) ) {
return $this->transformDeclaration( $toParse );
} else {
$nodes = [];
foreach ( $toParse as $n ) {
$nodes[] = $this->transformDeclaration( $n );
}
return $nodes;
}
}
public function find( $selector, $self = null, $filter = null ) {
@ -383,7 +490,7 @@ class Less_Tree_Ruleset extends Less_Tree {
// some directives and anonymous nodes are ruleset like, others are not
if ( $rule instanceof Less_Tree_Media || $rule instanceof Less_Tree_Ruleset ) {
return true;
} elseif ( $rule instanceof Less_Tree_Anonymous || $rule instanceof Less_Tree_Directive ) {
} elseif ( $rule instanceof Less_Tree_Anonymous || $rule instanceof Less_Tree_AtRule ) {
return $rule->isRulesetLike();
}
@ -419,7 +526,7 @@ class Less_Tree_Ruleset extends Less_Tree {
$importNodeIndex++;
}
$ruleNodes[] = $rule;
} elseif ( $rule instanceof Less_Tree_Directive && $rule->isCharset() ) {
} elseif ( $rule instanceof Less_Tree_AtRule && $rule->isCharset() ) {
array_splice( $ruleNodes, $charsetNodeIndex, 0, [ $rule ] );
$charsetNodeIndex++;
$importNodeIndex++;
@ -475,7 +582,7 @@ class Less_Tree_Ruleset extends Less_Tree {
Less_Environment::$lastRule = $currentLastRule;
if ( !Less_Environment::$lastRule ) {
if ( !Less_Environment::$lastRule && $rule->isVisible() ) {
$output->add( $tabRuleStr );
} else {
Less_Environment::$lastRule = false;

View file

@ -1,26 +0,0 @@
<?php
/**
* @private
*/
class Less_Tree_RulesetCall extends Less_Tree {
public $variable;
public $type = "RulesetCall";
/**
* @param string $variable
*/
public function __construct( $variable ) {
$this->variable = $variable;
}
public function accept( $visitor ) {
}
public function compile( $env ) {
$variable = new Less_Tree_Variable( $this->variable );
$detachedRuleset = $variable->compile( $env );
'@phan-var Less_Tree_DetachedRuleset $detachedRuleset';
return $detachedRuleset->callEval( $env );
}
}

View file

@ -4,21 +4,33 @@
*/
class Less_Tree_Selector extends Less_Tree {
/** @var Less_Tree_Element[] */
public $elements;
/** @var Less_Tree_Condition|null */
public $condition;
/** @var Less_Tree_Extend[] */
public $extendList = [];
public $_css;
/** @var int|null */
public $index;
/** @var bool */
public $evaldCondition = false;
/** @var array|null */
public $currentFileInfo = [];
/** @var null|bool */
public $isReferenced;
/** @var null|bool */
public $mediaEmpty;
/** @var int */
public $elements_len = 0;
/** @var string[] */
public $_oelements;
/** @var array<string,true> */
public $_oelements_assoc;
/** @var int */
public $_oelements_len;
/** @var bool */
public $cacheable = true;
/**
@ -97,17 +109,20 @@ class Less_Tree_Selector extends Less_Tree {
foreach ( $this->elements as $v ) {
$css .= $v->combinator;
if ( !$v->value_is_object ) {
if ( !( $v->value instanceof Less_Tree ) ) {
$css .= $v->value;
continue;
}
if ( isset( $v->value->value ) && !is_object( $v->value->value ) ) {
// @phan-suppress-next-line PhanUndeclaredProperty
if ( isset( $v->value->value ) && is_scalar( $v->value->value ) ) {
// @phan-suppress-next-line PhanUndeclaredProperty
$css .= $v->value->value;
continue;
}
if ( ( $v->value instanceof Less_Tree_Selector || $v->value instanceof Less_Tree_Variable )
// @phan-suppress-next-line PhanUndeclaredProperty
|| !is_string( $v->value->value ) ) {
$this->cacheable = false;
return;
@ -142,7 +157,7 @@ class Less_Tree_Selector extends Less_Tree {
$extendList = [];
foreach ( $this->extendList as $el ) {
$extendList[] = $el->compile( $el );
$extendList[] = $el->compile( $env );
}
$evaldCondition = false;

View file

@ -4,6 +4,7 @@
*/
class Less_Tree_UnicodeDescriptor extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var string */
public $value;
public function __construct( $value ) {

View file

@ -5,8 +5,11 @@
*/
class Less_Tree_Unit extends Less_Tree {
/** @var string[] */
public $numerator = [];
/** @var string[] */
public $denominator = [];
/** @var string|null */
public $backupUnit;
public function __construct( $numerator = [], $denominator = [], $backupUnit = null ) {

View file

@ -4,8 +4,10 @@
*/
class Less_Tree_UnitConversions {
/** @var string[] */
public static $groups = [ 'length', 'duration', 'angle' ];
/** @var array<string,float> */
public static $length = [
'm' => 1,
'cm' => 0.01,
@ -14,18 +16,20 @@ class Less_Tree_UnitConversions {
'px' => 0.00026458333333, // 0.0254 / 96,
'pt' => 0.00035277777777777776, // 0.0254 / 72,
'pc' => 0.004233333333333333, // 0.0254 / 72 * 12
];
];
/** @var array<string,float> */
public static $duration = [
's' => 1,
'ms' => 0.001
];
];
/** @var array<string,float> */
public static $angle = [
'rad' => 0.1591549430919, // 1/(2*M_PI),
'deg' => 0.002777778, // 1/360,
'grad' => 0.0025, // 1/400,
'turn' => 1
];
];
}

View file

@ -4,9 +4,11 @@
*/
class Less_Tree_Url extends Less_Tree implements Less_Tree_HasValueProperty {
public $attrs;
/** @var Less_Tree_Variable|Less_Tree_Quoted|Less_Tree_Anonymous */
public $value;
/** @var array|null */
public $currentFileInfo;
/** @var bool|null */
public $isEvald;
/**

View file

@ -6,12 +6,17 @@ class Less_Tree_Value extends Less_Tree implements Less_Tree_HasValueProperty {
/** @var Less_Tree[] */
public $value;
/** @var int|null */
public $index;
/** @var array */
public $currentFileInfo;
/**
* @param array<Less_Tree> $value
*/
public function __construct( $value ) {
public function __construct( $value, $index = null ) {
$this->value = $value;
$this->index = $index;
}
public function accept( $visitor ) {

View file

@ -4,9 +4,13 @@
*/
class Less_Tree_Variable extends Less_Tree {
/** @var string */
public $name;
/** @var int|null */
public $index;
/** @var array|null */
public $currentFileInfo;
/** @var bool */
public $evaluating = false;
/**
@ -21,9 +25,10 @@ class Less_Tree_Variable extends Less_Tree {
/**
* @param Less_Environment $env
* @return Less_Tree|Less_Tree_Keyword|Less_Tree_Quoted
* @see less-2.5.3.js#Variable.prototype.eval
* @see less-3.13.1.js#Variable.prototype.eval
*/
public function compile( $env ) {
// Optimization: Less.js checks if string starts with @@, we only check if second char is @
if ( $this->name[1] === '@' ) {
$v = new self( substr( $this->name, 1 ), $this->index + 1, $this->currentFileInfo );
// While some Less_Tree nodes have no 'value', we know these can't occur after a
@ -34,25 +39,45 @@ class Less_Tree_Variable extends Less_Tree {
}
if ( $this->evaluating ) {
throw new Less_Exception_Compiler( "Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo );
throw new Less_Exception_Compiler(
"Recursive variable definition for " . $name,
null,
$this->index, $this->currentFileInfo
);
}
$this->evaluating = true;
$variable = null;
foreach ( $env->frames as $frame ) {
/** @var Less_Tree_Ruleset $frame */
$v = $frame->variable( $name );
if ( $v ) {
if ( isset( $v->important ) && $v->important ) {
$importantScopeLength = count( $env->importantScope );
$env->importantScope[ $importantScopeLength - 1 ]['important'] = $v->important;
}
$r = $v->value->compile( $env );
$this->evaluating = false;
return $r;
// If in calc, wrap vars in a function call to cascade evaluate args first
if ( $env->inCalc ) {
$call = new Less_Tree_Call( '_SELF', [ $v->value ], $this->index, $this->currentFileInfo );
$variable = $call->compile( $env );
break;
} else {
$variable = $v->value->compile( $env );
break;
}
}
}
if ( $variable ) {
$this->evaluating = false;
return $variable;
}
throw new Less_Exception_Compiler( "variable " . $name . " is undefined in file " . $this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
throw new Less_Exception_Compiler(
"variable " . $name . " is undefined in file " . $this->currentFileInfo["filename"],
null,
$this->index,
$this->currentFileInfo
);
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @private
* @see less-3.13.1.js#VariableCall
* @todo missing allowRoot implementation https://github.com/less/less.js/commit/b67403d3
*/
class Less_Tree_VariableCall extends Less_Tree {
/** @var string */
public $variable;
/** @var string */
public $type = "VariableCall";
/**
* @var int
*/
private $index;
/**
* @var array
*/
private $currentFileInfo;
/**
* @param string $variable
*/
public function __construct( $variable, $index, $currentFileInfo ) {
$this->variable = $variable;
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
public function accept( $visitor ) {
}
public function compile( $env ) {
$detachedRuleset = ( new Less_Tree_Variable( $this->variable, $this->index, $this->currentFileInfo ) )
->compile( $env );
if ( !( $detachedRuleset instanceof Less_Tree_DetachedRuleset ) || !$detachedRuleset->ruleset ) {
// order differs from upstream to simplify the code
if ( is_array( $detachedRuleset ) ) {
$rules = new Less_Tree_Ruleset( null, $detachedRuleset );
} elseif (
( $detachedRuleset instanceof Less_Tree_Ruleset
|| $detachedRuleset instanceof Less_Tree_AtRule
|| $detachedRuleset instanceof Less_Tree_Media
|| $detachedRuleset instanceof Less_Tree_Mixin_Definition
) && $detachedRuleset->rules
) {
// @todo - note looks like dead code, do we need it ?
$rules = $detachedRuleset;
} elseif ( $detachedRuleset instanceof Less_Tree && is_array( $detachedRuleset->value ) ) {
// @phan-suppress-next-line PhanTypeMismatchArgument False positive
$rules = new Less_Tree_Ruleset( null, $detachedRuleset->value );
} else {
throw new Less_Exception_Compiler( 'Could not evaluate variable call ' . $this->variable );
}
$detachedRuleset = new Less_Tree_DetachedRuleset( $rules );
}
if ( $detachedRuleset->ruleset ) {
return $detachedRuleset->callEval( $env );
}
throw new Less_Exception_Compiler( 'Could not evaluate variable call ' . $this->variable );
}
}

View file

@ -6,11 +6,11 @@
class Less_Version {
/* Current release version of less.php */
public const version = '4.4.1';
public const version = '5.3.1';
/* Upstream less.js version that this release should be compatible with */
public const less_version = '2.5.3';
public const less_version = '3.13.1';
/* Parser cache version */
public const cache_version = '253-3';
public const cache_version = '3131-9';
}

View file

@ -4,7 +4,7 @@
*/
class Less_Visitor {
protected $methods = [];
/** @var array */
protected $_visitFnCache = [];
public function __construct() {
@ -13,10 +13,27 @@ class Less_Visitor {
}
public function visitObj( $node ) {
if ( !$node || !is_object( $node ) ) {
static $funcNames = [];
if ( !$node instanceof Less_Tree ) {
return $node;
}
$funcName = 'visit' . str_replace( [ 'Less_Tree_', '_' ], '', get_class( $node ) );
// Map a class name like "Less_Tree_Foo_Bar" to method like "visitFooBar".
//
// We do this by taking the last part of the class name (instead of doing
// a find-replace from "Less_Tree" to "visit"), so that we support codemod
// tools (such as Strauss and Mozart), which may modify our code in-place
// to add a namespace or class prefix.
// "MyVendor\Something_Less_Tree_Foo_Bar" should also map to "FooBar".
//
// https://packagist.org/packages/brianhenryie/strauss
// https://packagist.org/packages/coenjacobs/mozart
$class = get_class( $node );
$funcName = $funcNames[$class] ??= 'visit' . str_replace( [ '_', '\\' ], '',
substr( $class, strpos( $class, 'Less_Tree_' ) + 10 )
);
if ( isset( $this->_visitFnCache[$funcName] ) ) {
$visitDeeper = true;
$newNode = $this->$funcName( $node, $visitDeeper );
@ -24,7 +41,7 @@ class Less_Visitor {
$node = $newNode;
}
if ( $visitDeeper && is_object( $node ) ) {
if ( $visitDeeper && $node instanceof Less_Tree ) {
$node->accept( $this );
}

View file

@ -4,8 +4,11 @@
*/
class Less_Visitor_extendFinder extends Less_Visitor {
/** @var Less_Tree_Selector[] */
public $contexts = [];
/** @var Less_Tree_Extend[][] */
public $allExtendsStack;
/** @var bool */
public $foundExtends;
public function __construct() {
@ -23,7 +26,7 @@ class Less_Visitor_extendFinder extends Less_Visitor {
return $root;
}
public function visitRule( $ruleNode, &$visitDeeper ) {
public function visitDeclaration( $declNode, &$visitDeeper ) {
$visitDeeper = false;
}
@ -80,7 +83,7 @@ class Less_Visitor_extendFinder extends Less_Visitor {
}
public function visitRulesetOut( $rulesetNode ) {
if ( !is_object( $rulesetNode ) || !$rulesetNode->root ) {
if ( !$rulesetNode instanceof Less_Tree_Ruleset || !$rulesetNode->root ) {
array_pop( $this->contexts );
}
}
@ -94,12 +97,12 @@ class Less_Visitor_extendFinder extends Less_Visitor {
array_pop( $this->allExtendsStack );
}
public function visitDirective( $directiveNode ) {
$directiveNode->allExtends = [];
$this->allExtendsStack[] =& $directiveNode->allExtends;
public function visitAtRule( $atRuleNode ) {
$atRuleNode->allExtends = [];
$this->allExtendsStack[] =& $atRuleNode->allExtends;
}
public function visitDirectiveOut() {
public function visitAtRuleOut() {
array_pop( $this->allExtendsStack );
}
}

View file

@ -4,6 +4,7 @@
*/
class Less_Visitor_joinSelector extends Less_Visitor {
/** @var Less_Tree_Selector[][][] */
public $contexts = [ [] ];
/**
@ -13,7 +14,7 @@ class Less_Visitor_joinSelector extends Less_Visitor {
return $this->visitObj( $root );
}
public function visitRule( $ruleNode, &$visitDeeper ) {
public function visitDeclaration( $declNode, &$visitDeeper ) {
$visitDeeper = false;
}
@ -59,16 +60,16 @@ class Less_Visitor_joinSelector extends Less_Visitor {
public function visitMedia( $mediaNode ) {
$context = end( $this->contexts );
if ( count( $context ) === 0 || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) {
if ( count( $context ) === 0 || ( $context[0] instanceof Less_Tree_Ruleset && $context[0]->multiMedia ) ) {
$mediaNode->rules[0]->root = true;
}
}
public function visitDirective( $directiveNode ) {
public function visitAtRule( $atRuleNode ) {
$context = end( $this->contexts );
if ( $directiveNode->rules && count( $directiveNode->rules ) > 0 ) {
$directiveNode->rules[0]->root = $directiveNode->isRooted || count( $context ) === 0;
if ( $atRuleNode->rules && count( $atRuleNode->rules ) > 0 ) {
$atRuleNode->rules[0]->root = $atRuleNode->isRooted || count( $context ) === 0;
}
}

View file

@ -4,6 +4,7 @@
*/
class Less_Visitor_processExtends extends Less_Visitor {
/** @var Less_Tree_Extend[][] */
public $allExtendsStack;
/**
@ -106,7 +107,9 @@ class Less_Visitor_processExtends extends Less_Visitor {
$selectorTwo = "{unable to calculate}";
}
throw new Less_Exception_Parser( "extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")" );
throw new Less_Exception_Parser(
"extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")"
);
}
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
@ -116,7 +119,7 @@ class Less_Visitor_processExtends extends Less_Visitor {
return array_merge( $extendsList, $extendsToAdd );
}
protected function visitRule( $ruleNode, &$visitDeeper ) {
protected function visitDeclaration( $declNode, &$visitDeeper ) {
$visitDeeper = false;
}
@ -133,7 +136,7 @@ class Less_Visitor_processExtends extends Less_Visitor {
return;
}
$allExtends = end( $this->allExtendsStack );
$allExtends = end( $this->allExtendsStack );
$paths_len = count( $rulesetNode->paths );
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
@ -199,7 +202,12 @@ class Less_Visitor_processExtends extends Less_Visitor {
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
if ( $extend->allowBefore || ( $haystackSelectorIndex === 0 && $hackstackElementIndex === 0 ) ) {
$potentialMatches[] = [ 'pathIndex' => $haystackSelectorIndex, 'index' => $hackstackElementIndex, 'matched' => 0, 'initialCombinator' => $haystackElement->combinator ];
$potentialMatches[] = [
'pathIndex' => $haystackSelectorIndex,
'index' => $hackstackElementIndex,
'matched' => 0,
'initialCombinator' => $haystackElement->combinator
];
$potentialMatches_len++;
}
@ -212,7 +220,9 @@ class Less_Visitor_processExtends extends Less_Visitor {
if ( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ) {
$potentialMatch['finished'] = true;
if ( !$extend->allowAfter && ( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len ) ) {
if ( !$extend->allowAfter &&
( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len )
) {
$potentialMatch = null;
}
}
@ -399,7 +409,10 @@ class Less_Visitor_processExtends extends Less_Visitor {
if ( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ) {
$last_path = end( $path );
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
$last_path->elements = array_merge(
$last_path->elements,
array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex )
);
$currentSelectorPathElementIndex = 0;
$currentSelectorPathIndex++;
}
@ -411,9 +424,9 @@ class Less_Visitor_processExtends extends Less_Visitor {
// last parameter of array_slice is different than the last parameter of javascript's slice
$match['index'] - $currentSelectorPathElementIndex
),
[ $firstElement ],
array_slice( $replacementSelector->elements, 1 )
);
[ $firstElement ],
array_slice( $replacementSelector->elements, 1 )
);
if ( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ) {
$last_key = count( $path ) - 1;
@ -433,7 +446,10 @@ class Less_Visitor_processExtends extends Less_Visitor {
if ( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ) {
$last_path = end( $path );
$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
$last_path->elements = array_merge(
$last_path->elements,
array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex )
);
$currentSelectorPathIndex++;
}
@ -452,12 +468,12 @@ class Less_Visitor_processExtends extends Less_Visitor {
array_pop( $this->allExtendsStack );
}
protected function visitDirective( $directiveNode ) {
$newAllExtends = array_merge( $directiveNode->allExtends, end( $this->allExtendsStack ) );
$this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $directiveNode->allExtends );
protected function visitAtRule( $atRuleNode ) {
$newAllExtends = array_merge( $atRuleNode->allExtends, end( $this->allExtendsStack ) );
$this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $atRuleNode->allExtends );
}
protected function visitDirectiveOut() {
protected function visitAtRuleOut() {
array_pop( $this->allExtendsStack );
}

View file

@ -4,6 +4,7 @@
*/
class Less_Visitor_toCSS extends Less_VisitorReplacing {
/** @var bool|null */
private $charset;
public function __construct() {
@ -17,11 +18,11 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
return $this->visitObj( $root );
}
public function visitRule( $ruleNode ) {
if ( $ruleNode->variable ) {
public function visitDeclaration( $declNode ) {
if ( $declNode->variable ) {
return [];
}
return $ruleNode;
return $declNode;
}
public function visitMixinDefinition( $mixinNode ) {
@ -52,9 +53,9 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
return $mediaNode;
}
public function visitDirective( $directiveNode, &$visitDeeper ) {
if ( $directiveNode->name === '@charset' ) {
if ( !$directiveNode->getIsReferenced() ) {
public function visitAtRule( $atRuleNode, &$visitDeeper ) {
if ( $atRuleNode->name === '@charset' ) {
if ( !$atRuleNode->getIsReferenced() ) {
return;
}
if ( isset( $this->charset ) && $this->charset ) {
@ -64,35 +65,35 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
$this->charset = true;
}
if ( $directiveNode->rules ) {
$this->_mergeRules( $directiveNode->rules[0]->rules );
if ( $atRuleNode->rules ) {
self::_mergeRules( $atRuleNode->rules[0]->rules );
// process childs
$directiveNode->accept( $this );
$atRuleNode->accept( $this );
$visitDeeper = false;
// the directive was directly referenced and therefore needs to be shown in the output
if ( $directiveNode->getIsReferenced() ) {
return $directiveNode;
if ( $atRuleNode->getIsReferenced() ) {
return $atRuleNode;
}
if ( !$directiveNode->rules ) {
if ( !$atRuleNode->rules ) {
return;
}
if ( $this->hasVisibleChild( $directiveNode ) ) {
if ( $this->hasVisibleChild( $atRuleNode ) ) {
// marking as referenced in case the directive is stored inside another directive
$directiveNode->markReferenced();
return $directiveNode;
$atRuleNode->markReferenced();
return $atRuleNode;
}
// The directive was not directly referenced and does not contain anything that
//was referenced. Therefore it must not be shown in output.
return;
} else {
if ( !$directiveNode->getIsReferenced() ) {
if ( !$atRuleNode->getIsReferenced() ) {
return;
}
}
return $directiveNode;
return $atRuleNode;
}
public function checkPropertiesInRoot( $rulesetNode ) {
@ -101,7 +102,7 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
}
foreach ( $rulesetNode->rules as $ruleNode ) {
if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) {
if ( $ruleNode instanceof Less_Tree_Declaration && !$ruleNode->variable ) {
$msg = "properties must be inside selector blocks, they cannot be in the root. Index " . $ruleNode->index .
( $ruleNode->currentFileInfo ? ' Filename: ' . $ruleNode->currentFileInfo['filename'] : null );
throw new Less_Exception_Compiler( $msg );
@ -144,7 +145,7 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
if ( $rulesetNode->rules ) {
if ( count( $rulesetNode->rules ) > 1 ) {
$this->_mergeRules( $rulesetNode->rules );
self::_mergeRules( $rulesetNode->rules );
$this->_removeDuplicateRules( $rulesetNode->rules );
}
@ -220,14 +221,14 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
$ruleCache = [];
for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) {
$rule = $rules[$i];
if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) {
if ( $rule instanceof Less_Tree_Declaration || $rule instanceof Less_Tree_NameValue ) {
if ( !isset( $ruleCache[$rule->name] ) ) {
$ruleCache[$rule->name] = $rule;
} else {
$ruleList =& $ruleCache[$rule->name];
if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) {
if ( $ruleList instanceof Less_Tree_Declaration || $ruleList instanceof Less_Tree_NameValue ) {
$ruleList = $ruleCache[$rule->name] = [ $ruleCache[$rule->name]->toCSS() ];
}
@ -242,7 +243,7 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
}
}
protected function _mergeRules( &$rules ) {
public static function _mergeRules( &$rules ) {
$groups = [];
// obj($rules);
@ -251,7 +252,7 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
for ( $i = 0; $i < $rules_len; $i++ ) {
$rule = $rules[$i];
if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) {
if ( ( $rule instanceof Less_Tree_Declaration ) && $rule->merge ) {
$key = $rule->name;
if ( $rule->important ) {
@ -310,9 +311,9 @@ class Less_Visitor_toCSS extends Less_VisitorReplacing {
return new Less_Tree_Value( $mapped );
}
public function hasVisibleChild( $directiveNode ) {
public function hasVisibleChild( $atRuleNode ) {
// prepare list of childs
$rule = $bodyRules = $directiveNode->rules;
$rule = $bodyRules = $atRuleNode->rules;
// if there is only one nested ruleset and that one has no path, then it is
//just fake ruleset that got not replaced and we need to look inside it to
//get real childs

View file

@ -1,191 +0,0 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/../lib/Less/Autoloader.php';
Less_Autoloader::register();
// Create our environment
$env = array('compress' => false, 'relativeUrls' => false);
$silent = false;
$watch = false;
$rootpath = '';
// Check for arguments
array_shift($argv);
if (!count($argv)) {
$argv[] = '-h';
}
// parse arguments
foreach ($argv as $key => $arg) {
if (preg_match('/^--?([a-z][0-9a-z-]*)(?:=([^\s]+))?$/i', $arg, $matches)) {
$option = $matches[1];
$value = isset($matches[2]) ? $matches[2] : false;
unset($argv[$key]);
switch ($option) {
case 'h':
case 'help':
echo <<<EOD
Usage: lessc [options] sources [destination]
-h, --help Print help (this message) and exit.
-s, --silent Suppress output of error messages.
-v, --version Print version number and exit.
-x, --compress Compress output by removing some whitespaces.
--include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.
--strict-imports Force evaluation of imports.
-sm=on|off Turn on or off strict math, where in strict mode, math
--strict-math=on|off requires brackets. This option may default to on and then
be removed in the future.
-su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units
--strict-units=on|off that cannot be represented.
-ru, --relative-urls re-write relative urls to the base less file.
-rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.
Works with or without the relative-urls option.
-w, --watch Watch input files for changes.
EOD;
exit;
case 's':
case 'silent':
$silent = true;
break;
case 'w':
case 'watch':
$watch = true;
break;
case 'v':
case 'version':
echo "lessc " . Less_Version::version . " (less.php)\n\n";
exit;
case 'rp':
case 'rootpath':
$rootpath = $value;
break;
//parser options
case 'compress':
$env['compress'] = true;
break;
case 'ru':
case 'relative-urls':
$env['relativeUrls'] = true;
break;
case 'su':
case 'strict-units':
$env['strictUnits'] = ($value === 'on');
break;
case 'sm':
case 'strict-math':
$env['strictMath'] = ($value === 'on');
break;
case 'x':
case 'include-path':
$env['import_dirs'] = preg_split('#;|\:#', $value);
break;
}
}
}
if (count($argv) > 1) {
$output = array_pop($argv);
$inputs = $argv;
}
else {
$inputs = $argv;
$output = false;
}
if (!count($inputs)) {
echo("lessc: no input files\n");
exit;
}
if ($watch) {
if (!$output) {
echo("lessc: you must specify the output file if --watch is given\n");
exit;
}
$lastAction = 0;
echo("lessc: watching input files\n");
while (1) {
clearstatcache();
$updated = false;
foreach ($inputs as $input) {
if ($input == '-') {
if (count($inputs) == 1) {
echo("lessc: during watching files is not possible to watch stdin\n");
exit;
}
else {
continue;
}
}
if (filemtime($input) > $lastAction) {
$updated = true;
break;
}
}
if ($updated) {
$lastAction = time();
$parser = new Less_Parser($env);
foreach ($inputs as $input) {
try {
$parser->parseFile($input, $rootpath);
}
catch (Exception $e) {
echo("lessc: " . $e->getMessage() . " \n");
continue; // Invalid processing
}
}
file_put_contents($output, $parser->getCss());
echo("lessc: output file recompiled\n");
}
sleep(1);
}
}
else {
$parser = new Less_Parser($env);
foreach ($inputs as $input) {
if ($input == '-') {
$content = file_get_contents('php://stdin');
$parser->parse($content);
}
else {
try {
$parser->parseFile($input);
}
catch (Exception $e) {
if (!$silent) {
echo("lessc: " . ((string)$e) . " \n");
}
}
}
}
if ($output) {
file_put_contents($output, $parser->getCss());
}
else {
echo $parser->getCss();
}
}

View file

@ -8,19 +8,26 @@
// Register autoloader for non-composer installations
if ( !class_exists( 'Less_Parser' ) ) {
require_once __DIR__ . '/lib/Less/Autoloader.php';
require_once __DIR__ . '/Autoloader.php';
Less_Autoloader::register();
}
class lessc {
/** @var string */
public static $VERSION = Less_Version::less_version;
/** @var string|string[] */
public $importDir = '';
/** @var array<string,int> */
protected $allParsedFiles = [];
/** @var array<string,callable> */
protected $libFunctions = [];
/** @var array */
protected $registeredVars = [];
/** @var string */
private $formatterName;
/** @var array<string,mixed> */
private $options = [];
public function __construct( $lessc = null, $sourceName = null ) {

View file

@ -272,8 +272,10 @@ class Css{
$parser->SetImportDirs($import_dirs);
/* $parser->cache_method = 'php'; */
$parser::$options['cache_method'] == 'php';
$parser->SetCacheDir($dataDir . '/data/_cache');
\Less_Parser::$options['cache_method'] = 'php';
// $parser->SetCacheDir($dataDir . '/data/_cache');
\Less_Cache::SetCacheDir( $dataDir . '/data/_cache' );
// combine files
try{
@ -306,7 +308,7 @@ class Css{
gc_collect_cycles();
}
$less_files = $parser->allParsedFiles();
$less_files = $parser->getParsedFiles();
return [$compiled, $temp_sourcemap_name];
}