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
Fix inaccurate end positions when postcss-resolve-nested-selector
is used in selector-*
#6234
Comments
postcss-resolve-nested-selector
losing raw sourcepostcss-resolve-nested-selector
is used in selector-*
Nor do I. I've labelled the issue as ready to implement in case any has any ideas. |
Is one solution contributing upstream / having the package return the raw source positions? The actual library code is only 25 lines/rather small, so hopefully adjusting it wouldn't be too tricky? |
postcss-resolve-nested-selector
is used in selector-*
postcss-resolve-nested-selector
is used in selector-*
Porting the code of stylelint/docs/developer-guide/plugins.md Line 314 in 1c386a6
|
If possible, I’d like to rely on a PostCSS plugin that implements nesting well because I think maintaining ported code may be too much for us. For example, postcss-nesting seems to work for the CSS Nesting spec. If need to support non-standard syntaxes like Sass (e.g. EDIT: it’s needed to investigate whether such plugins would fit to Stylelint, though. |
I'm revisiting this issue and investigating @romainmenke Do you have any good idea? |
Would it be fine if the selectors are desugared purely with .foo, bar {
&:hover {}
} gives : I don't mind adding a package for this under csstools (similar to the specificity package). |
Ah, no, we don't want such a
I'll dig into the problem more. Thanks for the quick response! |
Do you have examples rules of where using Because resolving with |
For example, in the stylelint/lib/rules/selector-class-pattern/index.mjs Lines 64 to 70 in eebb786
This rule resolves a nested selector as follows, so the .a {
.b {}
}
/* ↓ resolved */
.a .b {} |
It is indeed unnecessary in simple cases but would it cause false positives/negatives if This can be trivially transformed without .a {
.b {}
} More problematic : /* `divdiv` vs. div:is(div) */
div {
&& {}
}
.foo, #bar {
& {
/* `&` has specificity of 1 id selector for matches on `.foo` */
}
}
/*
`.baz .foo .bar {}`
but also matches:
`.baz.foo .bar {}`
`.foo .baz .bar {}`
*/
.foo .bar {
.baz & {}
}
/* https://github.com/w3c/csswg-drafts/issues/2881 */
.foo, fooz {
.bar, baz {
&:hover, &:focus {}
}
} |
That's my understanding, too: This change will have a greater impact on rules like
That'd be fantastic! |
I've added a package for this, but there are some things that aren't entirely clear how they should work. https://github.com/csstools/postcss-plugins/tree/main/packages/selector-resolve-nested If I understand it correctly there are multiple issues here for Stylelint:
I think the first issue (spec compliance) is covered with the new package I linked above. By resolving two selectors and combining their AST's the source locations get mangled anyway. It is fairly trivial to correct the source locations so that the parent selectors point to e.g. It is much harder to keep Ideally all selector AST nodes would already be offset by their position in the overall CSS. |
@romainmenke Thanks for creating the package so quickly. It's fantastic! Yes, your understanding is correct, and my question was bad. As you commented, keeping an original source position is almost impossible through resolving a nested selector via a library. Instead, we should save such a position in a rule logic, e.g. |
I'd like to use |
One issue I didn't foresee is that rules like This rule (and others like it?) analyse selectors in a more superficial/syntatical way. The amount of The specificity is relevant and the matched elements are also relevant. This makes it hard to apply standard CSS analogues onto these rules. Example : foo #y, .z bar { foo > & {} } Is equivalent to : foo > :is(foo #y, .z bar) {} This contains 3 type selectors. But a rule like Maybe rules that work like I think a larger effort is needed to redefine which rules should exist for selectors and how they should work. I am concerned that without a good overview and a clear direction the end result wont be consistent. |
@romainmenke Thanks a lot for the further investigation. My understanding of {
"rules": {
"selector-max-type": 2
}
} foo #y, .z bar { foo > & {} } /* original */
foo > :is(foo #y, .z bar) {} /* resolved with :is() */
foo > foo #y, foo > .z bar {} /* resolved without :is() */ For now, all the examples are lint-free because the count of type selectors is within |
This indeed doesn't error, but because of another implementation detail. I am going to try and find the time to add a second algorithm to resolve nested selectors without Maybe we won't need this after all but I think that it will be easier to reason about all this if you can switch out the algorithms and investigate the different outcomes. I think we might need both algorithms to cover the existing rules. |
This is the crux of the issue. When we first built these rules, selectors were a different beast:
So we added rules to help users:
For example, we added We then added rules like: There's an overlap between these rules as each tries to limit complexity (often naively). A lot has changed in the past few years, and we should consider revising our selector rules to meet contemporary CSS. The most significant change is browsers support nesting and desugar to
Is it fair to say that only If so, correctly resolving relative selectors makes sense for this rule. Whereas the other
This will:
Are there other ways we can revisit these rules? As an aside, it's starting to feel like our next major release is an opportunity to modernise quite a few of our rules to make them more useful for modern CSS. There will be tension between pushing forward and backward compatibility, but I believe most of our users will benefit from rules that align with modern CSS practices. |
I've added this in : csstools/postcss-plugins#1267
If anyone has a suggestion, please let me know on that PR. Aside from the naming I think this new function does help with this issue. div {
&& {}
}
But the AST without To circle back to the original issue: By keeping track of the original selector before it is resolved we can accurately report where the problem is. see :
At first glance, yes :)
Specificity also isn't a good replacement for "max N". I think there will always be a use case for placing limits on complexity, which is what those rules do imho. |
I've added another demo implementation of this second function : It is still a breaking change, but the change is much smaller and all of the original intent of the rule remains. That would not have been true if we used the first function for Doing this I am more confident that we do need both ways of handling nested selectors. |
This issue is older than one month. Please ask before opening a pull request, as it may no longer be relevant. |
This issue is older than one month. Please ask before opening a pull request, as it may no longer be relevant. |
What steps are needed to reproduce the bug?
Because the
postcss-resolve-nested-selector
package returns just a list of strings and loses a raw source position, there are some cases that we cannot calculate Stylelint problems' end positions accurately.What Stylelint configuration is needed to reproduce the bug?
How did you run Stylelint?
CLI with
sylelint *.css
Which version of Stylelint are you using?
HEAD
ef37dc54e5e08786750baea0433114b0c56e965a
What did you expect to happen?
A correct end positions for nested selectors should be reported.
What actually happened?
See the following case:
stylelint/lib/rules/selector-class-pattern/index.js
Lines 92 to 98 in ef37dc5
the
endColumn
value should be10
but actually11
:stylelint/lib/rules/selector-class-pattern/__tests__/index.js
Lines 168 to 175 in ef37dc5
Does the bug relate to non-standard syntax?
Yes, it's related to nested selectors
Proposal to fix the bug
I don't have a good idea now.
selector-class-pattern
selector-max-attribute
selector-max-class
selector-max-combinators
selector-max-id
selector-max-type
selector-max-compound-selectors
selector-max-universal
selector-max-pseudo-class
selector-max-specificity
selector-no-qualifying-type
see also #7482 (comment)
The text was updated successfully, but these errors were encountered: