win: fix incomplete VSCEIP, location scripts
This commit improves the validation logic in parser, corrects Windows collection files to adhere to expected structure. This validation helps catch errors that previously led to incomplete generated code in scripts for disabling VSCEIP and location settings. Changes: - Add type validation for function call structures in the parser/compiler. This helps prevent runtime errors by ensuring that only correctly structured data is processed. - Fix scripts in the Windows collection that previoulsy had incomplete `code` or `revertCode` values. These corrections ensure that the scripts function as intended. - Refactor related logic within the compiler/parser to improve testability and maintainability.
This commit is contained in:
@@ -18,7 +18,10 @@ export const createCollectionUtilities: CategoryCollectionSpecificUtilitiesFacto
|
|||||||
) => {
|
) => {
|
||||||
const syntax = syntaxFactory.create(scripting.language);
|
const syntax = syntaxFactory.create(scripting.language);
|
||||||
return {
|
return {
|
||||||
compiler: new ScriptCompiler(functionsData ?? [], syntax),
|
compiler: new ScriptCompiler({
|
||||||
|
functions: functionsData ?? [],
|
||||||
|
syntax,
|
||||||
|
}),
|
||||||
syntax,
|
syntax,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +1,50 @@
|
|||||||
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
||||||
import { isArray, isPlainObject } from '@/TypeHelpers';
|
import { isArray, isPlainObject } from '@/TypeHelpers';
|
||||||
|
import { createTypeValidator, type TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
||||||
import type { FunctionCall } from './FunctionCall';
|
import type { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
|
export interface FunctionCallsParser {
|
||||||
const sequence = getCallSequence(calls);
|
(
|
||||||
return sequence.map((call) => parseFunctionCall(call));
|
calls: FunctionCallsData,
|
||||||
|
validator?: TypeValidator,
|
||||||
|
): FunctionCall[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
export const parseFunctionCalls: FunctionCallsParser = (
|
||||||
|
calls,
|
||||||
|
validator = createTypeValidator(),
|
||||||
|
) => {
|
||||||
|
const sequence = getCallSequence(calls, validator);
|
||||||
|
return sequence.map((call) => parseFunctionCall(call, validator));
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCallSequence(calls: FunctionCallsData, validator: TypeValidator): FunctionCallData[] {
|
||||||
if (!isPlainObject(calls) && !isArray(calls)) {
|
if (!isPlainObject(calls) && !isArray(calls)) {
|
||||||
throw new Error('called function(s) must be an object or array');
|
throw new Error('called function(s) must be an object or array');
|
||||||
}
|
}
|
||||||
if (isArray(calls)) {
|
if (isArray(calls)) {
|
||||||
|
validator.assertNonEmptyCollection({
|
||||||
|
value: calls,
|
||||||
|
valueName: 'function call sequence',
|
||||||
|
});
|
||||||
return calls as FunctionCallData[];
|
return calls as FunctionCallData[];
|
||||||
}
|
}
|
||||||
const singleCall = calls as FunctionCallData;
|
const singleCall = calls as FunctionCallData;
|
||||||
return [singleCall];
|
return [singleCall];
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFunctionCall(call: FunctionCallData): FunctionCall {
|
function parseFunctionCall(
|
||||||
|
call: FunctionCallData,
|
||||||
|
validator: TypeValidator,
|
||||||
|
): FunctionCall {
|
||||||
|
validator.assertObject({
|
||||||
|
value: call,
|
||||||
|
valueName: 'function call',
|
||||||
|
allowedProperties: ['function', 'parameters'],
|
||||||
|
});
|
||||||
const callArgs = parseArgs(call.parameters);
|
const callArgs = parseArgs(call.parameters);
|
||||||
return new ParsedFunctionCall(call.function, callArgs);
|
return new ParsedFunctionCall(call.function, callArgs);
|
||||||
}
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { FunctionData } from '@/application/collections/';
|
|
||||||
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
|
||||||
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
|
||||||
|
|
||||||
export interface ISharedFunctionsParser {
|
|
||||||
parseFunctions(
|
|
||||||
functions: readonly FunctionData[],
|
|
||||||
syntax: ILanguageSyntax,
|
|
||||||
): ISharedFunctionCollection;
|
|
||||||
}
|
|
||||||
@@ -12,50 +12,52 @@ import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/
|
|||||||
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
||||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||||
import { FunctionParameter } from './Parameter/FunctionParameter';
|
import { FunctionParameter } from './Parameter/FunctionParameter';
|
||||||
import { parseFunctionCalls } from './Call/FunctionCallParser';
|
import { parseFunctionCalls, type FunctionCallsParser } from './Call/FunctionCallsParser';
|
||||||
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
|
import { createFunctionParameterCollection, type FunctionParameterCollectionFactory } from './Parameter/FunctionParameterCollectionFactory';
|
||||||
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
import type { ISharedFunctionsParser } from './ISharedFunctionsParser';
|
|
||||||
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import type { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
import type { ISharedFunction } from './ISharedFunction';
|
import type { ISharedFunction } from './ISharedFunction';
|
||||||
|
|
||||||
const DefaultSharedFunctionsParsingUtilities: SharedFunctionsParsingUtilities = {
|
export interface SharedFunctionsParser {
|
||||||
|
(
|
||||||
|
functions: readonly FunctionData[],
|
||||||
|
syntax: ILanguageSyntax,
|
||||||
|
utilities?: SharedFunctionsParsingUtilities,
|
||||||
|
): ISharedFunctionCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseSharedFunctions: SharedFunctionsParser = (
|
||||||
|
functions: readonly FunctionData[],
|
||||||
|
syntax: ILanguageSyntax,
|
||||||
|
utilities = DefaultUtilities,
|
||||||
|
) => {
|
||||||
|
const collection = new SharedFunctionCollection();
|
||||||
|
if (!functions.length) {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
ensureValidFunctions(functions);
|
||||||
|
return functions
|
||||||
|
.map((func) => parseFunction(func, syntax, utilities))
|
||||||
|
.reduce((acc, func) => {
|
||||||
|
acc.addFunction(func);
|
||||||
|
return acc;
|
||||||
|
}, collection);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultUtilities: SharedFunctionsParsingUtilities = {
|
||||||
wrapError: wrapErrorWithAdditionalContext,
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
createParameter: (...args) => new FunctionParameter(...args),
|
createParameter: (...args) => new FunctionParameter(...args),
|
||||||
codeValidator: CodeValidator.instance,
|
codeValidator: CodeValidator.instance,
|
||||||
createParameterCollection: createFunctionParameterCollection,
|
createParameterCollection: createFunctionParameterCollection,
|
||||||
|
parseFunctionCalls,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class SharedFunctionsParser implements ISharedFunctionsParser {
|
|
||||||
public static readonly instance: ISharedFunctionsParser = new SharedFunctionsParser();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly utilities = DefaultSharedFunctionsParsingUtilities,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public parseFunctions(
|
|
||||||
functions: readonly FunctionData[],
|
|
||||||
syntax: ILanguageSyntax,
|
|
||||||
): ISharedFunctionCollection {
|
|
||||||
const collection = new SharedFunctionCollection();
|
|
||||||
if (!functions.length) {
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
ensureValidFunctions(functions);
|
|
||||||
return functions
|
|
||||||
.map((func) => parseFunction(func, syntax, this.utilities))
|
|
||||||
.reduce((acc, func) => {
|
|
||||||
acc.addFunction(func);
|
|
||||||
return acc;
|
|
||||||
}, collection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SharedFunctionsParsingUtilities {
|
interface SharedFunctionsParsingUtilities {
|
||||||
readonly wrapError: ErrorWithContextWrapper;
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
readonly createParameter: FunctionParameterFactory;
|
readonly createParameter: FunctionParameterFactory;
|
||||||
readonly codeValidator: ICodeValidator;
|
readonly codeValidator: ICodeValidator;
|
||||||
readonly createParameterCollection: FunctionParameterCollectionFactory;
|
readonly createParameterCollection: FunctionParameterCollectionFactory;
|
||||||
|
readonly parseFunctionCalls: FunctionCallsParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FunctionParameterFactory = (
|
export type FunctionParameterFactory = (
|
||||||
@@ -74,7 +76,7 @@ function parseFunction(
|
|||||||
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
||||||
}
|
}
|
||||||
// Has call
|
// Has call
|
||||||
const calls = parseFunctionCalls(data.call);
|
const calls = utilities.parseFunctionCalls(data.call);
|
||||||
return createCallerFunction(name, parameters, calls);
|
return createCallerFunction(name, parameters, calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,28 +6,46 @@ import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/
|
|||||||
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
||||||
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||||
import { createScriptCode, type ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
import { createScriptCode, type ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
||||||
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
|
||||||
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
|
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
|
||||||
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
import { parseFunctionCalls } from './Function/Call/FunctionCallsParser';
|
||||||
|
import { parseSharedFunctions, type SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||||
import type { CompiledCode } from './Function/Call/Compiler/CompiledCode';
|
import type { CompiledCode } from './Function/Call/Compiler/CompiledCode';
|
||||||
import type { IScriptCompiler } from './IScriptCompiler';
|
import type { IScriptCompiler } from './IScriptCompiler';
|
||||||
import type { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||||
import type { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
import type { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import type { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
|
||||||
|
interface ScriptCompilerUtilities {
|
||||||
|
readonly sharedFunctionsParser: SharedFunctionsParser;
|
||||||
|
readonly callCompiler: FunctionCallCompiler;
|
||||||
|
readonly codeValidator: ICodeValidator;
|
||||||
|
readonly wrapError: ErrorWithContextWrapper;
|
||||||
|
readonly scriptCodeFactory: ScriptCodeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultUtilities: ScriptCompilerUtilities = {
|
||||||
|
sharedFunctionsParser: parseSharedFunctions,
|
||||||
|
callCompiler: FunctionCallSequenceCompiler.instance,
|
||||||
|
codeValidator: CodeValidator.instance,
|
||||||
|
wrapError: wrapErrorWithAdditionalContext,
|
||||||
|
scriptCodeFactory: createScriptCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CategoryCollectionDataContext {
|
||||||
|
readonly functions: readonly FunctionData[];
|
||||||
|
readonly syntax: ILanguageSyntax;
|
||||||
|
}
|
||||||
|
|
||||||
export class ScriptCompiler implements IScriptCompiler {
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
private readonly functions: ISharedFunctionCollection;
|
private readonly functions: ISharedFunctionCollection;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
functions: readonly FunctionData[],
|
categoryContext: CategoryCollectionDataContext,
|
||||||
syntax: ILanguageSyntax,
|
private readonly utilities: ScriptCompilerUtilities = DefaultUtilities,
|
||||||
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
|
||||||
private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
|
|
||||||
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
|
||||||
private readonly wrapError: ErrorWithContextWrapper = wrapErrorWithAdditionalContext,
|
|
||||||
private readonly scriptCodeFactory: ScriptCodeFactory = createScriptCode,
|
|
||||||
) {
|
) {
|
||||||
this.functions = sharedFunctionsParser.parseFunctions(functions, syntax);
|
this.functions = this.utilities.sharedFunctionsParser(
|
||||||
|
categoryContext.functions,
|
||||||
|
categoryContext.syntax,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public canCompile(script: ScriptData): boolean {
|
public canCompile(script: ScriptData): boolean {
|
||||||
@@ -40,14 +58,14 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
throw new Error('Script does include any calls.');
|
throw new Error('Script does include any calls.');
|
||||||
}
|
}
|
||||||
const calls = parseFunctionCalls(script.call);
|
const calls = parseFunctionCalls(script.call);
|
||||||
const compiledCode = this.callCompiler.compileFunctionCalls(calls, this.functions);
|
const compiledCode = this.utilities.callCompiler.compileFunctionCalls(calls, this.functions);
|
||||||
validateCompiledCode(compiledCode, this.codeValidator);
|
validateCompiledCode(compiledCode, this.utilities.codeValidator);
|
||||||
return this.scriptCodeFactory(
|
return this.utilities.scriptCodeFactory(
|
||||||
compiledCode.code,
|
compiledCode.code,
|
||||||
compiledCode.revertCode,
|
compiledCode.revertCode,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw this.wrapError(error, `Failed to compile script: ${script.name}`);
|
throw this.utilities.wrapError(error, `Failed to compile script: ${script.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4362,9 +4362,10 @@ actions:
|
|||||||
deleteOnRevert: 'true' # Missing by default since Windows 10 Pro (≥ 22H2) and Windows 11 Pro (≥ 23H2)
|
deleteOnRevert: 'true' # Missing by default since Windows 10 Pro (≥ 22H2) and Windows 11 Pro (≥ 23H2)
|
||||||
-
|
-
|
||||||
function: RunInlineCode
|
function: RunInlineCode
|
||||||
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}" /v "SensorPermissionState" /d "0" /t REG_DWORD /f
|
parameters:
|
||||||
revertCode: >- # Default value is `1` since Windows 10 Pro (≥ 22H2) and Windows 11 Pro (≥ 23H2)
|
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}" /v "SensorPermissionState" /d "0" /t REG_DWORD /f
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}" /v "SensorPermissionState" /d "1" /t REG_DWORD /f
|
revertCode: >- # Default value is `1` since Windows 10 Pro (≥ 22H2) and Windows 11 Pro (≥ 23H2)
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}" /v "SensorPermissionState" /d "1" /t REG_DWORD /f
|
||||||
-
|
-
|
||||||
name: Disable device sensors
|
name: Disable device sensors
|
||||||
recommend: standard
|
recommend: standard
|
||||||
@@ -5764,30 +5765,31 @@ actions:
|
|||||||
-
|
-
|
||||||
# Using OS keys
|
# Using OS keys
|
||||||
function: RunInlineCode
|
function: RunInlineCode
|
||||||
code: |-
|
parameters:
|
||||||
if %PROCESSOR_ARCHITECTURE%==x86 ( REM is 32 bit?
|
code: |-
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
if %PROCESSOR_ARCHITECTURE%==x86 ( REM is 32 bit?
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
) else (
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
) else (
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
)
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 0 /f
|
||||||
revertCode: |-
|
)
|
||||||
if %PROCESSOR_ARCHITECTURE%==x86 ( REM is 32 bit?
|
revertCode: |-
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
if %PROCESSOR_ARCHITECTURE%==x86 ( REM is 32 bit?
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
) else (
|
reg add "HKLM\SOFTWARE\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
) else (
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\14.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\15.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\16.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
)
|
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\VSCommon\17.0\SQM" /v "OptIn" /t REG_DWORD /d 1 /f
|
||||||
|
)
|
||||||
-
|
-
|
||||||
function: SetRegistryValue
|
function: SetRegistryValue
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ describe('CategoryCollectionSpecificUtilities', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const functionsData = [createFunctionDataWithCode()];
|
const functionsData = [createFunctionDataWithCode()];
|
||||||
const syntax = new LanguageSyntaxStub();
|
const syntax = new LanguageSyntaxStub();
|
||||||
const expected = new ScriptCompiler(functionsData, syntax);
|
const expected = new ScriptCompiler({
|
||||||
|
functions: functionsData,
|
||||||
|
syntax,
|
||||||
|
});
|
||||||
const language = ScriptingLanguage.shellscript;
|
const language = ScriptingLanguage.shellscript;
|
||||||
const factoryMock = createSyntaxFactoryStub(language, syntax);
|
const factoryMock = createSyntaxFactoryStub(language, syntax);
|
||||||
const definition = new ScriptingDefinitionStub()
|
const definition = new ScriptingDefinitionStub()
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallParser';
|
|
||||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
|
||||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
|
||||||
|
|
||||||
describe('FunctionCallParser', () => {
|
|
||||||
describe('parseFunctionCalls', () => {
|
|
||||||
it('throws if call is not an object', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'called function(s) must be an object';
|
|
||||||
const invalidCalls = ['string', 33, false];
|
|
||||||
invalidCalls.forEach((invalidCall) => {
|
|
||||||
// act
|
|
||||||
const act = () => parseFunctionCalls(invalidCall as never);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('throws if call sequence has undefined function name', () => {
|
|
||||||
itEachAbsentStringValue((absentValue) => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'missing function name in function call';
|
|
||||||
const data = [
|
|
||||||
new FunctionCallDataStub().withName('function-name'),
|
|
||||||
new FunctionCallDataStub().withName(absentValue),
|
|
||||||
];
|
|
||||||
// act
|
|
||||||
const act = () => parseFunctionCalls(data);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
}, { excludeNull: true, excludeUndefined: true });
|
|
||||||
});
|
|
||||||
it('parses single call as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedFunctionName = 'functionName';
|
|
||||||
const expectedParameterName = 'parameterName';
|
|
||||||
const expectedArgumentValue = 'argumentValue';
|
|
||||||
const data = new FunctionCallDataStub()
|
|
||||||
.withName(expectedFunctionName)
|
|
||||||
.withParameters({ [expectedParameterName]: expectedArgumentValue });
|
|
||||||
// act
|
|
||||||
const actual = parseFunctionCalls(data);
|
|
||||||
// assert
|
|
||||||
expect(actual).to.have.lengthOf(1);
|
|
||||||
const call = actual[0];
|
|
||||||
expect(call.functionName).to.equal(expectedFunctionName);
|
|
||||||
const { args } = call;
|
|
||||||
expect(args.getAllParameterNames()).to.have.lengthOf(1);
|
|
||||||
expect(args.hasArgument(expectedParameterName)).to.equal(
|
|
||||||
true,
|
|
||||||
`Does not include expected parameter: "${expectedParameterName}"\n`
|
|
||||||
+ `But includes: "${args.getAllParameterNames()}"`,
|
|
||||||
);
|
|
||||||
const argument = args.getArgument(expectedParameterName);
|
|
||||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
|
||||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
|
||||||
});
|
|
||||||
it('parses multiple calls as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const getFunctionName = (index: number) => `functionName${index}`;
|
|
||||||
const getParameterName = (index: number) => `parameterName${index}`;
|
|
||||||
const getArgumentValue = (index: number) => `argumentValue${index}`;
|
|
||||||
const createCall = (index: number) => new FunctionCallDataStub()
|
|
||||||
.withName(getFunctionName(index))
|
|
||||||
.withParameters({ [getParameterName(index)]: getArgumentValue(index) });
|
|
||||||
const calls = [createCall(0), createCall(1), createCall(2), createCall(3)];
|
|
||||||
// act
|
|
||||||
const actual = parseFunctionCalls(calls);
|
|
||||||
// assert
|
|
||||||
expect(actual).to.have.lengthOf(calls.length);
|
|
||||||
for (let i = 0; i < calls.length; i++) {
|
|
||||||
const call = actual[i];
|
|
||||||
const expectedParameterName = getParameterName(i);
|
|
||||||
const expectedArgumentValue = getArgumentValue(i);
|
|
||||||
expect(call.functionName).to.equal(getFunctionName(i));
|
|
||||||
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
|
|
||||||
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
|
|
||||||
const argument = call.args.getArgument(expectedParameterName);
|
|
||||||
expect(argument.parameterName).to.equal(expectedParameterName);
|
|
||||||
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import type { FunctionCallsData, FunctionCallData } from '@/application/collections/';
|
||||||
|
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||||
|
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||||
|
import type { NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||||
|
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||||
|
|
||||||
|
describe('FunctionCallsParser', () => {
|
||||||
|
describe('parseFunctionCalls', () => {
|
||||||
|
describe('throws if single call is not an object or array', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'called function(s) must be an object or array';
|
||||||
|
const testScenarios: readonly {
|
||||||
|
readonly description: string;
|
||||||
|
readonly invalidData: FunctionCallsData;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
description: 'given a string',
|
||||||
|
invalidData: 'string' as unknown as FunctionCallsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'given a number',
|
||||||
|
invalidData: 33 as unknown as FunctionCallsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'given a boolean',
|
||||||
|
invalidData: false as unknown as FunctionCallsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'given null',
|
||||||
|
invalidData: null as unknown as FunctionCallsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'given undefined',
|
||||||
|
invalidData: undefined as unknown as FunctionCallsData,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
testScenarios.forEach(({ description, invalidData }) => {
|
||||||
|
it(description, () => {
|
||||||
|
const context = new TestContext()
|
||||||
|
.withData(invalidData);
|
||||||
|
// act
|
||||||
|
const act = () => context.parse();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('given a single call', () => {
|
||||||
|
it('validates single call as object', () => {
|
||||||
|
// arrange
|
||||||
|
const data = new FunctionCallDataStub();
|
||||||
|
const expectedAssertion: ObjectAssertion<FunctionCallData> = {
|
||||||
|
value: data,
|
||||||
|
valueName: 'function call',
|
||||||
|
allowedProperties: [
|
||||||
|
'function', 'parameters',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const validator = new TypeValidatorStub();
|
||||||
|
const context = new TestContext()
|
||||||
|
.withData(data)
|
||||||
|
.withTypeValidator(validator);
|
||||||
|
// act
|
||||||
|
context.parse();
|
||||||
|
// assert
|
||||||
|
validator.expectObjectAssertion(expectedAssertion);
|
||||||
|
});
|
||||||
|
it('parses single call as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFunctionName = 'functionName';
|
||||||
|
const expectedParameterName = 'parameterName';
|
||||||
|
const expectedArgumentValue = 'argumentValue';
|
||||||
|
const data = new FunctionCallDataStub()
|
||||||
|
.withName(expectedFunctionName)
|
||||||
|
.withParameters({ [expectedParameterName]: expectedArgumentValue });
|
||||||
|
// act
|
||||||
|
const actual = parseFunctionCalls(data);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.have.lengthOf(1);
|
||||||
|
const call = actual[0];
|
||||||
|
expect(call.functionName).to.equal(expectedFunctionName);
|
||||||
|
const { args } = call;
|
||||||
|
expect(args.getAllParameterNames()).to.have.lengthOf(1);
|
||||||
|
expect(args.hasArgument(expectedParameterName)).to.equal(
|
||||||
|
true,
|
||||||
|
`Does not include expected parameter: "${expectedParameterName}"\n`
|
||||||
|
+ `But includes: "${args.getAllParameterNames()}"`,
|
||||||
|
);
|
||||||
|
const argument = args.getArgument(expectedParameterName);
|
||||||
|
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||||
|
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('given a call sequence', () => {
|
||||||
|
describe('throws if call sequence has undefined function name', () => {
|
||||||
|
itEachAbsentStringValue((absentValue) => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'missing function name in function call';
|
||||||
|
const data = [
|
||||||
|
new FunctionCallDataStub().withName('function-name'),
|
||||||
|
new FunctionCallDataStub().withName(absentValue),
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const act = () => parseFunctionCalls(data);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
}, { excludeNull: true, excludeUndefined: true });
|
||||||
|
});
|
||||||
|
it('validates call sequence as non empty collection', () => {
|
||||||
|
// arrange
|
||||||
|
const data: FunctionCallsData = [new FunctionCallDataStub()];
|
||||||
|
const expectedAssertion: NonEmptyCollectionAssertion = {
|
||||||
|
value: data,
|
||||||
|
valueName: 'function call sequence',
|
||||||
|
};
|
||||||
|
const validator = new TypeValidatorStub();
|
||||||
|
const context = new TestContext()
|
||||||
|
.withData(data)
|
||||||
|
.withTypeValidator(validator);
|
||||||
|
// act
|
||||||
|
context.parse();
|
||||||
|
// assert
|
||||||
|
validator.expectNonEmptyCollectionAssertion(expectedAssertion);
|
||||||
|
});
|
||||||
|
it('validates a call in call sequence as object', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedValidatedCallData = new FunctionCallDataStub();
|
||||||
|
const data: FunctionCallsData = [expectedValidatedCallData];
|
||||||
|
const expectedAssertion: ObjectAssertion<FunctionCallData> = {
|
||||||
|
value: expectedValidatedCallData,
|
||||||
|
valueName: 'function call',
|
||||||
|
allowedProperties: [
|
||||||
|
'function', 'parameters',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const validator = new TypeValidatorStub();
|
||||||
|
const context = new TestContext()
|
||||||
|
.withData(data)
|
||||||
|
.withTypeValidator(validator);
|
||||||
|
// act
|
||||||
|
context.parse();
|
||||||
|
// assert
|
||||||
|
validator.expectObjectAssertion(expectedAssertion);
|
||||||
|
});
|
||||||
|
it('parses multiple calls as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const getFunctionName = (index: number) => `functionName${index}`;
|
||||||
|
const getParameterName = (index: number) => `parameterName${index}`;
|
||||||
|
const getArgumentValue = (index: number) => `argumentValue${index}`;
|
||||||
|
const createCall = (index: number) => new FunctionCallDataStub()
|
||||||
|
.withName(getFunctionName(index))
|
||||||
|
.withParameters({ [getParameterName(index)]: getArgumentValue(index) });
|
||||||
|
const calls = [createCall(0), createCall(1), createCall(2), createCall(3)];
|
||||||
|
// act
|
||||||
|
const actual = parseFunctionCalls(calls);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.have.lengthOf(calls.length);
|
||||||
|
for (let i = 0; i < calls.length; i++) {
|
||||||
|
const call = actual[i];
|
||||||
|
const expectedParameterName = getParameterName(i);
|
||||||
|
const expectedArgumentValue = getArgumentValue(i);
|
||||||
|
expect(call.functionName).to.equal(getFunctionName(i));
|
||||||
|
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
|
||||||
|
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
|
||||||
|
const argument = call.args.getArgument(expectedParameterName);
|
||||||
|
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||||
|
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class TestContext {
|
||||||
|
private validator: TypeValidator = new TypeValidatorStub();
|
||||||
|
|
||||||
|
private calls: FunctionCallsData = [new FunctionCallDataStub()];
|
||||||
|
|
||||||
|
public withTypeValidator(typeValidator: TypeValidator): this {
|
||||||
|
this.validator = typeValidator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withData(calls: FunctionCallsData): this {
|
||||||
|
this.calls = calls;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(): ReturnType<typeof parseFunctionCalls> {
|
||||||
|
return parseFunctionCalls(
|
||||||
|
this.calls,
|
||||||
|
this.validator,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
CallFunctionBody, CodeFunctionBody, SharedFunctionBody,
|
CallFunctionBody, CodeFunctionBody, SharedFunctionBody,
|
||||||
} from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
} from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
import { FunctionBodyType } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
import { FunctionBodyType } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||||
|
|
||||||
export function expectCodeFunctionBody(
|
export function expectCodeFunctionBody(
|
||||||
@@ -18,6 +19,7 @@ export function expectCallsFunctionBody(
|
|||||||
|
|
||||||
function expectBodyType(body: SharedFunctionBody, expectedType: FunctionBodyType) {
|
function expectBodyType(body: SharedFunctionBody, expectedType: FunctionBodyType) {
|
||||||
const actualType = body.type;
|
const actualType = body.type;
|
||||||
|
expectExists(actualType, 'Function has no body');
|
||||||
expect(actualType).to.equal(expectedType, formatAssertionMessage([
|
expect(actualType).to.equal(expectedType, formatAssertionMessage([
|
||||||
`Actual: ${FunctionBodyType[actualType]}`,
|
`Actual: ${FunctionBodyType[actualType]}`,
|
||||||
`Expected: ${FunctionBodyType[expectedType]}`,
|
`Expected: ${FunctionBodyType[expectedType]}`,
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import type { FunctionData, CodeInstruction } from '@/application/collections/';
|
import type {
|
||||||
|
FunctionData, CodeInstruction,
|
||||||
|
ParameterDefinitionData, FunctionCallsData,
|
||||||
|
} from '@/application/collections/';
|
||||||
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||||
import { SharedFunctionsParser, type FunctionParameterFactory } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
import { parseSharedFunctions, type FunctionParameterFactory } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
||||||
import { createFunctionDataWithCode, createFunctionDataWithoutCallOrCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
import { createFunctionDataWithCall, createFunctionDataWithCode, createFunctionDataWithoutCallOrCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
||||||
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
|
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
|
||||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||||
import { itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
import { itEachAbsentCollectionValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||||
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
||||||
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
||||||
import { itIsSingletonFactory } from '@tests/unit/shared/TestCases/SingletonFactoryTests';
|
|
||||||
import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
|
import { CodeValidatorStub } from '@tests/unit/shared/Stubs/CodeValidatorStub';
|
||||||
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
||||||
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
|
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
|
||||||
@@ -19,18 +21,17 @@ import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithC
|
|||||||
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
||||||
import type { FunctionParameterCollectionFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
|
import type { FunctionParameterCollectionFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
|
||||||
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
|
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
|
||||||
|
import type { FunctionCallsParser } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||||
|
import { createFunctionCallsParserStub } from '@tests/unit/shared/Stubs/FunctionCallsParserStub';
|
||||||
|
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||||
|
import type { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
|
import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
|
||||||
|
|
||||||
describe('SharedFunctionsParser', () => {
|
describe('SharedFunctionsParser', () => {
|
||||||
describe('instance', () => {
|
describe('parseSharedFunctions', () => {
|
||||||
itIsSingletonFactory({
|
|
||||||
getter: () => SharedFunctionsParser.instance,
|
|
||||||
expectedType: SharedFunctionsParser,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('parseFunctions', () => {
|
|
||||||
describe('validates functions', () => {
|
describe('validates functions', () => {
|
||||||
it('throws when functions have no names', () => {
|
it('throws when no name is provided', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const invalidFunctions = [
|
const invalidFunctions = [
|
||||||
createFunctionDataWithCode()
|
createFunctionDataWithCode()
|
||||||
@@ -45,13 +46,13 @@ describe('SharedFunctionsParser', () => {
|
|||||||
];
|
];
|
||||||
const expectedError = `Some function(s) have no names:\n${invalidFunctions.map((f) => JSON.stringify(f)).join('\n')}`;
|
const expectedError = `Some function(s) have no names:\n${invalidFunctions.map((f) => JSON.stringify(f)).join('\n')}`;
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions(invalidFunctions)
|
.withFunctions(invalidFunctions)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('throws when functions have same names', () => {
|
it('throws when functions have duplicate names', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const name = 'same-func-name';
|
const name = 'same-func-name';
|
||||||
const expectedError = `duplicate function name: "${name}"`;
|
const expectedError = `duplicate function name: "${name}"`;
|
||||||
@@ -60,14 +61,14 @@ describe('SharedFunctionsParser', () => {
|
|||||||
createFunctionDataWithCode().withName(name),
|
createFunctionDataWithCode().withName(name),
|
||||||
];
|
];
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions(functions)
|
.withFunctions(functions)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
describe('throws when when function have duplicate code', () => {
|
describe('throws when functions have duplicate code', () => {
|
||||||
it('code', () => {
|
it('throws on code duplication', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const code = 'duplicate-code';
|
const code = 'duplicate-code';
|
||||||
const expectedError = `duplicate "code" in functions: "${code}"`;
|
const expectedError = `duplicate "code" in functions: "${code}"`;
|
||||||
@@ -76,13 +77,13 @@ describe('SharedFunctionsParser', () => {
|
|||||||
createFunctionDataWithoutCallOrCode().withName('func-2').withCode(code),
|
createFunctionDataWithoutCallOrCode().withName('func-2').withCode(code),
|
||||||
];
|
];
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions(functions)
|
.withFunctions(functions)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('revertCode', () => {
|
it('throws on revert code duplication', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const revertCode = 'duplicate-revert-code';
|
const revertCode = 'duplicate-revert-code';
|
||||||
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
||||||
@@ -93,15 +94,15 @@ describe('SharedFunctionsParser', () => {
|
|||||||
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
||||||
];
|
];
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions(functions)
|
.withFunctions(functions)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ensures either call or code is defined', () => {
|
describe('throws when both or neither code and call are defined', () => {
|
||||||
it('both code and call are defined', () => {
|
it('throws when both code and call are defined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const functionName = 'invalid-function';
|
const functionName = 'invalid-function';
|
||||||
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
||||||
@@ -110,45 +111,48 @@ describe('SharedFunctionsParser', () => {
|
|||||||
.withCode('code')
|
.withCode('code')
|
||||||
.withMockCall();
|
.withMockCall();
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions([invalidFunction])
|
.withFunctions([invalidFunction])
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('neither code and call is defined', () => {
|
it('throws when neither code nor call is defined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const functionName = 'invalid-function';
|
const functionName = 'invalid-function';
|
||||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||||
const invalidFunction = createFunctionDataWithoutCallOrCode()
|
const invalidFunction = createFunctionDataWithoutCallOrCode()
|
||||||
.withName(functionName);
|
.withName(functionName);
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions([invalidFunction])
|
.withFunctions([invalidFunction])
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('throws when parameters type is not as expected', () => {
|
describe('throws when parameter types are invalid', () => {
|
||||||
const testScenarios = [
|
const testScenarios: readonly {
|
||||||
|
readonly description: string;
|
||||||
|
readonly invalidType: unknown;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
state: 'when not an array',
|
description: 'parameter is not an array',
|
||||||
invalidType: 5,
|
invalidType: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
state: 'when array but not of objects',
|
description: 'parameter array contains non-objects',
|
||||||
invalidType: ['a', { a: 'b' }],
|
invalidType: ['a', { a: 'b' }],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
for (const testCase of testScenarios) {
|
for (const testCase of testScenarios) {
|
||||||
it(testCase.state, () => {
|
it(testCase.description, () => {
|
||||||
// arrange
|
// arrange
|
||||||
const func = createFunctionDataWithCode()
|
const func = createFunctionDataWithCode()
|
||||||
.withParametersObject(testCase.invalidType as never);
|
.withParametersObject(testCase.invalidType as never);
|
||||||
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||||
// act
|
// act
|
||||||
const act = () => new ParseFunctionsCallerWithDefaults()
|
const act = () => new TestContext()
|
||||||
.withFunctions([func])
|
.withFunctions([func])
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
@@ -164,7 +168,7 @@ describe('SharedFunctionsParser', () => {
|
|||||||
.withRevertCode('expected revert code to be validated');
|
.withRevertCode('expected revert code to be validated');
|
||||||
const validator = new CodeValidatorStub();
|
const validator = new CodeValidatorStub();
|
||||||
// act
|
// act
|
||||||
new ParseFunctionsCallerWithDefaults()
|
new TestContext()
|
||||||
.withFunctions([functionData])
|
.withFunctions([functionData])
|
||||||
.withValidator(validator)
|
.withValidator(validator)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
@@ -190,7 +194,7 @@ describe('SharedFunctionsParser', () => {
|
|||||||
itThrowsContextualError({
|
itThrowsContextualError({
|
||||||
// act
|
// act
|
||||||
throwingAction: (wrapError) => {
|
throwingAction: (wrapError) => {
|
||||||
new ParseFunctionsCallerWithDefaults()
|
new TestContext()
|
||||||
.withFunctions([functionData])
|
.withFunctions([functionData])
|
||||||
.withFunctionParameterFactory(parameterFactory)
|
.withFunctionParameterFactory(parameterFactory)
|
||||||
.withErrorWrapper(wrapError)
|
.withErrorWrapper(wrapError)
|
||||||
@@ -203,10 +207,10 @@ describe('SharedFunctionsParser', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('given empty functions, returns empty collection', () => {
|
describe('handles empty function data', () => {
|
||||||
itEachAbsentCollectionValue<FunctionData>((absentValue) => {
|
itEachAbsentCollectionValue<FunctionData>((absentValue) => {
|
||||||
// act
|
// act
|
||||||
const actual = new ParseFunctionsCallerWithDefaults()
|
const actual = new TestContext()
|
||||||
.withFunctions(absentValue)
|
.withFunctions(absentValue)
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// assert
|
// assert
|
||||||
@@ -226,68 +230,181 @@ describe('SharedFunctionsParser', () => {
|
|||||||
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||||
);
|
);
|
||||||
// act
|
// act
|
||||||
const collection = new ParseFunctionsCallerWithDefaults()
|
const collection = new TestContext()
|
||||||
.withFunctions([expected])
|
.withFunctions([expected])
|
||||||
.parseFunctions();
|
.parseFunctions();
|
||||||
// expect
|
// expect
|
||||||
const actual = collection.getFunctionByName(name);
|
const actual = collection.getFunctionByName(name);
|
||||||
expectEqualName(expected, actual);
|
expectEqualName(expected, actual);
|
||||||
expectEqualParameters(expected, actual);
|
expectEqualParameters(expected.parameters, actual.parameters);
|
||||||
expectEqualFunctionWithInlineCode(expected, actual);
|
expectEqualFunctionWithInlineCode(expected, actual);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('function with calls', () => {
|
describe('function with calls', () => {
|
||||||
it('parses single function with call as expected', () => {
|
describe('parses single function correctly', () => {
|
||||||
// arrange
|
it('parses name correctly', () => {
|
||||||
const call = new FunctionCallDataStub()
|
// arrange
|
||||||
.withName('calleeFunction')
|
const expectedName = 'expected-function-name';
|
||||||
.withParameters({ test: 'value' });
|
const data = createFunctionDataWithCode()
|
||||||
const data = createFunctionDataWithoutCallOrCode()
|
.withName(expectedName);
|
||||||
.withName('caller-function')
|
// act
|
||||||
.withCall(call);
|
const collection = new TestContext()
|
||||||
// act
|
.withFunctions([data])
|
||||||
const collection = new ParseFunctionsCallerWithDefaults()
|
.parseFunctions();
|
||||||
.withFunctions([data])
|
// expect
|
||||||
.parseFunctions();
|
const actual = collection.getFunctionByName(expectedName);
|
||||||
// expect
|
expect(actual.name).to.equal(expectedName);
|
||||||
const actual = collection.getFunctionByName(data.name);
|
expectEqualName(data, actual);
|
||||||
expectEqualName(data, actual);
|
});
|
||||||
expectEqualParameters(data, actual);
|
it('parses parameters correctly', () => {
|
||||||
expectEqualCalls([call], actual);
|
// arrange
|
||||||
|
const functionCallsParserStub = createFunctionCallsParserStub();
|
||||||
|
const expectedParameters: readonly ParameterDefinitionData[] = [
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||||
|
];
|
||||||
|
const data = createFunctionDataWithCode()
|
||||||
|
.withParameters(...expectedParameters);
|
||||||
|
// act
|
||||||
|
const collection = new TestContext()
|
||||||
|
.withFunctions([data])
|
||||||
|
.withFunctionCallsParser(functionCallsParserStub.parser)
|
||||||
|
.parseFunctions();
|
||||||
|
// expect
|
||||||
|
const actual = collection.getFunctionByName(data.name);
|
||||||
|
expectEqualParameters(expectedParameters, actual.parameters);
|
||||||
|
});
|
||||||
|
it('parses call correctly', () => {
|
||||||
|
// arrange
|
||||||
|
const functionCallsParserStub = createFunctionCallsParserStub();
|
||||||
|
const inputCallData = new FunctionCallDataStub()
|
||||||
|
.withName('function-input-call');
|
||||||
|
const data = createFunctionDataWithoutCallOrCode()
|
||||||
|
.withCall(inputCallData);
|
||||||
|
const expectedCall = new FunctionCallStub()
|
||||||
|
.withFunctionName('function-expected-call');
|
||||||
|
functionCallsParserStub.setup(inputCallData, [expectedCall]);
|
||||||
|
// act
|
||||||
|
const collection = new TestContext()
|
||||||
|
.withFunctions([data])
|
||||||
|
.withFunctionCallsParser(functionCallsParserStub.parser)
|
||||||
|
.parseFunctions();
|
||||||
|
// expect
|
||||||
|
const actualFunction = collection.getFunctionByName(data.name);
|
||||||
|
expectEqualFunctionWithCalls([expectedCall], actualFunction);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('parses multiple functions with call as expected', () => {
|
describe('parses multiple functions correctly', () => {
|
||||||
// arrange
|
it('parses names correctly', () => {
|
||||||
const call1 = new FunctionCallDataStub()
|
// arrange
|
||||||
.withName('calleeFunction1')
|
const expectedNames: readonly string[] = [
|
||||||
.withParameters({ param: 'value' });
|
'expected-function-name-1',
|
||||||
const call2 = new FunctionCallDataStub()
|
'expected-function-name-2',
|
||||||
.withName('calleeFunction2')
|
'expected-function-name-3',
|
||||||
.withParameters({ param2: 'value2' });
|
];
|
||||||
const caller1 = createFunctionDataWithoutCallOrCode()
|
const data: readonly FunctionData[] = expectedNames.map(
|
||||||
.withName('caller-function')
|
(functionName) => createFunctionDataWithCall()
|
||||||
.withCall(call1);
|
.withName(functionName),
|
||||||
const caller2 = createFunctionDataWithoutCallOrCode()
|
);
|
||||||
.withName('caller-function-2')
|
// act
|
||||||
.withCall([call1, call2]);
|
const collection = new TestContext()
|
||||||
// act
|
.withFunctions(data)
|
||||||
const collection = new ParseFunctionsCallerWithDefaults()
|
.parseFunctions();
|
||||||
.withFunctions([caller1, caller2])
|
// expect
|
||||||
.parseFunctions();
|
expectedNames.forEach((name, index) => {
|
||||||
// expect
|
const compiledFunction = collection.getFunctionByName(name);
|
||||||
const compiledCaller1 = collection.getFunctionByName(caller1.name);
|
expectEqualName(data[index], compiledFunction);
|
||||||
expectEqualName(caller1, compiledCaller1);
|
});
|
||||||
expectEqualParameters(caller1, compiledCaller1);
|
});
|
||||||
expectEqualCalls([call1], compiledCaller1);
|
it('parses parameters correctly', () => {
|
||||||
const compiledCaller2 = collection.getFunctionByName(caller2.name);
|
// arrange
|
||||||
expectEqualName(caller2, compiledCaller2);
|
const testData: readonly {
|
||||||
expectEqualParameters(caller2, compiledCaller2);
|
readonly functionName: string;
|
||||||
expectEqualCalls([call1, call2], compiledCaller2);
|
readonly inputParameterData: readonly ParameterDefinitionData[];
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
functionName: 'func1',
|
||||||
|
inputParameterData: [
|
||||||
|
new ParameterDefinitionDataStub().withName('func1-first-parameter'),
|
||||||
|
new ParameterDefinitionDataStub().withName('func1-second-parameter'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
functionName: 'func2',
|
||||||
|
inputParameterData: [
|
||||||
|
new ParameterDefinitionDataStub().withName('func2-optional-parameter').withOptionality(true),
|
||||||
|
new ParameterDefinitionDataStub().withName('func2-required-parameter').withOptionality(false),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const data: readonly FunctionData[] = testData.map(
|
||||||
|
(d) => createFunctionDataWithCall()
|
||||||
|
.withName(d.functionName)
|
||||||
|
.withParameters(...d.inputParameterData),
|
||||||
|
);
|
||||||
|
// act
|
||||||
|
const collection = new TestContext()
|
||||||
|
.withFunctions(data)
|
||||||
|
.parseFunctions();
|
||||||
|
// expect
|
||||||
|
testData.forEach(({ functionName, inputParameterData }) => {
|
||||||
|
const actualFunction = collection.getFunctionByName(functionName);
|
||||||
|
expectEqualParameters(inputParameterData, actualFunction.parameters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('parses call correctly', () => {
|
||||||
|
// arrange
|
||||||
|
const functionCallsParserStub = createFunctionCallsParserStub();
|
||||||
|
const callData: readonly {
|
||||||
|
readonly functionName: string;
|
||||||
|
readonly inputData: FunctionCallsData,
|
||||||
|
readonly expectedCalls: ReturnType<FunctionCallsParser>,
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
functionName: 'function-1',
|
||||||
|
inputData: new FunctionCallDataStub().withName('function-1-input-function-call'),
|
||||||
|
expectedCalls: [
|
||||||
|
new FunctionCallStub().withFunctionName('function-1-compiled-function-call'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
functionName: 'function-2',
|
||||||
|
inputData: [
|
||||||
|
new FunctionCallDataStub().withName('function-2-input-function-call-1'),
|
||||||
|
new FunctionCallDataStub().withName('function-2-input-function-call-2'),
|
||||||
|
],
|
||||||
|
expectedCalls: [
|
||||||
|
new FunctionCallStub().withFunctionName('function-2-compiled-function-call-1'),
|
||||||
|
new FunctionCallStub().withFunctionName('function-2-compiled-function-call-2'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const data: readonly FunctionData[] = callData.map(
|
||||||
|
({ functionName, inputData }) => createFunctionDataWithoutCallOrCode()
|
||||||
|
.withName(functionName)
|
||||||
|
.withCall(inputData),
|
||||||
|
);
|
||||||
|
callData.forEach(({
|
||||||
|
inputData,
|
||||||
|
expectedCalls,
|
||||||
|
}) => functionCallsParserStub.setup(inputData, expectedCalls));
|
||||||
|
// act
|
||||||
|
const collection = new TestContext()
|
||||||
|
.withFunctions(data)
|
||||||
|
.withFunctionCallsParser(functionCallsParserStub.parser)
|
||||||
|
.parseFunctions();
|
||||||
|
// expect
|
||||||
|
callData.forEach(({ functionName, expectedCalls }) => {
|
||||||
|
const actualFunction = collection.getFunctionByName(functionName);
|
||||||
|
expectEqualFunctionWithCalls(expectedCalls, actualFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class ParseFunctionsCallerWithDefaults {
|
class TestContext {
|
||||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||||
|
|
||||||
private codeValidator: ICodeValidator = new CodeValidatorStub();
|
private codeValidator: ICodeValidator = new CodeValidatorStub();
|
||||||
@@ -296,6 +413,8 @@ class ParseFunctionsCallerWithDefaults {
|
|||||||
|
|
||||||
private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
|
private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
|
||||||
|
|
||||||
|
private functionCallsParser: FunctionCallsParser = createFunctionCallsParserStub().parser;
|
||||||
|
|
||||||
private parameterFactory: FunctionParameterFactory = (
|
private parameterFactory: FunctionParameterFactory = (
|
||||||
name: string,
|
name: string,
|
||||||
isOptional: boolean,
|
isOptional: boolean,
|
||||||
@@ -306,17 +425,22 @@ class ParseFunctionsCallerWithDefaults {
|
|||||||
private parameterCollectionFactory
|
private parameterCollectionFactory
|
||||||
: FunctionParameterCollectionFactory = () => new FunctionParameterCollectionStub();
|
: FunctionParameterCollectionFactory = () => new FunctionParameterCollectionStub();
|
||||||
|
|
||||||
public withSyntax(syntax: ILanguageSyntax) {
|
public withSyntax(syntax: ILanguageSyntax): this {
|
||||||
this.syntax = syntax;
|
this.syntax = syntax;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withValidator(codeValidator: ICodeValidator) {
|
public withValidator(codeValidator: ICodeValidator): this {
|
||||||
this.codeValidator = codeValidator;
|
this.codeValidator = codeValidator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withFunctions(functions: readonly FunctionData[]) {
|
public withFunctionCallsParser(functionCallsParser: FunctionCallsParser): this {
|
||||||
|
this.functionCallsParser = functionCallsParser;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withFunctions(functions: readonly FunctionData[]): this {
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -338,16 +462,18 @@ class ParseFunctionsCallerWithDefaults {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseFunctions() {
|
public parseFunctions(): ReturnType<typeof parseSharedFunctions> {
|
||||||
const sut = new SharedFunctionsParser(
|
return parseSharedFunctions(
|
||||||
|
this.functions,
|
||||||
|
this.syntax,
|
||||||
{
|
{
|
||||||
codeValidator: this.codeValidator,
|
codeValidator: this.codeValidator,
|
||||||
wrapError: this.wrapError,
|
wrapError: this.wrapError,
|
||||||
createParameter: this.parameterFactory,
|
createParameter: this.parameterFactory,
|
||||||
createParameterCollection: this.parameterCollectionFactory,
|
createParameterCollection: this.parameterCollectionFactory,
|
||||||
|
parseFunctionCalls: this.functionCallsParser,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return sut.parseFunctions(this.functions, this.syntax);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,12 +481,15 @@ function expectEqualName(expected: FunctionData, actual: ISharedFunction): void
|
|||||||
expect(actual.name).to.equal(expected.name);
|
expect(actual.name).to.equal(expected.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectEqualParameters(expected: FunctionData, actual: ISharedFunction): void {
|
function expectEqualParameters(
|
||||||
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
|
expected: readonly ParameterDefinitionData[] | undefined,
|
||||||
|
actual: IReadOnlyFunctionParameterCollection,
|
||||||
|
): void {
|
||||||
|
const actualSimplifiedParameters = actual.all.map((parameter) => ({
|
||||||
name: parameter.name,
|
name: parameter.name,
|
||||||
optional: parameter.isOptional,
|
optional: parameter.isOptional,
|
||||||
}));
|
}));
|
||||||
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
|
const expectedSimplifiedParameters = expected?.map((parameter) => ({
|
||||||
name: parameter.name,
|
name: parameter.name,
|
||||||
optional: parameter.optional || false,
|
optional: parameter.optional || false,
|
||||||
})) || [];
|
})) || [];
|
||||||
@@ -371,33 +500,18 @@ function expectEqualFunctionWithInlineCode(
|
|||||||
expected: CodeInstruction,
|
expected: CodeInstruction,
|
||||||
actual: ISharedFunction,
|
actual: ISharedFunction,
|
||||||
): void {
|
): void {
|
||||||
expect(actual.body, `function "${actual.name}" has no body`);
|
|
||||||
expectCodeFunctionBody(actual.body);
|
expectCodeFunctionBody(actual.body);
|
||||||
expect(actual.body.code, `function "${actual.name}" has no code`);
|
expect(actual.body.code, `function "${actual.name}" has no code`);
|
||||||
expect(actual.body.code.execute).to.equal(expected.code);
|
expect(actual.body.code.execute).to.equal(expected.code);
|
||||||
expect(actual.body.code.revert).to.equal(expected.revertCode);
|
expect(actual.body.code.revert).to.equal(expected.revertCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectEqualCalls(
|
function expectEqualFunctionWithCalls(
|
||||||
expected: FunctionCallDataStub[],
|
expectedCalls: readonly FunctionCall[],
|
||||||
actual: ISharedFunction,
|
actualFunction: ISharedFunction,
|
||||||
) {
|
): void {
|
||||||
expect(actual.body, `function "${actual.name}" has no body`);
|
expectCallsFunctionBody(actualFunction.body);
|
||||||
expectCallsFunctionBody(actual.body);
|
const actualCalls = actualFunction.body.calls;
|
||||||
expect(actual.body.calls, `function "${actual.name}" has no calls`);
|
expect(actualCalls.length).to.equal(expectedCalls.length);
|
||||||
const actualSimplifiedCalls = actual.body.calls
|
expect(actualCalls).to.have.members(expectedCalls);
|
||||||
.map((call) => ({
|
|
||||||
function: call.functionName,
|
|
||||||
params: call.args.getAllParameterNames().map((name) => ({
|
|
||||||
name, value: call.args.getArgument(name).argumentValue,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
const expectedSimplifiedCalls = expected
|
|
||||||
.map((call) => ({
|
|
||||||
function: call.function,
|
|
||||||
params: Object.keys(call.parameters).map((key) => (
|
|
||||||
{ name: key, value: call.parameters[key] }
|
|
||||||
)),
|
|
||||||
}));
|
|
||||||
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import type { FunctionData } from '@/application/collections/';
|
import type { FunctionData } from '@/application/collections/';
|
||||||
import { ScriptCompiler } from '@/application/Parser/Executable/Script/Compiler/ScriptCompiler';
|
import { ScriptCompiler } from '@/application/Parser/Executable/Script/Compiler/ScriptCompiler';
|
||||||
import type { ISharedFunctionsParser } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionsParser';
|
|
||||||
import type { FunctionCallCompiler } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
|
import type { FunctionCallCompiler } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
import { LanguageSyntaxStub } from '@tests/unit/shared/Stubs/LanguageSyntaxStub';
|
||||||
import { createFunctionDataWithCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
import { createFunctionDataWithCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
||||||
import { FunctionCallCompilerStub } from '@tests/unit/shared/Stubs/FunctionCallCompilerStub';
|
import { FunctionCallCompilerStub } from '@tests/unit/shared/Stubs/FunctionCallCompilerStub';
|
||||||
import { SharedFunctionsParserStub } from '@tests/unit/shared/Stubs/SharedFunctionsParserStub';
|
import { createSharedFunctionsParserStub } from '@tests/unit/shared/Stubs/SharedFunctionsParserStub';
|
||||||
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFunctionCollectionStub';
|
||||||
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallParser';
|
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||||
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
||||||
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
import type { ICodeValidator } from '@/application/Parser/Executable/Script/Validation/ICodeValidator';
|
||||||
@@ -22,6 +21,7 @@ import { ScriptCodeStub } from '@tests/unit/shared/Stubs/ScriptCodeStub';
|
|||||||
import type { ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
import type { ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
||||||
import { createScriptCodeFactoryStub } from '@tests/unit/shared/Stubs/ScriptCodeFactoryStub';
|
import { createScriptCodeFactoryStub } from '@tests/unit/shared/Stubs/ScriptCodeFactoryStub';
|
||||||
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
||||||
|
import type { SharedFunctionsParser } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
||||||
|
|
||||||
describe('ScriptCompiler', () => {
|
describe('ScriptCompiler', () => {
|
||||||
describe('canCompile', () => {
|
describe('canCompile', () => {
|
||||||
@@ -90,7 +90,7 @@ describe('ScriptCompiler', () => {
|
|||||||
const script = createScriptDataWithCall(call);
|
const script = createScriptDataWithCall(call);
|
||||||
const functions = [createFunctionDataWithCode().withName('existing-func')];
|
const functions = [createFunctionDataWithCode().withName('existing-func')];
|
||||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||||
const functionParserMock = new SharedFunctionsParserStub();
|
const functionParserMock = createSharedFunctionsParserStub();
|
||||||
functionParserMock.setup(functions, compiledFunctions);
|
functionParserMock.setup(functions, compiledFunctions);
|
||||||
const callCompilerMock = new FunctionCallCompilerStub();
|
const callCompilerMock = new FunctionCallCompilerStub();
|
||||||
callCompilerMock.setup(
|
callCompilerMock.setup(
|
||||||
@@ -102,7 +102,7 @@ describe('ScriptCompiler', () => {
|
|||||||
);
|
);
|
||||||
const sut = new ScriptCompilerBuilder()
|
const sut = new ScriptCompilerBuilder()
|
||||||
.withFunctions(...functions)
|
.withFunctions(...functions)
|
||||||
.withSharedFunctionsParser(functionParserMock)
|
.withSharedFunctionsParser(functionParserMock.parser)
|
||||||
.withFunctionCallCompiler(callCompilerMock)
|
.withFunctionCallCompiler(callCompilerMock)
|
||||||
.withScriptCodeFactory(scriptCodeFactory)
|
.withScriptCodeFactory(scriptCodeFactory)
|
||||||
.build();
|
.build();
|
||||||
@@ -118,33 +118,35 @@ describe('ScriptCompiler', () => {
|
|||||||
it('parses functions with expected syntax', () => {
|
it('parses functions with expected syntax', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected: ILanguageSyntax = new LanguageSyntaxStub();
|
const expected: ILanguageSyntax = new LanguageSyntaxStub();
|
||||||
const parser = new SharedFunctionsParserStub();
|
const functionParserMock = createSharedFunctionsParserStub();
|
||||||
const sut = new ScriptCompilerBuilder()
|
const sut = new ScriptCompilerBuilder()
|
||||||
.withSomeFunctions()
|
.withSomeFunctions()
|
||||||
.withSyntax(expected)
|
.withSyntax(expected)
|
||||||
.withSharedFunctionsParser(parser)
|
.withSharedFunctionsParser(functionParserMock.parser)
|
||||||
.build();
|
.build();
|
||||||
const scriptData = createScriptDataWithCall();
|
const scriptData = createScriptDataWithCall();
|
||||||
// act
|
// act
|
||||||
sut.compile(scriptData);
|
sut.compile(scriptData);
|
||||||
// assert
|
// assert
|
||||||
expect(parser.callHistory.length).to.equal(1);
|
const parserCalls = functionParserMock.callHistory;
|
||||||
expect(parser.callHistory[0].syntax).to.equal(expected);
|
expect(parserCalls.length).to.equal(1);
|
||||||
|
expect(parserCalls[0].syntax).to.equal(expected);
|
||||||
});
|
});
|
||||||
it('parses given functions', () => {
|
it('parses given functions', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedFunctions = [createFunctionDataWithCode().withName('existing-func')];
|
const expectedFunctions = [createFunctionDataWithCode().withName('existing-func')];
|
||||||
const parser = new SharedFunctionsParserStub();
|
const functionParserMock = createSharedFunctionsParserStub();
|
||||||
const sut = new ScriptCompilerBuilder()
|
const sut = new ScriptCompilerBuilder()
|
||||||
.withFunctions(...expectedFunctions)
|
.withFunctions(...expectedFunctions)
|
||||||
.withSharedFunctionsParser(parser)
|
.withSharedFunctionsParser(functionParserMock.parser)
|
||||||
.build();
|
.build();
|
||||||
const scriptData = createScriptDataWithCall();
|
const scriptData = createScriptDataWithCall();
|
||||||
// act
|
// act
|
||||||
sut.compile(scriptData);
|
sut.compile(scriptData);
|
||||||
// assert
|
// assert
|
||||||
expect(parser.callHistory.length).to.equal(1);
|
const parserCalls = functionParserMock.callHistory;
|
||||||
expect(parser.callHistory[0].functions).to.deep.equal(expectedFunctions);
|
expect(parserCalls.length).to.equal(1);
|
||||||
|
expect(parserCalls[0].functions).to.deep.equal(expectedFunctions);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('rethrows error with script name', () => {
|
describe('rethrows error with script name', () => {
|
||||||
@@ -243,7 +245,7 @@ class ScriptCompilerBuilder {
|
|||||||
|
|
||||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||||
|
|
||||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
private sharedFunctionsParser: SharedFunctionsParser = createSharedFunctionsParserStub().parser;
|
||||||
|
|
||||||
private callCompiler: FunctionCallCompiler = new FunctionCallCompilerStub();
|
private callCompiler: FunctionCallCompiler = new FunctionCallCompilerStub();
|
||||||
|
|
||||||
@@ -281,7 +283,7 @@ class ScriptCompilerBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public withSharedFunctionsParser(
|
public withSharedFunctionsParser(
|
||||||
sharedFunctionsParser: ISharedFunctionsParser,
|
sharedFunctionsParser: SharedFunctionsParser,
|
||||||
): this {
|
): this {
|
||||||
this.sharedFunctionsParser = sharedFunctionsParser;
|
this.sharedFunctionsParser = sharedFunctionsParser;
|
||||||
return this;
|
return this;
|
||||||
@@ -314,13 +316,17 @@ class ScriptCompilerBuilder {
|
|||||||
throw new Error('Function behavior not defined');
|
throw new Error('Function behavior not defined');
|
||||||
}
|
}
|
||||||
return new ScriptCompiler(
|
return new ScriptCompiler(
|
||||||
this.functions,
|
{
|
||||||
this.syntax,
|
functions: this.functions,
|
||||||
this.sharedFunctionsParser,
|
syntax: this.syntax,
|
||||||
this.callCompiler,
|
},
|
||||||
this.codeValidator,
|
{
|
||||||
this.wrapError,
|
sharedFunctionsParser: this.sharedFunctionsParser,
|
||||||
this.scriptCodeFactory,
|
callCompiler: this.callCompiler,
|
||||||
|
codeValidator: this.codeValidator,
|
||||||
|
wrapError: this.wrapError,
|
||||||
|
scriptCodeFactory: this.scriptCodeFactory,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
tests/unit/shared/Stubs/FunctionCallsParserStub.ts
Normal file
30
tests/unit/shared/Stubs/FunctionCallsParserStub.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import type { FunctionCallsParser } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||||
|
import type { FunctionCallsData } from '@/application/collections/';
|
||||||
|
import { FunctionCallStub } from './FunctionCallStub';
|
||||||
|
|
||||||
|
export function createFunctionCallsParserStub() {
|
||||||
|
const setupResults = new Map<FunctionCallsData, FunctionCall[]>();
|
||||||
|
const parser: FunctionCallsParser = (rawData) => {
|
||||||
|
if (setupResults.size === 0) {
|
||||||
|
return [
|
||||||
|
new FunctionCallStub().withFunctionName('function created by parser stub'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const setupResult = setupResults.get(rawData);
|
||||||
|
if (setupResult === undefined) {
|
||||||
|
throw new Error(`Stub error: Expected pre-configured input data was not found.\n
|
||||||
|
Received input: ${JSON.stringify(rawData, null, 2)}\n
|
||||||
|
Number of configurations available: ${setupResults.size}\n
|
||||||
|
Available configurations: ${JSON.stringify([...setupResults.keys()].map((key) => JSON.stringify(key, null, 2)), null, 2)}`);
|
||||||
|
}
|
||||||
|
return setupResult;
|
||||||
|
};
|
||||||
|
const setup = (rawData: FunctionCallsData, parsedData: FunctionCall[]) => {
|
||||||
|
setupResults.set(rawData, parsedData);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
parser,
|
||||||
|
setup,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,40 +1,51 @@
|
|||||||
import type { FunctionData } from '@/application/collections/';
|
import type { FunctionData } from '@/application/collections/';
|
||||||
import { sequenceEqual } from '@/application/Common/Array';
|
import { sequenceEqual } from '@/application/Common/Array';
|
||||||
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
|
import type { ISharedFunctionCollection } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import type { ISharedFunctionsParser } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunctionsParser';
|
import type { SharedFunctionsParser } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
||||||
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
import type { ILanguageSyntax } from '@/application/Parser/Executable/Script/Validation/Syntax/ILanguageSyntax';
|
||||||
import { SharedFunctionCollectionStub } from './SharedFunctionCollectionStub';
|
import { SharedFunctionCollectionStub } from './SharedFunctionCollectionStub';
|
||||||
|
|
||||||
export class SharedFunctionsParserStub implements ISharedFunctionsParser {
|
export function createSharedFunctionsParserStub() {
|
||||||
public callHistory = new Array<{
|
const callHistory = new Array<{
|
||||||
functions: readonly FunctionData[],
|
readonly functions: readonly FunctionData[],
|
||||||
syntax: ILanguageSyntax,
|
readonly syntax: ILanguageSyntax,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
private setupResults = new Array<{
|
const setupResults = new Array<{
|
||||||
functions: readonly FunctionData[],
|
readonly functions: readonly FunctionData[],
|
||||||
result: ISharedFunctionCollection,
|
readonly result: ISharedFunctionCollection,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
public setup(functions: readonly FunctionData[], result: ISharedFunctionCollection) {
|
const findResult = (
|
||||||
this.setupResults.push({ functions, result });
|
functions: readonly FunctionData[],
|
||||||
}
|
): ISharedFunctionCollection | undefined => {
|
||||||
|
return setupResults
|
||||||
|
.find((result) => sequenceEqual(result.functions, functions))
|
||||||
|
?.result;
|
||||||
|
};
|
||||||
|
|
||||||
public parseFunctions(
|
const parser: SharedFunctionsParser = (
|
||||||
functions: readonly FunctionData[],
|
functions: readonly FunctionData[],
|
||||||
syntax: ILanguageSyntax,
|
syntax: ILanguageSyntax,
|
||||||
): ISharedFunctionCollection {
|
) => {
|
||||||
this.callHistory.push({
|
callHistory.push({
|
||||||
functions: Array.from(functions),
|
functions: Array.from(functions),
|
||||||
syntax,
|
syntax,
|
||||||
});
|
});
|
||||||
const result = this.findResult(functions);
|
const result = findResult(functions);
|
||||||
return result || new SharedFunctionCollectionStub();
|
return result || new SharedFunctionCollectionStub();
|
||||||
}
|
};
|
||||||
|
|
||||||
private findResult(functions: readonly FunctionData[]): ISharedFunctionCollection | undefined {
|
const setup = (
|
||||||
return this.setupResults
|
functions: readonly FunctionData[],
|
||||||
.find((result) => sequenceEqual(result.functions, functions))
|
result: ISharedFunctionCollection,
|
||||||
?.result;
|
) => {
|
||||||
}
|
setupResults.push({ functions, result });
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
parser,
|
||||||
|
setup,
|
||||||
|
callHistory,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user