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

Re-enable CSS-in-JS tests #6556

Closed
wants to merge 7 commits into from
Closed

Re-enable CSS-in-JS tests #6556

wants to merge 7 commits into from

Conversation

hudochenkov
Copy link
Member

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

None.

Is there anything in the PR that needs further explanation?

I've developed new PostCSS syntax to parse template literals CSS-in-JS like styled-components: postcss-styled-syntax.

I thought we could re-enable CSS-in-JS tests. I believe they were disabled because @stylelint/postcss-css-in-js is based on PostCSS 7, and because of lack of maintenance there were not much trust in it.


I also propose to suggest postcss-styled-syntax in error message instead of deprecated @stylelint/postcss-css-in-js. Let me know if I need to revert it.

@changeset-bot
Copy link

changeset-bot bot commented Jan 5, 2023

⚠️ No Changeset found

Latest commit: 1641cb7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@hudochenkov
Copy link
Member Author

I guess, this PR is no-go until stylelint has dropped Node.js 12 and 14, since postcss-styled-syntax has syntax not supported in these versions.

I also just discovered that similar problem in property-no-unknown is also being fixed here: #6553

@ybiquitous
Copy link
Member

@hudochenkov Thanks for creating the pull request. I recommend using the v15 branch as a base because the branch has dropped Node.js 12 support.

One question. Does the postcss-styled-syntax package have the same features and purposes as postcss-styled-components? /cc @43081j

@hudochenkov hudochenkov changed the base branch from main to v15 January 5, 2023 01:20
@hudochenkov
Copy link
Member Author

One question. Does the postcss-styled-syntax package have the same features and purposes as postcss-styled-components?

OMG. When did it happen?! 🤦

On a quick inspection they serve exactly the same purpose — to parse styled-components.

But feature-wise postcss-styled-syntax has more features, in terms that it parses everything that styled-components syntax could be, while postcss-styled-components has many limitations regarding interpolations.

From a tech perspective they also have different approach:

postcss-styled-syntax has a more standard approach following PostCSS How to Write Custom Syntax. It has parser that uses tokenizer to parse source code.

postcss-styled-components uses different approach. It "hacks" source code so then it could be processed by default PostCSS parser.

I think postcss-styled-syntax is better in terms of capabilities and more reliable approach.

@43081j
Copy link
Contributor

43081j commented Jan 5, 2023

they do indeed do the same.

i published postcss-lit way back when these syntax changes happened and things were deprecated, and around the same time started on a styled-components iteration of that. more recently, i extracted most of the core of that to 43081j/postcss-js-core which can be used to build other css-in-js syntaxes.

as part of that i spun up a styled-components/emotion package built on top of it.

to clear something up too, fwiw its not a "hack", the expression interpolation you're talking about.

initially, we had an iteration of the core which had its own tokeniser and soon realised we were basically going to be playing catchup forever - keeping the custom tokeniser in sync with the better maintained, better tested postcss tokenizer. on top of that we had quite a few test cases failing which were more difficult to solve and maintain through a custom tokenizer.

ultimately though, nobody is tied to implementation nor should they be. if it was shown that a custom tokenizer could solve the interpolation problem in a better way than falling back to the postcss one, im sure thats the way we would go.

i understand you've spent a bunch of time on this too. it has happened to all of us before, writing something in parallel to someone else writing roughly the same thing for the same problem.

the packages i've published (js-core, lit, styled-components) aren't going anywhere since we already rely on them heavily and so do many other people. but im not against there being two options to people. or even better, if you could use what you have here and what you've learnt to contribute to postcss-js-core - so you can help out all syntaxes rather than just the styled-components one.

nobody wants to part with the package they've built, but you're probably one of the only people around right now who has tackled this other than me, so it would actually be super nice to collaborate.

@hudochenkov
Copy link
Member Author

hudochenkov commented Jan 5, 2023

i published postcss-lit way back when these syntax changes happened and things were deprecated, and around the same time started on a styled-components iteration of that. more recently, i extracted most of the core of that to 43081j/postcss-js-core which can be used to build other css-in-js syntaxes.

