stylelint: Fix false positives for pseudo classes in no-descending-specificity

Based on I’m using sass to develop css, I don’t want to define the class (.b) without parents. however, it will happen error with this rule.

is it(as below example) not usual for define class?

but I still need this rule to validate the other situations.

Which rule, if any, is this issue related to?

no-descending-specificity

What CSS is needed to reproduce this issue?

.a {
    .b {}
}

.a--modify {
    .b {}
}

What stylelint configuration is needed to reproduce this issue?

no-descending-specificity

{
  "rules": {
    "no-descending-specificity": true
  }
}

Which version of stylelint are you using?

version: 7.8.0

What actually happened (e.g. what warnings or errors you are getting)?

Expected selector “.a–modify .b” to come before selector “.a .b” Even I switch the order, it still happened.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 3
  • Comments: 31 (22 by maintainers)

Commits related to this issue

Most upvoted comments

Could you explain further?

Defo. I’m not 100% sure myself. This rule is somewhat tricky.

I think something is amiss. I’m unsure whether it’s a bug, a misalignment between the docs and rule behaviour, or, perhaps, an option is needed to allow @monochrome-yeh code style of:

.a .b {}
.a .b:focus {}

.a--alt .b {} /* rules says this should come before `a. .b:focus` */
.a--alt .b:focus {}

This currently warns because:

  1. .b is compared to .b:focus.
  2. .a .b:focus has a specificity of 0,3,0 and .a--alt .b, which follows it, has specificity of 0,2,0.

However, it feels like this is a valid code style because .b is a descendant with a different parent. The intent of the rule is to enforce that “Stylesheets are most legible when overriding selectors always come after the selectors they override” - however, in this situation, I think there is no risk of override because of the different parent.

So, it feels like the rule should continue to warn if there is:

  1. no parent
  2. or the parents are the same
  3. or one of the parents is of a different type of selector e.g. id selector (#a) vs class selector (.a)

For example, these should continue to warn:

.a:hover {}
.a {}
.a .b:hover {}
.a .b {}
.a .b {}
#a .b {}

However, if:

  1. there are parent selectors
  2. and there are the same number of parent selectors
  3. and these parent selectors are of the same selector types e.g. class selectors
  4. but they are different selectors e.g. .a vs .c

I’m wondering whether it shouldn’t warn e.g. this is ok:

.a .b:hover {}
.c .b {}

I think I’m finding the fact that pseudo-classes are treated differently than chained classes a bit confusing. The docs read:

Here’s how it works: This rule looks at the last compound selector in every full selector, and then compares it with other selectors in the stylesheet that end in the same way.

However, I think the rule treats .a and a:focus as the same compound selector even though they aren’t. It makes sense to do this because it allows the rule to catch a:focus {} a {}, but it feels like it might introduce the, arguable, false positive outlined above.

there are parent selectors and there are the same number of parent selectors and these parent selectors are of the same selector types e.g. class selectors but they are different selectors e.g. .a vs .c

I think this list is right, worth implementing.

@The-Code-Monkey

The CSS spec has not changed during this time, so this remains “not a bug”. Everything is working as it should with regards to this GitHub issue.

Please see my last comment if you need more details. https://github.com/stylelint/stylelint/issues/2489#issuecomment-518947126

Please also see the documentation for the rule that outlines where confusion can occur. no-descending-specificity#dom-limitations

@soundasleep

CSS specificity is complex, which is why this rule exists. There is no inconsistency in the errors you are seeing. There are 6 selectors mentioned in your error messages and when considered together the correct specificity order for the 3 th is:

table.expand-last-column th {}
table tfoot tr:first-child th {}
table.expand-last-column th:last-child {}

The same applies for the td selectors.

This can be confirmed with any CSS specificity calculator.

The way that you are nesting the rules makes putting things in specificity order difficult. You need to decide if you want your code to be short, or if you want it to be easier to understand which selectors take priority. In your situation you can’t have both.

@dawidk92 As discussed above, this isn’t a bug, even though the ticket remains open and marked as a bug.

I’ll comment on your specific issue on the linked page.

@gsc89 The issue you are having is compounded by the fact that you have a duplicate rule.

  &__form {
    padding: 16px;

    form {
      input,
      .select2-container {
        ...
      }

      .select2-container {
      ....
      }
    }
  }

Because you have the input included you can not combine the two rules, but doing so would probably allow you to fix your issue. You could possibly define the rules and @extend them into a input{} and then a single .select2-continer{} to get around the issue.

You mention that you get the “exact same error”, but I’m guessing the error is different and swapped between first-of-type and last-of-type between your two code blocks.

But in any case, this is a valid lint error because the CSS specificity order is not reflected in the order the code is in.

okay, I got it, thanks for your attention.

On further investigation, I think you have uncovered a bug.

According to the specs, pseudo classes are simple selectors:

Like other simple selectors, pseudo-classes are allowed in all compound selectors contained in a selector, and must follow the type selector or universal selector, if present. [Emphasis mine]

And a compound selector as:

A compound selector is a sequence of simple selectors that are not separated by a combinator.

As such, I think .c-menu--alt .c-menu__content {} and .c-menu .c-menu__content:focus {} should not be compared.

I’m wondering if this code should only target pseudo elements. As per the rule’s README:

There’s one other important feature: Selectors targeting pseudo-elements are not considered comparable to similar selectors without the pseudo-element, because they target other elements on the rendered page. For example, a::before {} will not be compared to a:hover {}, because a::before targets a pseudo-element whereas a:hover targets the actual .

I’m going to label this as a bug. @monochrome-yeh Feel free to investigate the code further, and, if that confirms my thoughts, please consider contributing a fix.