Merge pull request #2723 from epidemian/improved-error-messages

Improved error messages
This commit is contained in:
Jeremy Ashkenas
2013-03-10 16:46:25 -07:00
17 changed files with 476 additions and 301 deletions

View File

@@ -1942,8 +1942,10 @@ Expressions
else
$(el).text window.compiledJS
$('#error').hide()
catch error
$('#error').text(error.message).show()
catch {location, message}
if location?
message = "Error on line #{location.first_line + 1}: #{message}"
$('#error').text(message).show()
# Update permalink
$('#repl_permalink').attr 'href', "##{sourceFragment}#{encodeURIComponent source}"

View File

@@ -40,42 +40,34 @@
exports.helpers = helpers;
exports.compile = compile = function(code, options) {
var answer, currentColumn, currentLine, err, fragment, fragments, header, js, merge, newLines, sourceMap, _j, _len1;
var answer, currentColumn, currentLine, fragment, fragments, header, js, merge, newLines, sourceMap, _j, _len1;
if (options == null) {
options = {};
}
merge = exports.helpers.merge;
try {
if (options.sourceMap) {
sourceMap = new sourcemap.SourceMap();
}
fragments = (parser.parse(lexer.tokenize(code, options))).compileToFragments(options);
currentLine = 0;
if (options.header) {
currentLine += 1;
}
currentColumn = 0;
js = "";
for (_j = 0, _len1 = fragments.length; _j < _len1; _j++) {
fragment = fragments[_j];
if (sourceMap) {
if (fragment.locationData) {
sourceMap.addMapping([fragment.locationData.first_line, fragment.locationData.first_column], [currentLine, currentColumn], {
noReplace: true
});
}
newLines = helpers.count(fragment.code, "\n");
currentLine += newLines;
currentColumn = fragment.code.length - (newLines ? fragment.code.lastIndexOf("\n") : 0);
if (options.sourceMap) {
sourceMap = new sourcemap.SourceMap();
}
fragments = (parser.parse(lexer.tokenize(code, options))).compileToFragments(options);
currentLine = 0;
if (options.header) {
currentLine += 1;
}
currentColumn = 0;
js = "";
for (_j = 0, _len1 = fragments.length; _j < _len1; _j++) {
fragment = fragments[_j];
if (sourceMap) {
if (fragment.locationData) {
sourceMap.addMapping([fragment.locationData.first_line, fragment.locationData.first_column], [currentLine, currentColumn], {
noReplace: true
});
}
js += fragment.code;
newLines = helpers.count(fragment.code, "\n");
currentLine += newLines;
currentColumn = fragment.code.length - (newLines ? fragment.code.lastIndexOf("\n") : 0);
}
} catch (_error) {
err = _error;
if (options.filename) {
err.message = "In " + options.filename + ", " + err.message;
}
throw err;
js += fragment.code;
}
if (options.header) {
header = "Generated by CoffeeScript " + this.VERSION;
@@ -211,4 +203,11 @@
parser.yy = require('./nodes');
parser.yy.parseError = function(message, _arg) {
var token;
token = _arg.token;
message = "unexpected " + (token === 1 ? 'end of input' : token);
return helpers.throwSyntaxError(message, parser.lexer.yylloc);
};
}).call(this);

View File

@@ -148,7 +148,7 @@
};
compileScript = function(file, input, base) {
var compiled, err, o, options, t, task;
var compiled, err, message, o, options, t, task, useColors;
if (base == null) {
base = null;
}
@@ -195,11 +195,14 @@
if (CoffeeScript.listeners('failure').length) {
return;
}
useColors = process.stdout.isTTY && !process.env.NODE_DISABLE_COLORS;
message = helpers.prettyErrorMessage(err, file || '[stdin]', input, useColors);
if (o.watch) {
return printLine(err.message + '\x07');
return printLine(message + '\x07');
} else {
printWarn(message);
return process.exit(1);
}
printWarn(err instanceof Error && err.stack || ("ERROR: " + err));
return process.exit(1);
}
};

View File

@@ -0,0 +1,53 @@
// Generated by CoffeeScript 1.6.1
(function() {
var CompilerError, repeat,
__hasProp = {}.hasOwnProperty,
__extends = 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; };
repeat = require('./helpers').repeat;
exports.CompilerError = CompilerError = (function(_super) {
__extends(CompilerError, _super);
CompilerError.prototype.name = 'CompilerError';
function CompilerError(message, startLine, startColumn, endLine, endColumn) {
this.message = message;
this.startLine = startLine;
this.startColumn = startColumn;
this.endLine = endLine != null ? endLine : this.startLine;
this.endColumn = endColumn != null ? endColumn : this.startColumn;
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, CompilerError);
}
}
CompilerError.fromLocationData = function(message, _arg) {
var first_column, first_line, last_column, last_line;
first_line = _arg.first_line, first_column = _arg.first_column, last_line = _arg.last_line, last_column = _arg.last_column;
return new CompilerError(message, first_line, first_column, last_line, last_column);
};
CompilerError.prototype.prettyMessage = function(fileName, code, useColors) {
var colorize, end, errorLine, marker, message, start;
errorLine = code.split('\n')[this.startLine];
start = this.startColumn;
end = this.startLine === this.endLine ? this.endColumn + 1 : errorLine.length;
marker = repeat(' ', start) + repeat('^', end - start);
if (useColors) {
colorize = function(str) {
return "\x1B[1;31m" + str + "\x1B[0m";
};
errorLine = errorLine.slice(0, start) + colorize(errorLine.slice(start, end)) + errorLine.slice(end);
marker = colorize(marker);
}
message = "" + fileName + ":" + (this.startLine + 1) + ":" + (this.startColumn + 1) + ": error: " + this.message + "\n" + errorLine + "\n" + marker;
return message;
};
return CompilerError;
})(Error);
}).call(this);

View File

