@rocket.chat/apps-engine
Version:
The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.
246 lines (210 loc) • 11.4 kB
text/typescript
import { assertEquals, assertThrows } from 'https://deno.land/std@0.203.0/assert/mod.ts';
import { beforeEach, describe, it } from 'https://deno.land/std@0.203.0/testing/bdd.ts';
import { WalkerState, asyncifyScope, buildFixModifiedFunctionsOperation, checkReassignmentOfModifiedIdentifiers, getFunctionIdentifier, wrapWithAwait } from '../operations.ts';
import {
ArrowFunctionDerefCallExpression,
AssignmentExpressionOfArrowFunctionToFooIdentifier,
AssignmentExpressionOfNamedFunctionToFooMemberExpression,
AssignmentOfFooToBar,
AssignmentOfFooToBarMemberExpression,
AssignmentOfFooToBarPropertyDefinition,
AssignmentOfFooToBarVariableDeclarator,
ConstFooAssignedFunctionExpression,
FixSimpleCallExpression,
FunctionDeclarationFoo,
MethodDefinitionOfFooInClassBar,
SimpleCallExpressionOfFoo,
SyncFunctionDeclarationWithAsyncCallExpression,
} from './data/ast_blocks.ts';
import { AnyNode, ArrowFunctionExpression, AssignmentExpression, AwaitExpression, Expression, MethodDefinition, ReturnStatement, VariableDeclaration } from '../../../acorn.d.ts';
import { assertNotEquals } from 'https://deno.land/std@0.203.0/assert/assert_not_equals.ts';
describe('getFunctionIdentifier', () => {
it(`identifies the name "foo" for the code \`${FunctionDeclarationFoo.code}\``, () => {
// ancestors array is built by the walking lib
const nodeAncestors = [FunctionDeclarationFoo.node];
const functionNodeIndex = 0;
assertEquals('foo', getFunctionIdentifier(nodeAncestors, functionNodeIndex));
});
it(`identifies the name "foo" for the code \`${ConstFooAssignedFunctionExpression.code}\``, () => {
// ancestors array is built by the walking lib
const nodeAncestors = [
ConstFooAssignedFunctionExpression.node, // VariableDeclaration
ConstFooAssignedFunctionExpression.node.declarations[0], // VariableDeclarator
ConstFooAssignedFunctionExpression.node.declarations[0].init! // FunctionExpression
];
const functionNodeIndex = 2;
assertEquals('foo', getFunctionIdentifier(nodeAncestors, functionNodeIndex));
});
it(`identifies the name "foo" for the code \`${AssignmentExpressionOfArrowFunctionToFooIdentifier.code}\``, () => {
// ancestors array is built by the walking lib
const nodeAncestors = [
AssignmentExpressionOfArrowFunctionToFooIdentifier.node, // ExpressionStatement
AssignmentExpressionOfArrowFunctionToFooIdentifier.node.expression, // AssignmentExpression
(AssignmentExpressionOfArrowFunctionToFooIdentifier.node.expression as AssignmentExpression).right, // ArrowFunctionExpression
];
const functionNodeIndex = 2;
assertEquals('foo', getFunctionIdentifier(nodeAncestors, functionNodeIndex));
});
it(`identifies the name "foo" for the code \`${AssignmentExpressionOfNamedFunctionToFooMemberExpression.code}\``, () => {
// ancestors array is built by the walking lib
const nodeAncestors = [
AssignmentExpressionOfNamedFunctionToFooMemberExpression.node, // ExpressionStatement
AssignmentExpressionOfNamedFunctionToFooMemberExpression.node.expression, // AssignmentExpression
(AssignmentExpressionOfNamedFunctionToFooMemberExpression.node.expression as AssignmentExpression).right, // FunctionExpression
];
const functionNodeIndex = 2;
assertEquals('foo', getFunctionIdentifier(nodeAncestors, functionNodeIndex));
});
it(`identifies the name "foo" for the code \`${MethodDefinitionOfFooInClassBar.code}\``, () => {
// ancestors array is built by the walking lib
const nodeAncestors = [
MethodDefinitionOfFooInClassBar.node, // ClassDeclaration
MethodDefinitionOfFooInClassBar.node.body, // ClassBody
MethodDefinitionOfFooInClassBar.node.body!.body[0], // MethodDefinition
(MethodDefinitionOfFooInClassBar.node.body!.body[0] as MethodDefinition).value, // FunctionExpression
];
const functionNodeIndex = 3;
assertEquals('foo', getFunctionIdentifier(nodeAncestors, functionNodeIndex));
});
});
describe('wrapWithAwait', () => {
it('wraps a call expression with await', () => {
const node = structuredClone(SimpleCallExpressionOfFoo.node.expression);
wrapWithAwait(node);
assertEquals('AwaitExpression', node.type);
assertNotEquals(SimpleCallExpressionOfFoo.node.expression.type, node.type);
assertEquals(SimpleCallExpressionOfFoo.node.expression, (node as AwaitExpression).argument);
});
it('throws if node is not an expression', () => {
const node = structuredClone(SimpleCallExpressionOfFoo.node);
assertThrows(() => wrapWithAwait(node as unknown as Expression));
})
});
describe('asyncifyScope', () => {
it('makes only the first function scope async', () => {
const node = structuredClone(SyncFunctionDeclarationWithAsyncCallExpression.node);
const ancestors: AnyNode[] = [
node, // FunctionDeclaration
node.body, // BlockStatement
node.body!.body[0], // ReturnStatement
(node.body!.body[0] as ReturnStatement).argument!, // ArrowFunctionExpression
((node.body!.body[0] as ReturnStatement).argument! as ArrowFunctionExpression).body, // AwaitExpression
(((node.body!.body[0] as ReturnStatement).argument! as ArrowFunctionExpression).body as AwaitExpression).argument, // CallExpression
];
const state: WalkerState = {
isModified: false,
functionIdentifiers: new Set(),
}
asyncifyScope(ancestors, state);
// Assert the function did indeed change the expression to async
assertEquals(((node.body.body[0] as ReturnStatement).argument as ArrowFunctionExpression).async, true)
// Assert the function did NOT change all ancestors in the chain
assertEquals(node.async, false);
// Assert it couldn't find a function identifier
assertEquals(state.functionIdentifiers.size, 0);
});
});
describe('checkReassignmentofModifiedIdentifiers', () => {
it(`identifies the reassignment of "foo" in the code "${AssignmentOfFooToBar.code}"`, () => {
const node = structuredClone(AssignmentOfFooToBar.node);
const ancestors: AnyNode[] = [
node, // ExpressionStatement
node.expression, // AssignmentExpression
(node.expression as AssignmentExpression).right, // Identifier
];
const state: WalkerState = {
isModified: true,
functionIdentifiers: new Set(['foo']),
}
checkReassignmentOfModifiedIdentifiers(node.expression, state, ancestors, '');
assertEquals(state.functionIdentifiers.has('bar'), true);
});
it(`identifies the reassignment of "foo" in the code "${AssignmentOfFooToBarMemberExpression.code}"`, () => {
const node = structuredClone(AssignmentOfFooToBarMemberExpression.node);
const ancestors: AnyNode[] = [
node, // ExpressionStatement
node.expression, // AssignmentExpression
(node.expression as AssignmentExpression).right, // Identifier
];
const state: WalkerState = {
isModified: true,
functionIdentifiers: new Set(['foo']),
}
checkReassignmentOfModifiedIdentifiers(node.expression, state, ancestors, '');
assertEquals(state.functionIdentifiers.has('bar'), true);
});
it(`identifies the reassignment of "foo" in the code "${AssignmentOfFooToBarVariableDeclarator.code}"`, () => {
const node = structuredClone(AssignmentOfFooToBarVariableDeclarator.node);
const ancestors: AnyNode[] = [
node, // VariableDeclaration
node.declarations[0], // VariableDeclarator
];
const state: WalkerState = {
isModified: true,
functionIdentifiers: new Set(['foo']),
}
checkReassignmentOfModifiedIdentifiers(node.declarations[0], state, ancestors, '');
assertEquals(state.functionIdentifiers.has('bar'), true);
});
it(`identifies the reassignment of "foo" in the code "${AssignmentOfFooToBarPropertyDefinition.code}"`, () => {
const node = structuredClone(AssignmentOfFooToBarPropertyDefinition.node);
const ancestors: AnyNode[] = [
node, // ClassDeclaration
node.body, // ClassBody
node.body.body[0], // PropertyDefinition
];
const state: WalkerState = {
isModified: true,
functionIdentifiers: new Set(['foo']),
}
checkReassignmentOfModifiedIdentifiers(node.body.body[0], state, ancestors, '');
assertEquals(state.functionIdentifiers.has('bar'), true);
});
});
describe('buildFixModifiedFunctionsOperation', function() {
const state: WalkerState = {
isModified: false,
functionIdentifiers: new Set(['foo']),
};
const fixFunction = buildFixModifiedFunctionsOperation(state.functionIdentifiers);
beforeEach(() => {
state.isModified = false;
state.functionIdentifiers = new Set(['foo']);
});
it(`fixes calls of "foo" in the code "${FixSimpleCallExpression.code}"`, () => {
const node = structuredClone(FixSimpleCallExpression.node);
const ancestors: AnyNode[] = [
node, // FunctionDeclaration
node.body, // BlockStatement
node.body.body[0], // VariableDeclaration
(node.body.body[0] as VariableDeclaration).declarations[0], // VariableDeclarator
(node.body.body[0] as VariableDeclaration).declarations[0].init!, // CallExpression
];
fixFunction(ancestors[4], state, ancestors, '');
assertEquals(state.isModified, true);
assertEquals(state.functionIdentifiers.has('bar'), true);
assertNotEquals(FixSimpleCallExpression.node, node);
assertEquals(node.async, true);
assertEquals(ancestors[4].type, 'AwaitExpression');
});
it(`fixes calls of "foo" in the code "${ArrowFunctionDerefCallExpression.code}"`, () => {
const node = structuredClone(ArrowFunctionDerefCallExpression.node);
const ancestors: AnyNode[] = [
node, // VariableDeclaration
node.declarations[0], // VariableDeclarator
node.declarations[0].init!, // ArrowFunctionExpression
(node.declarations[0].init as ArrowFunctionExpression).body, // CallExpression
];
fixFunction(ancestors[3], state, ancestors, '');
// Recorded that a modification has been made
assertEquals(state.isModified, true);
// Recorded that the enclosing scope of the call also requires fixing
assertEquals(state.functionIdentifiers.has('bar'), true);
// Original node and fixed node are different
assertNotEquals(ArrowFunctionDerefCallExpression.node, node);
// The function call is now await'ed
assertEquals(ancestors[3].type, 'AwaitExpression');
// The parent function of the call is now marked as async
assertEquals((ancestors[2] as ArrowFunctionExpression).async, true);
});
})