Fun fact, I added Document node to PostCSS specifically to built syntaxes for CSS-in-JS :)

to clear something up too, fwiw its not a "hack", the expression interpolation you're talking about.

Sorry, I didn't mean to offend. I put it quotes, because I can't judge if that solution really a hack or not. CSS-in-JS is a wild west of CSS syntaxes, because it could have a lot of crazy stuff.

initially, we had an iteration of the core which had its own tokeniser and soon realised we were basically going to be playing catchup forever - keeping the custom tokeniser in sync with the better maintained, better tested postcss tokenizer. on top of that we had quite a few test cases failing which were more difficult to solve and maintain through a custom tokenizer.

I found the opposite true. Initial approach with comments that you and me came up with didn't work out, so instead of playing a guess game I decided to rely on PostCSS tokenizer and parser. I marked changed bits (trick I found in SCSS tokenizer) so if there updates to default parser, it would be easily carried over without the fear of breaking added logic.

or even better, if you could use what you have here and what you've learnt to contribute to postcss-js-core - so you can help out all syntaxes rather than just the styled-components one.

nobody wants to part with the package they've built, but you're probably one of the only people around right now who has tackled this other than me, so it would actually be super nice to collaborate.

That's the tricky part. Because approach is very different it's difficult to contribute without re-doing everything again. I don't see much sense for me personally spend all this time again. Also, I don't feel comfortable with approach taken in postcss-js-core.


I tried postcss-styled-components and noticed that we do parse differently and produce different AST.

In comparison:

let Component = styled.div`color: red; ${hello}`;

postcss-styled-components creates root with declaration and comment nodes. Which could be an issue for stylelint rules, because rules might analyze comment node, which doesn't even exist in original code. Possibility of false positives.

postcss-styled-components creates root with only declaration node. Interpolation goes into root.raws.after.

let Component = styled.div`${hello} color: red;`;

postcss-styled-components fails to parse.

postcss-styled-components creates root with declaration node. Interpolation goes into decl.raws.before.

Maybe my solution with putting interpolations into raws is not perfect, but for me it is much safer than creating nodes, which don't exists in the source.

postcss-styled-syntax parses much more than postcss-styled-components

# Conflicts:
#	lib/utils/isStandardSyntaxDeclaration.js
@43081j
Copy link
Contributor

43081j commented Jan 5, 2023

Fun fact, I added Document node to PostCSS specifically to built syntaxes for CSS-in-JS :)

You shouldn't mistake it for a competition, i was just pointing out that i started work on it a while ago, completely unrelated to anyone else having done so.

... so if there updates to default parser, it would be easily carried over without the fear of breaking added logic

That's the tricky part. Because approach is very different it's difficult to contribute without re-doing everything again. I don't see much sense for me personally spend all this time again. Also, I don't feel comfortable with approach taken in postcss-js-core.

these and a few other things you said are all about implementation detail, which im really not tied to. its possible (and likely) i will revisit using a custom tokeniser at some point to see if there's a middle ground or a third solution that has best of both worlds. it really doesn't matter much to me how it is implemented as long as it is clean and does the job as expected.

if you don't feel comfortable contributing to another repo or you feel like your work will be a sunk cost, thats fine, there's absolutely nothing wrong with having multiple solutions available in the world.

obviously, i'd prefer that collaboration happens since nobody knows everything alone, but there's no bad feelings from it not happening.

postcss-styled-components creates root with declaration and comment nodes. Which could be an issue for stylelint rules, because rules might analyze comment node, which doesn't even exist in original code. Possibility of false positives.

stylelint in fact already had logic for accounting for root-level declarations and such, thanks to the old css-in-js implementations. i fixed some of this very recently to account for new syntaxes (since it was previously hard coded to only support the old syntaxes).

postcss-styled-components fails to parse

by choice. the strategy used for dealing with interpolation is documented as not supporting these kinds of unusual (syntactically) positions. that'd likely change if we ever changed the strategy but isn't any loss right now thanks to it being an uncommon enough use case.


i feel like this has gone down a bit of a rabbit hole. you've spent most of your reply trying to compare the two implementations but i don't think that is necessary. if someone came along tomorrow and offered a better implementation into js-core, i would happily work with them to accept it.

im well aware of limitations in the approach i took and chose to accept them for now due to them being mostly edge cases. at some point, someone (not necessarily me) will likely update it with a much better approach which will solve those edge cases. it is an ongoing project.

i once converted parse5 to typescript (months of work) as another person did unknowingly in parallel. in the end, i threw mine away (but used it as reference) and we collaborate to the point where we both now maintain parse5. its not an easy conflict to solve. unfortunately in this case, all 3 of my packages are already used heavily in quite a few projects so its unlikely ill throw them away this time. but i still stand by that i'd love to collaborate and, if a tokeniser is a better way to go, get that into js-core (from scratch, not from your code or mine, together).

@ybiquitous
Copy link
Member

@hudochenkov @43081j Thank you so much for all the work you have done for CSS-in-JS. I have no objection that we have different packages for the same purpose (although it might be better if they worked together).

@hudochenkov I'm not sure why CI doesn't work, but I locally confirm that tests on Node.js 14 fail. Array.prototype.at() is not supported on Node.js 14. Can you fix it?

TypeError: foundNodes.filter(...).at is not a function

node_modules/postcss-styled-syntax/lib/parse.js:51:81

let lastComponent = foundNodes.filter((node) => isComponent(node, foundNodes)).at(-1);

@43081j
Copy link
Contributor

43081j commented Jan 6, 2023

@ybiquitous purely from the point of view of the stylelint repo then, this PR would need reworking.

the tests previously tested against css-in-js, a catch-all syntax for all JS based syntaxes. you can't replace it in all of those tests (and the source actually) with a new syntax which is not catch-all and actually only supports a subset.

for example, stating that *.js is entirely handled by styled-syntax makes no sense, and can't be true.

the tests would need to be updated, just like the file extension inference stuff such that there are multiple syntaxes rather than one monolith (in line with the whole point of moving away from css-in-js).

otherwise you're essentially re-enabling the tests for a single syntax, ignoring the many others which exist.

@hudochenkov hudochenkov added the pr: blocked is blocked by another issue or pr label Jan 6, 2023
@hudochenkov
Copy link
Member Author

hudochenkov commented Jan 6, 2023

Array.prototype.at() is not supported on Node.js 14. Can you fix it?

@ybiquitous I don't think I'm going to fix it, because syntax Node.js 14 will be out of live in less than 4 month. And LTS already two versions higher. I guess this PR can't be merged until Stylelint drop Node.js 14. Changed my mind. Given that there could be influx of reports for new CSS-in-JS syntaxes, like #6553 or hudochenkov/postcss-styled-syntax#1. It would be beneficial of having CSS-in-JS setup in tests already.

the tests previously tested against css-in-js, a catch-all syntax for all JS based syntaxes. you can't replace it in all of those tests (and the source actually) with a new syntax which is not catch-all and actually only supports a subset.

Currently all maintained CSS-in-JS PostCSS syntaxes are for template literals. We don't have syntaxes for object notation, or for JSX style attributes.

I think it still make sense to tests for existing syntaxes.

just like the file extension inference stuff

I think we need to create a new discussion issue for this. As now single file extension could mean a lot of different syntaxes. I reverted the change.

@hudochenkov hudochenkov removed the pr: blocked is blocked by another issue or pr label Jan 6, 2023
@43081j
Copy link
Contributor

43081j commented Jan 6, 2023

I think it still make sense to tests for existing syntaxes.

you could go ahead and add all the other existing syntaxes in that case.

I think we need to create a new discussion issue for this. As now single file extension could mean a lot of different syntaxes. I reverted the change.

agreed.

