UNPKG

decaffeinate-parser

Version:

A better AST for CoffeeScript, inspired by CoffeeScriptRedux.

178 lines (177 loc) 7.18 kB
"use strict"; 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); }