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

feat: add allowLineSeparatedGroups option to sort-keys (fixes #12759) #13573

Closed
wants to merge 13 commits into from
Closed

feat: add allowLineSeparatedGroups option to sort-keys (fixes #12759) #13573

wants to merge 13 commits into from

Conversation

JJoGeon
Copy link

@JJoGeon JJoGeon commented Aug 16, 2020

Prerequisites checklist

What is the purpose of this pull request? (put an "X" next to an item)

[x] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[x] Changes an existing rule (template)
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

What changes did you make? (Give an overview)

Add allowLineSeparatedGroups option to sort-keys rule #12759

Is there anything you'd like reviewers to focus on?

None

@eslint-deprecated eslint-deprecated bot added the triage An ESLint team member will look at this issue soon label Aug 16, 2020
@JJoGeon JJoGeon changed the title Issue 12759 Add allowLineSeparatedGroups option to sort-keys rule #12759 Aug 16, 2020
@anikethsaha anikethsaha changed the title Add allowLineSeparatedGroups option to sort-keys rule #12759 Update: add allowLineSeparatedGroups option to sort-keys (fixes #12759) Aug 16, 2020
@yeonjuan yeonjuan added accepted There is consensus among the team that this change meets the criteria for inclusion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules and removed triage An ESLint team member will look at this issue soon labels Aug 17, 2020
const isLineSeparatedGroups = prevEndLine && checkLineSeparatedGroups(thisStartLine, prevEndLine);

if (thisStartLine !== null) {
stack.prevEndLine = thisStartLine;
Copy link
Member

@yeonjuan yeonjuan Aug 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to treat the multiline property as a group separator because it compares the start line of the previous property and the start line of the current property. So the below case has no lint error.

{
   code: `
     var obj = {
        b: 1, // 1st Group
        c () { // 1st Group 

        },
        a: 3 // 2nd Group ?? No Error
      }
     `,
    options: ["asc", { allowLineSeparatedGroups: true }],
    parserOptions: { ecmaVersion: 6 }
},

Maybe, we should compare the end line of the previous property with the start line of the current property as what "sort-import" does

/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/

import b from 'foo.js';
import d 
   from 'bar.js';
import a from 'baz.js'; // Lint error, cause it's not separated from previous

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry !!!!
I made a mistake while clean the code.

It was stack.prevEndLine = node.loc.end.line; before PR.

Thank you !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry !!!!
I made a mistake while clean the code.

There's no need to be sorry :)
Can we add a test case for ensuring it? (maybe in invalid cases?)

{
   code: `
     var obj = {
        b: 1, 
        c () { 

        },
        a: 3
      }
     `,
    options: ["asc", { allowLineSeparatedGroups: true }],
    parserOptions: { ecmaVersion: 6 }
},

Copy link
Member

@yeonjuan yeonjuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution 👍 I left some comments :)

}
},
additionalProperties: false
}
],

messages: {
allowLineSeparatedGroups: "'{{thisName}}' should be before '{{prevName}}'. To separate groups by line break, apply the allowLineSeparatedGroups option.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's good to give option information to those who don't use this option.
I want to hear other member's opinion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove this message, and just use the existing one. I believe we don't have any error messages that advise users to reconfigure the rule.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the advice !!
I'll remove it !!

Copy link
Member

@yeonjuan yeonjuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JJoGeon Thanks for the change :) I left some comments.

I made a mistake while clean the code.
It was stack.prevEndLine = node.loc.end.line; before PR.

Can we add a test case for ensuring it? (maybe in invalid cases?)

{
   code: `
     var obj = {
        b: 1, 
        c () { 

        },
        a: 3
      }
     `,
    options: ["asc", { allowLineSeparatedGroups: true }],
    parserOptions: { ecmaVersion: 6 },
    errors : [/* ... */]
},

* @returns {number} number of lines between nodes.
* @private
*/
function checkLineSeparatedGroups(thisStartLine, prevEndLine) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change the function name to getLinesBetween or getNumberOfLinesBetween?
IMO check prefixed name seems to represent return boolean or call context.report() but actually it returns the number of lines between two.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I change it to !!Math.max(thisStartLine - prevEndLine - 1, 0); ?
In this function, distances the value of the two is not important but whether they are separated from each other is important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I change it to !!Math.max(thisStartLine - prevEndLine - 1, 0); ?

sure :). it makes sense to me

const thisStartLine = node.loc.start.line;
const isLineSeparatedGroups = prevEndLine && checkLineSeparatedGroups(thisStartLine, prevEndLine);