@@ -179,4 +179,38 @@
return /\.(litcoffee|coffee\.md)$/.test(file);
};
exports.throwSyntaxError = function(message, location) {
var error, _ref1, _ref2;
if ((_ref1 = location.last_line) == null) {
location.last_line = location.first_line;
}
if ((_ref2 = location.last_column) == null) {
location.last_column = location.first_column;
}
error = new SyntaxError(message);
error.location = location;
throw error;
};
exports.prettyErrorMessage = function(error, fileName, code, useColors) {
var codeLine, colorize, end, first_column, first_line, last_column, last_line, marker, message, start, _ref1;
if (!error.location) {
return error.stack || ("" + error);
}
_ref1 = error.location, first_line = _ref1.first_line, first_column = _ref1.first_column, last_line = _ref1.last_line, last_column = _ref1.last_column;
codeLine = code.split('\n')[first_line];
start = first_column;
end = first_line === last_line ? last_column + 1 : codeLine.length;
marker = repeat(' ', start) + repeat('^', end - start);
if (useColors) {
colorize = function(str) {
return "\x1B[1;31m" + str + "\x1B[0m";
};
codeLine = codeLine.slice(0, start) + colorize(codeLine.slice(start, end)) + codeLine.slice(end);
marker = colorize(marker);
}
message = "" + fileName + ":" + (first_line + 1) + ":" + (first_column + 1) + ": error: " + error.message + "\n" + codeLine + "\n" + marker;
return message;
};
}).call(this);

View File

