less.js icon indicating copy to clipboard operation
less.js copied to clipboard

CSS min and max function calls that hold CSS variables fail with "Operation on an invalid type"

Open rjgotten opened this issue 2 years ago • 24 comments

To reproduce:

With strict units enabled, perform a calc-like expression inside a min or max function and use a CSS variable. E.g.

prop : min(100% - var(--some-var), 10px);

Current behavior: Compiler will throw "Operation on an invalid type" error.

Expected behavior: Compiler knows the arguments to min and max can be calc-like and doesn't throw, but retains the arguments as-is.

Environment information:

  • less version: 4.1.3
  • nodejs version: 14
  • operating system: any

Having dug around a bit, it appears that the root of the issue is the fact that Operation is visited and flattened before passing to min and max, which means the var() node trips the compiler error as it afaict does not implement an operate method.

This problem can be avoided by setting evalArgs : false for both functions and performing custom lazy evaluation inside the functions themselves, where it can be wrapped in a try-catch and cause the function to go inert (and be treated as the CSS function proper) when incompatible types are present.

rjgotten avatar Feb 08 '23 09:02 rjgotten

can i work on this bug?

shindodkar avatar Mar 31 '23 12:03 shindodkar

@shindodkar I don't see why not!

matthew-dean avatar Mar 31 '23 19:03 matthew-dean

can I contribute to it ??

debmalya37 avatar Apr 27 '23 09:04 debmalya37

@GitsOfVivek @debmalya37 Anyone can do this at any time. @shindodkar asked about it March 31st, but nothing has been produced yet.

matthew-dean avatar May 15 '23 17:05 matthew-dean

One way to prevent having to do this several times in several places would be to, rather than creating a try / catch block within each function, would be to create an abstraction that outputs certain functions as-is.

In other words, a number of functions have had try / catch blocks added within the function statement, and outputs the nodes as-is instead of throwing an error if it can't evaluate something, but that gets repetitive to add per function.

A downside of doing this is that if the stylesheet author intended Less to evaluate the statement, and there is an error in the expression, rather than showing an error, it will just output the function.

matthew-dean avatar May 15 '23 17:05 matthew-dean

@GitsOfVivek I'm not sure what more can be said about it. min / max are Less functions. They are also CSS functions. So, passing a var() into it is invalid if you intended it to be evaluated as a Less function at compile time, but valid if you intended it to be evaluated as a CSS function at runtime.

matthew-dean avatar May 15 '23 17:05 matthew-dean

Can I be assigned this @matthew-dean

VIRAT9358 avatar Jun 24 '23 02:06 VIRAT9358

prop: clamp(10px, calc(100% - var(--some-var)), 100%); Can we use clamp() function has relatively good browser support but may not work in older or less commonly used browsers

VIRAT9358 avatar Jun 24 '23 02:06 VIRAT9358

I have been pretty active on github for the last few days, I'll be quick in fixing this. @matthew-dean can I take up this issue? There hasn't been any progress since the last 2 assignments

Cyddharth-Gupta avatar Jul 10 '23 11:07 Cyddharth-Gupta

@Cyddharth-Gupta 👍

matthew-dean avatar Jul 10 '23 17:07 matthew-dean

thanks @matthew-dean

Cyddharth-Gupta avatar Jul 11 '23 15:07 Cyddharth-Gupta

Hey @matthew-dean I tried implementing the abstraction in the src/less/functions/number.js

`function evaluateWithFallback(func, context) { return function (...args) { try { return func.apply(context, args); } catch (e) { // Return the original node as-is if evaluation fails return new Anonymous(func.toCSS(context)); } }; }

export default { min: evaluateWithFallback(function (...args) { return minMax.call(this,false, args); }, this), max: evaluateWithFallback(function (...args) { return minMax.call(this, true, args); }, this), convert: function (val, unit) { return val.convertTo(unit.value); }, pi: function () { return new Dimension(Math.PI); }, mod: function(a, b) { return new Dimension(a.value % b.value, a.unit); }, pow: function(x, y) { if (typeof x === 'number' && typeof y === 'number') { x = new Dimension(x); y = new Dimension(y); } else if (!(x instanceof Dimension) || !(y instanceof Dimension)) { throw { type: 'Argument', message: 'arguments must be numbers' }; }

    return new Dimension(Math.pow(x.value, y.value), x.unit);
},
percentage: function (n) {
    const result = mathHelper(num => num * 100, '%', n);

    return result;
}

}; ` I have added evaluateWithFallback() and make some changes in the export object in min and max.

the following test cases are failing: min(1, 4ex, 2pt) and max(5m, 3em);

Your expertise in this matter would be highly appreciated. Thanks

Cyddharth-Gupta avatar Jul 12 '23 14:07 Cyddharth-Gupta

@Cyddharth-Gupta Can you start a draft PR I can look at?

matthew-dean avatar Jul 12 '23 15:07 matthew-dean

the following test cases are failing: min(1, 4ex, 2pt) and max(5m, 3em);

I'm guessing Less tries to eagerly normalize them to the same unit type?

matthew-dean avatar Jul 12 '23 15:07 matthew-dean

@Cyddharth-Gupta Can you start a draft PR I can look at?

sure, will do that.

Cyddharth-Gupta avatar Jul 12 '23 15:07 Cyddharth-Gupta

@matthew-dean i created a Draft PR a couple of days ago, please have a look at it.

Cyddharth-Gupta avatar Jul 14 '23 18:07 Cyddharth-Gupta

@Cyddharth-Gupta CI failed , Before starting the PR review, make sure CI passes at least. Thanks

iChenLei avatar Jul 15 '23 03:07 iChenLei

@iChenLei the CI test that are failing are in accordance with the conversation with @matthew-dean , that's why Matthew asked me to start a draft PR, please go through the conversation above. Thanks

Cyddharth-Gupta avatar Jul 15 '23 04:07 Cyddharth-Gupta

@Cyddharth-Gupta it says you closed the PR?

matthew-dean avatar Jul 17 '23 18:07 matthew-dean

@matthew-dean my apologies, didn't get a reply for some days since I created the pr, so I thought it might not be an active issue. I will create a new one by tomorrow.

Cyddharth-Gupta avatar Jul 17 '23 18:07 Cyddharth-Gupta

Workaround: use string escaping

x {
   prop: e("min(100% - var(--some-var), 10px)");
}

results in

x {
   prop: min(100% - var(--some-var), 10px);
}

lubber-de avatar Aug 24 '23 17:08 lubber-de