if (thisStartLine !== null) {
Copy link
Member

@yeonjuan yeonjuan Aug 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 const thisStartLine = node.loc.start.line;

//...

if (thisStartLine !== null) { 

I think it's unneeded condition since thisStartLine seems equals with node.loc.start.line (number).
so thisStartLine !== null seems always true

@JJoGeon
Copy link
Author

JJoGeon commented Aug 18, 2020

Modification is complete!
Please check again !

thank you !

@mdjermanovic
Copy link
Member

Should the option insist on a blank line, as in the original issue, or just check if the properties aren't on consecutive lines as it is implemented in the actual iteration.

For example:

/* eslint sort-keys: ["error", "asc", { "allowLineSeparatedGroups": true} ] */

var obj = {
    b,
    // comment
    a
}

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the actual rule skips computed keys (except for simple ones), but compares non-computed keys around them:

/* eslint sort-keys: ["error", "asc"] */

var obj = {
    b,
    [foo + bar]: 1,
    a // sort-keys: 'a' should be before 'b'
}

online demo

If the option is "allowLineSeparatedGroups": true, what should be checked in the above example?

In the actual iteration, this code would produce an error, while it probably shouldn't:

/* eslint sort-keys: ["error", "asc", { "allowLineSeparatedGroups": true } ] */

var obj = {
  b,
  
  [foo + bar]: 1,
  a // sort-keys: 'a' should be before 'b'
}

Examples of **incorrect** code for the `{allowLineSeparatedGroups: true}` option:

```js
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*//
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/

Examples of **correct** code for the `{allowLineSeparatedGroups: true}` option:

```js
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*//
/*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/

@mdjermanovic mdjermanovic linked an issue Aug 22, 2020 that may be closed by this pull request
@mdjermanovic
Copy link
Member

We recently added allowSeparatedGroups to the sort-imports rule, and that option indeed checks if the imports are on consecutive lines.

However, for sort-keys it might make more sense to check for a blank line, in order to avoid problems and ambiguous behavior in cases like this one.

@yeonjuan, @JJoGeon what do you think of this?

@yeonjuan
Copy link
Member

yeonjuan commented Aug 22, 2020

HI @mdjermanovic :)

However, for sort-keys it might make more sense to check for a blank line, in order to avoid problems and ambiguous behavior in cases like this one.

for double-check, Is this mean a comment line cannot create groups?

/* eslint sort-keys: ["error", "asc", { "allowLineSeparatedGroups": true} ] */

var obj = {
    b,
    [foo + bar]: 1,
    a  // lint-error
}

var obj = {
    b,
    [foo + bar]: 1,
    // comments
    a  // lint-error
}

var obj = {
    b,
    [foo + bar]: 1,
    // comments

    a  // lint-free
}

var obj = {
    b,
    [foo + bar]: 1,

    // comments
    a  // lint-free
}

@mdjermanovic
Copy link
Member

for double-check, Is this mean a comment line cannot create groups?

Yes, I think comment lines shouldn't create groups. That way, I believe it would be easier to explain this option in the docs, and much easier for users to understand how this option exactly works: it simply checks if there are any blank lines between properties. That's just my opinion, it could be wrong.

I also tried the tslint rule mentioned in the original post of the issue, and it seems to be working like that: TSLint playground link

(we don't have to match the behavior, but it looks reasonable to me)

@JJoGeon
Copy link
Author

JJoGeon commented Aug 23, 2020

HI @mdjermanovic @yeonjuan !!

Thank you for the good Opinion!
I think comment lines shouldn't create groups, as mdjermanovic said.

var sampleObject1 = {
  a: 1,
  b: 2,
  // c description
  c: 3
} // one object, one group

var sampleObject2 = {
  a: 1,
  b: 2,
  // another group description
  c: 3
} // one object, two group

We don't know what that comment means. (eslint too.)
So I think it's better to judge by a different group only when there is a separated line.

var sampleObject1 = {
  a: 1,
  b: 2,
  // c or another group description
  c: 3
} // one object, one group

var sampleObject2 = {
  a: 1,
  b: 2,

  // c or another group description
  c: 3
} // one object, two group

@yeonjuan
Copy link
Member

@mdjermanovic

Yes, I think comment lines shouldn't create groups. That way, I believe it would be easier to explain this option in the docs, and much easier for users to understand how this option exactly works:

I agree with it 👍 thanks for the reply

@JJoGeon
Copy link
Author

JJoGeon commented Aug 29, 2020

Hi @yeonjuan, @mdjermanovic ! :)

Finish comment line check logic.

*/
function checkLineSeparatedGroups(thisStartLine, prevEndLine, sourceCodeLines) {
let blockCommentFlag = false;
let commentOffset = 0;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yeonjuan @mdjermanovic

I used the forEach function to check the comment line, but I'm not sure if it's okay.

Is there a better way?

Copy link
Member

@yeonjuan yeonjuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JJoGeon
Thanks for keep working it.
I left comments about false positives and suggestions.

});

return !!Math.max(thisStartLine - prevEndLine - commentOffset - 1, 0);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has some false positives when we use /*, */, // as a value in code.

        {
            code: `
                var obj = {
                    c: "/*",

                    a: "*/", // Error. Expected object keys to be in ascending order. 'a' should be before 'c'.
                }
            `,
            options: ["asc", { allowLineSeparatedGroups: true }]
        }

Is there a better way?

🤔 IMO, we can use SourceCode methods for it. there are some methods for treating comment as a node or token

getTokenAfter(nodeOrToken, {includeComments: true})
getTokenBefore(nodeOrToken, {includeComments: true}); 

Copy link
Author

@JJoGeon JJoGeon Sep 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @yeonjuan !!

in this case,

var sampleObject1 = {
  a: 1,
  b: 2,
  // comment 1
  // comment 2
  c: 3
}

I used getTokensBefore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be a complicated way to find out if there's a blank line.

For example, this is a false positive:

/* eslint sort-keys: ["error", "asc", { allowLineSeparatedGroups: true }] */

var obj = {
  b,
  /*
  */ //

  a
}

comments offset is calculated as 3, but it doesn't take into account if there are lines between comments or not.

Could we maybe simplify this? We could take tokens between properties and then see if there's a blank line between any two successive tokens.

More precisely, if we want to check if there's a blank line between two properties (property1 and property2), than we could check if there's a blank line between any two of the following items:

  • property1
  • comma after property1
  • comment1
  • comment2
  • ....
  • commentN
  • property2

@JJoGeon
Copy link
Author

JJoGeon commented Sep 7, 2020

Hi, @mdjermanovic !! :)

