-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Allow leading logical operators #5068
Allow leading logical operators #5068
Conversation
I’m still more of the mindset that using line continuers like Since this is possible with |
Same here. There's an addition I'd love to see here which is indentation sensitive line continuation similar to leading dot property accessor. Consider this: bGreets =
/^hello/i.test myString
and
/\s+world$/i.test myString
or /\s+coffee$/.test myString
or
/^goodbye$/.test myString |
FWIW, I think it makes sense to allow any binary operator, when placed at the beginning or end of a line, to parse as a continuation of the preceding or following line... |
I would also warn that the leading dot chaining notation, e.g. foo()
.then fn
.catch handleError Has been the largest source of bugs in CoffeeScript’s history. The more things that cause line continuation, the more potential trouble that gets caused, e.g.: foo()
.then fn() and
otherFn()
.then yetAnotherFn # Does this chain off of foo() or otherFn()? The problem is that suddenly whitespace is insignificant here. You can indent or not and it doesn’t matter. It’s hard for users to follow what implied line continuation matches with what, and it goes against the general philosophy that indentation should inform these things. Since |
fe0298c
to
2a7d1fe
Compare
@GeoffreyBooth you're right Basically it adds rules for Then, since the desired behavior (sort of like chaining) is to be able to either align or indent the leading-logical lines (ie both of these should be ok):
, instead of making the leading-logical regex be a This has proved to work well for the cases I've come up with (added a bunch of tests involving things like
@jashkenas it'd be great to be able to lead lines with any binary operator as the same readability benefits apply (you can just scan the left side of the expression to get a sense of its "shape"). But I got scared off thinking about potential tricky cases eg continuing @Inve1951 with this branch you couldn't write your example exactly as written - for one thing you couldn't indent the clause after "standalone" |
src/rewriter.coffee
Outdated
@@ -661,6 +662,18 @@ exports.Rewriter = class Rewriter | |||
@detectEnd i + 1, condition, action | |||
return 1 | |||
|
|||
# Convert TERMINATOR followed by && or || into a single LEADING_&& or | |||
# LEADING_|| token to disambiguate grammar. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not LEADING_AND
and LEADING_OR
? We prefer the English word names as a general rule in the codebase. Unless you need separate tokens for LEADING_AND
and LEADING_&&
?
Speaking of which, this should work for and
as well as &&
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth sure, I used LEADING_&&
to mimic the existing &&
token type, but agreed it reads badly, updated
It works equivalently for and
and &&
, as by the time the rewriter/grammar sees them they're the same token type (and both variants are included in INDENT_SUPPRESSOR
). A couple of the tests use &&
/||
src/lexer.coffee
Outdated
@@ -1294,6 +1302,7 @@ POSSIBLY_DIVISION = /// ^ /=?\s /// | |||
HERECOMMENT_ILLEGAL = /\*\// | |||
|
|||
LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | \??:: ) /// | |||
INDENT_SUPPRESSOR = /// ^ \s* (?: and\s+(?!:)\S | or\s+(?!:)\S | && | \|\| ) /// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code could use a nice detailed comment explaining these two variables. Not the regexes themselves necessarily, though that wouldn’t hurt, but what a line continuer is and what INDENT_SUPPRESSOR
is for (in terms of when and why you would suppress indents, of course).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth sure, added comments for LINE_CONTINUER
and INDENT_SUPPRESSOR
- the comments I added explain the token mechanics, but not sure if you think there should also be eg examples of what syntax these regexes allow?
@helixbass correct. The indented block after standalone Speaking of which, that would already be a breaking change. 😕 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth updated per your comments
src/lexer.coffee
Outdated
@@ -1294,6 +1302,7 @@ POSSIBLY_DIVISION = /// ^ /=?\s /// | |||
HERECOMMENT_ILLEGAL = /\*\// | |||
|
|||
LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | \??:: ) /// | |||
INDENT_SUPPRESSOR = /// ^ \s* (?: and\s+(?!:)\S | or\s+(?!:)\S | && | \|\| ) /// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth sure, added comments for LINE_CONTINUER
and INDENT_SUPPRESSOR
- the comments I added explain the token mechanics, but not sure if you think there should also be eg examples of what syntax these regexes allow?
src/rewriter.coffee
Outdated
@@ -661,6 +662,18 @@ exports.Rewriter = class Rewriter | |||
@detectEnd i + 1, condition, action | |||
return 1 | |||
|
|||
# Convert TERMINATOR followed by && or || into a single LEADING_&& or | |||
# LEADING_|| token to disambiguate grammar. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth sure, I used LEADING_&&
to mimic the existing &&
token type, but agreed it reads badly, updated
It works equivalently for and
and &&
, as by the time the rewriter/grammar sees them they're the same token type (and both variants are included in INDENT_SUPPRESSOR
). A couple of the tests use &&
/||
@Inve1951 here's the code you linked to:
To be clear, this PR isn't a "breaking change" with regards to this example nor should it be a breaking change at all If you're saying that your proposed "indented
|
src/rewriter.coffee
Outdated
@@ -649,7 +650,8 @@ exports.Rewriter = class Rewriter | |||
condition = (token, i) -> | |||
[tag] = token | |||
[prevTag] = @tokens[i - 1] | |||
tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS) | |||
[nextTag] = @tokens[i + 1] unless i is @tokens.length - 1 | |||
tag is 'TERMINATOR' and nextTag not in LEADING_LOGICAL or (tag is 'INDENT' and prevTag not in SINGLE_LINERS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffreyBooth made a small update to support leading logical operators inside eg an if
condition (added corresponding tests). For example:
if someCondition
and someOtherCondition
or somethingElse
doSomething()
Basically what was happening was since tagPostfixConditionals()
runs before tagLeadingLogical()
, the IF
was seeing a following TERMINATOR
(which hadn't yet been converted into eg LEADING_AND
) and deciding that it was a POST_IF
Can't just move tagLeadingLogical()
to before tagPostfixConditionals()
, since there's a dependency: tagLeadingLogical()
should run after addImplicitBracesAndParens()
, which should run after tagPostfixConditionals()
So basically detect what will become an eg LEADING_AND
here to avoid tagging as a postfix conditional
This is nice:
But the indentation is confusing. I would have thought the block should be more indented than the predicate, like:
Does the block need to be less indented? The named
Doesn't have the same appeal. There's a case for limiting this to named logical operators, and (a bit weaker case for) only when they're part of a predicate. |
@carlsmith nope the indentation of the continuing lines is up to you (as long as they’re not outdented), eg all of these work: if someCondition
and someOtherCondition
or somethingElse
doSomething() if someCondition
and someOtherCondition
or somethingElse
doSomething() if someCondition
and someOtherCondition
or somethingElse
doSomething() if someCondition
and someOtherCondition
or somethingElse
doSomething() |
@helixbass The third example shouldn’t work. This is invalid today: if someCondition and
someOtherCondition or
somethingElse
doSomething() |
@GeoffreyBooth ya I noticed that behavior with respect to chaining. While obviously it's a poor choice to "indent" the continuations the same as the following indented block, it also feels inconsistent that every other indentation is allowed:
but just that one (where the indentation "happens to match up") fails:
I also wasn't sure exactly what was causing the current behavior (for chaining or your example), I'm guessing it's that
which I haven't fully thought through but seemed questionable So if it's desirable to preserve this behavior (of failing if you try to "indent" continuation lines the same as the following actual indent) then I'll look more closely at how |
I’m not sure what’s desirable. I think the whole idea of indentation being insignificant strikes me as wrong, that we’re going against a core tenet of the language. |
@GeoffreyBooth I understand the unease. I do think that for both chaining and this, it makes sense to be flexible as far as how continuation lines are "indented", ie it wouldn't be a great idea syntax-wise to enforce a rigid "you have to align continuation lines with the initial line" or "you have to indent continuation lines consistently with respect to the initial line and a possible following indented block". So I think the mechanism here ( I believe Python works this way, where once you're continuing a line, indentation is not as strict I think the important questions then are whether there are strange interactions around these features that could be handled better. On a separate branch, I've been trying applying
If you see eg #4533, this has already been kind of an issue. But it's still wildly inconsistent, it only seems to apply the chain to the preceding object-property value if it's a non-initial property of a multi-property object, eg the above is currently equivalent to
is equivalent to
is equivalent to So in thinking about how to handle the equivalent case for leading logical operators (which we haven't defined the behavior of yet) and at least in theory how it should work for chaining I'd propose:
|
Detecting such things with regexes has a downside, if very minor:
Also while supporting more operators here would be – imho – a good thing, although there's no way to support operators that are both unary and binary. |
@vendethiel ah interesting edge case, ya that wouldn't have parsed anyway but it is a slightly unexpected error message. Updated the regex and added a test case To explain more fully for those interested: Initially the But now @vendethiel pointed out an edge case to that edge case: a |
Thanks for the clarification. |
@GeoffreyBooth implemented leading-logical-operator-continues-object-property-value behavior per previous comment and merged |
This isn't really required for generating an AST, is it? It's just for making the Prettier output a little prettier? If so, this feels like something we should add in a second phase, once we have regular AST output working. This change could have other side effects that we should study before merging it in. |
57c967e
to
99a5b81
Compare
Hi, for all, thanks for effort in make better shouldOmitBracesButNotIndent =
(parent.type is 'ExpressionStatement' \
and grandparent.type is 'ClassBody') \
or (parent.type is 'ArrayExpression' and parent.elements.length is 1) \
or isObjectPropertyValue path, {stackOffset, last: yes} |
Of course it's not necessary but neither are any of the other implicit line continuations; They are nice to have tho.
Why do you dislike it? I personally crave leading logicals and hope they get added into master. @voxsoftware |
@Inve1951 yes, my example is using explicit, I prefer that syntax and avoid errors can inherited from have a non explicit version |
Re-opened as #5279 |
@GeoffreyBooth I’ve been thinking about Prettier and formatting of long logical conditions (including your expressed opinion that you don’t like breaking lines after
and
/or
)One of the few things I’ve consistently wished that I could do in Coffeescript was to lead subsequent lines with the logical operator rather than have to break after the logical operator at the end of the line - I find it much easier to digest the “shape” of the logical expression if I don’t have to look to the ends of the lines to see how they relate logically
And in looking at code I’m formatting with the Prettier plugin, what I would like to see as “pretty” formatting would be when breaking long logical expressions, to lead lines with the logical operator. Eg instead of this which Prettier is currently generating (I have it breaking lines at logical operators similarly to how Prettier for JS does it - if you really don’t want to break long lines at logical operators, that’s something that we could easily add an option for):
I’d like to see it formatted like this:
To me that’s way easier to follow the logical flow
And I’d never realized it’s a simple change to allow this long-desired (by me) feature, just making the logical operators be
LINE_CONTINUER
s. It wouldn’t break existing code, since no valid lines could currently begin withand
(space)/or
(space)/&&
/||
PS the code snippet above is from the Prettier plugin printer source, which I’ve been writing in JS but have hacked together a very primitive version of
es2coffee
(repo, built as a Babel plugin that feeds its transformed AST to the Prettier plugin) which can transform it to Coffeescript pretty well - the decision to make our AST types as similar as possible to Babel AST types has proven beneficial