Skip to content

Commit

Permalink
Merge "Add support for calc() exception"
Browse files Browse the repository at this point in the history
  • Loading branch information
jenkins-bot authored and Gerrit Code Review committed Jun 26, 2024
2 parents 58c499c + f6e3695 commit de7b209
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 24 deletions.
14 changes: 14 additions & 0 deletions lib/Less/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Less_Environment {
public $inCalc = false;
public $mathOn = true;

private $calcStack = [];

/** @var Less_Tree_Media[] */
public $mediaBlocks = [];
/** @var Less_Tree_Media[] */
Expand Down Expand Up @@ -151,6 +153,18 @@ public static function isPathRelative( $path ) {
return !preg_match( '/^(?:[a-z-]+:|\/|#)/', $path );
}

public function enterCalc() {
$this->calcStack[] = true;
$this->inCalc = true;
}

public function exitCalc() {
array_pop( $this->calcStack );
if ( !$this->calcStack ) {
$this->inCalc = false;
}
}

/**
* Canonicalize a path by resolving references to '/./', '/../'
* Does not remove leading "../"
Expand Down
7 changes: 7 additions & 0 deletions lib/Less/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,13 @@ public function shade( $color, $amount = null ) {
return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
}

/**
* @see less-3.13.1.js#functions _SELF
*/
public function _self( $args ) {
return $args;
}

public function extract( $values, $index ) {
$index = (int)$index->value - 1; // (1-based index)
// handle non-array values as an array of length 1
Expand Down
19 changes: 15 additions & 4 deletions lib/Less/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1050,14 +1050,14 @@ private function parseComment() {
* "milky way" 'he\'s the one!'
*
* @return Less_Tree_Quoted|null
* @see less-2.5.3.js#entities.quoted
* @see less-3.13.1.js#entities.quoted
*/
private function parseEntitiesQuoted() {
private function parseEntitiesQuoted( $forceEscaped = false ) {
// Optimization: Determine match potential without save()/restore() overhead
// Optimization: Inline matchChar() here, with its skipWhitespace(1) call below
$startChar = $this->input[$this->pos] ?? null;
$isEscaped = $startChar === '~';
if ( !$isEscaped && $startChar !== "'" && $startChar !== '"' ) {
if ( ( !$isEscaped && $startChar !== "'" && $startChar !== '"' ) || ( $forceEscaped && !$isEscaped ) ) {
return;
}

Expand Down Expand Up @@ -2574,6 +2574,8 @@ private function parseCondition() {
/**
* An operand is anything that can be part of an operation,
* such as a Color, or a Variable
*
* @see less-3.13.1.js#parsers.operand
*/
private function parseOperand() {
$negate = false;
Expand All @@ -2582,11 +2584,20 @@ private function parseOperand() {
return;
}
$char = $this->input[$offset];
// TODO: handle char `$`
if ( $char === '@' || $char === '(' ) {
$negate = $this->matchChar( '-' );
}

$o = $this->parseSub() ?? $this->parseEntitiesDimension() ?? $this->parseEntitiesColor() ?? $this->parseEntitiesVariable() ?? $this->parseEntitiesCall();
$o = $this->parseSub()
?? $this->parseEntitiesDimension()
?? $this->parseEntitiesColor()
?? $this->parseEntitiesVariable()
// TODO: from less-3.13.1.js missing entities.property()
?? $this->parseEntitiesCall()
?? $this->parseEntitiesQuoted( true );
// TODO: from less-3.13.1.js missing entities.colorKeyword()
// TODO: from less-3.13.1.js missing entities.mixinLookup()

if ( $negate ) {
$o->parensInOp = true;
Expand Down
27 changes: 22 additions & 5 deletions lib/Less/Tree/Call.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Less_Tree_Call extends Less_Tree implements Less_Tree_HasValueProperty {
public function __construct( $name, $args, $index, $currentFileInfo = null ) {
$this->name = $name;
$this->args = $args;
$this->calc = ( $name === 'calc' );
$this->calc = $name === 'calc';
$this->index = $index;
$this->currentFileInfo = $currentFileInfo;
}
Expand Down Expand Up @@ -45,6 +45,17 @@ private function functionCaller( $function, array $arguments ) {
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,
Expand All @@ -55,12 +66,17 @@ private function functionCaller( $function, array $arguments ) {
// 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
/**
* Turn off math for calc(), and switch back on for evaluating nested functions
*/
$currentMathContext = $env->mathOn;
$env->mathOn = !$this->calc;
// TODO: Less.js 3.13 also checks/toggles $env->inCalc

if ( $this->calc || $env->inCalc ) {
$env->enterCalc();
}

$args = [];
foreach ( $this->args as $a ) {
Expand Down Expand Up @@ -114,6 +130,7 @@ public function compile( $env ) {
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
Expand All @@ -129,7 +146,7 @@ public function compile( $env ) {
if ( $result !== null ) {
return $result;
}

$this->exitCalc( $env, $currentMathContext );
return new self( $this->name, $args, $this->index, $this->currentFileInfo );
}

Expand Down
21 changes: 16 additions & 5 deletions lib/Less/Tree/Variable.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public function __construct( $name, $index = null, $currentFileInfo = null ) {
/**
* @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
Expand All @@ -38,19 +39,29 @@ public function compile( $env ) {
}

$this->evaluating = true;

$variable = null;
foreach ( $env->frames as $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 );
}
Expand Down
19 changes: 19 additions & 0 deletions test/Fixtures/lessjs-3.13.1/override/_main/calc.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.no-math {
root: calc(100% - 30px);
root2: calc(100% - 40px);
width: calc(50% + (25vh - 20px));
height: calc(50% + (25vh - 20px));
min-height: calc(10vh + calc(5vh));
foo: 3 calc(3 + 4) 11;
bar: calc(1 + 20%);
}
.b {
one: calc(100% - 20px);
two: calc(100% - (10px + 10px));
three: calc(100% - (3 * 1));
four: calc(100% - (3 * 1));
nested: calc(calc(2.25rem + 2px) - 1px * 2);
}
.c {
height: calc(100% - ((10px * 3) + (10px * 2)));
}
32 changes: 32 additions & 0 deletions test/Fixtures/lessjs-3.13.1/override/_main/calc.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@val: 10px;
.no-math {
@c: 10px + 20px;
@calc: (@val + 30px);
root: calc(100% - @c);
root2: calc(100% - @calc);
@var: 50vh/2;
width: calc(50% + (@var - 20px));
height: calc(50% + ((@var - 20px)));
min-height: calc(((10vh)) + calc((5vh)));
foo: 1 + 2 calc(3 + 4) 5 + 6;
@floor: floor(1 + .1);
bar: calc(@floor + 20%);
}

.b {
@a: 10px;
@b: 10px;

one: calc(100% - ((min(@a + @b))));
two: calc(100% - (((@a + @b))));
three: calc(e('100%') - (3 * 1));
four: calc(~'100%' - (3 * 1));
nested: calc(calc(2.25rem + 2px) - 1px * 2);
}

.c {
@v: 10px;
height: calc(100% - ((@v * 3) + (@v * 2)));
}

// TODO: provide support to parse `@p: .mk-map();` see less/_main/calc.less line 43
11 changes: 8 additions & 3 deletions test/compare.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,15 @@ public function compare( string $fixtureDir, bool $useOverride ): void {
$this->summaryUnsupported[] = basename( $lessFile );
continue;
}
$overrideFile = $overrideDir ? "$overrideDir/$name.css" : null;
if ( $overrideFile && file_exists( $overrideFile ) ) {
$cssFile = $overrideFile;
$overrideCssFile = $overrideDir ? "$overrideDir/$name.css" : null;
if ( $overrideCssFile && file_exists( $overrideCssFile ) ) {
$cssFile = $overrideCssFile;
}
$overrideLessFile = $overrideDir ? "$overrideDir/$name.less" : null;
if ( $overrideLessFile && file_exists( $overrideLessFile ) ) {
$lessFile = $overrideLessFile;
}

$this->handleFixture( $cssFile, $lessFile, $options );
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
'lessjs-3.13.1' => [
'lessDir' => "$fixtureDir/lessjs-3.13.1/less/_main",
'cssDir' => "$fixtureDir/lessjs-3.13.1/css/_main",
// 'overrideDir' => "$fixtureDir/lessjs-3.13.1/override",
'overrideDir' => "$fixtureDir/lessjs-3.13.1/override/_main",
'unsupported' => [
// Permanently disabled, intentionally not supported.
'javascript',
Expand Down
19 changes: 13 additions & 6 deletions test/phpunit/FixturesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class FixturesTest extends LessTestCase {
'parens' => true,
],
'lessjs-3.13.1' => [
'calc' => true, // New Feature
'functions' => true,
'functions-each' => true,
'import-reference-issues' => true,
Expand Down Expand Up @@ -50,13 +49,21 @@ public static function provideFixtures() {
foreach ( glob( "$cssDir/*.css" ) as $cssFile ) {
$name = basename( $cssFile, '.css' );
$lessFile = "$lessDir/$name.less";
$overrideFile = $overrideDir ? "$overrideDir/$name.css" : null;
if ( $overrideFile && file_exists( $overrideFile ) ) {
if ( file_get_contents( $overrideFile ) === file_get_contents( $cssFile ) ) {
print "WARNING: Redundant override for $overrideFile\n";
$overrideCssFile = $overrideDir ? "$overrideDir/$name.css" : null;
if ( $overrideCssFile && file_exists( $overrideCssFile ) ) {
if ( file_get_contents( $overrideCssFile ) === file_get_contents( $cssFile ) ) {
print "WARNING: Redundant override for $overrideCssFile\n";
}
$cssFile = $overrideFile;
$cssFile = $overrideCssFile;
}
$overrideLessFile = $overrideDir ? "$overrideDir/$name.less" : null;
if ( $overrideLessFile && file_exists( $overrideLessFile ) ) {
if ( file_get_contents( $overrideLessFile ) === file_get_contents( $lessFile ) ) {
print "WARNING: Redundant override for $overrideLessFile\n";
}
$lessFile = $overrideLessFile;
}

if ( in_array( $name, $unsupported ) ) {
continue;
}
Expand Down

0 comments on commit de7b209

Please sign in to comment.