mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Merge pull request #2723 from epidemian/improved-error-messages
Improved error messages
This commit is contained in:
@@ -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}"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
53
lib/coffee-script/error.js
Normal file
53
lib/coffee-script/error.js
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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**.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
# ---------
|
||||
|
||||
157
src/nodes.coffee
157
src/nodes.coffee
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
44
test/error_messages.coffee
Normal file
44
test/error_messages.coffee
Normal 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) ->
|
||||
^^^^
|
||||
'''
|
||||
@@ -143,4 +143,4 @@ test "#1299: Disallow token misnesting", ->
|
||||
'''
|
||||
ok no
|
||||
catch e
|
||||
eq 'unmatched ] on line 2', e.message
|
||||
eq 'unmatched ]', e.message
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
Reference in New Issue
Block a user