Skip to content

Commit

Permalink
[CS2] Throw an error for ambiguous get or set keywords or functio…
Browse files Browse the repository at this point in the history
…n calls (#4484)

* Throw an error for ambiguous `get` or `set` function calls or ES5 getter/setter keywords, to warn the user to use parentheses if they intend a function call (or to inform them that `get` or `set` cannot be used as a keyword)

* Code golf

* Catch get or set keyword before static method

* DRY up getting the previous token

* Throw an error if get or set are used as keywords before what looks like a function or method with an interpolated/dynamic name

* Allow `get` or `set` parentheses-less function calls when first argument is a string without a colon (so a plain string, not a property accessor)

* Revert "Allow `get` or `set` parentheses-less function calls when first argument is a string without a colon (so a plain string, not a property accessor)"

This reverts commit 2d1addf.

* Optimization

* No longer throw an error on `get` or `set` function calls to objects with dynamic property names (introduces a way to circumvent our check for trying to avoid the `get` or `set` keywords, but not worth the complications for this tiny edge case)
  • Loading branch information
GeoffreyBooth authored Apr 9, 2017
1 parent 76945ab commit 8292d25
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 27 deletions.
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 @@ -440,7 +451,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 @@ -468,7 +479,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 @@ -758,6 +769,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

0 comments on commit 8292d25

Please sign in to comment.