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 // Generated by CoffeeScript 1.10.0
(function() { (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, 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; }; 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; ref = require('./rewriter'), Rewriter = ref.Rewriter, INVERSES = ref.INVERSES;
@ -495,7 +496,7 @@
}; };
Lexer.prototype.literalToken = function() { 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)) { if (match = OPERATOR.exec(this.chunk)) {
value = match[0]; value = match[0];
if (CODE.test(value)) { if (CODE.test(value)) {
@ -506,16 +507,22 @@
} }
tag = value; tag = value;
ref2 = this.tokens, prev = ref2[ref2.length - 1]; ref2 = this.tokens, prev = ref2[ref2.length - 1];
if (value === '=' && prev) { if (prev && indexOf.call(['='].concat(slice.call(COMPOUND_ASSIGN)), value) >= 0) {
if (prev.variable && (ref3 = prev[1], indexOf.call(JS_FORBIDDEN, ref3) >= 0)) { skipToken = false;
if (prev.origin) { if (value === '=' && ((ref3 = prev[1]) === '||' || ref3 === '&&') && !prev.spaced) {
prev = prev.origin;
}
this.error("reserved word '" + prev[1] + "' can't be assigned", prev[2]);
}
if ((ref4 = prev[1]) === '||' || ref4 === '&&') {
prev[0] = 'COMPOUND_ASSIGN'; prev[0] = 'COMPOUND_ASSIGN';
prev[1] += '='; 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; 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']; 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']; 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']; 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); JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED); exports.JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED);
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED;
exports.JS_FORBIDDEN = JS_FORBIDDEN;
BOM = 65279; BOM = 65279;

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.10.0 // Generated by CoffeeScript 1.10.0
(function() { (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; }, 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, 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; }, 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; 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; 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); return IdentifierLiteral.__super__.constructor.apply(this, arguments);
} }
IdentifierLiteral.prototype.isAssignable = function() { IdentifierLiteral.prototype.isAssignable = YES;
var ref3;
return ref3 = this.value, indexOf.call(RESERVED, ref3) < 0;
};
return IdentifierLiteral; return IdentifierLiteral;
@ -1580,7 +1577,7 @@
Class.prototype.defaultClassVariableName = '_Class'; Class.prototype.defaultClassVariableName = '_Class';
Class.prototype.determineName = function() { Class.prototype.determineName = function() {
var name, node, ref3, tail; var message, name, node, ref3, tail;
if (!this.variable) { if (!this.variable) {
return this.defaultClassVariableName; return this.defaultClassVariableName;
} }
@ -1590,6 +1587,12 @@
return this.defaultClassVariableName; return this.defaultClassVariableName;
} }
name = node.value; name = node.value;
if (!tail) {
message = isUnassignable(name);
if (message) {
this.variable.error(message);
}
}
if (indexOf.call(JS_FORBIDDEN, name) >= 0) { if (indexOf.call(JS_FORBIDDEN, name) >= 0) {
return "_" + name; return "_" + name;
} else { } else {
@ -1762,7 +1765,6 @@
extend1(Assign, superClass1); extend1(Assign, superClass1);
function Assign(variable1, value1, context, options) { function Assign(variable1, value1, context, options) {
var forbidden, name, ref3;
this.variable = variable1; this.variable = variable1;
this.value = value1; this.value = value1;
this.context = context; this.context = context;
@ -1770,10 +1772,6 @@
options = {}; options = {};
} }
this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken; 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']; Assign.prototype.children = ['variable', 'value'];
@ -1823,7 +1821,7 @@
if (!this.context) { if (!this.context) {
varBase = this.variable.unwrapAll(); varBase = this.variable.unwrapAll();
if (!varBase.isAssignable()) { 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 (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) {
if (this.param) { if (this.param) {
@ -1854,7 +1852,7 @@
}; };
Assign.prototype.compilePatternMatch = function(o) { 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; top = o.level === LEVEL_TOP;
value = this.value; value = this.value;
objects = this.variable.base.objects; objects = this.variable.base.objects;
@ -1889,8 +1887,9 @@
acc = idx.unwrap() instanceof IdentifierLiteral; acc = idx.unwrap() instanceof IdentifierLiteral;
value = new Value(value); value = new Value(value);
value.properties.push(new (acc ? Access : Index)(idx)); value.properties.push(new (acc ? Access : Index)(idx));
if (ref5 = obj.unwrap().value, indexOf.call(RESERVED, ref5) >= 0) { message = isUnassignable(obj.unwrap().value);
obj.error("assignment to a reserved word: " + (obj.compile(o))); if (message) {
obj.error(message);
} }
if (defaultValue) { if (defaultValue) {
value = new Op('?', value, defaultValue); value = new Op('?', value, defaultValue);
@ -1945,7 +1944,7 @@
} }
defaultValue = null; defaultValue = null;
if (obj instanceof Assign && obj.context === 'object') { 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) { if (obj instanceof Assign) {
defaultValue = obj.value; defaultValue = obj.value;
obj = obj.variable; obj = obj.variable;
@ -1964,8 +1963,11 @@
val = new Op('?', val, defaultValue); val = new Op('?', val, defaultValue);
} }
} }
if ((name != null) && indexOf.call(RESERVED, name) >= 0) { if (name != null) {
obj.error("assignment to a reserved word: " + (obj.compile(o))); message = isUnassignable(name);
if (message) {
obj.error(message);
}
} }
assigns.push(new Assign(obj, val, null, { assigns.push(new Assign(obj, val, null, {
param: this.param, param: this.param,
@ -2223,12 +2225,13 @@
extend1(Param, superClass1); extend1(Param, superClass1);
function Param(name1, value1, splat) { function Param(name1, value1, splat) {
var name, ref3, token; var message, token;
this.name = name1; this.name = name1;
this.value = value1; this.value = value1;
this.splat = splat; this.splat = splat;
if (ref3 = (name = this.name.unwrapAll().value), indexOf.call(STRICT_PROSCRIBED, ref3) >= 0) { message = isUnassignable(this.name.unwrapAll().value);
this.name.error("parameter name \"" + name + "\" is not allowed"); if (message) {
this.name.error(message);
} }
if (this.name instanceof Obj && this.name.generated) { if (this.name instanceof Obj && this.name.generated) {
token = this.name.objects[0].operatorToken; token = this.name.objects[0].operatorToken;
@ -2611,7 +2614,7 @@
}; };
Op.prototype.compileNode = function(o) { 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(); isChain = this.isChainable() && this.first.isChainable();
if (!isChain) { if (!isChain) {
this.first.front = this.front; this.first.front = this.front;
@ -2619,8 +2622,11 @@
if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) { if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) {
this.error('delete operand may not be argument or var'); 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)) { if ((ref3 = this.operator) === '--' || ref3 === '++') {
this.error("cannot increment/decrement \"" + (this.first.unwrapAll().value) + "\""); message = isUnassignable(this.first.unwrapAll().value);
if (message) {
this.first.error(message);
}
} }
if (this.isYield()) { if (this.isYield()) {
return this.compileYield(o); return this.compileYield(o);
@ -2858,12 +2864,12 @@
}; };
Try.prototype.compileNode = function(o) { Try.prototype.compileNode = function(o) {
var catchPart, ensurePart, generatedErrorVariableName, placeholder, tryPart; var catchPart, ensurePart, generatedErrorVariableName, message, placeholder, tryPart;
o.indent += TAB; o.indent += TAB;
tryPart = this.attempt.compileToFragments(o, LEVEL_TOP); tryPart = this.attempt.compileToFragments(o, LEVEL_TOP);
catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', { catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', {
reserve: false 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 reserve: false
}), [this.makeCode(" catch (" + generatedErrorVariableName + ") {}")]) : []; }), [this.makeCode(" catch (" + generatedErrorVariableName + ") {}")]) : [];
ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")) : []; 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 value = @chunk.charAt 0
tag = value tag = value
[..., prev] = @tokens [..., prev] = @tokens
if value is '=' and prev
if prev.variable and prev[1] in JS_FORBIDDEN if prev and value in ['=', COMPOUND_ASSIGN...]
prev = prev.origin if prev.origin skipToken = false
@error "reserved word '#{prev[1]}' can't be assigned", prev[2] if value is '=' and prev[1] in ['||', '&&'] and not prev.spaced
if prev[1] in ['||', '&&']
prev[0] = 'COMPOUND_ASSIGN' prev[0] = 'COMPOUND_ASSIGN'
prev[1] += '=' 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 ';' if value is ';'
@seenFor = no @seenFor = no
tag = 'TERMINATOR' tag = 'TERMINATOR'
@ -744,6 +750,21 @@ exports.Lexer = class Lexer
{first_line, first_column, last_column: first_column + (options.length ? 1) - 1} {first_line, first_column, last_column: first_column + (options.length ? 1) - 1}
throwSyntaxError message, location 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 # Constants
# --------- # ---------
@ -782,15 +803,12 @@ RESERVED = [
'protected', 'public', 'static' '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 # The superset of both JavaScript keywords and reserved words, none of which may
# be used as identifiers or properties. # be used as identifiers or properties.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED) JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
exports.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
# The character code of the nasty Microsoft madness otherwise known as the BOM. # The character code of the nasty Microsoft madness otherwise known as the BOM.
BOM = 65279 BOM = 65279

View File

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

View File

@ -36,7 +36,7 @@ test "compiler error formatting", ->
evil = (foo, eval, bar) -> 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) -> evil = (foo, eval, bar) ->
^^^^ ^^^^
''' '''
@ -656,22 +656,88 @@ test "reserved words", ->
case case
^^^^ ^^^^
''' '''
assertErrorFormat '''
case = 1
''', '''
[stdin]:1:1: error: reserved word 'case'
case = 1
^^^^
'''
assertErrorFormat ''' assertErrorFormat '''
for = 1 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 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. # #2306: Show unaliased name in error messages.
assertErrorFormat ''' assertErrorFormat '''
on = 1 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 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", -> test "invalid numbers", ->
assertErrorFormat ''' assertErrorFormat '''
0X0 0X0
@ -832,35 +898,35 @@ test "#4130: unassignable in destructured param", ->
}) -> }) ->
console.log "Oh hello!" 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 @param : null
^^^^ ^^^^
''' '''
assertErrorFormat ''' assertErrorFormat '''
({a: null}) -> ({a: null}) ->
''', ''' ''', '''
[stdin]:1:6: error: assignment to a reserved word: null [stdin]:1:6: error: keyword 'null' can't be assigned
({a: null}) -> ({a: null}) ->
^^^^ ^^^^
''' '''
assertErrorFormat ''' assertErrorFormat '''
({a: 1}) -> ({a: 1}) ->
''', ''' ''', '''
[stdin]:1:6: error: "1" cannot be assigned [stdin]:1:6: error: '1' can't be assigned
({a: 1}) -> ({a: 1}) ->
^ ^
''' '''
assertErrorFormat ''' assertErrorFormat '''
({1}) -> ({1}) ->
''', ''' ''', '''
[stdin]:1:3: error: "1" cannot be assigned [stdin]:1:3: error: '1' can't be assigned
({1}) -> ({1}) ->
^ ^
''' '''
assertErrorFormat ''' assertErrorFormat '''
({a: true = 1}) -> ({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}) -> ({a: true = 1}) ->
^^^^ ^^^^
''' '''
@ -889,3 +955,37 @@ test "#4097: `yield return` as an expression", ->
-> (yield return) -> (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
^
'''