UNPKG

sucrase

Version:

Super-fast alternative to Babel for when you can target modern JS runtimes

186 lines (185 loc) 7.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const isIdentifier_1 = require("../util/isIdentifier"); const Transformer_1 = require("./Transformer"); class TypeScriptTransformer extends Transformer_1.default { constructor(rootTransformer, tokens) { super(); this.rootTransformer = rootTransformer; this.tokens = tokens; } process() { // We need to handle all classes specially in order to remove `implements`. if (this.tokens.matchesKeyword("class")) { this.rootTransformer.processClass(); return true; } const processedType = this.rootTransformer.processPossibleTypeRange(); if (processedType) { return true; } if (this.tokens.matches(["public"]) || this.tokens.matches(["protected"]) || this.tokens.matches(["private"]) || this.tokens.matches(["abstract"]) || this.tokens.matches(["readonly"])) { this.tokens.removeInitialToken(); return true; } if (this.isNonNullAssertion()) { this.tokens.removeInitialToken(); return true; } if (this.tokens.matches(["enum"]) || this.tokens.matches(["const", "enum"])) { this.processEnum(); return true; } if (this.tokens.matches(["export", "enum"]) || this.tokens.matches(["export", "const", "enum"])) { this.processEnum(true); return true; } return false; } /** * This is either a negation operator or a non-null assertion operator. If it's a non-null * assertion, get rid of it. */ isNonNullAssertion() { if (!this.tokens.matches(["!"])) { return false; } let index = this.tokens.currentIndex() - 1; // Walk left past all operators that might be either prefix or postfix operators. while (this.tokens.matchesAtIndex(index, ["!"]) || this.tokens.matchesAtIndex(index, ["++"]) || this.tokens.matchesAtIndex(index, ["--"])) { index--; } if (index < 0) { return false; } const prevToken = this.tokens.tokens[index]; // Bias toward keeping the token; if we remove it incorrectly, the code will have a subtle bug, // while if we don't remove it and we need to, the code will have a syntax error. if ([ "name", "num", "string", "false", "true", "null", "void", "this", ")", "]", "}", "`", ].includes(prevToken.type.label)) { return true; } return false; } processEnum(isExport = false) { // We might have "export const enum", so just remove all relevant tokens. this.tokens.removeInitialToken(); while (this.tokens.matches(["const"]) || this.tokens.matches(["enum"])) { this.tokens.removeToken(); } const enumName = this.tokens.currentToken().value; this.tokens.removeToken(); this.tokens.appendCode(`var ${enumName}; (function (${enumName})`); this.tokens.copyExpectedToken("{"); this.processEnumBody(enumName); this.tokens.copyExpectedToken("}"); if (isExport) { this.tokens.appendCode(`)(${enumName} || (exports.${enumName} = ${enumName} = {}));`); } else { this.tokens.appendCode(`)(${enumName} || (${enumName} = {}));`); } } /** * Rather than try to compute the actual enum values at compile time, we just create variables for * each one and let everything evaluate at runtime. There's some additional complexity due to * handling string literal names, including ones that happen to be valid identifiers. */ processEnumBody(enumName) { let isPreviousValidIdentifier = false; let lastValueReference = null; while (true) { if (this.tokens.matches(["}"])) { break; } const nameToken = this.tokens.currentToken(); let name; let isValidIdentifier; let nameStringCode; if (nameToken.type.label === "name") { name = nameToken.value; isValidIdentifier = true; nameStringCode = `"${name}"`; } else if (nameToken.type.label === "string") { name = nameToken.value; isValidIdentifier = isIdentifier_1.default(name); nameStringCode = this.tokens.code.slice(nameToken.start, nameToken.end); } else { throw new Error("Expected name or string at beginning of enum element."); } this.tokens.removeInitialToken(); let valueIsString; let valueCode; if (this.tokens.matches(["="])) { const rhsEndIndex = this.tokens.currentToken().rhsEndIndex; if (rhsEndIndex == null) { throw new Error("Expected rhsEndIndex on enum assign."); } this.tokens.removeToken(); if (this.tokens.matches(["string", ","]) || this.tokens.matches(["string", "}"])) { valueIsString = true; } const startToken = this.tokens.currentToken(); while (this.tokens.currentIndex() < rhsEndIndex) { this.tokens.removeToken(); } valueCode = this.tokens.code.slice(startToken.start, this.tokens.tokenAtRelativeIndex(-1).end); } else { valueIsString = false; if (lastValueReference != null) { if (isPreviousValidIdentifier) { valueCode = `${lastValueReference} + 1`; } else { valueCode = `(${lastValueReference}) + 1`; } } else { valueCode = "0"; } } if (this.tokens.matches([","])) { this.tokens.removeToken(); } let valueReference; if (isValidIdentifier) { this.tokens.appendCode(`const ${name} = ${valueCode}; `); valueReference = name; } else { valueReference = valueCode; } if (valueIsString) { this.tokens.appendCode(`${enumName}[${nameStringCode}] = ${valueReference};`); } else { this.tokens.appendCode(`${enumName}[${enumName}[${nameStringCode}] = ${valueReference}] = ${nameStringCode};`); } lastValueReference = valueReference; isPreviousValidIdentifier = isValidIdentifier; } } } exports.default = TypeScriptTransformer;