* updated grammar

* updated grammar

* tests

* location data tests

* fix from code review
This commit is contained in:
Julian Rosse 2019-02-14 22:51:33 -05:00 committed by Geoffrey Booth
parent 74e0798f89
commit 806a442894
7 changed files with 887 additions and 146 deletions

View File

@ -1991,7 +1991,7 @@
LOC(1)(Block.wrap([$1])),
{
type: $2,
statement: true
postfix: true
});
}),
o('Expression POST_IF Expression',
@ -2000,7 +2000,7 @@
LOC(1)(Block.wrap([$1])),
{
type: $2,
statement: true
postfix: true
});
})
],
@ -2035,7 +2035,7 @@
LOC(1)(Block.wrap([$1])),
{
type: $2,
statement: true
postfix: true
});
}),
o('Expression POST_IF ExpressionLine',
@ -2044,7 +2044,7 @@
LOC(1)(Block.wrap([$1])),
{
type: $2,
statement: true
postfix: true
});
})
],

View File

@ -363,7 +363,11 @@
// as JSON. This is what the `ast` option in the Node API returns.
// We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md)
// as closely as possible, for improved interoperability with other tools.
ast(o) {
ast(o, level) {
o = Object.assign({}, o);
if (level != null) {
o.level = level;
}
// Every abstract syntax tree node object has four categories of properties:
// - type, stored in the `type` field and a string like `NumberLiteral`.
// - location data, stored in the `loc`, `start`, `end` and `range` fields.
@ -373,7 +377,7 @@
// `parsedValue` are all top level fields in the AST node object. We have
// separate methods for returning each category, that we merge together here.
return Object.assign({}, {
type: this.astType()
type: this.astType(o)
}, this.astProperties(o), this.astLocationData());
}
@ -699,6 +703,7 @@
}
ast(o) {
o.level = LEVEL_TOP;
this.initializeScope(o);
return super.ast(o);
}
@ -1457,8 +1462,8 @@
return [this.makeCode('['), ...this.value.compileToFragments(o, LEVEL_LIST), this.makeCode(']')];
}
ast(o) {
return this.value.ast(o);
ast(o, level) {
return this.value.ast(o, level);
}
};
@ -1636,7 +1641,7 @@
astProperties(o) {
var ref1, ref2;
return {
argument: (ref1 = (ref2 = this.expression) != null ? ref2.ast(o) : void 0) != null ? ref1 : null
argument: (ref1 = (ref2 = this.expression) != null ? ref2.ast(o, LEVEL_PAREN) : void 0) != null ? ref1 : null
};
}
@ -1673,11 +1678,11 @@
}
}
ast(o) {
ast(o, level) {
this.checkScope(o);
return new Op(this.keyword, new Return(this.expression).withLocationDataFrom(this.expression != null ? {
locationData: mergeLocationData(this.returnKeyword.locationData, this.expression.locationData)
} : this.returnKeyword)).withLocationDataFrom(this).ast(o);
} : this.returnKeyword)).withLocationDataFrom(this).ast(o, level);
}
};
@ -1990,15 +1995,15 @@
return object;
}
ast(o) {
ast(o, level) {
if (!this.hasProperties()) {
// If the `Value` has no properties, the AST node is just whatever this
// nodes `base` is.
return this.base.ast(o);
return this.base.ast(o, level);
}
// Otherwise, call `Base::ast` which in turn calls the `astType` and
// `astProperties` methods below.
return super.ast(o);
return super.ast(o, level);
}
astType() {
@ -2019,7 +2024,7 @@
property.name.csx = true;
}
return {
object: this.object().ast(o),
object: this.object().ast(o, LEVEL_ACCESS),
property: property.ast(o),
computed: property instanceof Index || !(((ref2 = property.name) != null ? ref2.unwrap() : void 0) instanceof PropertyName),
optional: !!property.soak,
@ -2260,13 +2265,13 @@
return fragments;
}
ast() {
ast(o) {
var attribute, j, len1, ref1, results;
ref1 = this.attributes;
results = [];
for (j = 0, len1 = ref1.length; j < len1; j++) {
attribute = ref1[j];
results.push(attribute.ast());
results.push(attribute.ast(o));
}
return results;
}
@ -2315,7 +2320,7 @@
return !this.tagName.base.value.length;
}
ast(o) {
ast(o, level) {
var tagName;
// The location data spanning the opening element < ... > is captured by
// the generated Arr which contains the element's attributes
@ -2325,7 +2330,7 @@
if (this.content != null) {
this.closingElementLocationData = mergeAstLocationData(jisonLocationDataToAstLocationData(tagName.closingTagOpeningBracketLocationData), jisonLocationDataToAstLocationData(tagName.closingTagClosingBracketLocationData));
}
return super.ast(o);
return super.ast(o, level);
}
astType() {
@ -2601,14 +2606,14 @@
astProperties(o) {
var arg;
return {
callee: this.variable.ast(o),
callee: this.variable.ast(o, LEVEL_ACCESS),
arguments: (function() {
var j, len1, ref1, results;
ref1 = this.args;
results = [];
for (j = 0, len1 = ref1.length; j < len1; j++) {
arg = ref1[j];
results.push(arg.ast(o));
results.push(arg.ast(o, LEVEL_LIST));
}
return results;
}).call(this),
@ -2795,11 +2800,11 @@
}
}
ast(o) {
ast(o, level) {
// Babel doesnt have an AST node for `Access`, but rather just includes
// this Access nodes child `name` Identifier node as the `property` of
// the `MemberExpression` node.
return this.name.ast(o);
return this.name.ast(o, level);
}
};
@ -2830,13 +2835,13 @@
return this.index.shouldCache();
}
ast(o) {
ast(o, level) {
// Babel doesnt have an AST node for `Index`, but rather just includes
// this Index nodes child `index` Identifier node as the `property` of
// the `MemberExpression` node. The fact that the `MemberExpression`s
// `property` is an Index means that `computed` is `true` for the
// `MemberExpression`.
return this.index.ast(o);
return this.index.ast(o, level);
}
};
@ -3019,8 +3024,8 @@
return [this.makeCode(`.slice(${fragmentsToText(fromCompiled)}${toStr || ''})`)];
}
ast(o) {
return this.range.ast(o);
ast(o, level) {
return this.range.ast(o, level);
}
};
@ -3568,7 +3573,7 @@
results = [];
for (j = 0, len1 = ref1.length; j < len1; j++) {
object = ref1[j];
results.push(object.ast(o));
results.push(object.ast(o, LEVEL_LIST));
}
return results;
}).call(this)
@ -4863,8 +4868,8 @@
astProperties(o) {
var ref1, ret;
ret = {
right: this.value.ast(o),
left: this.variable.ast(o)
right: this.value.ast(o, LEVEL_LIST),
left: this.variable.ast(o, LEVEL_LIST)
};
if (!this.isDefaultAssignment()) {
ret.operator = (ref1 = this.originalContext) != null ? ref1 : '=';
@ -5341,9 +5346,9 @@
return results;
}
ast(o) {
ast(o, level) {
this.updateOptions(o);
return super.ast(o);
return super.ast(o, level);
}
astType() {
@ -5389,7 +5394,7 @@
}
return results;
}).call(this),
body: this.body.ast(o),
body: this.body.ast(o, LEVEL_TOP),
generator: !!this.isGenerator,
async: !!this.isAsync,
// We never generate named functions, so specify `id` as `null`, which
@ -5617,7 +5622,7 @@
astProperties(o) {
return {
argument: this.name.ast(o),
argument: this.name.ast(o, LEVEL_OP),
postfix: this.postfix
};
}
@ -6078,11 +6083,11 @@
return super.toString(idt, this.constructor.name + ' ' + this.operator);
}
ast(o) {
ast(o, level) {
if (this.isYield() || this.isAwait()) {
this.checkContinuation(o);
}
return super.ast(o);
return super.ast(o, level);
}
astType() {
@ -6111,8 +6116,8 @@
astProperties(o) {
var argument, firstAst, ref1, secondAst;
firstAst = this.first.ast(o);
secondAst = (ref1 = this.second) != null ? ref1.ast(o) : void 0;
firstAst = this.first.ast(o, LEVEL_OP);
secondAst = (ref1 = this.second) != null ? ref1.ast(o, LEVEL_OP) : void 0;
switch (false) {
case !this.isUnary():
argument = this.isYield() && this.first.unwrap().value === '' ? null : firstAst;
@ -6290,10 +6295,10 @@
astProperties(o) {
var ref1, ref2;
return {
block: this.attempt.ast(o),
block: this.attempt.ast(o, LEVEL_TOP),
handler: (ref1 = (ref2 = this.catch) != null ? ref2.ast(o) : void 0) != null ? ref1 : null,
// Include `finally` keyword in location data.
finalizer: this.ensure != null ? Object.assign(this.ensure.ast(o), mergeAstLocationData(jisonLocationDataToAstLocationData(this.finallyTag.locationData), this.ensure.astLocationData())) : null
finalizer: this.ensure != null ? Object.assign(this.ensure.ast(o, LEVEL_TOP), mergeAstLocationData(jisonLocationDataToAstLocationData(this.finallyTag.locationData), this.ensure.astLocationData())) : null
};
}
@ -6355,7 +6360,7 @@
var ref1, ref2;
return {
param: (ref1 = (ref2 = this.errorVariable) != null ? ref2.ast(o) : void 0) != null ? ref1 : null,
body: this.recovery.ast(o)
body: this.recovery.ast(o, LEVEL_TOP)
};
}
@ -6394,7 +6399,7 @@
astProperties(o) {
return {
argument: this.expression.ast(o)
argument: this.expression.ast(o, LEVEL_LIST)
};
}
@ -6535,7 +6540,7 @@
}
ast(o) {
return this.body.unwrap().ast(o);
return this.body.unwrap().ast(o, LEVEL_PAREN);
}
};
@ -7083,7 +7088,7 @@
astProperties(o) {
var ref1, ref2;
return {
discriminant: (ref1 = (ref2 = this.subject) != null ? ref2.ast(o) : void 0) != null ? ref1 : null,
discriminant: (ref1 = (ref2 = this.subject) != null ? ref2.ast(o, LEVEL_PAREN) : void 0) != null ? ref1 : null,
cases: this.casesAst(o)
};
}
@ -7110,8 +7115,8 @@
astProperties(o) {
var ref1, ref2, ref3, ref4;
return {
test: (ref1 = (ref2 = this.test) != null ? ref2.ast(o) : void 0) != null ? ref1 : null,
consequent: (ref3 = (ref4 = this.block) != null ? ref4.ast(o).body : void 0) != null ? ref3 : [],
test: (ref1 = (ref2 = this.test) != null ? ref2.ast(o, LEVEL_PAREN) : void 0) != null ? ref1 : null,
consequent: (ref3 = (ref4 = this.block) != null ? ref4.ast(o, LEVEL_TOP).body : void 0) != null ? ref3 : [],
trailing: !!this.trailing
};
}
@ -7149,13 +7154,13 @@
// because ternaries are already proper expressions, and dont need conversion.
exports.If = If = (function() {
class If extends Base {
constructor(condition, body1, options = {}) {
constructor(condition1, body1, options = {}) {
super();
this.condition = condition1;
this.body = body1;
this.condition = options.type === 'unless' ? condition.invert() : condition;
this.elseBody = null;
this.isChain = false;
({soak: this.soak} = options);
({soak: this.soak, postfix: this.postfix, type: this.type} = options);
if (this.condition.comments) {
moveComments(this.condition, this);
}
@ -7175,10 +7180,14 @@
addElse(elseBody) {
if (this.isChain) {
this.elseBodyNode().addElse(elseBody);
this.locationData = mergeLocationData(this.locationData, this.elseBodyNode().locationData);
} else {
this.isChain = elseBody instanceof If;
this.elseBody = this.ensureBlock(elseBody);
this.elseBody.updateLocationDataIfMissing(elseBody.locationData);
if ((this.locationData != null) && (this.elseBody.locationData != null)) {
this.locationData = mergeLocationData(this.locationData, this.elseBody.locationData);
}
}
return this;
}
@ -7227,12 +7236,12 @@
child = del(o, 'chainChild');
exeq = del(o, 'isExistentialEquals');
if (exeq) {
return new If(this.condition.invert(), this.elseBodyNode(), {
return new If(this.processedCondition().invert(), this.elseBodyNode(), {
type: 'if'
}).compileToFragments(o);
}
indent = o.indent + TAB;
cond = this.condition.compileToFragments(o, LEVEL_PAREN);
cond = this.processedCondition().compileToFragments(o, LEVEL_PAREN);
body = this.ensureBlock(this.body).compileToFragments(merge(o, {indent}));
ifPart = [].concat(this.makeCode("if ("), cond, this.makeCode(") {\n"), body, this.makeCode(`\n${this.tab}}`));
if (!child) {
@ -7254,7 +7263,7 @@
// Compile the `If` as a conditional operator.
compileExpression(o) {
var alt, body, cond, fragments;
cond = this.condition.compileToFragments(o, LEVEL_COND);
cond = this.processedCondition().compileToFragments(o, LEVEL_COND);
body = this.bodyNode().compileToFragments(o, LEVEL_LIST);
alt = this.elseBodyNode() ? this.elseBodyNode().compileToFragments(o, LEVEL_LIST) : [this.makeCode('void 0')];
fragments = cond.concat(this.makeCode(" ? "), body, this.makeCode(" : "), alt);
@ -7269,6 +7278,34 @@
return this.soak && this;
}
processedCondition() {
return this.processedConditionCache != null ? this.processedConditionCache : this.processedConditionCache = this.type === 'unless' ? this.condition.invert() : this.condition;
}
isStatementAst(o) {
return o.level === LEVEL_TOP;
}
astType(o) {
if (this.isStatementAst(o)) {
return 'IfStatement';
} else {
return 'ConditionalExpression';
}
}
astProperties(o) {
var isStatement, ref1, ref2;
isStatement = this.isStatementAst(o);
return {
test: this.condition.ast(o, isStatement ? LEVEL_PAREN : LEVEL_COND),
consequent: isStatement ? this.body.ast(o, LEVEL_TOP) : this.bodyNode().ast(o, LEVEL_TOP),
alternate: this.isChain ? this.elseBody.unwrap().ast(o, isStatement ? LEVEL_TOP : LEVEL_COND) : (ref1 = (ref2 = this.elseBody) != null ? ref2.ast(o, LEVEL_TOP) : void 0) != null ? ref1 : null,
postfix: !!this.postfix,
inverted: this.type === 'unless'
};
}
};
If.prototype.children = ['condition', 'body', 'elseBody'];

View File

@ -984,7 +984,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-2], $$[$0-2], _$[$0], $$[$0], true)(new yy.I
yy.addDataToNode(yy, _$[$0-2], $$[$0-2], null, null, true)(yy.Block.wrap([$$[$0-2]])),
{
type: $$[$0-1],
statement: true
postfix: true
}));
break;
case 360:

View File

@ -817,8 +817,8 @@ grammar =
If: [
o 'IfBlock'
o 'IfBlock ELSE Block', -> $1.addElse $3
o 'Statement POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
o 'Expression POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
o 'Statement POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
o 'Expression POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
]
IfBlockLine: [
@ -829,8 +829,8 @@ grammar =
IfLine: [
o 'IfBlockLine'
o 'IfBlockLine ELSE Block', -> $1.addElse $3
o 'Statement POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
o 'Expression POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
o 'Statement POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
o 'Expression POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
]
# Arithmetic and logical operators, working on one or more operands.

View File

@ -270,7 +270,9 @@ exports.Base = class Base
# as JSON. This is what the `ast` option in the Node API returns.
# We try to follow the [Babel AST spec](https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md)
# as closely as possible, for improved interoperability with other tools.
ast: (o) ->
ast: (o, level) ->
o = Object.assign {}, o
o.level = level if level?
# Every abstract syntax tree node object has four categories of properties:
# - type, stored in the `type` field and a string like `NumberLiteral`.
# - location data, stored in the `loc`, `start`, `end` and `range` fields.
@ -279,7 +281,7 @@ exports.Base = class Base
# These fields are all intermixed in the Babel spec; `type` and `start` and
# `parsedValue` are all top level fields in the AST node object. We have
# separate methods for returning each category, that we merge together here.
Object.assign {}, {type: @astType()}, @astProperties(o), @astLocationData()
Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
# By default, a node class has no specific properties.
astProperties: -> {}
@ -498,6 +500,7 @@ exports.Root = class Root extends Base
o.scope.parameter name for name in o.locals or []
ast: (o) ->
o.level = LEVEL_TOP
@initializeScope o
super o
@ -1011,8 +1014,8 @@ exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
compileNode: (o) ->
[@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
ast: (o) ->
@value.ast o
ast: (o, level) ->
@value.ast o, level
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
@ -1121,7 +1124,7 @@ exports.Return = class Return extends Base
astType: -> 'ReturnStatement'
astProperties: (o) ->
argument: @expression?.ast(o) ? null
argument: @expression?.ast(o, LEVEL_PAREN) ? null
# Parent class for `YieldReturn`/`AwaitReturn`.
exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
@ -1138,7 +1141,7 @@ exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
isStatementAst: NO
ast: (o) ->
ast: (o, level) ->
@checkScope o
new Op @keyword,
@ -1150,7 +1153,7 @@ exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
@returnKeyword
)
.withLocationDataFrom @
.ast o
.ast o, level
# `yield return` works exactly like `return`, except that it turns the function
# into a generator.
@ -1339,13 +1342,13 @@ exports.Value = class Value extends Base
mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
object
ast: (o) ->
ast: (o, level) ->
# If the `Value` has no properties, the AST node is just whatever this
# nodes `base` is.
return @base.ast o unless @hasProperties()
return @base.ast o, level unless @hasProperties()
# Otherwise, call `Base::ast` which in turn calls the `astType` and
# `astProperties` methods below.
super o
super o, level
astType: ->
if @isCSXTag()
@ -1360,7 +1363,7 @@ exports.Value = class Value extends Base
[..., property] = @properties
property.name.csx = yes if @isCSXTag()
return
object: @object().ast o
object: @object().ast o, LEVEL_ACCESS
property: property.ast o
computed: property instanceof Index or property.name?.unwrap() not instanceof PropertyName
optional: !!property.soak
@ -1519,8 +1522,8 @@ exports.CSXAttributes = class CSXAttributes extends Base
fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
fragments
ast: ->
attribute.ast() for attribute in @attributes
ast: (o) ->
attribute.ast(o) for attribute in @attributes
# Node for a CSX element
exports.CSXElement = class CSXElement extends Base
@ -1545,7 +1548,7 @@ exports.CSXElement = class CSXElement extends Base
isFragment: ->
!@tagName.base.value.length
ast: (o) ->
ast: (o, level) ->
# The location data spanning the opening element < ... > is captured by
# the generated Arr which contains the element's attributes
@openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData
@ -1558,7 +1561,7 @@ exports.CSXElement = class CSXElement extends Base
jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
)
super o
super o, level
astType: ->
if @isFragment()
@ -1769,8 +1772,8 @@ exports.Call = class Call extends Base
astProperties: (o) ->
return
callee: @variable.ast o
arguments: arg.ast(o) for arg in @args
callee: @variable.ast o, LEVEL_ACCESS
arguments: arg.ast(o, LEVEL_LIST) for arg in @args
optional: !!@soak
implicit: !!@implicit
@ -1890,11 +1893,11 @@ exports.Access = class Access extends Base
shouldCache: NO
ast: (o) ->
ast: (o, level) ->
# Babel doesnt have an AST node for `Access`, but rather just includes
# this Access nodes child `name` Identifier node as the `property` of
# the `MemberExpression` node.
@name.ast o
@name.ast o, level
#### Index
@ -1911,13 +1914,13 @@ exports.Index = class Index extends Base
shouldCache: ->
@index.shouldCache()
ast: (o) ->
ast: (o, level) ->
# Babel doesnt have an AST node for `Index`, but rather just includes
# this Index nodes child `index` Identifier node as the `property` of
# the `MemberExpression` node. The fact that the `MemberExpression`s
# `property` is an Index means that `computed` is `true` for the
# `MemberExpression`.
@index.ast o
@index.ast o, level
#### Range
@ -2074,8 +2077,8 @@ exports.Slice = class Slice extends Base
"+#{fragmentsToText compiled} + 1 || 9e9"
[@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
ast: (o) ->
@range.ast(o)
ast: (o, level) ->
@range.ast(o, level)
#### Obj
@ -2388,7 +2391,7 @@ exports.Arr = class Arr extends Base
astProperties: (o) ->
return
elements:
object.ast(o) for object in @objects
object.ast(o, LEVEL_LIST) for object in @objects
#### Class
@ -3263,8 +3266,8 @@ exports.Assign = class Assign extends Base
astProperties: (o) ->
ret =
right: @value.ast o
left: @variable.ast o
right: @value.ast o, LEVEL_LIST
left: @variable.ast o, LEVEL_LIST
unless @isDefaultAssignment()
ret.operator = @originalContext ? '='
@ -3599,9 +3602,9 @@ exports.Code = class Code extends Base
for {name} in @params when name instanceof Arr or name instanceof Obj
name.propagateLhs yes
ast: (o) ->
ast: (o, level) ->
@updateOptions o
super o
super o, level
astType: ->
if @bound
@ -3624,7 +3627,7 @@ exports.Code = class Code extends Base
astProperties: (o) ->
return
params: @paramForAst(param).ast(o) for param in @params
body: @body.ast o
body: @body.ast o, LEVEL_TOP
generator: !!@isGenerator
async: !!@isAsync
# We never generate named functions, so specify `id` as `null`, which
@ -3772,7 +3775,7 @@ exports.Splat = class Splat extends Base
'SpreadElement'
astProperties: (o) -> {
argument: @name.ast o
argument: @name.ast o, LEVEL_OP
@postfix
}
@ -4097,9 +4100,9 @@ exports.Op = class Op extends Base
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
ast: (o) ->
ast: (o, level) ->
@checkContinuation o if @isYield() or @isAwait()
super o
super o, level
astType: ->
return 'AwaitExpression' if @isAwait()
@ -4112,8 +4115,8 @@ exports.Op = class Op extends Base
else 'BinaryExpression'
astProperties: (o) ->
firstAst = @first.ast o
secondAst = @second?.ast o
firstAst = @first.ast o, LEVEL_OP
secondAst = @second?.ast o, LEVEL_OP
switch
when @isUnary()
argument =
@ -4219,11 +4222,11 @@ exports.Try = class Try extends Base
astProperties: (o) ->
return
block: @attempt.ast o
block: @attempt.ast o, LEVEL_TOP
handler: @catch?.ast(o) ? null
finalizer:
if @ensure?
Object.assign @ensure.ast(o),
Object.assign @ensure.ast(o, LEVEL_TOP),
# Include `finally` keyword in location data.
mergeAstLocationData(
jisonLocationDataToAstLocationData(@finallyTag.locationData),
@ -4263,7 +4266,7 @@ exports.Catch = class Catch extends Base
astProperties: (o) ->
return
param: @errorVariable?.ast(o) ? null
body: @recovery.ast o
body: @recovery.ast o, LEVEL_TOP
#### Throw
@ -4291,7 +4294,7 @@ exports.Throw = class Throw extends Base
astProperties: (o) ->
return
argument: @expression.ast o
argument: @expression.ast o, LEVEL_LIST
#### Existence
@ -4380,7 +4383,7 @@ exports.Parens = class Parens extends Base
return @wrapInBraces fragments if @csxAttribute
if bare then fragments else @wrapInParentheses fragments
ast: (o) -> @body.unwrap().ast o
ast: (o) -> @body.unwrap().ast o, LEVEL_PAREN
#### StringWithInterpolations
@ -4703,7 +4706,7 @@ exports.Switch = class Switch extends Base
astProperties: (o) ->
return
discriminant: @subject?.ast(o) ? null
discriminant: @subject?.ast(o, LEVEL_PAREN) ? null
cases: @casesAst o
class SwitchCase extends Base
@ -4714,8 +4717,8 @@ class SwitchCase extends Base
astProperties: (o) ->
return
test: @test?.ast(o) ? null
consequent: @block?.ast(o).body ? []
test: @test?.ast(o, LEVEL_PAREN) ? null
consequent: @block?.ast(o, LEVEL_TOP).body ? []
trailing: !!@trailing
exports.SwitchWhen = class SwitchWhen extends Base
@ -4732,12 +4735,11 @@ exports.SwitchWhen = class SwitchWhen extends Base
# Single-expression **Ifs** are compiled into conditional operators if possible,
# because ternaries are already proper expressions, and dont need conversion.
exports.If = class If extends Base
constructor: (condition, @body, options = {}) ->
constructor: (@condition, @body, options = {}) ->
super()
@condition = if options.type is 'unless' then condition.invert() else condition
@elseBody = null
@isChain = false
{@soak} = options
{@soak, @postfix, @type} = options
moveComments @condition, @ if @condition.comments
children: ['condition', 'body', 'elseBody']
@ -4749,10 +4751,12 @@ exports.If = class If extends Base
addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
@locationData = mergeLocationData @locationData, @elseBodyNode().locationData
else
@isChain = elseBody instanceof If
@elseBody = @ensureBlock elseBody
@elseBody.updateLocationDataIfMissing elseBody.locationData
@locationData = mergeLocationData @locationData, @elseBody.locationData if @locationData? and @elseBody.locationData?
this
# The **If** only compiles into a statement if either of its bodies needs
@ -4782,10 +4786,10 @@ exports.If = class If extends Base
exeq = del o, 'isExistentialEquals'
if exeq
return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
return new If(@processedCondition().invert(), @elseBodyNode(), type: 'if').compileToFragments o
indent = o.indent + TAB
cond = @condition.compileToFragments o, LEVEL_PAREN
cond = @processedCondition().compileToFragments o, LEVEL_PAREN
body = @ensureBlock(@body).compileToFragments merge o, {indent}
ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
ifPart.unshift @makeCode @tab unless child
@ -4800,7 +4804,7 @@ exports.If = class If extends Base
# Compile the `If` as a conditional operator.
compileExpression: (o) ->
cond = @condition.compileToFragments o, LEVEL_COND
cond = @processedCondition().compileToFragments o, LEVEL_COND
body = @bodyNode().compileToFragments o, LEVEL_LIST
alt = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
@ -4809,6 +4813,36 @@ exports.If = class If extends Base
unfoldSoak: ->
@soak and this
processedCondition: ->
@processedConditionCache ?= if @type is 'unless' then @condition.invert() else @condition
isStatementAst: (o) ->
o.level is LEVEL_TOP
astType: (o) ->
if @isStatementAst o
'IfStatement'
else
'ConditionalExpression'
astProperties: (o) ->
isStatement = @isStatementAst o
return
test: @condition.ast o, if isStatement then LEVEL_PAREN else LEVEL_COND
consequent:
if isStatement
@body.ast o, LEVEL_TOP
else
@bodyNode().ast o, LEVEL_TOP
alternate:
if @isChain
@elseBody.unwrap().ast o, if isStatement then LEVEL_TOP else LEVEL_COND
else
@elseBody?.ast(o, LEVEL_TOP) ? null
postfix: !!@postfix
inverted: @type is 'unless'
# Constants
# ---------

View File

@ -2321,48 +2321,151 @@ test "AST as expected for Switch node", ->
# # TODO: File issue for compile error when using `then` or `;` where `\n` is rn.
# test "AST as expected for If node", ->
# testExpression 'if maybe then yes',
# type: 'If'
# isChain: no
# condition:
# type: 'IdentifierLiteral'
# body:
# type: 'Value'
# base:
# type: 'BooleanLiteral'
test "AST as expected for If node", ->
testStatement 'if maybe then yes',
type: 'IfStatement'
test: ID 'maybe'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression:
type: 'BooleanLiteral'
]
alternate: null
postfix: no
inverted: no
# testExpression 'yes if maybe',
# type: 'If'
# isChain: no
# condition:
# type: 'IdentifierLiteral'
# body:
# type: 'Value'
# base:
# type: 'BooleanLiteral'
testStatement 'yes if maybe',
type: 'IfStatement'
test: ID 'maybe'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression:
type: 'BooleanLiteral'
]
alternate: null
postfix: yes
inverted: no
# # TODO: Where's the post-if flag?
testStatement 'unless x then x else if y then y else z',
type: 'IfStatement'
test: ID 'x'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'x'
]
alternate:
type: 'IfStatement'
test: ID 'y'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'y'
]
alternate:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'z'
]
postfix: no
inverted: no
postfix: no
inverted: yes
# testExpression 'unless x then x else if y then y else z',
# type: 'If'
# isChain: yes
# condition:
# type: 'Op'
# operator: '!'
# originalOperator: '!'
# flip: no
# body:
# type: 'Value'
# elseBody:
# type: 'If'
# isChain: no
# condition:
# type: 'IdentifierLiteral'
# body:
# type: 'Value'
# elseBody:
# type: 'Value'
# isDefaultValue: no
testStatement '''
if a
b
else
if c
d
''',
type: 'IfStatement'
test: ID 'a'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'b'
]
alternate:
type: 'BlockStatement'
body: [
type: 'IfStatement'
test: ID 'c'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'd'
]
alternate: null
postfix: no
inverted: no
]
postfix: no
inverted: no
# # TODO: AST generator should preserve use of `unless`.
testExpression '''
a =
if b then c else if d then e
''',
type: 'AssignmentExpression'
right:
type: 'ConditionalExpression'
test: ID 'b'
consequent: ID 'c'
alternate:
type: 'ConditionalExpression'
test: ID 'd'
consequent: ID 'e'
alternate: null
postfix: no
inverted: no
postfix: no
inverted: no
testExpression '''
f(
if b
c
d
)
''',
type: 'CallExpression'
arguments: [
type: 'ConditionalExpression'
test: ID 'b'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression:
ID 'c'
,
type: 'ExpressionStatement'
expression:
ID 'd'
]
postfix: no
inverted: no
]
testStatement 'a unless b',
type: 'IfStatement'
test: ID 'b'
consequent:
type: 'BlockStatement'
body: [
type: 'ExpressionStatement'
expression: ID 'a'
]
alternate: null
postfix: yes
inverted: yes

View File

@ -3889,3 +3889,570 @@ test "AST as expected for AwaitReturn node", ->
end:
line: 1
column: 15
test "AST as expected for If node", ->
testAstLocationData 'if maybe then yes',
type: 'IfStatement'
test:
start: 3
end: 8
range: [3, 8]
loc:
start:
line: 1
column: 3
end:
line: 1
column: 8
consequent:
body: [
expression:
start: 14
end: 17
range: [14, 17]
loc:
start:
line: 1
column: 14
end:
line: 1
column: 17
start: 14
end: 17
range: [14, 17]
loc:
start:
line: 1
column: 14
end:
line: 1
column: 17
]
start: 8
end: 17
range: [8, 17]
loc:
start:
line: 1
column: 8
end:
line: 1
column: 17
start: 0
end: 17
range: [0, 17]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 17
testAstLocationData 'yes if maybe',
type: 'IfStatement'
test:
start: 7
end: 12
range: [7, 12]
loc:
start:
line: 1
column: 7
end:
line: 1
column: 12
consequent:
body: [
expression:
start: 0
end: 3
range: [0, 3]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 3
start: 0
end: 3
range: [0, 3]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 3
]
start: 0
end: 3
range: [0, 3]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 3
start: 0
end: 12
range: [0, 12]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 12
testAstLocationData 'unless x then x else if y then y else z',
type: 'IfStatement'
test:
start: 7
end: 8
range: [7, 8]
loc:
start:
line: 1
column: 7
end:
line: 1
column: 8
consequent:
body: [
expression:
start: 14
end: 15
range: [14, 15]
loc:
start:
line: 1
column: 14
end:
line: 1
column: 15
start: 14
end: 15
range: [14, 15]
loc:
start:
line: 1
column: 14
end:
line: 1
column: 15
]
start: 8
end: 15
range: [8, 15]
loc:
start:
line: 1
column: 8
end:
line: 1
column: 15
alternate:
test:
start: 24
end: 25
range: [24, 25]
loc:
start:
line: 1
column: 24
end:
line: 1
column: 25
consequent:
body: [
expression:
start: 31
end: 32
range: [31, 32]
loc:
start:
line: 1
column: 31
end:
line: 1
column: 32
start: 31
end: 32
range: [31, 32]
loc:
start:
line: 1
column: 31
end:
line: 1
column: 32
]
start: 25
end: 32
range: [25, 32]
loc:
start:
line: 1
column: 25
end:
line: 1
column: 32
alternate:
body: [
expression:
start: 38
end: 39
range: [38, 39]
loc:
start:
line: 1
column: 38
end:
line: 1
column: 39
start: 38
end: 39
range: [38, 39]
loc:
start:
line: 1
column: 38
end:
line: 1
column: 39
]
start: 37
end: 39
range: [37, 39]
loc:
start:
line: 1
column: 37
end:
line: 1
column: 39
start: 21
end: 39
range: [21, 39]
loc:
start:
line: 1
column: 21
end:
line: 1
column: 39
start: 0
end: 39
range: [0, 39]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 39
testAstLocationData '''
if a
b
else
if c
d
''',
type: 'IfStatement'
test:
start: 3
end: 4
range: [3, 4]
loc:
start:
line: 1
column: 3
end:
line: 1
column: 4
consequent:
body: [
expression:
start: 7
end: 8
range: [7, 8]
loc:
start:
line: 2
column: 2
end:
line: 2
column: 3
start: 7
end: 8
range: [7, 8]
loc:
start:
line: 2
column: 2
end:
line: 2
column: 3
]
start: 5
end: 8
range: [5, 8]
loc:
start:
line: 2
column: 0
end:
line: 2
column: 3
alternate:
body: [
test:
start: 19
end: 20
range: [19, 20]
loc:
start:
line: 4
column: 5
end:
line: 4
column: 6
consequent:
body: [
expression:
start: 25
end: 26
range: [25, 26]
loc:
start:
line: 5
column: 4
end:
line: 5
column: 5
start: 25
end: 26
range: [25, 26]
loc:
start:
line: 5
column: 4
end:
line: 5
column: 5
]
start: 21
end: 26
range: [21, 26]
loc:
start:
line: 5
column: 0
end:
line: 5
column: 5
start: 16
end: 26
range: [16, 26]
loc:
start:
line: 4
column: 2
end:
line: 5
column: 5
]
start: 14
end: 26
range: [14, 26]
loc:
start:
line: 4
column: 0
end:
line: 5
column: 5
start: 0
end: 26
range: [0, 26]
loc:
start:
line: 1
column: 0
end:
line: 5
column: 5
testAstLocationData '''
a =
if b then c else if d then e
''',
type: 'AssignmentExpression'
right:
test:
start: 9
end: 10
range: [9, 10]
loc:
start:
line: 2
column: 5
end:
line: 2
column: 6
consequent:
start: 16
end: 17
range: [16, 17]
loc:
start:
line: 2
column: 12
end:
line: 2
column: 13
alternate:
test:
start: 26
end: 27
range: [26, 27]
loc:
start:
line: 2
column: 22
end:
line: 2
column: 23
consequent:
start: 33
end: 34
range: [33, 34]
loc:
start:
line: 2
column: 29
end:
line: 2
column: 30
start: 23
end: 34
range: [23, 34]
loc:
start:
line: 2
column: 19
end:
line: 2
column: 30
start: 6
end: 34
range: [6, 34]
loc:
start:
line: 2
column: 2
end:
line: 2
column: 30
start: 0
end: 34
range: [0, 34]
loc:
start:
line: 1
column: 0
end:
line: 2
column: 30
testAstLocationData '''
f(
if b
c
d
)
''',
type: 'CallExpression'
arguments: [
test:
start: 8
end: 9
range: [8, 9]
loc:
start:
line: 2
column: 5
end:
line: 2
column: 6
consequent:
body: [
expression:
start: 14
end: 15
range: [14, 15]
loc:
start:
line: 3
column: 4
end:
line: 3
column: 5
start: 14
end: 15
range: [14, 15]
loc:
start:
line: 3
column: 4
end:
line: 3
column: 5
,
expression:
start: 20
end: 21
range: [20, 21]
loc:
start:
line: 4
column: 4
end:
line: 4
column: 5
start: 20
end: 21
range: [20, 21]
loc:
start:
line: 4
column: 4
end:
line: 4
column: 5
]
start: 10
end: 21
range: [10, 21]
loc:
start:
line: 3
column: 0
end:
line: 4
column: 5
]
start: 0
end: 23
range: [0, 23]
loc:
start:
line: 1
column: 0
end:
line: 5
column: 1