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
This commit is contained in:
parent
70f6cb70e2
commit
8a25195442
|
@ -85,7 +85,7 @@
|
|||
// object, where sourceMap is a sourcemap.coffee#SourceMap object, handy for
|
||||
// doing programmatic lookups.
|
||||
exports.compile = compile = withPrettyErrors(function(code, options = {}) {
|
||||
var currentColumn, currentLine, encoded, filename, fragment, fragments, generateSourceMap, header, i, j, js, len, len1, map, newLines, ref, ref1, sourceMapDataURI, sourceURL, token, tokens, transpiler, transpilerOptions, transpilerOutput, v3SourceMap;
|
||||
var currentColumn, currentLine, encoded, filename, fragment, fragments, generateSourceMap, header, i, j, js, len, len1, map, newLines, nodes, ref, ref1, sourceMapDataURI, sourceURL, token, tokens, transpiler, transpilerOptions, transpilerOutput, v3SourceMap;
|
||||
// Clone `options`, to avoid mutating the `options` object passed in.
|
||||
options = Object.assign({}, options);
|
||||
// Always generate a source map if no filename is passed in, since without a
|
||||
|
@ -125,7 +125,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
fragments = parser.parse(tokens).compileToFragments(options);
|
||||
nodes = parser.parse(tokens);
|
||||
// If all that was requested was a POJO representation of the nodes, e.g.
|
||||
// the abstract syntax tree (AST), we can stop now and just return that.
|
||||
if (options.ast) {
|
||||
return nodes.toJSON();
|
||||
}
|
||||
fragments = nodes.compileToFragments(options);
|
||||
currentLine = 0;
|
||||
if (options.header) {
|
||||
currentLine += 1;
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
BANNER = 'Usage: coffee [options] path/to/script.coffee [args]\n\nIf called without options, `coffee` will run your script.';
|
||||
|
||||
// The list of all the valid option flags that `coffee` knows how to handle.
|
||||
SWITCHES = [['-b', '--bare', 'compile without a top-level function wrapper'], ['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-e', '--eval', 'pass a string from the command line as input'], ['-h', '--help', 'display this help message'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'], ['-m', '--map', 'generate source map and save as .js.map files'], ['-M', '--inline-map', 'generate source map and include it directly in output'], ['-n', '--nodes', 'print out the parse tree that the parser produces'], ['--nodejs [ARGS]', 'pass options directly to the "node" binary'], ['--no-header', 'suppress the "Generated by" header'], ['-o', '--output [PATH]', 'set the output path or path/filename for compiled JavaScript'], ['-p', '--print', 'print out the compiled JavaScript'], ['-r', '--require [MODULE*]', 'require the given module before eval or REPL'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-l', '--literate', 'treat stdio as literate style coffeescript'], ['-t', '--transpile', 'pipe generated JavaScript through Babel'], ['--tokens', 'print out the tokens that the lexer/rewriter produce'], ['-v', '--version', 'display the version number'], ['-w', '--watch', 'watch scripts for changes and rerun commands']];
|
||||
SWITCHES = [['--ast', 'generate an abstract syntax tree of nodes'], ['-b', '--bare', 'compile without a top-level function wrapper'], ['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-e', '--eval', 'pass a string from the command line as input'], ['-h', '--help', 'display this help message'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'], ['-l', '--literate', 'treat stdio as literate style coffeescript'], ['-m', '--map', 'generate source map and save as .js.map files'], ['-M', '--inline-map', 'generate source map and include it directly in output'], ['-n', '--nodes', 'print out the parse tree that the parser produces'], ['--nodejs [ARGS]', 'pass options directly to the "node" binary'], ['--no-header', 'suppress the "Generated by" header'], ['-o', '--output [PATH]', 'set the output path or path/filename for compiled JavaScript'], ['-p', '--print', 'print out the compiled JavaScript'], ['-r', '--require [MODULE*]', 'require the given module before eval or REPL'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-t', '--transpile', 'pipe generated JavaScript through Babel'], ['--tokens', 'print out the tokens that the lexer/rewriter produce'], ['-v', '--version', 'display the version number'], ['-w', '--watch', 'watch scripts for changes and rerun commands']];
|
||||
|
||||
// Top-level objects shared by all the functions.
|
||||
opts = {};
|
||||
|
@ -257,6 +257,9 @@
|
|||
return printTokens(CoffeeScript.tokens(task.input, task.options));
|
||||
} else if (opts.nodes) {
|
||||
return printLine(CoffeeScript.nodes(task.input, task.options).toString().trim());
|
||||
} else if (opts.ast) {
|
||||
compiled = CoffeeScript.compile(task.input, task.options);
|
||||
return printLine(JSON.stringify(compiled, null, 2));
|
||||
} else if (opts.run) {
|
||||
CoffeeScript.register();
|
||||
if (opts.prelude) {
|
||||
|
@ -655,7 +658,8 @@
|
|||
header: opts.compile && !opts['no-header'],
|
||||
transpile: opts.transpile,
|
||||
sourceMap: opts.map,
|
||||
inlineMap: opts['inline-map']
|
||||
inlineMap: opts['inline-map'],
|
||||
ast: opts.ast
|
||||
};
|
||||
if (filename) {
|
||||
if (base) {
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
// `toString` representation of the node, for inspecting the parse tree.
|
||||
// Debugging representation of the node, for inspecting the parse tree.
|
||||
// This is what `coffee --nodes` prints out.
|
||||
toString(idt = '', name = this.constructor.name) {
|
||||
var tree;
|
||||
|
@ -359,6 +359,65 @@
|
|||
return tree;
|
||||
}
|
||||
|
||||
// Plain JavaScript object representation of the node, that can be serialized
|
||||
// as JSON. This is used for generating an abstract syntax tree (AST).
|
||||
// This is what the `ast` option in the Node API returns.
|
||||
toJSON() {
|
||||
var attr, child, j, k, len1, len2, obj, property, ref1, ref2, ref3, value;
|
||||
// We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babylon/ast/spec.md)
|
||||
// as closely as possible, for improved interoperability with other tools.
|
||||
obj = {
|
||||
type: this.constructor.name,
|
||||
// Convert `locationData` to Babel’s style.
|
||||
loc: {
|
||||
start: {
|
||||
line: this.locationData.first_line,
|
||||
column: this.locationData.first_column
|
||||
},
|
||||
end: {
|
||||
line: this.locationData.last_line,
|
||||
column: this.locationData.last_column
|
||||
}
|
||||
}
|
||||
};
|
||||
ref1 = this;
|
||||
// Add serializable properties to the output. Properties that aren’t
|
||||
// automatically serializable (because they’re already a primitive type)
|
||||
// should be handled on a case-by-case basis in child node classes’ own
|
||||
// `toJSON` methods.
|
||||
for (property in ref1) {
|
||||
value = ref1[property];
|
||||
if (property === 'locationData' || property === 'children') {
|
||||
continue;
|
||||
}
|
||||
if (value === void 0) { // Don’t skip `null` or `false` values.
|
||||
continue;
|
||||
}
|
||||
if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
|
||||
obj[property] = value;
|
||||
}
|
||||
}
|
||||
ref2 = this.children;
|
||||
// Work our way down the tree. This is like `eachChild`, except that we
|
||||
// preserve the child node name, and arrays.
|
||||
for (j = 0, len1 = ref2.length; j < len1; j++) {
|
||||
attr = ref2[j];
|
||||
if (this[attr]) {
|
||||
if (Array.isArray(this[attr])) {
|
||||
obj[attr] = [];
|
||||
ref3 = flatten([this[attr]]);
|
||||
for (k = 0, len2 = ref3.length; k < len2; k++) {
|
||||
child = ref3[k];
|
||||
obj[attr].push(child.unwrap().toJSON());
|
||||
}
|
||||
} else {
|
||||
obj[attr] = this[attr].unwrap().toJSON();
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Passes each child to a function, breaking when the function returns `false`.
|
||||
eachChild(func) {
|
||||
var attr, child, j, k, len1, len2, ref1, ref2;
|
||||
|
|
|
@ -94,7 +94,13 @@ exports.compile = compile = withPrettyErrors (code, options = {}) ->
|
|||
options.bare = yes
|
||||
break
|
||||
|
||||
fragments = parser.parse(tokens).compileToFragments options
|
||||
nodes = parser.parse tokens
|
||||
# If all that was requested was a POJO representation of the nodes, e.g.
|
||||
# the abstract syntax tree (AST), we can stop now and just return that.
|
||||
if options.ast
|
||||
return nodes.toJSON()
|
||||
|
||||
fragments = nodes.compileToFragments options
|
||||
|
||||
currentLine = 0
|
||||
currentLine += 1 if options.header
|
||||
|
|
|
@ -32,12 +32,14 @@ BANNER = '''
|
|||
|
||||
# The list of all the valid option flags that `coffee` knows how to handle.
|
||||
SWITCHES = [
|
||||
[ '--ast', 'generate an abstract syntax tree of nodes']
|
||||
['-b', '--bare', 'compile without a top-level function wrapper']
|
||||
['-c', '--compile', 'compile to JavaScript and save as .js files']
|
||||
['-e', '--eval', 'pass a string from the command line as input']
|
||||
['-h', '--help', 'display this help message']
|
||||
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
|
||||
['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling']
|
||||
['-l', '--literate', 'treat stdio as literate style coffeescript']
|
||||
['-m', '--map', 'generate source map and save as .js.map files']
|
||||
['-M', '--inline-map', 'generate source map and include it directly in output']
|
||||
['-n', '--nodes', 'print out the parse tree that the parser produces']
|
||||
|
@ -47,7 +49,6 @@ SWITCHES = [
|
|||
['-p', '--print', 'print out the compiled JavaScript']
|
||||
['-r', '--require [MODULE*]', 'require the given module before eval or REPL']
|
||||
['-s', '--stdio', 'listen for and compile scripts over stdio']
|
||||
['-l', '--literate', 'treat stdio as literate style coffeescript']
|
||||
['-t', '--transpile', 'pipe generated JavaScript through Babel']
|
||||
[ '--tokens', 'print out the tokens that the lexer/rewriter produce']
|
||||
['-v', '--version', 'display the version number']
|
||||
|
@ -207,6 +208,9 @@ compileScript = (file, input, base = null) ->
|
|||
printTokens CoffeeScript.tokens task.input, task.options
|
||||
else if opts.nodes
|
||||
printLine CoffeeScript.nodes(task.input, task.options).toString().trim()
|
||||
else if opts.ast
|
||||
compiled = CoffeeScript.compile task.input, task.options
|
||||
printLine JSON.stringify(compiled, null, 2)
|
||||
else if opts.run
|
||||
CoffeeScript.register()
|
||||
CoffeeScript.eval opts.prelude, task.options if opts.prelude
|
||||
|
@ -499,6 +503,7 @@ compileOptions = (filename, base) ->
|
|||
transpile: opts.transpile
|
||||
sourceMap: opts.map
|
||||
inlineMap: opts['inline-map']
|
||||
ast: opts.ast
|
||||
|
||||
if filename
|
||||
if base
|
||||
|
|
|
@ -257,7 +257,7 @@ exports.Base = class Base
|
|||
lastNode: (list) ->
|
||||
if list.length is 0 then null else list[list.length - 1]
|
||||
|
||||
# `toString` representation of the node, for inspecting the parse tree.
|
||||
# Debugging representation of the node, for inspecting the parse tree.
|
||||
# This is what `coffee --nodes` prints out.
|
||||
toString: (idt = '', name = @constructor.name) ->
|
||||
tree = '\n' + idt + name
|
||||
|
@ -265,6 +265,45 @@ exports.Base = class Base
|
|||
@eachChild (node) -> tree += node.toString idt + TAB
|
||||
tree
|
||||
|
||||
# Plain JavaScript object representation of the node, that can be serialized
|
||||
# as JSON. This is used for generating an abstract syntax tree (AST).
|
||||
# This is what the `ast` option in the Node API returns.
|
||||
toJSON: ->
|
||||
# We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babylon/ast/spec.md)
|
||||
# as closely as possible, for improved interoperability with other tools.
|
||||
obj =
|
||||
type: @constructor.name
|
||||
# Convert `locationData` to Babel’s style.
|
||||
loc:
|
||||
start:
|
||||
line: @locationData.first_line
|
||||
column: @locationData.first_column
|
||||
end:
|
||||
line: @locationData.last_line
|
||||
column: @locationData.last_column
|
||||
|
||||
# Add serializable properties to the output. Properties that aren’t
|
||||
# automatically serializable (because they’re already a primitive type)
|
||||
# should be handled on a case-by-case basis in child node classes’ own
|
||||
# `toJSON` methods.
|
||||
for property, value of this
|
||||
continue if property in ['locationData', 'children']
|
||||
continue if value is undefined # Don’t skip `null` or `false` values.
|
||||
if typeof value is 'boolean' or typeof value is 'number' or typeof value is 'string'
|
||||
obj[property] = value
|
||||
|
||||
# Work our way down the tree. This is like `eachChild`, except that we
|
||||
# preserve the child node name, and arrays.
|
||||
for attr in @children when @[attr]
|
||||
if Array.isArray(@[attr])
|
||||
obj[attr] = []
|
||||
for child in flatten [@[attr]]
|
||||
obj[attr].push child.unwrap().toJSON()
|
||||
else
|
||||
obj[attr] = @[attr].unwrap().toJSON()
|
||||
|
||||
obj
|
||||
|
||||
# Passes each child to a function, breaking when the function returns `false`.
|
||||
eachChild: (func) ->
|
||||
return this unless @children
|
||||
|
|
|
@ -119,12 +119,14 @@ Usage: coffee [options] path/to/script.coffee [args]
|
|||
|
||||
If called without options, `coffee` will run your script.
|
||||
|
||||
--ast generate an abstract syntax tree of nodes
|
||||
-b, --bare compile without a top-level function wrapper
|
||||
-c, --compile compile to JavaScript and save as .js files
|
||||
-e, --eval pass a string from the command line as input
|
||||
-h, --help display this help message
|
||||
-i, --interactive run an interactive CoffeeScript REPL
|
||||
-j, --join concatenate the source CoffeeScript before compiling
|
||||
-l, --literate treat stdio as literate style coffeescript
|
||||
-m, --map generate source map and save as .js.map files
|
||||
-M, --inline-map generate source map and include it directly in output
|
||||
-n, --nodes print out the parse tree that the parser produces
|
||||
|
@ -134,7 +136,6 @@ If called without options, `coffee` will run your script.
|
|||
-p, --print print out the compiled JavaScript
|
||||
-r, --require require the given module before eval or REPL
|
||||
-s, --stdio listen for and compile scripts over stdio
|
||||
-l, --literate treat stdio as literate style coffeescript
|
||||
-t, --transpile pipe generated JavaScript through Babel
|
||||
--tokens print out the tokens that the lexer/rewriter produce
|
||||
-v, --version display the version number
|
||||
|
|
Loading…
Reference in New Issue