@@ -1,11 +1,11 @@
// Generated by CoffeeScript 1.6.1
(function() {
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, invertLiterate, key, last, locationDataToString, starts, _ref, _ref1,
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, invertLiterate, key, last, locationDataToString, starts, throwSyntaxError, _ref, _ref1,
__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; };
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
_ref1 = require('./helpers'), count = _ref1.count, starts = _ref1.starts, compact = _ref1.compact, last = _ref1.last, invertLiterate = _ref1.invertLiterate, locationDataToString = _ref1.locationDataToString;
_ref1 = require('./helpers'), count = _ref1.count, starts = _ref1.starts, compact = _ref1.compact, last = _ref1.last, invertLiterate = _ref1.invertLiterate, locationDataToString = _ref1.locationDataToString, throwSyntaxError = _ref1.throwSyntaxError;
exports.Lexer = Lexer = (function() {
@@ -779,7 +779,10 @@
};
Lexer.prototype.error = function(message) {
throw SyntaxError("" + message + " on line " + (this.chunkLine + 1));
return throwSyntaxError(message, {
first_line: this.chunkLine,
first_column: this.chunkColumn
});
};
return Lexer;

View File

@@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.1
(function() {
var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, some, starts, unfoldSoak, utility, _ref, _ref1, _ref2, _ref3,
var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1, _ref2, _ref3,
__hasProp = {}.hasOwnProperty,
__extends = 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; },
__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; },
@@ -12,7 +12,7 @@
_ref = require('./lexer'), RESERVED = _ref.RESERVED, STRICT_PROSCRIBED = _ref.STRICT_PROSCRIBED;
_ref1 = require('./helpers'), compact = _ref1.compact, flatten = _ref1.flatten, extend = _ref1.extend, merge = _ref1.merge, del = _ref1.del, starts = _ref1.starts, ends = _ref1.ends, last = _ref1.last, some = _ref1.some, addLocationDataFn = _ref1.addLocationDataFn, locationDataToString = _ref1.locationDataToString;
_ref1 = require('./helpers'), compact = _ref1.compact, flatten = _ref1.flatten, extend = _ref1.extend, merge = _ref1.merge, del = _ref1.del, starts = _ref1.starts, ends = _ref1.ends, last = _ref1.last, some = _ref1.some, addLocationDataFn = _ref1.addLocationDataFn, locationDataToString = _ref1.locationDataToString, throwSyntaxError = _ref1.throwSyntaxError;
exports.extend = extend;
@@ -89,8 +89,9 @@
};
Base.prototype.compileClosure = function(o) {
if (this.jumps()) {
throw SyntaxError('cannot use a pure statement in an expression.');
var jumpNode;
if (jumpNode = this.jumps()) {
jumpNode.error('cannot use a pure statement in an expression');
}
o.sharedScope = true;
return Closure.wrap(this).compileNode(o);
@@ -127,21 +128,15 @@
};
Base.prototype.contains = function(pred) {
var contains;
contains = false;
this.traverseChildren(false, function(node) {
if (pred(node)) {
contains = true;
var node;
node = void 0;
this.traverseChildren(false, function(n) {
if (pred(n)) {
node = n;
return false;
}
});
return contains;
};
Base.prototype.containsType = function(type) {
return this instanceof type || this.contains(function(node) {
return node instanceof type;
});
return node;
};
Base.prototype.lastNonComment = function(list) {
@@ -235,15 +230,16 @@
Base.prototype.assigns = NO;
Base.prototype.updateLocationDataIfMissing = function(locationData) {
if (!this.locationData) {
this.locationData = {};
extend(this.locationData, locationData);
}
this.locationData || (this.locationData = locationData);
return this.eachChild(function(child) {
return child.updateLocationDataIfMissing(locationData);
});
};
Base.prototype.error = function(message) {
return throwSyntaxError(message, this.locationData);
};
Base.prototype.makeCode = function(code) {
return new CodeFragment(this, code);
};
@@ -878,7 +874,7 @@
} else if (method != null ? method.ctor : void 0) {
return "" + method.name + ".__super__.constructor";
} else {
throw SyntaxError('cannot call super outside of an instance method.');
return this.error('cannot call super outside of an instance method.');
}
};
@@ -1231,7 +1227,7 @@
for (_i = 0, _len = props.length; _i < _len; _i++) {
node = props[_i];
if (node instanceof Value) {
throw new SyntaxError('cannot have an implicit value in an implicit object');
node.error('cannot have an implicit value in an implicit object');
}
}
}
@@ -1372,7 +1368,7 @@
}
decl = (tail = last(this.variable.properties)) ? tail instanceof Access && tail.name.value : this.variable.base.value;
if (__indexOf.call(STRICT_PROSCRIBED, decl) >= 0) {
throw SyntaxError("variable name may not be " + decl);
this.variable.error("class variable name may not be " + decl);
}
return decl && (decl = IDENTIFIER.test(decl) && decl);
};
@@ -1416,10 +1412,10 @@
func = assign.value;
if (base.value === 'constructor') {
if (this.ctor) {
throw new SyntaxError('cannot define more than one constructor in a class');
assign.error('cannot define more than one constructor in a class');
}
if (func.bound) {
throw new SyntaxError('cannot define a constructor as a bound function');
assign.error('cannot define a constructor as a bound function');
}
if (func instanceof Code) {
assign = this.ctor = func;
@@ -1561,7 +1557,7 @@
this.subpattern = options && options.subpattern;
forbidden = (_ref4 = (name = this.variable.unwrapAll().value), __indexOf.call(STRICT_PROSCRIBED, _ref4) >= 0);
if (forbidden && this.context !== 'object') {
throw SyntaxError("variable name may not be \"" + name + "\"");
this.variable.error("variable name may not be \"" + name + "\"");
}
}
@@ -1595,8 +1591,9 @@
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
name = fragmentsToText(compiledName);
if (!this.context) {
if (!(varBase = this.variable.unwrapAll()).isAssignable()) {
throw SyntaxError("\"" + (this.variable.compile(o)) + "\" cannot be assigned.");
varBase = this.variable.unwrapAll();
if (!varBase.isAssignable()) {
this.variable.error("\"" + (this.variable.compile(o)) + "\" cannot be assigned");
}
if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) {
if (this.param) {
@@ -1648,7 +1645,7 @@
value = new Value(value);
value.properties.push(new (acc ? Access : Index)(idx));
if (_ref6 = obj.unwrap().value, __indexOf.call(RESERVED, _ref6) >= 0) {
throw new SyntaxError("assignment to a reserved word: " + (obj.compile(o)) + " = " + (value.compile(o)));
obj.error("assignment to a reserved word: " + (obj.compile(o)));
}
return new Assign(obj, value, null, {
param: this.param
@@ -1692,8 +1689,7 @@
} else {
name = obj.unwrap().value;
if (obj instanceof Splat) {
obj = obj.name.compileToFragments(o);
throw new SyntaxError("multiple splats are disallowed in an assignment: " + obj + "...");
obj.error("multiple splats are disallowed in an assignment");
}
if (typeof idx === 'number') {
idx = new Literal(splat || idx);
@@ -1704,7 +1700,7 @@
val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]);
}
if ((name != null) && __indexOf.call(RESERVED, name) >= 0) {
throw new SyntaxError("assignment to a reserved word: " + (obj.compile(o)) + " = " + (val.compile(o)));
obj.error("assignment to a reserved word: " + (obj.compile(o)));
}
assigns.push(new Assign(obj, val, null, {
param: this.param,
@@ -1726,7 +1722,7 @@
var left, right, _ref4;
_ref4 = this.variable.cacheReference(o), left = _ref4[0], right = _ref4[1];
if (!left.properties.length && left.base instanceof Literal && left.base.value !== "this" && !o.scope.check(left.base.value)) {
throw new SyntaxError("the variable \"" + left.base.value + "\" can't be assigned with " + this.context + " because it has not been defined.");
this.variable.error("the variable \"" + left.base.value + "\" can't be assigned with " + this.context + " because it has not been declared before");
}
if (__indexOf.call(this.context, "?") >= 0) {
o.isExistentialEquals = true;
@@ -1793,7 +1789,7 @@
Code.prototype.jumps = NO;
Code.prototype.compileNode = function(o) {
var answer, code, exprs, i, idt, lit, name, p, param, params, ref, splats, uniqs, val, wasEmpty, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref10, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
var answer, code, exprs, i, idt, lit, p, param, params, ref, splats, uniqs, val, wasEmpty, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref4, _ref5, _ref6, _ref7, _ref8;
o.scope = new Scope(o.scope, this.body, this);
o.scope.shared = del(o, 'sharedScope');
o.indent += TAB;
@@ -1801,22 +1797,20 @@
delete o.isExistentialEquals;
params = [];
exprs = [];
_ref4 = this.paramNames();
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
name = _ref4[_i];
this.eachParamName(function(name) {
if (!o.scope.check(name)) {
o.scope.parameter(name);
return o.scope.parameter(name);
}
}
_ref5 = this.params;
for (_j = 0, _len1 = _ref5.length; _j < _len1; _j++) {
param = _ref5[_j];
});
_ref4 = this.params;
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
param = _ref4[_i];
if (!param.splat) {
continue;
}
_ref6 = this.params;
for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) {
p = _ref6[_k].name;
_ref5 = this.params;
for (_j = 0, _len1 = _ref5.length; _j < _len1; _j++) {
p = _ref5[_j].name;
if (p["this"]) {
p = p.properties[0].name;
}
@@ -1825,20 +1819,20 @@
}
}
splats = new Assign(new Value(new Arr((function() {
var _l, _len3, _ref7, _results;
_ref7 = this.params;
var _k, _len2, _ref6, _results;
_ref6 = this.params;
_results = [];
for (_l = 0, _len3 = _ref7.length; _l < _len3; _l++) {
p = _ref7[_l];
for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) {
p = _ref6[_k];
_results.push(p.asReference(o));
}
return _results;
}).call(this))), new Value(new Literal('arguments')));
break;
}
_ref7 = this.params;
for (_l = 0, _len3 = _ref7.length; _l < _len3; _l++) {
param = _ref7[_l];
_ref6 = this.params;
for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) {
param = _ref6[_k];
if (param.isComplex()) {
val = ref = param.asReference(o);
if (param.value) {
@@ -1864,27 +1858,25 @@
exprs.unshift(splats);
}
if (exprs.length) {
(_ref8 = this.body.expressions).unshift.apply(_ref8, exprs);
(_ref7 = this.body.expressions).unshift.apply(_ref7, exprs);
}
for (i = _m = 0, _len4 = params.length; _m < _len4; i = ++_m) {
for (i = _l = 0, _len3 = params.length; _l < _len3; i = ++_l) {
p = params[i];
params[i] = p.compileToFragments(o);
o.scope.parameter(fragmentsToText(params[i]));
}
uniqs = [];
_ref9 = this.paramNames();
for (_n = 0, _len5 = _ref9.length; _n < _len5; _n++) {
name = _ref9[_n];
this.eachParamName(function(name, node) {
if (__indexOf.call(uniqs, name) >= 0) {
throw SyntaxError("multiple parameters named '" + name + "'");
node.error("multiple parameters named '" + name + "'");
}
uniqs.push(name);
}
return uniqs.push(name);
});
if (!(wasEmpty || this.noReturn)) {
this.body.makeReturn();
}
if (this.bound) {
if ((_ref10 = o.scope.parent.method) != null ? _ref10.bound : void 0) {
if ((_ref8 = o.scope.parent.method) != null ? _ref8.bound : void 0) {
this.bound = this.context = o.scope.parent.method.context;
} else if (!this["static"]) {
o.scope.parent.assign('_this', 'this');
@@ -1897,7 +1889,7 @@
}
code += '(';
answer = [this.makeCode(code)];
for (i = _o = 0, _len6 = params.length; _o < _len6; i = ++_o) {
for (i = _m = 0, _len4 = params.length; _m < _len4; i = ++_m) {
p = params[i];
if (i) {
answer.push(this.makeCode(", "));
@@ -1919,15 +1911,15 @@
}
};
Code.prototype.paramNames = function() {
var names, param, _i, _len, _ref4;
names = [];
Code.prototype.eachParamName = function(iterator) {
var param, _i, _len, _ref4, _results;
_ref4 = this.params;
_results = [];
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
param = _ref4[_i];
names.push.apply(names, param.names());
_results.push(param.eachName(iterator));
}
return names;
return _results;
};
Code.prototype.traverseChildren = function(crossScope, func) {
@@ -1950,7 +1942,7 @@
this.value = value;
this.splat = splat;
if (_ref4 = (name = this.name.unwrapAll().value), __indexOf.call(STRICT_PROSCRIBED, _ref4) >= 0) {
throw SyntaxError("parameter name \"" + name + "\" is not allowed");
this.name.error("parameter name \"" + name + "\" is not allowed");
}
}
@@ -1985,47 +1977,44 @@
return this.name.isComplex();
};
Param.prototype.names = function(name) {
var atParam, names, obj, _i, _len, _ref4;
Param.prototype.eachName = function(iterator, name) {
var atParam, node, obj, _i, _len, _ref4;
if (name == null) {
name = this.name;
}
atParam = function(obj) {
var value;
value = obj.properties[0].name.value;
if (value.reserved) {
return [];
} else {
return [value];
var node;
node = obj.properties[0].name;
if (!node.value.reserved) {
return iterator(node.value, node);
}
};
if (name instanceof Literal) {
return [name.value];
return iterator(name.value, name);
}
if (name instanceof Value) {
return atParam(name);
}
names = [];
_ref4 = name.objects;
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
obj = _ref4[_i];
if (obj instanceof Assign) {
names.push.apply(names, this.names(obj.value.unwrap()));
this.eachName(iterator, obj.value.unwrap());
} else if (obj instanceof Splat) {
names.push(obj.name.unwrap().value);
node = obj.name.unwrap();
iterator(node.value, node);
} else if (obj instanceof Value) {
if (obj.isArray() || obj.isObject()) {
names.push.apply(names, this.names(obj.base));
this.eachName(iterator, obj.base);
} else if (obj["this"]) {
names.push.apply(names, atParam(obj));
atParam(obj);
} else {
names.push(obj.base.value);
iterator(obj.base.value, obj.base);
}
} else {
throw SyntaxError("illegal parameter " + (obj.compile()));
obj.error("illegal parameter " + (obj.compile()));
}
}
return names;
};
return Param;
@@ -2304,10 +2293,10 @@
this.first.front = this.front;
}
if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) {
throw SyntaxError('delete operand may not be argument or var');
this.error('delete operand may not be argument or var');
}
if (((_ref4 = this.operator) === '--' || _ref4 === '++') && (_ref5 = this.first.unwrapAll().value, __indexOf.call(STRICT_PROSCRIBED, _ref5) >= 0)) {
throw SyntaxError('prefix increment/decrement may not have eval or arguments operand');
this.error("cannot increment/decrement \"" + (this.first.unwrapAll().value) + "\"");
}
if (this.isUnary()) {
return this.compileUnary(o);
@@ -2464,9 +2453,9 @@
__extends(Try, _super);
function Try(attempt, error, recovery, ensure) {
function Try(attempt, errorVariable, recovery, ensure) {
this.attempt = attempt;
this.error = error;
this.errorVariable = errorVariable;
this.recovery = recovery;
this.ensure = ensure;
}
@@ -2491,25 +2480,10 @@
};
Try.prototype.compileNode = function(o) {
var catchPart, ensurePart, placeholder, tryPart;
var catchPart, ensurePart, placeholder, tryPart, _ref4;
o.indent += TAB;
tryPart = this.attempt.compileToFragments(o, LEVEL_TOP);
catchPart = (function() {
var _ref4;
if (this.recovery) {
placeholder = new Literal('_error');
this.recovery.unshift(new Assign(this.error, placeholder));
this.error = placeholder;
if (_ref4 = this.error.value, __indexOf.call(STRICT_PROSCRIBED, _ref4) >= 0) {
throw SyntaxError("catch variable may not be \"" + this.error.value + "\"");
}
return [].concat(this.makeCode(" catch ("), this.error.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"));
} else if (!(this.ensure || this.recovery)) {
return [this.makeCode(' catch (_error) {}')];
} else {
return [];
}
}).call(this);
catchPart = this.recovery ? (placeholder = new Literal('_error'), this.recovery.unshift(new Assign(this.errorVariable, placeholder)), this.errorVariable = placeholder, (_ref4 = this.errorVariable.value, __indexOf.call(STRICT_PROSCRIBED, _ref4) >= 0) ? this.errorVariable.error("catch variable may not be \"" + this.errorVariable.value + "\"") : void 0, [].concat(this.makeCode(" catch ("), this.errorVariable.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}"))) : !(this.ensure || this.recovery) ? [this.makeCode(' catch (_error) {}')] : [];
ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")) : [];
return [].concat(this.makeCode("" + this.tab + "try {\n"), tryPart, this.makeCode("\n" + this.tab + "}"), catchPart, ensurePart);
};
@@ -2623,15 +2597,15 @@
_ref4 = [this.index, this.name], this.name = _ref4[0], this.index = _ref4[1];
}
if (this.index instanceof Value) {
throw SyntaxError('index cannot be a pattern matching expression');
this.index.error('index cannot be a pattern matching expression');
}
this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length;
this.pattern = this.name instanceof Value;
if (this.range && this.index) {
throw SyntaxError('indexes do not apply to range loops');
this.index.error('indexes do not apply to range loops');
}
if (this.range && this.pattern) {
throw SyntaxError('cannot pattern match over range loops');
this.name.error('cannot pattern match over range loops');
}
this.returns = false;
}
@@ -2995,19 +2969,20 @@
Closure = {
wrap: function(expressions, statement, noReturn) {
var args, call, func, mentionsArgs, meth;
var args, argumentsNode, call, func, meth;
if (expressions.jumps()) {
return expressions;
}
func = new Code([], Block.wrap([expressions]));
args = [];
if ((mentionsArgs = expressions.contains(this.literalArgs)) || expressions.contains(this.literalThis)) {
if (mentionsArgs && expressions.classBody) {
throw SyntaxError("Class bodies shouldn't reference arguments");
}
meth = new Literal(mentionsArgs ? 'apply' : 'call');
argumentsNode = expressions.contains(this.isLiteralArguments);
if (argumentsNode && expressions.classBody) {
argumentsNode.error("Class bodies shouldn't reference arguments");
}
if (argumentsNode || expressions.contains(this.isLiteralThis)) {
meth = new Literal(argumentsNode ? 'apply' : 'call');
args = [new Literal('this')];
if (mentionsArgs) {
if (argumentsNode) {
args.push(new Literal('arguments'));
}
func = new Value(func, [new Access(meth)]);
@@ -3020,10 +2995,10 @@
return call;
}
},
literalArgs: function(node) {
isLiteralArguments: function(node) {
return node instanceof Literal && node.value === 'arguments' && !node.asKey;
},
literalThis: function(node) {
isLiteralThis: function(node) {
return (node instanceof Literal && node.value === 'this' && !node.asKey) || (node instanceof Code && node.bound) || (node instanceof Call && node.isSuper);
}
};

View File

@@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.1
(function() {
var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm;
var CoffeeScript, addMultilineHandler, merge, nodeREPL, prettyErrorMessage, replDefaults, vm, _ref;
vm = require('vm');
@@ -8,27 +8,26 @@
CoffeeScript = require('./coffee-script');
merge = require('./helpers').merge;
_ref = require('./helpers'), merge = _ref.merge, prettyErrorMessage = _ref.prettyErrorMessage;
replDefaults = {
prompt: 'coffee> ',
"eval": function(input, context, filename, cb) {
var err, js;
var Assign, Block, Literal, Value, ast, err, js, _ref1;
input = input.replace(/\uFF00/g, '\n');
input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3');
if (/^(\s*|\(\s*\))$/.test(input)) {
return cb(null);
}
input = input.replace(/^\(([\s\S]*)\n\)$/m, '$1');
_ref1 = require('./nodes'), Block = _ref1.Block, Assign = _ref1.Assign, Value = _ref1.Value, Literal = _ref1.Literal;
try {
js = CoffeeScript.compile("_=(" + input + "\n)", {
filename: filename,
ast = CoffeeScript.nodes(input);
ast = new Block([new Assign(new Value(new Literal('_')), ast, '=')]);
js = ast.compile({
bare: true
});
return cb(null, vm.runInContext(js, context, filename));
} catch (_error) {
err = _error;
return cb(err);
console.log(prettyErrorMessage(err, filename, input, true));
}
return cb(null, vm.runInContext(js, context, filename));
}
};

View File

@@ -41,35 +41,30 @@ exports.helpers = helpers
# lookups.
exports.compile = compile = (code, options = {}) ->
{merge} = exports.helpers
try
if options.sourceMap
sourceMap = new sourcemap.SourceMap()
if options.sourceMap
sourceMap = new sourcemap.SourceMap()
fragments = (parser.parse lexer.tokenize(code, options)).compileToFragments options
fragments = (parser.parse lexer.tokenize(code, options)).compileToFragments options
currentLine = 0
currentLine += 1 if options.header
currentColumn = 0
js = ""
for fragment in fragments
# Update the sourcemap with data from each fragment
if sourceMap
if fragment.locationData
sourceMap.addMapping(
[fragment.locationData.first_line, fragment.locationData.first_column],
[currentLine, currentColumn],
{noReplace: true})
newLines = helpers.count fragment.code, "\n"
currentLine += newLines
currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
currentLine = 0
currentLine += 1 if options.header
currentColumn = 0
js = ""
for fragment in fragments
# Update the sourcemap with data from each fragment
if sourceMap
if fragment.locationData
sourceMap.addMapping(
[fragment.locationData.first_line, fragment.locationData.first_column],
[currentLine, currentColumn],
{noReplace: true})
newLines = helpers.count fragment.code, "\n"
currentLine += newLines
currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
# Copy the code from each fragment into the final JavaScript.
js += fragment.code
catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename
throw err
# Copy the code from each fragment into the final JavaScript.
js += fragment.code
if options.header
header = "Generated by CoffeeScript #{@VERSION}"
@@ -175,4 +170,15 @@ parser.lexer =
upcomingInput: ->
""
# Make all the AST nodes visible to the parser.
parser.yy = require './nodes'
# Override Jison's default error handling function.
parser.yy.parseError = (message, {token}) ->
# Disregard Jison's message, it contains redundant line numer information.
message = "unexpected #{if token is 1 then 'end of input' else token}"
# The second argument has a `loc` property, which should have the location
# data for this token. Unfortunately, Jison seems to send an outdated `loc`
# (from the previous token), so we take the location information directly
# from the lexer.
helpers.throwSyntaxError message, parser.lexer.yylloc

View File

@@ -5,13 +5,13 @@
# interactive REPL.
# External dependencies.
fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events'
fs = require 'fs'
path = require 'path'
helpers = require './helpers'
optparse = require './optparse'
CoffeeScript = require './coffee-script'
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events'
exists = fs.exists or path.exists
@@ -142,9 +142,13 @@ compileScript = (file, input, base=null) ->
catch err
CoffeeScript.emit 'failure', err, task
return if CoffeeScript.listeners('failure').length
return printLine err.message + '\x07' if o.watch
printWarn err instanceof Error and err.stack or "ERROR: #{err}"
process.exit 1
useColors = process.stdout.isTTY and not process.env.NODE_DISABLE_COLORS
message = helpers.prettyErrorMessage err, file or '[stdin]', input, useColors
if o.watch
printLine message + '\x07'
else
printWarn message
process.exit 1
# Attach the appropriate listeners to compile scripts incoming over **stdin**,
# and write them back to **stdout**.

View File

@@ -92,8 +92,9 @@ buildLocationData = (first, last) ->
last_line: last.last_line
last_column: last.last_column
# This returns a function which takes an object as a parameter, and if that object is an AST node,
# updates that object's locationData. The object is returned either way.
# This returns a function which takes an object as a parameter, and if that
# object is an AST node, updates that object's locationData.
# The object is returned either way.
exports.addLocationDataFn = (first, last) ->
(obj) ->
if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
@@ -128,3 +129,41 @@ exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
# Determine if a filename represents a Literate CoffeeScript file.
exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
# Throws a SyntaxError with a source file location data attached to it in a
# property called `location`.
exports.throwSyntaxError = (message, location) ->
location.last_line ?= location.first_line
location.last_column ?= location.first_column
error = new SyntaxError message
error.location = location
throw error
# Creates a nice error message like, following the "standard" format
# <filename>:<line>:<col>: <message> plus the line with the error and a marker
# showing where the error is.
exports.prettyErrorMessage = (error, fileName, code, useColors) ->
return error.stack or "#{error}" unless error.location
{first_line, first_column, last_line, last_column} = error.location
codeLine = code.split('\n')[first_line]
start = first_column
# Show only the first line on multi-line errors.
end = if first_line is last_line then last_column + 1 else codeLine.length
marker = repeat(' ', start) + repeat('^', end - start)
if useColors
colorize = (str) -> "\x1B[1;31m#{str}\x1B[0m"
codeLine = codeLine[...start] + colorize(codeLine[start...end]) + codeLine[end..]
marker = colorize marker
message = """
#{fileName}:#{first_line + 1}:#{first_column + 1}: error: #{error.message}
#{codeLine}
#{marker}
"""
# Uncomment to add stacktrace.
#message += "\n#{error.stack}"
message

View File

@@ -12,7 +12,8 @@
{Rewriter, INVERSES} = require './rewriter'
# Import the helpers we need.
{count, starts, compact, last, invertLiterate, locationDataToString} = require './helpers'
{count, starts, compact, last, invertLiterate, locationDataToString,
throwSyntaxError} = require './helpers'
# The Lexer Class
# ---------------
@@ -40,7 +41,7 @@ exports.Lexer = class Lexer
@outdebt = 0 # The under-outdentation at the current level.
@indents = [] # The stack of all current indentation levels.
@ends = [] # The stack for pairing up tokens.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, location data]`.
@chunkLine =
opts.line or 0 # The start line for the current @chunk.
@@ -690,11 +691,11 @@ exports.Lexer = class Lexer
body = body.replace /// #{quote} ///g, '\\$&'
quote + @escapeLines(body, heredoc) + quote
# Throws a syntax error on the current `@line`.
# Throws a compiler error on the current position.
error: (message) ->
# TODO: Are there some cases we could improve the error line number by
# passing the offset in the chunk where the error happened?
throw SyntaxError "#{message} on line #{ @chunkLine + 1 }"
throwSyntaxError message, first_line: @chunkLine, first_column: @chunkColumn
# Constants
# ---------

View File

@@ -9,7 +9,8 @@ Error.stackTraceLimit = Infinity
{RESERVED, STRICT_PROSCRIBED} = require './lexer'
# Import the helpers we plan to use.
{compact, flatten, extend, merge, del, starts, ends, last, some, addLocationDataFn, locationDataToString} = require './helpers'
{compact, flatten, extend, merge, del, starts, ends, last, some,
addLocationDataFn, locationDataToString, throwSyntaxError} = require './helpers'
# Functions required by parser
exports.extend = extend
@@ -75,8 +76,8 @@ exports.Base = class Base
# Statements converted into expressions via closure-wrapping share a scope
# object with their parent closure, to preserve the expected lexical scope.
compileClosure: (o) ->
if @jumps()
throw SyntaxError 'cannot use a pure statement in an expression.'
if jumpNode = @jumps()
jumpNode.error 'cannot use a pure statement in an expression'
o.sharedScope = yes
Closure.wrap(this).compileNode o
@@ -110,20 +111,16 @@ exports.Base = class Base
new Return me
# Does this node, or any of its children, contain a node of a certain kind?
# Recursively traverses down the *children* of the nodes, yielding to a block
# and returning true when the block finds a match. `contains` does not cross
# Recursively traverses down the *children* nodes and returns the first one
# that verifies `pred`. Otherwise return undefined. `contains` does not cross
# scope boundaries.
contains: (pred) ->
contains = no
@traverseChildren no, (node) ->
if pred node
contains = yes
node = undefined
@traverseChildren no, (n) ->
if pred n
node = n
return no
contains
# Is this node of a certain type, or does it contain the type?
containsType: (type) ->
this instanceof type or @contains (node) -> node instanceof type
node
# Pull out the last non-comment node of a node list.
lastNonComment: (list) ->
@@ -176,16 +173,18 @@ exports.Base = class Base
# Is this node used to assign a certain variable?
assigns: NO
# For this node and all descendents, set the location data to `locationData` if the location
# data is not already set.
# For this node and all descendents, set the location data to `locationData`
# if the location data is not already set.
updateLocationDataIfMissing: (locationData) ->
if not @locationData
@locationData = {}
extend @locationData, locationData
@locationData or= locationData
@eachChild (child) ->
child.updateLocationDataIfMissing locationData
# Throw a SyntaxError associated with this node's location.
error: (message) ->
throwSyntaxError message, @locationData
makeCode: (code) ->
new CodeFragment this, code
@@ -588,7 +587,7 @@ exports.Call = class Call extends Base
else if method?.ctor
"#{method.name}.__super__.constructor"
else
throw SyntaxError 'cannot call super outside of an instance method.'
@error 'cannot call super outside of an instance method.'
# The appropriate `this` value for a `super` call.
superThis : (o) ->
@@ -882,7 +881,7 @@ exports.Obj = class Obj extends Base
return [@makeCode(if @front then '({})' else '{}')] unless props.length
if @generated
for node in props when node instanceof Value
throw new SyntaxError 'cannot have an implicit value in an implicit object'
node.error 'cannot have an implicit value in an implicit object'
idt = o.indent += TAB
lastNoncom = @lastNonComment @properties
answer = []
@@ -966,7 +965,7 @@ exports.Class = class Class extends Base
else
@variable.base.value
if decl in STRICT_PROSCRIBED
throw SyntaxError "variable name may not be #{decl}"
@variable.error "class variable name may not be #{decl}"
decl and= IDENTIFIER.test(decl) and decl
# For all `this`-references and bound functions in the class definition,
@@ -999,9 +998,9 @@ exports.Class = class Class extends Base
func = assign.value
if base.value is 'constructor'
if @ctor
throw new SyntaxError 'cannot define more than one constructor in a class'
assign.error 'cannot define more than one constructor in a class'
if func.bound
throw new SyntaxError 'cannot define a constructor as a bound function'
assign.error 'cannot define a constructor as a bound function'
if func instanceof Code
assign = @ctor = func
else
@@ -1107,7 +1106,7 @@ exports.Assign = class Assign extends Base
@subpattern = options and options.subpattern
forbidden = (name = @variable.unwrapAll().value) in STRICT_PROSCRIBED
if forbidden and @context isnt 'object'
throw SyntaxError "variable name may not be \"#{name}\""
@variable.error "variable name may not be \"#{name}\""
children: ['variable', 'value']
@@ -1132,8 +1131,9 @@ exports.Assign = class Assign extends Base
compiledName = @variable.compileToFragments o, LEVEL_LIST
name = fragmentsToText compiledName
unless @context
unless (varBase = @variable.unwrapAll()).isAssignable()
throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned."
varBase = @variable.unwrapAll()
unless varBase.isAssignable()
@variable.error "\"#{@variable.compile o}\" cannot be assigned"
unless varBase.hasProperties?()
if @param
o.scope.add name, 'var'
@@ -1172,7 +1172,7 @@ exports.Assign = class Assign extends Base
value = new Value value
value.properties.push new (if acc then Access else Index) idx
if obj.unwrap().value in RESERVED
throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}"
obj.error "assignment to a reserved word: #{obj.compile o}"
return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
@@ -1210,9 +1210,7 @@ exports.Assign = class Assign extends Base
else
name = obj.unwrap().value
if obj instanceof Splat
obj = obj.name.compileToFragments o
throw new SyntaxError \
"multiple splats are disallowed in an assignment: #{obj}..."
obj.error "multiple splats are disallowed in an assignment"
if typeof idx is 'number'
idx = new Literal splat or idx
acc = no
@@ -1220,7 +1218,7 @@ exports.Assign = class Assign extends Base
acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
if name? and name in RESERVED
throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}"
obj.error "assignment to a reserved word: #{obj.compile o}"
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, ', '
@@ -1234,7 +1232,7 @@ exports.Assign = class Assign extends Base
# Disallow conditional assignment of undefined variables.
if not left.properties.length and left.base instanceof Literal and
left.base.value != "this" and not o.scope.check left.base.value
throw new SyntaxError "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
@variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
if "?" in @context then o.isExistentialEquals = true
new Op(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o
@@ -1291,7 +1289,7 @@ exports.Code = class Code extends Base
delete o.isExistentialEquals
params = []
exprs = []
for name in @paramNames() # this step must be performed before the others
@eachParamName (name) -> # this step must be performed before the others
unless o.scope.check name then o.scope.parameter name
for param in @params when param.splat
for {name: p} in @params
@@ -1319,8 +1317,8 @@ exports.Code = class Code extends Base
params[i] = p.compileToFragments o
o.scope.parameter fragmentsToText params[i]
uniqs = []
for name in @paramNames()
throw SyntaxError "multiple parameters named '#{name}'" if name in uniqs
@eachParamName (name, node) ->
node.error "multiple parameters named '#{name}'" if name in uniqs
uniqs.push name
@body.makeReturn() unless wasEmpty or @noReturn
if @bound
@@ -1342,12 +1340,9 @@ exports.Code = class Code extends Base
return [@makeCode(@tab), answer...] if @ctor
if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
# A list of parameter names, excluding those generated by the compiler.
paramNames: ->
names = []
names.push param.names()... for param in @params
names
eachParamName: (iterator) ->
param.eachName iterator for param in @params
# Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries
# unless `crossScope` is `true`.
@@ -1362,7 +1357,7 @@ exports.Code = class Code extends Base
exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
throw SyntaxError "parameter name \"#{name}\" is not allowed"
@name.error "parameter name \"#{name}\" is not allowed"
children: ['name', 'value']
@@ -1385,41 +1380,40 @@ exports.Param = class Param extends Base
isComplex: ->
@name.isComplex()
# Finds the name or names of a `Param`; useful for detecting duplicates.
# In a sense, a destructured parameter represents multiple JS parameters,
# thus this method returns an `Array` of names.
# Reserved words used as param names, as well as the Object and Array
# literals used for destructured params, get a compiler generated name
# during the `Code` compilation step, so this is necessarily an incomplete
# list of a parameter's names.
names: (name = @name)->
# Iterates the name or names of a `Param`.
# In a sense, a destructured parameter represents multiple JS parameters. This
# method allows to iterate them all.
# The `iterator` function will be called as `iterator(name, node)` where
# `name` is the name of the parameter and `node` is the AST node corresponding
# to that name.
eachName: (iterator, name = @name)->
atParam = (obj) ->
{value} = obj.properties[0].name
return if value.reserved then [] else [value]
node = obj.properties[0].name
iterator node.value, node unless node.value.reserved
# * simple literals `foo`
return [name.value] if name instanceof Literal
return iterator name.value, name if name instanceof Literal
# * at-params `@foo`
return atParam(name) if name instanceof Value
names = []
return atParam name if name instanceof Value
for obj in name.objects
# * assignments within destructured parameters `{foo:bar}`
if obj instanceof Assign
names.push @names(obj.value.unwrap())...
@eachName iterator, obj.value.unwrap()
# * splats within destructured parameters `[xs...]`
else if obj instanceof Splat
names.push obj.name.unwrap().value
node = obj.name.unwrap()
iterator node.value, node
else if obj instanceof Value
# * destructured parameters within destructured parameters `[{a}]`
if obj.isArray() or obj.isObject()
names.push @names(obj.base)...
@eachName iterator, obj.base
# * at-params within destructured parameters `{@foo}`
else if obj.this
names.push atParam(obj)...
atParam obj
# * simple destructured parameters {foo}
else names.push obj.base.value
else iterator obj.base.value, obj.base
else
throw SyntaxError "illegal parameter #{obj.compile()}"
names
obj.error "illegal parameter #{obj.compile()}"
return
#### Splat
@@ -1620,9 +1614,9 @@ exports.Op = class Op extends Base
# as the chained expression is wrapped.
@first.front = @front unless isChain
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
throw SyntaxError '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
throw SyntaxError 'prefix increment/decrement may not have eval or arguments operand'
@error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
return @compileUnary o if @isUnary()
return @compileChain o if isChain
return @compileExistence o if @operator is '?'
@@ -1715,7 +1709,7 @@ exports.In = class In extends Base
# A classic *try/catch/finally* block.
exports.Try = class Try extends Base
constructor: (@attempt, @error, @recovery, @ensure) ->
constructor: (@attempt, @errorVariable, @recovery, @ensure) ->
children: ['attempt', 'recovery', 'ensure']
@@ -1736,11 +1730,11 @@ exports.Try = class Try extends Base
catchPart = if @recovery
placeholder = new Literal '_error'
@recovery.unshift new Assign @error, placeholder
@error = placeholder
if @error.value in STRICT_PROSCRIBED
throw SyntaxError "catch variable may not be \"#{@error.value}\""
[].concat @makeCode(" catch ("), @error.compileToFragments(o), @makeCode(") {\n"),
@recovery.unshift new Assign @errorVariable, placeholder
@errorVariable = placeholder
if @errorVariable.value in STRICT_PROSCRIBED
@errorVariable.error "catch variable may not be \"#{@errorVariable.value}\""
[].concat @makeCode(" catch ("), @errorVariable.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
else unless @ensure or @recovery
[@makeCode(' catch (_error) {}')]
@@ -1834,11 +1828,11 @@ exports.For = class For extends While
@own = !!source.own
@object = !!source.object
[@name, @index] = [@index, @name] if @object
throw SyntaxError 'index cannot be a pattern matching expression' if @index instanceof Value
@index.error 'index cannot be a pattern matching expression' if @index instanceof Value
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
@pattern = @name instanceof Value
throw SyntaxError 'indexes do not apply to range loops' if @range and @index
throw SyntaxError 'cannot pattern match over range loops' if @range and @pattern
@index.error 'indexes do not apply to range loops' if @range and @index
@name.error 'cannot pattern match over range loops' if @range and @pattern
@returns = false
children: ['body', 'source', 'guard', 'step']
@@ -2080,21 +2074,22 @@ Closure =
return expressions if expressions.jumps()
func = new Code [], Block.wrap [expressions]
args = []
if (mentionsArgs = expressions.contains @literalArgs) or expressions.contains @literalThis
if mentionsArgs and expressions.classBody
throw SyntaxError "Class bodies shouldn't reference arguments"
meth = new Literal if mentionsArgs then 'apply' else 'call'
argumentsNode = expressions.contains @isLiteralArguments
if argumentsNode and expressions.classBody
argumentsNode.error "Class bodies shouldn't reference arguments"
if argumentsNode or expressions.contains @isLiteralThis
meth = new Literal if argumentsNode then 'apply' else 'call'
args = [new Literal 'this']
args.push new Literal 'arguments' if mentionsArgs
args.push new Literal 'arguments' if argumentsNode
func = new Value func, [new Access meth]
func.noReturn = noReturn
call = new Call func, args
if statement then Block.wrap [call] else call
literalArgs: (node) ->
isLiteralArguments: (node) ->
node instanceof Literal and node.value is 'arguments' and not node.asKey
literalThis: (node) ->
isLiteralThis: (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
(node instanceof Code and node.bound) or
(node instanceof Call and node.isSuper)

View File

@@ -1,23 +1,32 @@
vm = require 'vm'
nodeREPL = require 'repl'
CoffeeScript = require './coffee-script'
{merge} = require './helpers'
{merge, prettyErrorMessage} = require './helpers'
replDefaults =
prompt: 'coffee> ',
eval: (input, context, filename, cb) ->
# XXX: multiline hack
# XXX: multiline hack.
input = input.replace /\uFF00/g, '\n'
# strip single-line comments
input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
# empty command
return cb null if /^(\s*|\(\s*\))$/.test input
# Node's REPL sends the input ending with a newline and then wrapped in
# parens. Unwrap all that.
input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'
# Require AST nodes to do some AST manipulation.
{Block, Assign, Value, Literal} = require './nodes'
# TODO: fix #1829: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
try
js = CoffeeScript.compile "_=(#{input}\n)", {filename, bare: yes}
cb null, vm.runInContext(js, context, filename)
# Generate the AST of the clean input.
ast = CoffeeScript.nodes input
# Add assignment to `_` variable to force the input to be an expression.
ast = new Block [
new Assign (new Value new Literal '_'), ast, '='
]
js = ast.compile bare: yes
catch err
cb err
console.log prettyErrorMessage err, filename, input, yes
cb null, vm.runInContext(js, context, filename)
addMultilineHandler = (repl) ->
{rli, inputStream, outputStream} = repl

View File

@@ -0,0 +1,44 @@
# Error Formating
# ---------------
# Ensure that errors of different kinds (lexer, parser and compiler) are shown
# in a consistent way.
{prettyErrorMessage} = CoffeeScript.helpers
assertErrorFormat = (code, expectedErrorFormat) ->
throws (-> CoffeeScript.compile code), (err) ->
message = prettyErrorMessage err, 'test.coffee', code
eq expectedErrorFormat, message
yes
test "lexer errors formating", ->
assertErrorFormat '''
normalObject = {}
insideOutObject = }{
''',
'''
test.coffee:2:19: error: unmatched }
insideOutObject = }{
^
'''
test "parser error formating", ->
assertErrorFormat '''
foo in bar or in baz
''',
'''
test.coffee:1:15: error: unexpected RELATION
foo in bar or in baz
^^
'''
test "compiler error formatting", ->
assertErrorFormat '''
evil = (foo, eval, bar) ->
''',
'''
test.coffee:1:14: error: parameter name "eval" is not allowed
evil = (foo, eval, bar) ->
^^^^
'''

View File

@@ -143,4 +143,4 @@ test "#1299: Disallow token misnesting", ->
'''
ok no
catch e
eq 'unmatched ] on line 2', e.message
eq 'unmatched ]', e.message

View File

@@ -2,7 +2,7 @@
# -------
# pull the helpers from `CoffeeScript.helpers` into local variables
{starts, ends, compact, count, merge, extend, flatten, del, last, baseFileName} = CoffeeScript.helpers
{starts, ends, repeat, compact, count, merge, extend, flatten, del, last, baseFileName} = CoffeeScript.helpers
# `starts`
@@ -27,6 +27,15 @@ test "the `ends` helper can take an optional offset", ->
ok not ends('01234', '234', 6)
# `repeat`
test "the `repeat` helper concatenates a given number of times", ->
eq 'asdasdasd', repeat('asd', 3)
test "`repeat`ing a string 0 times always returns the empty string", ->
eq '', repeat('whatever', 0)
# `compact`
test "the `compact` helper removes falsey values from an array, preserves truthy ones", ->