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

add outer padding to rule padded-blocks and cover existing jscs functionality #7356

Closed
quinn opened this issue Oct 13, 2016 · 120 comments · Fixed by #8099
Closed

add outer padding to rule padded-blocks and cover existing jscs functionality #7356

quinn opened this issue Oct 13, 2016 · 120 comments · Fixed by #8099
Assignees
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

Comments

@quinn
Copy link

quinn commented Oct 13, 2016

Update: please refer to this pull request for the current status of this issue:

#8099

1st proposal: updating padded-blocks

{
  inner: "always" | "never" | "before" | "after"
  outer: "always" | "never" | "before" | "after"
  prefer: "inner" | "outer"
  outerExceptions: "inCallExpressions" | "inNewExpressions" | "inArrayExpressions" | "inProperties" | "singleLine"
  innerExceptions: "blocks" | "classes" | "switches"
}

The above doesn't yet include additional options for backwards compatibility.

related issues:

related / converging rules:

2nd proposal

no modifications to padded-blocks. new rule that has exceptions to allow padded-blocks to work, if there is a conflict an error is thrown (is this even a thing?)

"padding-around-blocks": true | "before" | "after", {
  "exceptions": "inCallExpressions" | "inNewExpressions" | 
                "inArrayExpressions" | "inProperties" | "singleLine" | 
                "afterOpeningBlock" | "beforeEndingBlock"
}

"afterOpeningBlock" and "beforeEndingBlock" specify whether the rule should apply to a block placed at the beginning or end of the inside of another block. If they are both specified, then padded-blocks should work as expected. If they are not specified, then the new rule could potentially result in an error depending how padded-blocks is configured.

@eslintbot eslintbot added the triage An ESLint team member will look at this issue soon label Oct 13, 2016
@kaicataldo kaicataldo added this to the JSCS Compatibility milestone Oct 13, 2016
@kaicataldo kaicataldo added enhancement This change enhances an existing feature of ESLint rule Relates to ESLint's core rules evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion and removed triage An ESLint team member will look at this issue soon labels Oct 13, 2016
@kaicataldo
Copy link
Member

kaicataldo commented Oct 13, 2016

Closing the other issues so we can consolidate the discussion in one issue. Thanks @quinn!

@eslint/eslint-team Can we get some opinions from team members? @TheSavior and @quinn have done a great job of pushing forward with this on their own but I think we need the team involved.

@quinn
Copy link
Author

quinn commented Oct 17, 2016

@kaicataldo Anything I can do to help? I would be happy to try an implementation but there are already multiple PRs and I don't want to redo anyone else's work. I could try to merge the existing PRs or something. If there's anything I can do let me know. thanks :)

@kaicataldo Maybe the discussion from this issue should be moved here? #7145 In my example above, I only included the "singleLine" exception for "outer" padding, not inner, since it was not part of the current "padded-blocks" / jscs implementation. "singleLine" I think it a little bit odd in that list because it's slightly different than the other options (all of the other options have to do with the location of the block rather than the structure of the block). idk hopefully i am not making things more complicated :p

@kaicataldo
Copy link
Member

@quinn Sorry for not responding sooner - I think it's fine to leave that as a separate discussion so we can focus on padding outside blocks. Hopefully we'll get some discussion on here soon - thanks for your patience.

@not-an-aardvark
Copy link
Member

Thanks for the proposal. This looks good, but I have a few questions/comments:

  1. What are the proposed defaults for these options?

  2. I think the outerExceptions and innerExceptions should have better names; inCallExpressions, switches, etc. seem inconsistent to me. Maybe it would be better if it was just a node type: outerExceptions: ["CallExpression", "SwitchStatement"]

  3. Would it be possible to have two separate objects for inner and outer? For example:

    {"inner": {"padding": "always", "exceptions": ["NewExpression"]}, "outer": {"padding": "never"}}

@quinn
Copy link
Author

quinn commented Nov 4, 2016

@not-an-aardvark

  1. i * think * the defaults would simply be { "inner": "always" } since that would match (i think) what happens when you use padded-blocks without options currently.
  2. the outerExceptions were just pulled from the original jscs rule, but you're right they aren't consistent with the rest of it. as far as i've seen it seems like eslint tends to go with short option names without camel case.
  3. yeah, that seems cleaner to me. maybe if you aren't specifying any exceptions you can still set the padding type directly: { "inner": "never", "outer": "always" }. Are there any existing eslint rules that have nested options like that?

@nzakas
Copy link
Member

nzakas commented Nov 4, 2016

Maybe it's just me, but this seems like too much to combine into padded-blocks. Is there a way we can keep padded-blocks focused on the inner extra lines and create a new rule to deal without outer extra lines? Or is there some relationship here that I'm missing?

@not-an-aardvark
Copy link
Member

@nzakas The issue is that the two rules would conflict with each other:

/* eslint padded-blocks: [error, "always"] */
/* eslint padding-outside-blocks: [error, "never"] */

{
  function foo() {
  } // should there be an empty line below? padded-blocks says yes, the other rule says no

}

@quinn
Copy link
Author

quinn commented Nov 4, 2016

@nzakas @not-an-aardvark yeah. that conflict is one of the causes of the lengthy discussion. keeping it as a separate rule was discussed, but I think someone indicated it would be more weird to have a rule behave differently based on the.. rules of another rule. thats the reason for the prefer option above. I think it could still be simple to use, there are just these extra configurations available..

@caesarsol
Copy link

What about changing concept totally and split the rule in:

  • padding inside blocks
  • padding between blocks (of the same scope/indent)

In this way there would be no conflict, as far as I can see.
And the rule would be simplified.

@caesarsol
Copy link

To further elaborate, reading the JSCS rule in question makes it clear thet it's a quite different subject, only regarding padding after blocks.

My proposal does not cover the block-statement boundaries but only the block-block boundaries.
Could be an entirely new rule, or they could be joined.

I think a new small focused rule is better than a large number of parameters for an already existing rule.

What do you think?

@quinn
Copy link
Author

quinn commented Nov 5, 2016

@caesarsol I think the only reason all of this is being considered as a single rule is because of the situations where the 2 rules could conflict and there isn't a precedent in eslint already for what to do when 2 rules say to a different thing for a given statement (afaik) does that help or did I miss the point of what you were saying ??

@caesarsol
Copy link

I'm proposing about avoiding the conflict! Maybe with a new concept of block boundaries.
(You may have missed my first post just before)

@quinn
Copy link
Author

quinn commented Nov 5, 2016

@caesorsol ok, the issue isn't between blocks, it's when a block has inside of it a block as the first or last thing.. does that make sense? Also, I personally want padding around my blocks even if the things on either side are not blocks (except in the case of a wrapping block..)

@caesarsol
Copy link

(Sorry, it was difficult for me to explain)
Wouldn't the conflict issue be resolved if we reason about:

  • boundary between two blocks
  • boundary between a block and a piece of code

For each of those a rule about enforcing the newline.

This would certainly leave the inner block padding alone, making space for the rule without conflicts.

Any downside you can think of?

@kaicataldo
Copy link
Member

Many of our rules that enforce blank newlines before and after nodes defer to padded-blocks and ignore cases when the checked node is at the beginning and end of a block, respectively.

If we keep this as two separate rules - one that enforces padding inside blocks and one that enforces padding outside blocks - could we not do the same thing? JSCS has both these rules - how did they handle cases where they conflict?

@caesarsol
Copy link

Looking quickly at the 4 JSCS rules' source, seems there isn't any rules-conflict detection...
How is that possible? Does it allow an always-erroring config?

@TheSavior
Copy link
Contributor

I believe JSCS relied on users to not create conflicting configurations.

@quinn
Copy link
Author

quinn commented Nov 7, 2016

1   class Component {
2       init () { // ^^^^ problem ??
3           some.code()
4       }
5   
6       render () {
7           return ''
8       }
9   } // ^^^^ problem?

lets say that the rules you haven't defined enforce padding before and after a block, and also enforce no padding at the beginning and end of the inside of a block. the "conflict" here happens between lines 1 and 2, and between lines 8 and 9. I don't think there is a need to address redundant padding between blocks since I think this can be intuitively collapsed much like margin collapsing in CSS. Sorry if this is redundant, just want to confirm that we're all on the same page with this and these things imo can be hard to discuss w/o example code..

