Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Less fails to parse valid Custom Property values (ex: @apply polyfill) #2715

Closed
Jamtis opened this issue Oct 21, 2015 · 34 comments
Closed

Less fails to parse valid Custom Property values (ex: @apply polyfill) #2715

Jamtis opened this issue Oct 21, 2015 · 34 comments

Comments

@Jamtis
Copy link

Jamtis commented Oct 21, 2015

This code breaks:

paper-drawer-panel {
  --paper-drawer-panel-left-drawer: {
    background-color: red;
  };
}

The curly brackets in the property value break the parser.

I'd like LESS to support the @apply syntax, as used by Polymer.
https://www.polymer-project.org/1.0/docs/devguide/styling.html#custom-css-mixins

Also tried:

paper-drawer-panel {
  @ruleset: {
    prop: value;
  };
  --paper-drawer-panel-left-drawer: %(~"%d", @ruleset);
}

It didn't break the parser but delivered:

paper-drawer-panel {
  --paper-drawer-panel-left-drawer: ;
}

which is clearly not a desirable behavior.

@seven-phases-max
Copy link
Member

I'm afraid non-CSS syntax support is beyond Less scope. So my -1.

Either way notice you can emit whatever code into the output with escaping. E.g.:

xpaper-drawer-panel {
  --paper-drawer-panel-left-drawer: ~"{background-color: red;}";
}

Or in more hackish multiline way:

paper-drawer-panel {
  -:~";--paper-drawer-panel-left-drawer: {";
    background-color: red;
    color: blue;
  -:~";}";
}

In a more complex situations it's also always possible to use @import (inline).


As for attempts like %(~"%d", @ruleset); - No, Less variables are not macros, they are not expanded regardless of context, and specifically for unnamed rulesets the only proper syntax is @ruleset() within a ruleset body and not as a property value.

@Jamtis
Copy link
Author

Jamtis commented Oct 21, 2015

Well I agree with you that non-standard syntax should generally not be supported.

However this means that LESS in incompatible with Polymer in many cases.

Thanks for the reply anyway. 👍

@donny-dont
Copy link

Not sure if this makes a difference in your decision @seven-phases-max but there is an open spec for an @apply rule, http://tabatkins.github.io/specs/css-apply-rule/#issue-882293bd.

Also see Polymer/polymer#1373 as it sounds like something Google is going to really push through the process.

And SASS seems to be addressing this sass/sass#1128

@seven-phases-max
Copy link
Member

@donny-dont Note I do not decide anything (I did not close the proposal) - I'm only a critic.

Well, Tab Atkins generated tens of CSS standard proposals (named "ideas") and while Google guys may push this into their browser even tomorrow, this is still too far from becoming even CSS draft thing (remember CSS vars not entering CSS world for about ten years?).

@donny-dont
Copy link

@seven-phases-max apologies for the implication. I just wanted to make a note that Polymer is pushing the mixin proposal and Chrome is likely to implement it once its shaped up which was something @Jamtis didn't really communicate.

I'm fairly new to using LESS is there any sort of plugin system for the parser in these sorts of scenarios? Something like an experimental flag you can turn on and off?

@seven-phases-max
Copy link
Member

I'm reopening this to be able to redirect duplicate requests here.

@ebidel
Copy link

ebidel commented Jan 22, 2016

/sub

@dantman
Copy link

dantman commented May 9, 2016

We have a spec for this; a polyfill exists; Chrome has implemented it; Polymer is already using it in a 1.0 release; and websites are already using Polymer.

I think it's a perfectly reasonable expectation for Less to support the syntax at a bare minimum level.

Less already passes @apply, --foo: bar;, and var(--foo) through. The only thing missing is getting Less to pass through the { ... } in --foo: { property: value; } with the same basic processing given to other css blocks; instead of coming to a hard stop and throwing a parse error.

@stramel
Copy link

stramel commented Aug 3, 2016

I think the full spec of CSS Variables should be supported by LESS. Would love for this to happen!

@matthew-dean
Copy link
Member

@dantman

We have a spec for this; a polyfill exists; Chrome has implemented it; Polymer is already using it in a 1.0 release; and websites are already using Polymer.

To me, that's still not a very compelling reason. That translates to: Google, Google, and Google are using this. There's been no interest pop up anywhere else, and there are no indications there ever will be.

@donny-dont

