Skip to content

Commit 8a25195

Browse files
AST flag/API option; generic AST output for all nodes (#5044)
* Add `nodes` option to Node API, that returns POJO representation of AST; starting point for toPlainObject method on node classes to return the serializable plain JavaScript object representation of each node * Make --nodes also return JSON (pretty-printed, though not colorized) * Alphabetize CLI flags * Use new `ast` flag to request AST, restoring prior `nodes` flag; rename toPlainObject to toJSON
1 parent 70f6cb7 commit 8a25195

File tree

7 files changed

+129
-9
lines changed

7 files changed

+129
-9
lines changed

lib/coffeescript/coffeescript.js

+8-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/command.js

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/nodes.js

+60-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/coffeescript.coffee

+7-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,13 @@ exports.compile = compile = withPrettyErrors (code, options = {}) ->
9494
options.bare = yes
9595
break
9696

97-
fragments = parser.parse(tokens).compileToFragments options
97+
nodes = parser.parse tokens
98+
# If all that was requested was a POJO representation of the nodes, e.g.
99+
# the abstract syntax tree (AST), we can stop now and just return that.
100+
if options.ast
101+
return nodes.toJSON()
102+
103+
fragments = nodes.compileToFragments options
98104

99105
currentLine = 0
100106
currentLine += 1 if options.header

src/command.coffee

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ BANNER = '''
3232

3333
# The list of all the valid option flags that `coffee` knows how to handle.
3434
SWITCHES = [
35+
[ '--ast', 'generate an abstract syntax tree of nodes']
3536
['-b', '--bare', 'compile without a top-level function wrapper']
3637
['-c', '--compile', 'compile to JavaScript and save as .js files']
3738
['-e', '--eval', 'pass a string from the command line as input']
3839
['-h', '--help', 'display this help message']
3940
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
4041
['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling']
42+
['-l', '--literate', 'treat stdio as literate style coffeescript']
4143
['-m', '--map', 'generate source map and save as .js.map files']
4244
['-M', '--inline-map', 'generate source map and include it directly in output']
4345
['-n', '--nodes', 'print out the parse tree that the parser produces']
@@ -47,7 +49,6 @@ SWITCHES = [
4749
['-p', '--print', 'print out the compiled JavaScript']
4850
['-r', '--require [MODULE*]', 'require the given module before eval or REPL']
4951
['-s', '--stdio', 'listen for and compile scripts over stdio']
50-
['-l', '--literate', 'treat stdio as literate style coffeescript']
5152
['-t', '--transpile', 'pipe generated JavaScript through Babel']
5253
[ '--tokens', 'print out the tokens that the lexer/rewriter produce']
5354
['-v', '--version', 'display the version number']
@@ -207,6 +208,9 @@ compileScript = (file, input, base = null) ->
207208
printTokens CoffeeScript.tokens task.input, task.options
208209
else if opts.nodes
209210
printLine CoffeeScript.nodes(task.input, task.options).toString().trim()
211+
else if opts.ast
212+
compiled = CoffeeScript.compile task.input, task.options
213+
printLine JSON.stringify(compiled, null, 2)
210214
else if opts.run
211215
CoffeeScript.register()
212216
CoffeeScript.eval opts.prelude, task.options if opts.prelude
@@ -499,6 +503,7 @@ compileOptions = (filename, base) ->
499503
transpile: opts.transpile
500504
sourceMap: opts.map
501505
inlineMap: opts['inline-map']
506+
ast: opts.ast
502507

503508
if filename
504509
if base

src/nodes.coffee

+40-1
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,53 @@ exports.Base = class Base
257257
lastNode: (list) ->
258258
if list.length is 0 then null else list[list.length - 1]
259259

260-
# `toString` representation of the node, for inspecting the parse tree.
260+
# Debugging representation of the node, for inspecting the parse tree.
261261
# This is what `coffee --nodes` prints out.
262262
toString: (idt = '', name = @constructor.name) ->
263263
tree = '\n' + idt + name
264264
tree += '?' if @soak
265265
@eachChild (node) -> tree += node.toString idt + TAB
266266
tree
267267

268+
# Plain JavaScript object representation of the node, that can be serialized
269+
# as JSON. This is used for generating an abstract syntax tree (AST).
270+
# This is what the `ast` option in the Node API returns.
271+
toJSON: ->
272+
# We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babylon/ast/spec.md)
273+
# as closely as possible, for improved interoperability with other tools.
274+
obj =
275+
type: @constructor.name
276+
# Convert `locationData` to Babel’s style.
277+
loc:
278+
start:
279+
line: @locationData.first_line
280+
column: @locationData.first_column
281+
end:
282+
line: @locationData.last_line
283+
column: @locationData.last_column
284+
285+
# Add serializable properties to the output. Properties that aren’t
286+
# automatically serializable (because they’re already a primitive type)
287+
# should be handled on a case-by-case basis in child node classes’ own
288+
# `toJSON` methods.
289+
for property, value of this
290+
continue if property in ['locationData', 'children']
291+
continue if value is undefined # Don’t skip `null` or `false` values.
292+
if typeof value is 'boolean' or typeof value is 'number' or typeof value is 'string'
293+
obj[property] = value
294+
295+
# Work our way down the tree. This is like `eachChild`, except that we
296+
# preserve the child node name, and arrays.
297+
for attr in @children when @[attr]
298+
if Array.isArray(@[attr])
299+
obj[attr] = []
300+
for child in flatten [@[attr]]
301+
obj[attr].push child.unwrap().toJSON()
302+
else
303+
obj[attr] = @[attr].unwrap().toJSON()
304+
305+
obj
306+
268307
# Passes each child to a function, breaking when the function returns `false`.
269308
eachChild: (func) ->
270309
return this unless @children

test/argument_parsing.coffee

+2-1
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,14 @@ Usage: coffee [options] path/to/script.coffee [args]
119119
120120
If called without options, `coffee` will run your script.
121121
122+
--ast generate an abstract syntax tree of nodes
122123
-b, --bare compile without a top-level function wrapper
123124
-c, --compile compile to JavaScript and save as .js files
124125
-e, --eval pass a string from the command line as input
125126
-h, --help display this help message
126127
-i, --interactive run an interactive CoffeeScript REPL
127128
-j, --join concatenate the source CoffeeScript before compiling
129+
-l, --literate treat stdio as literate style coffeescript
128130
-m, --map generate source map and save as .js.map files
129131
-M, --inline-map generate source map and include it directly in output
130132
-n, --nodes print out the parse tree that the parser produces
@@ -134,7 +136,6 @@ If called without options, `coffee` will run your script.
134136
-p, --print print out the compiled JavaScript
135137
-r, --require require the given module before eval or REPL
136138
-s, --stdio listen for and compile scripts over stdio
137-
-l, --literate treat stdio as literate style coffeescript
138139
-t, --transpile pipe generated JavaScript through Babel
139140
--tokens print out the tokens that the lexer/rewriter produce
140141
-v, --version display the version number

0 commit comments

Comments
 (0)