Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CS2] Throw an error for ambiguous get or set keywords or function calls #4484

Merged
merged 10 commits into from
Apr 9, 2017
52 changes: 33 additions & 19 deletions lib/coffeescript/lexer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 21 additions & 6 deletions src/lexer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ exports.Lexer = class Lexer
@token 'DEFAULT', id
return id.length

[..., prev] = @tokens
prev = @prev()

tag =
if colon or prev? and
Expand Down Expand Up @@ -170,6 +170,16 @@ exports.Lexer = class Lexer
isForFrom(prev)
tag = 'FORFROM'
@seenFor = no
# Throw an error on attempts to use `get` or `set` as keywords, or
# what CoffeeScript would normally interpret as calls to functions named
# `get` or `set`, i.e. `get({foo: function () {}})`
else if tag is 'PROPERTY' and prev
if prev.spaced and prev[0] in CALLABLE and /^[gs]et$/.test(prev[1])
@error "'#{prev[1]}' cannot be used as a keyword, or as a function call without parentheses", prev[2]
else
prevprev = @tokens[@tokens.length - 2]
if prev[0] in ['@', 'THIS'] and prevprev and prevprev.spaced and /^[gs]et$/.test(prevprev[1])
@error "'#{prevprev[1]}' cannot be used as a keyword, or as a function call without parentheses", prevprev[2]

if tag is 'IDENTIFIER' and id in RESERVED
@error "reserved word '#{id}'", length: id.length
Expand Down Expand Up @@ -237,8 +247,9 @@ exports.Lexer = class Lexer

# If the preceding token is `from` and this is an import or export statement,
# properly tag the `from`.
if @tokens.length and @value() is 'from' and (@seenImport or @seenExport)
@tokens[@tokens.length - 1][0] = 'FROM'
prev = @prev()
if prev and @value() is 'from' and (@seenImport or @seenExport)
prev[0] = 'FROM'

regex = switch quote
when "'" then STRING_SINGLE
Expand Down Expand Up @@ -318,7 +329,7 @@ exports.Lexer = class Lexer
[regex, body, closed] = match
@validateEscapes body, isRegex: yes, offsetInChunk: 1
index = regex.length
[..., prev] = @tokens
prev = @prev()
if prev
if prev.spaced and prev[0] in CALLABLE
return 0 if not closed or POSSIBLY_DIVISION.test regex
Expand Down Expand Up @@ -443,7 +454,7 @@ exports.Lexer = class Lexer
whitespaceToken: ->
return 0 unless (match = WHITESPACE.exec @chunk) or
(nline = @chunk.charAt(0) is '\n')
[..., prev] = @tokens
prev = @prev()
prev[if match then 'spaced' else 'newLine'] = true if prev
if match then match[0].length else 0

Expand Down Expand Up @@ -471,7 +482,7 @@ exports.Lexer = class Lexer
else
value = @chunk.charAt 0
tag = value
[..., prev] = @tokens
prev = @prev()

if prev and value in ['=', COMPOUND_ASSIGN...]
skipToken = false
Expand Down Expand Up @@ -761,6 +772,10 @@ exports.Lexer = class Lexer
[..., token] = @tokens
token?[1]

# Get the previous token in the token stream.
prev: ->
@tokens[@tokens.length - 1]

# Are we in the midst of an unfinished expression?
unfinished: ->
LINE_CONTINUER.test(@chunk) or
Expand Down
2 changes: 1 addition & 1 deletion src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ exports.Call = class Call extends Base
constructor: (@variable, @args = [], @soak) ->
super()

@isNew = false
@isNew = no
if @variable instanceof Value and @variable.isNotCallable()
@variable.error "literal is not a function"

Expand Down
136 changes: 136 additions & 0 deletions test/error_messages.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -1340,3 +1340,139 @@ test "new with 'super'", ->
class extends A then foo: -> new super()
^^^^^
'''

test "getter keyword in object", ->
assertErrorFormat '''
obj =
get foo: ->
''', '''
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get foo: ->
^^^
'''

test "setter keyword in object", ->
assertErrorFormat '''
obj =
set foo: ->
''', '''
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
set foo: ->
^^^
'''

test "getter keyword in inline implicit object", ->
assertErrorFormat 'obj = get foo: ->', '''
[stdin]:1:7: error: 'get' cannot be used as a keyword, or as a function call without parentheses
obj = get foo: ->
^^^
'''

test "setter keyword in inline implicit object", ->
assertErrorFormat 'obj = set foo: ->', '''
[stdin]:1:7: error: 'set' cannot be used as a keyword, or as a function call without parentheses
obj = set foo: ->
^^^
'''

test "getter keyword in inline explicit object", ->
assertErrorFormat 'obj = {get foo: ->}', '''
[stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
obj = {get foo: ->}
^^^
'''

test "setter keyword in inline explicit object", ->
assertErrorFormat 'obj = {set foo: ->}', '''
[stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
obj = {set foo: ->}
^^^
'''

test "getter keyword in function", ->
assertErrorFormat '''
f = ->
get foo: ->
''', '''
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get foo: ->
^^^
'''

test "setter keyword in function", ->
assertErrorFormat '''
f = ->
set foo: ->
''', '''
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
set foo: ->
^^^
'''

test "getter keyword in inline function", ->
assertErrorFormat 'f = -> get foo: ->', '''
[stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
f = -> get foo: ->
^^^
'''

test "setter keyword in inline function", ->
assertErrorFormat 'f = -> set foo: ->', '''
[stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
f = -> set foo: ->
^^^
'''

test "getter keyword in class", ->
assertErrorFormat '''
class A
get foo: ->
''', '''
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get foo: ->
^^^
'''

test "setter keyword in class", ->
assertErrorFormat '''
class A
set foo: ->
''', '''
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
set foo: ->
^^^
'''

test "getter keyword in inline class", ->
assertErrorFormat 'class A then get foo: ->', '''
[stdin]:1:14: error: 'get' cannot be used as a keyword, or as a function call without parentheses
class A then get foo: ->
^^^
'''

test "setter keyword in inline class", ->
assertErrorFormat 'class A then set foo: ->', '''
[stdin]:1:14: error: 'set' cannot be used as a keyword, or as a function call without parentheses
class A then set foo: ->
^^^
'''

test "getter keyword before static method", ->
assertErrorFormat '''
class A
get @foo = ->
''', '''
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
get @foo = ->
^^^
'''

test "setter keyword before static method", ->
assertErrorFormat '''
class A
set @foo = ->
''', '''
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
set @foo = ->
^^^
'''
Loading