Merge pull request #16 from GeoffreyBooth/value-ast-ast-methods

AST methods
This commit is contained in:
Julian Rosse
2018-10-01 11:04:32 -04:00
committed by GitHub
4 changed files with 219 additions and 82 deletions

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, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, 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, 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, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, 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, 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, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
indexOf = [].indexOf,
splice = [].splice,
slice1 = [].slice;
@@ -1555,6 +1555,7 @@
}
this.base = base;
this.properties = props || [];
this.tag = tag;
if (tag) {
this[tag] = true;
}
@@ -1665,9 +1666,9 @@
}
isSplice() {
var lastProp, ref1;
ref1 = this.properties, [lastProp] = slice1.call(ref1, -1);
return lastProp instanceof Slice;
var lastProperty, ref1;
ref1 = this.properties, [lastProperty] = slice1.call(ref1, -1);
return lastProperty instanceof Slice;
}
looksStatic(className) {
@@ -1740,28 +1741,6 @@
return fragments;
}
ast() {
var j, len1, prop, propIndex, ref1, ref2, ret;
ret = this.base.ast();
ref1 = this.properties;
for (propIndex = j = 0, len1 = ref1.length; j < len1; propIndex = ++j) {
prop = ref1[propIndex];
ret = {
type: 'MemberExpression',
object: ret,
property: prop.ast(),
computed: prop instanceof Index || !(((ref2 = prop.name) != null ? ref2.unwrap() : void 0) instanceof PropertyName),
optional: !!prop.soak,
shorthand: !!prop.shorthand
};
// When the `Value` has properties, the location data of a `MemberExpression` AST node
// corresponding to a given property should span the location of the `Value`'s `base`
// (including parens if present) through the property's location
Object.assign(ret, mergeAstLocationData(this.base.astLocationData(), prop.astLocationData()));
}
return ret;
}
checkNewTarget(o) {
if (!(this.base instanceof IdentifierLiteral && this.base.value === 'new' && this.properties.length)) {
return;
@@ -1816,6 +1795,60 @@
}
}
// For AST generation, we need an `object` thats this `Value` minus its last
// property, if it has properties.
object() {
var initialProperties, object;
if (!this.hasProperties()) {
return this;
}
// Get all properties except the last one; for a `Value` with only one
// property, `initialProperties` is an empty array.
initialProperties = this.properties.slice(0, this.properties.length - 1);
// Create the `object` that becomes the new “base” for the split-off final
// property.
object = new Value(this.base, initialProperties, this.tag, this.isDefaultValue);
// Add location data to our new node, so that it has correct location data
// for source maps or later conversion into AST location data.
// This new `Value` has only one property, so the location data is just
// that of the parent `Value`s base.
// This new `Value` has multiple properties, so the location data spans
// from the parent `Value`s base to the last property thats included
// in this new node (a.k.a. the second-to-last property of the parent).
object.locationData = initialProperties.length === 0 ? this.base.locationData : mergeLocationData(this.base.locationData, initialProperties[initialProperties.length - 1].locationData);
return object;
}
ast() {
if (!this.hasProperties()) {
// If the `Value` has no properties, the AST node is just whatever this
// nodes `base` is.
return this.base.ast();
}
// Otherwise, call `Base::ast` which in turn calls the `astType` and
// `astProperties` methods below.
return super.ast();
}
astType() {
return 'MemberExpression';
}
// If this `Value` has properties, the *last* property (e.g. `c` in `a.b.c`)
// becomes the `property`, and the preceding properties (e.g. `a.b`) become
// a child `Value` node assigned to the `object` property.
astProperties() {
var property, ref1, ref2;
ref1 = this.properties, [property] = slice1.call(ref1, -1);
return {
object: this.object().ast(),
property: property.ast(),
computed: property instanceof Index || !(((ref2 = property.name) != null ? ref2.unwrap() : void 0) instanceof PropertyName),
optional: !!property.soak,
shorthand: !!property.shorthand
};
}
};
Value.prototype.children = ['base', 'properties'];
@@ -2284,6 +2317,9 @@
}
ast() {
// 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();
}
@@ -2311,14 +2347,19 @@
return [].concat(this.makeCode("["), this.index.compileToFragments(o, LEVEL_PAREN), this.makeCode("]"));
}
ast() {
return this.index.ast();
}
shouldCache() {
return this.index.shouldCache();
}
ast() {
// 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();
}
};
Index.prototype.children = ['index'];
@@ -5171,9 +5212,9 @@
//### In
exports.In = In = (function() {
class In extends Base {
constructor(object, array) {
constructor(object1, array) {
super();
this.object = object;
this.object = object1;
this.array = array;
}
@@ -6299,10 +6340,7 @@
return `${options.delimiter}${body}${options.delimiter}`;
};
// Take two AST nodes, or two AST nodes location data objects, and return a new
// location data object that encompasses the location data of both nodes. So the
// new `start` value will be the earlier of the two nodes `start` values, the
// new `end` value will be the later of the two nodes `end` values, etc.
// Helpers for `mergeLocationData` and `mergeAstLocationData` below.
lesser = function(a, b) {
if (a < b) {
return a;
@@ -6319,6 +6357,24 @@
}
};
// Take two nodes location data and return a new `locationData` object that
// encompasses the location data of both nodes. So the new `first_line` value
// will be the earlier of the two nodes `first_line` values, the new
// `last_column` the later of the two nodes `last_column` values, etc.
mergeLocationData = function(locationDataA, locationDataB) {
return {
first_line: lesser(locationDataA.first_line, locationDataB.first_line),
first_column: lesser(locationDataA.first_column, locationDataB.first_column),
last_line: greater(locationDataA.last_line, locationDataB.last_line),
last_column: greater(locationDataA.last_column, locationDataB.last_column),
range: [lesser(locationDataA.range[0], locationDataB.range[0]), greater(locationDataA.range[1], locationDataB.range[1])]
};
};
// Take two AST nodes, or two AST nodes location data objects, and return a new
// location data object that encompasses the location data of both nodes. So the
// new `start` value will be the earlier of the two nodes `start` values, the
// new `end` value will be the later of the two nodes `end` values, etc.
mergeAstLocationData = function(nodeA, nodeB) {
return {
loc: {

View File

@@ -914,6 +914,7 @@ exports.PropertyName = class PropertyName extends Literal
isAssignable: YES
astType: -> 'Identifier'
astProperties: ->
name: @value
@@ -1041,6 +1042,7 @@ exports.Value = class Value extends Base
return base if not props and base instanceof Value
@base = base
@properties = props or []
@tag = tag
@[tag] = yes if tag
@isDefaultValue = isDefaultValue
# If this is a `@foo =` assignment, if there are comments on `@` move them
@@ -1095,8 +1097,8 @@ exports.Value = class Value extends Base
@base.hasElision()
isSplice: ->
[..., lastProp] = @properties
lastProp instanceof Slice
[..., lastProperty] = @properties
lastProperty instanceof Slice
looksStatic: (className) ->
(@this or @base instanceof ThisLiteral or @base.value is className) and
@@ -1150,22 +1152,6 @@ exports.Value = class Value extends Base
fragments
ast: ->
ret = @base.ast()
for prop, propIndex in @properties
ret =
type: 'MemberExpression'
object: ret
property: prop.ast()
computed: prop instanceof Index or prop.name?.unwrap() not instanceof PropertyName
optional: !!prop.soak
shorthand: !!prop.shorthand
# When the `Value` has properties, the location data of a `MemberExpression` AST node
# corresponding to a given property should span the location of the `Value`'s `base`
# (including parens if present) through the property's location
Object.assign ret, mergeAstLocationData(@base.astLocationData(), prop.astLocationData())
ret
checkNewTarget: (o) ->
return unless @base instanceof IdentifierLiteral and @base.value is 'new' and @properties.length
if @properties[0] instanceof Access and @properties[0].name.value is 'target'
@@ -1200,6 +1186,52 @@ exports.Value = class Value extends Base
else
@error 'tried to assign to unassignable value'
# For AST generation, we need an `object` thats this `Value` minus its last
# property, if it has properties.
object: ->
return @ unless @hasProperties()
# Get all properties except the last one; for a `Value` with only one
# property, `initialProperties` is an empty array.
initialProperties = @properties[0...@properties.length - 1]
# Create the `object` that becomes the new “base” for the split-off final
# property.
object = new Value @base, initialProperties, @tag, @isDefaultValue
# Add location data to our new node, so that it has correct location data
# for source maps or later conversion into AST location data.
object.locationData =
if initialProperties.length is 0
# This new `Value` has only one property, so the location data is just
# that of the parent `Value`s base.
@base.locationData
else
# This new `Value` has multiple properties, so the location data spans
# from the parent `Value`s base to the last property thats included
# in this new node (a.k.a. the second-to-last property of the parent).
mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
object
ast: ->
# If the `Value` has no properties, the AST node is just whatever this
# nodes `base` is.
return @base.ast() unless @hasProperties()
# Otherwise, call `Base::ast` which in turn calls the `astType` and
# `astProperties` methods below.
super()
astType: -> 'MemberExpression'
# If this `Value` has properties, the *last* property (e.g. `c` in `a.b.c`)
# becomes the `property`, and the preceding properties (e.g. `a.b`) become
# a child `Value` node assigned to the `object` property.
astProperties: ->
[..., property] = @properties
return
object: @object().ast()
property: property.ast()
computed: property instanceof Index or property.name?.unwrap() not instanceof PropertyName
optional: !!property.soak
shorthand: !!property.shorthand
#### HereComment
# Comment delimited by `###` (becoming `/* */`).
@@ -1498,11 +1530,14 @@ exports.Access = class Access extends Base
else
[@makeCode('['), name..., @makeCode(']')]
ast: ->
@name.ast()
shouldCache: NO
ast: ->
# 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()
#### Index
# A `[ ... ]` indexed access into an array or object.
@@ -1515,12 +1550,17 @@ exports.Index = class Index extends Base
compileToFragments: (o) ->
[].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
ast: ->
@index.ast()
shouldCache: ->
@index.shouldCache()
ast: ->
# 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()
#### Range
# A range literal. Ranges can be used to extract portions (slices) of arrays,
@@ -3643,8 +3683,7 @@ exports.Parens = class Parens extends Base
return @wrapInBraces fragments if @csxAttribute
if bare then fragments else @wrapInParentheses fragments
ast: ->
@body.unwrap().ast()
ast: -> @body.unwrap().ast()
#### StringWithInterpolations
@@ -4163,12 +4202,29 @@ makeDelimitedLiteral = (body, options = {}) ->
when other then (if options.double then "\\#{other}" else other)
"#{options.delimiter}#{body}#{options.delimiter}"
# Helpers for `mergeLocationData` and `mergeAstLocationData` below.
lesser = (a, b) -> if a < b then a else b
greater = (a, b) -> if a > b then a else b
# Take two nodes location data and return a new `locationData` object that
# encompasses the location data of both nodes. So the new `first_line` value
# will be the earlier of the two nodes `first_line` values, the new
# `last_column` the later of the two nodes `last_column` values, etc.
mergeLocationData = (locationDataA, locationDataB) ->
return
first_line: lesser locationDataA.first_line, locationDataB.first_line
first_column: lesser locationDataA.first_column, locationDataB.first_column
last_line: greater locationDataA.last_line, locationDataB.last_line
last_column: greater locationDataA.last_column, locationDataB.last_column
range: [
lesser locationDataA.range[0], locationDataB.range[0]
greater locationDataA.range[1], locationDataB.range[1]
]
# Take two AST nodes, or two AST nodes location data objects, and return a new
# location data object that encompasses the location data of both nodes. So the
# new `start` value will be the earlier of the two nodes `start` values, the
# new `end` value will be the later of the two nodes `end` values, etc.
lesser = (a, b) -> if a < b then a else b
greater = (a, b) -> if a > b then a else b
mergeAstLocationData = (nodeA, nodeB) ->
return
loc:

View File

@@ -1113,11 +1113,10 @@ test "AST as expected for Index node", ->
# # NOTE: Soaking is covered in `Call` and `Access` nodes.
# test "AST as expected for Parens node", ->
# testExpression '(hmmmmm)',
# type: 'Parens',
# body:
# type: 'Value'
test "AST as expected for Parens node", ->
testExpression '(hmmmmm)',
type: 'Identifier'
name: 'hmmmmm'
# testExpression '(a + b) / c',
# type: 'Op'
@@ -1128,19 +1127,9 @@ test "AST as expected for Index node", ->
# type: 'Op'
# operator: '+'
# testExpression '(((1)))',
# type: 'Parens',
# body:
# type: 'Value'
# base:
# type: 'Block'
# expressions: [
# type: 'Parens',
# body:
# type: 'Value'
# base:
# value: '1'
# ]
testExpression '(((1)))',
type: 'NumericLiteral'
value: 1
# test "AST as expected for StringWithInterpolations node", ->
# testExpression '"#{o}/"',

View File

@@ -407,3 +407,39 @@ test "AST location data as expected for Index node", ->
end:
line: 1
column: 8
test "AST location data as expected for Parens node", ->
testAstLocationData '(hmmmmm)',
type: 'Identifier'
start: 1
end: 7
range: [1, 7]
loc:
start:
line: 1
column: 1
end:
line: 1
column: 7
# testExpression '(a + b) / c',
# type: 'Op'
# operator: '/'
# first:
# type: 'Parens'
# body:
# type: 'Op'
# operator: '+'
testAstLocationData '(((1)))',
type: 'NumericLiteral'
start: 3
end: 4
range: [3, 4]
loc:
start:
line: 1
column: 3
end:
line: 1
column: 4