@TheSavior
Copy link
Contributor

TheSavior commented Nov 7, 2016

@quinn, something we ran into with the JSCS rule are things like functions that are wrapped with bind, call, or apply. There should be a way to enforce a blank line after blocks wrapped in those, unless they come at the end of a parent block in which case there shouldn't be an extra blank line.

Does that make sense? Take a look at the jscs rule docs if not

@mysticatea
Copy link
Member

mysticatea commented Feb 14, 2017

I'm sorry for late.
I know I'm a late arrival, but may I describe my proposal here?

https://gist.github.com/mysticatea/1b74f00c4b88aa45767226ce63b4a524#file-newline-between-statements-md

This is a abstract rule for linebreaks between statements, just it complements padded-blocks:

{
    // padded-blocks
    foo()
    // newline-between-statements
    bar()
    // newline-between-statements
    {
        // padded-blocks
        foo()
        // newline-between-statements
        bar()
        // padded-blocks
    }
    // newline-between-statements
    baz()
    // padded-blocks
}
// eol-last

The point of this rule is that this rule focuses "between 2 statements" rather than "before/after a statement".

This rule has flexible configuration, so it can replace newline-after-var and newline-before-return, it's more controllable of conflicting. At the same time, that implementation would be simple.

One of cons is that users may be relatively hard to configure it.

Thought?

@aboyton
Copy link

aboyton commented Feb 14, 2017

I've probably missed something but I can't see @kaicataldo how you could later extend this rule by having

newline-after-block: "error", "always" or {
    FunctionDeclaration: "always",
    IfStatement: "never"
}

without conflicting with rules like newline-before-return. This is why I suggested we instead make the rule only enforce the presence never the absence of newlines. That or am I missing something and are conflicts not something we have to worry about?

Unless I'm missing something (and I probably am) the only way to not have conflicts is to have a config more like

newline-after-block: [FunctionDeclaration, IfStatement],
newline-before-block: [ReturnStatement]

@alberto
Copy link
Member

alberto commented Feb 15, 2017

@mysticatea As you said, this rule will be hard to configure. The fact that configuring the rule would require knowledge of the different AST node types is a smell of that.

To me, it's an example of an advanced rule that could also exist as custom rule for people wanting total control over line padding, but I would also like to hear more opinions.

@kaicataldo
Copy link
Member

kaicataldo commented Feb 15, 2017

Had a response written out on my phone and lost it - sorry!

@mysticatea I have the same concern as @alberto about the configuration being complex and requiring knowledge of the AST. That adds a lot of overhead to configuring a pretty universal rule, and I don't think it's necessarily fair to force people to have to learn this stuff to use the rule. I'm also curious about some node type-specific quirks, like not requiring a newline before a ReturnStatement if it's the only node in a block.

I do like the concept of the proposal, though. Is there any way we could make the configuration simpler/easier to understand for someone who knows JS but might not have prior experience with JS ASTs? I think I could support this proposal if we can find a way to do so, as it does give more control.

@aboyton The reason we can extend this is that we discussed deferring to other rules (padded-blocks, newline-before-return, and lines-around-comment, and newline-after-var) when those situations are encountered. There's precedence for this in our other rules. @mysticatea's proposal would combine all these into one rule so we wouldn't need to do that.

@mysticatea
Copy link
Member

I agree that it would be relatively hard to configure.

But I think it's hard as well if somebody configures multiple rules for newline carefully as avoiding conflictions. "Rules for newline after statement" and "rules for newline before statement" are conflictable essentially. We would need to set priority but the priority would be unclear and not controllable. Or we would need to add options but it might get the combinatorial explosion.

On the other hand, newline-between-statements would not conflict any rules (If we deprecate some existing rules) and it has "use the last matched setting" rule.

Also, newline-between-statements uses only statement types, it's fewer than expression node types. I think people can image what is it (except Export***Declaration).

  • ExpressionStatement
  • BlockStatement
  • EmptyStatement
  • DebuggerStatement
  • WithStatement
  • ReturnStatement
  • LabeledStatement
  • BreakStatement
  • ContinueStatement
  • IfStatement
  • SwitchStatement
  • ThrowStatement
  • TryStatement
  • WhileStatement
  • DoWhileStatement
  • ForStatement
  • ForInStatement
  • ForOfStatement
  • FunctionDeclaration
  • ClassDeclaration
  • VariableDeclaration
  • ImportDeclaration
  • ExportNamedDeclaration
  • ExportDefaultDeclaration
  • ExportAllDeclaration

In addition, we have some rules which use AST node types already. E.g. no-multi-spaces.


I'm also curious about some node type-specific quirks, like not requiring a newline before a ReturnStatement if it's the only node in a block.

Because newline-between-statements checks newline between 2 statements, it does nothing if only one statement exists in a block. padded-blocks should check newline around the statement.

if (a) {
    // padded-blocks
    return
    // padded-blocks
}
// newline-between-statements
if (b) {
    // padded-blocks
    foo()
    // newline-between-statements
    return
    // padded-blocks
}

However, yes, my proposal has 2 special types: } and directive.
The } is the type of statements that the last token is }, to express newline-after-block.
The directive is the type of directive prologue, to express newline-around-directive.
Other special cases might exist...

@kaicataldo
Copy link
Member

kaicataldo commented Feb 15, 2017

The strategy we've been discussing to avoid conflicts is to defer to rules that enforce newline-before-* behavior (i.e. don't warn if the node being checked is before a ReturnStatement). That being said, I agree that this isn't ideal either - it then requires that you use those other rules in conjuction with the one being proposed here. Also, if we go that route we'll also have to add exceptions if we ever add more newline-before-* or newline-around-* rules.

Also, newline-between-statements uses only statement types, it's fewer than expression node types. I think people can image what is it (except Export***Declaration).

I agree that most of these names are self-explanatory. Maybe it wouldn't be too much overhead?

I really do like the concept of changing this from looking at nodes individually to looking at statements in pairs.

@quinn
Copy link
Author

quinn commented Feb 15, 2017

@kaicataldo @mysticatea the newline-between-statements seems similar to #7356 (comment) made by @jensbodal. basically you group similar things. A return is a "thing", thus it gets a newline "around" itself (no need to have newline-before-return). right? Whatever solution is chosen, the existing newline-(before|after|around)-* rules should probably be dumped

@kaicataldo
Copy link
Member

kaicataldo commented Feb 15, 2017

@quinn

@mysticatea's proposal changes the original concept of enforcing padding lines around nodes to checking between nodes - so you define relationships, with priority being set by the order. If we go this route, we would be able to deprecate newline-after-var, lines-around-directive, and newline-before-return, leaving exceptions for lines-around-comment and padded-blocks.

The proposal I outlined above would require that the existing rules be kept.

@eslint/eslint-team Do we have any other opinions on this? My goal here is implementation and JSCS compatibility - let's try to decide on something! For reference, the two proposals on the table right now are #7356 (comment) and #7356 (comment).

I'm personally leaning toward @mysticatea's suggestion right now - my biggest concern is configuration complexity.

@quinn
Copy link
Author

quinn commented Feb 15, 2017

oh, neat. I like @mysticatea 's idea a lot !

@alberto
Copy link
Member

alberto commented Feb 16, 2017

@mysticatea can you show how you would configure the exceptions from the original JSCS rule?

@alberto
Copy link
Member

alberto commented Feb 16, 2017

TSC Summary: requirePaddingNewLineAfterBlocks is a JSCS rule and one that is often requested.

There are currently two proposals:

  • newline-after-blocks that is a more or less straightforward port of the original rule, also including classes and switches. It would avoid conflicting with other padding rules by ignoring certain situations (e.g when next statement is a return statement).

  • newline-between-statements which is a more general rule that deals with padding between statements. It would replace newline-before-return, newline-after-var and lines-around-directive.

TSC Question: Which rule should we accept, if any?

@alberto alberto added the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Feb 16, 2017
@mysticatea
Copy link
Member

@alberto

Ah, JSCS requirePaddingNewLinesAfterBlocks rule is checking newline about blocks inside of object literals, array literals, and call expressions as well. But my proposal newline-between-statements does not check those, so it's as same as "allExcept": ["inCallExpressions", "inNewExpressions", "inArrayExpressions", "inProperties"] configuration.

