-
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
Proposal: Ecosystem: CoffeeScript in Prettier #4984
Comments
From @GeoffreyBooth on 2017-11-25 07:15 By the way, either of these tasks are also investments in the extensibility of CoffeeScript in the future. The js2coffee project was possible in the first place because JavaScript has several excellent parsers that produce detailed ASTs. If js2coffee’s CoffeeScript code generation part could be replaced with a better code generator, js2coffee would be able to support the latest CoffeeScript features (and be more adaptable to future improvements). Coffeelint would be capable of greater things if it had a better AST to work with. And on and on. CoffeeScriptRedux got so many things right, it’s a shame it never got completed. One of its insights was that code generation should be its own module that took an AST as input. (It supported both cscodegen to generate CoffeeScript, or escodegen to generate JavaScript.) This is also how Babel works, with babel-generator taking an AST and producing JavaScript. This modularity is one of the keys to Babel’s success, and the growth of the ecosystem around it. If the CoffeeScript compiler produced an AST compatible with Babel, the CoffeeScript compiler could outsource the JavaScript code generation to that and therefore jettison |
From @lydell on 2017-11-25 10:41 Well summarized! I forgot to mention that every One way to go about this would be to start writing a |
From @vendethiel on 2017-12-29 02:21 CSR did get a lot right, yes. We probably need Concrete Syntax Tree, but our lexer/rewriter code is... hairy to say the least. |
From @GeoffreyBooth on 2017-12-29 02:45 What’s a “concrete” syntax tree? |
From @vendethiel on 2017-12-29 11:21 https://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees/ A CST is very similar to an AST, but it keeps more parse information around (and doesn't remove seemingly useless nodes). |
From @GeoffreyBooth on 2017-12-29 15:01 What I was thinking we would do is generate an AST add similar to Babel's as possible. Then we can crib code from the main JavaScript code path in Prettier to parse it. It'll also be useful for working with other tools to have as “standard” of an AST as possible. We would add extra properties to the AST nodes to preserve the info we would need to generate CoffeeScript again from the tree. Are you interested in helping tackle this? |
From @vendethiel on 2017-12-29 15:43 The project then becomes pretty much "rewrite the compiler" ... or "upgrade CSR to support all the things CS2 now supports", which is an insane amount of work. |
From @GeoffreyBooth on 2017-12-29 16:13 That's more ambitious than I had in mind. I was thinking of just doing what is proposed at the top of this thread: create a way for the compiler to output an AST as JSON, similar to how it currently outputs nodes data as text via |
From @vendethiel on 2017-12-29 16:22 We're discard too much info imho. Just for implicit objects etc. |
From @GeoffreyBooth on 2017-12-29 16:43 Right, that's what would need to be added to the nodes as extra info. Stuff like whether a boolean was written as |
From @GeoffreyBooth on 2018-01-02 06:56 Okay, I’ve taken the first few baby steps in getting CoffeeScript to produce an AST. Check out this branch, then create a coffee -e "console.log require('util').inspect require('./lib/coffeescript').compile(require('fs').readFileSync('./test.coffee').toString(), nodes: yes), {colors: yes, depth: 10}" You should see pretty-printed JSON like this: { type: 'Block',
loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 10 } },
expressions:
[ { type: 'Assign',
loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 10 } },
variable:
{ type: 'IdentifierLiteral',
loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 5 } },
value: 'answer' },
value:
{ type: 'NumberLiteral',
loc: { start: { line: 0, column: 9 }, end: { line: 0, column: 10 } },
value: '42' } } ] } This is the same output as the CLI’s
This was all done by adding just one method on the base node class, and for many nodes this is all the data we need. For more complicated nodes, the next step is to override this method to add additional serializable properties to flesh out the objects for those nodes; and in some of those cases, we’ll have to reach back to the lexer to make sure that currently-discarded data is forwarded along into |
From @rattrayalex on 2018-01-06 20:59 This is exciting. I may be able to help with some of the prettier parts. Could we write a test suite to ensure the ast being generated this way is always accurate? |
From @GeoffreyBooth on 2018-01-06 22:18 @rattrayalex I would love the help. I’ve started a branch in a local copy of the Prettier repo, I’ll clean it up and post it soon and give you access. We can certainly write tests around this. We could add a |
From @rattrayalex on 2018-01-06 22:30 Babylon has a nice ast snapshot suite, might be worth checking out. But may not be needed for this. loc information should probably be tested somewhere, but I’m not actually sure it’s needed for prettier anyway. Can’t recall for sure. |
From @GeoffreyBooth on 2018-01-08 07:39 @rattrayalex I’ve created a repo of my Prettier fork and branch here, and added you to it. If anyone else would like to contribute, please let me know and I would be happy to add you. In my fork, the default branch is There was some major reorganization of the Prettier codebase in the last few months since I initially started my fork, so some of my work needed to be thrown out; but I had only just barely gotten started anyway. Look in the |
From @GeoffreyBooth on 2018-01-08 07:49 Once you’ve checked out the Prettier CoffeeScript branch and run ./bin/prettier.js --parser coffeescript test.coffee Currently I’m just printing the AST, but you have to start somewhere 😄. This is using my |
From @rattrayalex on 2018-01-29 03:29 Coming back to this, I might be able to help a bit in March but probably not earlier than that 😕 any progress in the last few weeks? |
From @GeoffreyBooth on 2018-01-29 04:30 No, but I hope to have some time before March. I’ll push commits into both branches. Feel free to work in those repos, either in the same branches or other branches we can merge into them. |
@GeoffreyBooth I just came across this issue. I came at it from a slightly different angle but have been working on something similar - I've used Prettier and
So have been hacking on the backend to get it to target the Not sure exactly how what I've been working on relates to feeding a Coffeescript AST into Prettier (for formatting as Coffeescript code) - the basic approach I've been following is to have a
I think there'd always be the need (if we target a JS AST for outputting JS and want to be able to eg And then as far as Once I'm done with that pass I'll open a preliminary PR to raise specifics about what I've implemented, what realistically would still need to be done to move forward, etc. Hopefully by the end of this week (or if you're curious you can check out my |
Hi @helixbass, this is great to hear. If you could provide links to your branch, and your fork of Prettier, that would be great to have. @zdenko and @rattrayalex have expressed interest in this as well. I think the near-term goal is just to get the CoffeeScript compiler to take CoffeeScript source code input and generate a detailed AST as output. This is what I’ve started in my Refactoring the compiler such that we don’t need Once we get a good AST out of the compiler, the world opens up for ecosystem possibilities: not just Prettier, but easy integrations with linters, a new JS2Coffee, etc. The great ASTs produced by Babylon and Acorn and others are a big reason why the JavaScript ecosystem has bloomed. |
@GeoffreyBooth sure here is my Coffeescript branch which can output JS using this fork of
The changes to
Once I get through a pass of all Was still planning on opening a preliminary PR of my branch soon to allow for some discussion of specifics, but would you rather try and have some of that discussion here? Eg one thing that comes to mind: it seems like having our Coffeescript AST types stick as close as possible to the corresponding JS AST types is sure to be a win in various regards, but one big question from what I've done so far is which JS AST it makes sense to target. I've been targeting the |
Hey @helixbass, your branch has a tremendous amount of work in it! I wish we had discussed before you got so far in, not necessarily because you did any wasted work, but so that we could potentially collaborate (and avoid duplicative work). My I don’t think Prettier and ESLint should be integrated into the CoffeeScript compiler itself. I think we should be following the general design of my As for the tree formats, you’ve done more research into it, please advise what you think we should target. We’re going to have a unique tree format anyway because we have node types that don’t exist in JavaScript, like |
@GeoffreyBooth ya I was taking the approach of wanting to have a realistic sense of how viable the Prettier/ESLint integrations were before sharing (and taking it as an excuse to do a pretty deep solo dive into the compiler backend, which I wasn't that familiar with) - just finished an initial pass through the standard ESLint rules so am pretty much at the point I was wanting to get to
This is a nice way to start thinking about how to generically traverse the nodes to generate AST objects. I'm not very well-versed in tree traversal patterns (either in the compiler codebase or in general) but I can kind of picture what we're going for as far as being able to generate both a Coffeescript AST (that can be used to reconstruct/lint Coffeescript source) and a transformed JS AST, where ideally the two AST generations share code and are as simple of a transformation as possible from our nodes to the corresponding AST objects So then one interesting direction would be to try and start aligning our node class and child naming with the target JS AST's naming figuring that that would greatly reduce the amount of custom My biggest question as far as thinking about how to work on the Coffeescript AST generation is what to use as a reference point for making progress/having things "working" - with what I've been working on, there were very clear-cut things to measure against: getting existing tests to pass when using Prettier to generate JS, checking if ESLint rules run, etc. I'm not super anxious to dive deeply enough into Prettier to be able to start piecing together a Coffeescript formatting backend side-by-side with getting the Coffeescript compiler to feed it more node types to format, but if Prettier formatting of Coffeescript source is the primary/only target for the generated Coffeescript AST, then I guess that's what makes most sense What other targets might there be? A linter? Were you envisioning that the Coffeescript compiler would itself know how to reserialize a Coffeescript AST into Coffeescript source? I was picturing that as Prettier's domain, that's sort of the beauty of what I've been working on is that Prettier doesn't care almost at all about your original formatting, so if you can feed it a correctly structured AST your job is pretty much done. To run a linter against Coffeescript AST, you'd clearly need pretty complete source location/text info (as well as a structurally correct AST)
I figured that the ESLint integration code would belong in a separate package/repo a la |
The compiler already generates JavaScript source code, and there are plenty of tools like Babylon that can take JS source as input and produce a JS AST as output. The only reason for the CoffeeScript compiler to generate a JS AST would be if we wanted to drop support for our compiler generating JS source as output, in order to have Prettier or Such an effort would be a lot of work, as you’ve already started to see. All the weird edge cases that make the current And even if we went to all that trouble, we still need a CoffeeScript AST to do the things we want to do. You can use a JS AST to use ESLint, sure, but that will only lint the things that make sense in JavaScript. Look at some of the rules in Coffeelint, like What I mean by a “CoffeeScript AST” would be basically what you’ve generated, JSON that follows the style of the Babel or ESTree ASTs, but with a handful of CoffeeScript-specific node types included. So there would be a great amount of overlap, and maybe it would be possible to additionally have our compiler generate a fully Babel-compatible JS AST (like with extra methods to convert CS-only node types like |
@GeoffreyBooth I agree with a lot of that. I'd just encourage you to be open to adjusting your ideas about what the path forward might look like as a result of the work I've already done. There'd be a lot to do to get from where my branch is to production, but I've already handled the "weird edge cases" (a lot of which was able to effectively just be copied from the corresponding
I think there are huge differences between retargeting the backend and swapping out the frontend as far as compatibility etc
So I understand the attitude that "all we get by generating JS AST is the ability to generate JS, which we can already do", but the reality is that I'm leaning on Prettier for generating JS (more nicely than we'll ever be able to, because that's what it does) and leaning on ESLint (yes, formatting-related rules belong in a Coffee linter, but you turn those off anyway when you're using Prettier, and a lot of the big structural rules work - And I'd argue that in the same way that being able to generate Coffeescript AST just seems like a good idea in terms of ecosystem possibilities, the same holds true for being able to generate JS AST (yes, you can just re-parse generated JS into an AST, but the ESLint integration only works because we generate it directly, with original location info). And targeting it has forced me to clean up a lot of the backend messiness, where we eg create So just bear with me if I keep wanting to pursue it from where I've already gotten with it, and I'll for sure be trying to figure out how to move forward with making the Coffeescript AST a reality |
That’s a very good point. That’s a good reason to have the compiler potentially output an ESLint-compatible “JS AST” as an alternative to its native “CoffeeScript AST.” We should perhaps design this “output AST” feature so that it takes an argument of which type of AST is being requested. I’m not opposed to the compiler outputting a JS AST. I think you’ll possibly get a lot of pushback if you want to introduce Prettier or If you look at it another way, you could be tackling your effort in stages:
Steps 1 and 2 are uncontroversial, and we can certainly work as a team to achieve them. Step 3 . . . well, either we can convince everyone to drop the “no dependencies” rule or it could be achieved via some kind of wrapper around the CoffeeScript compiler, like a new project called CoffeeScript = require 'coffeescript'
Prettier = require 'prettier'
exports.compile = (coffeeSource) ->
ast = CoffeeScript.compile coffeeSource, {nodes: 'babel'} # Babel-spec AST
jsSource = Prettier.format ast
return jsSource It’s just that your branch does all three steps at once that feels a bit overwhelming to me. That’s where I think it goes a bit far. If we can break it up into chunks, it’ll be easier both to develop and to reach consensus about. |
@GeoffreyBooth ok that makes total sense. So then I'm inclined for the moment to try and move in two directions (corresponding to steps 1 and 2) from the current state of my branch:
I know you will likely push to separate out step 1 from step 2. That may well be how it lands as far as getting stuff merged. But for one thing I think there's actually significantly more trickiness in achieving a full-blown Coffeescript AST (since as has been alluded to it'd seem to involve a lot of restructuring of how we currently pass/consume/transform stuff through the rewriter/parser/node initialization in order to achieve full "preservation"/reconstructability of original source) than there has been in retargeting to a JS AST, where I've had to do a bit of pushing through/preserving additional source info (eg turning on source location |
This could also be broken down into even smaller chunks. See #5019 (comment), where we discuss moving as much of the string logic from the lexer into the nodes. Currently the lexer is doing things like replacing newlines with And that’s just one node type. This kind of cleanup work is even more of a prerequisite, and can happen in small PRs (one for strings, one for classes, etc.). It’s probably better that it does, and that in general we have lots of small PRs rather than one giant one, so that it’s easier for others to review. |
A little progress update: reached a nice milestone (with the help of #5079) that I can reformat the whole Coffeescript test suite (ie This is using my In order to achieve rules like "call parens are optional if the enclosing parent breaks", eg this being ok:
but this
, I had to introduce a new formatting primitive to Prettier (opened PR). But with that primitive in place, I'm able to have a rather sophisticated awareness of eg when it's ok to omit parens/braces, which I see as imperative for an opinionated Coffeescript formatter The biggest remaining chunk of both AST generation and Prettier formatting is comments |
@helixbass I created a CLI version of your plugin. |
Done via 2.5.0 and |
Great work! That's amazing. |
Seriously awesome work! I've been following along and I know a ton of
effort has gone into making this happen.
Thank you @helixbass and @GeoffreyBooth and everyone who has contributed to
this release!
What a great way to start off the new year! 🍾🍻🎉
…On Tue, Dec 31, 2019, 10:37 PM Geoffrey Booth ***@***.***> wrote:
Done via 2.5.0 <https://coffeescript.org/#2.5.0> and
prettier-plugin-coffeescript
<https://github.com/helixbass/prettier-plugin-coffeescript>. Amazing work
@helixbass <https://github.com/helixbass>! 🎉 🎉 🎉
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#4984?email_source=notifications&email_token=AAOLVRYFF5WTD22COTBKKUDQ3Q223A5CNFSM4ERJ2F62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEH465BQ#issuecomment-570027654>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAOLVR7EGCU37WU4SVRAPGLQ3Q223ANCNFSM4ERJ2F6Q>
.
|
It would be really great if CoffeeScript emitted an ESTree format AST like js2coffee/js2coffee@ For example, the following node types are exclusive to "CoffeeDoExpression" | "CoffeeEscapedExpression" | "CoffeeListExpression" | "CoffeeLoopStatement" | "CoffeePrototypeExpression" |
From @GeoffreyBooth on 2017-11-25 07:12
An oft-requested improvement to the CoffeeScript ecosystem is support for the language in Prettier. Our own @lydell is also a maintainer of that project, so I asked him what would be required to make it happen. He boiled it down to two major tasks:
Produce a detailed abstract syntax tree (AST)
Something would need to be able to produce a JSON representation of the nodes of the abstract syntax tree (AST). An AST is a representation of all the parts of syntax of a program, like
AssignmentExpression
; the site astexplorer.net has great examples. You can see a simplified version of CoffeeScript’s AST by runningcoffee --nodes test.coffee
. A fuller version can be seen by going to http://asaayers.github.io/clfiddle/ and clicking the AST tab, then one of the nodes in the tree.Since the CoffeeScript compiler itself already has the
--nodes
option, it seems logical to me to extend it to produce this JSON-based output. Currently the Node API for thecoffeescript
module doesn’t support anodes
option, so we could add one, and have its output be plain JavaScript objects that could beJSON.stringify
’ed.That wouldn’t be the end of the job, however. We would also need to ensure that this AST output is complete, with the same amount of information as the original source code, such that you could reconstruct the original source using nothing but this AST. In the CoffeeScript compiler, some simplifications are made at the lexer stage, before the nodes get generated: numbers lose
their original
0x
,0o
or0b
prefix (if any), whitespace is lost in multiline strings, multiline regexes are turned into aRegExp()
call, etc. These changes would need to be refactored to happen innodes.coffee
, or added detail about the node would need to be saved as a property on the node (like we currently tack on the source maps location data or comments). The goal is that this JSON representation of the source code could then be used to output new source code, formatted as Prettier deems it should be formatted. Which leads us to:Write a CoffeeScript code generator
Once a JSON version of the AST is available, we’ll need some function that takes it as input and produces a string of CoffeeScript source code as output. You’ve probably seen one of these already: js2coffee takes an AST produced by a JavaScript parser and creates CoffeeScript source code from those nodes. The function that does this is called a code generator, and js2coffee’s is here. With dependencies, it’s over a thousand lines of code. There’s one other CoffeeScript code generator that I’m aware of, cscodegen produced by the CoffeeScriptRedux effort, but it hasn’t been updated since 2012.
Prettier is itself a code generator. If it were to support CoffeeScript, a new code generator would need to be written as part of Prettier itself. Within the Prettier codebase, the code generators for supported languages are in src/printer*.js. One code generator supports all of JavaScript plus TypeScript and Flow, and it’s plain printer.js. It’s 5,000 lines of code. Writing a similar generator for CoffeeScript might not be much simpler, but you would be able to use js2coffee and cscodegen’s codebases as reference (not to mention Prettier’s JavaScript code generator) so you’re not starting from scratch.
So . . .
I would be willing to tackle the first task, outputting a detailed JSON AST, if one or more volunteers were up for the second task. Does anyone desire CoffeeScript support in Prettier strongly enough to invest the time in writing a quality CoffeeScript code generator?
The text was updated successfully, but these errors were encountered: