Skip to content

Commit 09caa21

Browse files
helixbassGeoffreyBooth
authored andcommitted
AST: test compile errors (#5260)
* multiple splats error * delete operand; catch variable * disallow multiple splats * unassignable conditional * lone expansion param * class bodies pure statements/arguments * remove duplicate * getAndCheckSplatProps() * getAndCheckSplatsAndExpansions() * clean up disallowLoneExpansionAndMultipleSplats()
1 parent a35df43 commit 09caa21

21 files changed

+448
-281
lines changed

lib/coffeescript/nodes.js

Lines changed: 180 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nodes.coffee

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,8 +2467,7 @@ exports.Obj = class Obj extends Base
24672467
# `foo = ({a, rest..., b}) ->` -> `foo = {a, b, rest...}) ->`
24682468
reorderProperties: ->
24692469
props = @properties
2470-
splatProps = (i for prop, i in props when prop instanceof Splat)
2471-
props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
2470+
splatProps = @getAndCheckSplatProps()
24722471
splatProp = props.splice splatProps[0], 1
24732472
@objects = @properties = [].concat props, splatProp
24742473

@@ -2538,6 +2537,13 @@ exports.Obj = class Obj extends Base
25382537
answer = @wrapInBraces answer
25392538
if @front then @wrapInParentheses answer else answer
25402539

2540+
getAndCheckSplatProps: ->
2541+
return unless @hasSplat() and @lhs
2542+
props = @properties
2543+
splatProps = (i for prop, i in props when prop instanceof Splat)
2544+
props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
2545+
splatProps
2546+
25412547
assigns: (name) ->
25422548
for prop in @properties when prop.assigns name then return yes
25432549
no
@@ -2590,6 +2596,10 @@ exports.Obj = class Obj extends Base
25902596
else if property instanceof Splat
25912597
property.lhs = yes
25922598

2599+
astNode: (o) ->
2600+
@getAndCheckSplatProps()
2601+
super o
2602+
25932603
astType: ->
25942604
if @lhs
25952605
'ObjectPattern'
@@ -2731,7 +2741,7 @@ exports.Arr = class Arr extends Base
27312741
@lhs = yes if setLhs
27322742
return unless @lhs
27332743
for object in @objects
2734-
object.lhs = yes if object instanceof Splat
2744+
object.lhs = yes if object instanceof Splat or object instanceof Expansion
27352745
unwrappedObject = object.unwrapAll()
27362746
if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
27372747
unwrappedObject.propagateLhs yes
@@ -2995,6 +3005,10 @@ exports.Class = class Class extends Base
29953005
isStatementAst: -> yes
29963006

29973007
astNode: (o) ->
3008+
if jumpNode = @body.jumps()
3009+
jumpNode.error 'Class bodies cannot contain pure statements'
3010+
if argumentsNode = @body.contains isLiteralArguments
3011+
argumentsNode.error "Class bodies shouldn't reference arguments"
29983012
@declareName o
29993013
@name = @determineName()
30003014
@body.isClassBody = yes
@@ -3502,7 +3516,7 @@ exports.Assign = class Assign extends Base
35023516
return @compileDestructuring o
35033517

35043518
return @compileSplice o if @variable.isSplice()
3505-
return @compileConditional o if @context in ['||=', '&&=', '?=']
3519+
return @compileConditional o if @isConditional()
35063520
return @compileSpecialMath o if @context in ['//=', '%%=']
35073521

35083522
@addScopeVariables o
@@ -3561,18 +3575,7 @@ exports.Assign = class Assign extends Base
35613575
[obj] = objects
35623576

35633577
@disallowLoneExpansion()
3564-
3565-
# Count all `Splats`: [a, b, c..., d, e]
3566-
splats = (i for obj, i in objects when obj instanceof Splat)
3567-
# Count all `Expansions`: [a, b, ..., c, d]
3568-
expans = (i for obj, i in objects when obj instanceof Expansion)
3569-
# Combine splats and expansions.
3570-
splatsAndExpans = [splats..., expans...]
3571-
# Show error if there is more than one `Splat`, or `Expansion`.
3572-
# Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
3573-
if splatsAndExpans.length > 1
3574-
# Sort 'splatsAndExpans' so we can show error at first disallowed token.
3575-
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
3578+
{splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
35763579

35773580
isSplat = splats?.length > 0
35783581
isExpans = expans?.length > 0
@@ -3710,6 +3713,23 @@ exports.Assign = class Assign extends Base
37103713
if loneObject instanceof Expansion
37113714
loneObject.error 'Destructuring assignment has no target'
37123715

3716+
# Show error if there is more than one `Splat`, or `Expansion`.
3717+
# Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
3718+
getAndCheckSplatsAndExpansions: ->
3719+
return {splats: [], expans: [], splatsAndExpans: []} unless @variable.base instanceof Arr
3720+
{objects} = @variable.base
3721+
3722+
# Count all `Splats`: [a, b, c..., d, e]
3723+
splats = (i for obj, i in objects when obj instanceof Splat)
3724+
# Count all `Expansions`: [a, b, ..., c, d]
3725+
expans = (i for obj, i in objects when obj instanceof Expansion)
3726+
# Combine splats and expansions.
3727+
splatsAndExpans = [splats..., expans...]
3728+
if splatsAndExpans.length > 1
3729+
# Sort 'splatsAndExpans' so we can show error at first disallowed token.
3730+
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
3731+
{splats, expans, splatsAndExpans}
3732+
37133733
# When compiling a conditional assignment, take care to ensure that the
37143734
# operands are only evaluated once, even though we have to reference them
37153735
# more than once.
@@ -3718,7 +3738,7 @@ exports.Assign = class Assign extends Base
37183738
# Disallow conditional assignment of undefined variables.
37193739
if not left.properties.length and left.base instanceof Literal and
37203740
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
3721-
@variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
3741+
@throwUnassignableConditionalError left.base.value
37223742
if "?" in @context
37233743
o.isExistentialEquals = true
37243744
new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
@@ -3770,10 +3790,21 @@ exports.Assign = class Assign extends Base
37703790
# destructured variables.
37713791
@variable.base.propagateLhs yes
37723792

3793+
throwUnassignableConditionalError: (name) ->
3794+
@variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"
3795+
3796+
isConditional: ->
3797+
@context in ['||=', '&&=', '?=']
3798+
37733799
isStatementAst: NO
37743800

37753801
astNode: (o) ->
37763802
@disallowLoneExpansion()
3803+
@getAndCheckSplatsAndExpansions()
3804+
if @isConditional()
3805+
variable = @variable.unwrap()
3806+
if variable instanceof IdentifierLiteral and not o.scope.check variable.value
3807+
@throwUnassignableConditionalError variable.value
37773808
@addScopeVariables o, allowAssignmentToExpansion: yes
37783809
super o
37793810

@@ -3855,6 +3886,7 @@ exports.Code = class Code extends Base
38553886
haveBodyParam = no
38563887

38573888
@checkForDuplicateParams()
3889+
@disallowLoneExpansionAndMultipleSplats()
38583890

38593891
# Separate `this` assignments.
38603892
@eachParamName (name, node, param, obj) ->
@@ -3883,14 +3915,9 @@ exports.Code = class Code extends Base
38833915
# declare and assign all subsequent parameters in the function body so that
38843916
# any non-idempotent parameters are evaluated in the correct order.
38853917
for param, i in @params
3886-
# Was `...` used with this parameter? (Only one such parameter is allowed
3887-
# per function.) Splat/expansion parameters cannot have default values,
3888-
# so we need not worry about that.
3918+
# Was `...` used with this parameter? Splat/expansion parameters cannot
3919+
# have default values, so we need not worry about that.
38893920
if param.splat or param instanceof Expansion
3890-
if haveSplatParam
3891-
param.error 'only one splat or expansion parameter is allowed per function definition'
3892-
else if param instanceof Expansion and @params.length is 1
3893-
param.error 'an expansion parameter cannot be the only parameter in a function definition'
38943921
haveSplatParam = yes
38953922
if param.splat
38963923
if param.name instanceof Arr or param.name instanceof Obj
@@ -4102,6 +4129,18 @@ exports.Code = class Code extends Base
41024129
@name.error 'Class constructor may not be async' if @isAsync
41034130
@name.error 'Class constructor may not be a generator' if @isGenerator
41044131

4132+
disallowLoneExpansionAndMultipleSplats: ->
4133+
seenSplatParam = no
4134+
for param in @params
4135+
# Was `...` used with this parameter? (Only one such parameter is allowed
4136+
# per function.)
4137+
if param.splat or param instanceof Expansion
4138+
if seenSplatParam
4139+
param.error 'only one splat or expansion parameter is allowed per function definition'
4140+
else if param instanceof Expansion and @params.length is 1
4141+
param.error 'an expansion parameter cannot be the only parameter in a function definition'
4142+
seenSplatParam = yes
4143+
41054144
expandCtorSuper: (thisAssignments) ->
41064145
return false unless @ctor
41074146

@@ -4141,8 +4180,12 @@ exports.Code = class Code extends Base
41414180
seenSuper
41424181

41434182
propagateLhs: ->
4144-
for {name} in @params when name instanceof Arr or name instanceof Obj
4145-
name.propagateLhs yes
4183+
for param in @params
4184+
{name} = param
4185+
if name instanceof Arr or name instanceof Obj
4186+
name.propagateLhs yes
4187+
else if param instanceof Expansion
4188+
param.lhs = yes
41464189

41474190
astAddParamsToScope: (o) ->
41484191
@eachParamName (name) ->
@@ -4153,6 +4196,7 @@ exports.Code = class Code extends Base
41534196
@checkForAsyncOrGeneratorConstructor()
41544197
@checkForDuplicateParams()
41554198
@disallowSuperInParamDefaults forAst: yes
4199+
@disallowLoneExpansionAndMultipleSplats()
41564200
seenSuper = @checkSuperCallsInConstructorBody()
41574201
if @ctor is 'derived' and not seenSuper
41584202
@eachParamName (name, node) =>
@@ -4281,6 +4325,9 @@ exports.Param = class Param extends Base
42814325
literal.error "'#{literal.value}' can't be assigned"
42824326

42834327
atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
4328+
if name instanceof Call
4329+
name.error "Function invocation can't be assigned"
4330+
42844331
# * simple literals `foo`
42854332
if name instanceof Literal
42864333
checkAssignabilityOfLiteral name
@@ -4392,13 +4439,22 @@ exports.Expansion = class Expansion extends Base
43924439
shouldCache: NO
43934440

43944441
compileNode: (o) ->
4395-
@error 'Expansion must be used inside a destructuring assignment or parameter list'
4442+
@throwLhsError()
43964443

43974444
asReference: (o) ->
43984445
this
43994446

44004447
eachName: (iterator) ->
44014448

4449+
throwLhsError: ->
4450+
@error 'Expansion must be used inside a destructuring assignment or parameter list'
4451+
4452+
astNode: (o) ->
4453+
unless @lhs
4454+
@throwLhsError()
4455+
4456+
super o
4457+
44024458
astType: -> 'RestElement'
44034459

44044460
astProperties: ->
@@ -4630,8 +4686,7 @@ exports.Op = class Op extends Base
46304686
# In chains, there's no need to wrap bare obj literals in parens,
46314687
# as the chained expression is wrapped.
46324688
@first.front = @front unless isChain
4633-
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
4634-
@error 'delete operand may not be argument or var'
4689+
@checkDeleteOperand o
46354690
return @compileContinuation o if @isYield() or @isAwait()
46364691
return @compileUnary o if @isUnary()
46374692
return @compileChain o if isChain
@@ -4719,8 +4774,13 @@ exports.Op = class Op extends Base
47194774
toString: (idt) ->
47204775
super idt, @constructor.name + ' ' + @operator
47214776

4777+
checkDeleteOperand: (o) ->
4778+
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
4779+
@error 'delete operand may not be argument or var'
4780+
47224781
astNode: (o) ->
47234782
@checkContinuation o if @isYield() or @isAwait()
4783+
@checkDeleteOperand o
47244784
super o
47254785

47264786
astType: ->
@@ -4902,14 +4962,19 @@ exports.Catch = class Catch extends Base
49024962
o.indent += TAB
49034963
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
49044964
placeholder = new IdentifierLiteral generatedErrorVariableName
4965+
@checkUnassignable()
49054966
if @errorVariable
4906-
message = isUnassignable @errorVariable.unwrapAll().value
4907-
@errorVariable.error message if message
49084967
@recovery.unshift new Assign @errorVariable, placeholder
49094968
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
49104969
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
49114970

4971+
checkUnassignable: ->
4972+
if @errorVariable
4973+
message = isUnassignable @errorVariable.unwrapAll().value
4974+
@errorVariable.error message if message
4975+
49124976
astNode: (o) ->
4977+
@checkUnassignable()
49134978
@errorVariable?.eachName (name) ->
49144979
alreadyDeclared = o.scope.find name.value
49154980
name.isDeclaration = not alreadyDeclared

test/abstract_syntax_tree.coffee

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,17 +2281,22 @@ test "AST as expected for Assign node", ->
22812281
name: 'c'
22822282
declaration: no
22832283

2284-
testExpression 'a ?= b',
2285-
type: 'AssignmentExpression'
2286-
left:
2287-
type: 'Identifier'
2288-
name: 'a'
2289-
declaration: no
2290-
right:
2291-
type: 'Identifier'
2292-
name: 'b'
2293-
declaration: no
2294-
operator: '?='
2284+
testExpression '(a = 1; a ?= b)',
2285+
type: 'SequenceExpression'
2286+
expressions: [
2287+
type: 'AssignmentExpression'
2288+
,
2289+
type: 'AssignmentExpression'
2290+
left:
2291+
type: 'Identifier'
2292+
name: 'a'
2293+
declaration: no
2294+
right:
2295+
type: 'Identifier'
2296+
name: 'b'
2297+
declaration: no
2298+
operator: '?='
2299+
]
22952300

22962301
# # `FuncGlyph` node isn't exported.
22972302

0 commit comments

Comments
 (0)