[CS2] Fix #4467: tagged template literal call (#4601)

* working on making Invocation a grammar Value

* cleanup

* update location when adding value properties

* test for #4467

* more location data tests
This commit is contained in:
Julian Rosse 2017-07-05 15:58:36 -05:00 committed by Geoffrey Booth
parent 56725ad275
commit ab52fd75c2
7 changed files with 274 additions and 234 deletions

View File

@ -48,7 +48,7 @@
return new StatementLiteral($1);
}), o('Import'), o('Export')
],
Expression: [o('Value'), o('Invocation'), o('Code'), o('Operation'), o('Assign'), o('If'), o('Try'), o('While'), o('For'), o('Switch'), o('Class'), o('Throw'), o('Yield')],
Expression: [o('Value'), o('Code'), o('Operation'), o('Assign'), o('If'), o('Try'), o('While'), o('For'), o('Switch'), o('Class'), o('Throw'), o('Yield')],
Yield: [
o('YIELD', function() {
return new Op($1, new Value(new Literal('')));
@ -242,8 +242,6 @@
return new Value($1);
}), o('Value Accessor', function() {
return $1.add($2);
}), o('Invocation Accessor', function() {
return new Value($1, [].concat($2));
}), o('ThisProperty')
],
Assignable: [
@ -260,7 +258,11 @@
return new Value($1);
}), o('Range', function() {
return new Value($1);
}), o('This'), o('Super')
}), o('Invocation', function() {
return new Value($1);
}), o('This'), o('Super', function() {
return new Value($1);
})
],
Super: [
o('SUPER . Property', function() {
@ -444,8 +446,6 @@
return new TaggedTemplateCall($1, $3, $2);
}), o('Value OptFuncExist Arguments', function() {
return new Call($1, $3, $2);
}), o('Invocation OptFuncExist Arguments', function() {
return new Call($1, $3, $2);
}), o('SUPER OptFuncExist Arguments', function() {
return new SuperCall(LOC(1)(new Super), $3, $2);
})

View File

@ -276,9 +276,10 @@
}
updateLocationDataIfMissing(locationData) {
if (this.locationData) {
if (this.locationData && !this.forceUpdateLocation) {
return this;
}
delete this.forceUpdateLocation;
this.locationData = locationData;
return this.eachChild(function(child) {
return child.updateLocationDataIfMissing(locationData);
@ -536,15 +537,15 @@
}
return results;
}).call(this);
rest = this.expressions.slice(preludeExps.length);
this.expressions = preludeExps;
if (preludeExps.length) {
rest = this.expressions.slice(preludeExps.length);
this.expressions = preludeExps;
prelude = this.compileNode(merge(o, {
indent: ''
}));
prelude.push(this.makeCode("\n"));
this.expressions = rest;
}
this.expressions = rest;
}
fragments = this.compileWithDeclarations(o);
HoistTarget.expand(fragments);
@ -855,6 +856,7 @@
add(props) {
this.properties = this.properties.concat(props);
this.forceUpdateLocation = true;
return this;
}
@ -3393,9 +3395,7 @@
if (res) {
return super.makeReturn(res);
} else {
this.returns = !this.jumps({
loop: true
});
this.returns = !this.jumps();
return this;
}
}
@ -3467,6 +3467,7 @@
class Op extends Base {
constructor(op, first, second, flip) {
var firstCall;
super();
if (op === 'in') {
return new In(first, second);
@ -3475,8 +3476,8 @@
return Op.prototype.generateDo(first);
}
if (op === 'new') {
if (first instanceof Call && !first.do && !first.isNew) {
return first.newInstance();
if ((firstCall = first.unwrap()) instanceof Call && !firstCall.do && !firstCall.isNew) {
return firstCall.newInstance();
}
if (first instanceof Code && first.bound || first.do) {
first = new Parens(first);
@ -3945,7 +3946,7 @@
return expr.compileToFragments(o);
}
fragments = expr.compileToFragments(o, LEVEL_PAREN);
bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)) && (o.level < LEVEL_COND || fragments.length <= 3);
bare = o.level < LEVEL_OP && (expr instanceof Op || expr.unwrap() instanceof Call || (expr instanceof For && expr.returns)) && (o.level < LEVEL_COND || fragments.length <= 3);
if (this.csxAttribute) {
return this.wrapInBraces(fragments);
}
@ -4035,8 +4036,8 @@
isNestedTag(element) {
var call, exprs, ref1;
exprs = element != null ? (ref1 = element.body) != null ? ref1.expressions : void 0 : void 0;
call = exprs != null ? exprs[0] : void 0;
exprs = (ref1 = element.body) != null ? ref1.expressions : void 0;
call = exprs != null ? exprs[0].unwrap() : void 0;
return this.csx && exprs && exprs.length === 1 && call instanceof Call && call.csx;
}

File diff suppressed because one or more lines are too long

View File

@ -112,7 +112,6 @@ grammar =
# them somewhat circular.
Expression: [
o 'Value'
o 'Invocation'
o 'Code'
o 'Operation'
o 'Assign'
@ -313,7 +312,6 @@ grammar =
SimpleAssignable: [
o 'Identifier', -> new Value $1
o 'Value Accessor', -> $1.add $2
o 'Invocation Accessor', -> new Value $1, [].concat $2
o 'ThisProperty'
]
@ -331,8 +329,9 @@ grammar =
o 'Literal', -> new Value $1
o 'Parenthetical', -> new Value $1
o 'Range', -> new Value $1
o 'Invocation', -> new Value $1
o 'This'
o 'Super'
o 'Super', -> new Value $1
]
# A `super`-based expression that can be used as a value.
@ -459,7 +458,6 @@ grammar =
Invocation: [
o 'Value OptFuncExist String', -> new TaggedTemplateCall $1, $3, $2
o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
o 'Invocation OptFuncExist Arguments', -> new Call $1, $3, $2
o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2
]

View File

@ -266,7 +266,8 @@ exports.Base = class Base
# For this node and all descendents, set the location data to `locationData`
# if the location data is not already set.
updateLocationDataIfMissing: (locationData) ->
return this if @locationData
return this if @locationData and not @forceUpdateLocation
delete @forceUpdateLocation
@locationData = locationData
@eachChild (child) ->
@ -286,11 +287,11 @@ exports.Base = class Base
[@makeCode('{'), fragments..., @makeCode('}')]
# `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be
# concatonated together, with `joinStr` added in between each, to produce a final flat array
# concatenated together, with `joinStr` added in between each, to produce a final flat array
# of fragments.
joinFragmentArrays: (fragmentsList, joinStr) ->
answer = []
for fragments,i in fragmentsList
for fragments, i in fragmentsList
if i then answer.push @makeCode joinStr
answer = answer.concat fragments
answer
@ -453,12 +454,12 @@ exports.Block = class Block extends Base
preludeExps = for exp, i in @expressions
break unless exp.unwrap() instanceof Comment
exp
rest = @expressions[preludeExps.length...]
@expressions = preludeExps
if preludeExps.length
rest = @expressions[preludeExps.length...]
@expressions = preludeExps
prelude = @compileNode merge(o, indent: '')
prelude.push @makeCode "\n"
@expressions = rest
@expressions = rest
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
return fragments if o.bare
@ -659,6 +660,7 @@ exports.Value = class Value extends Base
# Add a property (or *properties* ) `Access` to the list.
add: (props) ->
@properties = @properties.concat props
@forceUpdateLocation = yes
this
hasProperties: ->
@ -2585,7 +2587,7 @@ exports.While = class While extends Base
if res
super res
else
@returns = not @jumps loop: yes
@returns = not @jumps()
this
addBody: (@body) ->
@ -2635,7 +2637,8 @@ exports.Op = class Op extends Base
if op is 'do'
return Op::generateDo first
if op is 'new'
return first.newInstance() if first instanceof Call and not first.do and not first.isNew
if (firstCall = first.unwrap()) instanceof Call and not firstCall.do and not firstCall.isNew
return firstCall.newInstance()
first = new Parens first if first instanceof Code and first.bound or first.do
@operator = CONVERSIONS[op] or op
@ -2984,7 +2987,7 @@ exports.Parens = class Parens extends Base
expr.front = @front
return expr.compileToFragments o
fragments = expr.compileToFragments o, LEVEL_PAREN
bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
bare = o.level < LEVEL_OP and (expr instanceof Op or expr.unwrap() instanceof Call or
(expr instanceof For and expr.returns)) and (o.level < LEVEL_COND or
fragments.length <= 3)
return @wrapInBraces fragments if @csxAttribute
@ -3046,8 +3049,8 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base
fragments
isNestedTag: (element) ->
exprs = element?.body?.expressions
call = exprs?[0]
exprs = element.body?.expressions
call = exprs?[0].unwrap()
@csx and exprs and exprs.length is 1 and call instanceof Call and call.csx
#### For

View File

@ -613,3 +613,38 @@ test "Verify all tokens get a location", ->
tokens = CoffeeScript.tokens testScript
for token in tokens
ok !!token[2]
test 'Values with properties end up with a location that includes the properties', ->
source = '''
a.b
a.b.c
a['b']
a[b.c()].d
'''
block = CoffeeScript.nodes source
[
singleProperty
twoProperties
indexed
complexIndex
] = block.expressions
eq singleProperty.locationData.first_line, 0
eq singleProperty.locationData.first_column, 0
eq singleProperty.locationData.last_line, 0
eq singleProperty.locationData.last_column, 2
eq twoProperties.locationData.first_line, 1
eq twoProperties.locationData.first_column, 0
eq twoProperties.locationData.last_line, 1
eq twoProperties.locationData.last_column, 4
eq indexed.locationData.first_line, 2
eq indexed.locationData.first_column, 0
eq indexed.locationData.last_line, 2
eq indexed.locationData.last_column, 5
eq complexIndex.locationData.first_line, 3
eq complexIndex.locationData.first_column, 0
eq complexIndex.locationData.last_line, 3
eq complexIndex.locationData.last_column, 9

View File

@ -18,6 +18,7 @@ func = (text, expressions...) ->
outerobj =
obj:
func: func
f: -> func
# Example use
test "tagged template literal for html templating", ->
@ -169,3 +170,8 @@ test "tagged template literal with unnecessarily escaped ES interpolation", ->
test "tagged template literal special escaping", ->
eq 'text: [` ` \\` \\` \\\\` $ { ${ ${ \\${ \\${ \\\\${ | ` ${] expressions: [1]',
func"` \` \\` \\\` \\\\` $ { ${ \${ \\${ \\\${ \\\\${ #{1} ` ${"
test '#4467: tagged template literal call recognized as a callable function', ->
eq 'text: [dot notation] expressions: []',
outerobj.obj.f()'dot notation'