Unify, simplify and fixup assignment errors

- Show the same type of error message for compound assignment as for `=`
  assignment when the LHS is invalid.
- Show the same type of error message when trying to assign to a CoffeeScript
  keyword as when trying to assign to a JavaScript keyword.
- Now longer treat `&& =` as `&&=`. The same goes for `and=`, `||=` and `or=`.
- Unify the error message to: `<optional type> '<value>' can't be assigned`.
This commit is contained in:
Simon Lydell 2016-03-05 20:59:39 +01:00
parent 585932cf5b
commit 4d8cd03298
5 changed files with 228 additions and 78 deletions

View File

@ -1,7 +1,8 @@
// Generated by CoffeeScript 1.10.0
(function() {
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVALID_ESCAPE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, key, locationDataToString, ref, ref1, repeat, starts, throwSyntaxError,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVALID_ESCAPE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, isUnassignable, key, locationDataToString, ref, ref1, repeat, starts, throwSyntaxError,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
slice = [].slice;
ref = require('./rewriter'), Rewriter = ref.Rewriter, INVERSES = ref.INVERSES;
@ -495,7 +496,7 @@
};
Lexer.prototype.literalToken = function() {
var match, prev, ref2, ref3, ref4, ref5, ref6, tag, token, value;
var match, message, origin, prev, ref2, ref3, ref4, ref5, ref6, skipToken, tag, token, value;
if (match = OPERATOR.exec(this.chunk)) {
value = match[0];
if (CODE.test(value)) {
@ -506,16 +507,22 @@
}
tag = value;
ref2 = this.tokens, prev = ref2[ref2.length - 1];
if (value === '=' && prev) {
if (prev.variable && (ref3 = prev[1], indexOf.call(JS_FORBIDDEN, ref3) >= 0)) {
if (prev.origin) {
prev = prev.origin;
}
this.error("reserved word '" + prev[1] + "' can't be assigned", prev[2]);
}
if ((ref4 = prev[1]) === '||' || ref4 === '&&') {
if (prev && indexOf.call(['='].concat(slice.call(COMPOUND_ASSIGN)), value) >= 0) {
skipToken = false;
if (value === '=' && ((ref3 = prev[1]) === '||' || ref3 === '&&') && !prev.spaced) {
prev[0] = 'COMPOUND_ASSIGN';
prev[1] += '=';
prev = this.tokens[this.tokens.length - 2];
skipToken = true;
}
if (prev && prev.variable) {
origin = (ref4 = prev.origin) != null ? ref4 : prev;
message = isUnassignable(prev[1], origin[1]);
if (message) {
this.error(message, origin[2]);
}
}
if (skipToken) {
return value.length;
}
}
@ -886,6 +893,24 @@
})();
isUnassignable = function(name, displayName) {
if (displayName == null) {
displayName = name;
}
switch (false) {
case indexOf.call(slice.call(JS_KEYWORDS).concat(slice.call(COFFEE_KEYWORDS)), name) < 0:
return "keyword '" + displayName + "' can't be assigned";
case indexOf.call(STRICT_PROSCRIBED, name) < 0:
return "'" + displayName + "' can't be assigned";
case indexOf.call(RESERVED, name) < 0:
return "reserved word '" + displayName + "' can't be assigned";
default:
return false;
}
};
exports.isUnassignable = isUnassignable;
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super'];
COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
@ -915,15 +940,11 @@
RESERVED = ['case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', 'implements', 'interface', 'package', 'private', 'protected', 'public', 'static'];
STRICT_PROSCRIBED = ['arguments', 'eval', 'yield*'];
STRICT_PROSCRIBED = ['arguments', 'eval'];
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED);
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED;
exports.JS_FORBIDDEN = JS_FORBIDDEN;
exports.JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
BOM = 65279;

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.10.0
(function() {
var Access, Arr, Assign, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, IdentifierLiteral, If, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, RESERVED, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
var Access, Arr, Assign, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, IdentifierLiteral, If, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
@ -10,7 +10,7 @@
Scope = require('./scope').Scope;
ref1 = require('./lexer'), RESERVED = ref1.RESERVED, STRICT_PROSCRIBED = ref1.STRICT_PROSCRIBED, JS_FORBIDDEN = ref1.JS_FORBIDDEN;
ref1 = require('./lexer'), isUnassignable = ref1.isUnassignable, JS_FORBIDDEN = ref1.JS_FORBIDDEN;
ref2 = require('./helpers'), compact = ref2.compact, flatten = ref2.flatten, extend = ref2.extend, merge = ref2.merge, del = ref2.del, starts = ref2.starts, ends = ref2.ends, some = ref2.some, addLocationDataFn = ref2.addLocationDataFn, locationDataToString = ref2.locationDataToString, throwSyntaxError = ref2.throwSyntaxError;
@ -608,10 +608,7 @@
return IdentifierLiteral.__super__.constructor.apply(this, arguments);
}
IdentifierLiteral.prototype.isAssignable = function() {
var ref3;
return ref3 = this.value, indexOf.call(RESERVED, ref3) < 0;
};
IdentifierLiteral.prototype.isAssignable = YES;
return IdentifierLiteral;
@ -1580,7 +1577,7 @@
Class.prototype.defaultClassVariableName = '_Class';
Class.prototype.determineName = function() {
var name, node, ref3, tail;
var message, name, node, ref3, tail;
if (!this.variable) {
return this.defaultClassVariableName;
}
@ -1590,6 +1587,12 @@
return this.defaultClassVariableName;
}
name = node.value;
if (!tail) {
message = isUnassignable(name);
if (message) {
this.variable.error(message);
}
}
if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
return "_" + name;
} else {
@ -1762,7 +1765,6 @@
extend1(Assign, superClass1);
function Assign(variable1, value1, context, options) {
var forbidden, name, ref3;
this.variable = variable1;
this.value = value1;
this.context = context;
@ -1770,10 +1772,6 @@
options = {};
}
this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken;
forbidden = (ref3 = (name = this.variable.unwrapAll().value), indexOf.call(STRICT_PROSCRIBED, ref3) >= 0);
if (forbidden && this.context !== 'object') {
this.variable.error("variable name may not be \"" + name + "\"");
}
}
Assign.prototype.children = ['variable', 'value'];
@ -1823,7 +1821,7 @@
if (!this.context) {
varBase = this.variable.unwrapAll();
if (!varBase.isAssignable()) {
this.variable.error("\"" + (this.variable.compile(o)) + "\" cannot be assigned");
this.variable.error("'" + (this.variable.compile(o)) + "' can't be assigned");
}
if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) {
if (this.param) {
@ -1854,7 +1852,7 @@
};
Assign.prototype.compilePatternMatch = function(o) {
var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, name, obj, objects, olen, ref, ref3, ref4, ref5, ref6, ref7, rest, top, val, value, vvar, vvarText;
var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, ref3, ref4, ref5, ref6, rest, top, val, value, vvar, vvarText;
top = o.level === LEVEL_TOP;
value = this.value;
objects = this.variable.base.objects;
@ -1889,8 +1887,9 @@
acc = idx.unwrap() instanceof IdentifierLiteral;
value = new Value(value);
value.properties.push(new (acc ? Access : Index)(idx));
if (ref5 = obj.unwrap().value, indexOf.call(RESERVED, ref5) >= 0) {
obj.error("assignment to a reserved word: " + (obj.compile(o)));
message = isUnassignable(obj.unwrap().value);
if (message) {
obj.error(message);
}
if (defaultValue) {
value = new Op('?', value, defaultValue);
@ -1945,7 +1944,7 @@
}
defaultValue = null;
if (obj instanceof Assign && obj.context === 'object') {
ref6 = obj, (ref7 = ref6.variable, idx = ref7.base), obj = ref6.value;
ref5 = obj, (ref6 = ref5.variable, idx = ref6.base), obj = ref5.value;
if (obj instanceof Assign) {
defaultValue = obj.value;
obj = obj.variable;
@ -1964,8 +1963,11 @@
val = new Op('?', val, defaultValue);
}
}
if ((name != null) && indexOf.call(RESERVED, name) >= 0) {
obj.error("assignment to a reserved word: " + (obj.compile(o)));
if (name != null) {
message = isUnassignable(name);
if (message) {
obj.error(message);
}
}
assigns.push(new Assign(obj, val, null, {
param: this.param,
@ -2223,12 +2225,13 @@
extend1(Param, superClass1);
function Param(name1, value1, splat) {
var name, ref3, token;
var message, token;
this.name = name1;
this.value = value1;
this.splat = splat;
if (ref3 = (name = this.name.unwrapAll().value), indexOf.call(STRICT_PROSCRIBED, ref3) >= 0) {
this.name.error("parameter name \"" + name + "\" is not allowed");
message = isUnassignable(this.name.unwrapAll().value);
if (message) {
this.name.error(message);
}
if (this.name instanceof Obj && this.name.generated) {
token = this.name.objects[0].operatorToken;
@ -2611,7 +2614,7 @@
};
Op.prototype.compileNode = function(o) {
var answer, isChain, lhs, ref3, ref4, rhs;
var answer, isChain, lhs, message, ref3, rhs;
isChain = this.isChainable() && this.first.isChainable();
if (!isChain) {
this.first.front = this.front;
@ -2619,8 +2622,11 @@
if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) {
this.error('delete operand may not be argument or var');
}
if (((ref3 = this.operator) === '--' || ref3 === '++') && (ref4 = this.first.unwrapAll().value, indexOf.call(STRICT_PROSCRIBED, ref4) >= 0)) {
this.error("cannot increment/decrement \"" + (this.first.unwrapAll().value) + "\"");
if ((ref3 = this.operator) === '--' || ref3 === '++') {
message = isUnassignable(this.first.unwrapAll().value);
if (message) {
this.first.error(message);
}
}
if (this.isYield()) {
return this.compileYield(o);
@ -2858,12 +2864,12 @@
};
Try.prototype.compileNode = function(o) {
var catchPart, ensurePart, generatedErrorVariableName, placeholder, tryPart;
var catchPart, ensurePart, generatedErrorVariableName, message, placeholder, tryPart;
o.indent += TAB;
tryPart = this.attempt.compileToFragments(o, LEVEL_TOP);
catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', {
reserve: false
}), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? this.recovery.unshift(new Assign(this.errorVariable, placeholder)) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', {
}), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? (message = isUnassignable(this.errorVariable.unwrapAll().value), message ? this.errorVariable.error(message) : void 0, this.recovery.unshift(new Assign(this.errorVariable, placeholder))) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', {
reserve: false
}), [this.makeCode(" catch (" + generatedErrorVariableName + ") {}")]) : [];
ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")) : [];

View File

@ -411,14 +411,20 @@ exports.Lexer = class Lexer
value = @chunk.charAt 0
tag = value
[..., prev] = @tokens
if value is '=' and prev
if prev.variable and prev[1] in JS_FORBIDDEN
prev = prev.origin if prev.origin
@error "reserved word '#{prev[1]}' can't be assigned", prev[2]
if prev[1] in ['||', '&&']
if prev and value in ['=', COMPOUND_ASSIGN...]
skipToken = false
if value is '=' and prev[1] in ['||', '&&'] and not prev.spaced
prev[0] = 'COMPOUND_ASSIGN'
prev[1] += '='
return value.length
prev = @tokens[@tokens.length - 2]
skipToken = true
if prev and prev.variable
origin = prev.origin ? prev
message = isUnassignable prev[1], origin[1]
@error message, origin[2] if message
return value.length if skipToken
if value is ';'
@seenFor = no
tag = 'TERMINATOR'
@ -744,6 +750,21 @@ exports.Lexer = class Lexer
{first_line, first_column, last_column: first_column + (options.length ? 1) - 1}
throwSyntaxError message, location
# Helper functions
# ----------------
isUnassignable = (name, displayName = name) -> switch
when name in [JS_KEYWORDS..., COFFEE_KEYWORDS...]
"keyword '#{displayName}' can't be assigned"
when name in STRICT_PROSCRIBED
"'#{displayName}' can't be assigned"
when name in RESERVED
"reserved word '#{displayName}' can't be assigned"
else
false
exports.isUnassignable = isUnassignable
# Constants
# ---------
@ -782,15 +803,12 @@ RESERVED = [
'protected', 'public', 'static'
]
STRICT_PROSCRIBED = ['arguments', 'eval', 'yield*']
STRICT_PROSCRIBED = ['arguments', 'eval']
# The superset of both JavaScript keywords and reserved words, none of which may
# be used as identifiers or properties.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED)
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
exports.JS_FORBIDDEN = JS_FORBIDDEN
exports.JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
# The character code of the nasty Microsoft madness otherwise known as the BOM.
BOM = 65279

View File

@ -6,7 +6,7 @@
Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{RESERVED, STRICT_PROSCRIBED, JS_FORBIDDEN} = require './lexer'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
# Import the helpers we plan to use.
{compact, flatten, extend, merge, del, starts, ends, some,
@ -411,7 +411,7 @@ exports.RegexLiteral = class RegexLiteral extends Literal
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: -> @value not in RESERVED
isAssignable: YES
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
@ -1081,6 +1081,9 @@ exports.Class = class Class extends Base
@variable.base
return @defaultClassVariableName unless node instanceof IdentifierLiteral
name = node.value
unless tail
message = isUnassignable name
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
# For all `this`-references and bound functions in the class definition,
@ -1215,9 +1218,6 @@ exports.Class = class Class extends Base
exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) ->
{@param, @subpattern, @operatorToken} = options
forbidden = (name = @variable.unwrapAll().value) in STRICT_PROSCRIBED
if forbidden and @context isnt 'object'
@variable.error "variable name may not be \"#{name}\""
children: ['variable', 'value']
@ -1254,7 +1254,7 @@ exports.Assign = class Assign extends Base
unless @context
varBase = @variable.unwrapAll()
unless varBase.isAssignable()
@variable.error "\"#{@variable.compile o}\" cannot be assigned"
@variable.error "'#{@variable.compile o}' can't be assigned"
unless varBase.hasProperties?()
if @param
o.scope.add varBase.value, 'var'
@ -1309,8 +1309,8 @@ exports.Assign = class Assign extends Base
acc = idx.unwrap() instanceof IdentifierLiteral
value = new Value value
value.properties.push new (if acc then Access else Index) idx
if obj.unwrap().value in RESERVED
obj.error "assignment to a reserved word: #{obj.compile o}"
message = isUnassignable obj.unwrap().value
obj.error message if message
value = new Op '?', value, defaultValue if defaultValue
return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
vvar = value.compileToFragments o, LEVEL_LIST
@ -1369,8 +1369,9 @@ exports.Assign = class Assign extends Base
acc = idx.unwrap() instanceof IdentifierLiteral
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
val = new Op '?', val, defaultValue if defaultValue
if name? and name in RESERVED
obj.error "assignment to a reserved word: #{obj.compile o}"
if name?
message = isUnassignable name
obj.error message if message
assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
assigns.push vvar unless top or @subpattern
fragments = @joinFragmentArrays assigns, ', '
@ -1527,8 +1528,8 @@ exports.Code = class Code extends Base
# as well as be a splat, gathering up a group of parameters into an array.
exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
@name.error "parameter name \"#{name}\" is not allowed"
message = isUnassignable @name.unwrapAll().value
@name.error message if message
if @name instanceof Obj and @name.generated
token = @name.objects[0].operatorToken
token.error "unexpected #{token.value}"
@ -1817,8 +1818,9 @@ exports.Op = class Op extends Base
@first.front = @front unless isChain
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
@error 'delete operand may not be argument or var'
if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
@error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
if @operator in ['--', '++']
message = isUnassignable @first.unwrapAll().value
@first.error message if message
return @compileYield o if @isYield()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
@ -1969,7 +1971,10 @@ exports.Try = class Try extends Base
catchPart = if @recovery
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
placeholder = new IdentifierLiteral generatedErrorVariableName
@recovery.unshift new Assign @errorVariable, placeholder if @errorVariable
if @errorVariable
message = isUnassignable @errorVariable.unwrapAll().value
@errorVariable.error message if message
@recovery.unshift new Assign @errorVariable, placeholder
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
else unless @ensure or @recovery

View File

@ -36,7 +36,7 @@ test "compiler error formatting", ->
evil = (foo, eval, bar) ->
''',
'''
[stdin]:1:14: error: parameter name "eval" is not allowed
[stdin]:1:14: error: 'eval' can't be assigned
evil = (foo, eval, bar) ->
^^^^
'''
@ -656,22 +656,88 @@ test "reserved words", ->
case
^^^^
'''
assertErrorFormat '''
case = 1
''', '''
[stdin]:1:1: error: reserved word 'case'
case = 1
^^^^
'''
assertErrorFormat '''
for = 1
''', '''
[stdin]:1:1: error: reserved word 'for' can't be assigned
[stdin]:1:1: error: keyword 'for' can't be assigned
for = 1
^^^
'''
assertErrorFormat '''
unless = 1
''', '''
[stdin]:1:1: error: keyword 'unless' can't be assigned
unless = 1
^^^^^^
'''
assertErrorFormat '''
for += 1
''', '''
[stdin]:1:1: error: keyword 'for' can't be assigned
for += 1
^^^
'''
assertErrorFormat '''
for &&= 1
''', '''
[stdin]:1:1: error: keyword 'for' can't be assigned
for &&= 1
^^^
'''
# Make sure token look-behind doesn't go out of range.
assertErrorFormat '''
&&= 1
''', '''
[stdin]:1:1: error: unexpected &&=
&&= 1
^^^
'''
# #2306: Show unaliased name in error messages.
assertErrorFormat '''
on = 1
''', '''
[stdin]:1:1: error: reserved word 'on' can't be assigned
[stdin]:1:1: error: keyword 'on' can't be assigned
on = 1
^^
'''
test "strict mode errors", ->
assertErrorFormat '''
eval = 1
''', '''
[stdin]:1:1: error: 'eval' can't be assigned
eval = 1
^^^^
'''
assertErrorFormat '''
class eval
''', '''
[stdin]:1:7: error: 'eval' can't be assigned
class eval
^^^^
'''
assertErrorFormat '''
arguments++
''', '''
[stdin]:1:1: error: 'arguments' can't be assigned
arguments++
^^^^^^^^^
'''
assertErrorFormat '''
--arguments
''', '''
[stdin]:1:3: error: 'arguments' can't be assigned
--arguments
^^^^^^^^^
'''
test "invalid numbers", ->
assertErrorFormat '''
0X0
@ -832,35 +898,35 @@ test "#4130: unassignable in destructured param", ->
}) ->
console.log "Oh hello!"
''', '''
[stdin]:2:12: error: assignment to a reserved word: null
[stdin]:2:12: error: keyword 'null' can't be assigned
@param : null
^^^^
'''
assertErrorFormat '''
({a: null}) ->
''', '''
[stdin]:1:6: error: assignment to a reserved word: null
[stdin]:1:6: error: keyword 'null' can't be assigned
({a: null}) ->
^^^^
'''
assertErrorFormat '''
({a: 1}) ->
''', '''
[stdin]:1:6: error: "1" cannot be assigned
[stdin]:1:6: error: '1' can't be assigned
({a: 1}) ->
^
'''
assertErrorFormat '''
({1}) ->
''', '''
[stdin]:1:3: error: "1" cannot be assigned
[stdin]:1:3: error: '1' can't be assigned
({1}) ->
^
'''
assertErrorFormat '''
({a: true = 1}) ->
''', '''
[stdin]:1:6: error: reserved word 'true' can't be assigned
[stdin]:1:6: error: keyword 'true' can't be assigned
({a: true = 1}) ->
^^^^
'''
@ -889,3 +955,37 @@ test "#4097: `yield return` as an expression", ->
-> (yield return)
^^^^^^^^^^^^
'''
test "`&&=` and `||=` with a space in-between", ->
assertErrorFormat '''
a = 0
a && = 1
''', '''
[stdin]:2:6: error: unexpected =
a && = 1
^
'''
assertErrorFormat '''
a = 0
a and = 1
''', '''
[stdin]:2:7: error: unexpected =
a and = 1
^
'''
assertErrorFormat '''
a = 0
a || = 1
''', '''
[stdin]:2:6: error: unexpected =
a || = 1
^
'''
assertErrorFormat '''
a = 0
a or = 1
''', '''
[stdin]:2:6: error: unexpected =
a or = 1
^
'''