IanVS / prettier-plugin-sort-imports

An opinionated but flexible prettier plugin to sort import statements
Apache License 2.0
984 stars 25 forks source link

Special word to unmatch imports within the current group instead of having to use negative lookup in regex #164

Closed ADTC closed 6 months ago

ADTC commented 6 months ago

Is your feature request related to a problem? The negative lookup regex pattern is incredibly complex to read and understand. Regexr.com helps but still.

Describe the solution you'd like A special keyword called UNMATCH which inverts the match automatically (by discounting the matches and taking what's left instead).

"importOrder": ["<THIRD_PARTY_MODULES>", "^[.]<UNMATCH>.css$",  ".css$" ]
//              '------ Group 1 ------'  '---- Group 2 -----'  'Group 3'

Explanation: Group 2 would first match ^[.] but then grab what matches .css$ and add it back into the queue of unsorted imports, so that Group 3 can match them later.

Note: Group 2 and Group 3 can be anywhere in the array, not necessarily next to each other, but Group 2 must come first. However, even if you accidentally put Group 3 first there's nothing to worry about because the "unmatch" would simply produce zero results.

The key advantages here are:

Tip: We could potentially have multiple <UNMATCH> to keep excluding multiple subsets and putting them back into the queue of unsorted imports:

"importOrder": [
  "<THIRD_PARTY_MODULES>",
  "^[.]<UNMATCH>.css$<UNMATCH>.json$",
  ".json$",
  ".css$"
]

I don't even know how to begin to write the above in "negative lookup" regex. You'd have to be some kind of Regex God to do it. 😆

Describe alternatives you've considered The alternative is of course the negative lookup:

"importOrder": ["<THIRD_PARTY_MODULES>", "^(?!.*[.]css$)[./].*$", ".css$"]

It's just a convoluted messy regex that must be carefully maintained to match and negative match the same set of files in two different groups.

IanVS commented 6 months ago

Can you give an example of imports that you'd like to sort using this approach?

ADTC commented 6 months ago

@IanVS hey, so this is just trying to make it easier to do "negative lookup" by extracting that out of regex and making it part of the plugin's processing logic instead.

Assume:

import D from '/app/D.css';
import E from '/app/E';
import C from '/app/C.css';
import A from '/app/A';
import B from '/app/B';

A normal sort would sort it as:

import A from '/app/A';
import B from '/app/B';
import C from '/app/C.css';
import D from '/app/D.css';
import E from '/app/E';

A sort using "negative lookup" to filter the CSS:

"importOrder": ["<THIRD_PARTY_MODULES>", "^(?!.*[.]css$)[./].*$", "", ".css$"]

... would sort it as:

import A from '/app/A';
import B from '/app/B';
import E from '/app/E';

import C from '/app/C.css';
import D from '/app/D.css';

My proposal is being able to do the same thing above using a simpler pattern that uses a special word:

"importOrder": ["<THIRD_PARTY_MODULES>", "^[.]<UNMATCH>.css$", "", ".css$"]
//                                       '------------------'

Sorting it as:

import A from '/app/A';
import B from '/app/B';
import E from '/app/E';

import C from '/app/C.css';
import D from '/app/D.css';

The way it works is to split the string on <UNMATCH> (there may be multiple instances):

array = "^[.]<UNMATCH>.css$".split("<UNMATCH>") 
// gives:
array = ["^[.]", ".css$"]

Use array[0] to match the superset first:

import A from '/app/A';
import B from '/app/B';
import C from '/app/C.css';
import D from '/app/D.css';
import E from '/app/E';

Use array[1] ... array[n] to remove subset matches from the superset. What's removed is added back into the queue of imports that are still pending any matches.

import A from '/app/A';
import B from '/app/B';
import E from '/app/E';

Then it's just normal processing, continuing with "":

import A from '/app/A';
import B from '/app/B';
import E from '/app/E';
                       // <--  blank line

And with ".css$":

import A from '/app/A';
import B from '/app/B';
import E from '/app/E';

import C from '/app/C.css';
import D from '/app/D.css';

This makes it easier to write logic similar to "negative lookup" without the convoluted regex. It's much simpler to write "^[.]<UNMATCH>.css$". I can simply say "Match this superset first, but remove (unmatch) these subsets and add them back to the queue for the upcoming regex directives."

fbartho commented 6 months ago

Just my 2¢: I'm uncomfortable making a dialect of regex.

Supporting this in the code (long term) could be complicated/messy/a pain -- indeed the other special words are annoying enough.

Making a dialect of regex to patch a wart in regex feels like out of bounds for this tool, and would break regex sandboxes.

IanVS commented 6 months ago

Thanks for taking the time to open the issue and clearly describe what you're looking for here, @ADTC. I really appreciate hearing ideas and the reasoning behind them. And in fact, I use a similar regex exclusion to group my CSS modules at the bottom, which we document in the readme as the second example.

I'm afraid I agree with @fbartho here. Maintaining a project like this is a delicate balance between adding options and features that people want and keeping things simple and easy to maintain. In this case, what you're asking for is something that can be achieved using the tools we provide currently (regex) and while I agree that regex can be a bit complex and difficult to read, it's at least something that is well documented, flexible, and powerful. We also do our best to give multiple examples for common scenarios for those who may not be as well-versed in regex intricacies.

So, again, thanks for taking the time to open the issue, but we're not willing to make this change to the project. Please do feel free to bring up any other ideas you may have, though!

Obligatory regex comic:

https://xkcd.com/208/