I'm fairly new to using LESS is there any sort of plugin system for the parser in these sorts of scenarios? Something like an experimental flag you can turn on and off?

There is a plugin system, but no direct support for changing parsing. So, that would not be a trivial thing to do.

@dantman
Copy link

dantman commented Aug 4, 2016

@dantman
We have a spec for this; a polyfill exists; Chrome has implemented it; Polymer is already using it in a 1.0 release; and websites are already using Polymer.

To me, that's still not a very compelling reason. That translates to: Google, Google, and Google are using this. There's been no interest pop up anywhere else, and there are no indications there ever will be.

That conclusion would make sense if we were talking about a more proprietary and unsupported syntax that deviated significantly from normal css; but we're talking about Less generating parse errors when Less already understands nesting and the syntax in question fits perfectly within CSS' bracket matching based parse rules and doesn't break surrounding css, and the other major CSS preprocessor and postprocessor either are working on supporting it or already work.

@matthew-dean
Copy link
Member

That conclusion would make sense if we were talking about a more proprietary and unsupported syntax that deviated significantly from normal css; but we're talking about Less generating parse errors when Less already understands nesting and the syntax in question fits perfectly within CSS' bracket matching based parse rules and doesn't break surrounding css, and the other major CSS preprocessor and postprocessor either are working on supporting it or already work.

Not irrelevant. It's pretty similar to Less's detached rulesets. And there's precedent for other "pass-thru" stuff added to Less. So I'm not necessarily against it. Just skeptical if there is value beyond Polymer at the moment.

@stramel
Copy link

stramel commented Aug 4, 2016

Official Support for CSS Variables is listed here: http://caniuse.com/#feat=css-variables

Minimum Browser version for default CSS Variable Support

  • Chrome 49
  • Chrome for Android 51
  • Android Browser 51
  • Firefox for Android 47
  • Firefox 31
  • Safari 9.1
  • iOS Safari 9.3
  • Opera 36
  • Opera Mobile 37

Without a polyfill, this equates to roughly, 65% global browser usage.

With multiple CSS Variable Polyfills out there, browsers implementing it and large browser providers pushing for it. It seems like this would be a valuable feature to have.

Official Spec: https://drafts.csswg.org/css-variables/

@matthew-dean
Copy link
Member

matthew-dean commented Aug 4, 2016

@stramel That has nothing to do with this issue. CSS Variables are already supported in Less. The request is about @apply, which is not part of that spec.

EDIT: not all CSS Custom Property values are supported, see below.

@stramel
Copy link

stramel commented Aug 4, 2016

@matthew-dean Sorry, I misunderstood. Regardless, 👍 for CSS Mixins/Custom Property Sets

@donny-dont
Copy link

@matthew-dean what is the bar for less supporting? If another browser implemented then good to go?

@matthew-dean
Copy link
Member

@donny-dont Since Less is a community project, there's no specific bar. But yes, something beyond a single browser would make it a more general-purpose feature.

@henkrijneveld
Copy link

Just if someone is searching this (as I did): you can put all the Polymer non-standard css in a separate file and import this with the inline keyword in your less file. The less file has to interface between less variables and polymer custom properties. But that is the way Polymer wants it, anyway. Agree you cannot implement every deviation from standards.

@matthew-dean
Copy link
Member

matthew-dean commented Oct 12, 2017

Just to revisit this, I think I unfortunately had a narrow view of the scope of the problem.

That is: yes, @apply is just a proposal, BUT REGARDLESS, this is not invalid CSS. Not anymore.

paper-drawer-panel {
  --paper-drawer-panel-left-drawer: {
    background-color: red;
  };
}

My mistake was thinking that @apply was proposing an extension to the Custom Property syntax, but it isn't. The above IS a valid custom property value, and, as such, should be added to Less support. I wish I had taken the time to dig deeper into the spec. (Here: https://www.w3.org/TR/css-variables/#syntax.)

Less has a core goal to support valid CSS, and Custom Property syntax is implemented fully in every browser as of now. The spec is extremely permissive, and may require some big changes in the way property values are parsed. You can put almost anything into it. Just how crazy you can get with values and whether browsers will allow that, I'm not sure. But, the way I read the spec, mostly anything is valid if it's well formed. Meaning, until you encounter a "top-level" semicolon token, you can go nuts, wrapping all sorts of stuff in curly braces or parentheses.

This means that JavaScript libraries like Polymer are increasingly likely to "hack" CSS to place declarative properties / values where they're readable by JS, which was really not possible before this CSS feature.