I modified the code to pass all test cases.
review and check please.

thank you !

@mdjermanovic
Copy link
Member

Hi, @mdjermanovic !! :)

I modified the code to pass all test cases.
review and check please.

thank you !

@JJoGeon thanks for the changes and sorry for the delay from my side! I left a comment here

@JJoGeon
Copy link
Author

JJoGeon commented Oct 10, 2020

Hi, @mdjermanovic !! :)
sorry for the delay from my side.

I improved the getCommentOffset function !!

Please check the upgraded getCommentOffset function!

Thank you for your continued good opinion.

@mdjermanovic
Copy link
Member

This wouldn't work well with the comma-first style.

For example, this would be a false negative:

/* eslint sort-keys: ["error", "asc", { "allowLineSeparatedGroups": true }] */

var obj = {
    b: 1
  // comment
  , a: 2
};

Can we maybe check if there's a blank line between two properties by checking for blank lines between consecutive nodes/tokens (incl. comments), instead of calculating comments offset?

[property1, comment, comment, ... , comma, comment, comment, ... , property2]

@nzakas
Copy link
Member

nzakas commented Mar 25, 2021

@JJoGeon are you still working on this?

@JJoGeon
Copy link
Author

JJoGeon commented Mar 25, 2021

@nzakas yes.
Thank you remind me.

For personal reasons, i couldn't work this issue.
My personal problem is solved well now, so I'm going to proceed again soon.

@nzakas
Copy link
Member

nzakas commented Mar 25, 2021

@JJoGeon great, happy to hear it!

@JJoGeon
Copy link
Author

JJoGeon commented May 6, 2021

@mdjermanovic
Thank you for your opinion and feedback.

I solved the comma first style problem you told me.

Can we maybe check if there's a blank line between two properties by checking for blank lines between consecutive nodes/tokens (incl. comments), instead of calculating comments offset?

But this suggestion not solved.
I looked up functions for context.getSourceCode() on this site. (Link)
but no function was found to determine if there was a line break between the two nodes.

Then isn't there only a way to calculate the offset with the line information of the two nodes?

I want to finish with a better code.
Can you give me additional ideas or opinions?

@nzakas
Copy link
Member

nzakas commented May 25, 2021

@mdjermanovic please take a look

@mdjermanovic mdjermanovic changed the title Update: add allowLineSeparatedGroups option to sort-keys (fixes #12759) feat: add allowLineSeparatedGroups option to sort-keys (fixes #12759) Oct 26, 2021
@mdjermanovic
Copy link
Member

@JJoGeon sorry for the delay. I thought I left an answer, but apparently I didn't.

Can we maybe check if there's a blank line between two properties by checking for blank lines between consecutive nodes/tokens (incl. comments), instead of calculating comments offset?

But this suggestion not solved. I looked up functions for context.getSourceCode() on this site. (Link) but no function was found to determine if there was a line break between the two nodes.

There isn't a built-in helper function to determine if there is a blank line between two nodes, but we can easily determine that by checking their lines: If a and b are two consecutive nodes/tokens/comments, then b.loc.start.line - a.loc.end.line > 1 means that there's a blank line between them.

@JJoGeon
Copy link
Author

JJoGeon commented Feb 21, 2022

@mdjermanovic Thank you for your advice.

I'll wrap it up soon and PR again.
Thank you so much for your care.

@alexthehurst
Copy link

@JJoGeon Really looking forward to this improvement if you or anyone is able to push it forward :)

@mvuherer
Copy link

@JJoGeon any updates on this?

@snitin315
Copy link
Contributor

I'll finish this.

@mvuherer
Copy link

I'll finish this.

Thank you very much! You are the hero we don't deserve.

@JJoGeon
Copy link
Author

JJoGeon commented Jun 29, 2022

Thank you for your interest in my PR.
For personal reasons, I didn't care.

I'll try to finish it as soon as possible.

But it's okay for good people to finish before I finish. (@alexthehurst , @mvuherer , @snitin315)
Thank you and sorry.

@snitin315
Copy link
Contributor

Closing in favor of #16138

@snitin315 snitin315 closed this Jul 17, 2022
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Jan 14, 2023
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Jan 14, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add allowLineSeparatedGroups option to sort-keys rule
7 participants