1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00
jashkenas--coffeescript/lib/coffeescript/rewriter.js
Michal Srb dc0fb85fd3 [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
2017-06-06 23:33:46 -07:00

561 lines
20 KiB
JavaScript

// Generated by CoffeeScript 2.0.0-beta2
(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, throwSyntaxError,
indexOf = [].indexOf;
({throwSyntaxError} = require('./helpers'));
generate = function(tag, value, origin) {
var tok;
tok = [tag, value];
tok.generated = true;
if (origin) {
tok.origin = origin;
}
return tok;
};
exports.Rewriter = Rewriter = (function() {
class Rewriter {
rewrite(tokens1) {
this.tokens = tokens1;
this.removeLeadingNewlines();
this.closeOpenCalls();
this.closeOpenIndexes();
this.normalizeLines();
this.tagPostfixConditionals();
this.addImplicitBracesAndParens();
this.addLocationDataToGeneratedTokens();
this.enforceValidCSXAttributes();
this.fixOutdentLocationData();
return this.tokens;
}
scanTokens(block) {
var i, token, tokens;
({tokens} = this);
i = 0;
while (token = tokens[i]) {
i += block.call(this, token, i, tokens);
}
return true;
}
detectEnd(i, condition, action, opts = {}) {
var levels, ref, ref1, token, tokens;
({tokens} = this);
levels = 0;
while (token = tokens[i]) {
if (levels === 0 && condition.call(this, token, i)) {
return action.call(this, token, i);
}
if (ref = token[0], indexOf.call(EXPRESSION_START, ref) >= 0) {
levels += 1;
} else if (ref1 = token[0], indexOf.call(EXPRESSION_END, ref1) >= 0) {
levels -= 1;
}
if (levels < 0) {
if (opts.returnOnNegativeLevel) {
return;
}
return action.call(this, token, i);
}
i += 1;
}
return i - 1;
}
removeLeadingNewlines() {
var i, k, len, ref, tag;
ref = this.tokens;
for (i = k = 0, len = ref.length; k < len; i = ++k) {
[tag] = ref[i];
if (tag !== 'TERMINATOR') {
break;
}
}
if (i) {
return this.tokens.splice(0, i);
}
}
closeOpenCalls() {
var action, condition;
condition = function(token, i) {
var ref;
return (ref = token[0]) === ')' || ref === 'CALL_END';
};
action = function(token, i) {
return token[0] = 'CALL_END';
};
return this.scanTokens(function(token, i) {
if (token[0] === 'CALL_START') {
this.detectEnd(i + 1, condition, action);
}
return 1;
});
}
closeOpenIndexes() {
var action, condition;
condition = function(token, i) {
var ref;
return (ref = token[0]) === ']' || ref === 'INDEX_END';
};
action = function(token, i) {
return token[0] = 'INDEX_END';
};
return this.scanTokens(function(token, i) {
if (token[0] === 'INDEX_START') {
this.detectEnd(i + 1, condition, action);
}
return 1;
});
}
indexOfTag(i, ...pattern) {
var fuzz, j, k, ref, ref1;
fuzz = 0;
for (j = k = 0, ref = pattern.length; 0 <= ref ? k < ref : k > ref; j = 0 <= ref ? ++k : --k) {
while (this.tag(i + j + fuzz) === 'HERECOMMENT') {
fuzz += 2;
}
if (pattern[j] == null) {
continue;
}
if (typeof pattern[j] === 'string') {
pattern[j] = [pattern[j]];
}
if (ref1 = this.tag(i + j + fuzz), indexOf.call(pattern[j], ref1) < 0) {
return -1;
}
}
return i + j + fuzz - 1;
}
looksObjectish(j) {
var end, index;
if (this.indexOfTag(j, '@', null, ':') > -1 || this.indexOfTag(j, null, ':') > -1) {
return true;
}
index = this.indexOfTag(j, EXPRESSION_START);
if (index > -1) {
end = null;
this.detectEnd(index + 1, (function(token) {
var ref;
return ref = token[0], indexOf.call(EXPRESSION_END, ref) >= 0;
}), (function(token, i) {
return end = i;
}));
if (this.tag(end + 1) === ':') {
return true;
}
}
return false;
}
findTagsBackwards(i, tags) {
var backStack, ref, ref1, ref2, ref3, ref4, ref5;
backStack = [];
while (i >= 0 && (backStack.length || (ref2 = this.tag(i), indexOf.call(tags, ref2) < 0) && ((ref3 = this.tag(i), indexOf.call(EXPRESSION_START, ref3) < 0) || this.tokens[i].generated) && (ref4 = this.tag(i), indexOf.call(LINEBREAKS, ref4) < 0))) {
if (ref = this.tag(i), indexOf.call(EXPRESSION_END, ref) >= 0) {
backStack.push(this.tag(i));
}
if ((ref1 = this.tag(i), indexOf.call(EXPRESSION_START, ref1) >= 0) && backStack.length) {
backStack.pop();
}
i -= 1;
}
return ref5 = this.tag(i), indexOf.call(tags, ref5) >= 0;
}
addImplicitBracesAndParens() {
var stack, start;
stack = [];
start = null;
return this.scanTokens(function(token, i, tokens) {
var endImplicitCall, endImplicitObject, forward, implicitObjectContinues, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, isImplicit, isImplicitCall, isImplicitObject, k, newLine, nextTag, nextToken, offset, prevTag, prevToken, ref, s, sameLine, stackIdx, stackItem, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag;
[tag] = token;
[prevTag] = prevToken = i > 0 ? tokens[i - 1] : [];
[nextTag] = nextToken = i < tokens.length - 1 ? tokens[i + 1] : [];
stackTop = function() {
return stack[stack.length - 1];
};
startIdx = i;
forward = function(n) {
return i - startIdx + n;
};
isImplicit = function(stackItem) {
var ref;
return stackItem != null ? (ref = stackItem[2]) != null ? ref.ours : void 0 : void 0;
};
isImplicitObject = function(stackItem) {
return isImplicit(stackItem) && (stackItem != null ? stackItem[0] : void 0) === '{';
};
isImplicitCall = function(stackItem) {
return isImplicit(stackItem) && (stackItem != null ? stackItem[0] : void 0) === '(';
};
inImplicit = function() {
return isImplicit(stackTop());
};
inImplicitCall = function() {
return isImplicitCall(stackTop());
};
inImplicitObject = function() {
return isImplicitObject(stackTop());
};
inImplicitControl = function() {
var ref;
return inImplicit() && ((ref = stackTop()) != null ? ref[0] : void 0) === 'CONTROL';
};
startImplicitCall = function(idx) {
stack.push([
'(', idx, {
ours: true
}
]);
return tokens.splice(idx, 0, generate('CALL_START', '('));
};
endImplicitCall = function() {
stack.pop();
tokens.splice(i, 0, generate('CALL_END', ')', ['', 'end of input', token[2]]));
return i += 1;
};
startImplicitObject = function(idx, startsLine = true) {
var val;
stack.push([
'{', idx, {
sameLine: true,
startsLine: startsLine,
ours: true
}
]);
val = new String('{');
val.generated = true;
return tokens.splice(idx, 0, generate('{', val, token));
};
endImplicitObject = function(j) {
j = j != null ? j : i;
stack.pop();
tokens.splice(j, 0, generate('}', '}', token));
return i += 1;
};
implicitObjectContinues = (j) => {
var nextTerminatorIdx;
nextTerminatorIdx = null;
this.detectEnd(j, function(token) {
return token[0] === 'TERMINATOR';
}, function(token, i) {
return nextTerminatorIdx = i;
}, {
returnOnNegativeLevel: true
});
if (nextTerminatorIdx == null) {
return false;
}
return this.looksObjectish(nextTerminatorIdx + 1);
};
if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) {
stack.push([
'CONTROL', i, {
ours: true
}
]);
return forward(1);
}
if (tag === 'INDENT' && inImplicit()) {
if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'ELSE' && prevTag !== '=') {
while (inImplicitCall()) {
endImplicitCall();
}
}
if (inImplicitControl()) {
stack.pop();
}
stack.push([tag, i]);
return forward(1);
}
if (indexOf.call(EXPRESSION_START, tag) >= 0) {
stack.push([tag, i]);
return forward(1);
}
if (indexOf.call(EXPRESSION_END, tag) >= 0) {
while (inImplicit()) {
if (inImplicitCall()) {
endImplicitCall();
} else if (inImplicitObject()) {
endImplicitObject();
} else {
stack.pop();
}
}
start = stack.pop();
}
if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !nextToken.spaced && !nextToken.newLine)) {
if (tag === '?') {
tag = token[0] = 'FUNC_EXIST';
}
startImplicitCall(i + 1);
return forward(2);
}
if (indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.indexOfTag(i + 1, 'INDENT') > -1 && this.looksObjectish(i + 2) && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) {
startImplicitCall(i + 1);
stack.push(['INDENT', i + 2]);
return forward(3);
}
if (tag === ':') {
s = (function() {
var ref;
switch (false) {
case ref = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref) < 0:
return start[1];
case this.tag(i - 2) !== '@':
return i - 2;
default:
return i - 1;
}
}).call(this);
while (this.tag(s - 2) === 'HERECOMMENT') {
s -= 2;
}
this.insideForDeclaration = nextTag === 'FOR';
startsLine = s === 0 || (ref = this.tag(s - 1), indexOf.call(LINEBREAKS, ref) >= 0) || tokens[s - 1].newLine;
if (stackTop()) {
[stackTag, stackIdx] = stackTop();
if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) {
return forward(1);
}
}
startImplicitObject(s, !!startsLine);
return forward(2);
}
if (indexOf.call(LINEBREAKS, tag) >= 0) {
for (k = stack.length - 1; k >= 0; k += -1) {
stackItem = stack[k];
if (isImplicitObject(stackItem)) {
stackItem[2].sameLine = false;
}
}
}
newLine = prevTag === 'OUTDENT' || prevToken.newLine;
if (indexOf.call(IMPLICIT_END, tag) >= 0 || indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) {
while (inImplicit()) {
[stackTag, stackIdx, {sameLine, startsLine}] = stackTop();
if (inImplicitCall() && prevTag !== ',') {
endImplicitCall();
} else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' && !(tag === 'POST_IF' && startsLine && implicitObjectContinues(i + 1))) {
endImplicitObject();
} else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) {
if (nextTag === 'HERECOMMENT') {
return forward(1);
}
endImplicitObject();
} else {
break;
}
}
}
if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) {
offset = nextTag === 'OUTDENT' ? 1 : 0;
while (inImplicitObject()) {
endImplicitObject(i + offset);
}
}
return forward(1);
});
}
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() {
return this.scanTokens(function(token, i, tokens) {
var column, line, nextLocation, prevLocation, ref, ref1;
if (token[2]) {
return 1;
}
if (!(token.generated || token.explicit)) {
return 1;
}
if (token[0] === '{' && (nextLocation = (ref = tokens[i + 1]) != null ? ref[2] : void 0)) {
({
first_line: line,
first_column: column
} = nextLocation);
} else if (prevLocation = (ref1 = tokens[i - 1]) != null ? ref1[2] : void 0) {
({
last_line: line,
last_column: column
} = prevLocation);
} else {
line = column = 0;
}
token[2] = {
first_line: line,
first_column: column,
last_line: line,
last_column: column
};
return 1;
});
}
fixOutdentLocationData() {
return this.scanTokens(function(token, i, tokens) {
var prevLocationData;
if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END') || (token.generated && token[0] === '}'))) {
return 1;
}
prevLocationData = tokens[i - 1][2];
token[2] = {
first_line: prevLocationData.last_line,
first_column: prevLocationData.last_column,
last_line: prevLocationData.last_line,
last_column: prevLocationData.last_column
};
return 1;
});
}
normalizeLines() {
var action, condition, indent, outdent, starter;
starter = indent = outdent = null;
condition = function(token, i) {
var ref, ref1, ref2, ref3;
return token[1] !== ';' && (ref = token[0], indexOf.call(SINGLE_CLOSERS, ref) >= 0) && !(token[0] === 'TERMINATOR' && (ref1 = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((ref2 = token[0]) === 'CATCH' || ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (ref3 = token[0], indexOf.call(CALL_CLOSERS, ref3) >= 0) && (this.tokens[i - 1].newLine || this.tokens[i - 1][0] === 'OUTDENT');
};
action = function(token, i) {
return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent);
};
return this.scanTokens(function(token, i, tokens) {
var j, k, ref, ref1, tag;
[tag] = token;
if (tag === 'TERMINATOR') {
if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') {
tokens.splice(i, 1, ...this.indentation());
return 1;
}
if (ref = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref) >= 0) {
tokens.splice(i, 1);
return 0;
}
}
if (tag === 'CATCH') {
for (j = k = 1; k <= 2; j = ++k) {
if (!((ref1 = this.tag(i + j)) === 'OUTDENT' || ref1 === 'TERMINATOR' || ref1 === 'FINALLY')) {
continue;
}
tokens.splice(i + j, 0, ...this.indentation());
return 2 + j;
}
}
if (indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) {
starter = tag;
[indent, outdent] = this.indentation(tokens[i]);
if (starter === 'THEN') {
indent.fromThen = true;
}
tokens.splice(i + 1, 0, indent);
this.detectEnd(i + 2, condition, action);
if (tag === 'THEN') {
tokens.splice(i, 1);
}
return 1;
}
return 1;
});
}
tagPostfixConditionals() {
var action, condition, original;
original = null;
condition = function(token, i) {
var prevTag, tag;
[tag] = token;
[prevTag] = this.tokens[i - 1];
return tag === 'TERMINATOR' || (tag === 'INDENT' && indexOf.call(SINGLE_LINERS, prevTag) < 0);
};
action = function(token, i) {
if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) {
return original[0] = 'POST_' + original[0];
}
};
return this.scanTokens(function(token, i) {
if (token[0] !== 'IF') {
return 1;
}
original = token;
this.detectEnd(i + 1, condition, action);
return 1;
});
}
indentation(origin) {
var indent, outdent;
indent = ['INDENT', 2];
outdent = ['OUTDENT', 2];
if (origin) {
indent.generated = outdent.generated = true;
indent.origin = outdent.origin = origin;
} else {
indent.explicit = outdent.explicit = true;
}
return [indent, outdent];
}
tag(i) {
var ref;
return (ref = this.tokens[i]) != null ? ref[0] : void 0;
}
};
Rewriter.prototype.generate = generate;
return Rewriter;
})();
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END'], ['STRING_START', 'STRING_END'], ['REGEX_START', 'REGEX_END']];
exports.INVERSES = INVERSES = {};
EXPRESSION_START = [];
EXPRESSION_END = [];
for (k = 0, len = BALANCED_PAIRS.length; k < len; k++) {
[left, rite] = BALANCED_PAIRS[k];
EXPRESSION_START.push(INVERSES[rite] = left);
EXPRESSION_END.push(INVERSES[left] = rite);
}
EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
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_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR'];
SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN'];
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'];
CALL_CLOSERS = ['.', '?.', '::', '?::'];
}).call(this);