mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Merge pull request #3840 from lydell/dynakeys
Fix #3597: Allow interpolations in object keys
This commit is contained in:
@@ -264,7 +264,18 @@
|
||||
token = arg.token;
|
||||
errorToken = parser.errorToken, tokens = parser.tokens;
|
||||
errorTag = errorToken[0], errorText = errorToken[1], errorLoc = errorToken[2];
|
||||
errorText = errorToken === tokens[tokens.length - 1] ? 'end of input' : errorTag === 'INDENT' || errorTag === 'OUTDENT' ? 'indentation' : helpers.nameWhitespaceCharacter(errorText);
|
||||
errorText = (function() {
|
||||
switch (false) {
|
||||
case errorToken !== tokens[tokens.length - 1]:
|
||||
return 'end of input';
|
||||
case errorTag !== 'INDENT' && errorTag !== 'OUTDENT':
|
||||
return 'indentation';
|
||||
case errorTag !== 'IDENTIFIER' && errorTag !== 'NUMBER' && errorTag !== 'STRING' && errorTag !== 'STRING_START' && errorTag !== 'REGEX' && errorTag !== 'REGEX_START':
|
||||
return errorTag.replace(/_START$/, '').toLowerCase();
|
||||
default:
|
||||
return helpers.nameWhitespaceCharacter(errorText);
|
||||
}
|
||||
})();
|
||||
return helpers.throwSyntaxError("unexpected " + errorText, errorLoc);
|
||||
};
|
||||
|
||||
|
||||
@@ -63,16 +63,26 @@
|
||||
AlphaNumeric: [
|
||||
o('NUMBER', function() {
|
||||
return new Literal($1);
|
||||
}), o('STRING', function() {
|
||||
}), o('String')
|
||||
],
|
||||
String: [
|
||||
o('STRING', function() {
|
||||
return new Literal($1);
|
||||
}), o('STRING_START Body STRING_END', function() {
|
||||
return new Parens($2);
|
||||
})
|
||||
],
|
||||
Regex: [
|
||||
o('REGEX', function() {
|
||||
return new Literal($1);
|
||||
}), o('REGEX_START Invocation REGEX_END', function() {
|
||||
return $2;
|
||||
})
|
||||
],
|
||||
Literal: [
|
||||
o('AlphaNumeric'), o('JS', function() {
|
||||
return new Literal($1);
|
||||
}), o('REGEX', function() {
|
||||
return new Literal($1);
|
||||
}), o('DEBUGGER', function() {
|
||||
}), o('Regex'), o('DEBUGGER', function() {
|
||||
return new Literal($1);
|
||||
}), o('UNDEFINED', function() {
|
||||
return new Undefined;
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
};
|
||||
|
||||
Lexer.prototype.regexToken = function() {
|
||||
var body, closed, end, errorToken, flags, index, match, prev, ref2, ref3, ref4, regex, rparen, tokens;
|
||||
var body, closed, end, flags, index, match, origin, prev, ref2, ref3, ref4, regex, tokens;
|
||||
switch (false) {
|
||||
case !(match = REGEX_ILLEGAL.exec(this.chunk)):
|
||||
this.error("regular expressions cannot begin with " + match[2], {
|
||||
@@ -315,7 +315,7 @@
|
||||
index = regex.length;
|
||||
ref2 = this.tokens, prev = ref2[ref2.length - 1];
|
||||
if (prev) {
|
||||
if (prev.spaced && (ref3 = prev[0], indexOf.call(CALLABLE, ref3) >= 0) && !prev.stringEnd && !prev.regexEnd) {
|
||||
if (prev.spaced && (ref3 = prev[0], indexOf.call(CALLABLE, ref3) >= 0)) {
|
||||
if (!closed || POSSIBLY_DIVISION.test(regex)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -332,7 +332,7 @@
|
||||
}
|
||||
flags = REGEX_FLAGS.exec(this.chunk.slice(index))[0];
|
||||
end = index + flags.length;
|
||||
errorToken = this.makeToken('REGEX', this.chunk.slice(0, end), 0, end);
|
||||
origin = this.makeToken('REGEX', null, 0, end);
|
||||
switch (false) {
|
||||
case !!VALID_FLAGS.test(flags):
|
||||
this.error("invalid regular expression flags " + flags, {
|
||||
@@ -346,11 +346,12 @@
|
||||
}
|
||||
this.token('REGEX', "" + (this.makeDelimitedLiteral(body, {
|
||||
delimiter: '/'
|
||||
})) + flags, 0, end, errorToken);
|
||||
})) + flags, 0, end, origin);
|
||||
break;
|
||||
default:
|
||||
this.token('REGEX_START', '(', 0, 0, origin);
|
||||
this.token('IDENTIFIER', 'RegExp', 0, 0);
|
||||
this.token('CALL_START', '(', 0, 0, errorToken);
|
||||
this.token('CALL_START', '(', 0, 0);
|
||||
this.mergeInterpolationTokens(tokens, {
|
||||
delimiter: '"',
|
||||
double: true
|
||||
@@ -359,8 +360,8 @@
|
||||
this.token(',', ',', index, 0);
|
||||
this.token('STRING', '"' + flags + '"', index, flags.length);
|
||||
}
|
||||
rparen = this.token(')', ')', end, 0);
|
||||
rparen.regexEnd = true;
|
||||
this.token(')', ')', end, 0);
|
||||
this.token('REGEX_END', ')', end, 0);
|
||||
}
|
||||
return end;
|
||||
};
|
||||
@@ -522,7 +523,7 @@
|
||||
} else if (indexOf.call(LOGIC, value) >= 0 || value === '?' && (prev != null ? prev.spaced : void 0)) {
|
||||
tag = 'LOGIC';
|
||||
} else if (prev && !prev.spaced) {
|
||||
if (value === '(' && (ref5 = prev[0], indexOf.call(CALLABLE, ref5) >= 0) && !prev.stringEnd && !prev.regexEnd) {
|
||||
if (value === '(' && (ref5 = prev[0], indexOf.call(CALLABLE, ref5) >= 0)) {
|
||||
if (prev[0] === '?') {
|
||||
prev[0] = 'FUNC_EXIST';
|
||||
}
|
||||
@@ -633,6 +634,9 @@
|
||||
firstToken = tokens[0], lastToken = tokens[tokens.length - 1];
|
||||
firstToken[2].first_column -= delimiter.length;
|
||||
lastToken[2].last_column += delimiter.length;
|
||||
if (lastToken[1].length === 0) {
|
||||
lastToken[2].last_column -= 1;
|
||||
}
|
||||
return {
|
||||
tokens: tokens,
|
||||
index: offsetInChunk + delimiter.length
|
||||
@@ -640,18 +644,9 @@
|
||||
};
|
||||
|
||||
Lexer.prototype.mergeInterpolationTokens = function(tokens, options, fn) {
|
||||
var converted, errorToken, firstEmptyStringIndex, firstIndex, firstToken, i, interpolated, j, lastToken, len, locationToken, plusToken, ref2, ref3, rparen, tag, token, tokensToPush, value;
|
||||
if (interpolated = tokens.length > 1) {
|
||||
firstToken = tokens[0];
|
||||
errorToken = [
|
||||
'', 'interpolation', {
|
||||
first_line: firstToken[2].last_line,
|
||||
first_column: firstToken[2].last_column,
|
||||
last_line: firstToken[2].last_line,
|
||||
last_column: firstToken[2].last_column + 1
|
||||
}
|
||||
];
|
||||
this.token('(', '(', 0, 0, errorToken);
|
||||
var converted, firstEmptyStringIndex, firstIndex, i, j, lastToken, len, locationToken, lparen, plusToken, ref2, rparen, tag, token, tokensToPush, value;
|
||||
if (tokens.length > 1) {
|
||||
lparen = this.token('STRING_START', '(', 0, 0);
|
||||
}
|
||||
firstIndex = this.tokens.length;
|
||||
for (i = j = 0, len = tokens.length; j < len; i = ++j) {
|
||||
@@ -693,16 +688,23 @@
|
||||
}
|
||||
(ref2 = this.tokens).push.apply(ref2, tokensToPush);
|
||||
}
|
||||
if (interpolated) {
|
||||
ref3 = this.tokens, lastToken = ref3[ref3.length - 1];
|
||||
rparen = this.token(')', ')');
|
||||
rparen[2] = {
|
||||
if (lparen) {
|
||||
lastToken = tokens[tokens.length - 1];
|
||||
lparen.origin = [
|
||||
'STRING', null, {
|
||||
first_line: lparen[2].first_line,
|
||||
first_column: lparen[2].first_column,
|
||||
last_line: lastToken[2].last_line,
|
||||
last_column: lastToken[2].last_column
|
||||
}
|
||||
];
|
||||
rparen = this.token('STRING_END', ')');
|
||||
return rparen[2] = {
|
||||
first_line: lastToken[2].last_line,
|
||||
first_column: lastToken[2].last_column + 1,
|
||||
first_column: lastToken[2].last_column,
|
||||
last_line: lastToken[2].last_line,
|
||||
last_column: lastToken[2].last_column + 1
|
||||
last_column: lastToken[2].last_column
|
||||
};
|
||||
return rparen.stringEnd = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -987,7 +989,7 @@
|
||||
|
||||
CALLABLE = ['IDENTIFIER', ')', ']', '?', '@', 'THIS', 'SUPER'];
|
||||
|
||||
INDEXABLE = CALLABLE.concat(['NUMBER', 'STRING', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
|
||||
INDEXABLE = CALLABLE.concat(['NUMBER', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
|
||||
|
||||
NOT_REGEX = INDEXABLE.concat(['++', '--']);
|
||||
|
||||
|
||||
@@ -1267,11 +1267,8 @@
|
||||
Obj.prototype.children = ['properties'];
|
||||
|
||||
Obj.prototype.compileNode = function(o) {
|
||||
var answer, i, idt, indent, j, join, k, lastNoncom, len1, len2, node, prop, props;
|
||||
var answer, dynamicIndex, hasDynamic, i, idt, indent, j, join, k, key, l, lastNoncom, len1, len2, len3, node, oref, prop, props, ref3, value;
|
||||
props = this.properties;
|
||||
if (!props.length) {
|
||||
return [this.makeCode(this.front ? '({})' : '{}')];
|
||||
}
|
||||
if (this.generated) {
|
||||
for (j = 0, len1 = props.length; j < len1; j++) {
|
||||
node = props[j];
|
||||
@@ -1280,13 +1277,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
for (dynamicIndex = k = 0, len2 = props.length; k < len2; dynamicIndex = ++k) {
|
||||
prop = props[dynamicIndex];
|
||||
if ((prop.variable || prop).base instanceof Parens) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
hasDynamic = dynamicIndex < props.length;
|
||||
idt = o.indent += TAB;
|
||||
lastNoncom = this.lastNonComment(this.properties);
|
||||
answer = [];
|
||||
for (i = k = 0, len2 = props.length; k < len2; i = ++k) {
|
||||
if (hasDynamic) {
|
||||
oref = o.scope.freeVariable('obj');
|
||||
answer.push(this.makeCode("(\n" + idt + oref + " = "));
|
||||
}
|
||||
answer.push(this.makeCode("{" + (props.length === 0 || dynamicIndex === 0 ? '}' : '\n')));
|
||||
for (i = l = 0, len3 = props.length; l < len3; i = ++l) {
|
||||
prop = props[i];
|
||||
join = i === props.length - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n';
|
||||
if (i === dynamicIndex) {
|
||||
if (i !== 0) {
|
||||
answer.push(this.makeCode("\n" + idt + "}"));
|
||||
}
|
||||
answer.push(this.makeCode(',\n'));
|
||||
}
|
||||
join = i === props.length - 1 || i === dynamicIndex - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n';
|
||||
indent = prop instanceof Comment ? '' : idt;
|
||||
if (hasDynamic && i < dynamicIndex) {
|
||||
indent += TAB;
|
||||
}
|
||||
if (prop instanceof Assign && prop.variable instanceof Value && prop.variable.hasProperties()) {
|
||||
prop.variable.error('Invalid object key');
|
||||
}
|
||||
@@ -1294,10 +1312,20 @@
|
||||
prop = new Assign(prop.properties[0].name, prop, 'object');
|
||||
}
|
||||
if (!(prop instanceof Comment)) {
|
||||
if (!(prop instanceof Assign)) {
|
||||
prop = new Assign(prop, prop, 'object');
|
||||
if (i < dynamicIndex) {
|
||||
if (!(prop instanceof Assign)) {
|
||||
prop = new Assign(prop, prop, 'object');
|
||||
}
|
||||
(prop.variable.base || prop.variable).asKey = true;
|
||||
} else {
|
||||
if (prop instanceof Assign) {
|
||||
key = prop.variable;
|
||||
value = prop.value;
|
||||
} else {
|
||||
ref3 = prop.base.cache(o), key = ref3[0], value = ref3[1];
|
||||
}
|
||||
prop = new Assign(new Value(new Literal(oref), [new Access(key)]), value);
|
||||
}
|
||||
(prop.variable.base || prop.variable).asKey = true;
|
||||
}
|
||||
if (indent) {
|
||||
answer.push(this.makeCode(indent));
|
||||
@@ -1307,9 +1335,14 @@
|
||||
answer.push(this.makeCode(join));
|
||||
}
|
||||
}
|
||||
answer.unshift(this.makeCode("{" + (props.length && '\n')));
|
||||
answer.push(this.makeCode((props.length && '\n' + this.tab) + "}"));
|
||||
if (this.front) {
|
||||
if (hasDynamic) {
|
||||
answer.push(this.makeCode(",\n" + idt + oref + "\n" + this.tab + ")"));
|
||||
} else {
|
||||
if (props.length !== 0) {
|
||||
answer.push(this.makeCode("\n" + this.tab + "}"));
|
||||
}
|
||||
}
|
||||
if (this.front && !hasDynamic) {
|
||||
return this.wrapInBraces(answer);
|
||||
} else {
|
||||
return answer;
|
||||
@@ -1447,7 +1480,7 @@
|
||||
};
|
||||
|
||||
Class.prototype.addProperties = function(node, name, o) {
|
||||
var assign, base, exprs, func, props;
|
||||
var acc, assign, base, exprs, func, props;
|
||||
props = node.base.properties.slice(0);
|
||||
exprs = (function() {
|
||||
var results;
|
||||
@@ -1474,7 +1507,8 @@
|
||||
if (assign.variable["this"]) {
|
||||
func["static"] = true;
|
||||
} else {
|
||||
assign.variable = new Value(new Literal(name), [new Access(new Literal('prototype')), new Access(base)]);
|
||||
acc = base.isComplex() ? new Index(base) : new Access(base);
|
||||
assign.variable = new Value(new Literal(name), [new Access(new Literal('prototype')), acc]);
|
||||
if (func instanceof Code && func.bound) {
|
||||
this.boundFuncs.push(base);
|
||||
func.bound = false;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -108,7 +108,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
Rewriter.prototype.matchTags = function() {
|
||||
Rewriter.prototype.indexOfTag = function() {
|
||||
var fuzz, i, j, k, pattern, ref, ref1;
|
||||
i = arguments[0], pattern = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
||||
fuzz = 0;
|
||||
@@ -123,14 +123,31 @@
|
||||
pattern[j] = [pattern[j]];
|
||||
}
|
||||
if (ref1 = this.tag(i + j + fuzz), indexOf.call(pattern[j], ref1) < 0) {
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return i + j + fuzz - 1;
|
||||
};
|
||||
|
||||
Rewriter.prototype.looksObjectish = function(j) {
|
||||
return this.matchTags(j, '@', null, ':') || this.matchTags(j, null, ':');
|
||||
var end, index;
|
||||
if (this.indexOfTag(j, '@', null, ':') > -1 || this.indexOfTag(j, null, ':') > -1) {
|
||||
return true;
|
||||
}
|
||||
index = this.indexOfTag(j, EXPRESSION_START);
|
||||
if (index > -1) {
|
||||
end = null;
|
||||
this.detectEnd(index + 1, (function(token) {
|
||||
var ref;
|
||||
return ref = token[0], indexOf.call(EXPRESSION_END, ref) >= 0;
|
||||
}), (function(token, i) {
|
||||
return end = i;
|
||||
}));
|
||||
if (this.tag(end + 1) === ':') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Rewriter.prototype.findTagsBackwards = function(i, tags) {
|
||||
@@ -149,8 +166,9 @@
|
||||
};
|
||||
|
||||
Rewriter.prototype.addImplicitBracesAndParens = function() {
|
||||
var stack;
|
||||
var stack, start;
|
||||
stack = [];
|
||||
start = null;
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, ref, ref1, ref2, ref3, ref4, ref5, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag;
|
||||
tag = token[0];
|
||||
@@ -255,26 +273,32 @@
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
start = stack.pop();
|
||||
}
|
||||
if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced && !token.stringEnd && !token.regexEnd || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((ref = tokens[i + 1]) != null ? ref.spaced : void 0) && !((ref1 = tokens[i + 1]) != null ? ref1.newLine : void 0))) {
|
||||
if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((ref = tokens[i + 1]) != null ? ref.spaced : void 0) && !((ref1 = tokens[i + 1]) != null ? ref1.newLine : void 0))) {
|
||||
if (tag === '?') {
|
||||
tag = token[0] = 'FUNC_EXIST';
|
||||
}
|
||||
startImplicitCall(i + 1);
|
||||
return forward(2);
|
||||
}
|
||||
if (indexOf.call(IMPLICIT_FUNC, tag) >= 0 && !token.stringEnd && !token.regexEnd && this.matchTags(i + 1, 'INDENT', null, ':') && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) {
|
||||
if (indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.indexOfTag(i + 1, 'INDENT', null, ':') > -1 && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) {
|
||||
startImplicitCall(i + 1);
|
||||
stack.push(['INDENT', i + 2]);
|
||||
return forward(3);
|
||||
}
|
||||
if (tag === ':') {
|
||||
if (this.tag(i - 2) === '@') {
|
||||
s = i - 2;
|
||||
} else {
|
||||
s = i - 1;
|
||||
}
|
||||
s = (function() {
|
||||
var ref2;
|
||||
switch (false) {
|
||||
case ref2 = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref2) < 0:
|
||||
return start[1];
|
||||
case this.tag(i - 2) !== '@':
|
||||
return i - 2;
|
||||
default:
|
||||
return i - 1;
|
||||
}
|
||||
}).call(this);
|
||||
while (this.tag(s - 2) === 'HERECOMMENT') {
|
||||
s -= 2;
|
||||
}
|
||||
@@ -298,8 +322,8 @@
|
||||
ref4 = stackTop(), stackTag = ref4[0], stackIdx = ref4[1], (ref5 = ref4[2], sameLine = ref5.sameLine, startsLine = ref5.startsLine);
|
||||
if (inImplicitCall() && prevTag !== ',') {
|
||||
endImplicitCall();
|
||||
} else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && endImplicitObject()) {
|
||||
|
||||
} else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':') {
|
||||
endImplicitObject();
|
||||
} else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) {
|
||||
endImplicitObject();
|
||||
} else {
|
||||
@@ -440,7 +464,7 @@
|
||||
|
||||
})();
|
||||
|
||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']];
|
||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END'], ['STRING_START', 'STRING_END'], ['REGEX_START', 'REGEX_END']];
|
||||
|
||||
exports.INVERSES = INVERSES = {};
|
||||
|
||||
@@ -458,7 +482,7 @@
|
||||
|
||||
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
|
||||
|
||||
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
||||
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
||||
|
||||
IMPLICIT_UNSPACED_CALL = ['+', '-'];
|
||||
|
||||
|
||||
@@ -225,12 +225,15 @@ parser.yy.parseError = (message, {token}) ->
|
||||
{errorToken, tokens} = parser
|
||||
[errorTag, errorText, errorLoc] = errorToken
|
||||
|
||||
errorText = if errorToken is tokens[tokens.length - 1]
|
||||
'end of input'
|
||||
else if errorTag in ['INDENT', 'OUTDENT']
|
||||
'indentation'
|
||||
else
|
||||
helpers.nameWhitespaceCharacter errorText
|
||||
errorText = switch
|
||||
when errorToken is tokens[tokens.length - 1]
|
||||
'end of input'
|
||||
when errorTag in ['INDENT', 'OUTDENT']
|
||||
'indentation'
|
||||
when errorTag in ['IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
|
||||
errorTag.replace(/_START$/, '').toLowerCase()
|
||||
else
|
||||
helpers.nameWhitespaceCharacter errorText
|
||||
|
||||
# The second argument has a `loc` property, which should have the location
|
||||
# data for this token. Unfortunately, Jison seems to send an outdated `loc`
|
||||
|
||||
@@ -132,7 +132,17 @@ grammar =
|
||||
# they can also serve as keys in object literals.
|
||||
AlphaNumeric: [
|
||||
o 'NUMBER', -> new Literal $1
|
||||
o 'String'
|
||||
]
|
||||
|
||||
String: [
|
||||
o 'STRING', -> new Literal $1
|
||||
o 'STRING_START Body STRING_END', -> new Parens $2
|
||||
]
|
||||
|
||||
Regex: [
|
||||
o 'REGEX', -> new Literal $1
|
||||
o 'REGEX_START Invocation REGEX_END', -> $2
|
||||
]
|
||||
|
||||
# All of our immediate values. Generally these can be passed straight
|
||||
@@ -140,7 +150,7 @@ grammar =
|
||||
Literal: [
|
||||
o 'AlphaNumeric'
|
||||
o 'JS', -> new Literal $1
|
||||
o 'REGEX', -> new Literal $1
|
||||
o 'Regex'
|
||||
o 'DEBUGGER', -> new Literal $1
|
||||
o 'UNDEFINED', -> new Undefined
|
||||
o 'NULL', -> new Null
|
||||
|
||||
@@ -267,7 +267,7 @@ exports.Lexer = class Lexer
|
||||
index = regex.length
|
||||
[..., prev] = @tokens
|
||||
if prev
|
||||
if prev.spaced and prev[0] in CALLABLE and not prev.stringEnd and not prev.regexEnd
|
||||
if prev.spaced and prev[0] in CALLABLE
|
||||
return 0 if not closed or POSSIBLY_DIVISION.test regex
|
||||
else if prev[0] in NOT_REGEX
|
||||
return 0
|
||||
@@ -277,22 +277,23 @@ exports.Lexer = class Lexer
|
||||
|
||||
[flags] = REGEX_FLAGS.exec @chunk[index..]
|
||||
end = index + flags.length
|
||||
errorToken = @makeToken 'REGEX', @chunk[...end], 0, end
|
||||
origin = @makeToken 'REGEX', null, 0, end
|
||||
switch
|
||||
when not VALID_FLAGS.test flags
|
||||
@error "invalid regular expression flags #{flags}", offset: index, length: flags.length
|
||||
when regex or tokens.length is 1
|
||||
body ?= @formatHeregex tokens[0][1]
|
||||
@token 'REGEX', "#{@makeDelimitedLiteral body, delimiter: '/'}#{flags}", 0, end, errorToken
|
||||
@token 'REGEX', "#{@makeDelimitedLiteral body, delimiter: '/'}#{flags}", 0, end, origin
|
||||
else
|
||||
@token 'REGEX_START', '(', 0, 0, origin
|
||||
@token 'IDENTIFIER', 'RegExp', 0, 0
|
||||
@token 'CALL_START', '(', 0, 0, errorToken
|
||||
@token 'CALL_START', '(', 0, 0
|
||||
@mergeInterpolationTokens tokens, {delimiter: '"', double: yes}, @formatHeregex
|
||||
if flags
|
||||
@token ',', ',', index, 0
|
||||
@token 'STRING', '"' + flags + '"', index, flags.length
|
||||
rparen = @token ')', ')', end, 0
|
||||
rparen.regexEnd = true
|
||||
@token ')', ')', end, 0
|
||||
@token 'REGEX_END', ')', end, 0
|
||||
|
||||
end
|
||||
|
||||
@@ -420,7 +421,7 @@ exports.Lexer = class Lexer
|
||||
else if value in SHIFT then tag = 'SHIFT'
|
||||
else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
|
||||
else if prev and not prev.spaced
|
||||
if value is '(' and prev[0] in CALLABLE and not prev.stringEnd and not prev.regexEnd
|
||||
if value is '(' and prev[0] in CALLABLE
|
||||
prev[0] = 'FUNC_EXIST' if prev[0] is '?'
|
||||
tag = 'CALL_START'
|
||||
else if value is '[' and prev[0] in INDEXABLE
|
||||
@@ -471,8 +472,8 @@ exports.Lexer = class Lexer
|
||||
# If it encounters an interpolation, this method will recursively create a new
|
||||
# Lexer and tokenize until the `{` of `#{` is balanced with a `}`.
|
||||
#
|
||||
# - `regex` matches the contents of a token (but not `end`, and not `#{` if
|
||||
# interpolations are desired).
|
||||
# - `regex` matches the contents of a token (but not `delimiter`, and not
|
||||
# `#{` if interpolations are desired).
|
||||
# - `delimiter` is the delimiter of the token. Examples are `'`, `"`, `'''`,
|
||||
# `"""` and `///`.
|
||||
#
|
||||
@@ -525,6 +526,7 @@ exports.Lexer = class Lexer
|
||||
[firstToken, ..., lastToken] = tokens
|
||||
firstToken[2].first_column -= delimiter.length
|
||||
lastToken[2].last_column += delimiter.length
|
||||
lastToken[2].last_column -= 1 if lastToken[1].length is 0
|
||||
|
||||
{tokens, index: offsetInChunk + delimiter.length}
|
||||
|
||||
@@ -533,15 +535,8 @@ exports.Lexer = class Lexer
|
||||
# of 'NEOSTRING's are converted using `fn` and turned into strings using
|
||||
# `options` first.
|
||||
mergeInterpolationTokens: (tokens, options, fn) ->
|
||||
if interpolated = tokens.length > 1
|
||||
[firstToken] = tokens
|
||||
errorToken = ['', 'interpolation',
|
||||
first_line: firstToken[2].last_line
|
||||
first_column: firstToken[2].last_column
|
||||
last_line: firstToken[2].last_line
|
||||
last_column: firstToken[2].last_column + 1
|
||||
]
|
||||
@token '(', '(', 0, 0, errorToken
|
||||
if tokens.length > 1
|
||||
lparen = @token 'STRING_START', '(', 0, 0
|
||||
|
||||
firstIndex = @tokens.length
|
||||
for token, i in tokens
|
||||
@@ -583,16 +578,20 @@ exports.Lexer = class Lexer
|
||||
last_column: locationToken[2].first_column
|
||||
@tokens.push tokensToPush...
|
||||
|
||||
if interpolated
|
||||
[..., lastToken] = @tokens
|
||||
rparen = @token ')', ')'
|
||||
rparen[2] = {
|
||||
first_line: lastToken[2].last_line
|
||||
first_column: lastToken[2].last_column + 1
|
||||
if lparen
|
||||
[..., lastToken] = tokens
|
||||
lparen.origin = ['STRING', null,
|
||||
first_line: lparen[2].first_line
|
||||
first_column: lparen[2].first_column
|
||||
last_line: lastToken[2].last_line
|
||||
last_column: lastToken[2].last_column + 1
|
||||
}
|
||||
rparen.stringEnd = true
|
||||
last_column: lastToken[2].last_column
|
||||
]
|
||||
rparen = @token 'STRING_END', ')'
|
||||
rparen[2] =
|
||||
first_line: lastToken[2].last_line
|
||||
first_column: lastToken[2].last_column
|
||||
last_line: lastToken[2].last_line
|
||||
last_column: lastToken[2].last_column
|
||||
|
||||
# Pairs up a closing token, ensuring that all listed pairs of tokens are
|
||||
# correctly balanced throughout the course of the token stream.
|
||||
@@ -913,7 +912,10 @@ BOOL = ['TRUE', 'FALSE']
|
||||
# parentheses or bracket following these tokens will be recorded as the start
|
||||
# of a function invocation or indexing operation.
|
||||
CALLABLE = ['IDENTIFIER', ')', ']', '?', '@', 'THIS', 'SUPER']
|
||||
INDEXABLE = CALLABLE.concat ['NUMBER', 'STRING', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']
|
||||
INDEXABLE = CALLABLE.concat [
|
||||
'NUMBER', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END'
|
||||
'BOOL', 'NULL', 'UNDEFINED', '}', '::'
|
||||
]
|
||||
|
||||
# Tokens which a regular expression will never immediately follow (except spaced
|
||||
# CALLABLEs in some cases), but which a division operator can.
|
||||
|
||||
@@ -925,35 +925,54 @@ exports.Obj = class Obj extends Base
|
||||
|
||||
compileNode: (o) ->
|
||||
props = @properties
|
||||
return [@makeCode(if @front then '({})' else '{}')] unless props.length
|
||||
if @generated
|
||||
for node in props when node instanceof Value
|
||||
node.error 'cannot have an implicit value in an implicit object'
|
||||
break for prop, dynamicIndex in props when (prop.variable or prop).base instanceof Parens
|
||||
hasDynamic = dynamicIndex < props.length
|
||||
idt = o.indent += TAB
|
||||
lastNoncom = @lastNonComment @properties
|
||||
answer = []
|
||||
if hasDynamic
|
||||
oref = o.scope.freeVariable 'obj'
|
||||
answer.push @makeCode "(\n#{idt}#{oref} = "
|
||||
answer.push @makeCode "{#{if props.length is 0 or dynamicIndex is 0 then '}' else '\n'}"
|
||||
for prop, i in props
|
||||
join = if i is props.length - 1
|
||||
if i is dynamicIndex
|
||||
answer.push @makeCode "\n#{idt}}" unless i is 0
|
||||
answer.push @makeCode ',\n'
|
||||
join = if i is props.length - 1 or i is dynamicIndex - 1
|
||||
''
|
||||
else if prop is lastNoncom or prop instanceof Comment
|
||||
'\n'
|
||||
else
|
||||
',\n'
|
||||
indent = if prop instanceof Comment then '' else idt
|
||||
indent += TAB if hasDynamic and i < dynamicIndex
|
||||
if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
|
||||
prop.variable.error 'Invalid object key'
|
||||
if prop instanceof Value and prop.this
|
||||
prop = new Assign prop.properties[0].name, prop, 'object'
|
||||
if prop not instanceof Comment
|
||||
if prop not instanceof Assign
|
||||
prop = new Assign prop, prop, 'object'
|
||||
(prop.variable.base or prop.variable).asKey = yes
|
||||
if i < dynamicIndex
|
||||
if prop not instanceof Assign
|
||||
prop = new Assign prop, prop, 'object'
|
||||
(prop.variable.base or prop.variable).asKey = yes
|
||||
else
|
||||
if prop instanceof Assign
|
||||
key = prop.variable
|
||||
value = prop.value
|
||||
else
|
||||
[key, value] = prop.base.cache o
|
||||
prop = new Assign (new Value (new Literal oref), [new Access key]), value
|
||||
if indent then answer.push @makeCode indent
|
||||
answer.push prop.compileToFragments(o, LEVEL_TOP)...
|
||||
if join then answer.push @makeCode join
|
||||
answer.unshift @makeCode "{#{ props.length and '\n' }"
|
||||
answer.push @makeCode "#{ props.length and '\n' + @tab }}"
|
||||
if @front then @wrapInBraces answer else answer
|
||||
if hasDynamic
|
||||
answer.push @makeCode ",\n#{idt}#{oref}\n#{@tab})"
|
||||
else
|
||||
answer.push @makeCode "\n#{@tab}}" unless props.length is 0
|
||||
if @front and not hasDynamic then @wrapInBraces answer else answer
|
||||
|
||||
assigns: (name) ->
|
||||
for prop in @properties when prop.assigns name then return yes
|
||||
@@ -1057,7 +1076,8 @@ exports.Class = class Class extends Base
|
||||
if assign.variable.this
|
||||
func.static = yes
|
||||
else
|
||||
assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base])
|
||||
acc = if base.isComplex() then new Index base else new Access base
|
||||
assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), acc])
|
||||
if func instanceof Code and func.bound
|
||||
@boundFuncs.push base
|
||||
func.bound = no
|
||||
|
||||
@@ -93,24 +93,31 @@ class exports.Rewriter
|
||||
@detectEnd i + 1, condition, action if token[0] is 'INDEX_START'
|
||||
1
|
||||
|
||||
# Match tags in token stream starting at i with pattern, skipping HERECOMMENTs
|
||||
# Pattern may consist of strings (equality), an array of strings (one of)
|
||||
# or null (wildcard)
|
||||
matchTags: (i, pattern...) ->
|
||||
# Match tags in token stream starting at `i` with `pattern`, skipping 'HERECOMMENT's.
|
||||
# `pattern` may consist of strings (equality), an array of strings (one of)
|
||||
# or null (wildcard). Returns the index of the match or -1 if no match.
|
||||
indexOfTag: (i, pattern...) ->
|
||||
fuzz = 0
|
||||
for j in [0 ... pattern.length]
|
||||
fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT'
|
||||
continue if not pattern[j]?
|
||||
pattern[j] = [pattern[j]] if typeof pattern[j] is 'string'
|
||||
return no if @tag(i + j + fuzz) not in pattern[j]
|
||||
yes
|
||||
return -1 if @tag(i + j + fuzz) not in pattern[j]
|
||||
i + j + fuzz - 1
|
||||
|
||||
# yes iff standing in front of something looking like
|
||||
# @<x>: or <x>:, skipping over 'HERECOMMENT's
|
||||
# Returns `yes` if standing in front of something looking like
|
||||
# `@<x>:`, `<x>:` or `<EXPRESSION_START><x>...<EXPRESSION_END>:`,
|
||||
# skipping over 'HERECOMMENT's.
|
||||
looksObjectish: (j) ->
|
||||
@matchTags(j, '@', null, ':') or @matchTags(j, null, ':')
|
||||
return yes if @indexOfTag(j, '@', null, ':') > -1 or @indexOfTag(j, null, ':') > -1
|
||||
index = @indexOfTag(j, EXPRESSION_START)
|
||||
if index > -1
|
||||
end = null
|
||||
@detectEnd index + 1, ((token) -> token[0] in EXPRESSION_END), ((token, i) -> end = i)
|
||||
return yes if @tag(end + 1) is ':'
|
||||
no
|
||||
|
||||
# yes iff current line of tokens contain an element of tags on same
|
||||
# Returns `yes` if current line of tokens contain an element of tags on same
|
||||
# expression level. Stop searching at LINEBREAKS or explicit start of
|
||||
# containing balanced expression.
|
||||
findTagsBackwards: (i, tags) ->
|
||||
@@ -129,6 +136,7 @@ class exports.Rewriter
|
||||
addImplicitBracesAndParens: ->
|
||||
# Track current balancing depth (both implicit and explicit) on stack.
|
||||
stack = []
|
||||
start = null
|
||||
|
||||
@scanTokens (token, i, tokens) ->
|
||||
[tag] = token
|
||||
@@ -205,11 +213,11 @@ class exports.Rewriter
|
||||
endImplicitObject()
|
||||
else
|
||||
stack.pop()
|
||||
stack.pop()
|
||||
start = stack.pop()
|
||||
|
||||
# Recognize standard implicit calls like
|
||||
# f a, f() b, f? c, h[0] d etc.
|
||||
if (tag in IMPLICIT_FUNC and token.spaced and not token.stringEnd and not token.regexEnd or
|
||||
if (tag in IMPLICIT_FUNC and token.spaced or
|
||||
tag is '?' and i > 0 and not tokens[i - 1].spaced) and
|
||||
(nextTag in IMPLICIT_CALL or
|
||||
nextTag in IMPLICIT_UNSPACED_CALL and
|
||||
@@ -243,8 +251,8 @@ class exports.Rewriter
|
||||
# which is probably always unintended.
|
||||
# Furthermore don't allow this in literal arrays, as
|
||||
# that creates grammatical ambiguities.
|
||||
if tag in IMPLICIT_FUNC and not token.stringEnd and not token.regexEnd and
|
||||
@matchTags(i + 1, 'INDENT', null, ':') and
|
||||
if tag in IMPLICIT_FUNC and
|
||||
@indexOfTag(i + 1, 'INDENT', null, ':') > -1 and
|
||||
not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
|
||||
'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])
|
||||
startImplicitCall i + 1
|
||||
@@ -254,7 +262,10 @@ class exports.Rewriter
|
||||
# Implicit objects start here
|
||||
if tag is ':'
|
||||
# Go back to the (implicit) start of the object
|
||||
if @tag(i - 2) is '@' then s = i - 2 else s = i - 1
|
||||
s = switch
|
||||
when @tag(i - 1) in EXPRESSION_END then start[1]
|
||||
when @tag(i - 2) is '@' then i - 2
|
||||
else i - 1
|
||||
s -= 2 while @tag(s - 2) is 'HERECOMMENT'
|
||||
|
||||
# Mark if the value is a for loop
|
||||
@@ -298,7 +309,7 @@ class exports.Rewriter
|
||||
# Close implicit objects such as:
|
||||
# return a: 1, b: 2 unless true
|
||||
else if inImplicitObject() and not @insideForDeclaration and sameLine and
|
||||
tag isnt 'TERMINATOR' and prevTag isnt ':' and
|
||||
tag isnt 'TERMINATOR' and prevTag isnt ':'
|
||||
endImplicitObject()
|
||||
# Close implicit objects when at end of line, line didn't end with a comma
|
||||
# and the implicit object didn't start the line or the next line doesn't look like
|
||||
@@ -445,6 +456,8 @@ BALANCED_PAIRS = [
|
||||
['CALL_START', 'CALL_END']
|
||||
['PARAM_START', 'PARAM_END']
|
||||
['INDEX_START', 'INDEX_END']
|
||||
['STRING_START', 'STRING_END']
|
||||
['REGEX_START', 'REGEX_END']
|
||||
]
|
||||
|
||||
# The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can
|
||||
@@ -467,9 +480,10 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
|
||||
|
||||
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
||||
IMPLICIT_CALL = [
|
||||
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
|
||||
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'YIELD'
|
||||
'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
|
||||
'IDENTIFIER', 'NUMBER', 'STRING', 'STRING_START', 'JS', 'REGEX', 'REGEX_START'
|
||||
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL'
|
||||
'UNDEFINED', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW'
|
||||
'@', '->', '=>', '[', '(', '{', '--', '++'
|
||||
]
|
||||
|
||||
IMPLICIT_UNSPACED_CALL = ['+', '-']
|
||||
|
||||
@@ -285,6 +285,13 @@ test "#156: destructuring with expansion", ->
|
||||
throws (-> CoffeeScript.compile "[..., a, b...] = c"), null, "prohibit expansion and a splat"
|
||||
throws (-> CoffeeScript.compile "[...] = c"), null, "prohibit lone expansion"
|
||||
|
||||
test "destructuring with dynamic keys", ->
|
||||
{"#{'a'}": a, """#{'b'}""": b, c} = {a: 1, b: 2, c: 3}
|
||||
eq 1, a
|
||||
eq 2, b
|
||||
eq 3, c
|
||||
throws -> CoffeeScript.compile '{"#{a}"} = b'
|
||||
|
||||
|
||||
# Existential Assignment
|
||||
|
||||
|
||||
@@ -860,6 +860,7 @@ test "dynamic method names and super", ->
|
||||
class Base
|
||||
@m: -> 6
|
||||
m: -> 5
|
||||
m2: -> 4.5
|
||||
n: -> 4
|
||||
A = ->
|
||||
A extends Base
|
||||
@@ -877,14 +878,17 @@ test "dynamic method names and super", ->
|
||||
eq 1, count
|
||||
|
||||
m = 'm'
|
||||
m2 = 'm2'
|
||||
count = 0
|
||||
class B extends Base
|
||||
@[name()] = -> super
|
||||
@::[m] = -> super
|
||||
"#{m2}": -> super
|
||||
b = new B
|
||||
m = 'n'
|
||||
m = m2 = 'n'
|
||||
eq 6, B.m()
|
||||
eq 5, b.m()
|
||||
eq 4.5, b.m2()
|
||||
eq 1, count
|
||||
|
||||
class C extends B
|
||||
|
||||
@@ -87,12 +87,6 @@ if require?
|
||||
|
||||
|
||||
test "#1096: unexpected generated tokens", ->
|
||||
# Unexpected interpolation
|
||||
assertErrorFormat '{"#{key}": val}', '''
|
||||
[stdin]:1:3: error: unexpected interpolation
|
||||
{"#{key}": val}
|
||||
^^
|
||||
'''
|
||||
# Implicit ends
|
||||
assertErrorFormat 'a:, b', '''
|
||||
[stdin]:1:3: error: unexpected ,
|
||||
@@ -116,31 +110,79 @@ test "#1096: unexpected generated tokens", ->
|
||||
a +
|
||||
^
|
||||
'''
|
||||
# Unexpected implicit object
|
||||
# Unexpected key in implicit object (an implicit object itself is _not_
|
||||
# unexpected here)
|
||||
assertErrorFormat '''
|
||||
for i in [1]:
|
||||
1
|
||||
''', '''
|
||||
[stdin]:1:13: error: unexpected :
|
||||
[stdin]:1:10: error: unexpected [
|
||||
for i in [1]:
|
||||
^
|
||||
^
|
||||
'''
|
||||
# Unexpected regex
|
||||
assertErrorFormat '{/a/i: val}', '''
|
||||
[stdin]:1:2: error: unexpected /a/i
|
||||
[stdin]:1:2: error: unexpected regex
|
||||
{/a/i: val}
|
||||
^^^^
|
||||
'''
|
||||
assertErrorFormat '{///a///i: val}', '''
|
||||
[stdin]:1:2: error: unexpected ///a///i
|
||||
[stdin]:1:2: error: unexpected regex
|
||||
{///a///i: val}
|
||||
^^^^^^^^
|
||||
'''
|
||||
assertErrorFormat '{///#{a}///i: val}', '''
|
||||
[stdin]:1:2: error: unexpected ///#{a}///i
|
||||
[stdin]:1:2: error: unexpected regex
|
||||
{///#{a}///i: val}
|
||||
^^^^^^^^^^^
|
||||
'''
|
||||
# Unexpected string
|
||||
assertErrorFormat "a''", '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a''
|
||||
^^
|
||||
'''
|
||||
assertErrorFormat 'a""', '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a""
|
||||
^^
|
||||
'''
|
||||
assertErrorFormat "a'b'", '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a'b'
|
||||
^^^
|
||||
'''
|
||||
assertErrorFormat 'a"b"', '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a"b"
|
||||
^^^
|
||||
'''
|
||||
assertErrorFormat "a'''b'''", """
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a'''b'''
|
||||
^^^^^^^
|
||||
"""
|
||||
assertErrorFormat 'a"""b"""', '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a"""b"""
|
||||
^^^^^^^
|
||||
'''
|
||||
assertErrorFormat 'a"#{b}"', '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a"#{b}"
|
||||
^^^^^^
|
||||
'''
|
||||
assertErrorFormat 'a"""#{b}"""', '''
|
||||
[stdin]:1:2: error: unexpected string
|
||||
a"""#{b}"""
|
||||
^^^^^^^^^^
|
||||
'''
|
||||
# Unexpected number
|
||||
assertErrorFormat '"a"0x00Af2', '''
|
||||
[stdin]:1:4: error: unexpected number
|
||||
"a"0x00Af2
|
||||
^^^^^^^
|
||||
'''
|
||||
|
||||
test "#1316: unexpected end of interpolation", ->
|
||||
assertErrorFormat '''
|
||||
@@ -336,14 +378,14 @@ test "unexpected token after string", ->
|
||||
assertErrorFormat '''
|
||||
'foo'bar
|
||||
''', '''
|
||||
[stdin]:1:6: error: unexpected bar
|
||||
[stdin]:1:6: error: unexpected identifier
|
||||
'foo'bar
|
||||
^^^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
"foo"bar
|
||||
''', '''
|
||||
[stdin]:1:6: error: unexpected bar
|
||||
[stdin]:1:6: error: unexpected identifier
|
||||
"foo"bar
|
||||
^^^
|
||||
'''
|
||||
@@ -365,11 +407,11 @@ test "unexpected token after string", ->
|
||||
|
||||
test "#3348: Location data is wrong in interpolations with leading whitespace", ->
|
||||
assertErrorFormat '''
|
||||
"#{ {"#{key}": val} }"
|
||||
"#{ * }"
|
||||
''', '''
|
||||
[stdin]:1:7: error: unexpected interpolation
|
||||
"#{ {"#{key}": val} }"
|
||||
^^
|
||||
[stdin]:1:5: error: unexpected *
|
||||
"#{ * }"
|
||||
^
|
||||
'''
|
||||
|
||||
test "octal escapes", ->
|
||||
@@ -643,4 +685,62 @@ test "invalid numbers", ->
|
||||
[stdin]:1:1: error: octal literal '010' must be prefixed with '0o'
|
||||
010
|
||||
^^^
|
||||
'''
|
||||
|
||||
test "unexpected object keys", ->
|
||||
assertErrorFormat '''
|
||||
{[[]]}
|
||||
''', '''
|
||||
[stdin]:1:2: error: unexpected [
|
||||
{[[]]}
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
{[[]]: 1}
|
||||
''', '''
|
||||
[stdin]:1:2: error: unexpected [
|
||||
{[[]]: 1}
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
[[]]: 1
|
||||
''', '''
|
||||
[stdin]:1:1: error: unexpected [
|
||||
[[]]: 1
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
{(a + "b")}
|
||||
''', '''
|
||||
[stdin]:1:2: error: unexpected (
|
||||
{(a + "b")}
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
{(a + "b"): 1}
|
||||
''', '''
|
||||
[stdin]:1:2: error: unexpected (
|
||||
{(a + "b"): 1}
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
(a + "b"): 1
|
||||
''', '''
|
||||
[stdin]:1:1: error: unexpected (
|
||||
(a + "b"): 1
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
a: 1, [[]]: 2
|
||||
''', '''
|
||||
[stdin]:1:7: error: unexpected [
|
||||
a: 1, [[]]: 2
|
||||
^
|
||||
'''
|
||||
assertErrorFormat '''
|
||||
{a: 1, [[]]: 2}
|
||||
''', '''
|
||||
[stdin]:1:8: error: unexpected [
|
||||
{a: 1, [[]]: 2}
|
||||
^
|
||||
'''
|
||||
|
||||
@@ -438,15 +438,16 @@ test "#3822: Simple string/regex start/end should include delimiters", ->
|
||||
|
||||
test "#3621: Multiline regex and manual `Regex` call with interpolation should
|
||||
result in the same tokens", ->
|
||||
tokensA = CoffeeScript.tokens 'RegExp(".*#{a}[0-9]")'
|
||||
tokensA = CoffeeScript.tokens '(RegExp(".*#{a}[0-9]"))'
|
||||
tokensB = CoffeeScript.tokens '///.*#{a}[0-9]///'
|
||||
eq tokensA.length, tokensB.length
|
||||
for i in [0...tokensA.length] by 1
|
||||
tokenA = tokensA[i]
|
||||
tokenB = tokensB[i]
|
||||
eq tokenA[0], tokenB[0]
|
||||
eq tokenA[0], tokenB[0] unless tokenB[0] in ['REGEX_START', 'REGEX_END']
|
||||
eq tokenA[1], tokenB[1]
|
||||
eq tokenA.origin?[1], tokenB.origin?[1] unless tokenA[0] is 'CALL_START'
|
||||
unless tokenA[0] is 'STRING_START' or tokenB[0] is 'REGEX_START'
|
||||
eq tokenA.origin?[1], tokenB.origin?[1]
|
||||
eq tokenA.stringEnd, tokenB.stringEnd
|
||||
|
||||
test "Verify all tokens get a location", ->
|
||||
|
||||
@@ -446,3 +446,126 @@ test 'inline implicit object literals within multiline implicit object literals'
|
||||
b: 0
|
||||
eq 0, x.b
|
||||
eq 0, x.a.aa
|
||||
|
||||
test "object keys with interpolations", ->
|
||||
# Simple cases.
|
||||
a = 'a'
|
||||
obj = "#{a}": yes
|
||||
eq obj.a, yes
|
||||
obj = {"#{a}": yes}
|
||||
eq obj.a, yes
|
||||
obj = {"#{a}"}
|
||||
eq obj.a, 'a'
|
||||
obj = {"#{5}"}
|
||||
eq obj[5], '5' # Note that the value is a string, just like the key.
|
||||
|
||||
# Commas in implicit object.
|
||||
obj = "#{'a'}": 1, b: 2
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
obj = a: 1, "#{'b'}": 2
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
obj = "#{'a'}": 1, "#{'b'}": 2
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
|
||||
# Commas in explicit object.
|
||||
obj = {"#{'a'}": 1, b: 2}
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
obj = {a: 1, "#{'b'}": 2}
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
obj = {"#{'a'}": 1, "#{'b'}": 2}
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
|
||||
# Commas after key with interpolation.
|
||||
obj = {"#{'a'}": yes,}
|
||||
eq obj.a, yes
|
||||
obj = {
|
||||
"#{'a'}": 1,
|
||||
"#{'b'}": 2,
|
||||
### herecomment ###
|
||||
"#{'c'}": 3,
|
||||
}
|
||||
deepEqual obj, {a: 1, b: 2, c: 3}
|
||||
obj =
|
||||
"#{'a'}": 1,
|
||||
"#{'b'}": 2,
|
||||
### herecomment ###
|
||||
"#{'c'}": 3,
|
||||
deepEqual obj, {a: 1, b: 2, c: 3}
|
||||
obj =
|
||||
"#{'a'}": 1,
|
||||
"#{'b'}": 2,
|
||||
### herecomment ###
|
||||
"#{'c'}": 3, "#{'d'}": 4,
|
||||
deepEqual obj, {a: 1, b: 2, c: 3, d: 4}
|
||||
|
||||
# Key with interpolation mixed with `@prop`.
|
||||
deepEqual (-> {@a, "#{'b'}": 2}).call(a: 1), {a: 1, b: 2}
|
||||
|
||||
# Evaluate only once.
|
||||
count = 0
|
||||
b = -> count++; 'b'
|
||||
obj = {"#{b()}"}
|
||||
eq obj.b, 'b'
|
||||
eq count, 1
|
||||
|
||||
# Evaluation order.
|
||||
arr = []
|
||||
obj =
|
||||
a: arr.push 1
|
||||
b: arr.push 2
|
||||
"#{'c'}": arr.push 3
|
||||
"#{'d'}": arr.push 4
|
||||
e: arr.push 5
|
||||
"#{'f'}": arr.push 6
|
||||
g: arr.push 7
|
||||
arrayEq arr, [1..7]
|
||||
deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}
|
||||
|
||||
# Object starting with dynamic key.
|
||||
obj =
|
||||
"#{'a'}": 1
|
||||
b: 2
|
||||
deepEqual obj, {a: 1, b: 2}
|
||||
|
||||
# Comments in implicit object.
|
||||
obj =
|
||||
### leading comment ###
|
||||
"#{'a'}": 1
|
||||
|
||||
### middle ###
|
||||
|
||||
"#{'b'}": 2
|
||||
# regular comment
|
||||
'c': 3
|
||||
### foo ###
|
||||
d: 4
|
||||
"#{'e'}": 5
|
||||
deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
|
||||
# Comments in explicit object.
|
||||
obj = {
|
||||
### leading comment ###
|
||||
"#{'a'}": 1
|
||||
|
||||
### middle ###
|
||||
|
||||
"#{'b'}": 2
|
||||
# regular comment
|
||||
'c': 3
|
||||
### foo ###
|
||||
d: 4
|
||||
"#{'e'}": 5
|
||||
}
|
||||
deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
|
||||
# A more complicated case.
|
||||
obj = {
|
||||
"#{'interpolated'}":
|
||||
"""
|
||||
#{ '''nested''' }
|
||||
""": 123: 456
|
||||
}
|
||||
deepEqual obj,
|
||||
interpolated:
|
||||
nested:
|
||||
123: 456
|
||||
|
||||
Reference in New Issue
Block a user