4 ms·
Thanks for writing this, it was a great read! I maintain a source beautifier too [1], and it's not as nice as I would like. One of the issues I run into is tha
by ridiculous_fish 11y ago
Thanks for writing this, it was a great read!
I maintain a source beautifier too [1], and it's not as nice as I would like. One of the issues I run into is that the correct indent on a broken line is context dependent. For example:
while (someReallyReallyReallyReallyLongFunction() &&
anotherLongFunction()) {
loopBody();
}
is a nicer indenting than:
while (someReallyReallyReallyReallyLongFunction() &&
anotherLongFunction()) {
loopBody();
}
In the first case, the two conditions are aligned which makes the code clearer. Does dartfmt handle this? If not, do you have ideas on how it might?
Also, how does it handle invalid input? I may want to reindent my code before it's correct.
Also, did you explore constraint solvers instead of a graph traversal? It seems like they would be a natural fit.
[1]: fish_indent, https://github.com/fish-shell/fish-shell/blob/master/src/fish_indent.cpp https://github.com/fish-shell/fish-shell/blob/master/src/fis...
- sklogic 11y agoFor this sort of things I'm using an intermediate language (guess it's similar to what OP is talking about) with stack semantics: there are instructions like "PUSHINDENT", 'POPINDENT" and "INCINDENT" - with PUSHINDENT inserted after '(' in ifs, function calls, etc. - ensuring that if line breaks before the POP instruction, indentation would reflect the context. So, the biggest problem is in assigning weights to the break candidate instructions is this stream. This can only be done by a lot of experimenting, I could not find any formal method or a passable heuristic.
- vog 11y agoIn that specific case, isn't it better style to write it differently anyway? Either: conditionWithReadableName: function() { return someReallyReallyReallyReallyLongFunction() && anotherLongFunction(); } ... while (conditionWithReadableName()) { loopBody(); } Or if you don't want to make up a good name for your conditional: while (true) { if (!someReallyReallyReallyReallyLongFunction()) break; if (!anotherLongFunction()) break; loopBody(); } Or, in a more rules-based fashion: conditionWithReadableName: function() { if (!someReallyReallyReallyReallyLongFunction()) return false; if (!anotherLongFunction()) return false; return true; } ... while (conditionWithReadableName()) { loopBody(); } I find it troubling to make up "good" indentation rules when the code to indent isn't well-written in the first place. Multi-line conditionals are an anti-pattern in itself (no matter if they appear in "while", "if" or "for").
- ikurei 11y agoI agree with you in that multi-line conditionals should go, and no beautifier is going to make it up for bad code, but it's still great that they try. You can't always devote as much time as you'd like to make the code perfect, and that is when a beautifier comes the most handy. Also, having a good beautifier when you have to deal with other not-so-perfectionist programmer's code is great.
- vog 11y ago> having a good beautifier when you have to deal with other [...] programmer's code is great. Good point! I didn't think of that.
- philh 11y agoI'm not sure I like any of those as much as the original. When you introduce a new name, I don't know if you're going to use it anywhere else. (But having a name is good, so I'm on the fence.) When all your while conditions are inside the block, I have to actually look at it to figure out that it's really just a standard while loop.
- qznc 11y ago> do you have ideas on how it might? On each opening parens put the current line length (=indentation) on a stack. On closing parens pop from the stack. On newline use the top of the stack as indentation.
- deleted 11y ago[deleted]
- detrino 11y agoHave you used clang-format at all? It's the first formatting tool that I have come across that I have no problem letting format 100% of my code.
- munificent 11y ago> Does dartfmt handle this? If not, do you have ideas on how it might? Yes, it does! Correct indentation based for nested expressions is a vital feature for helping the reader understand the structure of the code. The basic idea is fairly simple. Any place a line break may appear, you mark it with a number representing how deeply nested in the expression it is. So in code like: function(outer(inner(first, second), third)); You would get chunks whose expression nesting level is like: function( 1 outer( 2 inner( 3 first, 3 second), 2 third)); When you line break, you ensure that its nesting level gets assigned a correct indentation level. Deeper nesting means deeper indentation: function( outer( inner( first, second), third)); There are a bunch of interesting edge cases, though. Some times a nesting level doesn't happen on a line break, so it doesn't need to get an indentation level associated with it: function(outer(inner( first, second), third)); Here, levels 1 and 2 don't appear in line breaks, so we give the first indentation level to nesting level 3. There are stranger cases where you may assign a deeper level before a shallower one like: function(outer(inner( first, second), third)); Shaking out all of the bugs requires a lot of work and a really big test suite. > Also, how does it handle invalid input? I may want to reindent my code before it's correct. If the code doesn't parse, it just exits with an error. Being able to run it on incomplete input would be useful, but I decided it was out of scope. > Also, did you explore constraint solvers instead of a graph traversal? Good question! I have a little experience with them. I think the line between the two is sort of fuzzy. I didn't approach it directly like a constraint solving problem, but it does have some features in common.
- ridiculous_fish 11y agoI'm not sure you understood my example. Let's say we have: while (first && second) { third } We might assign nesting levels like this: while( 1 first && 2 second) 2 third 2 which could lead to this line breaking: while (first && second) { third } This is bad because 'second' and 'third' are visually aligned, and so one might think that 'second' is in the loop body instead of the condition. We want to indent 'second' more than 'third', even though it has the same nesting level: while (first && second) { third } This is what I meant by "context dependent:" here we have two chunks at the same indentation level but that want different numbers of spaces. Does dartfmt attempt to handle this?