UNPKG

@casual-simulation/aux-runtime

Version:
1,052 lines (1,051 loc) 52 kB
/* 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;