So 👍 to implementing ASAP.

Sorry to those who tried to point out that this is valid custom property syntax.

@matthew-dean
Copy link
Member

Related: #2986

@matthew-dean matthew-dean changed the title CSS @apply Less parser fails to parse valid Custom Property values (ex: @apply polyfill) Oct 12, 2017
@matthew-dean matthew-dean changed the title Less parser fails to parse valid Custom Property values (ex: @apply polyfill) Less fails to parse valid Custom Property values (ex: @apply polyfill) Oct 12, 2017
@matthew-dean
Copy link
Member

I'm marking this both as bug and feature request because a) Less claims to parse custom property values, but fails sometimes (but it's not clear when), but b) the types of values Less needs to handle is beyond the original design of even CSS itself, let alone Less, so it is a new feature.

@matthew-dean
Copy link
Member

matthew-dean commented Feb 15, 2018

Another test that can be added to Less tests for custom property parsing. This is perfectly valid CSS as of today.

.test {
  --custom-function: () => { let x = 1; window['NAMESPACE'].doSomething(x); };
}

Essentially, once something is in () [] or {}, you can dump in any characters you want. So, semi-colons would pass in the above example. It requires some recursive parsing until braces get matched and closed (if/when they're opened) before you can test for a closing semi-colon.

I started that work here but not sure yet if this is correct. https://github.com/less/less.js/blob/edge/lib/less/parser/parser.js#L1326

Note: to clarify if anyone is confused at looking at the above line, this JavaScript would not DO anything. CSS would just consider it a long, unknown anonymous value.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 15, 2018

Essentially, once something is in () [] or {}, you can dump in any characters you want.

mm, is it? For me https://www.w3.org/TR/css-variables/#syntax sounds like any token (except those listed there) can be anywhere there (regardless of either kind of parens). Or does this assumed by some other paragraph? Could you, please point me, where it says about any character, or anything specific about values inside parens? (unless it's url(...) of course)?

In other words, are either of --var: ?;, --var: (?); or --var: [¾]; actually valid?

@matthew-dean
Copy link
Member

matthew-dean commented Feb 15, 2018

This is the relevant section up top, which I may be mis-interpreting.

The allowed syntax for custom properties is extremely permissive. The <declaration-value> production matches any sequence of one or more tokens, so long as the sequence does not contain <bad-string-token>, <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>, or top-level <semicolon-token> tokens or <delim-token> tokens with a value of "!".

So, important takeaways are:

  1. Only the top-level semicolon-token matters. I interpret that to mean "not within some other matched pair"? But I could be misinterpreting what "top-level" means.

  2. Maybe the JS example given on that page is misleading, (-foo: if(x > 5) this.width = 10; is valid.) But an unspaced = certainly is not a typical CSS token, nor is this.width. Without clarification, and given how permissive error recovery is on browsers, I don't think we can assume any parsing error for anything. We already know that the @apply syntax is valid (with a set of rules), even if it has a custom application. So we know that custom properties allow for multiple semi-colons within {}; it's therefore logical that this language implies something about the permissive nature within matching (), [], {}. There's a lot we don't know here, and without knowing what can go in a custom property, I think we have to assume anything can go in it, as long as it follows a particular structure ("so long as the sequence does not contain <bad-string-token>, <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>") and pass that property along to the browser. I believe the intent here is to create maximum flexibility for developers, while creating the bare minimum requirements for successful parsing.

So, you can't do this:

.bar {
  --foo: {;
  --baz: ";
}

...because the syntax wants to allow you to put in anything, including multiple semi-colons, but it wants to be able to know when you're done. That's where the matching quotes / braces / parentheses / brackets come into play. As long as you can indicate you've "closed" your syntax in some way, I believe the intention here is that you can do what you want within that syntax.

@matthew-dean
Copy link
Member

As to this:

are either of --var: ?;, --var: (?); or --var: [¾]; valid?

I don't know.

Should we treat them as valid? IMO yes. Because we don't / can't know, but we can successfully parse it without too much trouble.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 15, 2018

As for possible implementation... It's not a big problem to allow anything there until ; but then we still should handle at least strings, urls and nested {} (as they may contain ; inside). Obviously this would mean no Less code within.

A step further would be to treat it (at the parsing stage) as a sort of DR (w/o its outer {}) with just more tokens allowed (though it looks like a dead-way since you can't reuse DR-parser for something like --var: (1 > 2) / {whatever} foo;)

And finally, for something more convenient I can't honestly see anything but writing a full-featured CSS-tokeniser with some of the Less features (tokens and then their evaluation) allowed in. A biiiiiiiig problem in other words :(

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 15, 2018

Only the top-level semicolon-token matters. I interpret that to mean "not within some other matched pair"? But I could be misinterpreting what "top-level" means.

Yes I'm pretty sure it's about the terminating ;. I.e. as in: --var: ";" url(;) {;}; is valid and --var: ; {} foo; is not (the first ; by being "top-level" terminates the statement).
I'm not sure about just (;) though.

@matthew-dean
Copy link
Member

matthew-dean commented Feb 15, 2018

Obviously this would mean no Less code within.

My only thought is that we could maaaaybe allow interpolation syntax, in case you wanted to generate custom properties. But maybe that's sort of a "round 2 if there's interest" kind of idea.

I'm not sure about just (;) though.

I'm not either? I believe specs often have parsing details published somewhere (like token path diagrams), but I'm not sure if it exists in this case. As a stage 1 - only requiring matching {}, (), and quotes, and dumping anything into an anonymous node until a top-level ; seems fine. Matching brackets [] may not be necessary, but they're also mentioned and it's fairly trivial once all the other pieces are there.

I don't think we need to specially tokenize anything, or get into mixing this with DR stuff. After all, someone could end up with:

.weird {
  --php: ($x = 5 /* + 15 */ + 5; echo $x;);
  --example: [My DR will be --this: { 
    blah: nope;
    --never mind i gave up;
    no wait here it --is: {
      lol: cats;
    }
  }];
}

In other words, it would be better for Less to wipe its hands clean of dealing with the contents of custom properties.

Also, it's technically feasible for a Less plugin author to write a visitor plugin that takes pieces and sends matching custom properties back through the Less parser to create new nodes. All we should really do is dump the contents into a rule node and mark the rule as a custom property, then just as faithfully dump the output.

@matthew-dean
Copy link
Member

Note that if we modify the above example, THIS should be considered invalid:

.weird {
  --example: [My DR will be --this: { 
    blah: nope;
    --never mind i gave up;
    no wait here it --is: {
      lol: cats;
   // missing matching }
  }];
}

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 15, 2018

"no Less code"

Probably, but this means a downgrade - like now one can write:
--var: darken(red, 5%) + 1;
and it works, but then (for the sake of --fortran: read (*, *, iostat=ierr) radius, height;) it won't :(

We can have an option for this probably (like --oh-no-yet-another-option-for-custom-properties-to-be-parsed-one-way-or-another: on :)

@matthew-dean
Copy link
Member

matthew-dean commented Feb 15, 2018

Probably, but this means a downgrade - like now one can write --var: darken(red, 5%); and it works, but then (for the sake of --fortran: read (*, *, iostat=ierr) radius, height;) it won't :(

🤔 Well..... yes I see what you mean, which is why maybe interpolation of some kind might be necessary? We could basically at the parser treat things similar to:

@iostat: ierr;
--fortran: read (*, *, iostat=@{iostat}) radius, height;

// treat similar to:
--fortran: ~"read (*, *, iostat=@{iostat}) radius, height";

(With the aforementioned caveats of proper matching tokens?)

I mean.... the alternative is that we essentially recommend escaping syntax. Just would be nice for fewer modifications of in-place CSS code.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 15, 2018

Yes, interpolation will do the trick. Though it still would be nice to a DR-ike parsing as an experimental option (I'm pretty sure many will prefer real --var: darken(red, 5%); to imaginary --javascript: 1 = 2; at least until such custom property hacking becomes widespread :)

In other words, I'm fine with both ways (separately or simultaneously).

@matthew-dean
Copy link
Member

I think I have a fairly robust implementation / solution for this. I used a number of code examples from this thread in tests. So, custom property values and unknown at-rules are essentially treated as escaped quoted values to allow interpolation. Check out #3213.

matthew-dean added a commit that referenced this issue Jun 22, 2018
* Adds permissive parsing for at-rules and custom properties
* Added error tests for permissive parsing
* Change custom property value to quoted-like value
* Allow interpolation in unknown at-rules
* Allows variables to fallback to permissive parsing
* Allow escaping of blocks
@matthew-dean
Copy link
Member

Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants