Skip to content

Conversation

@corpulentcoffee
Copy link
Contributor

Between 0.15.0 and 0.16.0, dePseudify() seemed to start mishandling selectors that have multiple pseudos, which isn't a case well-represented in the test suite but does occur in the wild (e.g. in Bootstrap 4).

I'm including here a failing test and a possible fix.


On 0.15.0:

$ echo '<p class="used">Hi</p>' | ./bin/uncss --raw '.used { color: red } .unused:visited::before { content: "a" }'
.used { color: red }

On master / 0.16.0:

$ echo '<p class="used">Hi</p>' | ./bin/uncss --raw '.used { color: red } .unused:visited::before { content: "a" }'
.used { color: red } .unused:visited::before { content: "a" }

I bisected this to 0170fe1 (#345), which I think is the culprit.

That pseudosRegex = new RegExp('([^\\\\])(' + ignoredPseudos.join('|') + ')', 'g') produces weird results if you have multiple pseudos hanging off a selector, e.g.:

$ node
> ignoredPseudos = [ . . . ]
[ . . . ]
> pseudosRegex = new RegExp('([^\\\\])(' + ignoredPseudos.join('|') + ')', 'g')
/([^\\])(:link|:visited|:hover|:active|:focus|:enabled|:disabled|:checked|:indeterminate|:required|:invalid|:valid|::first-line|::first-letter|::selection|::before|::after|:target|:before|:after|::?-(?:moz|ms|webkit|o)-[a-z0-9-]+)/g
> 'a:visited'.replace(pseudosRegex, '$1')
'a'
> 'a::before'.replace(pseudosRegex, '$1')
'a'
> 'a:visited:hover'.replace(pseudosRegex, '$1')
'a:hover'
> 'a:visited::before'.replace(pseudosRegex, '$1')
'a:'

I think if we just give the pseudo group a quantifier instead of trying to use the g flag from the old regex, we would get what we want (i.e. replace on any non-slash character followed by one or more pseudos):

> pseudosRegex = new RegExp('([^\\\\])(' + ignoredPseudos.join('|') + ')+')
/([^\\])(:link|:visited|:hover|:active|:focus|:enabled|:disabled|:checked|:indeterminate|:required|:invalid|:valid|::first-line|::first-letter|::selection|::before|::after|:target|:before|:after|::?-(?:moz|ms|webkit|o)-[a-z0-9-]+)+/
> 'a:visited'.replace(pseudosRegex, '$1')
'a'
> 'a::before'.replace(pseudosRegex, '$1')
'a'
> 'a:visited:hover'.replace(pseudosRegex, '$1')
'a'
> 'a:visited::before'.replace(pseudosRegex, '$1')
'a'

It should also still work for the original "escaped" case the original change wanted to accomplished:

> '.sm\\:hover\\:font-hairline:hover'.replace(pseudosRegex, '$1')
'.sm\\:hover\\:font-hairline'
> '.sm\\:hover\\:font-hairline:hover::before'.replace(pseudosRegex, '$1')
'.sm\\:hover\\:font-hairline'

It also seems to work for at least what is present in today's test suite.

Introduces a new selector-based automated test that uses a pseudo class
followed by a pseudo element (in this case, `.unused:hover::before`).

This sort of selector is used in Bootstrap 4, and was correctly removed
by the 0.15.0 release of uncss. It is unexpectedly left in in the 0.16.0
release.
If the `selector` here has multiple pseudo elements attached to it, then
this new "escape-aware" regular expression doesn't work without allowing
adding a quantifier after the closing parenthesis for the ignored
pseudos.
@mikelambert
Copy link
Collaborator

Looks great, thanks for the catch and fix!

@mikelambert mikelambert merged commit 78e9df8 into uncss:master Nov 26, 2017
@corpulentcoffee corpulentcoffee deleted the pseudo-class-with-pseudo-element branch November 26, 2017 21:19
@mikelambert
Copy link
Collaborator

Released as 0.16.1.

@XhmikosR
Copy link
Member

@mikelambert: please push the git tag also.

@mikelambert
Copy link
Collaborator

Oops sorry, pushed.

@uncss uncss locked as spam and limited conversation to collaborators May 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants