mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Root AST (#5137)
* 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:
committed by
Geoffrey Booth
parent
4392d26985
commit
38c8b2f35f
@@ -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 might’ve 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
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -1373,7 +1373,7 @@
|
||||
// so if last_column == first_column, then we’re 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 don’t
|
||||
// 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 don’t
|
||||
// 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, we’re not including `sourceType` on the `Program` AST node.
|
||||
// Its value could be either `'script'` or `'module'`, and there’s 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 Node’s 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
|
||||
|
||||
@@ -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]]));
|
||||
|
||||
@@ -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 might’ve 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
114
src/nodes.coffee
114
src/nodes.coffee
@@ -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 don’t
|
||||
# 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 it’s an expression, simply return it. If it’s 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 don’t
|
||||
# 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, we’re not including `sourceType` on the `Program` AST node.
|
||||
# Its value could be either `'script'` or `'module'`, and there’s 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 Node’s 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
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user