mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
[CS2] Support for CSX - equivalent of JSX (#4551)
* CSX implementation * fixed comment, used toJS, added error tests, fixed error in identifier regex, fixed interpolation inside attributes value and added test * added missing test for bare attributes, split attribute and indentifier regex, fixed checking for closing tags closing angle bracket * Refactor tests that compare expected generated JavaScript with actual generated JavaScript to use common helper; add colors to error message to make differences easier to read * Better match the style of the rest of the codebase * Remove unused function * More style fixes * Allow unspaced less-than operator when not using CSX * Replace includesCSX with a counter and simplify the unspaced operator logic * Fixed indexing and realized that I completely enabled the tight spacing, added a test for it too * Style fixes
This commit is contained in:
parent
63b109a4f5
commit
dc0fb85fd3
17 changed files with 1712 additions and 609 deletions
4
Cakefile
4
Cakefile
|
@ -378,6 +378,10 @@ runTests = (CoffeeScript) ->
|
||||||
# Convenience aliases.
|
# Convenience aliases.
|
||||||
global.CoffeeScript = CoffeeScript
|
global.CoffeeScript = CoffeeScript
|
||||||
global.Repl = require './lib/coffeescript/repl'
|
global.Repl = require './lib/coffeescript/repl'
|
||||||
|
global.bold = bold
|
||||||
|
global.red = red
|
||||||
|
global.green = green
|
||||||
|
global.reset = reset
|
||||||
|
|
||||||
# Our test helper function for delimiting different test cases.
|
# Our test helper function for delimiting different test cases.
|
||||||
global.test = (description, fn) ->
|
global.test = (description, fn) ->
|
||||||
|
|
|
@ -68,6 +68,8 @@
|
||||||
Identifier: [
|
Identifier: [
|
||||||
o('IDENTIFIER', function() {
|
o('IDENTIFIER', function() {
|
||||||
return new IdentifierLiteral($1);
|
return new IdentifierLiteral($1);
|
||||||
|
}), o('CSX_TAG', function() {
|
||||||
|
return new CSXTag($1);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
Property: [
|
Property: [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generated by CoffeeScript 2.0.0-beta2
|
// Generated by CoffeeScript 2.0.0-beta2
|
||||||
(function() {
|
(function() {
|
||||||
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError,
|
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, CSX_ATTRIBUTE, CSX_IDENTIFIER, CSX_INTERPOLATION, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_CSX, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError,
|
||||||
indexOf = [].indexOf;
|
indexOf = [].indexOf;
|
||||||
|
|
||||||
({Rewriter, INVERSES} = require('./rewriter'));
|
({Rewriter, INVERSES} = require('./rewriter'));
|
||||||
|
@ -24,12 +24,13 @@
|
||||||
this.seenExport = false;
|
this.seenExport = false;
|
||||||
this.importSpecifierList = false;
|
this.importSpecifierList = false;
|
||||||
this.exportSpecifierList = false;
|
this.exportSpecifierList = false;
|
||||||
|
this.csxDepth = 0;
|
||||||
this.chunkLine = opts.line || 0;
|
this.chunkLine = opts.line || 0;
|
||||||
this.chunkColumn = opts.column || 0;
|
this.chunkColumn = opts.column || 0;
|
||||||
code = this.clean(code);
|
code = this.clean(code);
|
||||||
i = 0;
|
i = 0;
|
||||||
while (this.chunk = code.slice(i)) {
|
while (this.chunk = code.slice(i)) {
|
||||||
consumed = this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken();
|
consumed = this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.stringToken() || this.numberToken() || this.csxToken() || this.regexToken() || this.jsToken() || this.literalToken();
|
||||||
[this.chunkLine, this.chunkColumn] = this.getLineAndColumnFromChunk(consumed);
|
[this.chunkLine, this.chunkColumn] = this.getLineAndColumnFromChunk(consumed);
|
||||||
i += consumed;
|
i += consumed;
|
||||||
if (opts.untilBalanced && this.ends.length === 0) {
|
if (opts.untilBalanced && this.ends.length === 0) {
|
||||||
|
@ -65,8 +66,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
identifierToken() {
|
identifierToken() {
|
||||||
var alias, colon, colonOffset, id, idLength, input, match, poppedToken, prev, prevprev, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, tag, tagToken;
|
var alias, colon, colonOffset, colonToken, id, idLength, inCSXTag, input, match, poppedToken, prev, prevprev, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, regex, tag, tagToken;
|
||||||
if (!(match = IDENTIFIER.exec(this.chunk))) {
|
inCSXTag = this.atCSXTag();
|
||||||
|
regex = inCSXTag ? CSX_ATTRIBUTE : IDENTIFIER;
|
||||||
|
if (!(match = regex.exec(this.chunk))) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
[input, id, colon] = match;
|
[input, id, colon] = match;
|
||||||
|
@ -180,8 +183,14 @@
|
||||||
[tagToken[2].first_line, tagToken[2].first_column] = [poppedToken[2].first_line, poppedToken[2].first_column];
|
[tagToken[2].first_line, tagToken[2].first_column] = [poppedToken[2].first_line, poppedToken[2].first_column];
|
||||||
}
|
}
|
||||||
if (colon) {
|
if (colon) {
|
||||||
colonOffset = input.lastIndexOf(':');
|
colonOffset = input.lastIndexOf(inCSXTag ? '=' : ':');
|
||||||
this.token(':', ':', colonOffset, colon.length);
|
colonToken = this.token(':', ':', colonOffset, colon.length);
|
||||||
|
if (inCSXTag) {
|
||||||
|
colonToken.csxColon = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inCSXTag && tag === 'IDENTIFIER' && prev[0] !== ':') {
|
||||||
|
this.token(',', ',', 0, 0, tagToken);
|
||||||
}
|
}
|
||||||
return input.length;
|
return input.length;
|
||||||
}
|
}
|
||||||
|
@ -313,6 +322,9 @@
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (this.atCSXTag()) {
|
||||||
|
this.token(',', ',', 0, 0, this.prev);
|
||||||
|
}
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,6 +576,98 @@
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
csxToken() {
|
||||||
|
var afterTag, colon, csxTag, end, firstChar, id, input, match, origin, prev, ref, token, tokens;
|
||||||
|
firstChar = this.chunk[0];
|
||||||
|
if (firstChar === '<') {
|
||||||
|
match = CSX_IDENTIFIER.exec(this.chunk.slice(1));
|
||||||
|
if (!(match && (this.csxDepth > 0 || !(prev = this.prev()) || prev.spaced || (ref = prev[0], indexOf.call(COMPARABLE_LEFT_SIDE, ref) < 0)))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
[input, id, colon] = match;
|
||||||
|
origin = this.token('CSX_TAG', id, 1, id.length);
|
||||||
|
this.token('CALL_START', '(');
|
||||||
|
this.token('{', '{');
|
||||||
|
this.ends.push({
|
||||||
|
tag: '/>',
|
||||||
|
origin: origin,
|
||||||
|
name: id
|
||||||
|
});
|
||||||
|
this.csxDepth++;
|
||||||
|
return id.length + 1;
|
||||||
|
} else if (csxTag = this.atCSXTag()) {
|
||||||
|
if (this.chunk.slice(0, 2) === '/>') {
|
||||||
|
this.pair('/>');
|
||||||
|
this.token('}', '}', 0, 2);
|
||||||
|
this.token('CALL_END', ')', 0, 2);
|
||||||
|
this.csxDepth--;
|
||||||
|
return 2;
|
||||||
|
} else if (firstChar === '{') {
|
||||||
|
token = this.token('(', '(');
|
||||||
|
this.ends.push({
|
||||||
|
tag: '}',
|
||||||
|
origin: token
|
||||||
|
});
|
||||||
|
return 1;
|
||||||
|
} else if (firstChar === '>') {
|
||||||
|
this.pair('/>');
|
||||||
|
origin = this.token('}', '}');
|
||||||
|
this.token(',', ',');
|
||||||
|
({
|
||||||
|
tokens,
|
||||||
|
index: end
|
||||||
|
} = this.matchWithInterpolations(INSIDE_CSX, '>', '</', CSX_INTERPOLATION));
|
||||||
|
this.mergeInterpolationTokens(tokens, {
|
||||||
|
delimiter: '"'
|
||||||
|
}, (value, i) => {
|
||||||
|
return this.formatString(value, {
|
||||||
|
delimiter: '>'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
match = CSX_IDENTIFIER.exec(this.chunk.slice(end));
|
||||||
|
if (!match || match[0] !== csxTag.name) {
|
||||||
|
this.error(`expected corresponding CSX closing tag for ${csxTag.name}`, csxTag.origin[2]);
|
||||||
|
}
|
||||||
|
afterTag = end + csxTag.name.length;
|
||||||
|
if (this.chunk[afterTag] !== '>') {
|
||||||
|
this.error("missing closing > after tag name", {
|
||||||
|
offset: afterTag,
|
||||||
|
length: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.token('CALL_END', ')', end, csxTag.name.length + 1);
|
||||||
|
this.csxDepth--;
|
||||||
|
return afterTag + 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (this.atCSXTag(1)) {
|
||||||
|
if (firstChar === '}') {
|
||||||
|
this.pair(firstChar);
|
||||||
|
this.token(')', ')');
|
||||||
|
this.token(',', ',');
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atCSXTag(depth = 0) {
|
||||||
|
var i, last, ref;
|
||||||
|
if (this.csxDepth === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = this.ends.length - 1;
|
||||||
|
while (((ref = this.ends[i]) != null ? ref.tag : void 0) === 'OUTDENT' || depth-- > 0) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
last = this.ends[i];
|
||||||
|
return (last != null ? last.tag : void 0) === '/>' && last;
|
||||||
|
}
|
||||||
|
|
||||||
literalToken() {
|
literalToken() {
|
||||||
var match, message, origin, prev, ref, ref1, ref2, ref3, skipToken, tag, token, value;
|
var match, message, origin, prev, ref, ref1, ref2, ref3, skipToken, tag, token, value;
|
||||||
if (match = OPERATOR.exec(this.chunk)) {
|
if (match = OPERATOR.exec(this.chunk)) {
|
||||||
|
@ -652,7 +756,7 @@
|
||||||
case ']':
|
case ']':
|
||||||
this.pair(value);
|
this.pair(value);
|
||||||
}
|
}
|
||||||
this.tokens.push(token);
|
this.tokens.push(this.makeToken(tag, value));
|
||||||
return value.length;
|
return value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,8 +795,14 @@
|
||||||
return this.outdentToken(this.indent);
|
return this.outdentToken(this.indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
matchWithInterpolations(regex, delimiter) {
|
matchWithInterpolations(regex, delimiter, closingDelimiter, interpolators) {
|
||||||
var close, column, firstToken, index, lastToken, line, nested, offsetInChunk, open, ref, str, strPart, tokens;
|
var braceInterpolator, close, column, firstToken, index, interpolationOffset, interpolator, lastToken, line, match, nested, offsetInChunk, open, ref, rest, str, strPart, tokens;
|
||||||
|
if (closingDelimiter == null) {
|
||||||
|
closingDelimiter = delimiter;
|
||||||
|
}
|
||||||
|
if (interpolators == null) {
|
||||||
|
interpolators = /^#\{/;
|
||||||
|
}
|
||||||
tokens = [];
|
tokens = [];
|
||||||
offsetInChunk = delimiter.length;
|
offsetInChunk = delimiter.length;
|
||||||
if (this.chunk.slice(0, offsetInChunk) !== delimiter) {
|
if (this.chunk.slice(0, offsetInChunk) !== delimiter) {
|
||||||
|
@ -708,32 +818,43 @@
|
||||||
tokens.push(this.makeToken('NEOSTRING', strPart, offsetInChunk));
|
tokens.push(this.makeToken('NEOSTRING', strPart, offsetInChunk));
|
||||||
str = str.slice(strPart.length);
|
str = str.slice(strPart.length);
|
||||||
offsetInChunk += strPart.length;
|
offsetInChunk += strPart.length;
|
||||||
if (str.slice(0, 2) !== '#{') {
|
if (!(match = interpolators.exec(str))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
[line, column] = this.getLineAndColumnFromChunk(offsetInChunk + 1);
|
[interpolator] = match;
|
||||||
|
interpolationOffset = interpolator.length - 1;
|
||||||
|
[line, column] = this.getLineAndColumnFromChunk(offsetInChunk + interpolationOffset);
|
||||||
|
rest = str.slice(interpolationOffset);
|
||||||
({
|
({
|
||||||
tokens: nested,
|
tokens: nested,
|
||||||
index
|
index
|
||||||
} = new Lexer().tokenize(str.slice(1), {
|
} = new Lexer().tokenize(rest, {
|
||||||
line: line,
|
line: line,
|
||||||
column: column,
|
column: column,
|
||||||
untilBalanced: true
|
untilBalanced: true
|
||||||
}));
|
}));
|
||||||
index += 1;
|
index += interpolationOffset;
|
||||||
open = nested[0], close = nested[nested.length - 1];
|
braceInterpolator = str[index - 1] === '}';
|
||||||
open[0] = open[1] = '(';
|
if (braceInterpolator) {
|
||||||
close[0] = close[1] = ')';
|
open = nested[0], close = nested[nested.length - 1];
|
||||||
close.origin = ['', 'end of interpolation', close[2]];
|
open[0] = open[1] = '(';
|
||||||
|
close[0] = close[1] = ')';
|
||||||
|
close.origin = ['', 'end of interpolation', close[2]];
|
||||||
|
}
|
||||||
if (((ref = nested[1]) != null ? ref[0] : void 0) === 'TERMINATOR') {
|
if (((ref = nested[1]) != null ? ref[0] : void 0) === 'TERMINATOR') {
|
||||||
nested.splice(1, 1);
|
nested.splice(1, 1);
|
||||||
}
|
}
|
||||||
|
if (!braceInterpolator) {
|
||||||
|
open = this.makeToken('(', '(', offsetInChunk, 0);
|
||||||
|
close = this.makeToken(')', ')', offsetInChunk + index, 0);
|
||||||
|
nested = [open, ...nested, close];
|
||||||
|
}
|
||||||
tokens.push(['TOKENS', nested]);
|
tokens.push(['TOKENS', nested]);
|
||||||
str = str.slice(index);
|
str = str.slice(index);
|
||||||
offsetInChunk += index;
|
offsetInChunk += index;
|
||||||
}
|
}
|
||||||
if (str.slice(0, delimiter.length) !== delimiter) {
|
if (str.slice(0, closingDelimiter.length) !== closingDelimiter) {
|
||||||
this.error(`missing ${delimiter}`, {
|
this.error(`missing ${closingDelimiter}`, {
|
||||||
length: delimiter.length
|
length: delimiter.length
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -741,16 +862,16 @@
|
||||||
firstToken[2].first_column -= delimiter.length;
|
firstToken[2].first_column -= delimiter.length;
|
||||||
if (lastToken[1].substr(-1) === '\n') {
|
if (lastToken[1].substr(-1) === '\n') {
|
||||||
lastToken[2].last_line += 1;
|
lastToken[2].last_line += 1;
|
||||||
lastToken[2].last_column = delimiter.length - 1;
|
lastToken[2].last_column = closingDelimiter.length - 1;
|
||||||
} else {
|
} else {
|
||||||
lastToken[2].last_column += delimiter.length;
|
lastToken[2].last_column += closingDelimiter.length;
|
||||||
}
|
}
|
||||||
if (lastToken[1].length === 0) {
|
if (lastToken[1].length === 0) {
|
||||||
lastToken[2].last_column -= 1;
|
lastToken[2].last_column -= 1;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
tokens,
|
tokens,
|
||||||
index: offsetInChunk + delimiter.length
|
index: offsetInChunk + closingDelimiter.length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1080,6 +1201,10 @@
|
||||||
|
|
||||||
IDENTIFIER = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)([^\n\S]*:(?!:))?/;
|
IDENTIFIER = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)([^\n\S]*:(?!:))?/;
|
||||||
|
|
||||||
|
CSX_IDENTIFIER = /^(?![\d<])((?:(?!\s)[\.\-$\w\x7f-\uffff])+)/;
|
||||||
|
|
||||||
|
CSX_ATTRIBUTE = /^(?!\d)((?:(?!\s)[\-$\w\x7f-\uffff])+)([^\S]*=(?!=))?/;
|
||||||
|
|
||||||
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
|
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
|
||||||
|
|
||||||
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/;
|
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/;
|
||||||
|
@ -1106,6 +1231,10 @@
|
||||||
|
|
||||||
HEREDOC_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|"(?!"")|\#(?!\{))*/;
|
HEREDOC_DOUBLE = /^(?:[^\\"#]|\\[\s\S]|"(?!"")|\#(?!\{))*/;
|
||||||
|
|
||||||
|
INSIDE_CSX = /^(?:[^\{<])*/;
|
||||||
|
|
||||||
|
CSX_INTERPOLATION = /^(?:\{|<(?!\/))/;
|
||||||
|
|
||||||
STRING_OMIT = /((?:\\\\)+)|\\[^\S\n]*\n\s*/g;
|
STRING_OMIT = /((?:\\\\)+)|\\[^\S\n]*\n\s*/g;
|
||||||
|
|
||||||
SIMPLE_STRING_OMIT = /\s*\n\s*/g;
|
SIMPLE_STRING_OMIT = /\s*\n\s*/g;
|
||||||
|
@ -1162,6 +1291,8 @@
|
||||||
|
|
||||||
INDEXABLE = CALLABLE.concat(['NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
|
INDEXABLE = CALLABLE.concat(['NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);
|
||||||
|
|
||||||
|
COMPARABLE_LEFT_SIDE = ['IDENTIFIER', ')', ']', 'NUMBER'];
|
||||||
|
|
||||||
NOT_REGEX = INDEXABLE.concat(['++', '--']);
|
NOT_REGEX = INDEXABLE.concat(['++', '--']);
|
||||||
|
|
||||||
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Generated by CoffeeScript 2.0.0-beta2
|
// Generated by CoffeeScript 2.0.0-beta2
|
||||||
(function() {
|
(function() {
|
||||||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, utility,
|
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXTag, Call, Class, Code, CodeFragment, Comment, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, utility,
|
||||||
splice = [].splice,
|
splice = [].splice,
|
||||||
indexOf = [].indexOf,
|
indexOf = [].indexOf,
|
||||||
slice = [].slice;
|
slice = [].slice;
|
||||||
|
@ -294,7 +294,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapInParentheses(fragments) {
|
wrapInParentheses(fragments) {
|
||||||
return [].concat(this.makeCode('('), fragments, this.makeCode(')'));
|
return [this.makeCode('('), ...fragments, this.makeCode(')')];
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapInBraces(fragments) {
|
||||||
|
return [this.makeCode('{'), ...fragments, this.makeCode('}')];
|
||||||
}
|
}
|
||||||
|
|
||||||
joinFragmentArrays(fragmentsList, joinStr) {
|
joinFragmentArrays(fragmentsList, joinStr) {
|
||||||
|
@ -666,7 +670,23 @@
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.StringLiteral = StringLiteral = class StringLiteral extends Literal {};
|
exports.StringLiteral = StringLiteral = class StringLiteral extends Literal {
|
||||||
|
compileNode(o) {
|
||||||
|
var res;
|
||||||
|
return res = this.csx ? [this.makeCode(this.unquote(true))] : super.compileNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
unquote(literal) {
|
||||||
|
var unquoted;
|
||||||
|
unquoted = this.value.slice(1, -1);
|
||||||
|
if (literal) {
|
||||||
|
return unquoted.replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
||||||
|
} else {
|
||||||
|
return unquoted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
exports.RegexLiteral = RegexLiteral = class RegexLiteral extends Literal {};
|
exports.RegexLiteral = RegexLiteral = class RegexLiteral extends Literal {};
|
||||||
|
|
||||||
|
@ -686,6 +706,8 @@
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
exports.CSXTag = CSXTag = class CSXTag extends IdentifierLiteral {};
|
||||||
|
|
||||||
exports.PropertyName = PropertyName = (function() {
|
exports.PropertyName = PropertyName = (function() {
|
||||||
class PropertyName extends Literal {};
|
class PropertyName extends Literal {};
|
||||||
|
|
||||||
|
@ -1060,6 +1082,7 @@
|
||||||
if (this.variable instanceof Value && this.variable.isNotCallable()) {
|
if (this.variable instanceof Value && this.variable.isNotCallable()) {
|
||||||
this.variable.error("literal is not a function");
|
this.variable.error("literal is not a function");
|
||||||
}
|
}
|
||||||
|
this.csx = this.variable.base instanceof CSXTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLocationDataIfMissing(locationData) {
|
updateLocationDataIfMissing(locationData) {
|
||||||
|
@ -1145,6 +1168,9 @@
|
||||||
|
|
||||||
compileNode(o) {
|
compileNode(o) {
|
||||||
var arg, argIndex, compiledArgs, fragments, j, len1, ref1, ref2;
|
var arg, argIndex, compiledArgs, fragments, j, len1, ref1, ref2;
|
||||||
|
if (this.csx) {
|
||||||
|
return this.compileCSX(o);
|
||||||
|
}
|
||||||
if ((ref1 = this.variable) != null) {
|
if ((ref1 = this.variable) != null) {
|
||||||
ref1.front = this.front;
|
ref1.front = this.front;
|
||||||
}
|
}
|
||||||
|
@ -1169,6 +1195,26 @@
|
||||||
return fragments;
|
return fragments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileCSX(o) {
|
||||||
|
var attributes, content, fragments, tag;
|
||||||
|
[attributes, content] = this.args;
|
||||||
|
attributes.base.csx = true;
|
||||||
|
if (content != null) {
|
||||||
|
content.base.csx = true;
|
||||||
|
}
|
||||||
|
fragments = [this.makeCode('<')];
|
||||||
|
fragments.push(...(tag = this.variable.compileToFragments(o, LEVEL_ACCESS)));
|
||||||
|
fragments.push(...attributes.compileToFragments(o, LEVEL_PAREN));
|
||||||
|
if (content) {
|
||||||
|
fragments.push(this.makeCode('>'));
|
||||||
|
fragments.push(...content.compileNode(o, LEVEL_LIST));
|
||||||
|
fragments.push(...[this.makeCode('</'), ...tag, this.makeCode('>')]);
|
||||||
|
} else {
|
||||||
|
fragments.push(this.makeCode(' />'));
|
||||||
|
}
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Call.prototype.children = ['variable', 'args'];
|
Call.prototype.children = ['variable', 'args'];
|
||||||
|
@ -1514,15 +1560,15 @@
|
||||||
ref1 = this.properties;
|
ref1 = this.properties;
|
||||||
for (k = 0, len2 = ref1.length; k < len2; k++) {
|
for (k = 0, len2 = ref1.length; k < len2; k++) {
|
||||||
prop = ref1[k];
|
prop = ref1[k];
|
||||||
if (prop instanceof Comment || (prop instanceof Assign && prop.context === 'object')) {
|
if (prop instanceof Comment || (prop instanceof Assign && prop.context === 'object' && !this.csx)) {
|
||||||
isCompact = false;
|
isCompact = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
answer = [];
|
answer = [];
|
||||||
answer.push(this.makeCode(`{${(isCompact ? '' : '\n')}`));
|
answer.push(this.makeCode(isCompact ? '' : '\n'));
|
||||||
for (i = l = 0, len3 = props.length; l < len3; i = ++l) {
|
for (i = l = 0, len3 = props.length; l < len3; i = ++l) {
|
||||||
prop = props[i];
|
prop = props[i];
|
||||||
join = i === props.length - 1 ? '' : isCompact ? ', ' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n';
|
join = i === props.length - 1 ? '' : isCompact && this.csx ? ' ' : isCompact ? ', ' : prop === lastNoncom || prop instanceof Comment || this.csx ? '\n' : ',\n';
|
||||||
indent = isCompact || prop instanceof Comment ? '' : idt;
|
indent = isCompact || prop instanceof Comment ? '' : idt;
|
||||||
key = prop instanceof Assign && prop.context === 'object' ? prop.variable : prop instanceof Assign ? (!this.lhs ? prop.operatorToken.error(`unexpected ${prop.operatorToken.value}`) : void 0, prop.variable) : !(prop instanceof Comment) ? prop : void 0;
|
key = prop instanceof Assign && prop.context === 'object' ? prop.variable : prop instanceof Assign ? (!this.lhs ? prop.operatorToken.error(`unexpected ${prop.operatorToken.value}`) : void 0, prop.variable) : !(prop instanceof Comment) ? prop : void 0;
|
||||||
if (key instanceof Value && key.hasProperties()) {
|
if (key instanceof Value && key.hasProperties()) {
|
||||||
|
@ -1546,12 +1592,21 @@
|
||||||
if (indent) {
|
if (indent) {
|
||||||
answer.push(this.makeCode(indent));
|
answer.push(this.makeCode(indent));
|
||||||
}
|
}
|
||||||
|
if (this.csx) {
|
||||||
|
prop.csx = true;
|
||||||
|
}
|
||||||
|
if (this.csx && i === 0) {
|
||||||
|
answer.push(this.makeCode(' '));
|
||||||
|
}
|
||||||
answer.push(...prop.compileToFragments(o, LEVEL_TOP));
|
answer.push(...prop.compileToFragments(o, LEVEL_TOP));
|
||||||
if (join) {
|
if (join) {
|
||||||
answer.push(this.makeCode(join));
|
answer.push(this.makeCode(join));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
answer.push(this.makeCode(`${(isCompact ? '' : `\n${this.tab}`)}}`));
|
answer.push(this.makeCode(isCompact ? '' : `\n${this.tab}`));
|
||||||
|
if (!this.csx) {
|
||||||
|
answer = this.wrapInBraces(answer);
|
||||||
|
}
|
||||||
if (this.front) {
|
if (this.front) {
|
||||||
return this.wrapInParentheses(answer);
|
return this.wrapInParentheses(answer);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2386,6 +2441,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.csx) {
|
||||||
|
this.value.base.csxAttribute = true;
|
||||||
|
}
|
||||||
val = this.value.compileToFragments(o, LEVEL_LIST);
|
val = this.value.compileToFragments(o, LEVEL_LIST);
|
||||||
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
|
||||||
if (this.context === 'object') {
|
if (this.context === 'object') {
|
||||||
|
@ -2393,7 +2451,7 @@
|
||||||
compiledName.unshift(this.makeCode('['));
|
compiledName.unshift(this.makeCode('['));
|
||||||
compiledName.push(this.makeCode(']'));
|
compiledName.push(this.makeCode(']'));
|
||||||
}
|
}
|
||||||
return compiledName.concat(this.makeCode(": "), val);
|
return compiledName.concat(this.makeCode(this.csx ? '=' : ': '), val);
|
||||||
}
|
}
|
||||||
answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val);
|
answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val);
|
||||||
if (o.level > LEVEL_LIST || (isValue && this.variable.base instanceof Obj && !this.param)) {
|
if (o.level > LEVEL_LIST || (isValue && this.variable.base instanceof Obj && !this.param)) {
|
||||||
|
@ -3672,12 +3730,15 @@
|
||||||
compileNode(o) {
|
compileNode(o) {
|
||||||
var bare, expr, fragments;
|
var bare, expr, fragments;
|
||||||
expr = this.body.unwrap();
|
expr = this.body.unwrap();
|
||||||
if (expr instanceof Value && expr.isAtomic()) {
|
if (expr instanceof Value && expr.isAtomic() && !this.csxAttribute) {
|
||||||
expr.front = this.front;
|
expr.front = this.front;
|
||||||
return expr.compileToFragments(o);
|
return expr.compileToFragments(o);
|
||||||
}
|
}
|
||||||
fragments = expr.compileToFragments(o, LEVEL_PAREN);
|
fragments = expr.compileToFragments(o, LEVEL_PAREN);
|
||||||
bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)) && (o.level < LEVEL_COND || fragments.length <= 3);
|
bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)) && (o.level < LEVEL_COND || fragments.length <= 3);
|
||||||
|
if (this.csxAttribute) {
|
||||||
|
return this.wrapInBraces(fragments);
|
||||||
|
}
|
||||||
if (bare) {
|
if (bare) {
|
||||||
return fragments;
|
return fragments;
|
||||||
} else {
|
} else {
|
||||||
|
@ -3709,7 +3770,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
compileNode(o) {
|
compileNode(o) {
|
||||||
var element, elements, expr, fragments, j, len1, value;
|
var code, element, elements, expr, fragments, j, len1, value, wrapped;
|
||||||
|
if (this.csxAttribute) {
|
||||||
|
wrapped = new Parens(new StringWithInterpolations(this.body));
|
||||||
|
wrapped.csxAttribute = true;
|
||||||
|
return wrapped.compileNode(o);
|
||||||
|
}
|
||||||
expr = this.body.unwrap();
|
expr = this.body.unwrap();
|
||||||
elements = [];
|
elements = [];
|
||||||
expr.traverseChildren(false, function(node) {
|
expr.traverseChildren(false, function(node) {
|
||||||
|
@ -3723,29 +3789,47 @@
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
fragments = [];
|
fragments = [];
|
||||||
fragments.push(this.makeCode('`'));
|
if (!this.csx) {
|
||||||
|
fragments.push(this.makeCode('`'));
|
||||||
|
}
|
||||||
for (j = 0, len1 = elements.length; j < len1; j++) {
|
for (j = 0, len1 = elements.length; j < len1; j++) {
|
||||||
element = elements[j];
|
element = elements[j];
|
||||||
if (element instanceof StringLiteral) {
|
if (element instanceof StringLiteral) {
|
||||||
value = element.value.slice(1, -1);
|
value = element.unquote(this.csx);
|
||||||
value = value.replace(/(\\*)(`|\$\{)/g, function(match, backslashes, toBeEscaped) {
|
if (!this.csx) {
|
||||||
if (backslashes.length % 2 === 0) {
|
value = value.replace(/(\\*)(`|\$\{)/g, function(match, backslashes, toBeEscaped) {
|
||||||
return `${backslashes}\\${toBeEscaped}`;
|
if (backslashes.length % 2 === 0) {
|
||||||
} else {
|
return `${backslashes}\\${toBeEscaped}`;
|
||||||
return match;
|
} else {
|
||||||
}
|
return match;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
fragments.push(this.makeCode(value));
|
fragments.push(this.makeCode(value));
|
||||||
} else {
|
} else {
|
||||||
fragments.push(this.makeCode('${'));
|
if (!this.csx) {
|
||||||
fragments.push(...element.compileToFragments(o, LEVEL_PAREN));
|
fragments.push(this.makeCode('$'));
|
||||||
fragments.push(this.makeCode('}'));
|
}
|
||||||
|
code = element.compileToFragments(o, LEVEL_PAREN);
|
||||||
|
if (!this.isNestedTag(element)) {
|
||||||
|
code = this.wrapInBraces(code);
|
||||||
|
}
|
||||||
|
fragments.push(...code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragments.push(this.makeCode('`'));
|
if (!this.csx) {
|
||||||
|
fragments.push(this.makeCode('`'));
|
||||||
|
}
|
||||||
return fragments;
|
return fragments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNestedTag(element) {
|
||||||
|
var call, exprs, ref1;
|
||||||
|
exprs = element != null ? (ref1 = element.body) != null ? ref1.expressions : void 0 : void 0;
|
||||||
|
call = exprs != null ? exprs[0] : void 0;
|
||||||
|
return this.csx && exprs && exprs.length === 1 && call instanceof Call && call.csx;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
StringWithInterpolations.prototype.children = ['body'];
|
StringWithInterpolations.prototype.children = ['body'];
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,10 @@
|
||||||
// Generated by CoffeeScript 2.0.0-beta2
|
// Generated by CoffeeScript 2.0.0-beta2
|
||||||
(function() {
|
(function() {
|
||||||
var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, generate, k, left, len, rite,
|
var BALANCED_PAIRS, CALL_CLOSERS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, generate, k, left, len, rite, throwSyntaxError,
|
||||||
indexOf = [].indexOf;
|
indexOf = [].indexOf;
|
||||||
|
|
||||||
|
({throwSyntaxError} = require('./helpers'));
|
||||||
|
|
||||||
generate = function(tag, value, origin) {
|
generate = function(tag, value, origin) {
|
||||||
var tok;
|
var tok;
|
||||||
tok = [tag, value];
|
tok = [tag, value];
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
this.tagPostfixConditionals();
|
this.tagPostfixConditionals();
|
||||||
this.addImplicitBracesAndParens();
|
this.addImplicitBracesAndParens();
|
||||||
this.addLocationDataToGeneratedTokens();
|
this.addLocationDataToGeneratedTokens();
|
||||||
|
this.enforceValidCSXAttributes();
|
||||||
this.fixOutdentLocationData();
|
this.fixOutdentLocationData();
|
||||||
return this.tokens;
|
return this.tokens;
|
||||||
}
|
}
|
||||||
|
@ -362,6 +365,19 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enforceValidCSXAttributes() {
|
||||||
|
return this.scanTokens(function(token, i, tokens) {
|
||||||
|
var next, ref;
|
||||||
|
if (token.csxColon) {
|
||||||
|
next = tokens[i + 1];
|
||||||
|
if ((ref = next[0]) !== 'STRING_START' && ref !== 'STRING' && ref !== '(') {
|
||||||
|
throwSyntaxError('expected wrapped or quoted CSX attribute', next[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addLocationDataToGeneratedTokens() {
|
addLocationDataToGeneratedTokens() {
|
||||||
return this.scanTokens(function(token, i, tokens) {
|
return this.scanTokens(function(token, i, tokens) {
|
||||||
var column, line, nextLocation, prevLocation, ref, ref1;
|
var column, line, nextLocation, prevLocation, ref, ref1;
|
||||||
|
@ -528,7 +544,7 @@
|
||||||
|
|
||||||
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
|
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
|
||||||
|
|
||||||
IMPLICIT_CALL = ['IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
IMPLICIT_CALL = ['IDENTIFIER', 'CSX_TAG', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
||||||
|
|
||||||
IMPLICIT_UNSPACED_CALL = ['+', '-'];
|
IMPLICIT_UNSPACED_CALL = ['+', '-'];
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,7 @@ grammar =
|
||||||
|
|
||||||
Identifier: [
|
Identifier: [
|
||||||
o 'IDENTIFIER', -> new IdentifierLiteral $1
|
o 'IDENTIFIER', -> new IdentifierLiteral $1
|
||||||
|
o 'CSX_TAG', -> new CSXTag $1
|
||||||
]
|
]
|
||||||
|
|
||||||
Property: [
|
Property: [
|
||||||
|
|
166
src/lexer.coffee
166
src/lexer.coffee
|
@ -48,6 +48,7 @@ exports.Lexer = class Lexer
|
||||||
@seenExport = no # Used to recognize EXPORT FROM? AS? tokens.
|
@seenExport = no # Used to recognize EXPORT FROM? AS? tokens.
|
||||||
@importSpecifierList = no # Used to identify when in an IMPORT {...} FROM? ...
|
@importSpecifierList = no # Used to identify when in an IMPORT {...} FROM? ...
|
||||||
@exportSpecifierList = no # Used to identify when in an EXPORT {...} FROM? ...
|
@exportSpecifierList = no # Used to identify when in an EXPORT {...} FROM? ...
|
||||||
|
@csxDepth = 0 # Used to optimize CSX checks, how deep in CSX we are.
|
||||||
|
|
||||||
@chunkLine =
|
@chunkLine =
|
||||||
opts.line or 0 # The start line for the current @chunk.
|
opts.line or 0 # The start line for the current @chunk.
|
||||||
|
@ -67,6 +68,7 @@ exports.Lexer = class Lexer
|
||||||
@lineToken() or
|
@lineToken() or
|
||||||
@stringToken() or
|
@stringToken() or
|
||||||
@numberToken() or
|
@numberToken() or
|
||||||
|
@csxToken() or
|
||||||
@regexToken() or
|
@regexToken() or
|
||||||
@jsToken() or
|
@jsToken() or
|
||||||
@literalToken()
|
@literalToken()
|
||||||
|
@ -105,7 +107,9 @@ exports.Lexer = class Lexer
|
||||||
# referenced as property names here, so you can still do `jQuery.is()` even
|
# referenced as property names here, so you can still do `jQuery.is()` even
|
||||||
# though `is` means `===` otherwise.
|
# though `is` means `===` otherwise.
|
||||||
identifierToken: ->
|
identifierToken: ->
|
||||||
return 0 unless match = IDENTIFIER.exec @chunk
|
inCSXTag = @atCSXTag()
|
||||||
|
regex = if inCSXTag then CSX_ATTRIBUTE else IDENTIFIER
|
||||||
|
return 0 unless match = regex.exec @chunk
|
||||||
[input, id, colon] = match
|
[input, id, colon] = match
|
||||||
|
|
||||||
# Preserve length of id for location data
|
# Preserve length of id for location data
|
||||||
|
@ -205,8 +209,11 @@ exports.Lexer = class Lexer
|
||||||
[tagToken[2].first_line, tagToken[2].first_column] =
|
[tagToken[2].first_line, tagToken[2].first_column] =
|
||||||
[poppedToken[2].first_line, poppedToken[2].first_column]
|
[poppedToken[2].first_line, poppedToken[2].first_column]
|
||||||
if colon
|
if colon
|
||||||
colonOffset = input.lastIndexOf ':'
|
colonOffset = input.lastIndexOf if inCSXTag then '=' else ':'
|
||||||
@token ':', ':', colonOffset, colon.length
|
colonToken = @token ':', ':', colonOffset, colon.length
|
||||||
|
colonToken.csxColon = yes if inCSXTag # used by rewriter
|
||||||
|
if inCSXTag and tag is 'IDENTIFIER' and prev[0] isnt ':'
|
||||||
|
@token ',', ',', 0, 0, tagToken
|
||||||
|
|
||||||
input.length
|
input.length
|
||||||
|
|
||||||
|
@ -289,6 +296,9 @@ exports.Lexer = class Lexer
|
||||||
' '
|
' '
|
||||||
value
|
value
|
||||||
|
|
||||||
|
if @atCSXTag()
|
||||||
|
@token ',', ',', 0, 0, @prev
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Matches and consumes comments.
|
# Matches and consumes comments.
|
||||||
|
@ -475,6 +485,76 @@ exports.Lexer = class Lexer
|
||||||
@tokens.pop() if @value() is '\\'
|
@tokens.pop() if @value() is '\\'
|
||||||
this
|
this
|
||||||
|
|
||||||
|
# CSX is like JSX but for CoffeeScript.
|
||||||
|
csxToken: ->
|
||||||
|
firstChar = @chunk[0]
|
||||||
|
if firstChar is '<'
|
||||||
|
match = CSX_IDENTIFIER.exec @chunk[1...]
|
||||||
|
return 0 unless match and (
|
||||||
|
@csxDepth > 0 or
|
||||||
|
# Not the right hand side of an unspaced comparison (i.e. `a<b`).
|
||||||
|
not (prev = @prev()) or
|
||||||
|
prev.spaced or
|
||||||
|
prev[0] not in COMPARABLE_LEFT_SIDE
|
||||||
|
)
|
||||||
|
[input, id, colon] = match
|
||||||
|
origin = @token 'CSX_TAG', id, 1, id.length
|
||||||
|
@token 'CALL_START', '('
|
||||||
|
@token '{', '{'
|
||||||
|
@ends.push tag: '/>', origin: origin, name: id
|
||||||
|
@csxDepth++
|
||||||
|
return id.length + 1
|
||||||
|
else if csxTag = @atCSXTag()
|
||||||
|
if @chunk[...2] is '/>'
|
||||||
|
@pair '/>'
|
||||||
|
@token '}', '}', 0, 2
|
||||||
|
@token 'CALL_END', ')', 0, 2
|
||||||
|
@csxDepth--
|
||||||
|
return 2
|
||||||
|
else if firstChar is '{'
|
||||||
|
token = @token '(', '('
|
||||||
|
@ends.push {tag: '}', origin: token}
|
||||||
|
return 1
|
||||||
|
else if firstChar is '>'
|
||||||
|
# Ignore terminators inside a tag.
|
||||||
|
@pair '/>' # As if the current tag was self-closing.
|
||||||
|
origin = @token '}', '}'
|
||||||
|
@token ',', ','
|
||||||
|
{tokens, index: end} =
|
||||||
|
@matchWithInterpolations INSIDE_CSX, '>', '</', CSX_INTERPOLATION
|
||||||
|
@mergeInterpolationTokens tokens, {delimiter: '"'}, (value, i) =>
|
||||||
|
@formatString value, delimiter: '>'
|
||||||
|
match = CSX_IDENTIFIER.exec @chunk[end...]
|
||||||
|
if not match or match[0] isnt csxTag.name
|
||||||
|
@error "expected corresponding CSX closing tag for #{csxTag.name}",
|
||||||
|
csxTag.origin[2]
|
||||||
|
afterTag = end + csxTag.name.length
|
||||||
|
if @chunk[afterTag] isnt '>'
|
||||||
|
@error "missing closing > after tag name", offset: afterTag, length: 1
|
||||||
|
# +1 for the closing `>`.
|
||||||
|
@token 'CALL_END', ')', end, csxTag.name.length + 1
|
||||||
|
@csxDepth--
|
||||||
|
return afterTag + 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
else if @atCSXTag 1
|
||||||
|
if firstChar is '}'
|
||||||
|
@pair firstChar
|
||||||
|
@token ')', ')'
|
||||||
|
@token ',', ','
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
|
||||||
|
atCSXTag: (depth = 0) ->
|
||||||
|
return no if @csxDepth is 0
|
||||||
|
i = @ends.length - 1
|
||||||
|
i-- while @ends[i]?.tag is 'OUTDENT' or depth-- > 0 # Ignore indents.
|
||||||
|
last = @ends[i]
|
||||||
|
last?.tag is '/>' and last
|
||||||
|
|
||||||
# We treat all other single characters as a token. E.g.: `( ) , . !`
|
# We treat all other single characters as a token. E.g.: `( ) , . !`
|
||||||
# Multi-character operators are also literal tokens, so that Jison can assign
|
# Multi-character operators are also literal tokens, so that Jison can assign
|
||||||
# the proper order of operations. There are some symbols that we tag specially
|
# the proper order of operations. There are some symbols that we tag specially
|
||||||
|
@ -535,7 +615,7 @@ exports.Lexer = class Lexer
|
||||||
switch value
|
switch value
|
||||||
when '(', '{', '[' then @ends.push {tag: INVERSES[value], origin: token}
|
when '(', '{', '[' then @ends.push {tag: INVERSES[value], origin: token}
|
||||||
when ')', '}', ']' then @pair value
|
when ')', '}', ']' then @pair value
|
||||||
@tokens.push token
|
@tokens.push @makeToken tag, value
|
||||||
value.length
|
value.length
|
||||||
|
|
||||||
# Token Manipulators
|
# Token Manipulators
|
||||||
|
@ -582,10 +662,16 @@ exports.Lexer = class Lexer
|
||||||
# `#{` if interpolations are desired).
|
# `#{` if interpolations are desired).
|
||||||
# - `delimiter` is the delimiter of the token. Examples are `'`, `"`, `'''`,
|
# - `delimiter` is the delimiter of the token. Examples are `'`, `"`, `'''`,
|
||||||
# `"""` and `///`.
|
# `"""` and `///`.
|
||||||
|
# - `closingDelimiter` is different from `delimiter` only in CSX
|
||||||
|
# - `interpolators` matches the start of an interpolation, for CSX it's both
|
||||||
|
# `{` and `<` (i.e. nested CSX tag)
|
||||||
#
|
#
|
||||||
# This method allows us to have strings within interpolations within strings,
|
# This method allows us to have strings within interpolations within strings,
|
||||||
# ad infinitum.
|
# ad infinitum.
|
||||||
matchWithInterpolations: (regex, delimiter) ->
|
matchWithInterpolations: (regex, delimiter, closingDelimiter, interpolators) ->
|
||||||
|
closingDelimiter ?= delimiter
|
||||||
|
interpolators ?= /^#\{/
|
||||||
|
|
||||||
tokens = []
|
tokens = []
|
||||||
offsetInChunk = delimiter.length
|
offsetInChunk = delimiter.length
|
||||||
return null unless @chunk[...offsetInChunk] is delimiter
|
return null unless @chunk[...offsetInChunk] is delimiter
|
||||||
|
@ -601,44 +687,55 @@ exports.Lexer = class Lexer
|
||||||
str = str[strPart.length..]
|
str = str[strPart.length..]
|
||||||
offsetInChunk += strPart.length
|
offsetInChunk += strPart.length
|
||||||
|
|
||||||
break unless str[...2] is '#{'
|
break unless match = interpolators.exec str
|
||||||
|
[interpolator] = match
|
||||||
|
|
||||||
# The `1`s are to remove the `#` in `#{`.
|
# To remove the `#` in `#{`.
|
||||||
[line, column] = @getLineAndColumnFromChunk offsetInChunk + 1
|
interpolationOffset = interpolator.length - 1
|
||||||
|
[line, column] = @getLineAndColumnFromChunk offsetInChunk + interpolationOffset
|
||||||
|
rest = str[interpolationOffset..]
|
||||||
{tokens: nested, index} =
|
{tokens: nested, index} =
|
||||||
new Lexer().tokenize str[1..], line: line, column: column, untilBalanced: on
|
new Lexer().tokenize rest, line: line, column: column, untilBalanced: on
|
||||||
# Skip the trailing `}`.
|
# Account for the `#` in `#{`
|
||||||
index += 1
|
index += interpolationOffset
|
||||||
|
|
||||||
# Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
|
braceInterpolator = str[index - 1] is '}'
|
||||||
# parentheses will be removed later.
|
if braceInterpolator
|
||||||
[open, ..., close] = nested
|
# Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
|
||||||
open[0] = open[1] = '('
|
# parentheses will be removed later.
|
||||||
close[0] = close[1] = ')'
|
[open, ..., close] = nested
|
||||||
close.origin = ['', 'end of interpolation', close[2]]
|
open[0] = open[1] = '('
|
||||||
|
close[0] = close[1] = ')'
|
||||||
|
close.origin = ['', 'end of interpolation', close[2]]
|
||||||
|
|
||||||
# Remove leading `'TERMINATOR'` (if any).
|
# Remove leading `'TERMINATOR'` (if any).
|
||||||
nested.splice 1, 1 if nested[1]?[0] is 'TERMINATOR'
|
nested.splice 1, 1 if nested[1]?[0] is 'TERMINATOR'
|
||||||
|
|
||||||
|
unless braceInterpolator
|
||||||
|
# We are not using `{` and `}`, so wrap the interpolated tokens instead.
|
||||||
|
open = @makeToken '(', '(', offsetInChunk, 0
|
||||||
|
close = @makeToken ')', ')', offsetInChunk + index, 0
|
||||||
|
nested = [open, nested..., close]
|
||||||
|
|
||||||
# Push a fake `'TOKENS'` token, which will get turned into real tokens later.
|
# Push a fake `'TOKENS'` token, which will get turned into real tokens later.
|
||||||
tokens.push ['TOKENS', nested]
|
tokens.push ['TOKENS', nested]
|
||||||
|
|
||||||
str = str[index..]
|
str = str[index..]
|
||||||
offsetInChunk += index
|
offsetInChunk += index
|
||||||
|
|
||||||
unless str[...delimiter.length] is delimiter
|
unless str[...closingDelimiter.length] is closingDelimiter
|
||||||
@error "missing #{delimiter}", length: delimiter.length
|
@error "missing #{closingDelimiter}", length: delimiter.length
|
||||||
|
|
||||||
[firstToken, ..., lastToken] = tokens
|
[firstToken, ..., lastToken] = tokens
|
||||||
firstToken[2].first_column -= delimiter.length
|
firstToken[2].first_column -= delimiter.length
|
||||||
if lastToken[1].substr(-1) is '\n'
|
if lastToken[1].substr(-1) is '\n'
|
||||||
lastToken[2].last_line += 1
|
lastToken[2].last_line += 1
|
||||||
lastToken[2].last_column = delimiter.length - 1
|
lastToken[2].last_column = closingDelimiter.length - 1
|
||||||
else
|
else
|
||||||
lastToken[2].last_column += delimiter.length
|
lastToken[2].last_column += closingDelimiter.length
|
||||||
lastToken[2].last_column -= 1 if lastToken[1].length is 0
|
lastToken[2].last_column -= 1 if lastToken[1].length is 0
|
||||||
|
|
||||||
{tokens, index: offsetInChunk + delimiter.length}
|
{tokens, index: offsetInChunk + closingDelimiter.length}
|
||||||
|
|
||||||
# Merge the array `tokens` of the fake token types `'TOKENS'` and `'NEOSTRING'`
|
# Merge the array `tokens` of the fake token types `'TOKENS'` and `'NEOSTRING'`
|
||||||
# (as returned by `matchWithInterpolations`) into the token stream. The value
|
# (as returned by `matchWithInterpolations`) into the token stream. The value
|
||||||
|
@ -976,6 +1073,17 @@ IDENTIFIER = /// ^
|
||||||
( [^\n\S]* : (?!:) )? # Is this a property name?
|
( [^\n\S]* : (?!:) )? # Is this a property name?
|
||||||
///
|
///
|
||||||
|
|
||||||
|
CSX_IDENTIFIER = /// ^
|
||||||
|
(?![\d<]) # Must not start with `<`.
|
||||||
|
( (?: (?!\s)[\.\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s and `.`s.
|
||||||
|
///
|
||||||
|
|
||||||
|
CSX_ATTRIBUTE = /// ^
|
||||||
|
(?!\d)
|
||||||
|
( (?: (?!\s)[\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s.
|
||||||
|
( [^\S]* = (?!=) )? # Is this an attribute with a value?
|
||||||
|
///
|
||||||
|
|
||||||
NUMBER = ///
|
NUMBER = ///
|
||||||
^ 0b[01]+ | # binary
|
^ 0b[01]+ | # binary
|
||||||
^ 0o[0-7]+ | # octal
|
^ 0o[0-7]+ | # octal
|
||||||
|
@ -1012,6 +1120,17 @@ STRING_DOUBLE = /// ^(?: [^\\"#] | \\[\s\S] | \#(?!\{) )* ///
|
||||||
HEREDOC_SINGLE = /// ^(?: [^\\'] | \\[\s\S] | '(?!'') )* ///
|
HEREDOC_SINGLE = /// ^(?: [^\\'] | \\[\s\S] | '(?!'') )* ///
|
||||||
HEREDOC_DOUBLE = /// ^(?: [^\\"#] | \\[\s\S] | "(?!"") | \#(?!\{) )* ///
|
HEREDOC_DOUBLE = /// ^(?: [^\\"#] | \\[\s\S] | "(?!"") | \#(?!\{) )* ///
|
||||||
|
|
||||||
|
INSIDE_CSX = /// ^(?:
|
||||||
|
[^
|
||||||
|
\{ # Start of CoffeeScript interpolation.
|
||||||
|
< # Maybe CSX tag (`<` not allowed even if bare).
|
||||||
|
]
|
||||||
|
)* /// # Similar to `HEREDOC_DOUBLE` but there is no escaping.
|
||||||
|
CSX_INTERPOLATION = /// ^(?:
|
||||||
|
\{ # CoffeeScript interpolation.
|
||||||
|
| <(?!/) # CSX opening tag.
|
||||||
|
)///
|
||||||
|
|
||||||
STRING_OMIT = ///
|
STRING_OMIT = ///
|
||||||
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
|
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
|
||||||
| \\[^\S\n]*\n\s* # Remove escaped newlines.
|
| \\[^\S\n]*\n\s* # Remove escaped newlines.
|
||||||
|
@ -1115,6 +1234,9 @@ INDEXABLE = CALLABLE.concat [
|
||||||
'BOOL', 'NULL', 'UNDEFINED', '}', '::'
|
'BOOL', 'NULL', 'UNDEFINED', '}', '::'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Tokens which can be the left-hand side of a less-than comparison, i.e. `a<b`.
|
||||||
|
COMPARABLE_LEFT_SIDE = ['IDENTIFIER', ')', ']', 'NUMBER']
|
||||||
|
|
||||||
# Tokens which a regular expression will never immediately follow (except spaced
|
# Tokens which a regular expression will never immediately follow (except spaced
|
||||||
# CALLABLEs in some cases), but which a division operator can.
|
# CALLABLEs in some cases), but which a division operator can.
|
||||||
#
|
#
|
||||||
|
|
|
@ -280,7 +280,10 @@ exports.Base = class Base
|
||||||
new CodeFragment this, code
|
new CodeFragment this, code
|
||||||
|
|
||||||
wrapInParentheses: (fragments) ->
|
wrapInParentheses: (fragments) ->
|
||||||
[].concat @makeCode('('), fragments, @makeCode(')')
|
[@makeCode('('), fragments..., @makeCode(')')]
|
||||||
|
|
||||||
|
wrapInBraces: (fragments) ->
|
||||||
|
[@makeCode('{'), fragments..., @makeCode('}')]
|
||||||
|
|
||||||
# `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be
|
# `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be
|
||||||
# concatonated together, with `joinStr` added in between each, to produce a final flat array
|
# concatonated together, with `joinStr` added in between each, to produce a final flat array
|
||||||
|
@ -534,6 +537,16 @@ exports.NaNLiteral = class NaNLiteral extends NumberLiteral
|
||||||
if o.level >= LEVEL_OP then @wrapInParentheses code else code
|
if o.level >= LEVEL_OP then @wrapInParentheses code else code
|
||||||
|
|
||||||
exports.StringLiteral = class StringLiteral extends Literal
|
exports.StringLiteral = class StringLiteral extends Literal
|
||||||
|
compileNode: (o) ->
|
||||||
|
res = if @csx then [@makeCode @unquote yes] else super()
|
||||||
|
|
||||||
|
unquote: (literal) ->
|
||||||
|
unquoted = @value[1...-1]
|
||||||
|
if literal
|
||||||
|
unquoted.replace /\\n/g, '\n'
|
||||||
|
.replace /\\"/g, '"'
|
||||||
|
else
|
||||||
|
unquoted
|
||||||
|
|
||||||
exports.RegexLiteral = class RegexLiteral extends Literal
|
exports.RegexLiteral = class RegexLiteral extends Literal
|
||||||
|
|
||||||
|
@ -545,6 +558,8 @@ exports.IdentifierLiteral = class IdentifierLiteral extends Literal
|
||||||
eachName: (iterator) ->
|
eachName: (iterator) ->
|
||||||
iterator @
|
iterator @
|
||||||
|
|
||||||
|
exports.CSXTag = class CSXTag extends IdentifierLiteral
|
||||||
|
|
||||||
exports.PropertyName = class PropertyName extends Literal
|
exports.PropertyName = class PropertyName extends Literal
|
||||||
isAssignable: YES
|
isAssignable: YES
|
||||||
|
|
||||||
|
@ -778,6 +793,8 @@ exports.Call = class Call extends Base
|
||||||
if @variable instanceof Value and @variable.isNotCallable()
|
if @variable instanceof Value and @variable.isNotCallable()
|
||||||
@variable.error "literal is not a function"
|
@variable.error "literal is not a function"
|
||||||
|
|
||||||
|
@csx = @variable.base instanceof CSXTag
|
||||||
|
|
||||||
children: ['variable', 'args']
|
children: ['variable', 'args']
|
||||||
|
|
||||||
# When setting the location, we sometimes need to update the start location to
|
# When setting the location, we sometimes need to update the start location to
|
||||||
|
@ -840,6 +857,7 @@ exports.Call = class Call extends Base
|
||||||
|
|
||||||
# Compile a vanilla function call.
|
# Compile a vanilla function call.
|
||||||
compileNode: (o) ->
|
compileNode: (o) ->
|
||||||
|
return @compileCSX o if @csx
|
||||||
@variable?.front = @front
|
@variable?.front = @front
|
||||||
compiledArgs = []
|
compiledArgs = []
|
||||||
for arg, argIndex in @args
|
for arg, argIndex in @args
|
||||||
|
@ -854,6 +872,21 @@ exports.Call = class Call extends Base
|
||||||
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
|
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
|
||||||
fragments
|
fragments
|
||||||
|
|
||||||
|
compileCSX: (o) ->
|
||||||
|
[attributes, content] = @args
|
||||||
|
attributes.base.csx = yes
|
||||||
|
content?.base.csx = yes
|
||||||
|
fragments = [@makeCode('<')]
|
||||||
|
fragments.push (tag = @variable.compileToFragments(o, LEVEL_ACCESS))...
|
||||||
|
fragments.push attributes.compileToFragments(o, LEVEL_PAREN)...
|
||||||
|
if content
|
||||||
|
fragments.push @makeCode('>')
|
||||||
|
fragments.push content.compileNode(o, LEVEL_LIST)...
|
||||||
|
fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
|
||||||
|
else
|
||||||
|
fragments.push @makeCode(' />')
|
||||||
|
fragments
|
||||||
|
|
||||||
#### Super
|
#### Super
|
||||||
|
|
||||||
# Takes care of converting `super()` calls into calls against the prototype's
|
# Takes care of converting `super()` calls into calls against the prototype's
|
||||||
|
@ -1134,17 +1167,19 @@ exports.Obj = class Obj extends Base
|
||||||
|
|
||||||
isCompact = yes
|
isCompact = yes
|
||||||
for prop in @properties
|
for prop in @properties
|
||||||
if prop instanceof Comment or (prop instanceof Assign and prop.context is 'object')
|
if prop instanceof Comment or (prop instanceof Assign and prop.context is 'object' and not @csx)
|
||||||
isCompact = no
|
isCompact = no
|
||||||
|
|
||||||
answer = []
|
answer = []
|
||||||
answer.push @makeCode "{#{if isCompact then '' else '\n'}"
|
answer.push @makeCode if isCompact then '' else '\n'
|
||||||
for prop, i in props
|
for prop, i in props
|
||||||
join = if i is props.length - 1
|
join = if i is props.length - 1
|
||||||
''
|
''
|
||||||
|
else if isCompact and @csx
|
||||||
|
' '
|
||||||
else if isCompact
|
else if isCompact
|
||||||
', '
|
', '
|
||||||
else if prop is lastNoncom or prop instanceof Comment
|
else if prop is lastNoncom or prop instanceof Comment or @csx
|
||||||
'\n'
|
'\n'
|
||||||
else
|
else
|
||||||
',\n'
|
',\n'
|
||||||
|
@ -1172,9 +1207,12 @@ exports.Obj = class Obj extends Base
|
||||||
prop = new Assign prop, prop, 'object'
|
prop = new Assign prop, prop, 'object'
|
||||||
|
|
||||||
if indent then answer.push @makeCode indent
|
if indent then answer.push @makeCode indent
|
||||||
|
prop.csx = yes if @csx
|
||||||
|
answer.push @makeCode ' ' if @csx and i is 0
|
||||||
answer.push prop.compileToFragments(o, LEVEL_TOP)...
|
answer.push prop.compileToFragments(o, LEVEL_TOP)...
|
||||||
if join then answer.push @makeCode join
|
if join then answer.push @makeCode join
|
||||||
answer.push @makeCode "#{if isCompact then '' else "\n#{@tab}"}}"
|
answer.push @makeCode if isCompact then '' else "\n#{@tab}"
|
||||||
|
answer = @wrapInBraces answer if not @csx
|
||||||
if @front then @wrapInParentheses answer else answer
|
if @front then @wrapInParentheses answer else answer
|
||||||
|
|
||||||
assigns: (name) ->
|
assigns: (name) ->
|
||||||
|
@ -1758,6 +1796,7 @@ exports.Assign = class Assign extends Base
|
||||||
[properties..., prototype, name] = @variable.properties
|
[properties..., prototype, name] = @variable.properties
|
||||||
@value.name = name if prototype.name?.value is 'prototype'
|
@value.name = name if prototype.name?.value is 'prototype'
|
||||||
|
|
||||||
|
@value.base.csxAttribute = yes if @csx
|
||||||
val = @value.compileToFragments o, LEVEL_LIST
|
val = @value.compileToFragments o, LEVEL_LIST
|
||||||
compiledName = @variable.compileToFragments o, LEVEL_LIST
|
compiledName = @variable.compileToFragments o, LEVEL_LIST
|
||||||
|
|
||||||
|
@ -1765,7 +1804,7 @@ exports.Assign = class Assign extends Base
|
||||||
if @variable.shouldCache()
|
if @variable.shouldCache()
|
||||||
compiledName.unshift @makeCode '['
|
compiledName.unshift @makeCode '['
|
||||||
compiledName.push @makeCode ']'
|
compiledName.push @makeCode ']'
|
||||||
return compiledName.concat @makeCode(": "), val
|
return compiledName.concat @makeCode(if @csx then '=' else ': '), val
|
||||||
|
|
||||||
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
|
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
|
||||||
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
|
# Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
|
||||||
|
@ -2773,13 +2812,14 @@ exports.Parens = class Parens extends Base
|
||||||
|
|
||||||
compileNode: (o) ->
|
compileNode: (o) ->
|
||||||
expr = @body.unwrap()
|
expr = @body.unwrap()
|
||||||
if expr instanceof Value and expr.isAtomic()
|
if expr instanceof Value and expr.isAtomic() and not @csxAttribute
|
||||||
expr.front = @front
|
expr.front = @front
|
||||||
return expr.compileToFragments o
|
return expr.compileToFragments o
|
||||||
fragments = expr.compileToFragments o, LEVEL_PAREN
|
fragments = expr.compileToFragments o, LEVEL_PAREN
|
||||||
bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
|
bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
|
||||||
(expr instanceof For and expr.returns)) and (o.level < LEVEL_COND or
|
(expr instanceof For and expr.returns)) and (o.level < LEVEL_COND or
|
||||||
fragments.length <= 3)
|
fragments.length <= 3)
|
||||||
|
return @wrapInBraces fragments if @csxAttribute
|
||||||
if bare then fragments else @wrapInParentheses fragments
|
if bare then fragments else @wrapInParentheses fragments
|
||||||
|
|
||||||
#### StringWithInterpolations
|
#### StringWithInterpolations
|
||||||
|
@ -2798,6 +2838,11 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base
|
||||||
shouldCache: -> @body.shouldCache()
|
shouldCache: -> @body.shouldCache()
|
||||||
|
|
||||||
compileNode: (o) ->
|
compileNode: (o) ->
|
||||||
|
if @csxAttribute
|
||||||
|
wrapped = new Parens new StringWithInterpolations @body
|
||||||
|
wrapped.csxAttribute = yes
|
||||||
|
return wrapped.compileNode o
|
||||||
|
|
||||||
# Assumes that `expr` is `Value` » `StringLiteral` or `Op`
|
# Assumes that `expr` is `Value` » `StringLiteral` or `Op`
|
||||||
expr = @body.unwrap()
|
expr = @body.unwrap()
|
||||||
|
|
||||||
|
@ -2812,25 +2857,31 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base
|
||||||
return yes
|
return yes
|
||||||
|
|
||||||
fragments = []
|
fragments = []
|
||||||
fragments.push @makeCode '`'
|
fragments.push @makeCode '`' unless @csx
|
||||||
for element in elements
|
for element in elements
|
||||||
if element instanceof StringLiteral
|
if element instanceof StringLiteral
|
||||||
value = element.value[1...-1]
|
value = element.unquote @csx
|
||||||
# Backticks and `${` inside template literals must be escaped.
|
unless @csx
|
||||||
value = value.replace /(\\*)(`|\$\{)/g, (match, backslashes, toBeEscaped) ->
|
# Backticks and `${` inside template literals must be escaped.
|
||||||
if backslashes.length % 2 is 0
|
value = value.replace /(\\*)(`|\$\{)/g, (match, backslashes, toBeEscaped) ->
|
||||||
"#{backslashes}\\#{toBeEscaped}"
|
if backslashes.length % 2 is 0
|
||||||
else
|
"#{backslashes}\\#{toBeEscaped}"
|
||||||
match
|
else
|
||||||
|
match
|
||||||
fragments.push @makeCode value
|
fragments.push @makeCode value
|
||||||
else
|
else
|
||||||
fragments.push @makeCode '${'
|
fragments.push @makeCode '$' unless @csx
|
||||||
fragments.push element.compileToFragments(o, LEVEL_PAREN)...
|
code = element.compileToFragments(o, LEVEL_PAREN)
|
||||||
fragments.push @makeCode '}'
|
code = @wrapInBraces code unless @isNestedTag element
|
||||||
fragments.push @makeCode '`'
|
fragments.push code...
|
||||||
|
fragments.push @makeCode '`' unless @csx
|
||||||
fragments
|
fragments
|
||||||
|
|
||||||
|
isNestedTag: (element) ->
|
||||||
|
exprs = element?.body?.expressions
|
||||||
|
call = exprs?[0]
|
||||||
|
@csx and exprs and exprs.length is 1 and call instanceof Call and call.csx
|
||||||
|
|
||||||
#### For
|
#### For
|
||||||
|
|
||||||
# CoffeeScript's replacement for the *for* loop is our array and object
|
# CoffeeScript's replacement for the *for* loop is our array and object
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
# shorthand into the unambiguous long form, add implicit indentation and
|
# shorthand into the unambiguous long form, add implicit indentation and
|
||||||
# parentheses, and generally clean things up.
|
# parentheses, and generally clean things up.
|
||||||
|
|
||||||
|
{throwSyntaxError} = require './helpers'
|
||||||
|
|
||||||
# Create a generated token: one that exists due to a use of implicit syntax.
|
# Create a generated token: one that exists due to a use of implicit syntax.
|
||||||
generate = (tag, value, origin) ->
|
generate = (tag, value, origin) ->
|
||||||
tok = [tag, value]
|
tok = [tag, value]
|
||||||
|
@ -31,6 +33,7 @@ exports.Rewriter = class Rewriter
|
||||||
@tagPostfixConditionals()
|
@tagPostfixConditionals()
|
||||||
@addImplicitBracesAndParens()
|
@addImplicitBracesAndParens()
|
||||||
@addLocationDataToGeneratedTokens()
|
@addLocationDataToGeneratedTokens()
|
||||||
|
@enforceValidCSXAttributes()
|
||||||
@fixOutdentLocationData()
|
@fixOutdentLocationData()
|
||||||
@tokens
|
@tokens
|
||||||
|
|
||||||
|
@ -354,6 +357,15 @@ exports.Rewriter = class Rewriter
|
||||||
endImplicitObject i + offset
|
endImplicitObject i + offset
|
||||||
return forward(1)
|
return forward(1)
|
||||||
|
|
||||||
|
# Make sure only strings and wrapped expressions are used in CSX attributes
|
||||||
|
enforceValidCSXAttributes: ->
|
||||||
|
@scanTokens (token, i, tokens) ->
|
||||||
|
if token.csxColon
|
||||||
|
next = tokens[i + 1]
|
||||||
|
if next[0] not in ['STRING_START', 'STRING', '(']
|
||||||
|
throwSyntaxError 'expected wrapped or quoted CSX attribute', next[2]
|
||||||
|
return 1
|
||||||
|
|
||||||
# Add location data to all tokens generated by the rewriter.
|
# Add location data to all tokens generated by the rewriter.
|
||||||
addLocationDataToGeneratedTokens: ->
|
addLocationDataToGeneratedTokens: ->
|
||||||
@scanTokens (token, i, tokens) ->
|
@scanTokens (token, i, tokens) ->
|
||||||
|
@ -504,7 +516,7 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'IN
|
||||||
|
|
||||||
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
||||||
IMPLICIT_CALL = [
|
IMPLICIT_CALL = [
|
||||||
'IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN'
|
'IDENTIFIER', 'CSX_TAG', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN'
|
||||||
'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
|
'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
|
||||||
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
|
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
|
||||||
'UNDEFINED', 'NULL', 'BOOL'
|
'UNDEFINED', 'NULL', 'BOOL'
|
||||||
|
|
|
@ -213,57 +213,50 @@ test "#2916: block comment before implicit call with implicit object", ->
|
||||||
a: yes
|
a: yes
|
||||||
|
|
||||||
test "#3132: Format single-line block comment nicely", ->
|
test "#3132: Format single-line block comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
### Single-line block comment without additional space here => ###"""
|
### Single-line block comment without additional space here => ###""",
|
||||||
|
"""
|
||||||
output = """
|
|
||||||
/* Single-line block comment without additional space here => */
|
/* Single-line block comment without additional space here => */
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
test "#3132: Format multi-line block comment nicely", ->
|
test "#3132: Format multi-line block comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
###
|
###
|
||||||
# Multi-line
|
# Multi-line
|
||||||
# block
|
# block
|
||||||
# comment
|
# comment
|
||||||
###"""
|
###""",
|
||||||
|
"""
|
||||||
output = """
|
|
||||||
/*
|
/*
|
||||||
* Multi-line
|
* Multi-line
|
||||||
* block
|
* block
|
||||||
* comment
|
* comment
|
||||||
*/
|
*/
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
test "#3132: Format simple block comment nicely", ->
|
test "#3132: Format simple block comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
###
|
###
|
||||||
No
|
No
|
||||||
Preceding hash
|
Preceding hash
|
||||||
###"""
|
###""",
|
||||||
|
"""
|
||||||
output = """
|
|
||||||
/*
|
/*
|
||||||
No
|
No
|
||||||
Preceding hash
|
Preceding hash
|
||||||
*/
|
*/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
test "#3132: Format indented block-comment nicely", ->
|
test "#3132: Format indented block-comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
fn = () ->
|
fn = () ->
|
||||||
###
|
###
|
||||||
# Indented
|
# Indented
|
||||||
Multiline
|
Multiline
|
||||||
###
|
###
|
||||||
1"""
|
1""",
|
||||||
|
"""
|
||||||
output = """
|
|
||||||
var fn;
|
var fn;
|
||||||
|
|
||||||
fn = function() {
|
fn = function() {
|
||||||
|
@ -275,21 +268,19 @@ test "#3132: Format indented block-comment nicely", ->
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
# Although adequately working, block comment-placement is not yet perfect.
|
# Although adequately working, block comment-placement is not yet perfect.
|
||||||
# (Considering a case where multiple variables have been declared …)
|
# (Considering a case where multiple variables have been declared …)
|
||||||
test "#3132: Format jsdoc-style block-comment nicely", ->
|
test "#3132: Format jsdoc-style block-comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
###*
|
###*
|
||||||
# Multiline for jsdoc-"@doctags"
|
# Multiline for jsdoc-"@doctags"
|
||||||
#
|
#
|
||||||
# @type {Function}
|
# @type {Function}
|
||||||
###
|
###
|
||||||
fn = () -> 1
|
fn = () -> 1
|
||||||
|
""",
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output = """
|
|
||||||
/**
|
/**
|
||||||
* Multiline for jsdoc-"@doctags"
|
* Multiline for jsdoc-"@doctags"
|
||||||
*
|
*
|
||||||
|
@ -300,21 +291,19 @@ test "#3132: Format jsdoc-style block-comment nicely", ->
|
||||||
fn = function() {
|
fn = function() {
|
||||||
return 1;
|
return 1;
|
||||||
};"""
|
};"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
# Although adequately working, block comment-placement is not yet perfect.
|
# Although adequately working, block comment-placement is not yet perfect.
|
||||||
# (Considering a case where multiple variables have been declared …)
|
# (Considering a case where multiple variables have been declared …)
|
||||||
test "#3132: Format hand-made (raw) jsdoc-style block-comment nicely", ->
|
test "#3132: Format hand-made (raw) jsdoc-style block-comment nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
###*
|
###*
|
||||||
* Multiline for jsdoc-"@doctags"
|
* Multiline for jsdoc-"@doctags"
|
||||||
*
|
*
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
###
|
###
|
||||||
fn = () -> 1
|
fn = () -> 1
|
||||||
|
""",
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output = """
|
|
||||||
/**
|
/**
|
||||||
* Multiline for jsdoc-"@doctags"
|
* Multiline for jsdoc-"@doctags"
|
||||||
*
|
*
|
||||||
|
@ -325,12 +314,11 @@ test "#3132: Format hand-made (raw) jsdoc-style block-comment nicely", ->
|
||||||
fn = function() {
|
fn = function() {
|
||||||
return 1;
|
return 1;
|
||||||
};"""
|
};"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
# Although adequately working, block comment-placement is not yet perfect.
|
# Although adequately working, block comment-placement is not yet perfect.
|
||||||
# (Considering a case where multiple variables have been declared …)
|
# (Considering a case where multiple variables have been declared …)
|
||||||
test "#3132: Place block-comments nicely", ->
|
test "#3132: Place block-comments nicely", ->
|
||||||
input = """
|
eqJS """
|
||||||
###*
|
###*
|
||||||
# A dummy class definition
|
# A dummy class definition
|
||||||
#
|
#
|
||||||
|
@ -350,9 +338,8 @@ test "#3132: Place block-comments nicely", ->
|
||||||
###
|
###
|
||||||
@instance = new DummyClass()
|
@instance = new DummyClass()
|
||||||
|
|
||||||
|
""",
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output = """
|
|
||||||
/**
|
/**
|
||||||
* A dummy class definition
|
* A dummy class definition
|
||||||
*
|
*
|
||||||
|
@ -383,22 +370,19 @@ test "#3132: Place block-comments nicely", ->
|
||||||
return DummyClass;
|
return DummyClass;
|
||||||
|
|
||||||
})();"""
|
})();"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
test "#3638: Demand a whitespace after # symbol", ->
|
test "#3638: Demand a whitespace after # symbol", ->
|
||||||
input = """
|
eqJS """
|
||||||
###
|
###
|
||||||
#No
|
#No
|
||||||
#whitespace
|
#whitespace
|
||||||
###"""
|
###""",
|
||||||
|
"""
|
||||||
output = """
|
|
||||||
/*
|
/*
|
||||||
#No
|
#No
|
||||||
#whitespace
|
#whitespace
|
||||||
*/"""
|
*/"""
|
||||||
|
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
test "#3761: Multiline comment at end of an object", ->
|
test "#3761: Multiline comment at end of an object", ->
|
||||||
anObject =
|
anObject =
|
||||||
|
|
726
test/csx.coffee
Normal file
726
test/csx.coffee
Normal file
|
@ -0,0 +1,726 @@
|
||||||
|
# We usually do not check the actual JS output from the compiler, but since
|
||||||
|
# JSX is not natively supported by Node, we do it in this case.
|
||||||
|
|
||||||
|
test 'self closing', ->
|
||||||
|
eqJS '''
|
||||||
|
<div />
|
||||||
|
''', '''
|
||||||
|
<div />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'self closing formatting', ->
|
||||||
|
eqJS '''
|
||||||
|
<div/>
|
||||||
|
''', '''
|
||||||
|
<div />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'self closing multiline', ->
|
||||||
|
eqJS '''
|
||||||
|
<div
|
||||||
|
/>
|
||||||
|
''', '''
|
||||||
|
<div />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'regex attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x={/>asds/} />
|
||||||
|
''', '''
|
||||||
|
<div x={/>asds/} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'string attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x="a" />
|
||||||
|
''', '''
|
||||||
|
<div x="a" />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'simple attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x={42} />
|
||||||
|
''', '''
|
||||||
|
<div x={42} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'assignment attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x={y = 42} />
|
||||||
|
''', '''
|
||||||
|
var y;
|
||||||
|
|
||||||
|
<div x={y = 42} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'object attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x={{y: 42}} />
|
||||||
|
''', '''
|
||||||
|
<div x={{
|
||||||
|
y: 42
|
||||||
|
}} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'attribute without value', ->
|
||||||
|
eqJS '''
|
||||||
|
<div checked x="hello" />
|
||||||
|
''', '''
|
||||||
|
<div checked x="hello" />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'paired', ->
|
||||||
|
eqJS '''
|
||||||
|
<div></div>
|
||||||
|
''', '''
|
||||||
|
<div></div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'simple content', ->
|
||||||
|
eqJS '''
|
||||||
|
<div>Hello world</div>
|
||||||
|
''', '''
|
||||||
|
<div>Hello world</div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'content interpolation', ->
|
||||||
|
eqJS '''
|
||||||
|
<div>Hello {42}</div>
|
||||||
|
''', '''
|
||||||
|
<div>Hello {42}</div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'nested tag', ->
|
||||||
|
eqJS '''
|
||||||
|
<div><span /></div>
|
||||||
|
''', '''
|
||||||
|
<div><span /></div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag inside interpolation formatting', ->
|
||||||
|
eqJS '''
|
||||||
|
<div>Hello {<span />}</div>
|
||||||
|
''', '''
|
||||||
|
<div>Hello <span /></div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag inside interpolation, tags are callable', ->
|
||||||
|
eqJS '''
|
||||||
|
<div>Hello {<span /> x}</div>
|
||||||
|
''', '''
|
||||||
|
<div>Hello {<span />(x)}</div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tags inside interpolation, tags trigger implicit calls', ->
|
||||||
|
eqJS '''
|
||||||
|
<div>Hello {f <span />}</div>
|
||||||
|
''', '''
|
||||||
|
<div>Hello {f(<span />)}</div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'regex in interpolation', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x={/>asds/}><div />{/>asdsad</}</div>
|
||||||
|
''', '''
|
||||||
|
<div x={/>asds/}><div />{/>asdsad</}</div>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'interpolation in string attribute value', ->
|
||||||
|
eqJS '''
|
||||||
|
<div x="Hello #{world}" />
|
||||||
|
''', '''
|
||||||
|
<div x={`Hello ${world}`} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Unlike in `coffee-react-transform`.
|
||||||
|
test 'bare numbers not allowed', ->
|
||||||
|
throws -> CoffeeScript.compile '<div x=3 />'
|
||||||
|
|
||||||
|
test 'bare expressions not allowed', ->
|
||||||
|
throws -> CoffeeScript.compile '<div x=y />'
|
||||||
|
|
||||||
|
test 'bare complex expressions not allowed', ->
|
||||||
|
throws -> CoffeeScript.compile '<div x=f(3) />'
|
||||||
|
|
||||||
|
test 'unescaped opening tag angle bracket disallowed', ->
|
||||||
|
throws -> CoffeeScript.compile '<Person><<</Person>'
|
||||||
|
|
||||||
|
test 'space around equal sign', ->
|
||||||
|
eqJS '''
|
||||||
|
<div popular = "yes" />
|
||||||
|
''', '''
|
||||||
|
<div popular="yes" />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# The following tests were adopted from James Friend’s
|
||||||
|
# [https://github.com/jsdf/coffee-react-transform](https://github.com/jsdf/coffee-react-transform).
|
||||||
|
|
||||||
|
test 'ambiguous tag-like expression', ->
|
||||||
|
throws -> CoffeeScript.compile 'x = a <b > c'
|
||||||
|
|
||||||
|
test 'ambiguous tag', ->
|
||||||
|
eqJS '''
|
||||||
|
a <b > c </b>
|
||||||
|
''', '''
|
||||||
|
a(<b> c </b>);
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'escaped CoffeeScript attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person name={if test() then 'yes' else 'no'} />
|
||||||
|
''', '''
|
||||||
|
<Person name={test() ? 'yes' : 'no'} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'escaped CoffeeScript attribute over multiple lines', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person name={
|
||||||
|
if test()
|
||||||
|
'yes'
|
||||||
|
else
|
||||||
|
'no'
|
||||||
|
} />
|
||||||
|
''', '''
|
||||||
|
<Person name={test() ? 'yes' : 'no'} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'multiple line escaped CoffeeScript with nested CSX', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person name={
|
||||||
|
if test()
|
||||||
|
'yes'
|
||||||
|
else
|
||||||
|
'no'
|
||||||
|
}>
|
||||||
|
{
|
||||||
|
|
||||||
|
for n in a
|
||||||
|
<div> a
|
||||||
|
asf
|
||||||
|
<li xy={"as"}>{ n+1 }<a /> <a /> </li>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Person>
|
||||||
|
''', '''
|
||||||
|
var n;
|
||||||
|
|
||||||
|
<Person name={test() ? 'yes' : 'no'}>
|
||||||
|
{(function() {
|
||||||
|
var i, len, results;
|
||||||
|
results = [];
|
||||||
|
for (i = 0, len = a.length; i < len; i++) {
|
||||||
|
n = a[i];
|
||||||
|
results.push(<div> a
|
||||||
|
asf
|
||||||
|
<li xy={"as"}>{n + 1}<a /> <a /> </li>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
})()}
|
||||||
|
|
||||||
|
</Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'nested CSX within an attribute, with object attr value', ->
|
||||||
|
eqJS '''
|
||||||
|
<Company>
|
||||||
|
<Person name={<NameComponent attr3={ {'a': {}, b: '{'} } />} />
|
||||||
|
</Company>
|
||||||
|
''', '''
|
||||||
|
<Company>
|
||||||
|
<Person name={<NameComponent attr3={{
|
||||||
|
'a': {},
|
||||||
|
b: '{'
|
||||||
|
}} />} />
|
||||||
|
</Company>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'complex nesting', ->
|
||||||
|
eqJS '''
|
||||||
|
<div code={someFunc({a:{b:{}, C:'}{}{'}})} />
|
||||||
|
''', '''
|
||||||
|
<div code={someFunc({
|
||||||
|
a: {
|
||||||
|
b: {},
|
||||||
|
C: '}{}{'
|
||||||
|
}
|
||||||
|
})} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'multiline tag with nested CSX within an attribute', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person
|
||||||
|
name={
|
||||||
|
name = formatName(user.name)
|
||||||
|
<NameComponent name={name.toUppercase()} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
blah blah blah
|
||||||
|
</Person>
|
||||||
|
''', '''
|
||||||
|
var name;
|
||||||
|
|
||||||
|
<Person name={name = formatName(user.name), <NameComponent name={name.toUppercase()} />}>
|
||||||
|
blah blah blah
|
||||||
|
</Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'escaped CoffeeScript with nested object literals', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person>
|
||||||
|
blah blah blah {
|
||||||
|
{'a' : {}, 'asd': 'asd'}
|
||||||
|
}
|
||||||
|
</Person>
|
||||||
|
''', '''
|
||||||
|
<Person>
|
||||||
|
blah blah blah {{
|
||||||
|
'a': {},
|
||||||
|
'asd': 'asd'
|
||||||
|
}}
|
||||||
|
</Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'multiline tag attributes with escaped CoffeeScript', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person name={if isActive() then 'active' else 'inactive'}
|
||||||
|
someattr='on new line' />
|
||||||
|
''', '''
|
||||||
|
<Person name={isActive() ? 'active' : 'inactive'} someattr='on new line' />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'lots of attributes', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person eyes={2} friends={getFriends()} popular = "yes"
|
||||||
|
active={ if isActive() then 'active' else 'inactive' } data-attr='works' checked check={me_out}
|
||||||
|
/>
|
||||||
|
''', '''
|
||||||
|
<Person eyes={2} friends={getFriends()} popular="yes" active={isActive() ? 'active' : 'inactive'} data-attr='works' checked check={me_out} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# TODO: fix partially indented CSX
|
||||||
|
# test 'multiline elements', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <div something={
|
||||||
|
# do ->
|
||||||
|
# test = /432/gm # this is a regex
|
||||||
|
# 6 /432/gm # this is division
|
||||||
|
# }
|
||||||
|
# >
|
||||||
|
# <div>
|
||||||
|
# <div>
|
||||||
|
# <div>
|
||||||
|
# <article name={ new Date() } number={203}
|
||||||
|
# range={getRange()}
|
||||||
|
# >
|
||||||
|
# </article>
|
||||||
|
# </div>
|
||||||
|
# </div>
|
||||||
|
# </div>
|
||||||
|
# </div>
|
||||||
|
# ''', '''
|
||||||
|
# bla
|
||||||
|
# '''
|
||||||
|
|
||||||
|
test 'complex regex', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person />
|
||||||
|
/\\/\\/<Person \\/>\\>\\//
|
||||||
|
''', '''
|
||||||
|
<Person />;
|
||||||
|
|
||||||
|
/\\/\\/<Person \\/>\\>\\//;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'heregex', ->
|
||||||
|
eqJS '''
|
||||||
|
test = /432/gm # this is a regex
|
||||||
|
6 /432/gm # this is division
|
||||||
|
<Tag>
|
||||||
|
{test = /<Tag>/} this is a regex containing something which looks like a tag
|
||||||
|
</Tag>
|
||||||
|
<Person />
|
||||||
|
REGEX = /// ^
|
||||||
|
(/ (?! [\s=] ) # comment comment <comment>comment</comment>
|
||||||
|
[^ [ / \n \\ ]* # comment comment
|
||||||
|
(?:
|
||||||
|
<Tag />
|
||||||
|
(?: \\[\s\S] # comment comment
|
||||||
|
| \[ # comment comment
|
||||||
|
[^ \] \n \\ ]*
|
||||||
|
(?: \\[\s\S] [^ \] \n \\ ]* )*
|
||||||
|
<Tag>tag</Tag>
|
||||||
|
]
|
||||||
|
) [^ [ / \n \\ ]*
|
||||||
|
)*
|
||||||
|
/) ([imgy]{0,4}) (?!\w)
|
||||||
|
///
|
||||||
|
<Person />
|
||||||
|
''', '''
|
||||||
|
var REGEX, test;
|
||||||
|
|
||||||
|
test = /432/gm;
|
||||||
|
|
||||||
|
6 / 432 / gm;
|
||||||
|
|
||||||
|
<Tag>
|
||||||
|
{(test = /<Tag>/)} this is a regex containing something which looks like a tag
|
||||||
|
</Tag>;
|
||||||
|
|
||||||
|
<Person />;
|
||||||
|
|
||||||
|
REGEX = /^(\\/(?![s=])[^[\\/ ]*(?:<Tag\\/>(?:\\[sS]|[[^] ]*(?:\\[sS][^] ]*)*<Tag>tag<\\/Tag>])[^[\\/ ]*)*\\/)([imgy]{0,4})(?!w)/;
|
||||||
|
|
||||||
|
<Person />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'comment within CSX is not treated as comment', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person>
|
||||||
|
# i am not a comment
|
||||||
|
</Person>
|
||||||
|
''', '''
|
||||||
|
<Person>
|
||||||
|
# i am not a comment
|
||||||
|
</Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'comment at start of CSX escape', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person>
|
||||||
|
{# i am a comment
|
||||||
|
"i am a string"
|
||||||
|
}
|
||||||
|
</Person>
|
||||||
|
''', '''
|
||||||
|
<Person>
|
||||||
|
{"i am a string"}
|
||||||
|
</Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'CSX comment cannot be used inside interpolation', ->
|
||||||
|
throws -> CoffeeScript.compile '''
|
||||||
|
<Person>
|
||||||
|
{# i am a comment}
|
||||||
|
</Person>
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'comment syntax cannot be used inline', ->
|
||||||
|
throws -> CoffeeScript.compile '''
|
||||||
|
<Person>{#comment inline}</Person>
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'string within CSX is ignored', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person> "i am not a string" 'nor am i' </Person>
|
||||||
|
''', '''
|
||||||
|
<Person> "i am not a string" 'nor am i' </Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'special chars within CSX are ignored', ->
|
||||||
|
eqJS """
|
||||||
|
<Person> a,/';][' a\''@$%^&˚¬∑˜˚∆å∂¬˚*()*&^%$>> '"''"'''\'\'m' i </Person>
|
||||||
|
""", """
|
||||||
|
<Person> a,/';][' a''@$%^&˚¬∑˜˚∆å∂¬˚*()*&^%$>> '"''"'''''m' i </Person>;
|
||||||
|
"""
|
||||||
|
|
||||||
|
test 'html entities (name, decimal, hex) within CSX', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person> &&&€ € €;; </Person>
|
||||||
|
''', '''
|
||||||
|
<Person> &&&€ € €;; </Person>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag with {{}}', ->
|
||||||
|
eqJS '''
|
||||||
|
<Person name={{value: item, key, item}} />
|
||||||
|
''', '''
|
||||||
|
<Person name={{
|
||||||
|
value: item,
|
||||||
|
key,
|
||||||
|
item
|
||||||
|
}} />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag with namespace', ->
|
||||||
|
eqJS '''
|
||||||
|
<Something.Tag></Something.Tag>
|
||||||
|
''', '''
|
||||||
|
<Something.Tag></Something.Tag>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag with lowercase namespace', ->
|
||||||
|
eqJS '''
|
||||||
|
<something.tag></something.tag>
|
||||||
|
''', '''
|
||||||
|
<something.tag></something.tag>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'self closing tag with namespace', ->
|
||||||
|
eqJS '''
|
||||||
|
<Something.Tag />
|
||||||
|
''', '''
|
||||||
|
<Something.Tag />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'self closing tag with spread attribute', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component a={b} {... x } b="c" />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({"a": (b)}, x , {"b": "c"}))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'complex spread attribute', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component {...x} a={b} {... x } b="c" {...$my_xtraCoolVar123 } />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({}, x, {"a": (b)}, x , {"b": "c"}, $my_xtraCoolVar123 ))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'multiline spread attribute', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component {...
|
||||||
|
# x } a={b} {... x } b="c" {...z }>
|
||||||
|
# </Component>
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({},
|
||||||
|
# x , {"a": (b)}, x , {"b": "c"}, z )
|
||||||
|
# )
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'multiline tag with spread attribute', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# z="1"
|
||||||
|
# {...x}
|
||||||
|
# a={b}
|
||||||
|
# b="c"
|
||||||
|
# >
|
||||||
|
# </Component>
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({ \
|
||||||
|
# "z": "1"
|
||||||
|
# }, x, { \
|
||||||
|
# "a": (b), \
|
||||||
|
# "b": "c"
|
||||||
|
# })
|
||||||
|
# )
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'multiline tag with spread attribute first', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# {...
|
||||||
|
# x}
|
||||||
|
# z="1"
|
||||||
|
# a={b}
|
||||||
|
# b="c"
|
||||||
|
# >
|
||||||
|
# </Component>
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({}, \
|
||||||
|
|
||||||
|
# x, { \
|
||||||
|
# "z": "1", \
|
||||||
|
# "a": (b), \
|
||||||
|
# "b": "c"
|
||||||
|
# })
|
||||||
|
# )
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'complex multiline spread attribute', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# {...
|
||||||
|
# y} a={b} {... x } b="c" {...z }>
|
||||||
|
# <div code={someFunc({a:{b:{}, C:'}'}})} />
|
||||||
|
# </Component>
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({}, \
|
||||||
|
|
||||||
|
# y, {"a": (b)}, x , {"b": "c"}, z ),
|
||||||
|
# React.createElement("div", {"code": (someFunc({a:{b:{}, C:'}'}}))})
|
||||||
|
# )
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'self closing spread attribute on single line', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component a="b" c="d" {...@props} />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({"a": "b", "c": "d"}, @props ))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'self closing spread attribute on new line', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# a="b"
|
||||||
|
# c="d"
|
||||||
|
# {...@props}
|
||||||
|
# />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({ \
|
||||||
|
# "a": "b", \
|
||||||
|
# "c": "d"
|
||||||
|
# }, @props
|
||||||
|
# ))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'self closing spread attribute on same line', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# a="b"
|
||||||
|
# c="d"
|
||||||
|
# {...@props} />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({ \
|
||||||
|
# "a": "b", \
|
||||||
|
# "c": "d"
|
||||||
|
# }, @props ))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
# TODO: Uncomment the following test once destructured object spreads are supported.
|
||||||
|
# test 'self closing spread attribute on next line', ->
|
||||||
|
# eqJS '''
|
||||||
|
# <Component
|
||||||
|
# a="b"
|
||||||
|
# c="d"
|
||||||
|
# {...@props}
|
||||||
|
|
||||||
|
# />
|
||||||
|
# ''', '''
|
||||||
|
# React.createElement(Component, Object.assign({ \
|
||||||
|
# "a": "b", \
|
||||||
|
# "c": "d"
|
||||||
|
# }, @props
|
||||||
|
|
||||||
|
# ))
|
||||||
|
# '''
|
||||||
|
|
||||||
|
test 'empty strings are not converted to true', ->
|
||||||
|
eqJS '''
|
||||||
|
<Component val="" />
|
||||||
|
''', '''
|
||||||
|
<Component val="" />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'CoffeeScript @ syntax in tag name', ->
|
||||||
|
throws -> CoffeeScript.compile '''
|
||||||
|
<@Component>
|
||||||
|
<Component />
|
||||||
|
</@Component>
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'hyphens in tag names', ->
|
||||||
|
eqJS '''
|
||||||
|
<paper-button className="button">{text}</paper-button>
|
||||||
|
''', '''
|
||||||
|
<paper-button className="button">{text}</paper-button>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'closing tags must be closed', ->
|
||||||
|
throws -> CoffeeScript.compile '''
|
||||||
|
<a></a
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Tests for allowing less than operator without spaces when ther is no CSX
|
||||||
|
|
||||||
|
test 'unspaced less than without CSX: identifier', ->
|
||||||
|
a = 3
|
||||||
|
div = 5
|
||||||
|
ok a<div
|
||||||
|
|
||||||
|
test 'unspaced less than without CSX: number', ->
|
||||||
|
div = 5
|
||||||
|
ok 3<div
|
||||||
|
|
||||||
|
test 'unspaced less than without CSX: paren', ->
|
||||||
|
div = 5
|
||||||
|
ok (3)<div
|
||||||
|
|
||||||
|
test 'unspaced less than without CSX: index', ->
|
||||||
|
div = 5
|
||||||
|
a = [3]
|
||||||
|
ok a[0]<div
|
||||||
|
|
||||||
|
test 'tag inside CSX works following: identifier', ->
|
||||||
|
eqJS '''
|
||||||
|
<span>a<div /></span>
|
||||||
|
''', '''
|
||||||
|
<span>a<div /></span>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag inside CSX works following: number', ->
|
||||||
|
eqJS '''
|
||||||
|
<span>3<div /></span>
|
||||||
|
''', '''
|
||||||
|
<span>3<div /></span>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag inside CSX works following: paren', ->
|
||||||
|
eqJS '''
|
||||||
|
<span>(3)<div /></span>
|
||||||
|
''', '''
|
||||||
|
<span>(3)<div /></span>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'tag inside CSX works following: square bracket', ->
|
||||||
|
eqJS '''
|
||||||
|
<span>]<div /></span>
|
||||||
|
''', '''
|
||||||
|
<span>]<div /></span>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'unspaced less than inside CSX works but is not encouraged', ->
|
||||||
|
eqJS '''
|
||||||
|
a = 3
|
||||||
|
div = 5
|
||||||
|
html = <span>{a<div}</span>
|
||||||
|
''', '''
|
||||||
|
var a, div, html;
|
||||||
|
|
||||||
|
a = 3;
|
||||||
|
|
||||||
|
div = 5;
|
||||||
|
|
||||||
|
html = <span>{a < div}</span>;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'unspaced less than before CSX works but is not encouraged', ->
|
||||||
|
eqJS '''
|
||||||
|
div = 5
|
||||||
|
res = 2<div
|
||||||
|
html = <span />
|
||||||
|
''', '''
|
||||||
|
var div, html, res;
|
||||||
|
|
||||||
|
div = 5;
|
||||||
|
|
||||||
|
res = 2 < div;
|
||||||
|
|
||||||
|
html = <span />;
|
||||||
|
'''
|
||||||
|
|
||||||
|
test 'unspaced less than after CSX works but is not encouraged', ->
|
||||||
|
eqJS '''
|
||||||
|
div = 5
|
||||||
|
html = <span />
|
||||||
|
res = 2<div
|
||||||
|
''', '''
|
||||||
|
var div, html, res;
|
||||||
|
|
||||||
|
div = 5;
|
||||||
|
|
||||||
|
html = <span />;
|
||||||
|
|
||||||
|
res = 2 < div;
|
||||||
|
'''
|
|
@ -1545,3 +1545,43 @@ test "#4248: Unicode code point escapes", ->
|
||||||
'\\u{a}\\u{1111110000}'
|
'\\u{a}\\u{1111110000}'
|
||||||
\ ^\^^^^^^^^^^^^^
|
\ ^\^^^^^^^^^^^^^
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
test "CSX error: non-matching tag names", ->
|
||||||
|
assertErrorFormat '''
|
||||||
|
<div><span></div></span>
|
||||||
|
''',
|
||||||
|
'''
|
||||||
|
[stdin]:1:7: error: expected corresponding CSX closing tag for span
|
||||||
|
<div><span></div></span>
|
||||||
|
^^^^
|
||||||
|
'''
|
||||||
|
|
||||||
|
test "CSX error: bare expressions not allowed", ->
|
||||||
|
assertErrorFormat '''
|
||||||
|
<div x=3 />
|
||||||
|
''',
|
||||||
|
'''
|
||||||
|
[stdin]:1:8: error: expected wrapped or quoted CSX attribute
|
||||||
|
<div x=3 />
|
||||||
|
^
|
||||||
|
'''
|
||||||
|
|
||||||
|
test "CSX error: unescaped opening tag angle bracket disallowed", ->
|
||||||
|
assertErrorFormat '''
|
||||||
|
<Person><<</Person>
|
||||||
|
''',
|
||||||
|
'''
|
||||||
|
[stdin]:1:9: error: unexpected <<
|
||||||
|
<Person><<</Person>
|
||||||
|
^^
|
||||||
|
'''
|
||||||
|
|
||||||
|
test "CSX error: ambiguous tag-like expression", ->
|
||||||
|
assertErrorFormat '''
|
||||||
|
x = a <b > c
|
||||||
|
''',
|
||||||
|
'''
|
||||||
|
[stdin]:1:10: error: missing </
|
||||||
|
x = a <b > c
|
||||||
|
^
|
||||||
|
'''
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -300,18 +300,16 @@ test "#4248: Unicode code point escapes", ->
|
||||||
ok /a\u{12345}c/.test 'a\ud808\udf45c'
|
ok /a\u{12345}c/.test 'a\ud808\udf45c'
|
||||||
|
|
||||||
# rewrite code point escapes unless u flag is set
|
# rewrite code point escapes unless u flag is set
|
||||||
input = """
|
eqJS """
|
||||||
/\\u{bcdef}\\u{abc}/u
|
/\\u{bcdef}\\u{abc}/u
|
||||||
"""
|
""",
|
||||||
output = """
|
"""
|
||||||
/\\u{bcdef}\\u{abc}/u;
|
/\\u{bcdef}\\u{abc}/u;
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
input = """
|
eqJS """
|
||||||
///#{ 'a' }\\u{bcdef}///
|
///#{ 'a' }\\u{bcdef}///
|
||||||
"""
|
""",
|
||||||
output = """
|
"""
|
||||||
/a\\udab3\\uddef/;
|
/a\\udab3\\uddef/;
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
|
@ -415,18 +415,16 @@ test "#4248: Unicode code point escapes", ->
|
||||||
eq '\\u{123456}', "#{'\\'}#{'u{123456}'}"
|
eq '\\u{123456}', "#{'\\'}#{'u{123456}'}"
|
||||||
|
|
||||||
# don't rewrite code point escapes
|
# don't rewrite code point escapes
|
||||||
input = """
|
eqJS """
|
||||||
'\\u{bcdef}\\u{abc}'
|
'\\u{bcdef}\\u{abc}'
|
||||||
"""
|
""",
|
||||||
output = """
|
"""
|
||||||
'\\u{bcdef}\\u{abc}';
|
'\\u{bcdef}\\u{abc}';
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
||||||
input = """
|
eqJS """
|
||||||
"#{ 'a' }\\u{bcdef}"
|
"#{ 'a' }\\u{bcdef}"
|
||||||
"""
|
""",
|
||||||
output = """
|
"""
|
||||||
"a\\u{bcdef}";
|
"a\\u{bcdef}";
|
||||||
"""
|
"""
|
||||||
eq toJS(input), output
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# See http://wiki.ecmascript.org/doku.php?id=harmony:egal
|
# See [http://wiki.ecmascript.org/doku.php?id=harmony:egal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
||||||
egal = (a, b) ->
|
egal = (a, b) ->
|
||||||
if a is b
|
if a is b
|
||||||
a isnt 0 or 1/a is 1/b
|
a isnt 0 or 1/a is 1/b
|
||||||
|
@ -13,9 +13,20 @@ arrayEgal = (a, b) ->
|
||||||
return no for el, idx in a when not arrayEgal el, b[idx]
|
return no for el, idx in a when not arrayEgal el, b[idx]
|
||||||
yes
|
yes
|
||||||
|
|
||||||
exports.eq = (a, b, msg) -> ok egal(a, b), msg or "Expected #{a} to equal #{b}"
|
exports.eq = (a, b, msg) ->
|
||||||
exports.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg or "Expected #{a} to deep equal #{b}"
|
ok egal(a, b), msg or
|
||||||
|
"Expected #{reset}#{a}#{red} to equal #{reset}#{b}#{red}"
|
||||||
|
|
||||||
exports.toJS = (str) ->
|
exports.arrayEq = (a, b, msg) ->
|
||||||
CoffeeScript.compile str, bare: yes
|
ok arrayEgal(a,b), msg or
|
||||||
.replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace
|
"Expected #{reset}#{a}#{red} to deep equal #{reset}#{b}#{red}"
|
||||||
|
|
||||||
|
exports.eqJS = (input, expectedOutput, msg) ->
|
||||||
|
actualOutput = CoffeeScript.compile input, bare: yes
|
||||||
|
.replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace.
|
||||||
|
|
||||||
|
ok egal(expectedOutput, actualOutput), msg or
|
||||||
|
"""Expected generated JavaScript to be:
|
||||||
|
#{reset}#{expectedOutput}#{red}
|
||||||
|
but instead it was:
|
||||||
|
#{reset}#{actualOutput}#{red}"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue