Civet icon indicating copy to clipboard operation
Civet copied to clipboard

Formatter

Open edemaine opened this issue 2 years ago • 8 comments

Civet should either gain a "formatter" mode, or its AST should become rich enough to support a separate formatter tool (probably in the same repo still, because AST changes need to be closely tied). Here are possible specific formatting rules we might aim for:

  • [ ] Consistent indentation, configurable to be a specific number of spaces or tabs. This should be easy, especially once we add Statement AST nodes. (Currently they are arrays [indent, exp, delim] at specific layers of the AST.)
  • [ ] No extra spacing, e.g. x + 2x + 2. Some of this can be done by processing _ nodes.
  • [ ] Collapsing multiple blank lines as in Prettier
  • [ ] Single vs. double vs. back quotes, as in Prettier
  • [ ] Single vs. triple quoted strings
  • [ ] Line length limits / line wrapping. This is easy to measure, but tricky to modify... See Prettier print width.
  • [ ] No unnecessary parentheses, e.g. ((x)+y)x+y. This might require us to actually parse binary expressions into proper ASTs, instead of the current flat representation.
  • [ ] Preferred call style: f(x) vs. f x. Obviously can only use the latter when the parens are unnecessary.
  • [ ] Preferred "no implicit return" style: :void vs. trailing ~~comma~~ semicolon.
  • [ ] Preferred code blocks: braces vs. unbraced.
  • [ ] Preferred if conditions: parenthesized vs. not.
  • [ ] Preferred object style: braced vs. unbraced. Probably preferring braced when using features not supported by unbraced.
  • [ ] Preferred method syntax for objects ({ f() {} } vs. f: ->)
  • [ ] Preferred multiline array style: [...] vs. bullets (once that exists).
  • [ ] Preferred arrow style (-> vs. =>) for functions that don't use this. this checking would need to be recursive for nested => but not nested ->
  • [ ] Preferred function declaration style (function f() vs. f := ->)
  • [ ] Preferred declaration style (const/let vs. :=/.=)
  • [ ] Preferred import style (import ... from vs. ... from vs. from ... import)
  • [ ] Preferred operator names: and vs. &&, === vs. is, !== vs. is not/isnt
  • [ ] Preferred Unicode vs. ASCII operators
  • [ ] Preferred conditional expressions: ?: vs. if
  • [ ] Preferred length vs. #
  • [ ] Preferred JSX style: closing tags or not, remove <>...</> when implicit, remove extra {}s, preferred code block syntax
  • [ ] Preferred Civet compile flags. This might be a way to offer (limited) automatic changes to compiler flags. For example, adding or removing "civet coffeeForLoops" is fairly challenging but plausibly automatable.

Detecting issues should be doable with the AST, possibly once it's fleshed out more. (Ideally we can do this piece by piece.) There are two possible ways to do the emit process:

  1. Keep track of the entire Civet source in the AST somehow, in particular no longer throwing away compiled-away tokens like then. We've discussed this for other purposes too (#40?). Maybe something parallel to token like source... The trouble with this is that it requires a lot of work before we can get anything working.
  2. Use source maps to translate all edits of surviving tokens back to modifications to the Civet code. So e.g. when converting f(x) to f x, we find the location of ( and replace it with , and find the location of ) and remove it. These edits can be applied to the original source code all at once at the end, and then the file can be re-emitted. (Somewhat similar to LSP auto completions.) This is more annoying to work with, and will still require adding some Civet-only tokens that don't normally get emitted, but it's easier to get started with.

By contrast, I don't think Civet should gain a full "lint" mode that tests for bugs. See Prettier on formatting vs. code-quality rules. I think ideally the output of Civet would be compatible with most eslint code-quality rules, so we can use eslint directly for those. But formatting is fundamentally tied to the Civet source, not the generated JS/TS.

This post distills Discord discussion with @bbrk24.

edemaine avatar Feb 08 '24 16:02 edemaine

Preferred "no implicit return" style: :void vs. trailing comma.

Comma? Not semicolon?

A couple other rules I thought of:

  • Preferred arrow style (-> vs =>) for functions that don't use this. this checking would need to be recursive for nested => but not nested ->
  • Preferred function declaration style (function f() vs f := ->)
  • Preferred method syntax for objects ({ f() {} } vs f: ->)

bbrk24 avatar Feb 08 '24 16:02 bbrk24

Are you conceiving of the rule list posted here as a list of options that the user of the formatter can go either way on, or are you seeking a One True Civet Format in which the formatter would embody one specific choice on each option (i.e., be "opinionated" in the sense of Prettier)? I ask because in one project I worked on, we all hated one or two of Prettier's choices enough that we invested a fair amount of time in tooling that ran Prettier and then eslint -fix to undo what we saw as a couple bad choices by the creators of Prettier. That was a pain.

gwhitney avatar Feb 14 '24 19:02 gwhitney

Some of these would have to be configurable (maximum line length and :void vs ; for example). Ideally, it would be possible to turn all of these off individually, just like with ESLint.

bbrk24 avatar Feb 14 '24 20:02 bbrk24

One True Civet Format

Definitely not that. There are so many ways to do things in Civet, and different people have different preferences on what they want to use. The goal is to make these choices fully configurable, to the extent possible (unlike Prettier). We will try to choose reasonable defaults, though. Of course, it's all speculation at the moment. :-)

edemaine avatar Feb 14 '24 21:02 edemaine

Civet should either gain a "formatter" mode, or its AST should become rich enough to support a separate formatter tool

Preferably the latter, and with ESLint support (https://github.com/DanielXMoore/Civet/discussions/852) ideally work with eslint --fix.

See also ESLint Stylistic, and https://antfu.me/posts/why-not-prettier.

danielbayley avatar Mar 03 '24 09:03 danielbayley

I doubt we'd be able to make an AST that's eslint compatible, given how different Civet is from JS. This is the approach taken by CoffeeScript, and in my experience, it's rather fragile (only works on old eslint versions), so I'd hesitate to even try here.

For this reason, I think we need to do formatting ourselves from scratch. We're planning to make it highly configurable, taking best ideas from eslint and Prettier.

On the other hand, we'd like to use existing eslint rules for semantic checks, because that could hopefully be done just on the transformed JS. Hence why this issue is about formatting only. (Of course, it's a blurry line, so maybe one day our formatter does semantic checks too. But this is all hypothetical at the moment.)

edemaine avatar Mar 03 '24 13:03 edemaine

Are there any new developments in this discussion. When I try to use Civet in some projects I migrate a lot of code from the original TS. The lack of a formatting tool makes this process extremely difficult. I spend a lot of time tweaking formatting errors to fit Civet.

bear-ei avatar Jun 13 '24 03:06 bear-ei

No, no progress yet. (If there were, it would be reported here.)

If you're converting from TS, you might consider running Prettier first, and/or check out ts2civet.

edemaine avatar Jun 13 '24 10:06 edemaine