MetaProperty AST (new.target) (#5170)

* updated grammar

* updated grammar

* passing tests

* test errors ast
This commit is contained in:
Julian Rosse 2019-03-16 21:51:39 -04:00 committed by Geoffrey Booth
parent 99b7826f1b
commit 3f5abb3c60
11 changed files with 355 additions and 197 deletions

View File

@ -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: [

View File

@ -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 !== '@')) {

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, 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

View File

@ -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: [

View File

@ -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

View File

@ -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 `/* */`).

View File

@ -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
]

View File

@ -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

View File

@ -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

View File

@ -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
^
'''