I think newline rules of each place should check it, probably behind options, if it's needed. Because even if we make an independent rule, we need ignoreBlocks-like options to avoid confliction between the rule and newline rules of each place.

For "singleLine" exception, I can add block and multilineBlock instead of } into STATEMENT_TYPE of newline-between-statements.


@kaicataldo

Maybe we can use the keyword which is at the top of statements instead of AST node types.

  • debugger → DebuggerStatement
  • with → WithStatement
  • return → ReturnStatement
  • break → BreakStatement
  • continue → ContinueStatement
  • if → IfStatement
  • switch → SwitchStatement
  • throw → ThrowStatement
  • try → TryStatement
  • while → WhileStatement
  • do → DoWhileStatement
  • for → ForStatement, ForInStatement, ForOfStatement
  • function → FunctionDeclaration
  • class → ClassDeclaration
  • var, let, const → VariableDeclaration
  • import → ImportDeclaration
  • export → ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration
  • ??? → ExpressionStatement, BlockStatement, EmptyStatement, LabeledStatement

@platinumazure
Copy link
Member

platinumazure commented Feb 16, 2017

@mysticatea I really like the direction your proposal is going. Could we support both cases (node types and keywords)? And what about groupings (e.g., maybe all Export*Declaration can have no blank lines in between but there must be a blank line surrounding the group)?

@mysticatea
Copy link
Member

@platinumazure

Could we support both cases (node types and keywords)?

Sure.

And what about groupings (e.g., maybe all Export*Declaration can have no blank lines in between but there must be a blank line surrounding the group)?

It can realize with:

{
    "newline-between-statements": ["error", [
        ["blankline", "export", "*"],  // need blank lines after exports.
        ["always", "export", "export"] // no need blank lines between exports. This is prior to `["blankline", "export", "*"]`
    ]]
}

@alberto
Copy link
Member

alberto commented Feb 16, 2017

TSC resolution: We have decided to go with @mysticatea proposal.

@mysticatea are you championing this?

@alberto alberto added accepted There is consensus among the team that this change meets the criteria for inclusion and removed evaluating The team will evaluate this issue to decide whether it meets the criteria for inclusion needs bikeshedding Minor details about this change need to be discussed tsc agenda This issue will be discussed by ESLint's TSC at the next meeting labels Feb 16, 2017
@mysticatea mysticatea self-assigned this Feb 16, 2017
@alberto
Copy link
Member

alberto commented Feb 16, 2017

Also thanks to everyone who contributed in the discussion or with a PR and helped us flesh this out! special hat tip to @quinn @TheSavior @LinusU @ekmartin

We know it has been painfully slow, and we apologize for that

@TheSavior
Copy link
Contributor

While this configuration enables lots of flexibility, it's also going to put a higher priority on solving the problem about extending configurations: #7957

Since all of the rules are being consolidated, if, for example, airbnb implements this, nobody will be able to add more things without having to duplicate their config for this rule and thus not be able to stay in sync.

not-an-aardvark pushed a commit that referenced this issue May 15, 2017
* New: newline-between-statements (fixes #7356)

* Chore: refactor with AST selectors

* Fix: add more tests.

- The kind `directive`  matches to only directive prologue.
- The kind `expression` does not match to directive prologue.
- The kinds `block-like` and `multiline-block-like` match to do-while
statements.
- The kinds `block-like` and `multiline-block-like` do not match to
classes.

* Update: change options' form.

- It was the array of arrays, but it becomes the array of objects.

* Update: rename

* Update: deprecate 3 rules as replaced by this

* use astUtils.STATEMENT_LIST_PARENTS instead of local RegExp.

* avoid catastrophic backtracking

* improve IIFE check for unary expressions

* fix a bug about semicolon-less style and empty statements

* unwrap the array of options

* use constants for error messages.

* update document

* fix plural

* fix blankline → blankLine
@eslint-deprecated eslint-deprecated bot locked and limited conversation to collaborators Feb 6, 2018
@eslint-deprecated eslint-deprecated bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Feb 6, 2018
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 a pull request may close this issue.