decaffeinate-parser
Version:
A better AST for CoffeeScript, inspired by CoffeeScriptRedux.
178 lines (177 loc) • 7.18 kB
JavaScript
;
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
exports.__esModule = true;
var SourceType_1 = require("coffee-lex/dist/SourceType");
var nodes_1 = require("decaffeinate-coffeescript2/lib/coffeescript/nodes");
var nodes_2 = require("../nodes");
var isImplicitPlusOp_1 = require("./isImplicitPlusOp");
var parseString_1 = require("./parseString");
/**
* Reconstruct template literal information given the coffee-lex tokens and the
* CoffeeScript AST. Since the CoffeeScript AST doesn't attempt to represent a
* template literal (it's a bunch of + operations instead), the source locations
* are generally unreliable and we need to rely on the token locations instead.
*/
function getTemplateLiteralComponents(context, node) {
var tokens = context.sourceTokens;
var quasis = [];
var unmappedExpressions = [];
var elements = getElements(node, context);
var nodeRange = context.getRange(node);
if (!nodeRange) {
throw new Error('Expected valid range on template literal node.');
}
var _a = getStartToken(nodeRange[0], tokens), startTokenIndex = _a.startTokenIndex, startToken = _a.startToken;
var depth = 0;
var lastToken = startToken;
for (var tokenIndex = startTokenIndex; tokenIndex; tokenIndex = tokenIndex.next()) {
var token = tokens.tokenAtIndex(tokenIndex);
if (!token) {
break;
}
if (token.type === SourceType_1["default"].INTERPOLATION_START ||
token.type === SourceType_1["default"].CSX_OPEN_TAG_START) {
depth++;
if (depth === 1) {
quasis.push(findQuasi(lastToken, token, context, elements));
lastToken = token;
}
}
else if (token.type === SourceType_1["default"].INTERPOLATION_END ||
token.type === SourceType_1["default"].CSX_SELF_CLOSING_TAG_END ||
token.type === SourceType_1["default"].CSX_CLOSE_TAG_END) {
depth--;
if (depth === 0) {
unmappedExpressions.push(findExpression(lastToken, token, context, elements));
lastToken = token;
}
}
else if (depth === 0 && isTemplateLiteralEnd(token)) {
quasis.push(findQuasi(lastToken, token, context, elements));
lastToken = token;
break;
}
}
return {
quasis: quasis,
unmappedExpressions: unmappedExpressions,
start: startToken.start,
end: lastToken.end
};
}
exports["default"] = getTemplateLiteralComponents;
function getElements(node, context) {
if (node instanceof nodes_1.Op && isImplicitPlusOp_1["default"](node, context)) {
if (!node.second) {
throw new Error('Expected second operand on plus op.');
}
return __spreadArrays(getElements(node.first, context), getElements(node.second, context));
}
return [node];
}
/**
* Usually the start token is at the start index of the relevant AST node, but
* if the start of the template literal is an interpolation, it's two before
* that one, so check to see which case we are and return what we find.
*/
function getStartToken(start, tokens) {
var tokenIndex = tokens.indexOfTokenNearSourceIndex(start);
for (var i = 0; i < 5; i++) {
var token = tokens.tokenAtIndex(tokenIndex);
if (!token) {
throw new Error('Expected to find a start token in a template literal.');
}
if (isTemplateLiteralStart(token)) {
return { startToken: token, startTokenIndex: tokenIndex };
}
var prevToken = tokenIndex.previous();
if (!prevToken) {
throw new Error('Expected a previous token when searching for a template start.');
}
tokenIndex = prevToken;
}
throw new Error('Expected a template literal start token.');
}
function findQuasi(leftToken, rightToken, context, elements) {
var matchingElements = elements.filter(function (elem) {
var range = context.getRange(elem);
if (!range) {
throw new Error('Unexpected invalid range.');
}
return range[0] >= leftToken.start && range[1] <= rightToken.end;
});
var start = leftToken.end;
var end = rightToken.start;
var startLoc = context.linesAndColumns.locationForIndex(leftToken.end);
if (!startLoc) {
throw new Error("Expected to find a location for index " + leftToken.end + ".");
}
var raw = context.source.slice(start, end);
if (matchingElements.length === 0) {
return new nodes_2.Quasi(startLoc.line + 1, startLoc.column + 1, start, end, raw, '');
}
else if (matchingElements.length === 1) {
var element = matchingElements[0];
var literal = void 0;
if (element instanceof nodes_1.Literal) {
literal = element;
}
else if (element instanceof nodes_1.Value &&
element.properties.length === 0 &&
element.base instanceof nodes_1.Literal) {
literal = element.base;
}
else {
throw new Error('Expected quasi element to be either a literal or a value containing only a literal.');
}
var stringValue = parseString_1["default"](literal.value);
return new nodes_2.Quasi(startLoc.line + 1, startLoc.column + 1, start, end, raw, stringValue !== undefined ? stringValue : literal.value);
}
else {
throw new Error('Unexpectedly found multiple elements in string interpolation.');
}
}
function findExpression(leftToken, rightToken, context, elements) {
var matchingElements = elements.filter(function (elem) {
var range = context.getRange(elem);
if (!range) {
throw new Error('Unexpected invalid range.');
}
return range[0] >= leftToken.start && range[1] <= rightToken.end;
});
if (matchingElements.length === 0) {
return null;
}
else if (matchingElements.length === 1) {
return matchingElements[0];
}
else {
throw new Error('Unexpectedly found multiple elements in string interpolation.');
}
}
function isTemplateLiteralStart(token) {
return ([
SourceType_1["default"].DSTRING_START,
SourceType_1["default"].SSTRING_START,
SourceType_1["default"].TDSTRING_START,
SourceType_1["default"].TSSTRING_START,
SourceType_1["default"].HEREGEXP_START,
SourceType_1["default"].CSX_OPEN_TAG_END
].indexOf(token.type) >= 0);
}
function isTemplateLiteralEnd(token) {
return ([
SourceType_1["default"].DSTRING_END,
SourceType_1["default"].SSTRING_END,
SourceType_1["default"].TDSTRING_END,
SourceType_1["default"].TSSTRING_END,
SourceType_1["default"].HEREGEXP_END,
SourceType_1["default"].CSX_CLOSE_TAG_START
].indexOf(token.type) >= 0);
}