@casual-simulation/aux-runtime
Version:
Runtime for AUX projects
1,052 lines (1,051 loc) • 52 kB
JavaScript
/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences.
*
* Copyright (c) 2019-2025 Casual Simulation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as Acorn from 'acorn';
import { generate, GENERATOR } from 'astring';
import LRUCache from 'lru-cache';
import { traverse, VisitorKeys } from 'estraverse';
import { createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, getItem, Doc, } from 'yjs';
import { createAbsolutePositionFromStateVector, createRelativePositionFromStateVector, getClock, } from '@casual-simulation/aux-common/yjs/YjsHelpers';
import { calculateIndexFromLocation, calculateLocationFromIndex, } from './TranspilerUtils';
import { tsPlugin } from 'acorn-typescript';
/**
* The symbol that is used in script dependencies to represent any argument.
*/
export const anyArgument = Symbol('anyArgument');
const exJsGenerator = Object.assign({}, GENERATOR, {
ImportExpression: function (node, state) {
state.write('import(');
this[node.source.type](node.source, state);
state.write(')');
},
});
/**
* The estraverse visitor keys that are used for TypeScript nodes.
*/
export const TypeScriptVisistorKeys = {
TSTypeParameterDeclaration: [],
TSCallSignatureDeclaration: [],
TSConstructSignatureDeclaration: [],
TSInterfaceDeclaration: [],
TSModuleDeclaration: [],
TSEnumDeclaration: [],
TSTypeAliasDeclaration: [],
TSDeclareFunction: [],
TSDeclareMethod: [],
TSExternalModuleReference: [],
TSQualifiedName: [],
TSEnumMember: [],
TSModuleBlock: [],
TSTypePredicate: [],
TSThisType: [],
TSTypeAnnotation: [],
TSLiteralType: [],
TSVoidKeyword: [],
TSNeverKeyword: [],
TSNumberKeyword: [],
TSAnyKeyword: [],
TSBooleanKeyword: [],
TSBigIntKeyword: [],
TSObjectKeyword: [],
TSStringKeyword: [],
TSSymbolKeyword: [],
TSUndefinedKeyword: [],
TSUnknownKeyword: [],
TSFunctionType: [],
TSConstructorType: [],
TSUnionType: [],
TSIntersectionType: [],
TSInferType: [],
TSImportType: [],
TSTypeQuery: [],
TSTypeParameter: [],
TSMappedType: [],
TSTypeLiteral: [],
TSTupleType: [],
TSNamedTupleMember: [],
TSRestType: [],
TSOptionalType: [],
TSTypeReference: [],
TSParenthesizedType: [],
TSArrayType: [],
TSIndexedAccessType: [],
TSConditionalType: [],
TSIndexSignature: [],
TSTypeParameterInstantiation: [],
TSExpressionWithTypeArguments: [],
TSInterfaceBody: [],
TSIntrinsicKeyword: [],
TSImportEqualsDeclaration: [],
TSNonNullExpression: [],
TSTypeOperator: [],
TSMethodSignature: [],
TSPropertySignature: [],
TSAsExpression: [],
TSParameterProperty: ['parameter'],
ClassDeclaration: [
...VisitorKeys.ClassDeclaration,
'implements',
'typeParameters',
],
ClassExpression: [
...VisitorKeys.ClassExpression,
'implements',
'typeParameters',
],
VariableDeclarator: [...VisitorKeys.VariableDeclarator, 'typeAnnotation'],
FunctionDeclaration: [
...VisitorKeys.FunctionDeclaration,
'returnType',
'typeParameters',
],
FunctionExpression: [
...VisitorKeys.FunctionExpression,
'returnType',
'typeParameters',
],
Identifier: [...VisitorKeys.Identifier, 'typeAnnotation'],
PropertyDefinition: [
...VisitorKeys.PropertyDefinition,
'typeAnnotation',
],
};
/**
* The list of macros that the sandbox uses on the input code before transpiling it.
*/
const MACROS = [
{
test: /^(?:🧬)/g,
replacement: (val) => '',
},
];
/**
* The list of node types that have their own async scopes. To be ignored in _isAsyncNode.
*/
const asyncBoundaryNodes = new Set([
'FunctionDeclaration',
'FunctionExpression',
'ArrowFunctionExpression',
'MethodDefinition',
'ClassDeclaration',
'ClassExpression',
'ObjectMethod',
'ClassMethod',
]);
/**
* Replaces macros in the given text.
* @param text The text that the macros should be replaced in.
*/
export function replaceMacros(text) {
if (!text) {
return text;
}
for (let m of MACROS) {
text = text.replace(m.test, m.replacement);
}
return text;
}
/**
* Defines a class that is able to compile code from AUX's custom JavaScript dialect
* into pure ES6 JavaScript. Does not preserve spacing or comments.
*
* See https://docs.google.com/document/d/1WQXQPjdXxyx_lau15WPpwTTYvt66_wPCu3x-08rpLoY/edit?usp=sharing
*/
export class Transpiler {
get forceSync() {
return this._forceSync;
}
set forceSync(value) {
this._forceSync = value;
}
constructor(options) {
var _a, _b, _c, _d, _e, _f;
this._cache = new LRUCache({
max: 1000,
});
this._parser = Acorn.Parser.extend(tsPlugin());
this._parser.prototype.parseReturnStatement = function (node) {
this.next();
// In `return` (and `break`/`continue`), the keywords with
// optional arguments, we eagerly look for a semicolon or the
// possibility to insert one.
if (this.eat(Acorn.tokTypes.semi) || this.insertSemicolon()) {
node.argument = null;
}
else {
node.argument = this.parseExpression();
this.semicolon();
}
return this.finishNode(node, 'ReturnStatement');
};
this._jsxFactory = (_a = options === null || options === void 0 ? void 0 : options.jsxFactory) !== null && _a !== void 0 ? _a : 'h';
this._jsxFragment = (_b = options === null || options === void 0 ? void 0 : options.jsxFragment) !== null && _b !== void 0 ? _b : 'Fragment';
this._importFactory = (_c = options === null || options === void 0 ? void 0 : options.importFactory) !== null && _c !== void 0 ? _c : 'importModule';
this._importMetaFactory = (_d = options === null || options === void 0 ? void 0 : options.importMetaFactory) !== null && _d !== void 0 ? _d : 'importMeta';
this._exportFactory = (_e = options === null || options === void 0 ? void 0 : options.exportFactory) !== null && _e !== void 0 ? _e : 'exports';
this._forceSync = (_f = options === null || options === void 0 ? void 0 : options.forceSync) !== null && _f !== void 0 ? _f : false;
}
parse(code) {
const macroed = replaceMacros(code);
const node = this._parse(macroed);
return node;
}
/**
* Transpiles the given code into ES6 JavaScript Code.
*/
transpile(code) {
const result = this._transpile(code);
return result.code;
}
/**
* Transpiles the given code and returns the result with the generated metadata.
* @param code The code that should be transpiled.
*/
transpileWithMetadata(code) {
return this._transpile(code);
}
/**
* Determines if the given node contains any await expressions.
* @param node The node.
*/
_isAsyncNode(node) {
if (!node || asyncBoundaryNodes.has(node.type)) {
return false;
}
if (node.type === 'AwaitExpression') {
return true;
}
for (const key in node) {
const child = node[key];
if (!child || typeof child !== 'object' || key === 'parent') {
continue;
}
if (Array.isArray(child)) {
for (const item of child) {
if (this._isAsyncNode(item)) {
return true;
}
}
}
else if (this._isAsyncNode(child)) {
return true;
}
}
return false;
}
/**
* Transpiles the given code into ES6 JavaScript Code.
*/
_transpile(code) {
const cached = this._cache.get(code);
if (cached) {
return cached;
}
const macroed = replaceMacros(code);
const node = this._parse(macroed);
const isAsync = this._isAsyncNode(node);
// we create a YJS document to track
// text changes. This lets us use a separate client ID for each change
// which makes the calculations for indexes much simpler.
// This is because we can use a separate client ID for every required
// change and ignore other changes when looking for the right edit position.
const doc = new Doc();
doc.clientID = 0;
const text = doc.getText();
text.insert(0, code);
let metadata = {
doc,
text,
isAsync,
isModule: false,
};
this._replace(node, doc, text, metadata);
const finalCode = text.toString();
const result = {
code: finalCode,
original: macroed,
metadata,
};
this._cache.set(code, result);
return result;
}
/**
* Parses the given code into a syntax tree.
* @param code
*/
_parse(code) {
const node = this._parser.parse(code, {
ecmaVersion: 14,
locations: true,
sourceType: 'module',
});
return node;
}
getTagNodeValues(n) {
let currentNode = n.identifier;
let identifier;
let args = [];
let nodes = [];
while (currentNode.type === 'MemberExpression') {
currentNode = currentNode.object;
}
if (currentNode.type === 'CallExpression') {
identifier = currentNode.callee;
args = currentNode.arguments;
currentNode = n.identifier;
while (currentNode.type === 'MemberExpression') {
nodes.unshift(currentNode);
currentNode = currentNode.object;
}
}
else {
identifier = n.identifier;
}
let tag;
if (identifier.type === 'MemberExpression') {
tag = this.toJs(identifier);
}
else {
tag = identifier.name || identifier.value;
}
return { tag, args, nodes };
}
toJs(node) {
return generate(node, {
generator: exJsGenerator,
});
}
_replace(node, doc, text, metadata) {
if (!node) {
return;
}
traverse(node, {
enter: ((n, parent) => {
var _a;
if (n.type === 'ImportDeclaration') {
this._replaceImportDeclaration(n, parent, doc, text, metadata);
}
else if (n.type === 'ImportExpression') {
this._replaceImportExpression(n, parent, doc, text, metadata);
}
else if (n.type === 'ExportNamedDeclaration') {
this._replaceExportNamedDeclaration(n, parent, doc, text, metadata);
}
else if (n.type === 'ExportDefaultDeclaration') {
this._replaceExportDefaultDeclaration(n, parent, doc, text, metadata);
}
else if (n.type === 'ExportAllDeclaration') {
this._replaceExportAllDeclaration(n, parent, doc, text, metadata);
}
else if (n.type === 'MetaProperty' &&
n.meta.name === 'import' &&
n.property.name === 'meta') {
this._replaceImportMeta(n, parent, doc, text, metadata);
}
else if (n.type === 'WhileStatement') {
this._replaceWhileStatement(n, doc, text);
}
else if (n.type === 'DoWhileStatement') {
this._replaceDoWhileStatement(n, doc, text);
}
else if (n.type === 'ForStatement') {
this._replaceForStatement(n, doc, text);
}
else if (n.type === 'ForInStatement') {
this._replaceForInStatement(n, doc, text);
}
else if (n.type === 'ForOfStatement') {
this._replaceForOfStatement(n, doc, text);
}
else if (n.type === 'JSXElement') {
this._replaceJSXElement(n, doc, text, metadata);
}
else if (n.type === 'JSXText') {
this._replaceJSXText(n, doc, text);
}
else if (n.type === 'JSXExpressionContainer') {
this._replaceJSXExpressionContainer(n, doc, text);
}
else if (n.type === 'JSXFragment') {
this._replaceJSXFragment(n, doc, text, metadata);
}
else if (n.type === 'JSXEmptyExpression') {
this._replaceJSXEmptyExpression(n, doc, text);
}
else if (this._forceSync &&
n.type === 'FunctionDeclaration' &&
n.async) {
this._replaceAsyncFunction(n, doc, text);
}
else if (this._forceSync &&
n.type === 'ArrowFunctionExpression' &&
n.async) {
this._replaceAsyncFunction(n, doc, text);
}
else if (this._forceSync &&
n.type === 'FunctionExpression' &&
n.async) {
if (parent.type === 'Property' && parent.method) {
this._replaceAsyncFunction(parent, doc, text);
}
else {
this._replaceAsyncFunction(n, doc, text);
}
}
else if (this._forceSync && n.type === 'AwaitExpression') {
this._replaceAwaitExpression(n, doc, text);
}
else if (n.type === 'ClassDeclaration' ||
n.type === 'ClassExpression') {
if (((_a = n.implements) === null || _a === void 0 ? void 0 : _a.length) > 0) {
this._removeClassImplements(n, doc, text);
}
if (n.abstract === true) {
this._removeClassAbstract(n, doc, text);
}
}
else if (n.type === 'TSParameterProperty') {
// do nothing
}
else if (n.type === 'MethodDefinition') {
if (n.abstract) {
this._removeNodeOrReplaceWithUndefined(n, doc, text);
}
else if (n.accessibility) {
this._removeAccessibility(n, doc, text);
}
}
else if (n.type === 'PropertyDefinition') {
if (n.accessibility) {
this._removeAccessibility(n, doc, text);
}
}
else if (n.type === 'TSAsExpression') {
this._removeAsExpression(n, doc, text);
}
else if (n.type === 'Identifier' && n.optional === true) {
this._removeOptionalFromIdentifier(n, doc, text);
}
else if (n.type.startsWith('TS')) {
this._removeNodeOrReplaceWithUndefined(n, doc, text);
}
}),
keys: {
JSXElement: [],
JSXFragment: [],
JSXOpeningElement: [],
JSXClosingElement: [],
JSXText: [],
JSXExpressionContainer: ['expression'],
JSXEmptyExpression: [],
...TypeScriptVisistorKeys,
},
});
}
_replaceImportDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const statementEnd = createRelativePositionFromStateVector(text, version, node.source.end, 1, true);
const sourceStart = createRelativePositionFromStateVector(text, version, node.source.start, -1, true);
const sourceEnd = createRelativePositionFromStateVector(text, version, node.source.end, 1, true);
let currentIndex = absoluteStart.index;
let importCall = node.specifiers.length > 0 ? `const ` : '';
const namespaceImport = node.specifiers.find((s) => s.type === 'ImportNamespaceSpecifier');
const defaultImport = node.specifiers.find((s) => s.type === 'ImportDefaultSpecifier');
if (namespaceImport) {
importCall += `${namespaceImport.local.name} = `;
}
else {
let addedBraces = false;
for (let specifier of node.specifiers) {
if (specifier.type === 'ImportSpecifier') {
if (!addedBraces) {
addedBraces = true;
importCall += `{ `;
}
if (specifier.local === specifier.imported) {
importCall += `${specifier.local.name}, `;
}
else {
importCall += `${specifier.imported.name}: ${specifier.local.name}, `;
}
}
else if (specifier.type === 'ImportDefaultSpecifier') {
if (!addedBraces) {
addedBraces = true;
importCall += `{ `;
}
importCall += `default: ${specifier.local.name}, `;
}
}
if (addedBraces) {
importCall += `}`;
}
if (node.specifiers.length > 0) {
importCall += ` = `;
}
}
if (node.importKind === 'type') {
importCall += `{}`;
}
else {
importCall += `await ${this._importFactory}(`;
}
text.insert(currentIndex, importCall);
currentIndex += importCall.length;
if (node.importKind !== 'type') {
const absoluteSourceEnd = createAbsolutePositionFromRelativePosition(sourceEnd, doc);
text.insert(absoluteSourceEnd.index, `, ${this._importMetaFactory})`);
if (namespaceImport && defaultImport) {
const absoluteEnd = createAbsolutePositionFromRelativePosition(statementEnd, doc);
let defaultImportSource = `\nconst { default: ${defaultImport.local.name} } = ${namespaceImport.local.name};`;
text.insert(absoluteEnd.index + 1, defaultImportSource);
}
const absoluteSourceStart = createAbsolutePositionFromRelativePosition(sourceStart, doc);
text.delete(currentIndex, absoluteSourceStart.index - currentIndex);
}
else {
const absoluteSourceEnd = createAbsolutePositionFromRelativePosition(sourceEnd, doc);
text.delete(currentIndex, absoluteSourceEnd.index - currentIndex);
}
}
_replaceImportExpression(node, parent, doc, text, metadata) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const sourceStart = createRelativePositionFromStateVector(text, version, node.source.start, -1, true);
const sourceEnd = createRelativePositionFromStateVector(text, version, node.source.end, 1, true);
let currentIndex = absoluteStart.index;
let importCall = `${this._importFactory}(`;
text.insert(currentIndex, importCall);
currentIndex += importCall.length;
const absoluteSourceEnd = createAbsolutePositionFromRelativePosition(sourceEnd, doc);
text.insert(absoluteSourceEnd.index, `, ${this._importMetaFactory}`);
const absoluteSourceStart = createAbsolutePositionFromRelativePosition(sourceStart, doc);
text.delete(currentIndex, absoluteSourceStart.index - currentIndex);
}
_replaceImportMeta(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const absoluteEnd = createAbsolutePositionFromStateVector(doc, text, version, node.end, 1, true);
text.insert(absoluteEnd.index, `${this._importMetaFactory}`);
text.delete(absoluteStart.index, absoluteEnd.index - absoluteStart.index);
}
_replaceExportNamedDeclaration(node, parent, doc, text, metadata) {
if (node.declaration) {
this._replaceExportVariableDeclaration(node, parent, doc, text, metadata);
}
else if (node.source) {
this._replaceExportFromSourceDeclaration(node, parent, doc, text, metadata);
}
else {
this._replaceExportSpecifiersDeclaration(node, parent, doc, text, metadata);
}
}
_replaceExportVariableDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const declarationStart = createAbsolutePositionFromStateVector(doc, text, version, node.declaration.start, undefined, true);
const declarationEnd = createRelativePositionFromStateVector(text, version, node.declaration.end, -1, true);
text.delete(absoluteStart.index, declarationStart.index - absoluteStart.index);
const absoluteDeclarationEnd = createAbsolutePositionFromRelativePosition(declarationEnd, doc);
let exportCall = `\nawait ${this._exportFactory}({ `;
if (node.declaration.type === 'VariableDeclaration') {
for (let declaration of node.declaration.declarations) {
exportCall += `${declaration.id.name}, `;
}
}
else {
exportCall += `${node.declaration.id.name}, `;
}
exportCall += `});`;
text.insert(absoluteDeclarationEnd.index + 1, exportCall);
}
_replaceExportSpecifiersDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const relativeEnd = createRelativePositionFromStateVector(text, version, node.end, -1, true);
if (node.specifiers.length > 0) {
let exportCall = `await ${this._exportFactory}({ `;
for (let specifier of node.specifiers) {
if (specifier.local === specifier.exported) {
exportCall += `${specifier.local.name}, `;
}
else {
exportCall += `${specifier.exported.name}: ${specifier.local.name}, `;
}
}
exportCall += `});`;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const absoluteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
text.delete(currentIndex, absoluteEnd.index);
}
else {
let exportCall = `await ${this._exportFactory}({});`;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const absoluteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
text.delete(currentIndex, absoluteEnd.index);
}
}
_replaceExportFromSourceDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const relativeSourceStart = createRelativePositionFromStateVector(text, version, node.source.start, -1, true);
const relativeSourceEnd = createRelativePositionFromStateVector(text, version, node.source.end, -1, true);
const relativeEnd = createRelativePositionFromStateVector(text, version, node.end, -1, true);
if (node.specifiers.length > 0) {
let exportCall = `await ${this._exportFactory}(`;
let specifiers = ', [';
for (let specifier of node.specifiers) {
if (specifier.local === specifier.exported) {
specifiers += `'${specifier.local.name}', `;
}
else {
specifiers += `['${specifier.local.name}', '${specifier.exported.name}'], `;
}
}
specifiers += ']);';
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const sourceEnd = createAbsolutePositionFromRelativePosition(relativeSourceEnd, doc);
text.insert(sourceEnd.index, specifiers);
const sourceStart = createAbsolutePositionFromRelativePosition(relativeSourceStart, doc);
text.delete(currentIndex, sourceStart.index - currentIndex);
const absoluteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
const finalSourceEnd = createAbsolutePositionFromRelativePosition(relativeSourceEnd, doc);
text.delete(absoluteEnd.index - 1, absoluteEnd.index - finalSourceEnd.index);
}
else {
let exportCall = `await ${this._exportFactory}({});`;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const absoluteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
text.delete(currentIndex, absoluteEnd.index);
}
}
_replaceExportDefaultDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const declarationStart = createRelativePositionFromStateVector(text, version, node.declaration.start, -1, true);
const declarationEnd = createRelativePositionFromStateVector(text, version, node.declaration.end, -1, true);
const relativeEnd = createRelativePositionFromStateVector(text, version, node.end, -1, true);
let exportCall = `await ${this._exportFactory}({ default: `;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const absoluteDeclarationStart = createAbsolutePositionFromRelativePosition(declarationStart, doc);
text.delete(currentIndex, absoluteDeclarationStart.index - currentIndex);
const absoluteDeclarationEnd = createAbsolutePositionFromRelativePosition(declarationEnd, doc);
text.insert(absoluteDeclarationEnd.index, ' });');
const absoulteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
text.delete(absoulteEnd.index - 1, 1);
}
_replaceExportAllDeclaration(node, parent, doc, text, metadata) {
metadata.isModule = true;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const sourceStart = createRelativePositionFromStateVector(text, version, node.source.start, -1, true);
const sourceEnd = createRelativePositionFromStateVector(text, version, node.source.end, -1, true);
const relativeEnd = createRelativePositionFromStateVector(text, version, node.end, -1, true);
let exportCall = `await ${this._exportFactory}(`;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, exportCall);
currentIndex += exportCall.length;
const absolutesourceStart = createAbsolutePositionFromRelativePosition(sourceStart, doc);
text.delete(currentIndex, absolutesourceStart.index - currentIndex);
const absoluteSourceEnd = createAbsolutePositionFromRelativePosition(sourceEnd, doc);
text.insert(absoluteSourceEnd.index, ');');
const absoulteEnd = createAbsolutePositionFromRelativePosition(relativeEnd, doc);
text.delete(absoulteEnd.index - 1, 1);
}
_replaceWhileStatement(node, doc, text) {
this._insertEnergyCheckIntoStatement(doc, text, node.body);
}
_replaceDoWhileStatement(node, doc, text) {
this._insertEnergyCheckIntoStatement(doc, text, node.body);
}
_replaceForStatement(node, doc, text) {
this._insertEnergyCheckIntoStatement(doc, text, node.body);
}
_replaceForInStatement(node, doc, text) {
this._insertEnergyCheckIntoStatement(doc, text, node.body);
}
_replaceForOfStatement(node, doc, text) {
this._insertEnergyCheckIntoStatement(doc, text, node.body);
}
_removeClassImplements(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const firstImplemented = node.implements[0];
const lastImplemented = node.implements[node.implements.length - 1];
const implementedStart = createAbsolutePositionFromStateVector(doc, text, version, firstImplemented.start - 'implements '.length, undefined, true);
const implementedEnd = createAbsolutePositionFromStateVector(doc, text, version, lastImplemented.end, -1, true);
text.delete(implementedStart.index, implementedEnd.index - implementedStart.index);
}
_removeClassAbstract(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const abstractStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const abstractEnd = createAbsolutePositionFromStateVector(doc, text, version, node.start + 'abstract '.length, -1, true);
text.delete(abstractStart.index, abstractEnd.index - abstractStart.index);
}
_removeAccessibility(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const accessibility = node.accessibility + ' ';
const t = text.toString();
const relativeStart = createRelativePositionFromStateVector(text, version, node.start, -1, true);
const absoluteStart = createAbsolutePositionFromRelativePosition(relativeStart, doc);
const indexOfAccessibility = t.indexOf(accessibility, absoluteStart.index);
if (indexOfAccessibility < 0 || indexOfAccessibility > node.key.start) {
return;
}
text.delete(indexOfAccessibility, accessibility.length);
}
_removeAsExpression(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const expressionEnd = createAbsolutePositionFromStateVector(doc, text, version, node.expression.end, -1, true);
const absoluteEnd = createAbsolutePositionFromStateVector(doc, text, version, node.end, -1, true);
text.delete(expressionEnd.index, absoluteEnd.index - expressionEnd.index);
}
_removeNodeOrReplaceWithUndefined(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
if (node.type === 'TSInterfaceDeclaration' ||
node.type === 'TSCallSignatureDeclaration' ||
node.type === 'TSEnumDeclaration' ||
node.type === 'TSTypeAliasDeclaration') {
// replace with const Identifier = void 0; instead of deleting
// This is because these declarations might be referenced in a later export {} declaration,
// so we need to keep the identifier around.
// In the future, we might optimize this to also delete TypeScript-sepecific declarations from those exports.
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const identifierStart = createRelativePositionFromStateVector(text, version, node.id.start, undefined, true);
const identifierEnd = createRelativePositionFromStateVector(text, version, node.id.end, -1, true);
const end = createRelativePositionFromStateVector(text, version, node.end, -1, true);
const absoluteIdentifierStart = createAbsolutePositionFromRelativePosition(identifierStart, doc);
text.insert(absoluteIdentifierStart.index, 'const ');
text.delete(absoluteStart.index, absoluteIdentifierStart.index - absoluteStart.index);
const absoluteIdentifierEnd = createAbsolutePositionFromRelativePosition(identifierEnd, doc);
let str = ` = void 0;`;
text.insert(absoluteIdentifierEnd.index, str);
const absoluteEnd = createAbsolutePositionFromRelativePosition(end, doc);
text.delete(absoluteIdentifierEnd.index + str.length, absoluteEnd.index - absoluteIdentifierEnd.index - str.length);
return;
}
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const absoluteEnd = createAbsolutePositionFromStateVector(doc, text, version, node.end, -1, true);
text.delete(absoluteStart.index, absoluteEnd.index - absoluteStart.index);
}
_removeOptionalFromIdentifier(node, doc, text) {
var _a, _b;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const absoluteEnd = createAbsolutePositionFromStateVector(doc, text, version, (_b = (_a = node.typeAnnotation) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : node.end, -1, true);
text.delete(absoluteEnd.index - 1, 1);
}
_replaceJSXElement(node, doc, text, metadata) {
this._insertJSXFactoryCall(node, node.openingElement, node.closingElement, doc, text, metadata);
this._removeTag(node, node.openingElement, node.closingElement, doc, text);
}
_replaceJSXFragment(node, doc, text, metadata) {
this._insertJSXFactoryCall(node, node.openingFragment, node.closingFragment, doc, text, metadata);
this._removeTag(node, node.openingFragment, node.closingFragment, doc, text);
}
_replaceJSXEmptyExpression(node, doc, text) {
const version = { '0': getClock(doc, 0) };
// Position for the opening "{"
const valueStartBegin = createRelativePositionFromStateVector(text, version, node.start, undefined, true);
// Position for the closing "}"
const valueEndEnd = createRelativePositionFromStateVector(text, version, node.end + 1, -1, true);
// Delete the expression
const absoluteValueStartBegin = createAbsolutePositionFromRelativePosition(valueStartBegin, doc);
const absoluteValueEndEnd = createAbsolutePositionFromRelativePosition(valueEndEnd, doc);
const length = absoluteValueEndEnd.index - absoluteValueStartBegin.index;
text.delete(absoluteValueStartBegin.index, length);
text.insert(absoluteValueEndEnd.index, "''");
}
_insertJSXFactoryCall(node, openElement, closeElement, doc, text, metadata) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const openingFunctionCall = `${this._jsxFactory}(`;
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, node.start, undefined, true);
const end = createRelativePositionFromStateVector(text, version, !!closeElement ? closeElement.end : node.end, -1, true);
const nameStart = node.type === 'JSXElement'
? createRelativePositionFromStateVector(text, version, openElement.name.start, undefined, true)
: null;
const nameEnd = node.type == 'JSXElement'
? createRelativePositionFromStateVector(text, version, openElement.name.end, -1, true)
: null;
let currentIndex = absoluteStart.index;
text.insert(currentIndex, openingFunctionCall);
currentIndex += openingFunctionCall.length;
if (node.type === 'JSXElement') {
const nameElement = openElement.name;
let addQuotes = false;
if (nameElement.type === 'JSXIdentifier') {
const name = openElement.name.name;
if (/^[a-z]/.test(name)) {
addQuotes = true;
}
}
// Add quotes around builtin component names
if (addQuotes) {
const startIndex = createAbsolutePositionFromRelativePosition(nameStart, doc);
text.insert(startIndex.index, '"');
const endIndex = createAbsolutePositionFromRelativePosition(nameEnd, doc);
text.insert(endIndex.index, '",');
currentIndex = endIndex.index + 2;
}
else {
// Add the comma after variable reference names
const endIndex = createAbsolutePositionFromRelativePosition(nameEnd, doc);
text.insert(endIndex.index, ',');
currentIndex = endIndex.index + 1;
}
}
else {
// make string literal
const nodeName = this._jsxFragment + ',';
text.insert(currentIndex, nodeName);
currentIndex += nodeName.length;
}
if (openElement.attributes.length > 0) {
this._replaceJSXElementAttributes(node, openElement, openElement.attributes, doc, text, metadata);
}
else {
const props = `null,`;
text.insert(currentIndex, props);
}
this._replaceJSXElementChildren(node, node.children, doc, text, metadata);
const absoluteEnd = createAbsolutePositionFromRelativePosition(end, doc);
doc.clientID += 1;
text.insert(absoluteEnd.index, ')');
}
_replaceJSXElementAttributes(node, openElement, attributes, doc, text, metadata) {
var _a, _b;
// doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const start = createRelativePositionFromStateVector(text, version, (_b = (_a = openElement.name) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : openElement.start + 1, undefined, true);
const end = createRelativePositionFromStateVector(text, version, openElement.selfClosing ? openElement.end - 2 : openElement.end, -1, true);
let attrs = [];
for (let attr of attributes) {
const attrStart = createRelativePositionFromStateVector(text, version, attr.start, undefined, true);
if (attr.type === 'JSXSpreadAttribute') {
attrs.push([attr, attrStart]);
}
else {
const attrName = attr.name.name;
attrs.push([attr, attrStart, attrName]);
}
}
let index = 0;
for (let [attr, start, name] of attrs) {
const pos = createAbsolutePositionFromRelativePosition(start, doc);
let val = '';
if (index > 0) {
val = ',';
}
if (attr.type !== 'JSXSpreadAttribute') {
val += `"${name}":`;
if (!attr.value) {
val += 'true';
}
}
text.insert(pos.index, val);
index++;
if (attr.type === 'JSXSpreadAttribute') {
this._replace(attr.argument, doc, text, metadata);
}
else {
this._replace(attr.value, doc, text, metadata);
}
}
const startAbsolute = createAbsolutePositionFromRelativePosition(start, doc);
text.insert(startAbsolute.index, '{');
const endAbsolute = createAbsolutePositionFromRelativePosition(end, doc);
text.insert(endAbsolute.index, '},');
}
_replaceJSXElementChildren(node, children, doc, text, metadata) {
const version = { '0': getClock(doc, 0) };
for (let child of children) {
const pos = createRelativePositionFromStateVector(text, version, child.end, undefined, true);
this._replace(child, doc, text, metadata);
doc.clientID += 1;
const absoluteEnd = createAbsolutePositionFromRelativePosition(pos, doc);
text.insert(absoluteEnd.index, ',');
}
}
_replaceJSXText(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const startIndex = node.start;
const endIndex = node.end;
const absoluteStart = createAbsolutePositionFromStateVector(doc, text, version, startIndex, undefined, true);
text.insert(absoluteStart.index, '`');
const absoluteEnd = createAbsolutePositionFromStateVector(doc, text, version, endIndex, undefined, true);
text.insert(absoluteEnd.index, '`');
}
_replaceJSXExpressionContainer(node, doc, text) {
const version = { '0': getClock(doc, 0) };
// Positions for the opening "{"
const valueStartBegin = createRelativePositionFromStateVector(text, version, node.start, undefined, true);
const valueStartEnd = createRelativePositionFromStateVector(text, version, node.start + 1, -1, true);
// Positions for the closing "}"
const valueEndBegin = createRelativePositionFromStateVector(text, version, node.end - 1, undefined, true);
const valueEndEnd = createRelativePositionFromStateVector(text, version, node.end + 1, -1, true);
// Delete the opening "{"
const absoluteValueStartBegin = createAbsolutePositionFromRelativePosition(valueStartBegin, doc);
const absoluteValueStartEnd = createAbsolutePositionFromRelativePosition(valueStartEnd, doc);
text.delete(absoluteValueStartBegin.index, 1);
// Delete the closing "}"
const absoluteValueEndBegin = createAbsolutePositionFromRelativePosition(valueEndBegin, doc);
const absoluteValueEndEnd = createAbsolutePositionFromRelativePosition(valueEndEnd, doc);
text.delete(absoluteValueEndBegin.index, 1);
}
_removeTag(node, openElement, closeElement, doc, text) {
var _a, _b;
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
// Save relative positions here
const openStart = createRelativePositionFromStateVector(text, version, openElement.start, undefined, true);
const openNameEnd = createRelativePositionFromStateVector(text, version, (_b = (_a = openElement.name) === null || _a === void 0 ? void 0 : _a.end) !== null && _b !== void 0 ? _b : openElement.start + 1, -1, true);
const openEnd = createRelativePositionFromStateVector(text, version, openElement.end - (closeElement ? 1 : 2), undefined, true);
const closingStart = !!closeElement
? createRelativePositionFromStateVector(text, version, closeElement.start, undefined, true)
: null;
const closingEnd = !!closeElement
? createRelativePositionFromStateVector(text, version, closeElement.end, -1, true)
: null;
let attributePositions = [];
for (let attribute of openElement.attributes) {
if (attribute.type !== 'JSXSpreadAttribute') {
const nameStart = createRelativePositionFromStateVector(text, version, attribute.name.start, undefined, true);
const nameEnd = createRelativePositionFromStateVector(text, version, !!attribute.value
? attribute.name.end + 1
: attribute.name.end, -1, true);
attributePositions.push([nameStart, nameEnd]);
}
else {
const openBraceStart = createRelativePositionFromStateVector(text, version, attribute.start, undefined, true);
const openBraceEnd = createRelativePositionFromStateVector(text, version, attribute.start + 1, undefined, true);
const closeBraceStart = createRelativePositionFromStateVector(text, version, attribute.end - 1, -1, true);
const closeBraceEnd = createRelativePositionFromStateVector(text, version, attribute.end, -1, true);
attributePositions.push([openBraceStart, openBraceEnd]);
attributePositions.push([closeBraceStart, closeBraceEnd]);
}
}
for (let [start, end] of attributePositions) {
// remove attribute name
const nameStart = createAbsolutePositionFromRelativePosition(start, doc);
const nameEnd = createAbsolutePositionFromRelativePosition(end, doc);
// remove name + "="
text.delete(nameStart.index, nameEnd.index - nameStart.index);
}
// remove open tag <
const openStartAbsolute = createAbsolutePositionFromRelativePosition(openStart, doc);
text.delete(openStartAbsolute.index, 1);
// remove open tag >
const openEndAbsolute = createAbsolutePositionFromRelativePosition(openEnd, doc);
text.delete(openEndAbsolute.index, closeElement ? 1 : 2);
if (closeElement) {
// remove closing tag
const closingStartAbsolute = createAbsolutePositionFromRelativePosition(closingStart, doc);
const closingEndAbsolute = createAbsolutePositionFromRelativePosition(closingEnd, doc);
text.delete(closingStartAbsolute.index, closingEndAbsolute.index - closingStartAbsolute.index);
}
}
_insertEnergyCheckIntoStatement(doc, text, statement) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
let startIndex;
let postfix = '';
let wrapWithBraces = false;
if (statement.type === 'BlockStatement') {
// Block statements look like this:
// while(true) { }
// Block statements are wrapped with curly braces
// so we should use a full statement "__energyCheck();"
startIndex = statement.start + 1;
postfix = ';';
}
else if (statement.type === 'ExpressionStatement') {
// Expression statements look like this:
// while(true) abc();
// therefore, we can find the start of the expression and use a comma
// between our call and the other code to make it a sequence expression.
startIndex = statement.start;
postfix = ';';
wrapWithBraces = true;
}
else if (statement.type === 'EmptyStatement') {
// Empty statements look like this:
// while(true) ;
// as a result, we only need to insert the call to convert the expression from empty to
// an expression statement containing a single function call.
startIndex = statement.start;
}
else {
// Other statements (like "if", "try", "throw" and other loops)
// should be wrapped in curly braces
startIndex = statement.start;
postfix = ';';
wrapWithBraces = true;
}
this._insertEnergyCheck(doc, text, version, startIndex, statement.end, postfix, wrapWithBraces);
}
_insertEnergyCheck(doc, text, version, startIndex, endIndex, postfix, wrapWithBraces) {
if (wrapWithBraces) {
const absolute = createAbsolutePositionFromStateVector(doc, text, version, startIndex, undefined, true);
text.insert(absolute.index, '{');
}
const absolute = createAbsolutePositionFromStateVector(doc, text, version, startIndex, undefined, true);
text.insert(absolute.index, ENERGY_CHECK_CALL + postfix);
if (wrapWithBraces) {
const absolute = createAbsolutePositionFromStateVector(doc, text, version, endIndex, undefined, true);
text.insert(absolute.index, '}');
}
}
_replaceAsyncFunction(node, doc, text) {
doc.clientID += 1;
const version = { '0': getClock(doc, 0) };
const functionStart = createRelativePositionFromStateVector(text, version, node.start, undefined, true);
const functionStartAbsolute = createAbsolutePositionFromRelativePosition(functionStart, doc);
text.delete(functionStartAbsolute.index, 'async '.length);
}
_replaceAwaitExpression(node, doc, text) {
doc.clientID += 1;