sucrase
Version:
Super-fast alternative to Babel for when you can target modern JS runtimes
157 lines (146 loc) • 5.18 kB
JavaScript
import {TokenType as tt} from "../parser/tokenizer/types";
import isIdentifier from "../util/isIdentifier";
import Transformer from "./Transformer";
export default class TypeScriptTransformer extends Transformer {
constructor(
rootTransformer,
tokens,
isImportsTransformEnabled,
) {
super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.isImportsTransformEnabled = isImportsTransformEnabled;;
}
process() {
if (
this.rootTransformer.processPossibleArrowParamEnd() ||
this.rootTransformer.processPossibleAsyncArrowWithTypeParams() ||
this.rootTransformer.processPossibleTypeRange()
) {
return true;
}
if (
this.tokens.matches1(tt._public) ||
this.tokens.matches1(tt._protected) ||
this.tokens.matches1(tt._private) ||
this.tokens.matches1(tt._abstract) ||
this.tokens.matches1(tt._readonly) ||
this.tokens.matches1(tt.nonNullAssertion)
) {
this.tokens.removeInitialToken();
return true;
}
if (this.tokens.matches1(tt._enum) || this.tokens.matches2(tt._const, tt._enum)) {
this.processEnum();
return true;
}
if (
this.tokens.matches2(tt._export, tt._enum) ||
this.tokens.matches3(tt._export, tt._const, tt._enum)
) {
this.processEnum(true);
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.matches1(tt._const) || this.tokens.matches1(tt._enum)) {
this.tokens.removeToken();
}
const enumName = this.tokens.identifierName();
this.tokens.removeToken();
if (isExport && !this.isImportsTransformEnabled) {
this.tokens.appendCode("export ");
}
this.tokens.appendCode(`var ${enumName}; (function (${enumName})`);
this.tokens.copyExpectedToken(tt.braceL);
this.processEnumBody(enumName);
this.tokens.copyExpectedToken(tt.braceR);
if (isExport && this.isImportsTransformEnabled) {
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.matches1(tt.braceR)) {
break;
}
const nameToken = this.tokens.currentToken();
let name;
let nameStringCode;
if (nameToken.type === tt.name) {
name = this.tokens.identifierNameForToken(nameToken);
nameStringCode = `"${name}"`;
} else if (nameToken.type === tt.string) {
name = this.tokens.stringValueForToken(nameToken);
nameStringCode = this.tokens.code.slice(nameToken.start, nameToken.end);
} else {
throw new Error("Expected name or string at beginning of enum element.");
}
const isValidIdentifier = isIdentifier(name);
this.tokens.removeInitialToken();
let valueIsString;
let valueCode;
if (this.tokens.matches1(tt.eq)) {
const rhsEndIndex = this.tokens.currentToken().rhsEndIndex;
if (rhsEndIndex == null) {
throw new Error("Expected rhsEndIndex on enum assign.");
}
this.tokens.removeToken();
if (
this.tokens.matches2(tt.string, tt.comma) ||
this.tokens.matches2(tt.string, tt.braceR)
) {
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.matches1(tt.comma)) {
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;
}
}
}