if these tests are re-enabled, the following should be true IMO:

  • the file inference should not be tied to your syntax or any other (we're trying to move away from that monolithic behaviour by having these new individual syntaxes)
  • the tests should be against individual syntaxes, not tied into a specific one like you have here

if it was updated to that state, it'd make sense to me (see below). im sure you're well aware of that.

a quick glance at the actual tests themselves also raises the question of if they should exist at all. IMHO each individual syntax should be responsible for its own tests from now on. it doesn't make much sense to put a burden on the stylelint maintainers to test external modules (and an increasing amount of them as time goes on). it isn't as simple as before when there was one module to test.

i'd vote for throwing the whole lot away and leaving it to syntax maintainers to test their own code.

@hudochenkov
Copy link
Member Author

hudochenkov commented Jan 7, 2023

you could go ahead and add all the other existing syntaxes in that case.

If there is a real need for a specific syntax to be tested, then, of course, we can look into it.

This PR goes from no tests for any CSS-in-JS syntax, to at least some tests.

the tests should be against individual syntaxes, not tied into a specific one like you have here

I don't understand what does it mean. Could you elaborate, please?

a quick glance at the actual tests themselves also raises the question of if they should exist at all. IMHO each individual syntax should be responsible for its own tests from now on. it doesn't make much sense to put a burden on the stylelint maintainers to test external modules (and an increasing amount of them as time goes on). it isn't as simple as before when there was one module to test.

i'd vote for throwing the whole lot away and leaving it to syntax maintainers to test their own code.

I would disagree. I understand this statement as PostCSS should have test to all PostCSS plugins including Stylelint. Stylelint shouldn't have tests for any any non-default PostCSS parser. Similar for Babel, it should have tests for all plugins. Maybe I misunderstood.

PostCSS syntaxes can't have tests for all possible tools that might use them. Stylelint is a bunch of PostCSS plugins (each rule is a PostCSS plugin). It takes PostCSS AST from whatever parser is used (default or custom) and make analysis of AST. If for some syntaxes it makes incorrect analysis, then Stylelint needs to be adjusted, if, of course, parse makes valid AST. So if logic is inside Stylelint repository, then tests also should be there.

@43081j
Copy link
Contributor

43081j commented Jan 7, 2023

I don't understand what does it mean. Could you elaborate, please?

the tests you are updating were intended to test the css-in-js implementation (as in there was one at the time, and only one).

this isn't true anymore and never will be. there are now multiple distinct syntaxes, not a catch-all css-in-js syntax.

that means most or all of the tests you're re-enabling are no longer accurate, in that they belong in a test suite specific to your particular syntax, not in a test suite intended for all syntax.

because of that, they're basically unit tests and integration tests for your own code, but living in the stylelint repo.

that means we should probably do either of the following:

  • rework the tests in stylelint such that there's a test category/suite per known syntax (huge burden on the stylelint maintainers)
  • remove the tests and rely on syntax creators to test their own code (moves the burden to syntax creators but less effort)

if all stylelint maintainers are happy with owning those test suites, we should do the former (which isn't far off what you have here, just reorganised a bit).

you have to remember stylelint now should be able to operate against an AST without any knowledge of what created it or how. basically a separation of concerns. that should really mean its own tests are against any valid postcss syntax tree, rather than hard coding knowledge of individual syntaxes.

@hudochenkov
Copy link
Member Author

the tests you are updating were intended to test the css-in-js implementation (as in there was one at the time, and only one).

They are to test Stylelint, that it does work correctly with specific custom syntax. Same as we have for scss or less. Both scss and less produce AST not expected in default PostCSS AST, so there is code in Stylelint ensuring Stylelint works correctly.

that means most or all of the tests you're re-enabling are no longer accurate, in that they belong in a test suite specific to your particular syntax, not in a test suite intended for all syntax.

All tests that have customSyntax are for particular syntax package it was always the case since Stylelint removed built-in parsers.

Even when there was one catch-all syntax for CSS-in-JS. Tests were only for one of many flavors of CSS-in-JS from that package. Not for every possible flavor.

Nothing changes. It's just that one particular flavor could be used from a separate package.

because of that, they're basically unit tests and integration tests for your own code, but living in the stylelint repo.

These are tests to test Stylelint ability to operate on the AST. It's not the test of AST, but Stylelint's logic working with the AST.

you have to remember stylelint now should be able to operate against an AST without any knowledge of what created it or how. basically a separation of concerns. that should really mean its own tests are against any valid postcss syntax tree, rather than hard coding knowledge of individual syntaxes.

Agree. And Stylelint tries to do that. But in reality it's not possible. You will find in Stylelint many examples where specific syntax AST is treated in a special way (SCSS, Less). PostCSS AST is not very strict specifically to allow many of CSS-flavors to be processed. There is general structure and direction, but not strict structure.

@43081j
Copy link
Contributor

43081j commented Jan 7, 2023

They are to test Stylelint, that it does work correctly with specific custom syntax. Same as we have for scss or less. Both scss and less produce AST not expected in default PostCSS AST, so there is code in Stylelint ensuring Stylelint works correctly

this is because really there are 3 syntaxes: sass, less (actually more or less the same as css), css.

the css-in-js syntaxes are more like filetype syntaxes for purposes of extracting one of the above "source" syntaxes.

whatever the css-in-js ones produce probably should be (and likely is) one of those 3 ASTs stylelint is aware of (all of which are really just postcss ASTs with more node types).

These are tests to test Stylelint ability to operate on the AST. It's not the test of AST, but Stylelint's logic working with the AST

they are near enough integration tests for your syntax, testing that stylelint will work correctly with your syntax.

that's why either the ones you have here should be in a test suite specific to your syntax, or should be in your repo rather than this one (that choice is upto stylelint maintainers as a group i suppose).

what you have isn't far off that, it isn't a huge change.

Agree. And Stylelint tries to do that. But in reality it's not possible. You will find in Stylelint many examples where specific syntax AST is treated in a special way (SCSS, Less).

mostly, if not entirely, sass/less. there shouldn't really be any css-in-js syntax specific logic in stylelint anymore (if there ever was). and one day, sass/less will likely also go away as they fall out of use/popularity.

there's also a question now of whether stylelint should even be testing such things anymore. if a css-in-js syntax is thoroughly tested to produce a valid AST for stylelint, it probably doesn't make sense anymore to have individual per-rule tests (duplicates) per syntax, since every css-in-js syntax should produce the same AST (i.e. stylelint would only need one test).

@ybiquitous
Copy link
Member

@hudochenkov @43081j Thanks a lot for providing great opinions and consideration.

I think we should not rely on specific non-standard syntaxes as much as possible, even not only for CSS-in-JS syntaxes but also for SCSS or Less. The more external modules we depend on, the more difficult we maintain the codebase. It's important for the Stylelint core to focus only on standard CSS syntaxes for healthy maintenance, balancing with usability.

I believe that it's unnecessary that each rule tests use non-standard syntaxes and it's necessary to remove them gradually. But some of isStandardSyntax* utils need custom syntaxes for tests. These utils can avoid bugs around custom syntaxes.

So, I suggest removing currently skipped tests for a CSS-in-JS syntax in rule tests, and using (or installing) a syntax as needed for isStandardSyntax* utils. For example, PR such as #6553 or #6565 may suit that.

What do you think?

@43081j
Copy link
Contributor

43081j commented Jan 9, 2023

i would agree. its more difficult for people like @hudochenkov and i (syntax maintainers) but if we don't do it this way, it means stylelint will continuously grow its test suite around codebases/behaviours it doesn't maintain.

the tests we have for those css-in-js specific behaviours (as in #6565 for example) would still exist as part of stylelint, not to test the custom syntax in question but to test the generic support stylelint has in such a function (pretty much what you said about the isStandardSyntax* utils).

although in those cases its still a little awkward as the easiest way to test such a codepath is by pulling in a specific syntax that hits it. it'd be nice if there was a way to test those branches "generically" so we know all syntaxes which conform to the same tree will work with it. not sure how we'd do that though

it'd actually be nice to reduce the amount of syntax-specific support in stylelint as time goes on, too. in the hope that newer syntaxes we've yet to see will conform to standard CSS AST.

@hudochenkov
Copy link
Member Author

I think we should document such decision in docs, so there is a place to look up decision from now onward.

@ybiquitous
Copy link
Member

I think we should document such decision in docs, so there is a place to look up decision from now onward.

Agreed. I've opened PR #6572 to do that.

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

Successfully merging this pull request may close these issues.

None yet

3 participants