mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Merge pull request #16 from GeoffreyBooth/value-ast-ast-methods
AST methods
This commit is contained in:
@@ -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` that’s 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 that’s 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
|
||||
// node’s `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 doesn’t have an AST node for `Access`, but rather just includes
|
||||
// this Access node’s 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 doesn’t have an AST node for `Index`, but rather just includes
|
||||
// this Index node’s 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: {
|
||||
|
||||
112
src/nodes.coffee
112
src/nodes.coffee
@@ -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` that’s 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 that’s 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
|
||||
# node’s `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 doesn’t have an AST node for `Access`, but rather just includes
|
||||
# this Access node’s 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 doesn’t have an AST node for `Index`, but rather just includes
|
||||
# this Index node’s 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:
|
||||
|
||||
@@ -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}/"',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user