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

Fix inaccurate end positions when postcss-resolve-nested-selector is used in selector-* #6234

Closed
12 tasks done
ybiquitous opened this issue Jul 28, 2022 · 27 comments
Closed
12 tasks done
Labels
status: wip is being worked on by someone

Comments

@ybiquitous
Copy link
Member

ybiquitous commented Jul 28, 2022

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?

{
  "rules": {
    "selector-class-pattern": ["^[A-Z]+$", { "resolveNestedSelectors": true }]
  }
}

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:

// TODO: `selector` may be resolved. So, getting its raw value may be pretty hard.
// It means `endIndex` may be inaccurate (though non-standard selectors).
//
// For example, given ".abc { &_x {} }".
// Then, an expected raw `selector` is "&_x",
// but, an actual `selector` is ".abc_x".
const endIndex = index + selector.length;

the endColumn value should be 10 but actually 11:

{
code: '.A { &__B { }}',
message: messages.expected('.A__B', '^[A-Z]+$'),
line: 1,
column: 6,
endLine: 1,
endColumn: 11,
},

.A { &__B { }}
/*   ↑   ↑
     ↑   ↑
     6   10
*/

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.


  • no-descending-specificity
  • no-duplicate-selectors
  • 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

@ybiquitous ybiquitous added status: needs investigation triage needs further investigation type: bug labels Jul 28, 2022
@jeddy3 jeddy3 removed the type: bug label Jul 29, 2022
@jeddy3 jeddy3 changed the title postcss-resolve-nested-selector losing raw source Fix inaccurate end positions whenpostcss-resolve-nested-selector is used in selector-* Jul 29, 2022
@jeddy3 jeddy3 added status: ready to implement is ready to be worked on by someone type: bug and removed status: needs investigation triage needs further investigation labels Jul 29, 2022
@jeddy3
Copy link
Member

jeddy3 commented Jul 29, 2022

I don't have a good idea now.

Nor do I. I've labelled the issue as ready to implement in case any has any ideas.

@mattxwang
Copy link
Member

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?

@ybiquitous ybiquitous changed the title Fix inaccurate end positions whenpostcss-resolve-nested-selector is used in selector-* Fix inaccurate end positions when postcss-resolve-nested-selector is used in selector-* Jul 30, 2022
@ybiquitous
Copy link
Member Author

Porting the code of postcss-resolve-nested-selector may be a solution. If doing so, it may be helpful for plugin authors to expose the ported code as a utility. See below:

- [postcss-resolve-nested-selector](https://github.com/davidtheclark/postcss-resolve-nested-selector): given a (nested) selector in a PostCSS AST, return an array of resolved selectors.

@ybiquitous
Copy link
Member Author

ybiquitous commented Jul 30, 2022

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. &_foo) as before, postcss-nested may be a choice.

EDIT: it’s needed to investigate whether such plugins would fit to Stylelint, though.

@ybiquitous
Copy link
Member Author

I'm revisiting this issue and investigating postcss-nesting and postcss-nested. In short, using these packages instead of postcss-resolve-nested-selector seems difficult because they're a PostCSS plugin and are not a utility to resolve nested selectors. Parsing selectors by the packages should cause performance overhead.

@romainmenke Do you have any good idea?

@romainmenke
Copy link
Member

Would it be fine if the selectors are desugared purely with :is()?

.foo, bar {
  &:hover {}
}

gives : :is(.foo, bar):hover

I don't mind adding a package for this under csstools (similar to the specificity package).

@ybiquitous
Copy link
Member Author

Ah, no, we don't want such a :is() optimization here. Instead of postcss-resolve-nested-selector, we want a utility to resolve nested selectors and follow the CSS Nesting spec.

postcss-resolve-nested-selector still works in most cases, but it becomes inappropriate for Stylelint since it supports the identifier concatenation like Sass (e.g. &_foo) which is forbidden in the CSS spec.

I'll dig into the problem more. Thanks for the quick response!

@romainmenke
Copy link
Member

Do you have examples rules of where using :is() would give different outcomes?

