MetaProperty AST (new.target) (#5170)
* updated grammar * updated grammar * passing tests * test errors ast
This commit is contained in:
parent
99b7826f1b
commit
3f5abb3c60
|
@ -657,6 +657,10 @@
|
|||
}),
|
||||
o('This'),
|
||||
o('Super',
|
||||
function() {
|
||||
return new Value($1);
|
||||
}),
|
||||
o('MetaProperty',
|
||||
function() {
|
||||
return new Value($1);
|
||||
})
|
||||
|
@ -678,6 +682,14 @@
|
|||
$1);
|
||||
})
|
||||
],
|
||||
// A "meta-property" access e.g. `new.target`
|
||||
MetaProperty: [
|
||||
o('NEW_TARGET . Property',
|
||||
function() {
|
||||
return new MetaProperty(LOC(1)(new IdentifierLiteral($1)),
|
||||
LOC(3)(new Access($3)));
|
||||
})
|
||||
],
|
||||
// The general group of accessors into an object, by property, by prototype
|
||||
// or by array index or slice.
|
||||
Accessor: [
|
||||
|
|
|
@ -211,7 +211,7 @@
|
|||
if (prev.spaced && (ref9 = prev[0], indexOf.call(CALLABLE, ref9) >= 0) && /^[gs]et$/.test(prev[1]) && this.tokens.length > 1 && ((ref10 = this.tokens[this.tokens.length - 2][0]) !== '.' && ref10 !== '?.' && ref10 !== '@')) {
|
||||
this.error(`'${prev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prev[2]);
|
||||
} else if (prev[0] === '.' && this.tokens.length > 1 && (prevprev = this.tokens[this.tokens.length - 2])[0] === 'UNARY' && prevprev[1] === 'new') {
|
||||
prevprev[0] = 'IDENTIFIER';
|
||||
prevprev[0] = 'NEW_TARGET';
|
||||
} else if (this.tokens.length > 2) {
|
||||
prevprev = this.tokens[this.tokens.length - 2];
|
||||
if (((ref11 = prev[0]) === '@' || ref11 === 'THIS') && prevprev && prevprev.spaced && /^[gs]et$/.test(prevprev[1]) && ((ref12 = this.tokens[this.tokens.length - 3][0]) !== '.' && ref12 !== '?.' && ref12 !== '@')) {
|
||||
|
|
|
@ -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, Catch, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, 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, SwitchCase, SwitchWhen, 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, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, 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, Catch, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, 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, MetaProperty, 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, SwitchCase, SwitchWhen, 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, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, 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;
|
||||
|
@ -1893,7 +1893,6 @@
|
|||
// evaluate anything twice when building the soak chain.
|
||||
compileNode(o) {
|
||||
var fragments, j, len1, prop, props;
|
||||
this.checkNewTarget(o);
|
||||
this.base.front = this.front;
|
||||
props = this.properties;
|
||||
if (props.length && (this.base.cached != null)) {
|
||||
|
@ -1917,19 +1916,6 @@
|
|||
return fragments;
|
||||
}
|
||||
|
||||
checkNewTarget(o) {
|
||||
if (!(this.base instanceof IdentifierLiteral && this.base.value === 'new' && this.properties.length)) {
|
||||
return;
|
||||
}
|
||||
if (this.properties[0] instanceof Access && this.properties[0].name.value === 'target') {
|
||||
if (o.scope.parent == null) {
|
||||
return this.error("new.target can only occur inside functions");
|
||||
}
|
||||
} else {
|
||||
return this.error("the only valid meta property for new is new.target");
|
||||
}
|
||||
}
|
||||
|
||||
// Unfold a soak into an `If`: `a?.b` -> `a.b if a?`
|
||||
unfoldSoak(o) {
|
||||
return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (() => {
|
||||
|
@ -2048,6 +2034,51 @@
|
|||
|
||||
}).call(this);
|
||||
|
||||
exports.MetaProperty = MetaProperty = (function() {
|
||||
class MetaProperty extends Base {
|
||||
constructor(meta, property1) {
|
||||
super();
|
||||
this.meta = meta;
|
||||
this.property = property1;
|
||||
}
|
||||
|
||||
checkValid(o) {
|
||||
if (this.meta.value === 'new') {
|
||||
if (this.property instanceof Access && this.property.name.value === 'target') {
|
||||
if (o.scope.parent == null) {
|
||||
return this.error("new.target can only occur inside functions");
|
||||
}
|
||||
} else {
|
||||
return this.error("the only valid meta property for new is new.target");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileNode(o) {
|
||||
var fragments;
|
||||
this.checkValid(o);
|
||||
fragments = [];
|
||||
fragments.push(...this.meta.compileToFragments(o, LEVEL_ACCESS));
|
||||
fragments.push(...this.property.compileToFragments(o));
|
||||
return fragments;
|
||||
}
|
||||
|
||||
astProperties(o) {
|
||||
this.checkValid(o);
|
||||
return {
|
||||
meta: this.meta.ast(o, LEVEL_ACCESS),
|
||||
property: this.property.ast(o)
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
MetaProperty.prototype.children = ['meta', 'property'];
|
||||
|
||||
return MetaProperty;
|
||||
|
||||
}).call(this);
|
||||
|
||||
//### HereComment
|
||||
|
||||
// Comment delimited by `###` (becoming `/* */`).
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -386,6 +386,7 @@ grammar =
|
|||
o 'DoIife', -> new Value $1
|
||||
o 'This'
|
||||
o 'Super', -> new Value $1
|
||||
o 'MetaProperty', -> new Value $1
|
||||
]
|
||||
|
||||
# A `super`-based expression that can be used as a value.
|
||||
|
@ -394,6 +395,11 @@ grammar =
|
|||
o 'SUPER INDEX_START Expression INDEX_END', -> new Super LOC(3)(new Index $3), [], no, $1
|
||||
]
|
||||
|
||||
# A "meta-property" access e.g. `new.target`
|
||||
MetaProperty: [
|
||||
o 'NEW_TARGET . Property', -> new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)
|
||||
]
|
||||
|
||||
# The general group of accessors into an object, by property, by prototype
|
||||
# or by array index or slice.
|
||||
Accessor: [
|
||||
|
|
|
@ -201,7 +201,7 @@ exports.Lexer = class Lexer
|
|||
@error "'#{prev[1]}' cannot be used as a keyword, or as a function call
|
||||
without parentheses", prev[2]
|
||||
else if prev[0] is '.' and @tokens.length > 1 and (prevprev = @tokens[@tokens.length - 2])[0] is 'UNARY' and prevprev[1] is 'new'
|
||||
prevprev[0] = 'IDENTIFIER'
|
||||
prevprev[0] = 'NEW_TARGET'
|
||||
else if @tokens.length > 2
|
||||
prevprev = @tokens[@tokens.length - 2]
|
||||
if prev[0] in ['@', 'THIS'] and prevprev and prevprev.spaced and
|
||||
|
|
|
@ -1264,7 +1264,6 @@ exports.Value = class Value extends Base
|
|||
# operators `?.` interspersed. Then we have to take care not to accidentally
|
||||
# evaluate anything twice when building the soak chain.
|
||||
compileNode: (o) ->
|
||||
@checkNewTarget o
|
||||
@base.front = @front
|
||||
props = @properties
|
||||
if props.length and @base.cached?
|
||||
|
@ -1284,14 +1283,6 @@ exports.Value = class Value extends Base
|
|||
|
||||
fragments
|
||||
|
||||
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'
|
||||
unless o.scope.parent?
|
||||
@error "new.target can only occur inside functions"
|
||||
else
|
||||
@error "the only valid meta property for new is new.target"
|
||||
|
||||
# Unfold a soak into an `If`: `a?.b` -> `a.b if a?`
|
||||
unfoldSoak: (o) ->
|
||||
@unfoldedSoak ?= do =>
|
||||
|
@ -1377,6 +1368,34 @@ exports.Value = class Value extends Base
|
|||
jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
|
||||
)
|
||||
|
||||
exports.MetaProperty = class MetaProperty extends Base
|
||||
constructor: (@meta, @property) ->
|
||||
super()
|
||||
|
||||
children: ['meta', 'property']
|
||||
|
||||
checkValid: (o) ->
|
||||
if @meta.value is 'new'
|
||||
if @property instanceof Access and @property.name.value is 'target'
|
||||
unless o.scope.parent?
|
||||
@error "new.target can only occur inside functions"
|
||||
else
|
||||
@error "the only valid meta property for new is new.target"
|
||||
|
||||
compileNode: (o) ->
|
||||
@checkValid o
|
||||
fragments = []
|
||||
fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
|
||||
fragments.push @property.compileToFragments(o)...
|
||||
fragments
|
||||
|
||||
astProperties: (o) ->
|
||||
@checkValid o
|
||||
|
||||
return
|
||||
meta: @meta.ast o, LEVEL_ACCESS
|
||||
property: @property.ast o
|
||||
|
||||
#### HereComment
|
||||
|
||||
# Comment delimited by `###` (becoming `/* */`).
|
||||
|
|
|
@ -2572,3 +2572,36 @@ test "AST as expected for If node", ->
|
|||
alternate: null
|
||||
postfix: yes
|
||||
inverted: yes
|
||||
|
||||
test "AST as expected for MetaProperty node", ->
|
||||
testExpression '''
|
||||
-> new.target
|
||||
''',
|
||||
type: 'FunctionExpression'
|
||||
body:
|
||||
type: 'BlockStatement'
|
||||
body: [
|
||||
type: 'ExpressionStatement'
|
||||
expression:
|
||||
type: 'MetaProperty'
|
||||
meta: ID 'new'
|
||||
property: ID 'target'
|
||||
]
|
||||
|
||||
testExpression '''
|
||||
-> new.target.name
|
||||
''',
|
||||
type: 'FunctionExpression'
|
||||
body:
|
||||
type: 'BlockStatement'
|
||||
body: [
|
||||
type: 'ExpressionStatement'
|
||||
expression:
|
||||
type: 'MemberExpression'
|
||||
object:
|
||||
type: 'MetaProperty'
|
||||
meta: ID 'new'
|
||||
property: ID 'target'
|
||||
property: ID 'name'
|
||||
computed: no
|
||||
]
|
||||
|
|
|
@ -4896,3 +4896,55 @@ test "AST as expected for While node", ->
|
|||
end:
|
||||
line: 3
|
||||
column: 5
|
||||
|
||||
test "AST location data as expected for MetaProperty node", ->
|
||||
testAstLocationData '''
|
||||
-> new.target
|
||||
''',
|
||||
type: 'FunctionExpression'
|
||||
body:
|
||||
body: [
|
||||
expression:
|
||||
meta:
|
||||
start: 3
|
||||
end: 6
|
||||
range: [3, 6]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 3
|
||||
end:
|
||||
line: 1
|
||||
column: 6
|
||||
property:
|
||||
start: 7
|
||||
end: 13
|
||||
range: [7, 13]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 7
|
||||
end:
|
||||
line: 1
|
||||
column: 13
|
||||
start: 3
|
||||
end: 13
|
||||
range: [3, 13]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 3
|
||||
end:
|
||||
line: 1
|
||||
column: 13
|
||||
]
|
||||
start: 0
|
||||
end: 13
|
||||
range: [0, 13]
|
||||
loc:
|
||||
start:
|
||||
line: 1
|
||||
column: 0
|
||||
end:
|
||||
line: 1
|
||||
column: 13
|
||||
|
|
|
@ -1487,15 +1487,6 @@ test "ES6 prototypes can be overriden", ->
|
|||
# # Should throw, but doesn't
|
||||
# a = new A
|
||||
|
||||
# TODO: new.target needs support Separate issue
|
||||
# test "ES6 support for new.target (functions and constructors)", ->
|
||||
# throwsA = """
|
||||
# class A
|
||||
# constructor: () ->
|
||||
# a = new.target.name
|
||||
# """
|
||||
# throws -> CoffeeScript.compile throwsA, bare: yes
|
||||
|
||||
test "only one method named constructor allowed", ->
|
||||
throwsA = """
|
||||
class A
|
||||
|
|
|
@ -1920,7 +1920,7 @@ test "#3933: prevent implicit calls when cotrol flow is missing `THEN`", ->
|
|||
'''
|
||||
|
||||
test "`new.target` outside of a function", ->
|
||||
assertErrorFormat '''
|
||||
assertErrorFormatAst '''
|
||||
new.target
|
||||
''', '''
|
||||
[stdin]:1:1: error: new.target can only occur inside functions
|
||||
|
@ -1929,10 +1929,20 @@ test "`new.target` outside of a function", ->
|
|||
'''
|
||||
|
||||
test "`new.target` is only allowed meta property", ->
|
||||
assertErrorFormat '''
|
||||
assertErrorFormatAst '''
|
||||
-> new.something
|
||||
''', '''
|
||||
[stdin]:1:4: error: the only valid meta property for new is new.target
|
||||
-> new.something
|
||||
^^^^^^^^^^^^^
|
||||
'''
|
||||
|
||||
test "`new.target` cannot be assigned", ->
|
||||
assertErrorFormatAst '''
|
||||
->
|
||||
new.target = b
|
||||
''', '''
|
||||
[stdin]:2:14: error: unexpected =
|
||||
new.target = b
|
||||
^
|
||||
'''
|
||||
|
|
Loading…
Reference in New Issue