* root ast

* updated grammar

* preserve CoffeeScript.nodes() API

* root ast methods

* updates from code review

* Style

* Fix a few missing returns

* Expand sourceType explanation

* Simplify

* Refactor Block.astProperties: use expression.astLocationData() to get location data, rather than extracting it from the whole AST object; move all the logic into one function, rather than spreading it out across several functions on the Block class that all appear to be internal

* testing root location data

* Fix location end data for root/File » Program AST node
This commit is contained in:
Julian Rosse
2019-01-16 16:10:08 -05:00
committed by Geoffrey Booth
parent 4392d26985
commit 38c8b2f35f
12 changed files with 363 additions and 92 deletions

View File

@@ -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, nodes, ref, ref1, sourceMapDataURI, sourceURL, token, tokens, transpiler, transpilerOptions, transpilerOutput, v3SourceMap;
var ast, currentColumn, currentLine, encoded, filename, fragment, fragments, generateSourceMap, header, i, j, js, len, len1, map, newLines, nodes, ref, ref1, sourceCodeLastLine, sourceCodeNumberOfLines, 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
@@ -127,9 +127,18 @@
}
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.
// the abstract syntax tree (AST), we can stop now and just return that
// (after fixing the location data for the root/`File`»`Program` node,
// which mightve gotten misaligned from the original source due to the
// `clean` function in the lexer).
if (options.ast) {
return nodes.ast(options);
sourceCodeNumberOfLines = (code.match(/\r?\n/g) || '').length + 1;
sourceCodeLastLine = /.*$/.exec(code)[0];
ast = nodes.ast(options);
ast.end = ast.range[1] = ast.program.end = ast.program.range[1] = code.length;
ast.loc.end.line = ast.program.loc.end.line = sourceCodeNumberOfLines;
ast.loc.end.column = ast.program.loc.end.column = sourceCodeLastLine.length;
return ast;
}
fragments = nodes.compileToFragments(options);
currentLine = 0;
@@ -223,10 +232,9 @@
// or traverse it by using `.traverseChildren()` with a callback.
exports.nodes = withPrettyErrors(function(source, options) {
if (typeof source === 'string') {
return parser.parse(lexer.tokenize(source, options));
} else {
return parser.parse(source);
source = lexer.tokenize(source, options);
}
return parser.parse(source).body;
});
// This file used to export these methods; leave stubs that throw warnings

View File

@@ -78,9 +78,12 @@
Root: [
o('',
function() {
return new Block();
return new Root(new Block());
}),
o('Body')
o('Body',
function() {
return new Root($1);
})
],
// Any list of statements and expressions, separated by line breaks or semicolons.
Body: [

View File

@@ -1373,7 +1373,7 @@
// so if last_column == first_column, then were looking at a character of length 1.
lastCharacter = length > 0 ? length - 1 : 0;
[locationData.last_line, locationData.last_column, endOffset] = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter);
locationData.range[1] = endOffset + 1;
locationData.range[1] = length > 0 ? endOffset + 1 : endOffset;
return locationData;
}

View File

@@ -4,7 +4,7 @@
// nodes are created as the result of actions in the [grammar](grammar.html),
// but some are created by other nodes as a method of code generation. To convert
// the syntax tree into a string of JavaScript code, call `compile()` on the root.
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXAttribute, CSXAttributes, CSXElement, CSXExpressionContainer, CSXIdentifier, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isFunction, isLiteralArguments, isLiteralThis, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXAttribute, CSXAttributes, CSXElement, CSXExpressionContainer, CSXIdentifier, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, Root, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isFunction, isLiteralArguments, isLiteralThis, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
indexOf = [].indexOf,
splice = [].splice,
slice1 = [].slice;
@@ -654,6 +654,50 @@
};
//### Root
// The root node of the node tree
exports.Root = Root = class Root extends Base {
constructor(body1) {
super();
this.body = body1;
}
// Wrap everything in a safety closure, unless requested not to. It would be
// better not to generate them in the first place, but for now, clean up
// obvious double-parentheses.
compileNode(o) {
var fragments, j, len1, name, ref1, ref2;
o.indent = o.bare ? '' : TAB;
o.level = LEVEL_TOP;
o.scope = new Scope(null, this.body, null, (ref1 = o.referencedVars) != null ? ref1 : []);
ref2 = o.locals || [];
for (j = 0, len1 = ref2.length; j < len1; j++) {
name = ref2[j];
// Mark given local variables in the root scope as parameters so they dont
// end up being declared on the root block.
o.scope.parameter(name);
}
fragments = this.body.compileRoot(o);
if (o.bare) {
return fragments;
}
return [].concat(this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n"));
}
astType() {
return 'File';
}
astProperties() {
return {
program: Object.assign(this.body.ast(), this.astLocationData()),
comments: []
};
}
};
//### Block
// The block is the list of expressions that forms the body of an
@@ -752,13 +796,11 @@
return this;
}
// A **Block** is the only node that can serve as the root.
compileToFragments(o = {}, level) {
if (o.scope) {
return super.compileToFragments(o, level);
} else {
return this.compileRoot(o);
compile(o, lvl) {
if (!o.scope) {
return new Root(this).withLocationDataFrom(this).compile(o, lvl);
}
return super.compile(o, lvl);
}
// Compile all expressions within the **Block** body. If we need to return
@@ -818,29 +860,12 @@
}
}
// If we happen to be the top-level **Block**, wrap everything in a safety
// closure, unless requested not to. It would be better not to generate them
// in the first place, but for now, clean up obvious double-parentheses.
compileRoot(o) {
var fragments, j, len1, name, ref1, ref2;
o.indent = o.bare ? '' : TAB;
o.level = LEVEL_TOP;
var fragments;
this.spaced = true;
o.scope = new Scope(null, this, null, (ref1 = o.referencedVars) != null ? ref1 : []);
ref2 = o.locals || [];
for (j = 0, len1 = ref2.length; j < len1; j++) {
name = ref2[j];
// Mark given local variables in the root scope as parameters so they dont
// end up being declared on this block.
o.scope.parameter(name);
}
fragments = this.compileWithDeclarations(o);
HoistTarget.expand(fragments);
fragments = this.compileComments(fragments);
if (o.bare) {
return fragments;
}
return [].concat(this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n"));
return this.compileComments(fragments);
}
// Compile the expressions body for the contents of a function, with
@@ -1059,11 +1084,43 @@
return new Block(nodes);
}
astProperties() {
astType() {
return 'Program';
}
astProperties(o) {
var body, expression, j, len1, ref1;
body = [];
ref1 = this.expressions;
for (j = 0, len1 = ref1.length; j < len1; j++) {
expression = ref1[j];
// If an expression is a statement, it can be added to the body as is.
if (expression.isStatement(o)) {
body.push(expression.ast());
} else {
// Otherwise, we need to wrap it in an `ExpressionStatement` AST node.
body.push(Object.assign({
type: 'ExpressionStatement',
expression: expression.ast()
}, expression.astLocationData()));
}
}
return {
expressions: this.expressions.map((child) => {
return child.ast();
})
// For now, were not including `sourceType` on the `Program` AST node.
// Its value could be either `'script'` or `'module'`, and theres no way
// for CoffeeScript to always know which it should be. The presence of an
// `import` or `export` statement in source code would imply that it should
// be a `module`, but a project may consist of mostly such files and also
// an outlier file that lacks `import` or `export` but is still imported
// into the project and therefore expects to be treated as a `module`.
// Determining the value of `sourceType` is essentially the same challenge
// posed by determining the parse goal of a JavaScript file, also `module`
// or `script`, and so if Node figures out a way to do so for `.js` files
// then CoffeeScript can copy Nodes algorithm.
// sourceType: 'module'
body: body,
directives: []
};
}
@@ -1075,6 +1132,7 @@
}).call(this);
//### Literal
// `Literal` is a base class for static values that can be passed through

View File

@@ -84,10 +84,10 @@ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* actio
var $0 = $$.length - 1;
switch (yystate) {
case 1:
return this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Block());
return this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Root(new yy.Block()));
break;
case 2:
return this.$ = $$[$0];
return this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Root($$[$0]));
break;
case 3:
this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(yy.Block.wrap([$$[$0]]));

View File

@@ -96,9 +96,18 @@ exports.compile = compile = withPrettyErrors (code, 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.
# the abstract syntax tree (AST), we can stop now and just return that
# (after fixing the location data for the root/`File`»`Program` node,
# which mightve gotten misaligned from the original source due to the
# `clean` function in the lexer).
if options.ast
return nodes.ast options
sourceCodeNumberOfLines = (code.match(/\r?\n/g) or '').length + 1
sourceCodeLastLine = /.*$/.exec(code)[0] # `.*` matches all but line break characters.
ast = nodes.ast options
ast.end = ast.range[1] = ast.program.end = ast.program.range[1] = code.length
ast.loc.end.line = ast.program.loc.end.line = sourceCodeNumberOfLines
ast.loc.end.column = ast.program.loc.end.column = sourceCodeLastLine.length
return ast
fragments = nodes.compileToFragments options
@@ -181,10 +190,8 @@ exports.tokens = withPrettyErrors (code, options) ->
# return the AST. You can then compile it by calling `.compile()` on the root,
# or traverse it by using `.traverseChildren()` with a callback.
exports.nodes = withPrettyErrors (source, options) ->
if typeof source is 'string'
parser.parse lexer.tokenize source, options
else
parser.parse source
source = lexer.tokenize source, options if typeof source is 'string'
parser.parse(source).body
# This file used to export these methods; leave stubs that throw warnings
# instead. These methods have been moved into `index.coffee` to provide

View File

@@ -73,8 +73,8 @@ grammar =
# The **Root** is the top-level node in the syntax tree. Since we parse bottom-up,
# all parsing must end here.
Root: [
o '', -> new Block
o 'Body'
o '', -> new Root new Block
o 'Body', -> new Root $1
]
# Any list of statements and expressions, separated by line breaks or semicolons.

View File

@@ -973,7 +973,7 @@ exports.Lexer = class Lexer
lastCharacter = if length > 0 then (length - 1) else 0
[locationData.last_line, locationData.last_column, endOffset] =
@getLineAndColumnFromChunk offsetInChunk + lastCharacter
locationData.range[1] = endOffset + 1
locationData.range[1] = if length > 0 then endOffset + 1 else endOffset
locationData

View File

@@ -468,6 +468,34 @@ exports.HoistTarget = class HoistTarget extends Base
compileClosure: (o) ->
@compileToFragments o
#### Root
# The root node of the node tree
exports.Root = class Root extends Base
constructor: (@body) ->
super()
# Wrap everything in a safety closure, unless requested not to. It would be
# better not to generate them in the first place, but for now, clean up
# obvious double-parentheses.
compileNode: (o) ->
o.indent = if o.bare then '' else TAB
o.level = LEVEL_TOP
o.scope = new Scope null, @body, null, o.referencedVars ? []
# Mark given local variables in the root scope as parameters so they dont
# end up being declared on the root block.
o.scope.parameter name for name in o.locals or []
fragments = @body.compileRoot o
return fragments if o.bare
[].concat @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
astType: -> 'File'
astProperties: ->
return
program: Object.assign @body.ast(), @astLocationData()
comments: []
#### Block
# The block is the list of expressions that forms the body of an
@@ -535,9 +563,10 @@ exports.Block = class Block extends Base
break
this
# A **Block** is the only node that can serve as the root.
compileToFragments: (o = {}, level) ->
if o.scope then super o, level else @compileRoot o
compile: (o, lvl) ->
return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
super o, lvl
# Compile all expressions within the **Block** body. If we need to return
# the result, and its an expression, simply return it. If its a statement,
@@ -581,22 +610,11 @@ exports.Block = class Block extends Base
answer = [@makeCode 'void 0']
if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
# If we happen to be the top-level **Block**, wrap everything in a safety
# closure, unless requested not to. It would be better not to generate them
# in the first place, but for now, clean up obvious double-parentheses.
compileRoot: (o) ->
o.indent = if o.bare then '' else TAB
o.level = LEVEL_TOP
@spaced = yes
o.scope = new Scope null, this, null, o.referencedVars ? []
# Mark given local variables in the root scope as parameters so they dont
# end up being declared on this block.
o.scope.parameter name for name in o.locals or []
@spaced = yes
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
fragments = @compileComments fragments
return fragments if o.bare
[].concat @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
@compileComments fragments
# Compile the expressions body for the contents of a function, with
# declarations of all inner variables pushed up to the top.
@@ -755,8 +773,38 @@ exports.Block = class Block extends Base
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
new Block nodes
astProperties: ->
expressions: @expressions.map (child) => child.ast()
astType: -> 'Program'
astProperties: (o) ->
body = []
for expression in @expressions
# If an expression is a statement, it can be added to the body as is.
if expression.isStatement o
body.push expression.ast()
# Otherwise, we need to wrap it in an `ExpressionStatement` AST node.
else
body.push Object.assign
type: 'ExpressionStatement'
expression: expression.ast()
,
expression.astLocationData()
return
# For now, were not including `sourceType` on the `Program` AST node.
# Its value could be either `'script'` or `'module'`, and theres no way
# for CoffeeScript to always know which it should be. The presence of an
# `import` or `export` statement in source code would imply that it should
# be a `module`, but a project may consist of mostly such files and also
# an outlier file that lacks `import` or `export` but is still imported
# into the project and therefore expects to be treated as a `module`.
# Determining the value of `sourceType` is essentially the same challenge
# posed by determining the parse goal of a JavaScript file, also `module`
# or `script`, and so if Node figures out a way to do so for `.js` files
# then CoffeeScript can copy Nodes algorithm.
# sourceType: 'module'
body: body
directives: [] # Directives like `'use strict'` are coming soon.
#### Literal
@@ -776,7 +824,8 @@ exports.Literal = class Literal extends Base
[@makeCode @value]
astProperties: ->
value: @value
return
value: @value
toString: ->
# This is only intended for debugging.
@@ -795,10 +844,11 @@ exports.NumberLiteral = class NumberLiteral extends Literal
astType: -> 'NumericLiteral'
astProperties: ->
value: @parsedValue
extra:
rawValue: @parsedValue
raw: @value
return
value: @parsedValue
extra:
rawValue: @parsedValue
raw: @value
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
compileNode: ->
@@ -807,7 +857,8 @@ exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
astType: -> 'Identifier'
astProperties: ->
name: 'Infinity'
return
name: 'Infinity'
exports.NaNLiteral = class NaNLiteral extends NumberLiteral
constructor: ->
@@ -820,7 +871,8 @@ exports.NaNLiteral = class NaNLiteral extends NumberLiteral
astType: -> 'Identifier'
astProperties: ->
name: 'NaN'
return
name: 'NaN'
exports.StringLiteral = class StringLiteral extends Literal
constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
@@ -935,7 +987,8 @@ exports.PropertyName = class PropertyName extends Literal
'Identifier'
astProperties: ->
name: @value
return
name: @value
exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
compileNode: (o) ->
@@ -974,7 +1027,8 @@ exports.ThisLiteral = class ThisLiteral extends Literal
astType: -> 'ThisExpression'
astProperties: ->
shorthand: @shorthand
return
shorthand: @shorthand
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
@@ -986,7 +1040,8 @@ exports.UndefinedLiteral = class UndefinedLiteral extends Literal
astType: -> 'Identifier'
astProperties: ->
name: @value
return
name: @value
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
@@ -1005,7 +1060,8 @@ exports.DefaultLiteral = class DefaultLiteral extends Literal
astType: -> 'Identifier'
astProperties: ->
name: 'default'
return
name: 'default'
#### Return

View File

@@ -92,11 +92,18 @@ test 'Confirm functionality of `deepStrictIncludeExpectedProperties`', ->
# properties are as expected.
test "AST as expected for Block node", ->
deepStrictIncludeExpectedProperties CoffeeScript.compile('return', ast: yes),
type: 'Block'
expressions: [
type: 'Return'
]
deepStrictIncludeExpectedProperties CoffeeScript.compile('a', ast: yes),
type: 'File'
program:
type: 'Program'
# sourceType: 'module'
body: [
type: 'ExpressionStatement'
expression:
type: 'Identifier'
]
directives: []
comments: []
test "AST as expected for NumberLiteral node", ->
testExpression '42',

View File

@@ -4,6 +4,9 @@
testAstLocationData = (code, expected) ->
testAstNodeLocationData getAstExpression(code), expected
testAstRootLocationData = (code, expected) ->
testAstNodeLocationData getAstRoot(code), expected
testAstNodeLocationData = (node, expected, path = '') ->
extendPath = (additionalPath) ->
return additionalPath unless path
@@ -2143,7 +2146,7 @@ test "AST location data as expected for Existence node", ->
line: 1
column: 7
test "AST location Data as expected for CSXTag node", ->
test "AST location data as expected for CSXTag node", ->
testAstLocationData '<CSXY />',
type: 'JSXElement'
openingElement:
@@ -2663,3 +2666,124 @@ test "AST location Data as expected for CSXTag node", ->
line: 1
column: 11
]
test "AST location data as expected for Root node", ->
testAstRootLocationData '1\n2',
type: 'File'
program:
start: 0
end: 3
range: [0, 3]
loc:
start:
line: 1
column: 0
end:
line: 2
column: 1
start: 0
end: 3
range: [0, 3]
loc:
start:
line: 1
column: 0
end:
line: 2
column: 1
testAstRootLocationData 'a = 1\nb',
type: 'File'
program:
start: 0
end: 7
range: [0, 7]
loc:
start:
line: 1
column: 0
end:
line: 2
column: 1
start: 0
end: 7
range: [0, 7]
loc:
start:
line: 1
column: 0
end:
line: 2
column: 1
testAstRootLocationData 'a = 1\nb\n\n',
type: 'File'
program:
start: 0
end: 9
range: [0, 9]
loc:
start:
line: 1
column: 0
end:
line: 4
column: 0
start: 0
end: 9
range: [0, 9]
loc:
start:
line: 1
column: 0
end:
line: 4
column: 0
testAstRootLocationData 'a = 1\n\n# Comment',
type: 'File'
program:
start: 0
end: 16
range: [0, 16]
loc:
start:
line: 1
column: 0
end:
line: 3
column: 9
start: 0
end: 16
range: [0, 16]
loc:
start:
line: 1
column: 0
end:
line: 3
column: 9
testAstRootLocationData 'a = 1\n\n# Comment\n',
type: 'File'
program:
start: 0
end: 17
range: [0, 17]
loc:
start:
line: 1
column: 0
end:
line: 4
column: 0
start: 0
end: 17
range: [0, 17]
loc:
start:
line: 1
column: 0
end:
line: 4
column: 0

View File

@@ -47,12 +47,20 @@ exports.inspect = (obj) ->
depth: 10
colors: if process.env.NODE_DISABLE_COLORS then no else yes
# Helpers to get AST nodes for a string of code. The root node is always a
# `Block` node, so for brevity in the tests return its children from
# `expressions`.
exports.getAstExpressions = (code) ->
ast = CoffeeScript.compile code, ast: yes
ast.expressions
# Helpers to get AST nodes for a string of code.
exports.getAstRoot = getAstRoot = (code) ->
CoffeeScript.compile code, ast: yes
# The root node is always a `File` node, so for brevity in the tests return its
# children from `program.body`.
getAstExpressions = (code) ->
ast = getAstRoot code
ast.program.body
# Many tests want just the root node.
exports.getAstExpression = (code) -> getAstExpressions(code)[0]
exports.getAstExpression = (code) ->
expressionAst = getAstExpressions(code)[0]
if expressionAst.type is 'ExpressionStatement'
expressionAst.expression
else
expressionAst