Skip to content

Commit 8292d25

Browse files
[CS2] Throw an error for ambiguous get or set keywords or function 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)
1 parent 76945ab commit 8292d25

File tree

5 files changed

+234
-27
lines changed

5 files changed

+234
-27
lines changed

lib/coffeescript/lexer.js

+33-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lexer.coffee

+21-6
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ exports.Lexer = class Lexer
132132
@token 'DEFAULT', id
133133
return id.length
134134

135-
[..., prev] = @tokens
135+
prev = @prev()
136136

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

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

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

243254
regex = switch quote
244255
when "'" then STRING_SINGLE
@@ -318,7 +329,7 @@ exports.Lexer = class Lexer
318329
[regex, body, closed] = match
319330
@validateEscapes body, isRegex: yes, offsetInChunk: 1
320331
index = regex.length
321-
[..., prev] = @tokens
332+
prev = @prev()
322333
if prev
323334
if prev.spaced and prev[0] in CALLABLE
324335
return 0 if not closed or POSSIBLY_DIVISION.test regex
@@ -440,7 +451,7 @@ exports.Lexer = class Lexer
440451
whitespaceToken: ->
441452
return 0 unless (match = WHITESPACE.exec @chunk) or
442453
(nline = @chunk.charAt(0) is '\n')
443-
[..., prev] = @tokens
454+
prev = @prev()
444455
prev[if match then 'spaced' else 'newLine'] = true if prev
445456
if match then match[0].length else 0
446457

@@ -468,7 +479,7 @@ exports.Lexer = class Lexer
468479
else
469480
value = @chunk.charAt 0
470481
tag = value
471-
[..., prev] = @tokens
482+
prev = @prev()
472483

473484
if prev and value in ['=', COMPOUND_ASSIGN...]
474485
skipToken = false
@@ -758,6 +769,10 @@ exports.Lexer = class Lexer
758769
[..., token] = @tokens
759770
token?[1]
760771

772+
# Get the previous token in the token stream.
773+
prev: ->
774+
@tokens[@tokens.length - 1]
775+
761776
# Are we in the midst of an unfinished expression?
762777
unfinished: ->
763778
LINE_CONTINUER.test(@chunk) or

src/nodes.coffee

+1-1
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ exports.Call = class Call extends Base
775775
constructor: (@variable, @args = [], @soak) ->
776776
super()
777777

778-
@isNew = false
778+
@isNew = no
779779
if @variable instanceof Value and @variable.isNotCallable()
780780
@variable.error "literal is not a function"
781781

test/error_messages.coffee

+136
Original file line numberDiff line numberDiff line change
@@ -1340,3 +1340,139 @@ test "new with 'super'", ->
13401340
class extends A then foo: -> new super()
13411341
^^^^^
13421342
'''
1343+
1344+
test "getter keyword in object", ->
1345+
assertErrorFormat '''
1346+
obj =
1347+
get foo: ->
1348+
''', '''
1349+
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1350+
get foo: ->
1351+
^^^
1352+
'''
1353+
1354+
test "setter keyword in object", ->
1355+
assertErrorFormat '''
1356+
obj =
1357+
set foo: ->
1358+
''', '''
1359+
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1360+
set foo: ->
1361+
^^^
1362+
'''
1363+
1364+
test "getter keyword in inline implicit object", ->
1365+
assertErrorFormat 'obj = get foo: ->', '''
1366+
[stdin]:1:7: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1367+
obj = get foo: ->
1368+
^^^
1369+
'''
1370+
1371+
test "setter keyword in inline implicit object", ->
1372+
assertErrorFormat 'obj = set foo: ->', '''
1373+
[stdin]:1:7: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1374+
obj = set foo: ->
1375+
^^^
1376+
'''
1377+
1378+
test "getter keyword in inline explicit object", ->
1379+
assertErrorFormat 'obj = {get foo: ->}', '''
1380+
[stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1381+
obj = {get foo: ->}
1382+
^^^
1383+
'''
1384+
1385+
test "setter keyword in inline explicit object", ->
1386+
assertErrorFormat 'obj = {set foo: ->}', '''
1387+
[stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1388+
obj = {set foo: ->}
1389+
^^^
1390+
'''
1391+
1392+
test "getter keyword in function", ->
1393+
assertErrorFormat '''
1394+
f = ->
1395+
get foo: ->
1396+
''', '''
1397+
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1398+
get foo: ->
1399+
^^^
1400+
'''
1401+
1402+
test "setter keyword in function", ->
1403+
assertErrorFormat '''
1404+
f = ->
1405+
set foo: ->
1406+
''', '''
1407+
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1408+
set foo: ->
1409+
^^^
1410+
'''
1411+
1412+
test "getter keyword in inline function", ->
1413+
assertErrorFormat 'f = -> get foo: ->', '''
1414+
[stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1415+
f = -> get foo: ->
1416+
^^^
1417+
'''
1418+
1419+
test "setter keyword in inline function", ->
1420+
assertErrorFormat 'f = -> set foo: ->', '''
1421+
[stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1422+
f = -> set foo: ->
1423+
^^^
1424+
'''
1425+
1426+
test "getter keyword in class", ->
1427+
assertErrorFormat '''
1428+
class A
1429+
get foo: ->
1430+
''', '''
1431+
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1432+
get foo: ->
1433+
^^^
1434+
'''
1435+
1436+
test "setter keyword in class", ->
1437+
assertErrorFormat '''
1438+
class A
1439+
set foo: ->
1440+
''', '''
1441+
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1442+
set foo: ->
1443+
^^^
1444+
'''
1445+
1446+
test "getter keyword in inline class", ->
1447+
assertErrorFormat 'class A then get foo: ->', '''
1448+
[stdin]:1:14: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1449+
class A then get foo: ->
1450+
^^^
1451+
'''
1452+
1453+
test "setter keyword in inline class", ->
1454+
assertErrorFormat 'class A then set foo: ->', '''
1455+
[stdin]:1:14: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1456+
class A then set foo: ->
1457+
^^^
1458+
'''
1459+
1460+
test "getter keyword before static method", ->
1461+
assertErrorFormat '''
1462+
class A
1463+
get @foo = ->
1464+
''', '''
1465+
[stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
1466+
get @foo = ->
1467+
^^^
1468+
'''
1469+
1470+
test "setter keyword before static method", ->
1471+
assertErrorFormat '''
1472+
class A
1473+
set @foo = ->
1474+
''', '''
1475+
[stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
1476+
set @foo = ->
1477+
^^^
1478+
'''

0 commit comments

Comments
 (0)