Because resolving with :is() is the closest to the CSS nesting spec you can get in a static analysis tool.

@ybiquitous
Copy link
Member Author

For example, in the selector-class-pattern code:

for (const nestedSelector of resolveNestedSelector(selector, ruleNode)) {
if (!isStandardSyntaxSelector(nestedSelector)) {
continue;
}
parseSelector(nestedSelector, result, ruleNode, (s) => checkSelector(s, ruleNode));
}

This rule resolves a nested selector as follows, so the :is() transformation is unnecessary here to check if the given pattern matches the resolve selector:

.a {
  .b {}
}
/* ↓ resolved */
.a .b {}

@romainmenke
Copy link
Member

It is indeed unnecessary in simple cases but would it cause false positives/negatives if :is() was used?


This can be trivially transformed without :is() and the package I have in mind would also skip :is() here :

.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 {}
  }
}

@jeddy3
Copy link
Member

jeddy3 commented Jan 21, 2024

Because resolving with :is() is the closest to the CSS nesting spec you can get in a static analysis tool.

That's my understanding, too:
https://drafts.csswg.org/css-nesting/#nest-selector

This change will have a greater impact on rules like no-descending-specificity and selector-max-specificity, but it's a necessary change to bring Stylelint inline with the specifications.

I don't mind adding a package for this under csstools (similar to the specificity package).

That'd be fantastic!

@romainmenke
Copy link
Member

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:

  • postcss-resolve-nested-selector doesn't follow the CSS nesting specification so it no longer aligns with the current focus of Stylelint
  • postcss-resolve-nested-selector takes strings as input and output so the source locations are lost

I think the first issue (spec compliance) is covered with the new package I linked above.
But the second issue is more complicated.

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. .a and &:hover becomes : .a:hover
And .a has the same source locations as &.

It is much harder to keep .a pointing to its rule and the source location in that rule.
That information is lost.

Ideally all selector AST nodes would already be offset by their position in the overall CSS.
Then it wouldn't be needed to correct anything.

@ybiquitous
Copy link
Member Author

@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. selector-class-pattern.

@ybiquitous
Copy link
Member Author

I'd like to use @csstools/selector-resolve-nested soon instead of postcss-resolve-nested-selector, but we may not be able to do that since there are compatibility issues in postcss-resolve-nested-selector (see #7482).

Copy link
Contributor

github-actions bot commented May 9, 2024

This issue is older than one month. Please ask before opening a pull request, as it may no longer be relevant.

@github-actions github-actions bot added status: ask to implement ask before implementing as may no longer be relevant and removed status: ready to implement is ready to be worked on by someone labels May 9, 2024
@Mouvedia Mouvedia added status: ready to implement is ready to be worked on by someone and removed status: ask to implement ask before implementing as may no longer be relevant labels May 9, 2024
@romainmenke
Copy link
Member

romainmenke commented Jun 1, 2024

Can we ignore selector-class-pattern for the purpose of this issue?

see: #7482

When looking into replacing postcss-resolve-nested-selector I noticed that it only uses this package to apply sass-like selector concatenation (e.g. &__b becomes .a__b)

Checking of standards compliant CSS doesn't need postcss-resolve-nested-selector or flattenNestedSelectorsForRule for this rule to work as expected.

So I would rather remove postcss-resolve-nested-selector entirely in a next major without an intermediate migration to flattenNestedSelectorsForRule.

Thoughts?

@ybiquitous
Copy link
Member Author

I agree. selector-class-pattern is a particular case and should be addressed in #7482, not here. 👍🏼

@romainmenke
Copy link
Member

Only one more after: #7867 🎉

@ybiquitous ybiquitous added status: wip is being worked on by someone and removed status: ready to implement is ready to be worked on by someone help wanted is likely non-trival and help is wanted labels Aug 21, 2024
@ybiquitous
Copy link
Member Author

@romainmenke Thanks a lot for your continuous work. Just to make sure, can we close this issue once PR #7937 is merged?

@romainmenke
Copy link
Member

Yes, this was the last one 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: wip is being worked on by someone
Development

No branches or pull requests

5 participants