Fix compiler bug with nested optional arguments
This commit fixes compiler bug where it fails when optional values are compiled into absent values in nested calls. - Throw exception with more context for easier future debugging. - Add better validation of argument values for nested calls. - Refactor `FunctionCallCompiler` for better clarity and modularize it to make it more maintainable and testable. - Refactor related interface to not have `I` prefix, and function/variable names for better clarity. Context: Discovered this issue while attempting to call `RunInlineCodeAsTrustedInstaller` which in turn invokes `RunPowerShell` for issue #246. This led to the realization that despite parameters flagged as optional, the nested argument compilation didn't support them.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { NewlineCodeSegmentMerger } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CodeSegmentJoin/NewlineCodeSegmentMerger';
|
||||
import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
|
||||
import { getAbsentStringTestCases, itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('NewlineCodeSegmentMerger', () => {
|
||||
describe('mergeCodeParts', () => {
|
||||
describe('throws given empty segments', () => {
|
||||
itEachAbsentCollectionValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing segments';
|
||||
const segments = absentValue;
|
||||
const merger = new NewlineCodeSegmentMerger();
|
||||
// act
|
||||
const act = () => merger.mergeCodeParts(segments);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('merges correctly', () => {
|
||||
const testCases: ReadonlyArray<{
|
||||
readonly description: string,
|
||||
readonly segments: CompiledCodeStub[],
|
||||
readonly expected: {
|
||||
readonly code: string,
|
||||
readonly revertCode?: string,
|
||||
},
|
||||
}> = [
|
||||
{
|
||||
description: 'given `code` and `revertCode`',
|
||||
segments: [
|
||||
new CompiledCodeStub().withCode('code1').withRevertCode('revert1'),
|
||||
new CompiledCodeStub().withCode('code2').withRevertCode('revert2'),
|
||||
new CompiledCodeStub().withCode('code3').withRevertCode('revert3'),
|
||||
],
|
||||
expected: {
|
||||
code: 'code1\ncode2\ncode3',
|
||||
revertCode: 'revert1\nrevert2\nrevert3',
|
||||
},
|
||||
},
|
||||
...getAbsentStringTestCases().map((absentTestCase) => ({
|
||||
description: `filter out ${absentTestCase.valueName} \`revertCode\``,
|
||||
segments: [
|
||||
new CompiledCodeStub().withCode('code1').withRevertCode('revert1'),
|
||||
new CompiledCodeStub().withCode('code2').withRevertCode(absentTestCase.absentValue),
|
||||
new CompiledCodeStub().withCode('code3').withRevertCode('revert3'),
|
||||
],
|
||||
expected: {
|
||||
code: 'code1\ncode2\ncode3',
|
||||
revertCode: 'revert1\nrevert3',
|
||||
},
|
||||
})),
|
||||
{
|
||||
description: 'given only `code` in segments',
|
||||
segments: [
|
||||
new CompiledCodeStub().withCode('code1').withRevertCode(''),
|
||||
new CompiledCodeStub().withCode('code2').withRevertCode(''),
|
||||
],
|
||||
expected: {
|
||||
code: 'code1\ncode2',
|
||||
revertCode: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'given mix of segments with only `code` or `revertCode`',
|
||||
segments: [
|
||||
new CompiledCodeStub().withCode('code1').withRevertCode(''),
|
||||
new CompiledCodeStub().withCode('').withRevertCode('revert2'),
|
||||
new CompiledCodeStub().withCode('code3').withRevertCode(''),
|
||||
],
|
||||
expected: {
|
||||
code: 'code1\ncode3',
|
||||
revertCode: 'revert2',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'given only `revertCode` in segments',
|
||||
segments: [
|
||||
new CompiledCodeStub().withCode('').withRevertCode('revert1'),
|
||||
new CompiledCodeStub().withCode('').withRevertCode('revert2'),
|
||||
],
|
||||
expected: {
|
||||
code: '',
|
||||
revertCode: 'revert1\nrevert2',
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const { segments, expected, description } of testCases) {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const merger = new NewlineCodeSegmentMerger();
|
||||
// act
|
||||
const actual = merger.mergeCodeParts(segments);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revertCode);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,522 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { FunctionCallParametersData } from '@/application/collections/';
|
||||
import { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
|
||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { SharedFunctionStub } from '@tests/unit/shared/Stubs/SharedFunctionStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
|
||||
describe('FunctionCallCompiler', () => {
|
||||
describe('instance', () => {
|
||||
itIsSingleton({
|
||||
getter: () => FunctionCallCompiler.instance,
|
||||
expectedType: FunctionCallCompiler,
|
||||
});
|
||||
});
|
||||
describe('compileCall', () => {
|
||||
describe('parameter validation', () => {
|
||||
describe('call', () => {
|
||||
describe('throws with missing call', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing calls';
|
||||
const call = absentValue;
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws if call sequence has absent call', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing function call';
|
||||
const call = [
|
||||
new FunctionCallStub(),
|
||||
absentValue,
|
||||
];
|
||||
const functions = new SharedFunctionCollectionStub();
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws if call parameters does not match function parameters', () => {
|
||||
// arrange
|
||||
const functionName = 'test-function-name';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'provided: single unexpected parameter, when: another expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
name: 'provided: multiple unexpected parameters, when: different one is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter1', 'unexpected-parameter2'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
|
||||
functionParameters: ['expected-parameter1', 'expected-parameter2'],
|
||||
callParameters: ['expected-parameter1', 'expected-parameter2', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter1", "expected-parameter2"',
|
||||
},
|
||||
{
|
||||
name: 'provided: an unexpected parameter, when: none required',
|
||||
functionParameters: [],
|
||||
callParameters: ['unexpected-call-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"`
|
||||
+ '. Expected parameter(s): none',
|
||||
},
|
||||
{
|
||||
name: 'provided: expected and unexpected parameter, when: one of them is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['expected-parameter', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('test-function-name')
|
||||
.withParameterNames(...testCase.functionParameters);
|
||||
const params = testCase.callParameters
|
||||
.reduce((result, parameter) => {
|
||||
return { ...result, [parameter]: 'defined-parameter-value ' };
|
||||
}, {} as FunctionCallParametersData);
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(params);
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(func);
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
describe('throws with missing functions', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing functions';
|
||||
const call = new FunctionCallStub();
|
||||
const functions = absentValue;
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('throws if function does not exist', () => {
|
||||
// arrange
|
||||
const expectedError = 'function does not exist';
|
||||
const call = new FunctionCallStub();
|
||||
const functions: ISharedFunctionCollection = {
|
||||
getFunctionByName: () => { throw new Error(expectedError); },
|
||||
};
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
describe('builds single call as expected', () => {
|
||||
// arrange
|
||||
const parametersTestCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
parameters: [],
|
||||
callArgs: { },
|
||||
},
|
||||
{
|
||||
name: 'non-empty parameters',
|
||||
parameters: ['param1', 'param2'],
|
||||
callArgs: { param1: 'value1', param2: 'value2' },
|
||||
},
|
||||
];
|
||||
for (const testCase of parametersTestCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = {
|
||||
execute: 'expected code (execute)',
|
||||
revert: 'expected code (revert)',
|
||||
};
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withParameterNames(...testCase.parameters);
|
||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(testCase.callArgs);
|
||||
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||
const { code } = func.body;
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ givenCode: code.execute, givenArgs: args, result: expected.execute })
|
||||
.setup({ givenCode: code.revert, givenArgs: args, result: expected.revert });
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall([call], functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.execute);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('builds call sequence as expected', () => {
|
||||
// arrange
|
||||
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('first-function-name')
|
||||
.withCode('first-function-code')
|
||||
.withRevertCode('first-function-revert-code');
|
||||
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('second-function-name')
|
||||
.withParameterNames('testParameter')
|
||||
.withCode('second-function-code')
|
||||
.withRevertCode('second-function-revert-code');
|
||||
const secondCallArguments = { testParameter: 'testValue' };
|
||||
const calls = [
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(firstFunction.name)
|
||||
.withArguments({}),
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(secondFunction.name)
|
||||
.withArguments(secondCallArguments),
|
||||
];
|
||||
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(secondCallArguments);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
|
||||
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
|
||||
const expectedExecute = `${firstFunction.body.code.execute}\n${secondFunction.body.code.execute}`;
|
||||
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(firstFunction)
|
||||
.withFunction(secondFunction);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(calls, functions);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expectedExecute);
|
||||
expect(actual.revertCode).to.equal(expectedRevert);
|
||||
});
|
||||
describe('can compile a call tree (function calling another)', () => {
|
||||
describe('single deep function call', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const deepFunctionName = 'deepFunction';
|
||||
const functions = {
|
||||
deep: new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(deepFunctionName)
|
||||
.withCode('deep function code')
|
||||
.withRevertCode('deep function final code'),
|
||||
front: new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(deepFunctionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.execute,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: functions.deep.body.code.revert,
|
||||
givenArgs: emptyArgs,
|
||||
result: expected.revert,
|
||||
});
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(functions.front.name)
|
||||
.withArgumentCollection(emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 2nd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
front: {
|
||||
functionName: 'frontFunction',
|
||||
parameterName: 'frontFunctionParameterName',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "front" to "deep" in function definition',
|
||||
toNextCompiled: 'argument from "front" to "deep" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
|
||||
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.front.functionName)
|
||||
.withParameterNames(scenario.front.parameterName)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.deep.functionName)
|
||||
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic)),
|
||||
},
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
parameterName: 'deepFunctionParameterName',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.deep.functionName)
|
||||
.withParameterNames(scenario.deep.parameterName)
|
||||
.withCode(`${scenario.deep.functionName} function code`)
|
||||
.withRevertCode(`${scenario.deep.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // Front ===args===> Deep
|
||||
givenCode: scenario.front.args.toNextStatic,
|
||||
givenArgs: scenario.front.callArgs.initialFromMainCall(),
|
||||
result: scenario.front.args.toNextCompiled,
|
||||
})
|
||||
// set-up compiling of deep, compiled argument should be sent
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.execute,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.deep.getFunction().body.code.revert,
|
||||
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(scenario.front.functionName)
|
||||
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
|
||||
],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.deep.getFunction(),
|
||||
scenario.front.getFunction(),
|
||||
),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
it('builds 3rd level of depth by compiling arguments', () => {
|
||||
// arrange
|
||||
const scenario = {
|
||||
first: {
|
||||
functionName: 'firstFunction',
|
||||
parameter: 'firstParameter',
|
||||
args: {
|
||||
fromMainCall: 'initial argument to be compiled',
|
||||
toNextStatic: 'value from "first" to "second" in function definition',
|
||||
toNextCompiled: 'argument from "first" to "second" (compiled)',
|
||||
},
|
||||
callArgs: {
|
||||
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
|
||||
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.first.functionName)
|
||||
.withParameterNames(scenario.first.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.second.functionName)
|
||||
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic)),
|
||||
},
|
||||
second: {
|
||||
functionName: 'secondFunction',
|
||||
parameter: 'secondParameter',
|
||||
args: {
|
||||
toNextCompiled: 'argument second to third (compiled)',
|
||||
toNextStatic: 'calling second to third',
|
||||
},
|
||||
callArgs: {
|
||||
expectedToThird: () => new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
|
||||
},
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName(scenario.second.functionName)
|
||||
.withParameterNames(scenario.second.parameter)
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(scenario.third.functionName)
|
||||
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic)),
|
||||
},
|
||||
third: {
|
||||
functionName: 'thirdFunction',
|
||||
parameter: 'thirdParameter',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(scenario.third.functionName)
|
||||
.withParameterNames(scenario.third.parameter)
|
||||
.withCode(`${scenario.third.functionName} function code`)
|
||||
.withRevertCode(`${scenario.third.functionName} function revert code`),
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
code: 'final code',
|
||||
revert: 'final revert code',
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup({ // First ===args===> Second
|
||||
givenCode: scenario.first.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.initialFromMainCall(),
|
||||
result: scenario.first.args.toNextCompiled,
|
||||
})
|
||||
.setup({ // Second ===args===> third
|
||||
givenCode: scenario.second.args.toNextStatic,
|
||||
givenArgs: scenario.first.callArgs.expectedToSecond(),
|
||||
result: scenario.second.args.toNextCompiled,
|
||||
})
|
||||
// Compiling of third functions code with expected arguments
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.execute,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.code,
|
||||
})
|
||||
.setup({
|
||||
givenCode: scenario.third.getFunction().body.code.revert,
|
||||
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||
result: expected.revert,
|
||||
});
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const mainCall = new FunctionCallStub()
|
||||
.withFunctionName(scenario.first.functionName)
|
||||
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
[mainCall],
|
||||
new SharedFunctionCollectionStub().withFunction(
|
||||
scenario.first.getFunction(),
|
||||
scenario.second.getFunction(),
|
||||
scenario.third.getFunction(),
|
||||
),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
describe('multiple deep function calls', () => {
|
||||
it('builds 2nd level of depth without arguments', () => {
|
||||
// arrange
|
||||
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||
const functions = {
|
||||
call1: {
|
||||
deep: {
|
||||
functionName: 'deepFunction',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call1.deep.functionName)
|
||||
.withCode('deep function (1) code')
|
||||
.withRevertCode('deep function (1) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
},
|
||||
},
|
||||
call2: {
|
||||
deep: {
|
||||
functionName: 'deepFunction2',
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(functions.call2.deep.functionName)
|
||||
.withCode('deep function (2) code')
|
||||
.withRevertCode('deep function (2) final code'),
|
||||
},
|
||||
front: {
|
||||
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withName('frontFunction2')
|
||||
.withCalls(new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.deep.functionName)
|
||||
.withArgumentCollection(emptyArgs)),
|
||||
},
|
||||
},
|
||||
getMainCall: () => [
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call1.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
new FunctionCallStub()
|
||||
.withFunctionName(functions.call2.front.getFunction().name)
|
||||
.withArgumentCollection(emptyArgs),
|
||||
],
|
||||
getCollection: () => new SharedFunctionCollectionStub().withFunction(
|
||||
functions.call1.deep.getFunction(),
|
||||
functions.call1.front.getFunction(),
|
||||
functions.call2.deep.getFunction(),
|
||||
functions.call2.front.getFunction(),
|
||||
),
|
||||
};
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
|
||||
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
const expected = {
|
||||
code: `${functions.call1.deep.getFunction().body.code.execute}\n${functions.call2.deep.getFunction().body.code.execute}`,
|
||||
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
|
||||
};
|
||||
// act
|
||||
const actual = sut.compileCall(
|
||||
functions.getMainCall(),
|
||||
functions.getCollection(),
|
||||
);
|
||||
// assert
|
||||
expect(actual.code).to.equal(expected.code);
|
||||
expect(actual.revertCode).to.equal(expected.revert);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MockableFunctionCallCompiler extends FunctionCallCompiler {
|
||||
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
|
||||
super(expressionsCompiler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { FunctionCallSequenceCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallSequenceCompiler';
|
||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { SingleCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/SingleCallCompiler';
|
||||
import { CodeSegmentMerger } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CodeSegmentJoin/CodeSegmentMerger';
|
||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
||||
import { SingleCallCompilerStub } from '@tests/unit/shared/Stubs/SingleCallCompilerStub';
|
||||
import { CodeSegmentMergerStub } from '@tests/unit/shared/Stubs/CodeSegmentMergerStub';
|
||||
import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
|
||||
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||
|
||||
describe('FunctionCallSequenceCompiler', () => {
|
||||
describe('instance', () => {
|
||||
itIsSingleton({
|
||||
getter: () => FunctionCallSequenceCompiler.instance,
|
||||
expectedType: FunctionCallSequenceCompiler,
|
||||
});
|
||||
});
|
||||
describe('compileFunctionCalls', () => {
|
||||
describe('parameter validation', () => {
|
||||
describe('calls', () => {
|
||||
describe('throws with missing call', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing calls';
|
||||
const calls = absentValue;
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withCalls(calls);
|
||||
// act
|
||||
const act = () => builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws if call sequence has absent call', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing function call';
|
||||
const calls = [
|
||||
new FunctionCallStub(),
|
||||
absentValue,
|
||||
];
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withCalls(calls);
|
||||
// act
|
||||
const act = () => builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('functions', () => {
|
||||
describe('throws with missing functions', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const expectedError = 'missing functions';
|
||||
const functions = absentValue;
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withFunctions(functions);
|
||||
// act
|
||||
const act = () => builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('invokes single call compiler correctly', () => {
|
||||
describe('calls', () => {
|
||||
it('with expected call', () => {
|
||||
// arrange
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const expectedCall = new FunctionCallStub();
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withSingleCallCompiler(singleCallCompilerStub)
|
||||
.withCalls([expectedCall]);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(singleCallCompilerStub.callHistory).to.have.lengthOf(1);
|
||||
const calledMethod = singleCallCompilerStub.callHistory.find((m) => m.methodName === 'compileSingleCall');
|
||||
expect(calledMethod).toBeDefined();
|
||||
expect(calledMethod.args[0]).to.equal(expectedCall);
|
||||
});
|
||||
it('with every call', () => {
|
||||
// arrange
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const expectedCalls = [
|
||||
new FunctionCallStub(), new FunctionCallStub(), new FunctionCallStub(),
|
||||
];
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withSingleCallCompiler(singleCallCompilerStub)
|
||||
.withCalls(expectedCalls);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
const calledMethods = singleCallCompilerStub.callHistory.filter((m) => m.methodName === 'compileSingleCall');
|
||||
expect(calledMethods).to.have.lengthOf(expectedCalls.length);
|
||||
const callArguments = calledMethods.map((c) => c.args[0]);
|
||||
expect(expectedCalls).to.have.members(callArguments);
|
||||
});
|
||||
});
|
||||
describe('context', () => {
|
||||
it('with expected functions', () => {
|
||||
// arrange
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const expectedFunctions = new SharedFunctionCollectionStub();
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withSingleCallCompiler(singleCallCompilerStub)
|
||||
.withFunctions(expectedFunctions);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(singleCallCompilerStub.callHistory).to.have.lengthOf(1);
|
||||
const calledMethod = singleCallCompilerStub.callHistory.find((m) => m.methodName === 'compileSingleCall');
|
||||
expect(calledMethod).toBeDefined();
|
||||
const actualFunctions = calledMethod.args[1].allFunctions;
|
||||
expect(actualFunctions).to.equal(expectedFunctions);
|
||||
});
|
||||
it('with expected call sequence', () => {
|
||||
// arrange
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const expectedCallSequence = [new FunctionCallStub(), new FunctionCallStub()];
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withSingleCallCompiler(singleCallCompilerStub)
|
||||
.withCalls(expectedCallSequence);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
const calledMethods = singleCallCompilerStub.callHistory.filter((m) => m.methodName === 'compileSingleCall');
|
||||
expect(calledMethods).to.have.lengthOf(expectedCallSequence.length);
|
||||
const calledSequenceArgs = calledMethods.map((call) => call.args[1].rootCallSequence);
|
||||
expect(calledSequenceArgs.every((sequence) => sequence === expectedCallSequence));
|
||||
});
|
||||
it('with expected call compiler', () => {
|
||||
// arrange
|
||||
const expectedCompiler = new SingleCallCompilerStub();
|
||||
const rootCallSequence = [new FunctionCallStub(), new FunctionCallStub()];
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withCalls(rootCallSequence)
|
||||
.withSingleCallCompiler(expectedCompiler);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
const calledMethods = expectedCompiler.callHistory.filter((m) => m.methodName === 'compileSingleCall');
|
||||
expect(calledMethods).to.have.lengthOf(rootCallSequence.length);
|
||||
const compilerArgs = calledMethods.map((call) => call.args[1].singleCallCompiler);
|
||||
expect(compilerArgs.every((compiler) => compiler === expectedCompiler));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('code segment merger', () => {
|
||||
it('invokes code segment merger correctly', () => {
|
||||
// arrange
|
||||
const singleCallCompilationScenario = new Map<FunctionCall, CompiledCode[]>([
|
||||
[new FunctionCallStub(), [new CompiledCodeStub()]],
|
||||
[new FunctionCallStub(), [new CompiledCodeStub(), new CompiledCodeStub()]],
|
||||
]);
|
||||
const expectedFlattenedSegments = [...singleCallCompilationScenario.values()].flat();
|
||||
const calls = [...singleCallCompilationScenario.keys()];
|
||||
const singleCallCompiler = new SingleCallCompilerStub()
|
||||
.withCallCompilationScenarios(singleCallCompilationScenario);
|
||||
const codeSegmentMergerStub = new CodeSegmentMergerStub();
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withCalls(calls)
|
||||
.withSingleCallCompiler(singleCallCompiler)
|
||||
.withCodeSegmentMerger(codeSegmentMergerStub);
|
||||
// act
|
||||
builder.compileFunctionCalls();
|
||||
// assert
|
||||
const [actualSegments] = codeSegmentMergerStub.callHistory.find((c) => c.methodName === 'mergeCodeParts').args;
|
||||
expect(expectedFlattenedSegments).to.have.lengthOf(actualSegments.length);
|
||||
expect(expectedFlattenedSegments).to.have.deep.members(actualSegments);
|
||||
});
|
||||
it('returns code segment merger result', () => {
|
||||
// arrange
|
||||
const expectedResult = new CompiledCodeStub();
|
||||
const codeSegmentMergerStub = new CodeSegmentMergerStub();
|
||||
codeSegmentMergerStub.mergeCodeParts = () => expectedResult;
|
||||
const builder = new FunctionCallSequenceCompilerBuilder()
|
||||
.withCodeSegmentMerger(codeSegmentMergerStub);
|
||||
// act
|
||||
const actualResult = builder.compileFunctionCalls();
|
||||
// assert
|
||||
expect(actualResult).to.equal(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallSequenceCompilerBuilder {
|
||||
private singleCallCompiler: SingleCallCompiler = new SingleCallCompilerStub();
|
||||
|
||||
private codeSegmentMerger: CodeSegmentMerger = new CodeSegmentMergerStub();
|
||||
|
||||
private functions: ISharedFunctionCollection = new SharedFunctionCollectionStub();
|
||||
|
||||
private calls: readonly FunctionCall[] = [
|
||||
new FunctionCallStub(),
|
||||
];
|
||||
|
||||
public withSingleCallCompiler(compiler: SingleCallCompiler): this {
|
||||
this.singleCallCompiler = compiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withCodeSegmentMerger(merger: CodeSegmentMerger): this {
|
||||
this.codeSegmentMerger = merger;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withCalls(calls: readonly FunctionCall[]): this {
|
||||
this.calls = calls;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctions(functions: ISharedFunctionCollection): this {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public compileFunctionCalls() {
|
||||
const compiler = new TestableFunctionCallSequenceCompiler({
|
||||
singleCallCompiler: this.singleCallCompiler,
|
||||
codeSegmentMerger: this.codeSegmentMerger,
|
||||
});
|
||||
return compiler.compileFunctionCalls(
|
||||
this.calls,
|
||||
this.functions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface FunctionCallSequenceCompilerStubs {
|
||||
readonly singleCallCompiler?: SingleCallCompiler;
|
||||
readonly codeSegmentMerger: CodeSegmentMerger;
|
||||
}
|
||||
|
||||
class TestableFunctionCallSequenceCompiler extends FunctionCallSequenceCompiler {
|
||||
public constructor(options: FunctionCallSequenceCompilerStubs) {
|
||||
super(
|
||||
options.singleCallCompiler,
|
||||
options.codeSegmentMerger,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { SharedFunctionStub } from '@tests/unit/shared/Stubs/SharedFunctionStub';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { NestedFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/NestedFunctionCallCompiler';
|
||||
import { ArgumentCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/ArgumentCompiler';
|
||||
import { ArgumentCompilerStub } from '@tests/unit/shared/Stubs/ArgumentCompilerStub';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { FunctionCallCompilationContextStub } from '@tests/unit/shared/Stubs/FunctionCallCompilationContextStub';
|
||||
import { SingleCallCompilerStub } from '@tests/unit/shared/Stubs/SingleCallCompilerStub';
|
||||
import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||
import { expectDeepThrowsError } from '@tests/unit/shared/Assertions/ExpectDeepThrowsError';
|
||||
|
||||
describe('NestedFunctionCallCompiler', () => {
|
||||
describe('canCompile', () => {
|
||||
it('returns `true` for code body function', () => {
|
||||
// arrange
|
||||
const expected = true;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withSomeCalls();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
const actual = compiler.canCompile(func);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('returns `false` for non-code body function', () => {
|
||||
// arrange
|
||||
const expected = false;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
const actual = compiler.canCompile(func);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
describe('argument compilation', () => {
|
||||
it('uses correct context', () => {
|
||||
// arrange
|
||||
const argumentCompiler = new ArgumentCompilerStub();
|
||||
const expectedContext = new FunctionCallCompilationContextStub();
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompiler)
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
|
||||
// assert
|
||||
const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
|
||||
expect(calls).have.lengthOf(1);
|
||||
const [,,actualContext] = calls[0].args;
|
||||
expect(actualContext).to.equal(expectedContext);
|
||||
});
|
||||
it('uses correct parent call', () => {
|
||||
// arrange
|
||||
const argumentCompiler = new ArgumentCompilerStub();
|
||||
const expectedContext = new FunctionCallCompilationContextStub();
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompiler)
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
|
||||
// assert
|
||||
const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
|
||||
expect(calls).have.lengthOf(1);
|
||||
const [,actualParentCall] = calls[0].args;
|
||||
expect(actualParentCall).to.equal(callToFrontFunc);
|
||||
});
|
||||
it('uses correct nested call', () => {
|
||||
// arrange
|
||||
const argumentCompiler = new ArgumentCompilerStub();
|
||||
const expectedContext = new FunctionCallCompilationContextStub();
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompiler)
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
|
||||
// assert
|
||||
const calls = argumentCompiler.callHistory.filter((call) => call.methodName === 'createCompiledNestedCall');
|
||||
expect(calls).have.lengthOf(1);
|
||||
const [actualNestedCall] = calls[0].args;
|
||||
expect(actualNestedCall).to.deep.equal(callToFrontFunc);
|
||||
});
|
||||
});
|
||||
describe('re-compilation with compiled args', () => {
|
||||
it('uses correct context', () => {
|
||||
// arrange
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const expectedContext = new FunctionCallCompilationContextStub()
|
||||
.withSingleCallCompiler(singleCallCompilerStub);
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
|
||||
// assert
|
||||
const calls = singleCallCompilerStub.callHistory.filter((call) => call.methodName === 'compileSingleCall');
|
||||
expect(calls).have.lengthOf(1);
|
||||
const [,actualContext] = calls[0].args;
|
||||
expect(expectedContext).to.equal(actualContext);
|
||||
});
|
||||
it('uses compiled nested call', () => {
|
||||
// arrange
|
||||
const expectedCall = new FunctionCallStub();
|
||||
const argumentCompilerStub = new ArgumentCompilerStub();
|
||||
argumentCompilerStub.createCompiledNestedCall = () => expectedCall;
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub();
|
||||
const context = new FunctionCallCompilationContextStub()
|
||||
.withSingleCallCompiler(singleCallCompilerStub);
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompilerStub)
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(frontFunc, callToFrontFunc, context);
|
||||
// assert
|
||||
const calls = singleCallCompilerStub.callHistory.filter((call) => call.methodName === 'compileSingleCall');
|
||||
expect(calls).have.lengthOf(1);
|
||||
const [actualNestedCall] = calls[0].args;
|
||||
expect(expectedCall).to.equal(actualNestedCall);
|
||||
});
|
||||
});
|
||||
it('flattens re-compiled functions', () => {
|
||||
// arrange
|
||||
const deepFunc1 = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const deepFunc2 = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const callToDeepFunc1 = new FunctionCallStub().withFunctionName(deepFunc1.name);
|
||||
const callToDeepFunc2 = new FunctionCallStub().withFunctionName(deepFunc2.name);
|
||||
const singleCallCompilationScenario = new Map<FunctionCall, CompiledCode[]>([
|
||||
[callToDeepFunc1, [new CompiledCodeStub()]],
|
||||
[callToDeepFunc2, [new CompiledCodeStub(), new CompiledCodeStub()]],
|
||||
]);
|
||||
const argumentCompiler = new ArgumentCompilerStub()
|
||||
.withScenario({ givenNestedFunctionCall: callToDeepFunc1, result: callToDeepFunc1 })
|
||||
.withScenario({ givenNestedFunctionCall: callToDeepFunc2, result: callToDeepFunc2 });
|
||||
const expectedFlattenedCodes = [...singleCallCompilationScenario.values()].flat();
|
||||
const frontFunc = new SharedFunctionStub(FunctionBodyType.Calls)
|
||||
.withCalls(callToDeepFunc1, callToDeepFunc2);
|
||||
const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunc.name);
|
||||
const singleCallCompilerStub = new SingleCallCompilerStub()
|
||||
.withCallCompilationScenarios(singleCallCompilationScenario);
|
||||
const expectedContext = new FunctionCallCompilationContextStub()
|
||||
.withSingleCallCompiler(singleCallCompilerStub);
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompiler)
|
||||
.build();
|
||||
// act
|
||||
const actualCodes = compiler.compileFunction(frontFunc, callToFrontFunc, expectedContext);
|
||||
// assert
|
||||
expect(actualCodes).have.lengthOf(expectedFlattenedCodes.length);
|
||||
expect(actualCodes).to.have.members(expectedFlattenedCodes);
|
||||
});
|
||||
describe('error handling', () => {
|
||||
it('handles argument compiler errors', () => {
|
||||
// arrange
|
||||
const argumentCompilerError = new Error('Test error');
|
||||
const argumentCompilerStub = new ArgumentCompilerStub();
|
||||
argumentCompilerStub.createCompiledNestedCall = () => {
|
||||
throw argumentCompilerError;
|
||||
};
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const expectedError = new AggregateError(
|
||||
[argumentCompilerError],
|
||||
`Error with call to "${callToFrontFunc.functionName}" function from "${callToFrontFunc.functionName}" function`,
|
||||
);
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.withArgumentCompiler(argumentCompilerStub)
|
||||
.build();
|
||||
// act
|
||||
const act = () => compiler.compileFunction(
|
||||
frontFunc,
|
||||
callToFrontFunc,
|
||||
new FunctionCallCompilationContextStub(),
|
||||
);
|
||||
// assert
|
||||
expectDeepThrowsError(act, expectedError);
|
||||
});
|
||||
it('handles single call compiler errors', () => {
|
||||
// arrange
|
||||
const singleCallCompilerError = new Error('Test error');
|
||||
const singleCallCompiler = new SingleCallCompilerStub();
|
||||
singleCallCompiler.compileSingleCall = () => {
|
||||
throw singleCallCompilerError;
|
||||
};
|
||||
const context = new FunctionCallCompilationContextStub()
|
||||
.withSingleCallCompiler(singleCallCompiler);
|
||||
const { frontFunc, callToFrontFunc } = createSingleFuncCallingAnotherFunc();
|
||||
const expectedError = new AggregateError(
|
||||
[singleCallCompilerError],
|
||||
`Error with call to "${callToFrontFunc.functionName}" function from "${callToFrontFunc.functionName}" function`,
|
||||
);
|
||||
const compiler = new NestedFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
const act = () => compiler.compileFunction(
|
||||
frontFunc,
|
||||
callToFrontFunc,
|
||||
context,
|
||||
);
|
||||
// assert
|
||||
expectDeepThrowsError(act, expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createSingleFuncCallingAnotherFunc() {
|
||||
const deepFunc = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const callToDeepFunc = new FunctionCallStub().withFunctionName(deepFunc.name);
|
||||
const frontFunc = new SharedFunctionStub(FunctionBodyType.Calls).withCalls(callToDeepFunc);
|
||||
const callToFrontFunc = new FunctionCallStub().withFunctionName(frontFunc.name);
|
||||
return {
|
||||
deepFunc,
|
||||
frontFunc,
|
||||
callToFrontFunc,
|
||||
callToDeepFunc,
|
||||
};
|
||||
}
|
||||
|
||||
class NestedFunctionCallCompilerBuilder {
|
||||
private argumentCompiler: ArgumentCompiler = new ArgumentCompilerStub();
|
||||
|
||||
public withArgumentCompiler(argumentCompiler: ArgumentCompiler): this {
|
||||
this.argumentCompiler = argumentCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): NestedFunctionCallCompiler {
|
||||
return new NestedFunctionCallCompiler(
|
||||
this.argumentCompiler,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { SharedFunctionStub } from '@tests/unit/shared/Stubs/SharedFunctionStub';
|
||||
import type { FunctionCallParametersData } from '@/application/collections/';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
||||
import { AdaptiveFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/AdaptiveFunctionCallCompiler';
|
||||
import { SingleCallCompilerStrategy } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/SingleCallCompilerStrategy';
|
||||
import { SingleCallCompilerStrategyStub } from '@tests/unit/shared/Stubs/SingleCallCompilerStrategyStub';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||
import { FunctionCallCompilationContextStub } from '@tests/unit/shared/Stubs/FunctionCallCompilationContextStub';
|
||||
import { CompiledCodeStub } from '@tests/unit/shared/Stubs/CompiledCodeStub';
|
||||
import { SingleCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/SingleCallCompiler';
|
||||
|
||||
describe('AdaptiveFunctionCallCompiler', () => {
|
||||
describe('compileSingleCall', () => {
|
||||
describe('throws if call parameters does not match function parameters', () => {
|
||||
// arrange
|
||||
const functionName = 'test-function-name';
|
||||
const testCases: Array<{
|
||||
readonly description: string,
|
||||
readonly functionParameters: string[],
|
||||
readonly callParameters: string[]
|
||||
readonly expectedError: string;
|
||||
}> = [
|
||||
{
|
||||
description: 'provided: single unexpected parameter, when: another expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
description: 'provided: multiple unexpected parameters, when: different one is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['unexpected-parameter1', 'unexpected-parameter2'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
{
|
||||
description: 'provided: an unexpected parameter, when: multiple parameters are expected',
|
||||
functionParameters: ['expected-parameter1', 'expected-parameter2'],
|
||||
callParameters: ['expected-parameter1', 'expected-parameter2', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter1", "expected-parameter2"',
|
||||
},
|
||||
{
|
||||
description: 'provided: an unexpected parameter, when: none required',
|
||||
functionParameters: [],
|
||||
callParameters: ['unexpected-call-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"`
|
||||
+ '. Expected parameter(s): none',
|
||||
},
|
||||
{
|
||||
description: 'provided: expected and unexpected parameter, when: one of them is expected',
|
||||
functionParameters: ['expected-parameter'],
|
||||
callParameters: ['expected-parameter', 'unexpected-parameter'],
|
||||
expectedError:
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
|
||||
+ '. Expected parameter(s): "expected-parameter"',
|
||||
},
|
||||
];
|
||||
testCases.forEach(({
|
||||
description, functionParameters, callParameters, expectedError,
|
||||
}) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName('test-function-name')
|
||||
.withParameterNames(...functionParameters);
|
||||
const params = callParameters
|
||||
.reduce((result, parameter) => {
|
||||
return { ...result, [parameter]: 'defined-parameter-value' };
|
||||
}, {} as FunctionCallParametersData);
|
||||
const call = new FunctionCallStub()
|
||||
.withFunctionName(func.name)
|
||||
.withArguments(params);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withContext(new FunctionCallCompilationContextStub()
|
||||
.withAllFunctions(
|
||||
new SharedFunctionCollectionStub().withFunctions(func),
|
||||
))
|
||||
.withCall(call);
|
||||
// act
|
||||
const act = () => builder.compileSingleCall();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('strategy selection', () => {
|
||||
it('uses the matching strategy among multiple', () => {
|
||||
// arrange
|
||||
const matchedStrategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true);
|
||||
const unmatchedStrategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(false);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withStrategies([matchedStrategy, unmatchedStrategy]);
|
||||
// act
|
||||
builder.compileSingleCall();
|
||||
// assert
|
||||
expect(matchedStrategy.callHistory.filter((c) => c.methodName === 'compileFunction')).to.have.lengthOf(1);
|
||||
expect(unmatchedStrategy.callHistory.filter((c) => c.methodName === 'compileFunction')).to.have.lengthOf(0);
|
||||
});
|
||||
it('throws if multiple strategies can compile', () => {
|
||||
// arrange
|
||||
const expectedError = 'Multiple strategies found to compile the function call.';
|
||||
const matchedStrategy1 = new SingleCallCompilerStrategyStub().withCanCompileResult(true);
|
||||
const matchedStrategy2 = new SingleCallCompilerStrategyStub().withCanCompileResult(true);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder().withStrategies(
|
||||
[matchedStrategy1, matchedStrategy2],
|
||||
);
|
||||
// act
|
||||
const act = () => builder.compileSingleCall();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws if no strategy can compile', () => {
|
||||
// arrange
|
||||
const expectedError = 'No strategies found to compile the function call.';
|
||||
const unmatchedStrategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(false);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withStrategies([unmatchedStrategy]);
|
||||
// act
|
||||
const act = () => builder.compileSingleCall();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('strategy invocation', () => {
|
||||
it('passes correct function for compilation ability check', () => {
|
||||
// arrange
|
||||
const expectedFunction = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const strategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withContext(new FunctionCallCompilationContextStub()
|
||||
.withAllFunctions(
|
||||
new SharedFunctionCollectionStub().withFunctions(expectedFunction),
|
||||
))
|
||||
.withCall(new FunctionCallStub().withFunctionName(expectedFunction.name))
|
||||
.withStrategies([strategy]);
|
||||
// act
|
||||
builder.compileSingleCall();
|
||||
// assert
|
||||
const call = strategy.callHistory.filter((c) => c.methodName === 'canCompile');
|
||||
expect(call).to.have.lengthOf(1);
|
||||
expect(call[0].args[0]).to.equal(expectedFunction);
|
||||
});
|
||||
describe('compilation arguments', () => {
|
||||
it('uses correct function', () => {
|
||||
// arrange
|
||||
const expectedFunction = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const strategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withContext(new FunctionCallCompilationContextStub()
|
||||
.withAllFunctions(
|
||||
new SharedFunctionCollectionStub().withFunctions(expectedFunction),
|
||||
))
|
||||
.withCall(new FunctionCallStub().withFunctionName(expectedFunction.name))
|
||||
.withStrategies([strategy]);
|
||||
// act
|
||||
builder.compileSingleCall();
|
||||
// assert
|
||||
const call = strategy.callHistory.filter((c) => c.methodName === 'compileFunction');
|
||||
expect(call).to.have.lengthOf(1);
|
||||
const [actualFunction] = call[0].args;
|
||||
expect(actualFunction).to.equal(expectedFunction);
|
||||
});
|
||||
it('uses correct call', () => {
|
||||
// arrange
|
||||
const expectedCall = new FunctionCallStub();
|
||||
const strategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withStrategies([strategy])
|
||||
.withCall(expectedCall);
|
||||
// act
|
||||
builder.compileSingleCall();
|
||||
// assert
|
||||
const call = strategy.callHistory.filter((c) => c.methodName === 'compileFunction');
|
||||
expect(call).to.have.lengthOf(1);
|
||||
const [,actualCall] = call[0].args;
|
||||
expect(actualCall).to.equal(expectedCall);
|
||||
});
|
||||
it('uses correct context', () => {
|
||||
// arrange
|
||||
const expectedContext = new FunctionCallCompilationContextStub();
|
||||
const strategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withStrategies([strategy])
|
||||
.withContext(expectedContext);
|
||||
// act
|
||||
builder.compileSingleCall();
|
||||
// assert
|
||||
const call = strategy.callHistory.filter((c) => c.methodName === 'compileFunction');
|
||||
expect(call).to.have.lengthOf(1);
|
||||
const [,,actualContext] = call[0].args;
|
||||
expect(actualContext).to.equal(expectedContext);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('returns compiled code from strategy', () => {
|
||||
// arrange
|
||||
const expectedResult = [new CompiledCodeStub(), new CompiledCodeStub()];
|
||||
const strategy = new SingleCallCompilerStrategyStub()
|
||||
.withCanCompileResult(true)
|
||||
.withCompiledFunctionResult(expectedResult);
|
||||
const builder = new AdaptiveFunctionCallCompilerBuilder()
|
||||
.withStrategies([strategy]);
|
||||
// act
|
||||
const actualResult = builder.compileSingleCall();
|
||||
// assert
|
||||
expect(expectedResult).to.equal(actualResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class AdaptiveFunctionCallCompilerBuilder implements SingleCallCompiler {
|
||||
private strategies: SingleCallCompilerStrategy[] = [
|
||||
new SingleCallCompilerStrategyStub().withCanCompileResult(true),
|
||||
];
|
||||
|
||||
private call: FunctionCall = new FunctionCallStub();
|
||||
|
||||
private context: FunctionCallCompilationContext = new FunctionCallCompilationContextStub();
|
||||
|
||||
public withCall(call: FunctionCall): this {
|
||||
this.call = call;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withContext(context: FunctionCallCompilationContext): this {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withStrategies(strategies: SingleCallCompilerStrategy[]): this {
|
||||
this.strategies = strategies;
|
||||
return this;
|
||||
}
|
||||
|
||||
public compileSingleCall() {
|
||||
const compiler = new AdaptiveFunctionCallCompiler(this.strategies);
|
||||
return compiler.compileSingleCall(
|
||||
this.call,
|
||||
this.context,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { ArgumentCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/ArgumentCompiler';
|
||||
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { NestedFunctionArgumentCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/Argument/NestedFunctionArgumentCompiler';
|
||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
|
||||
import { FunctionCallCompilationContextStub } from '@tests/unit/shared/Stubs/FunctionCallCompilationContextStub';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { expectDeepThrowsError } from '@tests/unit/shared/Assertions/ExpectDeepThrowsError';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
import { SharedFunctionStub } from '@tests/unit/shared/Stubs/SharedFunctionStub';
|
||||
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
|
||||
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
|
||||
describe('NestedFunctionArgumentCompiler', () => {
|
||||
describe('createCompiledNestedCall', () => {
|
||||
it('should handle error from expressions compiler', () => {
|
||||
// arrange
|
||||
const parameterName = 'parameterName';
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withFunctionName('nested-function-call')
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(parameterName, 'unimportant-value'));
|
||||
const parentCall = new FunctionCallStub()
|
||||
.withFunctionName('parent-function-call');
|
||||
const expressionsCompilerError = new Error('child-');
|
||||
const expectedError = new AggregateError(
|
||||
[expressionsCompilerError],
|
||||
`Error when compiling argument for "${parameterName}"`,
|
||||
);
|
||||
const expressionsCompiler = new ExpressionsCompilerStub();
|
||||
expressionsCompiler.compileExpressions = () => { throw expressionsCompilerError; };
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withParentFunctionCall(parentCall)
|
||||
.withNestedFunctionCall(nestedCall)
|
||||
.withExpressionsCompiler(expressionsCompiler);
|
||||
// act
|
||||
const act = () => builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expectDeepThrowsError(act, expectedError);
|
||||
});
|
||||
describe('compilation', () => {
|
||||
describe('without arguments', () => {
|
||||
it('matches nested call name', () => {
|
||||
// arrange
|
||||
const expectedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub().withEmptyArguments());
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withNestedFunctionCall(expectedCall);
|
||||
// act
|
||||
const actualCall = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(actualCall.functionName).to.equal(expectedCall.functionName);
|
||||
});
|
||||
it('has no arguments or parameters', () => {
|
||||
// arrange
|
||||
const expectedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub().withEmptyArguments());
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withNestedFunctionCall(expectedCall);
|
||||
// act
|
||||
const actualCall = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(actualCall.args.getAllParameterNames()).to.have.lengthOf(0);
|
||||
});
|
||||
it('does not compile expressions', () => {
|
||||
// arrange
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub();
|
||||
const call = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub().withEmptyArguments());
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withNestedFunctionCall(call)
|
||||
.withExpressionsCompiler(expressionsCompilerStub);
|
||||
// act
|
||||
builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(expressionsCompilerStub.callHistory).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
describe('with arguments', () => {
|
||||
it('matches nested call name', () => {
|
||||
// arrange
|
||||
const expectedName = 'expected-nested-function-call-name';
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withFunctionName(expectedName)
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub().withSomeArguments());
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withNestedFunctionCall(nestedCall);
|
||||
// act
|
||||
const call = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(call.functionName).to.equal(expectedName);
|
||||
});
|
||||
it('matches nested call parameters', () => {
|
||||
// arrange
|
||||
const expectedParameterNames = ['expectedFirstParameterName', 'expectedSecondParameterName'];
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(expectedParameterNames.reduce((acc, name) => ({ ...acc, ...{ [name]: 'unimportant-value' } }), {})));
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withNestedFunctionCall(nestedCall);
|
||||
// act
|
||||
const call = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
const actualParameterNames = call.args.getAllParameterNames();
|
||||
expect(actualParameterNames.length).to.equal(expectedParameterNames.length);
|
||||
expect(actualParameterNames).to.have.members(expectedParameterNames);
|
||||
});
|
||||
it('compiles args using parent parameters', () => {
|
||||
// arrange
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub();
|
||||
const testParameterScenarios = [
|
||||
{
|
||||
parameterName: 'firstParameterName',
|
||||
rawArgumentValue: 'first-raw-argument-value',
|
||||
compiledArgumentValue: 'first-compiled-argument-value',
|
||||
},
|
||||
{
|
||||
parameterName: 'secondParameterName',
|
||||
rawArgumentValue: 'second-raw-argument-value',
|
||||
compiledArgumentValue: 'second-compiled-argument-value',
|
||||
},
|
||||
];
|
||||
const parentCall = new FunctionCallStub().withArgumentCollection(
|
||||
new FunctionCallArgumentCollectionStub().withSomeArguments(),
|
||||
);
|
||||
testParameterScenarios.forEach(({ rawArgumentValue }) => {
|
||||
expressionsCompilerStub.setup({
|
||||
givenCode: rawArgumentValue,
|
||||
givenArgs: parentCall.args,
|
||||
result: testParameterScenarios.find(
|
||||
(r) => r.rawArgumentValue === rawArgumentValue,
|
||||
).compiledArgumentValue,
|
||||
});
|
||||
});
|
||||
const nestedCallArgs = new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(testParameterScenarios.reduce((
|
||||
acc,
|
||||
{ parameterName, rawArgumentValue },
|
||||
) => ({ ...acc, ...{ [parameterName]: rawArgumentValue } }), {}));
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(nestedCallArgs);
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.withParentFunctionCall(parentCall)
|
||||
.withNestedFunctionCall(nestedCall);
|
||||
// act
|
||||
const compiledCall = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
const expectedParameterNames = testParameterScenarios.map((p) => p.parameterName);
|
||||
const actualParameterNames = compiledCall.args.getAllParameterNames();
|
||||
expect(expectedParameterNames.length).to.equal(actualParameterNames.length);
|
||||
expect(expectedParameterNames).to.have.members(actualParameterNames);
|
||||
const getActualArgumentValue = (parameterName: string) => compiledCall
|
||||
.args
|
||||
.getArgument(parameterName)
|
||||
.argumentValue;
|
||||
testParameterScenarios.forEach(({ parameterName, compiledArgumentValue }) => {
|
||||
expect(getActualArgumentValue(parameterName)).to.equal(compiledArgumentValue);
|
||||
});
|
||||
});
|
||||
describe('when expression compiler returns empty', () => {
|
||||
it('throws for required parameter', () => {
|
||||
// arrange
|
||||
const parameterName = 'requiredParameter';
|
||||
const initialValue = 'initial-value';
|
||||
const compiledValue = undefined;
|
||||
const expectedError = `Compilation resulted in empty value for required parameter: "${parameterName}"`;
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(parameterName, initialValue));
|
||||
const parentCall = new FunctionCallStub().withArgumentCollection(
|
||||
new FunctionCallArgumentCollectionStub().withSomeArguments(),
|
||||
);
|
||||
const context = createContextWithParameter({
|
||||
existingFunctionName: nestedCall.functionName,
|
||||
existingParameterName: parameterName,
|
||||
isExistingParameterOptional: false,
|
||||
});
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: initialValue,
|
||||
givenArgs: parentCall.args,
|
||||
result: compiledValue,
|
||||
});
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.withParentFunctionCall(parentCall)
|
||||
.withContext(context)
|
||||
.withNestedFunctionCall(nestedCall);
|
||||
// act
|
||||
const act = () => builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('succeeds for optional parameter', () => {
|
||||
// arrange
|
||||
const parameterName = 'optionalParameter';
|
||||
const initialValue = 'initial-value';
|
||||
const compiledValue = undefined;
|
||||
const nestedCall = new FunctionCallStub()
|
||||
.withArgumentCollection(new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(parameterName, initialValue));
|
||||
const parentCall = new FunctionCallStub().withArgumentCollection(
|
||||
new FunctionCallArgumentCollectionStub().withSomeArguments(),
|
||||
);
|
||||
const context = createContextWithParameter({
|
||||
existingFunctionName: nestedCall.functionName,
|
||||
existingParameterName: parameterName,
|
||||
isExistingParameterOptional: true,
|
||||
});
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: initialValue,
|
||||
givenArgs: parentCall.args,
|
||||
result: compiledValue,
|
||||
});
|
||||
const builder = new NestedFunctionArgumentCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.withParentFunctionCall(parentCall)
|
||||
.withContext(context)
|
||||
.withNestedFunctionCall(nestedCall);
|
||||
// act
|
||||
const compiledCall = builder.createCompiledNestedCall();
|
||||
// assert
|
||||
expect(compiledCall.args.hasArgument(parameterName)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createContextWithParameter(options: {
|
||||
readonly existingFunctionName: string,
|
||||
readonly existingParameterName: string,
|
||||
readonly isExistingParameterOptional: boolean,
|
||||
}): FunctionCallCompilationContext {
|
||||
const parameters = new FunctionParameterCollectionStub()
|
||||
.withParameterName(options.existingParameterName, options.isExistingParameterOptional);
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||
.withName(options.existingFunctionName)
|
||||
.withParameters(parameters);
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunctions(func);
|
||||
const context = new FunctionCallCompilationContextStub()
|
||||
.withAllFunctions(functions);
|
||||
return context;
|
||||
}
|
||||
|
||||
class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
|
||||
private expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub();
|
||||
|
||||
private nestedFunctionCall: FunctionCall = new FunctionCallStub();
|
||||
|
||||
private parentFunctionCall: FunctionCall = new FunctionCallStub();
|
||||
|
||||
private context: FunctionCallCompilationContext = new FunctionCallCompilationContextStub();
|
||||
|
||||
public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
|
||||
this.expressionsCompiler = expressionsCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParentFunctionCall(parentFunctionCall: FunctionCall): this {
|
||||
this.parentFunctionCall = parentFunctionCall;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withNestedFunctionCall(nestedFunctionCall: FunctionCall): this {
|
||||
this.nestedFunctionCall = nestedFunctionCall;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withContext(context: FunctionCallCompilationContext): this {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public createCompiledNestedCall(): FunctionCall {
|
||||
const compiler = new NestedFunctionArgumentCompiler(this.expressionsCompiler);
|
||||
return compiler.createCompiledNestedCall(
|
||||
this.nestedFunctionCall,
|
||||
this.parentFunctionCall,
|
||||
this.context,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { InlineFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/SingleCall/Strategies/InlineFunctionCallCompiler';
|
||||
import { SharedFunctionStub } from '@tests/unit/shared/Stubs/SharedFunctionStub';
|
||||
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
|
||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('InlineFunctionCallCompiler', () => {
|
||||
describe('canCompile', () => {
|
||||
it('returns `true` if function has code body', () => {
|
||||
// arrange
|
||||
const expected = true;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const compiler = new InlineFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
const actual = compiler.canCompile(func);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('returns `false` if function does not have code body', () => {
|
||||
// arrange
|
||||
const expected = false;
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Calls);
|
||||
const compiler = new InlineFunctionCallCompilerBuilder()
|
||||
.build();
|
||||
// act
|
||||
const actual = compiler.canCompile(func);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('compiles expressions with correct arguments', () => {
|
||||
// arrange
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub();
|
||||
const expectedArgs = new FunctionCallArgumentCollectionStub();
|
||||
const compiler = new InlineFunctionCallCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.build();
|
||||
// act
|
||||
compiler.compileFunction(
|
||||
new SharedFunctionStub(FunctionBodyType.Code),
|
||||
new FunctionCallStub()
|
||||
.withArgumentCollection(expectedArgs),
|
||||
);
|
||||
// assert
|
||||
const actualArgs = expressionsCompilerStub.callHistory.map((call) => call.args[1]);
|
||||
expect(actualArgs.every((arg) => arg === expectedArgs));
|
||||
});
|
||||
it('creates compiled code with compiled `execute`', () => {
|
||||
// arrange
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const expectedCode = 'expected-code';
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: func.body.code.execute,
|
||||
givenArgs: args,
|
||||
result: expectedCode,
|
||||
});
|
||||
const compiler = new InlineFunctionCallCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.build();
|
||||
// act
|
||||
const compiledCodes = compiler
|
||||
.compileFunction(func, new FunctionCallStub().withArgumentCollection(args));
|
||||
// assert
|
||||
expect(compiledCodes).to.have.lengthOf(1);
|
||||
const actualCode = compiledCodes[0].code;
|
||||
expect(actualCode).to.equal(expectedCode);
|
||||
});
|
||||
it('creates compiled revert code with compiled `revert`', () => {
|
||||
// arrange
|
||||
const func = new SharedFunctionStub(FunctionBodyType.Code);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const expectedRevertCode = 'expected-revert-code';
|
||||
const expressionsCompilerStub = new ExpressionsCompilerStub()
|
||||
.setup({
|
||||
givenCode: func.body.code.revert,
|
||||
givenArgs: args,
|
||||
result: expectedRevertCode,
|
||||
});
|
||||
const compiler = new InlineFunctionCallCompilerBuilder()
|
||||
.withExpressionsCompiler(expressionsCompilerStub)
|
||||
.build();
|
||||
// act
|
||||
const compiledCodes = compiler
|
||||
.compileFunction(func, new FunctionCallStub().withArgumentCollection(args));
|
||||
// assert
|
||||
expect(compiledCodes).to.have.lengthOf(1);
|
||||
const actualRevertCode = compiledCodes[0].revertCode;
|
||||
expect(actualRevertCode).to.equal(expectedRevertCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class InlineFunctionCallCompilerBuilder {
|
||||
private expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub();
|
||||
|
||||
public build(): InlineFunctionCallCompiler {
|
||||
return new InlineFunctionCallCompiler(this.expressionsCompiler);
|
||||
}
|
||||
|
||||
public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
|
||||
this.expressionsCompiler = expressionsCompiler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('FunctionCall', () => {
|
||||
describe('ParsedFunctionCall', () => {
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
describe('throws when args is missing', () => {
|
||||
@@ -76,6 +76,6 @@ class FunctionCallBuilder {
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCall(this.functionName, this.args);
|
||||
return new ParsedFunctionCall(this.functionName, this.args);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
|
||||
import { createCallerFunction, createFunctionWithInlineCode } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
||||
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import { FunctionBodyType, ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import {
|
||||
@@ -132,7 +132,7 @@ describe('SharedFunction', () => {
|
||||
});
|
||||
});
|
||||
describe('createCallerFunction', () => {
|
||||
describe('callSequence', () => {
|
||||
describe('rootCallSequence', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
@@ -141,7 +141,7 @@ describe('SharedFunction', () => {
|
||||
];
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withCallSequence(expected)
|
||||
.withRootCallSequence(expected)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(sut.body.calls).equal(expected);
|
||||
@@ -150,12 +150,12 @@ describe('SharedFunction', () => {
|
||||
itEachAbsentCollectionValue((absentValue) => {
|
||||
// arrange
|
||||
const functionName = 'invalidFunction';
|
||||
const callSequence = absentValue;
|
||||
const rootCallSequence = absentValue;
|
||||
const expectedError = `missing call sequence in function "${functionName}"`;
|
||||
// act
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withName(functionName)
|
||||
.withCallSequence(callSequence)
|
||||
.withRootCallSequence(rootCallSequence)
|
||||
.createCallerFunction();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
@@ -206,7 +206,7 @@ class SharedFunctionBuilder {
|
||||
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
private callSequence: readonly IFunctionCall[] = [new FunctionCallStub()];
|
||||
private callSequence: readonly FunctionCall[] = [new FunctionCallStub()];
|
||||
|
||||
private code = 'code';
|
||||
|
||||
@@ -249,7 +249,7 @@ class SharedFunctionBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withCallSequence(callSequence: readonly IFunctionCall[]) {
|
||||
public withRootCallSequence(callSequence: readonly FunctionCall[]) {
|
||||
this.callSequence = callSequence;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { FunctionData } from '@/application/collections/';
|
||||
import { ScriptCode } from '@/domain/ScriptCode';
|
||||
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
|
||||
import { ISharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionsParser';
|
||||
import { ICompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/ICompiledCode';
|
||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/IFunctionCallCompiler';
|
||||
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||
import { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
|
||||
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
||||
import { ScriptDataStub } from '@tests/unit/shared/Stubs/ScriptDataStub';
|
||||
import { FunctionDataStub } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
||||
@@ -91,7 +91,7 @@ describe('ScriptCompiler', () => {
|
||||
});
|
||||
it('returns code as expected', () => {
|
||||
// arrange
|
||||
const expected: ICompiledCode = {
|
||||
const expected: CompiledCode = {
|
||||
code: 'expected-code',
|
||||
revertCode: 'expected-revert-code',
|
||||
};
|
||||
@@ -152,8 +152,8 @@ describe('ScriptCompiler', () => {
|
||||
const scriptName = 'scriptName';
|
||||
const innerError = 'innerError';
|
||||
const expectedError = `Script "${scriptName}" ${innerError}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => { throw new Error(innerError); },
|
||||
const callCompiler: FunctionCallCompiler = {
|
||||
compileFunctionCalls: () => { throw new Error(innerError); },
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
@@ -170,13 +170,13 @@ describe('ScriptCompiler', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const invalidCode: ICompiledCode = { code: undefined, revertCode: undefined };
|
||||
const invalidCode: CompiledCode = { code: undefined, revertCode: undefined };
|
||||
const realExceptionMessage = collectExceptionMessage(
|
||||
() => new ScriptCode(invalidCode.code, invalidCode.revertCode),
|
||||
);
|
||||
const expectedError = `Script "${scriptName}" ${realExceptionMessage}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => invalidCode,
|
||||
const callCompiler: FunctionCallCompiler = {
|
||||
compileFunctionCalls: () => invalidCode,
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
@@ -226,7 +226,7 @@ class ScriptCompilerBuilder {
|
||||
|
||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||
|
||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
private callCompiler: FunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
|
||||
private codeValidator: ICodeValidator = new CodeValidatorStub();
|
||||
|
||||
@@ -269,7 +269,7 @@ class ScriptCompilerBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||
public withFunctionCallCompiler(callCompiler: FunctionCallCompiler): ScriptCompilerBuilder {
|
||||
this.callCompiler = callCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user